diff --git a/.vpython3 b/.vpython3
index 76fc87e..a701358 100644
--- a/.vpython3
+++ b/.vpython3
@@ -531,8 +531,16 @@
 >
 
 # Used by:
+#   tools/perf/crossbench
+wheel: <
+  name: "infra/python/wheels/hjson-py2_py3"
+  version: "version:3.1.0"
+>
+
+# Used by:
 #   chrome/test/mini_installer/test_chrome_with_chromedriver.py
 #   testing/script/run_variations_smoke_tests.py
+#   tools/perf/crossbench
 # Keep this to the same revision as third_party/webdriver/pylib, which is pulled
 # in via DEPS.
 wheel: <
diff --git a/DEPS b/DEPS
index bfc665b..96d9f2b 100644
--- a/DEPS
+++ b/DEPS
@@ -299,7 +299,7 @@
   # 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': '59dcffca90618578a6f9522dbad7c500a98884a1',
+  'skia_revision': 'e08e5c9058a09e1ba7a971b70d46abf566c65be0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -326,7 +326,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:11.20230129.1.1',
+  'fuchsia_version': 'version:11.20230129.3.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -372,6 +372,10 @@
   # and whatever else without interference from each other.
   'catapult_revision': '35d06490ad8ba91feb48301edd8f842046ce7fd9',
   # Three lines of non-changing comments so that
+  # the commit queue can handle CLs rolling CrossBench
+  # and whatever else without interference from each other.
+  'crossbench_revision': '146c4da6088475e6e34aa4f3c30150d53f2c15a9',
+  # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
   'libfuzzer_revision': 'debe7d2d1982e540fbd6bd78604bf001753f9e74',
@@ -414,11 +418,11 @@
   # 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': 'e8168a177ab220bbb4be23e1e78afe72730b1811',
+  'dawn_revision': 'a12ff9abd17832664dc64f0a31dae08d0aa09a7e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '18631e198ebd9f3c4984b76dcd52258b5ddf4a93',
+  'quiche_revision': '1fdac9bdbf85d27537e8ce47ca4f1eaa034f1ff5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -752,7 +756,7 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    'a2c86438eb101876f70bacba5adec8b2c3716b19',
+    'a3955fc5f2aa8351f6c539ee20fd110f1a61e64c',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -941,7 +945,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'Aw6kA-NpPjzWNknKBiY0EyY5bNO-dUuS9QdZHdjLzX8C',
+          'version': 'Hdb7ZPqGV3lLyY7geGwmoelVab7mxM0oA0jtglEVp2MC',
       },
     ],
     'condition': 'checkout_android',
@@ -1180,6 +1184,10 @@
       'condition': 'checkout_linux',
   },
 
+  'src/third_party/crossbench':
+    Var('chromium_git') + '/crossbench.git' + '@' + Var('crossbench_revision'),
+
+
   'src/third_party/crubit/src': {
       'url': Var('chromium_git') + '/external/github.com/google/crubit.git' + '@' + Var('crubit_revision'),
       'condition': 'checkout_crubit',
@@ -1650,7 +1658,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '45275bed1173d8a0cd0995560ca2882eb7d45e19',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '81c39bac7b68480ad61e2e71d41625acd1d729cc',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1690,7 +1698,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/r8',
-              'version': 'l0C1ThOaE6rumuAbPi3UJ4mbRYtfEz2MYsw8oNHsehYC',
+              'version': 'kTwoRbYJ0cNEX_B1XARsNkSFKf4bOHgQCEiP4afsmBUC',
           },
       ],
       'condition': 'checkout_android',
@@ -1788,14 +1796,14 @@
       'packages': [
           {
               'package': 'chromium/third_party/turbine',
-              'version': 't0TeGgk2CZr3B0HtEqBb60BSRwVPRJm9066izhJwzz0C',
+              'version': 'YQC-btuan_DTe9V9dv_e1LxgYSWeOoDfrd-VSqzIvHkC',
           },
       ],
       'condition': 'checkout_android',
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@9988b37a33510f8ed6975544a3fb4228f1ca879b',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@be674ea25e15b319352b8b908604efa77c2a9edb',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1835,7 +1843,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '7324baba2b12a71cecc155d72680478ec75b6591',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'dc7a14bb669eb770908db0748995d9238b705235',
+    Var('webrtc_git') + '/src.git' + '@' + '68564bb59cf3792ea9009be0e124e6c36d4488dc',
 
   # 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.
@@ -1905,7 +1913,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d19a58e3d0df3ee6b2e2889b7779ba4cf44c2e83',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@f120cb807531a0513a94ab14e64c4a426126d290',
     'condition': 'checkout_src_internal',
   },
 
@@ -1946,7 +1954,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'zGZ3VTATJuOZ71PnzXR3M-WDFHg7Dx16r7L4hF_CO68C',
+        'version': '-RdtXluBu-dcSG5Gw1K8IuC_0VZgiXRqD01UX27egB8C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1957,7 +1965,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'aBpoCHEPfMKLdl7Tez28r4Vk4n52JH2cSQVC-VJ4Mc0C',
+        'version': 'lnCJuzNKn7hl8VCKC7RD6UfB1nt-ADxMvaQlx6rzmxsC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/browser/aw_autofill_client.cc b/android_webview/browser/aw_autofill_client.cc
index 99c0881..c58feb2 100644
--- a/android_webview/browser/aw_autofill_client.cc
+++ b/android_webview/browser/aw_autofill_client.cc
@@ -213,7 +213,7 @@
 bool AwAutofillClient::TryToShowFastCheckout(
     const autofill::FormData& form,
     const autofill::FormFieldData& field,
-    autofill::AutofillDriver* driver) {
+    base::WeakPtr<autofill::AutofillManager> autofill_manager) {
   return false;
 }
 
diff --git a/android_webview/browser/aw_autofill_client.h b/android_webview/browser/aw_autofill_client.h
index 1169ae62..a6a9c68 100644
--- a/android_webview/browser/aw_autofill_client.h
+++ b/android_webview/browser/aw_autofill_client.h
@@ -115,9 +115,10 @@
       AddressProfileSavePromptCallback callback) override;
   bool HasCreditCardScanFeature() override;
   void ScanCreditCard(CreditCardScanCallback callback) override;
-  bool TryToShowFastCheckout(const autofill::FormData& form,
-                             const autofill::FormFieldData& field,
-                             autofill::AutofillDriver* driver) override;
+  bool TryToShowFastCheckout(
+      const autofill::FormData& form,
+      const autofill::FormFieldData& field,
+      base::WeakPtr<autofill::AutofillManager> autofill_manager) override;
   void HideFastCheckout(bool allow_further_runs) override;
   bool IsFastCheckoutSupported() override;
   bool IsShowingFastCheckoutUI() override;
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
index 704a5f50..235bfd4f 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
@@ -44,6 +44,7 @@
     // larger in future. However, we don't want it too large as that will make the tests slower and
     // require more memory.
     private static final long REASONABLE_HEAP_SIZE = 100 * 1024 * 1024;
+    private static final int LARGE_NAMED_DATA_SIZE = 2 * 1024 * 1024;
 
     @Test
     @MediumTest
@@ -801,4 +802,343 @@
             }
         }
     }
+
+    @Test
+    @LargeTest
+    public void testArrayBufferSizeEnforced() throws Throwable {
+        final long maxHeapSize = REASONABLE_HEAP_SIZE;
+        // V8 cannot sparsely allocate array buffers, so no fill required.
+        final String oomingCode = ""
+                + "const bigArray = new Float32Array(new ArrayBuffer(" + (maxHeapSize + 1) + "));"
+                + "'Unreachable'";
+        final String stableCode = ""
+                + "const smallArray = new Float32Array(new ArrayBuffer(100));"
+                + "'PASS'";
+        final String stableExpected = "PASS";
+        Context context = ContextUtils.getApplicationContext();
+        ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
+            IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
+            isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
+                // Check that unserviceable large allocations fail.
+                ListenableFuture<String> resultFuture1 =
+                        jsIsolate.evaluateJavaScriptAsync(oomingCode);
+                try {
+                    resultFuture1.get(5, TimeUnit.SECONDS);
+                    Assert.fail("Should have thrown.");
+                } catch (ExecutionException e) {
+                    if (!(e.getCause() instanceof EvaluationFailedException)) {
+                        throw e;
+                    }
+                    EvaluationFailedException cause = (EvaluationFailedException) e.getCause();
+                    if (!cause.getMessage().startsWith(
+                                "Uncaught RangeError: Array buffer allocation failed")) {
+                        throw e;
+                    }
+                }
+
+                // Check that the same isolate can be used to perform a smaller allocation.
+                ListenableFuture<String> resultFuture2 =
+                        jsIsolate.evaluateJavaScriptAsync(stableCode);
+                String result = resultFuture2.get(5, TimeUnit.SECONDS);
+                Assert.assertEquals(stableExpected, result);
+            }
+        }
+    }
+
+    @Test
+    @LargeTest
+    public void testGarbageCollection() throws Throwable {
+        final long maxHeapSize = REASONABLE_HEAP_SIZE;
+        Context context = ContextUtils.getApplicationContext();
+        ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
+            IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
+            isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
+            final long num_doubles = 1024 * 1024;
+            // There may be additional allocation overhead beyond this value.
+            final long allocation_size = 8 * num_doubles;
+            final long memoryUseFactor = 2;
+            final long allocationsToTry = memoryUseFactor * maxHeapSize / allocation_size;
+            // This test will exercise both the V8 heap and ArrayBuffer-allocated memory. Each will
+            // have allocations totalling approximately memoryUseFactor times the available memory.
+            //
+            // Note that the configured heap limit is not precisely enforced by V8, so we need to go
+            // comfortably over our specified limit (and not just by an extra allocation).
+            //
+            // We use doubles (rather than bytes) to reduce (heap) Array overheads.
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
+                final String code = ""
+                        + "this.arrayLength = " + num_doubles + ";"
+                        + "this.obj = {"
+                        + " array: Array(this.arrayLength).fill(Math.random(), 0),"
+                        + " arraybuffer: new Float64Array(new ArrayBuffer(8 * this.arrayLength)),"
+                        + "};"
+                        + "'PASS'";
+                final String expected = "PASS";
+                for (int i = 0; i < allocationsToTry; i++) {
+                    ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
+                    // Execution time will be unstable when GC kicks in, so go with 60 seconds.
+                    String result = resultFuture.get(60, TimeUnit.SECONDS);
+                    Assert.assertEquals(expected, result);
+                }
+            }
+        }
+    }
+
+    @Test
+    @LargeTest
+    public void testNamedDataCanBeFreed() throws Throwable {
+        final long maxHeapSize = REASONABLE_HEAP_SIZE;
+        Context context = ContextUtils.getApplicationContext();
+        ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
+            Assume.assumeTrue(
+                    jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
+            IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
+            isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
+            // There will be named data allocations of approximately memoryUseFactor times the
+            // available memory. Note that the memory usage in the Java side is constant with
+            // respect to number of allocations as we reuse the same input bytes.
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
+                final byte[] bytes = new byte[LARGE_NAMED_DATA_SIZE];
+                final long memoryUseFactor = 2;
+                final long allocationsToTry = memoryUseFactor * maxHeapSize / LARGE_NAMED_DATA_SIZE;
+                for (int i = 0; i < allocationsToTry; i++) {
+                    boolean provideNamedDataReturn = jsIsolate.provideNamedData("id-" + i, bytes);
+                    Assert.assertTrue(provideNamedDataReturn);
+                    final String code = ""
+                            + "android.consumeNamedDataAsArrayBuffer('id-' + " + i + ")"
+                            + " .then((arrayBuffer) => {"
+                            + "  const len = arrayBuffer.byteLength;"
+                            + "  if (len != " + LARGE_NAMED_DATA_SIZE + ") {"
+                            + "   throw new Error('Bad length ' + len);"
+                            + "  }"
+                            + "  return 'PASS';"
+                            + " })";
+                    final String expected = "PASS";
+                    ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
+                    // Execution time may be unstable if the GC kicks in, so go with 60 seconds.
+                    String result = resultFuture.get(60, TimeUnit.SECONDS);
+                    Assert.assertEquals(expected, result);
+                }
+            }
+        }
+    }
+
+    @Test
+    @LargeTest
+    public void testNamedDataCanTriggerGarbageCollection() throws Throwable {
+        // Array buffers for named data are created differently to ordinary array buffers (see
+        // native service code android_webview::JsSandboxIsolate::tryAllocateArrayBuffer). The
+        // special allocation code needs to run the garbage collector if we've run out of external
+        // memory budget. We test this by using up the budget with array buffers (such that there
+        // would not be enough space to consume the named data), and then forget about (turn into
+        // garbage) enough buffer memory for the allocation to succeed. This means that when we run
+        // our own allocation code, there is only enough memory to proceed after a garbage
+        // collection (but not without one).
+        final long maxHeapSize = REASONABLE_HEAP_SIZE;
+        final byte[] bytes = new byte[LARGE_NAMED_DATA_SIZE];
+        final long allocationsToTry = (maxHeapSize / LARGE_NAMED_DATA_SIZE) + 1;
+        final String code = ""
+                + "const allocation_size = " + LARGE_NAMED_DATA_SIZE + ";"
+                + "this.array_buffers = new Array(" + allocationsToTry + ");"
+                + "let i;"
+                + "for (i = 0; i < this.array_buffers.length; i++) {"
+                + " try {"
+                + "  this.array_buffers[i] = new ArrayBuffer(allocation_size);"
+                + " } catch (e) {"
+                + "  if (e instanceof RangeError) {"
+                + "   break;"
+                + "  }"
+                + " }"
+                + "}"
+                + "if (i == this.array_buffers.length) {"
+                + " throw new Error('Expected to run out of memory, but did not');"
+                + "} else if (i == 0) {"
+                + " throw new Error('Could not achieve at least one allocation');"
+                + "}"
+                + "this.array_buffers[0] = null;"
+                + "android.consumeNamedDataAsArrayBuffer('test')"
+                + " .then((arrayBuffer) => {"
+                + "  const len = arrayBuffer.byteLength;"
+                + "  if (len != " + LARGE_NAMED_DATA_SIZE + ") {"
+                + "   throw new Error('Bad length ' + len);"
+                + "  }"
+                + "  return 'PASS';"
+                + " })";
+        final String expected = "PASS";
+        Context context = ContextUtils.getApplicationContext();
+        ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
+            Assume.assumeTrue(
+                    jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
+            IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
+            isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
+                boolean provideNamedDataReturn = jsIsolate.provideNamedData("test", bytes);
+                Assert.assertTrue(provideNamedDataReturn);
+                ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
+                // Execution time may be unstable if the GC kicks in, so go with 60 seconds.
+                String result = resultFuture.get(60, TimeUnit.SECONDS);
+                Assert.assertEquals(expected, result);
+            }
+        }
+    }
+
+    @Test
+    @LargeTest
+    public void testArrayBuffersAllocatedInPages() throws Throwable {
+        final long maxHeapSize = REASONABLE_HEAP_SIZE;
+        Context context = ContextUtils.getApplicationContext();
+        ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
+            IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
+            isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
+            // The service code should assume that small allocations have overhead and will cost at
+            // least a 4096 byte page size. (Even if the page size was larger, that shouldn't
+            // interfere with the accuracy of the test.)
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
+                final long allocations = maxHeapSize / 4096 + 1;
+                // We need to use `new Uint8Array(new ArrayBuffer(1))` instead of `new
+                // Uint8Array(1)` because V8 appears to instead internalize (onto the V8 heap)
+                // smaller directly constructed typed arrays.
+                final String code = ""
+                        + "(function(){"
+                        + " const arrayLength = " + allocations + ";"
+                        + " const buffers = Array(arrayLength);"
+                        + " for (let i = 0; i < arrayLength; i++) {"
+                        + "  try {"
+                        + "   buffers[i] = new Uint8Array(new ArrayBuffer(1));"
+                        + "  } catch (e) {"
+                        + "   if (e instanceof RangeError) {"
+                        + "    return i;"
+                        + "   } else {"
+                        + "    throw e;"
+                        + "   }"
+                        + "  }"
+                        + " }"
+                        + " return 'FAIL';"
+                        + "})()";
+                final String notExpected = "FAIL";
+                ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
+                String result = resultFuture.get(60, TimeUnit.SECONDS);
+                Assert.assertNotEquals(notExpected, result);
+            }
+            // Allocating one larger contiguous array buffer should not incur significant overhead.
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
+                // At most maxHeapSize is available to array buffers, but maybe less. Go with
+                // something less than the full limit, but which is still much more than what was
+                // logically requested by the many smaller buffers.
+                final long size = maxHeapSize / 8;
+                final String code = ""
+                        + "const buffer = new Uint8Array(new ArrayBuffer(" + size + "));"
+                        + "'PASS'";
+                final String expected = "PASS";
+                ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
+                String result = resultFuture.get(10, TimeUnit.SECONDS);
+                Assert.assertEquals(expected, result);
+            }
+        }
+    }
+
+    @Test
+    @LargeTest
+    public void testOversizedNamedData() throws Throwable {
+        final long maxHeapSize = REASONABLE_HEAP_SIZE;
+        final long largeSize = (maxHeapSize + 1L);
+        Assert.assertTrue(largeSize <= Integer.MAX_VALUE);
+        final byte[] largeBytes = new byte[(int) largeSize];
+        final String provideString = "Hello World";
+        final byte[] smallBytes = provideString.getBytes(StandardCharsets.US_ASCII);
+        // Test that attempting to consume an oversized named data into a new array buffer fails
+        // with a RangeError, and a subsequent smaller request succeeds.
+        final String code = ""
+                + "function ab2str(buf) {"
+                + " return String.fromCharCode.apply(null, new Uint8Array(buf));"
+                + "}"
+                + "async function test() {"
+                + " try {"
+                + "  await android.consumeNamedDataAsArrayBuffer('large');"
+                + "  throw new Error('consumption of large named data should not have succeeded');"
+                + " } catch (e) {"
+                + "  if (!(e instanceof RangeError)) {"
+                + "   throw e;"
+                + "  }"
+                + " }"
+                + " const buffer = await android.consumeNamedDataAsArrayBuffer('small');"
+                + " return await ab2str(buffer);"
+                + "}"
+                + "test()";
+        Context context = ContextUtils.getApplicationContext();
+        ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
+            Assume.assumeTrue(
+                    jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
+            IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
+            isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
+                boolean provideNamedDataLargeReturn =
+                        jsIsolate.provideNamedData("large", largeBytes);
+                Assert.assertTrue(provideNamedDataLargeReturn);
+                boolean provideNamedDataSmallReturn =
+                        jsIsolate.provideNamedData("small", smallBytes);
+                Assert.assertTrue(provideNamedDataSmallReturn);
+                ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
+                String result = resultFuture.get(5, TimeUnit.SECONDS);
+                Assert.assertEquals(provideString, result);
+            }
+        }
+    }
+
+    @Test
+    @LargeTest
+    public void testUnconsumedNamedData() throws Throwable {
+        // Ensure that creating and discarding loads of separate unconsumed named data do not result
+        // in leaks (particularly memory, file descriptors, and threads).
+        final byte[] bytes = new byte[LARGE_NAMED_DATA_SIZE];
+        final int numIsolates = 100;
+        final int numNames = 100;
+        Context context = ContextUtils.getApplicationContext();
+        ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
+            for (int i = 0; i < numIsolates; i++) {
+                try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+                    for (int j = 0; j < numNames; j++) {
+                        boolean provideNamedDataReturn =
+                                jsIsolate.provideNamedData("id-" + j, bytes);
+                        Assert.assertTrue(provideNamedDataReturn);
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/android_webview/js_sandbox/service/BUILD.gn b/android_webview/js_sandbox/service/BUILD.gn
index 339293d..e887105b 100644
--- a/android_webview/js_sandbox/service/BUILD.gn
+++ b/android_webview/js_sandbox/service/BUILD.gn
@@ -4,6 +4,8 @@
 
 source_set("js_sandbox") {
   sources = [
+    "js_sandbox_array_buffer_allocator.cc",
+    "js_sandbox_array_buffer_allocator.h",
     "js_sandbox_isolate.cc",
     "js_sandbox_isolate.h",
     "js_sandbox_isolate_callback.cc",
diff --git a/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.cc b/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.cc
new file mode 100644
index 0000000..3328ee1
--- /dev/null
+++ b/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.cc
@@ -0,0 +1,118 @@
+// 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 "android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.h"
+
+#include <cstddef>
+#include <memory>
+
+#include "base/check.h"
+#include "base/check_op.h"
+#include "base/logging.h"
+#include "base/memory/raw_ref.h"
+#include "v8/include/v8-array-buffer.h"
+
+namespace {
+
+size_t RoundUpToPage(const size_t amount, const size_t page_size) {
+  CHECK_LE(amount, SIZE_MAX / page_size * page_size);
+  return ((amount + page_size - 1) / page_size) * page_size;
+}
+
+}  // namespace
+
+namespace android_webview {
+
+JsSandboxArrayBufferAllocator::JsSandboxArrayBufferAllocator(
+    v8::ArrayBuffer::Allocator& inner,
+    const size_t budget,
+    const size_t page_size)
+    : inner_allocator_(inner),
+      remaining_(budget),
+      budget_(budget),
+      page_size_(page_size) {
+  DCHECK_GT(page_size, size_t{0});
+}
+
+JsSandboxArrayBufferAllocator::~JsSandboxArrayBufferAllocator() {
+  // Note, remaining_ <= budget is an invariant maintained by CHECKs, so this
+  // should only ever fail if remaining_ < budget_.
+  DCHECK_EQ(remaining_, budget_) << "Memory leaked: " << (budget_ - remaining_)
+                                 << " bytes of array buffers not freed before "
+                                    "array buffer allocator destruction";
+}
+
+void* JsSandboxArrayBufferAllocator::Allocate(const size_t length) {
+  if (!AllocateBudget(length)) {
+    return nullptr;
+  }
+  void* const buffer = inner_allocator_->Allocate(length);
+  if (!buffer) {
+    FreeBudget(length);
+    return nullptr;
+  }
+  return buffer;
+}
+
+void* JsSandboxArrayBufferAllocator::AllocateUninitialized(
+    const size_t length) {
+  if (!AllocateBudget(length)) {
+    return nullptr;
+  }
+  void* const buffer = inner_allocator_->AllocateUninitialized(length);
+  if (!buffer) {
+    FreeBudget(length);
+    return nullptr;
+  }
+  return buffer;
+}
+
+void JsSandboxArrayBufferAllocator::Free(void* const data,
+                                         const size_t length) {
+  inner_allocator_->Free(data, length);
+  FreeBudget(length);
+}
+
+void* JsSandboxArrayBufferAllocator::Reallocate(void* const data,
+                                                const size_t old_length,
+                                                const size_t new_length) {
+  // Don't assume we will only need new_length minus old_length extra bytes
+  // during the operation, or that if the new_length is less than old_length
+  // that everything will just happen in place.  In fact, the V8 docs stipulate
+  // that the default implementation will always allocate a new block and copy
+  // the old data over.
+  if (!AllocateBudget(new_length)) {
+    return nullptr;
+  }
+  void* const new_data =
+      inner_allocator_->Reallocate(data, old_length, new_length);
+  if (!new_data) {
+    FreeBudget(new_length);
+    return nullptr;
+  }
+  FreeBudget(old_length);
+  return new_data;
+}
+
+bool JsSandboxArrayBufferAllocator::AllocateBudget(const size_t amount) {
+  const size_t rounded_amount = RoundUpToPage(amount, page_size_);
+  if (remaining_ < rounded_amount) {
+    return false;
+  }
+  remaining_ -= rounded_amount;
+  return true;
+}
+
+void JsSandboxArrayBufferAllocator::FreeBudget(const size_t amount) {
+  const size_t rounded_amount = RoundUpToPage(amount, page_size_);
+  CHECK_LE(amount, GetUsage())
+      << "attempted to free more array buffer memory than is allocated";
+  remaining_ += rounded_amount;
+}
+
+size_t JsSandboxArrayBufferAllocator::GetUsage() const {
+  return budget_ - remaining_;
+}
+
+}  // namespace android_webview
diff --git a/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.h b/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.h
new file mode 100644
index 0000000..551f4e2
--- /dev/null
+++ b/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.h
@@ -0,0 +1,93 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ANDROID_WEBVIEW_JS_SANDBOX_SERVICE_JS_SANDBOX_ARRAY_BUFFER_ALLOCATOR_H_
+#define ANDROID_WEBVIEW_JS_SANDBOX_SERVICE_JS_SANDBOX_ARRAY_BUFFER_ALLOCATOR_H_
+
+#include <cstddef>
+
+#include "base/memory/raw_ref.h"
+#include "v8/include/v8-array-buffer.h"
+
+namespace android_webview {
+
+// A v8::ArrayBuffer::Allocator which imposes a limit on the amount of
+// simultaneously allocated memory. See also the V8 documentation for
+// v8::ArrayBuffer::Allocator.
+//
+// This allocator must only be used from one thread at a time. Allocation and
+// deallocation methods should only happen on the isolate thread, but
+// construction and destruction will typically happen outside of the isolate
+// thread.
+class JsSandboxArrayBufferAllocator final : public v8::ArrayBuffer::Allocator {
+ public:
+  // Value to supply as a budget to indicate an unlimited budget.
+  static constexpr size_t kUnlimitedBudget = SIZE_MAX;
+
+  // Wrap the given inner allocator and impose a maximum allocation budget.
+  //
+  // The JsSandboxArrayBufferAllocator allocator does NOT assume ownership of or
+  // copy the inner allocator, so the caller must ensure that it outlives the
+  // JsSandboxArrayBufferAllocator.
+  //
+  // The memory budget specifies the maximum amount of memory which can be
+  // allocated at any given time. Note that a value of 0 will disallow all
+  // allocations. Use kUnlimitedBudget if you want no effective limit.
+  //
+  // The page size should desribe the page size of the allocator used (this is
+  // not necessarily the OS page size), and is used to (pessimistically) account
+  // for possible internal memory fragmentation in the budget.
+  explicit JsSandboxArrayBufferAllocator(v8::ArrayBuffer::Allocator& inner,
+                                         size_t memory_budget,
+                                         size_t page_size);
+  JsSandboxArrayBufferAllocator(const JsSandboxArrayBufferAllocator&) = delete;
+  JsSandboxArrayBufferAllocator& operator=(
+      const JsSandboxArrayBufferAllocator&) = delete;
+  // All prior allocations must be freed before the allocator is destructed.
+  ~JsSandboxArrayBufferAllocator() override;
+  // Attempt to allocate length bytes (zero-initialized).
+  //
+  // The tracked memory usage may be rounded up to a multiple of the page size.
+  //
+  // nullptr will be returned if the request exceeds the memory budget or the
+  // inner allocator returns nullptr.
+  void* Allocate(size_t length) override;
+  // Similar to Allocate(), but does not explicitly zero-(re)initialize memory
+  // in userspace.
+  void* AllocateUninitialized(size_t length) override;
+  // Deallocate a currently allocated memory region that was previously returned
+  // by Allocate (or Reallocate).
+  //
+  // The length must match the current length associated with the region.
+  void Free(void* data, size_t length) override;
+  // Attempt to reallocate a region of memory (usually to expand or contract
+  // it).
+  //
+  // In order for a reallocation to work, there must be at least new_length
+  // remaining bytes left in the memory budget up-front, and the inner
+  // allocator's reallocate operation must also succeed.
+  void* Reallocate(void* data, size_t old_length, size_t new_length) override;
+
+  // Return the amount of allocated (used) memory budget.
+  size_t GetUsage() const;
+
+ private:
+  // Try to allocate some budget and return true iff successful
+  bool AllocateBudget(size_t amount);
+  // Deallocate some budget
+  void FreeBudget(size_t amount);
+
+  // The underlying allocator which does the actual memory management.
+  const raw_ref<v8::ArrayBuffer::Allocator> inner_allocator_;
+  // The amount of unused budget
+  size_t remaining_;
+  // The overall memory budget this allocator has to stay within
+  const size_t budget_;
+  // Page size for rounding up allocation amounts.
+  const size_t page_size_;
+};
+
+}  // namespace android_webview
+
+#endif
diff --git a/android_webview/js_sandbox/service/js_sandbox_isolate.cc b/android_webview/js_sandbox/service/js_sandbox_isolate.cc
index cea8ff8..6925e165 100644
--- a/android_webview/js_sandbox/service/js_sandbox_isolate.cc
+++ b/android_webview/js_sandbox/service/js_sandbox_isolate.cc
@@ -10,17 +10,21 @@
 #include <string>
 
 #include "android_webview/js_sandbox/js_sandbox_jni_headers/JsSandboxIsolate_jni.h"
+#include "android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.h"
 #include "android_webview/js_sandbox/service/js_sandbox_isolate_callback.h"
 #include "base/android/callback_android.h"
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/auto_reset.h"
+#include "base/check.h"
+#include "base/check_op.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
 #include "base/immediate_crash.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/numerics/safe_math.h"
 #include "base/strings/string_piece.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/system/sys_info.h"
@@ -37,6 +41,7 @@
 #include "gin/try_catch.h"
 #include "gin/v8_initializer.h"
 #include "js_sandbox_isolate.h"
+#include "v8/include/v8-array-buffer.h"
 #include "v8/include/v8-function.h"
 #include "v8/include/v8-microtask-queue.h"
 #include "v8/include/v8-statistics.h"
@@ -52,19 +57,21 @@
 // this once error handling is in place.
 constexpr base::StringPiece resource_name = "<expression>";
 
+size_t GetAllocatePageSize() {
+  return gin::V8Platform::Get()->GetPageAllocator()->AllocatePageSize();
+}
+
 // AdjustToValidHeapSize will either round the provided heap size up to a valid
 // allocation page size or clip the value to the maximum supported heap size.
-size_t AdjustToValidHeapSize(const uint64_t heap_size_bytes) {
-  // The value of 64K should just work on all platforms. Smaller page sizes
-  // might work in practice, although we currently don't have long-term
-  // guarantees. This value is not necessarily the same as the system's memory
-  // page size. https://bugs.chromium.org/p/v8/issues/detail?id=13172#c6
-  constexpr size_t page_size = 65536;
-  constexpr size_t max_supported_heap_size =
-      (size_t)UINT_MAX / page_size * page_size;
+size_t AdjustToValidHeapSize(const size_t heap_size_bytes) {
+  // This value is not necessarily the same as the system's memory page
+  // size. https://bugs.chromium.org/p/v8/issues/detail?id=13172#c6
+  const size_t page_size = GetAllocatePageSize();
+  const size_t max_supported_heap_size =
+      size_t{UINT_MAX} / page_size * page_size;
 
-  if (heap_size_bytes < (uint64_t)max_supported_heap_size) {
-    return ((size_t)heap_size_bytes + (page_size - 1)) / page_size * page_size;
+  if (heap_size_bytes < max_supported_heap_size) {
+    return (heap_size_bytes + (page_size - 1)) / page_size * page_size;
   } else {
     return max_supported_heap_size;
   }
@@ -140,14 +147,21 @@
   length = len;
 }
 
-JsSandboxIsolate::JsSandboxIsolate(jlong max_heap_size_bytes)
-    : isolate_max_heap_size_bytes_(max_heap_size_bytes) {
-  CHECK_GE(isolate_max_heap_size_bytes_, 0);
-  control_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner({});
-  isolate_task_runner_ = base::ThreadPool::CreateSingleThreadTaskRunner(
-      {base::TaskPriority::USER_BLOCKING,
-       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()},
-      base::SingleThreadTaskRunnerThreadMode::DEDICATED);
+JsSandboxIsolate::JsSandboxIsolate(const size_t max_heap_size_bytes)
+    : isolate_max_heap_size_bytes_(max_heap_size_bytes),
+      array_buffer_allocator_(std::make_unique<JsSandboxArrayBufferAllocator>(
+          *gin::ArrayBufferAllocator::SharedInstance(),
+          max_heap_size_bytes > 0
+              ? max_heap_size_bytes
+              : JsSandboxArrayBufferAllocator::kUnlimitedBudget,
+          // This is a bit of an implementation detail - gin uses the same
+          // underlying allocator for pages and array buffers.
+          GetAllocatePageSize())),
+      control_task_runner_(base::ThreadPool::CreateSequencedTaskRunner({})),
+      isolate_task_runner_(base::ThreadPool::CreateSingleThreadTaskRunner(
+          {base::TaskPriority::USER_BLOCKING,
+           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()},
+          base::SingleThreadTaskRunnerThreadMode::DEDICATED)) {
   control_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&JsSandboxIsolate::CreateCancelableTaskTracker,
                                 base::Unretained(this)));
@@ -253,53 +267,59 @@
 // Called from control sequence.
 void JsSandboxIsolate::ConvertPromiseToArrayBufferInControlSequence(
     std::string name,
-    std::unique_ptr<v8::BackingStore> backing_store) {
+    std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer,
+    std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver) {
   cancelable_task_tracker_->PostTask(
       isolate_task_runner_.get(), FROM_HERE,
       base::BindOnce(
           &JsSandboxIsolate::ConvertPromiseToArrayBufferInIsolateSequence,
-          base::Unretained(this), std::move(name), std::move(backing_store)));
+          base::Unretained(this), std::move(name), std::move(array_buffer),
+          std::move(resolver)));
 }
 
 // Called from control sequence.
+//
+// The array_buffer's API must only be used from the isolate thread.
 void JsSandboxIsolate::ConvertPromiseToFailureInControlSequence(
     std::string name,
+    std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer,
+    std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver,
     std::string reason) {
   cancelable_task_tracker_->PostTask(
       isolate_task_runner_.get(), FROM_HERE,
       base::BindOnce(
           &JsSandboxIsolate::ConvertPromiseToFailureInIsolateSequence,
-          base::Unretained(this), std::move(name), std::move(reason)));
+          base::Unretained(this), std::move(name), std::move(array_buffer),
+          std::move(resolver), std::move(reason)));
 }
 
 // Called from Thread pool.
+//
+// The array_buffer's API must only be used from the isolate thread, but the
+// internal data (inner_buffer) may be accessed in whatever thread is currently
+// processing the task, so long as array_buffer remains alive.
 void JsSandboxIsolate::ConvertPromiseToArrayBufferInThreadPool(
     base::ScopedFD fd,
     ssize_t length,
-    std::string name) {
-  char* buffer =
-      (char*)gin::ArrayBufferAllocator::SharedInstance()->Allocate(length);
-  if (base::ReadFromFD(fd.get(), buffer, length)) {
-    auto deleter = [](void* buffer, size_t length, void* data) {
-      gin::ArrayBufferAllocator::SharedInstance()->Free(buffer, length);
-    };
-
-    std::unique_ptr<v8::BackingStore> backing_store =
-        v8::ArrayBuffer::NewBackingStore(buffer, length, deleter, nullptr);
+    std::string name,
+    std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer,
+    std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver,
+    void* inner_buffer) {
+  if (base::ReadFromFD(fd.get(), static_cast<char*>(inner_buffer), length)) {
     control_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(
             &JsSandboxIsolate::ConvertPromiseToArrayBufferInControlSequence,
-            base::Unretained(this), std::move(name), std::move(backing_store)));
+            base::Unretained(this), std::move(name), std::move(array_buffer),
+            std::move(resolver)));
   } else {
-    gin::ArrayBufferAllocator::SharedInstance()->Free(buffer, length);
     std::string failure_reason = "Reading data failed.";
     control_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(
             &JsSandboxIsolate::ConvertPromiseToFailureInControlSequence,
-            base::Unretained(this), std::move(name),
-            std::move(failure_reason)));
+            base::Unretained(this), std::move(name), std::move(array_buffer),
+            std::move(resolver), std::move(failure_reason)));
   }
 }
 
@@ -332,6 +352,7 @@
 void JsSandboxIsolate::InitializeIsolateOnThread() {
   std::unique_ptr<v8::Isolate::CreateParams> params =
       gin::IsolateHolder::getDefaultIsolateParams();
+  params->array_buffer_allocator = array_buffer_allocator_.get();
   if (isolate_max_heap_size_bytes_ > 0) {
     params->constraints.ConfigureDefaultsFromHeapSize(
         0, AdjustToValidHeapSize(isolate_max_heap_size_bytes_));
@@ -466,30 +487,35 @@
 // Called from isolate thread.
 void JsSandboxIsolate::ConvertPromiseToArrayBufferInIsolateSequence(
     std::string name,
-    std::unique_ptr<v8::BackingStore> backing_store) {
+    std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer,
+    std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver) {
   v8::Isolate::Scope isolate_scope(isolate_holder_->isolate());
   v8::HandleScope handle_scope(isolate_holder_->isolate());
   v8::Context::Scope scope(context_holder_->context());
 
-  v8::Local<v8::ArrayBuffer> array_buffer = v8::ArrayBuffer::New(
-      isolate_holder_->isolate(), std::move(backing_store));
-  auto it = named_resolver_.find(name);
-  it->second.Get(isolate_holder_->isolate())
-      ->Resolve(context_holder_->context(), array_buffer)
+  resolver->Get(isolate_holder_->isolate())
+      ->Resolve(context_holder_->context(),
+                array_buffer->Get(isolate_holder_->isolate()))
       .ToChecked();
-  named_resolver_.erase(it);
 }
 
 // Called from isolate thread.
+//
+// We pass the array_buffer to the isolate thread so that it (or the handle)
+// only gets destructed from the isolate thread.
 void JsSandboxIsolate::ConvertPromiseToFailureInIsolateSequence(
     std::string name,
+    std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer,
+    std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver,
     std::string reason) {
   v8::Isolate::Scope isolate_scope(isolate_holder_->isolate());
   v8::HandleScope handle_scope(isolate_holder_->isolate());
   v8::Context::Scope scope(context_holder_->context());
 
-  named_resolver_[name]
-      .Get(isolate_holder_->isolate())
+  // Allow array buffer to be garbage collectable before further V8 calls.
+  array_buffer = nullptr;
+
+  resolver->Get(isolate_holder_->isolate())
       ->Reject(context_holder_->context(),
                v8::Exception::Error(
                    gin::StringToV8(isolate_holder_->isolate(), reason)))
@@ -520,25 +546,61 @@
   {
     base::AutoLock lock(named_fd_lock_);
     auto entry = named_fd_.find(name);
-    if (entry == named_fd_.end()) {
-      std::string reason = "No NamedData available with the given name";
-      global_resolver.Get(isolate_holder_->isolate())
-          ->Reject(context_holder_->context(),
-                   v8::Exception::Error(
-                       gin::StringToV8(isolate_holder_->isolate(), reason)))
-          .ToChecked();
-      args->Return(promise);
-      return;
+    if (entry != named_fd_.end()) {
+      // When we move the fd, we invalidate the entry in the map such that it
+      // cannot be used again, even if the operation fails before we read any
+      // data from the pipe.
+      fd = std::move(entry->second.fd);
+      length = entry->second.length;
     }
-    fd = std::move(entry->second.fd);
-    length = entry->second.length;
   }
-  named_resolver_.insert({name, std::move(global_resolver)});
+  if (!fd.is_valid()) {
+    std::string reason = "No NamedData available with the given name";
+    global_resolver.Get(isolate_holder_->isolate())
+        ->Reject(context_holder_->context(),
+                 v8::Exception::Error(
+                     gin::StringToV8(isolate_holder_->isolate(), reason)))
+        .ToChecked();
+    args->Return(promise);
+    return;
+  }
+
+  // V8 only accounts for the external memory used by backing stores once they
+  // are bound to an array buffer. So we set up the whole array buffer up-front
+  // on the isolate thread. (This will prevent V8's view of external memory
+  // usage getting out of sync with our own.)
+  v8::MaybeLocal<v8::ArrayBuffer> maybe_array_buffer =
+      tryAllocateArrayBuffer(length);
+  if (maybe_array_buffer.IsEmpty()) {
+    const std::string reason =
+        "Array buffer allocation failed for consumeNamedDataAsArrayBuffer";
+    global_resolver.Get(isolate_holder_->isolate())
+        ->Reject(context_holder_->context(),
+                 v8::Exception::RangeError(
+                     gin::StringToV8(isolate_holder_->isolate(), reason)))
+        .ToChecked();
+    args->Return(promise);
+    return;
+  }
+
+  v8::Local<v8::ArrayBuffer> local_array_buffer =
+      maybe_array_buffer.ToLocalChecked();
+  void* const inner_buffer = local_array_buffer->Data();
+  // V8 documentation provides no guarantees about the thread-safety of Globals
+  // - even move construction/destruction. Wrap it in a unique_ptr so that it
+  // can be treated as an opaque pointer until it's handed back to the isolate
+  // thread.
+  std::unique_ptr<v8::Global<v8::ArrayBuffer>> global_array_buffer(
+      std::make_unique<v8::Global<v8::ArrayBuffer>>(
+          isolate, std::move(local_array_buffer)));
   base::ThreadPool::PostTask(
       FROM_HERE, {base::MayBlock()},
       base::BindOnce(&JsSandboxIsolate::ConvertPromiseToArrayBufferInThreadPool,
                      base::Unretained(this), std::move(fd), length,
-                     std::move(name)));
+                     std::move(name), std::move(global_array_buffer),
+                     std::make_unique<v8::Global<v8::Promise::Resolver>>(
+                         std::move(global_resolver)),
+                     inner_buffer));
   args->Return(promise);
 }
 
@@ -561,11 +623,16 @@
   // currently running evaluation.
   CHECK(current_callback_)
       << "Isolate ran out of memory outside of an evaluation.";
-  uint64_t memory_limit = static_cast<uint64_t>(isolate_max_heap_size_bytes_);
+  uint64_t memory_limit = uint64_t{isolate_max_heap_size_bytes_};
   v8::HeapStatistics heap_statistics;
   isolate_holder_->isolate()->GetHeapStatistics(&heap_statistics);
-  uint64_t heap_usage = heap_statistics.used_heap_size();
-  current_callback_->ReportMemoryLimitExceededError(memory_limit, heap_usage);
+  uint64_t v8_heap_usage = heap_statistics.used_heap_size();
+  // Note that we use our own memory accounting, and not V8's external memory
+  // accounting, for non-heap usage. These numbers can differ, particularly as
+  // our own memory accounting considers whole pages rather than just bytes.
+  uint64_t non_v8_heap_usage = uint64_t{array_buffer_allocator_->GetUsage()};
+  current_callback_->ReportMemoryLimitExceededError(memory_limit, v8_heap_usage,
+                                                    non_v8_heap_usage);
   FreezeThread();
 }
 
@@ -586,6 +653,52 @@
   base::ImmediateCrash();
 }
 
+// Called from isolate thread.
+//
+// Attempts to allocate an array buffer of given size. If unsuccessful, no array
+// is returned, instead of an OOM crash.
+//
+// The public V8 APIs don't expose native methods for trying to allocate an
+// array buffer without the risk of an OOM crash.
+//
+// The returned buffer will not be resizable.
+v8::MaybeLocal<v8::ArrayBuffer> JsSandboxIsolate::tryAllocateArrayBuffer(
+    const size_t length) {
+  void* buffer = array_buffer_allocator_->Allocate(length);
+  if (!buffer) {
+    // Encourage V8 to perform some garbage collection, which might result in
+    // previous array buffers getting deallocated. Note this won't free memory
+    // from the heap itself, but it will clean up any garbage which is keeping
+    // otherwise disused array buffers alive.
+    //
+    // Note that this may cause overly aggressive garbage collection, but is the
+    // only sensible API provided.
+    isolate_holder_->isolate()->LowMemoryNotification();
+    // Try again after GC.
+    buffer = array_buffer_allocator_->Allocate(length);
+    if (!buffer) {
+      return v8::MaybeLocal<v8::ArrayBuffer>();
+    }
+  }
+
+  std::unique_ptr<v8::BackingStore> backing_store =
+      v8::ArrayBuffer::NewBackingStore(
+          buffer, length,
+          [](void* buffer_to_delete, size_t length, void* allocator) {
+            static_cast<v8::ArrayBuffer::Allocator*>(allocator)->Free(
+                buffer_to_delete, length);
+          },
+          array_buffer_allocator_.get());
+
+  // We do not need to call AdjustAmountOfExternalAllocatedMemory ourselves. V8
+  // will automatically call AdjustAmountOfExternalAllocatedMemory with the size
+  // of the backing store involved, which may further trigger garbage
+  // collections if memory usage is being unreasonable. This is done deep within
+  // the call to v8::ArrayBuffer::New().
+  return v8::MaybeLocal<v8::ArrayBuffer>(v8::ArrayBuffer::New(
+      isolate_holder_->isolate(), std::move(backing_store)));
+}
+
 static void JNI_JsSandboxIsolate_InitializeEnvironment(JNIEnv* env) {
   base::ThreadPoolInstance::CreateAndStartWithDefaultParams("JsSandboxIsolate");
 #ifdef V8_USE_EXTERNAL_STARTUP_DATA
@@ -598,7 +711,9 @@
 static jlong JNI_JsSandboxIsolate_CreateNativeJsSandboxIsolateWrapper(
     JNIEnv* env,
     jlong max_heap_size_bytes) {
-  JsSandboxIsolate* processor = new JsSandboxIsolate(max_heap_size_bytes);
+  CHECK_GE(max_heap_size_bytes, 0);
+  JsSandboxIsolate* processor =
+      new JsSandboxIsolate(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 59b69e4..12af034 100644
--- a/android_webview/js_sandbox/service/js_sandbox_isolate.h
+++ b/android_webview/js_sandbox/service/js_sandbox_isolate.h
@@ -16,6 +16,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/synchronization/lock.h"
 #include "base/thread_annotations.h"
+#include "v8/include/v8-array-buffer.h"
 #include "v8/include/v8-promise.h"
 
 namespace base {
@@ -31,18 +32,18 @@
 }  // namespace gin
 
 namespace v8 {
-class BackingStore;
 class ObjectTemplate;
 }  // namespace v8
 
 namespace android_webview {
 
 class FdWithLength;
+class JsSandboxArrayBufferAllocator;
 class JsSandboxIsolateCallback;
 
 class JsSandboxIsolate {
  public:
-  explicit JsSandboxIsolate(jlong max_heap_size_bytes = 0);
+  explicit JsSandboxIsolate(size_t max_heap_size_bytes = 0);
   ~JsSandboxIsolate();
 
   jboolean EvaluateJavascript(
@@ -74,19 +75,31 @@
   void PostEvaluationToIsolateThread(
       const std::string code,
       scoped_refptr<JsSandboxIsolateCallback> callback);
-  void ConvertPromiseToArrayBufferInThreadPool(base::ScopedFD fd,
-                                               ssize_t length,
-                                               std::string name);
+  void ConvertPromiseToArrayBufferInThreadPool(
+      base::ScopedFD fd,
+      ssize_t length,
+      std::string name,
+      std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer,
+      std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver,
+      void* inner_buffer);
   void ConvertPromiseToArrayBufferInControlSequence(
       std::string name,
-      std::unique_ptr<v8::BackingStore> backing_store);
-  void ConvertPromiseToFailureInControlSequence(std::string name,
-                                                std::string reason);
-  void ConvertPromiseToFailureInIsolateSequence(std::string name,
-                                                std::string reason);
+      std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer,
+      std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver);
+  void ConvertPromiseToFailureInControlSequence(
+      std::string name,
+      std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer,
+      std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver,
+      std::string reason);
+  void ConvertPromiseToFailureInIsolateSequence(
+      std::string name,
+      std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer,
+      std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver,
+      std::string reason);
   void ConvertPromiseToArrayBufferInIsolateSequence(
       std::string name,
-      std::unique_ptr<v8::BackingStore>);
+      std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer,
+      std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver);
 
   void ConsumeNamedDataAsArrayBuffer(gin::Arguments* args);
   v8::Local<v8::ObjectTemplate> CreateAndroidNamespaceTemplate(
@@ -96,10 +109,20 @@
   [[noreturn]] static size_t NearHeapLimitCallback(void* data,
                                                    size_t current_heap_limit,
                                                    size_t initial_heap_limit);
+  v8::MaybeLocal<v8::ArrayBuffer> tryAllocateArrayBuffer(size_t length);
+
   // Must only be used from isolate thread
   [[noreturn]] void MemoryLimitExceeded();
   [[noreturn]] void FreezeThread();
 
+  // V8 heap size limit. Must be non-negative.
+  //
+  // 0 indicates no explicit limit (but use the default V8 limits).
+  const size_t isolate_max_heap_size_bytes_;
+  // Apart from construction/destruction, must only be used from the isolate
+  // thread.
+  std::unique_ptr<JsSandboxArrayBufferAllocator> array_buffer_allocator_;
+
   // Used as a control sequence to add ordering to binder threadpool requests.
   scoped_refptr<base::SequencedTaskRunner> control_task_runner_;
   // Should be used from control_task_runner_.
@@ -115,17 +138,10 @@
   std::unique_ptr<gin::IsolateHolder> isolate_holder_;
   // Should be used from isolate_task_runner_.
   std::unique_ptr<gin::ContextHolder> context_holder_;
-  // Should be used from isolate_task_runner_.
-  std::unordered_map<std::string, v8::Global<v8::Promise::Resolver>>
-      named_resolver_;
 
   base::Lock named_fd_lock_;
   std::unordered_map<std::string, FdWithLength> named_fd_
       GUARDED_BY(named_fd_lock_);
-  // V8 heap size limit. Must be non-negative.
-  //
-  // 0 indicates no explicit limit (but use the default V8 limits).
-  const jlong isolate_max_heap_size_bytes_;
 
   // The callback associated with the current evaluation, if any. Used for
   // signaling errors from V8 callbacks.
diff --git a/android_webview/js_sandbox/service/js_sandbox_isolate_callback.cc b/android_webview/js_sandbox/service/js_sandbox_isolate_callback.cc
index d596c51..38ef6a5 100644
--- a/android_webview/js_sandbox/service/js_sandbox_isolate_callback.cc
+++ b/android_webview/js_sandbox/service/js_sandbox_isolate_callback.cc
@@ -37,7 +37,8 @@
 
 void JsSandboxIsolateCallback::ReportMemoryLimitExceededError(
     const uint64_t memory_limit,
-    const uint64_t heap_usage) {
+    const uint64_t v8_heap_usage,
+    const uint64_t non_v8_heap_usage) {
   std::ostringstream details;
   details << "Memory limit exceeded.\n";
   if (memory_limit > 0) {
@@ -45,7 +46,8 @@
   } else {
     details << "Memory limit not explicitly configured\n";
   }
-  details << "Heap usage: " << heap_usage << " bytes\n";
+  details << "V8 heap usage: " << v8_heap_usage << " bytes\n";
+  details << "Non-V8 heap usage: " << non_v8_heap_usage << " bytes\n";
   ReportError(ErrorType::kMemoryLimitExceeded, details.str());
 }
 
diff --git a/android_webview/js_sandbox/service/js_sandbox_isolate_callback.h b/android_webview/js_sandbox/service/js_sandbox_isolate_callback.h
index b3e8b06..b5f9b0d 100644
--- a/android_webview/js_sandbox/service/js_sandbox_isolate_callback.h
+++ b/android_webview/js_sandbox/service/js_sandbox_isolate_callback.h
@@ -32,9 +32,12 @@
   //
   // memory_limit == 0 indicates that no explicit limit was configured.
   //
-  // heap_usage describes memory usage before the failed allocation.
+  // v8_heap_usage describes V8-internal (V8 heap) memory usage before the
+  // failed allocation. non_v8_heap_usage describes memory usage external to the
+  // V8 heap.
   void ReportMemoryLimitExceededError(uint64_t memory_limit,
-                                      uint64_t heap_usage);
+                                      uint64_t v8_heap_usage,
+                                      uint64_t non_v8_heap_usage);
 
  private:
   friend class base::RefCounted<JsSandboxIsolateCallback>;
diff --git a/ash/constants/ash_switches.cc b/ash/constants/ash_switches.cc
index 69d41178..365307c 100644
--- a/ash/constants/ash_switches.cc
+++ b/ash/constants/ash_switches.cc
@@ -697,6 +697,9 @@
 // 2. A terminal to start lacros-chrome with a debugger.
 const char kLacrosMojoSocketForTesting[] = "lacros-mojo-socket-for-testing";
 
+// When this flag is set, the lacros-selection policy is ignored.
+const char kLacrosSelectionPolicyIgnore[] = "lacros-selection-policy-ignore";
+
 // Start Chrome in RMA mode. Launches RMA app automatically.
 // kRmaNotAllowed switch takes priority over this one.
 const char kLaunchRma[] = "launch-rma";
diff --git a/ash/constants/ash_switches.h b/ash/constants/ash_switches.h
index cb06ce94..31e144f1 100644
--- a/ash/constants/ash_switches.h
+++ b/ash/constants/ash_switches.h
@@ -221,6 +221,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLacrosChromeAdditionalEnv[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLacrosChromePath[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLacrosMojoSocketForTesting[];
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kLacrosSelectionPolicyIgnore[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLaunchRma[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLoginManager[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLoginProfile[];
diff --git a/ash/login/ui/login_auth_user_view.cc b/ash/login/ui/login_auth_user_view.cc
index 9b910eb2..4d9ac0c4 100644
--- a/ash/login/ui/login_auth_user_view.cc
+++ b/ash/login/ui/login_auth_user_view.cc
@@ -1109,7 +1109,7 @@
       gfx::Size(/*ignored*/ 0, kPinPasswordToggleButtonHeight));
 
   auto pin_view = std::make_unique<LoginPinView>(
-      LoginPinView::Style::kAlphanumeric,
+      LoginPinView::Style::kNumeric,
       base::BindRepeating(&LoginAuthUserView::OnPinPadInsertDigit,
                           base::Unretained(this)),
       base::BindRepeating(&LoginAuthUserView::OnPinPadBackspace,
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index ef3285a..1b1a3cb 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -169,6 +169,11 @@
   # For use by tools/clang/scripts/analyze_includes.py
   show_includes = false
 
+  # Enable Profi algorithm. Profi can infer block and edge counts.
+  # https://clang.llvm.org/docs/UsersManual.html#using-sampling-profilers
+  # TODO(crbug.com/1375958i:) Possibly enable this for Android too.
+  use_profi = is_chromeos
+
   # If true, linker crashes will be rerun with `--reproduce` which causes
   # a reproducer file to be saved.
   save_reproducers_on_lld_crash = false
@@ -2344,6 +2349,9 @@
       rebased_clang_sample_profile =
           rebase_path(_clang_sample_profile, root_build_dir)
       cflags += [ "-fprofile-sample-use=${rebased_clang_sample_profile}" ]
+      if (use_profi) {
+        cflags += [ "-fsample-profile-use-profi" ]
+      }
       inputs = [ _clang_sample_profile ]
     }
   } else if (auto_profile_path != "" && is_a_target_toolchain) {
diff --git a/chrome/app/password_manager_ui_strings.grdp b/chrome/app/password_manager_ui_strings.grdp
index c4098a7..a97c31b 100644
--- a/chrome/app/password_manager_ui_strings.grdp
+++ b/chrome/app/password_manager_ui_strings.grdp
@@ -256,4 +256,16 @@
   <message name="IDS_PASSWORD_MANAGER_UI_HELP" desc="Aria label for the help button in the toolbar.">
     Help
   </message>
+
+<if expr="is_macosx">
+  <message name="IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC" desc="Label for a toggle that allows users to be authenticate using their TouchID or any other means of authentication when logging into webpages.">
+    Use your screen lock when filling passwords
+  </message>
+</if>
+
+<if expr="is_win">
+  <message name="IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_WIN" desc="Label for a toggle that allows users to be authenticate using their Windows Hello when logging into webpages.">
+    Use Windows Hello when filling passwords
+  </message>
+</if>
 </grit-part>
diff --git a/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC.png.sha1 b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC.png.sha1
new file mode 100644
index 0000000..29ebb03
--- /dev/null
+++ b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC.png.sha1
@@ -0,0 +1 @@
+fe2659f18f8a6273e533c22be381355ce82393b9
\ No newline at end of file
diff --git a/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_WIN.png.sha1 b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_WIN.png.sha1
new file mode 100644
index 0000000..86e5e6f8
--- /dev/null
+++ b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_WIN.png.sha1
@@ -0,0 +1 @@
+cc46aa505cc4af0cda1b51980e504b3879820817
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index c779716e..ac12452 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -955,6 +955,9 @@
      crosapi::browser_util::kLacrosSelectionRootfs},
 };
 
+const char kLacrosSelectionPolicyIgnoreInternalName[] =
+    "lacros-selection-ignore";
+
 const FeatureEntry::Choice kLacrosAvailabilityPolicyChoices[] = {
     {flags_ui::kGenericExperimentChoiceDefault, "", ""},
     {crosapi::browser_util::kLacrosAvailabilityPolicyUserChoice,
@@ -3634,6 +3637,10 @@
     {kLacrosSelectionInternalName, flag_descriptions::kLacrosSelectionName,
      flag_descriptions::kLacrosSelectionDescription, kOsCrOS,
      MULTI_VALUE_TYPE(kLacrosSelectionChoices)},
+    {kLacrosSelectionPolicyIgnoreInternalName,
+     flag_descriptions::kLacrosSelectionPolicyIgnoreName,
+     flag_descriptions::kLacrosSelectionPolicyIgnoreDescription, kOsCrOS,
+     SINGLE_VALUE_TYPE(ash::switches::kLacrosSelectionPolicyIgnore)},
     {kWebAppsCrosapiInternalName, flag_descriptions::kWebAppsCrosapiName,
      flag_descriptions::kWebAppsCrosapiDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(features::kWebAppsCrosapi)},
@@ -9389,6 +9396,11 @@
               entry.internal_name)) {
     return true;
   }
+  // Skip lacros-selection if it is controlled by LacrosSelection policy.
+  if (!strcmp(kLacrosSelectionInternalName, entry.internal_name)) {
+    return crosapi::browser_util::GetCachedLacrosSelectionPolicy() !=
+           crosapi::browser_util::LacrosSelectionPolicy::kUserChoice;
+  }
 
   if (!strcmp(kPreferDcheckInternalName, entry.internal_name)) {
     return !crosapi::browser_util::IsLacrosAllowedToBeEnabled();
diff --git a/chrome/browser/ash/crosapi/browser_loader.cc b/chrome/browser/ash/crosapi/browser_loader.cc
index 71055bc..01e29da 100644
--- a/chrome/browser/ash/crosapi/browser_loader.cc
+++ b/chrome/browser/ash/crosapi/browser_loader.cc
@@ -206,21 +206,20 @@
     return;
   }
 
-  // If the user has specified to force using stateful or rootfs lacros-chrome
-  // binary, force the selection.
-  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-  if (cmdline->HasSwitch(browser_util::kLacrosSelectionSwitch)) {
+  // If the LacrosSelection policy or the user have specified to force using
+  // stateful or rootfs lacros-chrome binary, force the selection. Otherwise,
+  // load the newest available binary.
+  if (absl::optional<browser_util::LacrosSelection> lacros_selection =
+          browser_util::DetermineLacrosSelection()) {
     // TODO(crbug.com/1293250): We should check the version compatibility here,
     // too.
-    auto value =
-        cmdline->GetSwitchValueASCII(browser_util::kLacrosSelectionSwitch);
-    if (value == browser_util::kLacrosSelectionRootfs) {
-      LoadRootfsLacros(std::move(callback));
-      return;
-    }
-    if (value == browser_util::kLacrosSelectionStateful) {
-      LoadStatefulLacros(std::move(callback));
-      return;
+    switch (lacros_selection.value()) {
+      case browser_util::LacrosSelection::kRootfs:
+        LoadRootfsLacros(std::move(callback));
+        return;
+      case browser_util::LacrosSelection::kStateful:
+        LoadStatefulLacros(std::move(callback));
+        return;
     }
   }
 
diff --git a/chrome/browser/ash/crosapi/browser_loader_unittest.cc b/chrome/browser/ash/crosapi/browser_loader_unittest.cc
index 5e9882b0..bcee937 100644
--- a/chrome/browser/ash/crosapi/browser_loader_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_loader_unittest.cc
@@ -10,13 +10,17 @@
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_command_line.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/component_updater/fake_cros_component_manager.h"
 #include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
 #include "chromeos/ash/components/dbus/upstart/fake_upstart_client.h"
 #include "components/component_updater/mock_component_updater_service.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
 #include "components/update_client/update_client.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -32,6 +36,33 @@
 constexpr char kLacrosMounterUpstartJob[] = "lacros_2dmounter";
 constexpr char kLacrosUnmounterUpstartJob[] = "lacros_2dunmounter";
 
+// This implementation of RAII for LacrosSelection is to make it easy reset
+// the state between runs.
+class ScopedLacrosSelectionCache {
+ public:
+  explicit ScopedLacrosSelectionCache(
+      browser_util::LacrosSelectionPolicy lacros_selection) {
+    SetLacrosSelection(lacros_selection);
+  }
+  ScopedLacrosSelectionCache(const ScopedLacrosSelectionCache&) = delete;
+  ScopedLacrosSelectionCache& operator=(const ScopedLacrosSelectionCache&) =
+      delete;
+  ~ScopedLacrosSelectionCache() {
+    browser_util::ClearLacrosSelectionCacheForTest();
+  }
+
+ private:
+  void SetLacrosSelection(
+      browser_util::LacrosSelectionPolicy lacros_selection) {
+    policy::PolicyMap policy;
+    policy.Set(policy::key::kLacrosSelection, policy::POLICY_LEVEL_MANDATORY,
+               policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+               base::Value(GetLacrosSelectionPolicyName(lacros_selection)),
+               /*external_data_fetcher=*/nullptr);
+    browser_util::CacheLacrosSelection(policy);
+  }
+};
+
 }  // namespace
 
 class BrowserLoaderTest : public testing::Test {
@@ -229,4 +260,70 @@
   EXPECT_TRUE(callback_called);
 }
 
+TEST_F(BrowserLoaderTest, OnLoadSelectionPolicyIsRootfs) {
+  ScopedLacrosSelectionCache cache(
+      browser_util::LacrosSelectionPolicy::kRootfs);
+  base::test::ScopedCommandLine command_line;
+  command_line.GetProcessCommandLine()->AppendSwitchASCII(
+      browser_util::kLacrosSelectionSwitch,
+      browser_util::kLacrosSelectionStateful);
+
+  base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
+  browser_loader_->Load(future.GetCallback<const base::FilePath&,
+                                           LacrosSelection, base::Version>());
+
+  const LacrosSelection selection = future.Get<1>();
+  EXPECT_EQ(selection, LacrosSelection::kRootfs);
+}
+
+TEST_F(BrowserLoaderTest, OnLoadSelectionPolicyIsStateful) {
+  ScopedLacrosSelectionCache cache(
+      browser_util::LacrosSelectionPolicy::kStateful);
+  base::test::ScopedCommandLine command_line;
+  command_line.GetProcessCommandLine()->AppendSwitchASCII(
+      browser_util::kLacrosSelectionSwitch,
+      browser_util::kLacrosSelectionRootfs);
+
+  base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
+  browser_loader_->Load(future.GetCallback<const base::FilePath&,
+                                           LacrosSelection, base::Version>());
+
+  const LacrosSelection selection = future.Get<1>();
+  EXPECT_EQ(selection, LacrosSelection::kStateful);
+}
+
+TEST_F(BrowserLoaderTest,
+       OnLoadSelectionPolicyIsUserChoiceAndCommandLineIsRootfs) {
+  ScopedLacrosSelectionCache cache(
+      browser_util::LacrosSelectionPolicy::kUserChoice);
+  base::test::ScopedCommandLine command_line;
+  command_line.GetProcessCommandLine()->AppendSwitchASCII(
+      browser_util::kLacrosSelectionSwitch,
+      browser_util::kLacrosSelectionRootfs);
+
+  base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
+  browser_loader_->Load(future.GetCallback<const base::FilePath&,
+                                           LacrosSelection, base::Version>());
+
+  const LacrosSelection selection = future.Get<1>();
+  EXPECT_EQ(selection, LacrosSelection::kRootfs);
+}
+
+TEST_F(BrowserLoaderTest,
+       OnLoadSelectionPolicyIsUserChoiceAndCommandLineIsStateful) {
+  ScopedLacrosSelectionCache cache(
+      browser_util::LacrosSelectionPolicy::kUserChoice);
+  base::test::ScopedCommandLine command_line;
+  command_line.GetProcessCommandLine()->AppendSwitchASCII(
+      browser_util::kLacrosSelectionSwitch,
+      browser_util::kLacrosSelectionStateful);
+
+  base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
+  browser_loader_->Load(future.GetCallback<const base::FilePath&,
+                                           LacrosSelection, base::Version>());
+
+  const LacrosSelection selection = future.Get<1>();
+  EXPECT_EQ(selection, LacrosSelection::kStateful);
+}
+
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/browser_util.cc b/chrome/browser/ash/crosapi/browser_util.cc
index 718c350..8852866 100644
--- a/chrome/browser/ash/crosapi/browser_util.cc
+++ b/chrome/browser/ash/crosapi/browser_util.cc
@@ -64,6 +64,10 @@
 absl::optional<LacrosDataBackwardMigrationMode>
     g_lacros_data_backward_migration_mode;
 
+// At session start the value for LacrosSelection logic is applied and the
+// result is stored in this variable which is used after that as a cache.
+absl::optional<LacrosSelectionPolicy> g_lacros_selection_cache;
+
 // The rootfs lacros-chrome metadata keys.
 constexpr char kLacrosMetadataContentKey[] = "content";
 constexpr char kLacrosMetadataVersionKey[] = "version";
@@ -906,6 +910,60 @@
       value ? value->GetString() : base::StringPiece());
 }
 
+void CacheLacrosSelection(const policy::PolicyMap& map) {
+  if (g_lacros_selection_cache.has_value()) {
+    // Some browser tests might call this multiple times.
+    LOG(ERROR) << "Trying to cache LacrosSelection and the value was set";
+    return;
+  }
+
+  // Users can set this switch in chrome://flags to disable the effect of the
+  // lacros-selection policy. This should only be allows for googlers.
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  if (cmdline->HasSwitch(ash::switches::kLacrosSelectionPolicyIgnore) &&
+      IsGoogleInternal()) {
+    LOG(WARNING) << "LacrosSelection policy is ignored due to the ignore flag";
+    return;
+  }
+
+  const base::Value* value =
+      map.GetValue(policy::key::kLacrosSelection, base::Value::Type::STRING);
+  g_lacros_selection_cache = ParseLacrosSelectionPolicy(
+      value ? value->GetString() : base::StringPiece());
+}
+
+LacrosSelectionPolicy GetCachedLacrosSelectionPolicy() {
+  return g_lacros_selection_cache.value_or(LacrosSelectionPolicy::kUserChoice);
+}
+
+absl::optional<LacrosSelection> DetermineLacrosSelection() {
+  switch (GetCachedLacrosSelectionPolicy()) {
+    case LacrosSelectionPolicy::kRootfs:
+      return LacrosSelection::kRootfs;
+    case LacrosSelectionPolicy::kStateful:
+      return LacrosSelection::kStateful;
+    case LacrosSelectionPolicy::kUserChoice:
+      break;
+  }
+
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+
+  if (!cmdline->HasSwitch(browser_util::kLacrosSelectionSwitch)) {
+    return absl::nullopt;
+  }
+
+  auto value =
+      cmdline->GetSwitchValueASCII(browser_util::kLacrosSelectionSwitch);
+  if (value == browser_util::kLacrosSelectionRootfs) {
+    return LacrosSelection::kRootfs;
+  }
+  if (value == browser_util::kLacrosSelectionStateful) {
+    return LacrosSelection::kStateful;
+  }
+
+  return absl::nullopt;
+}
+
 ComponentInfo GetLacrosComponentInfoForChannel(version_info::Channel channel) {
   // We default to the Dev component for UNKNOWN channels.
   static const auto kChannelToComponentInfoMap =
@@ -980,6 +1038,10 @@
   g_lacros_data_backward_migration_mode.reset();
 }
 
+void ClearLacrosSelectionCacheForTest() {
+  g_lacros_selection_cache.reset();
+}
+
 MigrationMode GetMigrationMode(const user_manager::User* user,
                                PolicyInitState policy_init_state) {
   if (base::FeatureList::IsEnabled(
@@ -1134,6 +1196,17 @@
   return base::StringPiece();
 }
 
+base::StringPiece GetLacrosSelectionPolicyName(LacrosSelectionPolicy value) {
+  for (const auto& entry : kLacrosSelectionPolicyMap) {
+    if (entry.second == value) {
+      return entry.first;
+    }
+  }
+
+  NOTREACHED();
+  return base::StringPiece();
+}
+
 bool IsAshBrowserSyncEnabled() {
   // Turn off sync from Ash if Lacros is enabled and Ash web browser is
   // disabled.
diff --git a/chrome/browser/ash/crosapi/browser_util.h b/chrome/browser/ash/crosapi/browser_util.h
index 530ff03e..f8fad6ed 100644
--- a/chrome/browser/ash/crosapi/browser_util.h
+++ b/chrome/browser/ash/crosapi/browser_util.h
@@ -348,6 +348,20 @@
 // LacrosDataBackwardMigrationMode policy.
 void CacheLacrosDataBackwardMigrationMode(const policy::PolicyMap& map);
 
+// To be called at primary user login, to cache the policy value for
+// LacrosSelection policy. The effective value of the policy does not
+// change for the duration of the user session, so cached value shall be
+// checked.
+void CacheLacrosSelection(const policy::PolicyMap& map);
+
+// Returns cached value of LacrosSelection policy. See `CacheLacrosSelection`
+// for details.
+LacrosSelectionPolicy GetCachedLacrosSelectionPolicy();
+
+// Returns lacros selection option according to LarcrosSelectionPolicy and
+// lacros-selection flag. Returns nullopt if there is no preference.
+absl::optional<LacrosSelection> DetermineLacrosSelection();
+
 // Returns the lacros ComponentInfo for a given channel.
 ComponentInfo GetLacrosComponentInfoForChannel(version_info::Channel channel);
 
@@ -383,6 +397,9 @@
 // Clears the cached value for LacrosDataBackwardMigrationMode.
 void ClearLacrosDataBackwardMigrationModeCacheForTest();
 
+// Clears the cached value for LacrosSelection policy.
+void ClearLacrosSelectionCacheForTest();
+
 bool IsProfileMigrationEnabled(const AccountId& account_id);
 
 // Returns true if the profile migration can run, but not yet completed.
@@ -466,6 +483,10 @@
 base::StringPiece GetLacrosDataBackwardMigrationModeName(
     LacrosDataBackwardMigrationMode value);
 
+// Returns the LacrosSelection policy value name from the given value. Returned
+// StringPiece is guaranteed to never be invalidated.
+base::StringPiece GetLacrosSelectionPolicyName(LacrosSelectionPolicy value);
+
 // Stores that "Go to files button" on the migration error screen is clicked.
 void SetGotoFilesClicked(PrefService* local_state,
                          const std::string& user_id_hash);
diff --git a/chrome/browser/ash/crosapi/browser_util_unittest.cc b/chrome/browser/ash/crosapi/browser_util_unittest.cc
index e98e03497..4d4ce85 100644
--- a/chrome/browser/ash/crosapi/browser_util_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_util_unittest.cc
@@ -69,6 +69,33 @@
   }
 };
 
+// This implementation of RAII for LacrosSelection is to make it easy reset
+// the state between runs.
+class ScopedLacrosSelectionCache {
+ public:
+  explicit ScopedLacrosSelectionCache(
+      browser_util::LacrosSelectionPolicy lacros_selection) {
+    SetLacrosSelection(lacros_selection);
+  }
+  ScopedLacrosSelectionCache(const ScopedLacrosSelectionCache&) = delete;
+  ScopedLacrosSelectionCache& operator=(const ScopedLacrosSelectionCache&) =
+      delete;
+  ~ScopedLacrosSelectionCache() {
+    browser_util::ClearLacrosSelectionCacheForTest();
+  }
+
+ private:
+  void SetLacrosSelection(
+      browser_util::LacrosSelectionPolicy lacros_selection) {
+    policy::PolicyMap policy;
+    policy.Set(policy::key::kLacrosSelection, policy::POLICY_LEVEL_MANDATORY,
+               policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+               base::Value(GetLacrosSelectionPolicyName(lacros_selection)),
+               /*external_data_fetcher=*/nullptr);
+    browser_util::CacheLacrosSelection(policy);
+  }
+};
+
 }  // namespace
 
 class BrowserUtilTest : public testing::Test {
@@ -1118,4 +1145,116 @@
   EXPECT_EQ(serial_number.value(), expected_serial_number);
 }
 
+TEST_F(BrowserUtilTest, LacrosSelection) {
+  // Neither policy nor command line have any preference on Lacros selection.
+  EXPECT_FALSE(browser_util::DetermineLacrosSelection());
+
+  {
+    // LacrosSelection policy has precedence over command line.
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kRootfs);
+    base::test::ScopedCommandLine cmd_line;
+    cmd_line.GetProcessCommandLine()->AppendSwitchASCII(
+        browser_util::kLacrosSelectionSwitch,
+        browser_util::kLacrosSelectionStateful);
+    EXPECT_EQ(browser_util::DetermineLacrosSelection(),
+              LacrosSelection::kRootfs);
+  }
+
+  {
+    // LacrosSelection policy has precedence over command line.
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kStateful);
+    base::test::ScopedCommandLine cmd_line;
+    cmd_line.GetProcessCommandLine()->AppendSwitchASCII(
+        browser_util::kLacrosSelectionSwitch,
+        browser_util::kLacrosSelectionRootfs);
+    EXPECT_EQ(browser_util::DetermineLacrosSelection(),
+              LacrosSelection::kStateful);
+  }
+
+  {
+    // LacrosSelection allows command line check, but command line is not set.
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kUserChoice);
+    EXPECT_FALSE(browser_util::DetermineLacrosSelection());
+  }
+
+  {
+    // LacrosSelection allows command line check.
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kUserChoice);
+    base::test::ScopedCommandLine cmd_line;
+    cmd_line.GetProcessCommandLine()->AppendSwitchASCII(
+        browser_util::kLacrosSelectionSwitch,
+        browser_util::kLacrosSelectionRootfs);
+    EXPECT_EQ(browser_util::DetermineLacrosSelection(),
+              LacrosSelection::kRootfs);
+  }
+
+  {
+    // LacrosSelection allows command line check.
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kUserChoice);
+    base::test::ScopedCommandLine cmd_line;
+    cmd_line.GetProcessCommandLine()->AppendSwitchASCII(
+        browser_util::kLacrosSelectionSwitch,
+        browser_util::kLacrosSelectionStateful);
+    EXPECT_EQ(browser_util::DetermineLacrosSelection(),
+              LacrosSelection::kStateful);
+  }
+}
+
+// LacrosSelection has no effect on non-googlers.
+TEST_F(BrowserUtilTest, LacrosSelectionPolicyIgnoreNonGoogle) {
+  AddRegularUser("user@random.com");
+
+  base::test::ScopedCommandLine cmd_line;
+  cmd_line.GetProcessCommandLine()->AppendSwitch(
+      ash::switches::kLacrosSelectionPolicyIgnore);
+
+  {
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kRootfs);
+    EXPECT_EQ(browser_util::GetCachedLacrosSelectionPolicy(),
+              browser_util::LacrosSelectionPolicy::kRootfs);
+    EXPECT_EQ(browser_util::DetermineLacrosSelection(),
+              LacrosSelection::kRootfs);
+  }
+
+  {
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kStateful);
+    EXPECT_EQ(browser_util::GetCachedLacrosSelectionPolicy(),
+              browser_util::LacrosSelectionPolicy::kStateful);
+    EXPECT_EQ(browser_util::DetermineLacrosSelection(),
+              LacrosSelection::kStateful);
+  }
+}
+
+// LacrosSelection has an effect on googlers.
+TEST_F(BrowserUtilTest, LacrosSelectionPolicyIgnoreGoogleDisableToUserChoice) {
+  AddRegularUser("user@google.com");
+
+  base::test::ScopedCommandLine cmd_line;
+  cmd_line.GetProcessCommandLine()->AppendSwitch(
+      ash::switches::kLacrosSelectionPolicyIgnore);
+
+  {
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kRootfs);
+    EXPECT_EQ(browser_util::GetCachedLacrosSelectionPolicy(),
+              browser_util::LacrosSelectionPolicy::kUserChoice);
+    EXPECT_FALSE(browser_util::DetermineLacrosSelection());
+  }
+
+  {
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kStateful);
+    EXPECT_EQ(browser_util::GetCachedLacrosSelectionPolicy(),
+              browser_util::LacrosSelectionPolicy::kUserChoice);
+    EXPECT_FALSE(browser_util::DetermineLacrosSelection());
+  }
+}
+
 }  // namespace crosapi
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc b/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc
index e8c30e9..2c00f45 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc
@@ -190,6 +190,10 @@
   if (params->urls.empty())
     return RespondNow(Error("No URLs provided"));
 
+  if (params->dlp_source_urls.size() != params->urls.size()) {
+    return RespondNow(Error("Mismatching URLs and DLP source URLs provided"));
+  }
+
   profile_ = Profile::FromBrowserContext(browser_context());
 
   const scoped_refptr<storage::FileSystemContext> file_system_context =
@@ -210,6 +214,8 @@
     file_system_urls_.push_back(file_system_url);
   }
 
+  dlp_source_urls_ = std::move(params->dlp_source_urls);
+
   mime_type_collector_ =
       std::make_unique<app_file_handler_util::MimeTypeCollector>(profile_);
   mime_type_collector_->CollectForURLs(
@@ -249,7 +255,8 @@
   }
 
   sharesheet_service->ShowBubble(
-      GetSenderWebContents(), apps_util::MakeShareIntent(urls_, *mime_types),
+      GetSenderWebContents(),
+      apps_util::MakeShareIntent(urls_, *mime_types, dlp_source_urls_),
       contains_hosted_document_, launch_source, base::NullCallback());
   Respond(NoArguments());
 }
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.h b/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.h
index 480f321..c188e5bf 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.h
+++ b/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.h
@@ -112,6 +112,7 @@
   std::vector<GURL> urls_;
   Profile* profile_ = nullptr;
   std::vector<storage::FileSystemURL> file_system_urls_;
+  std::vector<std::string> dlp_source_urls_;
   bool contains_hosted_document_ = false;
 };
 
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest.cc b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
index b035d38..ada597f 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
@@ -1466,7 +1466,8 @@
         TestCase("driveDeleteDialogDoesntMentionPermanentDelete"),
         TestCase("driveInlineSyncStatusSingleFile").EnableInlineStatusSync(),
         TestCase("driveInlineSyncStatusParentFolder").EnableInlineStatusSync(),
-        TestCase("driveLocalDeleteUnpinsItem").EnableBulkPinning()
+        TestCase("driveLocalDeleteUnpinsItem").EnableBulkPinning(),
+        TestCase("driveCloudDeleteUnpinsItem").EnableBulkPinning()
         // TODO(b/189173190): Enable
         // TestCase("driveEnableDocsOfflineDialog"),
         // TODO(b/189173190): Enable
@@ -1546,13 +1547,14 @@
         TestCase("transferShowDlpToast").EnableDlp(),
         TestCase("dlpShowManagedIcon").EnableDlp(),
         TestCase("dlpContextMenuRestrictionDetails").EnableDlp(),
-        TestCase("saveAsDlpRestrictedDirectory").EnableDlp(),
+        TestCase("saveAsDlpRestrictedAndroid").EnableArcVm().EnableDlp(),
         TestCase("saveAsDlpRestrictedCrostini").EnableDlp(),
         TestCase("saveAsNonDlpRestricted").EnableDlp(),
         TestCase("saveAsDlpRestrictedRedirectsToMyFiles").EnableDlp(),
         TestCase("saveAsDlpRestrictedVm").EnableDlp(),
         TestCase("openDlpRestrictedFile").EnableDlp(),
-        TestCase("openFolderDlpRestricted").EnableDlp()));
+        TestCase("openFolderDlpRestricted").EnableDlp(),
+        TestCase("fileTasksDlpRestricted").EnableDlp()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     DriveSpecific, /* drive_specific.js */
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
index cab9633..d1381166 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
@@ -1321,6 +1321,21 @@
     drivefs_delegate.FlushForTesting();
   }
 
+  void SendCloudDeleteEvent(const std::string& path) {
+    const base::FilePath file_path(path);
+    absl::optional<drivefs::FakeDriveFs::FileMetadata> metadata =
+        fake_drivefs_helper_->fake_drivefs().GetItemMetadata(file_path);
+    ASSERT_TRUE(metadata.has_value()) << "No file metadata with path: " << path;
+
+    std::vector<drivefs::mojom::FileChangePtr> file_changes;
+    file_changes.emplace_back(absl::in_place, file_path,
+                              drivefs::mojom::FileChange::Type::kDelete,
+                              metadata.value().stable_id);
+    auto& drivefs_delegate = fake_drivefs_helper_->fake_drivefs().delegate();
+    drivefs_delegate->OnFilesChanged(std::move(file_changes));
+    drivefs_delegate.FlushForTesting();
+  }
+
   absl::optional<drivefs::mojom::DialogResult> last_dialog_result() {
     return last_dialog_result_;
   }
@@ -3266,6 +3281,13 @@
     return;
   }
 
+  if (name == "sendDriveCloudDeleteEvent") {
+    const std::string* path = value.FindString("path");
+    ASSERT_TRUE(path) << "No supplied path to sendDriveFilesChangedEvent";
+    drive_volume_->SendCloudDeleteEvent(*path);
+    return;
+  }
+
   if (HandleGuestOsCommands(name, value, output)) {
     return;
   }
diff --git a/chrome/browser/ash/file_manager/file_manager_string_util.cc b/chrome/browser/ash/file_manager/file_manager_string_util.cc
index 85e1934..d8f37de 100644
--- a/chrome/browser/ash/file_manager/file_manager_string_util.cc
+++ b/chrome/browser/ash/file_manager/file_manager_string_util.cc
@@ -972,6 +972,7 @@
              IDS_FILE_BROWSER_MOVE_TO_TRASH_ITEMS_REMAINING);
   SET_STRING("UNKNOWN_FILESYSTEM_WARNING",
              IDS_FILE_BROWSER_UNKNOWN_FILESYSTEM_WARNING);
+  SET_STRING("UNMOUNT_BUTTON_LABEL", IDS_FILE_BROWSER_UNMOUNT_BUTTON_LABEL);
   SET_STRING("UNMOUNT_DEVICE_BUTTON_LABEL",
              IDS_FILE_BROWSER_UNMOUNT_DEVICE_BUTTON_LABEL);
   SET_STRING("UNMOUNT_FAILED", IDS_FILE_BROWSER_UNMOUNT_FAILED);
diff --git a/chrome/browser/ash/login/screens/gaia_screen.cc b/chrome/browser/ash/login/screens/gaia_screen.cc
index 536e59b..26ce976b 100644
--- a/chrome/browser/ash/login/screens/gaia_screen.cc
+++ b/chrome/browser/ash/login/screens/gaia_screen.cc
@@ -34,14 +34,6 @@
     return false;
   }
 
-  // Always return `true` if the testing switch is set. It will allow to test
-  // the recovery without triggering the real recovery conditions which may be
-  // difficult as of now.
-  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kForceCryptohomeRecoveryForTesting)) {
-    return true;
-  }
-
   // Cryptohome recovery is probably needed when password is entered incorrectly
   // for many times or password changed.
   // TODO(b/197615068): Add metric to record the number of times we prepared for
@@ -99,6 +91,16 @@
   view_->SetGaiaPath(gaia_path);
   view_->SetReauthRequestToken(std::string());
 
+  // Always fetch Gaia reauth request token if the testing switch is set. It
+  // will allow to test the recovery without triggering the real recovery
+  // conditions which may be difficult as of now.
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kForceCryptohomeRecoveryForTesting)) {
+    DCHECK(features::IsCryptohomeRecoveryFlowEnabled());
+    FetchGaiaReauthToken(account);
+    return;
+  }
+
   if (ShouldPrepareForRecovery(account)) {
     auto user_context = std::make_unique<UserContext>();
     user_context->SetAccountId(account);
@@ -216,16 +218,19 @@
   bool is_configured =
       config.HasConfiguredFactor(cryptohome::AuthFactorType::kRecovery);
   if (is_configured) {
-    gaia_reauth_token_fetcher_ =
-        std::make_unique<GaiaReauthTokenFetcher>(base::BindOnce(
-            &GaiaScreen::OnGaiaReauthTokenFetched,
-            weak_ptr_factory_.GetWeakPtr(), user_context->GetAccountId()));
-    gaia_reauth_token_fetcher_->Fetch();
+    FetchGaiaReauthToken(user_context->GetAccountId());
   } else {
     view_->LoadGaiaAsync(user_context->GetAccountId());
   }
 }
 
+void GaiaScreen::FetchGaiaReauthToken(const AccountId& account) {
+  gaia_reauth_token_fetcher_ = std::make_unique<GaiaReauthTokenFetcher>(
+      base::BindOnce(&GaiaScreen::OnGaiaReauthTokenFetched,
+                     weak_ptr_factory_.GetWeakPtr(), account));
+  gaia_reauth_token_fetcher_->Fetch();
+}
+
 void GaiaScreen::OnGaiaReauthTokenFetched(const AccountId& account,
                                           const std::string& token) {
   gaia_reauth_token_fetcher_.reset();
diff --git a/chrome/browser/ash/login/screens/gaia_screen.h b/chrome/browser/ash/login/screens/gaia_screen.h
index cf03551..9a2c509 100644
--- a/chrome/browser/ash/login/screens/gaia_screen.h
+++ b/chrome/browser/ash/login/screens/gaia_screen.h
@@ -75,6 +75,8 @@
 
   void OnGetAuthFactorsConfiguration(std::unique_ptr<UserContext> user_context,
                                      absl::optional<AuthenticationError> error);
+  // Fetch Gaia reauth request token from the recovery service.
+  void FetchGaiaReauthToken(const AccountId& account);
   void OnGaiaReauthTokenFetched(const AccountId& account,
                                 const std::string& token);
 
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc b/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc
index ccf84687..dc393d14 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc
@@ -136,6 +136,15 @@
         );
         chrome.test.succeed();
       },
+      async function getUsbBusInfo() {
+        await chrome.test.assertPromiseRejects(
+            chrome.os.telemetry.getUsbBusInfo(),
+            'Error: Unauthorized access to ' +
+            'chrome.os.telemetry.getUsbBusInfo. ' +
+            '%s'
+        );
+        chrome.test.succeed();
+      },
       async function getVpdInfo() {
         await chrome.test.assertPromiseRejects(
             chrome.os.telemetry.getVpdInfo(),
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.cc b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.cc
index c4e7b72..7538038 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.cc
@@ -386,6 +386,34 @@
   Respond(ArgumentList(telemetry::GetTpmInfo::Results::Create(result)));
 }
 
+// OsTelemetryGetUsbBusInfoFunction --------------------------------------------
+
+void OsTelemetryGetUsbBusInfoFunction::RunIfAllowed() {
+  auto cb = base::BindOnce(&OsTelemetryGetUsbBusInfoFunction::OnResult, this);
+
+  GetRemoteService()->ProbeTelemetryInfo(
+      {crosapi::mojom::ProbeCategoryEnum::kBus}, std::move(cb));
+}
+
+void OsTelemetryGetUsbBusInfoFunction::OnResult(
+    crosapi::mojom::ProbeTelemetryInfoPtr ptr) {
+  if (!ptr || !ptr->bus_result || !ptr->bus_result->is_bus_devices_info()) {
+    Respond(Error("API internal error"));
+    return;
+  }
+
+  telemetry::UsbBusDevices result;
+  auto bus_infos = std::move(ptr->bus_result->get_bus_devices_info());
+  for (auto& info : bus_infos) {
+    if (info->is_usb_bus_info()) {
+      result.devices.push_back(converters::ConvertPtr<telemetry::UsbBusInfo>(
+          std::move(info->get_usb_bus_info())));
+    }
+  }
+
+  Respond(ArgumentList(telemetry::GetUsbBusInfo::Results::Create(result)));
+}
+
 // OsTelemetryGetVpdInfoFunction -----------------------------------------------
 
 void OsTelemetryGetVpdInfoFunction::RunIfAllowed() {
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.h b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.h
index 3e6111b..08f70ab0 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.h
+++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.h
@@ -152,18 +152,6 @@
   void OnResult(crosapi::mojom::ProbeTelemetryInfoPtr ptr);
 };
 
-class OsTelemetryGetVpdInfoFunction : public TelemetryApiFunctionBase {
-  DECLARE_EXTENSION_FUNCTION("os.telemetry.getVpdInfo", OS_TELEMETRY_GETVPDINFO)
-
- private:
-  ~OsTelemetryGetVpdInfoFunction() override = default;
-
-  // BaseTelemetryExtensionApiGuardFunction:
-  void RunIfAllowed() override;
-
-  void OnResult(crosapi::mojom::ProbeTelemetryInfoPtr ptr);
-};
-
 class OsTelemetryGetStatefulPartitionInfoFunction
     : public TelemetryApiFunctionBase {
   DECLARE_EXTENSION_FUNCTION("os.telemetry.getStatefulPartitionInfo",
@@ -190,6 +178,31 @@
   void OnResult(crosapi::mojom::ProbeTelemetryInfoPtr ptr);
 };
 
+class OsTelemetryGetUsbBusInfoFunction : public TelemetryApiFunctionBase {
+  DECLARE_EXTENSION_FUNCTION("os.telemetry.getUsbBusInfo",
+                             OS_TELEMETRY_GETUSBBUSINFO)
+
+ private:
+  ~OsTelemetryGetUsbBusInfoFunction() override = default;
+
+  // BaseTelemetryExtensionApiGuardFunction:
+  void RunIfAllowed() override;
+
+  void OnResult(crosapi::mojom::ProbeTelemetryInfoPtr ptr);
+};
+
+class OsTelemetryGetVpdInfoFunction : public TelemetryApiFunctionBase {
+  DECLARE_EXTENSION_FUNCTION("os.telemetry.getVpdInfo", OS_TELEMETRY_GETVPDINFO)
+
+ private:
+  ~OsTelemetryGetVpdInfoFunction() override = default;
+
+  // BaseTelemetryExtensionApiGuardFunction:
+  void RunIfAllowed() override;
+
+  void OnResult(crosapi::mojom::ProbeTelemetryInfoPtr ptr);
+};
+
 }  // namespace chromeos
 
 #endif  // CHROME_BROWSER_CHROMEOS_EXTENSIONS_TELEMETRY_API_TELEMETRY_API_H_
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_browsertest.cc b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_browsertest.cc
index 06358b9..8cc87026 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_browsertest.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_browsertest.cc
@@ -1411,6 +1411,30 @@
   )");
 }
 
+IN_PROC_BROWSER_TEST_F(TelemetryExtensionTelemetryApiBrowserTest,
+                       GetUsbBusInfo_NoFeatureFlagEnabledError) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // If Probe interface is not available on this version of ash-chrome, this
+  // test suite will no-op.
+  if (!IsServiceAvailable()) {
+    return;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+  // If the permission is not enabled, the method isn't defined
+  // on `chrome.os.telemetry`.
+  CreateExtensionAndRunServiceWorker(R"(
+    chrome.test.runTests([
+      function getUsbBusInfo() {
+        chrome.test.assertThrows(() => { chrome.os.telemetry.getUsbBusInfo(); },
+          [], "chrome.os.telemetry.getUsbBusInfo is not a function");
+
+        chrome.test.succeed();
+      }
+    ]);
+  )");
+}
+
 class TelemetryExtensionTelemetryApiWithoutAdditionalPermissionsBrowserTest
     : public TelemetryExtensionTelemetryApiBrowserTest {
  public:
@@ -1673,4 +1697,140 @@
   )");
 }
 
+class PendingApprovalTelemetryExtensionTelemetryApiBrowserTest
+    : public TelemetryExtensionTelemetryApiBrowserTest {
+ public:
+  PendingApprovalTelemetryExtensionTelemetryApiBrowserTest() {
+    feature_list_.InitAndEnableFeature(
+        extensions_features::kTelemetryExtensionPendingApprovalApi);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(PendingApprovalTelemetryExtensionTelemetryApiBrowserTest,
+                       GetUsbBusInfo_Error) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // If Probe interface is not available on this version of ash-chrome, this
+  // test suite will no-op.
+  if (!IsServiceAvailable()) {
+    return;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+  // Configure FakeProbeService.
+  {
+    auto fake_service_impl = std::make_unique<FakeProbeService>();
+    fake_service_impl->SetExpectedLastRequestedCategories(
+        {crosapi::mojom::ProbeCategoryEnum::kBus});
+
+    SetServiceForTesting(std::move(fake_service_impl));
+  }
+
+  CreateExtensionAndRunServiceWorker(R"(
+    chrome.test.runTests([
+      async function getUsbBusInfo() {
+        await chrome.test.assertPromiseRejects(
+            chrome.os.telemetry.getUsbBusInfo(),
+            'Error: API internal error'
+        );
+        chrome.test.succeed();
+      }
+    ]);
+  )");
+}
+
+IN_PROC_BROWSER_TEST_F(PendingApprovalTelemetryExtensionTelemetryApiBrowserTest,
+                       GetUsbBusInfo_Success) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // If Probe interface is not available on this version of ash-chrome, this
+  // test suite will no-op.
+  if (!IsServiceAvailable()) {
+    return;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+  // Configure FakeProbeService.
+  {
+    auto telemetry_info = crosapi::mojom::ProbeTelemetryInfo::New();
+    {
+      std::vector<crosapi::mojom::ProbeUsbBusInterfaceInfoPtr> interfaces;
+      interfaces.push_back(crosapi::mojom::ProbeUsbBusInterfaceInfo::New(
+          crosapi::mojom::UInt8Value::New(42),
+          crosapi::mojom::UInt8Value::New(41),
+          crosapi::mojom::UInt8Value::New(43),
+          crosapi::mojom::UInt8Value::New(44), "MyDriver"));
+
+      auto fwupd_version = crosapi::mojom::ProbeFwupdFirmwareVersionInfo::New(
+          "MyVersion", crosapi::mojom::ProbeFwupdVersionFormat::kPair);
+
+      auto usb_bus_info = crosapi::mojom::ProbeUsbBusInfo::New();
+      usb_bus_info->class_id = crosapi::mojom::UInt8Value::New(45);
+      usb_bus_info->subclass_id = crosapi::mojom::UInt8Value::New(46);
+      usb_bus_info->protocol_id = crosapi::mojom::UInt8Value::New(47);
+      usb_bus_info->vendor_id = crosapi::mojom::UInt16Value::New(48);
+      usb_bus_info->product_id = crosapi::mojom::UInt16Value::New(49);
+      usb_bus_info->interfaces = std::move(interfaces);
+      usb_bus_info->fwupd_firmware_version_info = std::move(fwupd_version);
+      usb_bus_info->version = crosapi::mojom::ProbeUsbVersion::kUsb3;
+      usb_bus_info->spec_speed = crosapi::mojom::ProbeUsbSpecSpeed::k20Gbps;
+
+      auto bus_info =
+          crosapi::mojom::ProbeBusInfo::NewUsbBusInfo(std::move(usb_bus_info));
+
+      std::vector<crosapi::mojom::ProbeBusInfoPtr> input;
+      input.push_back(std::move(bus_info));
+
+      telemetry_info->bus_result =
+          crosapi::mojom::ProbeBusResult::NewBusDevicesInfo(std::move(input));
+    }
+
+    auto fake_service_impl = std::make_unique<FakeProbeService>();
+    fake_service_impl->SetProbeTelemetryInfoResponse(std::move(telemetry_info));
+    fake_service_impl->SetExpectedLastRequestedCategories(
+        {crosapi::mojom::ProbeCategoryEnum::kBus});
+
+    SetServiceForTesting(std::move(fake_service_impl));
+  }
+
+  CreateExtensionAndRunServiceWorker(R"(
+    chrome.test.runTests([
+      async function getUsbBusInfo() {
+        const result = await chrome.os.telemetry.getUsbBusInfo();
+        chrome.test.assertEq(
+          // The dictionary members are ordered lexicographically by the Unicode
+          // codepoints that comprise their identifiers.
+          {
+            "devices": [
+              {
+                "classId": 45,
+                "fwupdFirmwareVersionInfo": {
+                  "version": "MyVersion",
+                  "version_format":"pair"
+                },
+                "interfaces": [
+                  {
+                    "classId": 41,
+                    "driver": "MyDriver",
+                    "interfaceNumber": 42,
+                    "protocolId": 44,
+                    "subclassId": 43
+                  }
+                ],
+                "productId": 49,
+                "protocolId": 47,
+                "spec_speed": "n20Gbps",
+                "subclassId": 46,
+                "vendorId": 48,
+                "version": "usb3"
+              }
+            ]
+          }, result);
+        chrome.test.succeed();
+      }
+    ]);
+  )");
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.cc b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.cc
index 6002e3fc..ae6f015a 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.cc
@@ -310,6 +310,69 @@
   return result;
 }
 
+telemetry_api::UsbBusInterfaceInfo UncheckedConvertPtr(
+    telemetry_service::ProbeUsbBusInterfaceInfoPtr input) {
+  telemetry_api::UsbBusInterfaceInfo result;
+
+  if (input->interface_number) {
+    result.interface_number = input->interface_number->value;
+  }
+  if (input->class_id) {
+    result.class_id = input->class_id->value;
+  }
+  if (input->subclass_id) {
+    result.subclass_id = input->subclass_id->value;
+  }
+  if (input->protocol_id) {
+    result.protocol_id = input->protocol_id->value;
+  }
+  result.driver = input->driver;
+
+  return result;
+}
+
+telemetry_api::FwupdFirmwareVersionInfo UncheckedConvertPtr(
+    telemetry_service::ProbeFwupdFirmwareVersionInfoPtr input) {
+  telemetry_api::FwupdFirmwareVersionInfo result;
+
+  result.version = input->version;
+  result.version_format = Convert(input->version_format);
+
+  return result;
+}
+
+telemetry_api::UsbBusInfo UncheckedConvertPtr(
+    telemetry_service::ProbeUsbBusInfoPtr input) {
+  telemetry_api::UsbBusInfo result;
+
+  if (input->class_id) {
+    result.class_id = input->class_id->value;
+  }
+  if (input->subclass_id) {
+    result.subclass_id = input->subclass_id->value;
+  }
+  if (input->protocol_id) {
+    result.protocol_id = input->protocol_id->value;
+  }
+  if (input->vendor_id) {
+    result.vendor_id = input->vendor_id->value;
+  }
+  if (input->product_id) {
+    result.product_id = input->product_id->value;
+  }
+  if (input->interfaces) {
+    result.interfaces = ConvertPtrVector<telemetry_api::UsbBusInterfaceInfo>(
+        std::move(input->interfaces.value()));
+  }
+  result.fwupd_firmware_version_info =
+      ConvertPtr<telemetry_api::FwupdFirmwareVersionInfo>(
+          std::move(input->fwupd_firmware_version_info));
+  result.version = Convert(input->version);
+  result.spec_speed = Convert(input->spec_speed);
+
+  return result;
+}
+
 }  // namespace unchecked
 
 telemetry_api::CpuArchitectureEnum Convert(
@@ -390,5 +453,74 @@
   NOTREACHED();
 }
 
+telemetry_api::FwupdVersionFormat Convert(
+    telemetry_service::ProbeFwupdVersionFormat input) {
+  switch (input) {
+    case crosapi::mojom::ProbeFwupdVersionFormat::kUnknown:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PLAIN;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kPlain:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PLAIN;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kNumber:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_NUMBER;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kPair:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PAIR;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kTriplet:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_TRIPLET;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kQuad:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_QUAD;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kBcd:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_BCD;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kIntelMe:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_INTELME;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kIntelMe2:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_INTELME2;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kSurfaceLegacy:
+      return telemetry_api::FwupdVersionFormat::
+          FWUPD_VERSION_FORMAT_SURFACELEGACY;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kSurface:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_SURFACE;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kDellBios:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_DELLBIOS;
+    case crosapi::mojom::ProbeFwupdVersionFormat::kHex:
+      return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_HEX;
+  }
+  NOTREACHED();
+}
+
+telemetry_api::UsbVersion Convert(telemetry_service::ProbeUsbVersion input) {
+  switch (input) {
+    case crosapi::mojom::ProbeUsbVersion::kUnknown:
+      return telemetry_api::UsbVersion::USB_VERSION_UNKNOWN;
+    case crosapi::mojom::ProbeUsbVersion::kUsb1:
+      return telemetry_api::UsbVersion::USB_VERSION_USB1;
+    case crosapi::mojom::ProbeUsbVersion::kUsb2:
+      return telemetry_api::UsbVersion::USB_VERSION_USB2;
+    case crosapi::mojom::ProbeUsbVersion::kUsb3:
+      return telemetry_api::UsbVersion::USB_VERSION_USB3;
+  }
+  NOTREACHED();
+}
+
+telemetry_api::UsbSpecSpeed Convert(
+    telemetry_service::ProbeUsbSpecSpeed input) {
+  switch (input) {
+    case crosapi::mojom::ProbeUsbSpecSpeed::kUnknown:
+      return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_UNKNOWN;
+    case crosapi::mojom::ProbeUsbSpecSpeed::k1_5Mbps:
+      return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N1_5MBPS;
+    case crosapi::mojom::ProbeUsbSpecSpeed::k12Mbps:
+      return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N12MBPS;
+    case crosapi::mojom::ProbeUsbSpecSpeed::k480Mbps:
+      return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N480MBPS;
+    case crosapi::mojom::ProbeUsbSpecSpeed::k5Gbps:
+      return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N5GBPS;
+    case crosapi::mojom::ProbeUsbSpecSpeed::k10Gbps:
+      return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N10GBPS;
+    case crosapi::mojom::ProbeUsbSpecSpeed::k20Gbps:
+      return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N20GBPS;
+  }
+  NOTREACHED();
+}
+
 }  // namespace converters
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.h b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.h
index bcce467..c3f1289 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.h
+++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.h
@@ -74,6 +74,15 @@
 chromeos::api::os_telemetry::TpmInfo UncheckedConvertPtr(
     crosapi::mojom::ProbeTpmInfoPtr input);
 
+chromeos::api::os_telemetry::UsbBusInterfaceInfo UncheckedConvertPtr(
+    crosapi::mojom::ProbeUsbBusInterfaceInfoPtr input);
+
+chromeos::api::os_telemetry::FwupdFirmwareVersionInfo UncheckedConvertPtr(
+    crosapi::mojom::ProbeFwupdFirmwareVersionInfoPtr input);
+
+chromeos::api::os_telemetry::UsbBusInfo UncheckedConvertPtr(
+    crosapi::mojom::ProbeUsbBusInfoPtr input);
+
 }  // namespace unchecked
 
 chromeos::api::os_telemetry::CpuArchitectureEnum Convert(
@@ -88,6 +97,15 @@
 chromeos::api::os_telemetry::TpmGSCVersion Convert(
     crosapi::mojom::ProbeTpmGSCVersion input);
 
+chromeos::api::os_telemetry::FwupdVersionFormat Convert(
+    crosapi::mojom::ProbeFwupdVersionFormat input);
+
+chromeos::api::os_telemetry::UsbVersion Convert(
+    crosapi::mojom::ProbeUsbVersion input);
+
+chromeos::api::os_telemetry::UsbSpecSpeed Convert(
+    crosapi::mojom::ProbeUsbSpecSpeed input);
+
 template <class OutputT, class InputT>
 std::vector<OutputT> ConvertPtrVector(std::vector<InputT> input) {
   std::vector<OutputT> output;
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters_unittest.cc b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters_unittest.cc
index c19f9cf9..b269c86 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters_unittest.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters_unittest.cc
@@ -742,5 +742,198 @@
                 *dictionary_attack_result.lockout_seconds_remaining));
 }
 
+TEST(TelemetryApiConverters, UsbVersion) {
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbVersion::kUnknown),
+            telemetry_api::UsbVersion::USB_VERSION_UNKNOWN);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbVersion::kUsb1),
+            telemetry_api::UsbVersion::USB_VERSION_USB1);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbVersion::kUsb2),
+            telemetry_api::UsbVersion::USB_VERSION_USB2);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbVersion::kUsb3),
+            telemetry_api::UsbVersion::USB_VERSION_USB3);
+}
+
+TEST(TelemetryApiConverters, UsbSpecSpeed) {
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::kUnknown),
+            telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_UNKNOWN);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k1_5Mbps),
+            telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N1_5MBPS);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k12Mbps),
+            telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N12MBPS);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k480Mbps),
+            telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N480MBPS);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k5Gbps),
+            telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N5GBPS);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k10Gbps),
+            telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N10GBPS);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k20Gbps),
+            telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N20GBPS);
+}
+
+TEST(TelemetryApiConverters, FwupdVersionFormat) {
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kUnknown),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PLAIN);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kPlain),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PLAIN);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kNumber),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_NUMBER);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kPair),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PAIR);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kTriplet),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_TRIPLET);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kBcd),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_BCD);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kIntelMe),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_INTELME);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kIntelMe2),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_INTELME2);
+
+  EXPECT_EQ(
+      Convert(crosapi::mojom::ProbeFwupdVersionFormat::kSurfaceLegacy),
+      telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_SURFACELEGACY);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kSurface),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_SURFACE);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kDellBios),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_DELLBIOS);
+
+  EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kHex),
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_HEX);
+}
+
+TEST(TelemetryApiConverters, FwupdFirmwareVersionInfo) {
+  constexpr char kVersion[] = "MyVersion";
+
+  auto input = crosapi::mojom::ProbeFwupdFirmwareVersionInfo::New(
+      kVersion, crosapi::mojom::ProbeFwupdVersionFormat::kHex);
+
+  auto result =
+      ConvertPtr<telemetry_api::FwupdFirmwareVersionInfo>(std::move(input));
+
+  EXPECT_EQ(result.version, kVersion);
+  EXPECT_EQ(result.version_format,
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_HEX);
+}
+
+TEST(TelemetryApiConverters, UsbBusInterfaceInfo) {
+  constexpr uint8_t kInterfaceNumber = 41;
+  constexpr uint8_t kClassId = 42;
+  constexpr uint8_t kSubclassId = 43;
+  constexpr uint8_t kProtocolId = 44;
+  constexpr char kDriver[] = "MyDriver";
+
+  auto input = crosapi::mojom::ProbeUsbBusInterfaceInfo::New(
+      crosapi::mojom::UInt8Value::New(kInterfaceNumber),
+      crosapi::mojom::UInt8Value::New(kClassId),
+      crosapi::mojom::UInt8Value::New(kSubclassId),
+      crosapi::mojom::UInt8Value::New(kProtocolId), kDriver);
+
+  auto result =
+      ConvertPtr<telemetry_api::UsbBusInterfaceInfo>(std::move(input));
+
+  ASSERT_TRUE(result.interface_number);
+  EXPECT_EQ(static_cast<uint8_t>(*result.interface_number), kInterfaceNumber);
+  ASSERT_TRUE(result.class_id);
+  EXPECT_EQ(static_cast<uint8_t>(*result.class_id), kClassId);
+  ASSERT_TRUE(result.subclass_id);
+  EXPECT_EQ(static_cast<uint8_t>(*result.subclass_id), kSubclassId);
+  ASSERT_TRUE(result.protocol_id);
+  EXPECT_EQ(static_cast<uint8_t>(*result.protocol_id), kProtocolId);
+  ASSERT_TRUE(result.driver);
+  EXPECT_EQ(result.driver, kDriver);
+}
+
+TEST(TelemetryApiConverters, UsbBusInfo) {
+  constexpr uint8_t kInterfaceNumberInterface = 41;
+  constexpr uint8_t kClassIdInterface = 42;
+  constexpr uint8_t kSubclassIdInterface = 43;
+  constexpr uint8_t kProtocolIdInterface = 44;
+  constexpr char kDriverInterface[] = "MyDriver";
+
+  std::vector<crosapi::mojom::ProbeUsbBusInterfaceInfoPtr> interfaces;
+  interfaces.push_back(crosapi::mojom::ProbeUsbBusInterfaceInfo::New(
+      crosapi::mojom::UInt8Value::New(kInterfaceNumberInterface),
+      crosapi::mojom::UInt8Value::New(kClassIdInterface),
+      crosapi::mojom::UInt8Value::New(kSubclassIdInterface),
+      crosapi::mojom::UInt8Value::New(kProtocolIdInterface), kDriverInterface));
+
+  constexpr uint8_t kClassId = 45;
+  constexpr uint8_t kSubclassId = 46;
+  constexpr uint8_t kProtocolId = 47;
+  constexpr uint16_t kVendor = 48;
+  constexpr uint16_t kProductId = 49;
+
+  constexpr char kVersion[] = "MyVersion";
+
+  auto fwupd_version = crosapi::mojom::ProbeFwupdFirmwareVersionInfo::New(
+      kVersion, crosapi::mojom::ProbeFwupdVersionFormat::kPair);
+
+  auto input = crosapi::mojom::ProbeUsbBusInfo::New();
+  input->class_id = crosapi::mojom::UInt8Value::New(kClassId);
+  input->subclass_id = crosapi::mojom::UInt8Value::New(kSubclassId);
+  input->protocol_id = crosapi::mojom::UInt8Value::New(kProtocolId);
+  input->vendor_id = crosapi::mojom::UInt16Value::New(kVendor);
+  input->product_id = crosapi::mojom::UInt16Value::New(kProductId);
+  input->interfaces = std::move(interfaces);
+  input->fwupd_firmware_version_info = std::move(fwupd_version);
+  input->version = crosapi::mojom::ProbeUsbVersion::kUsb3;
+  input->spec_speed = crosapi::mojom::ProbeUsbSpecSpeed::k20Gbps;
+
+  auto result = ConvertPtr<telemetry_api::UsbBusInfo>(std::move(input));
+
+  ASSERT_TRUE(result.class_id);
+  EXPECT_EQ(static_cast<uint8_t>(*result.class_id), kClassId);
+  ASSERT_TRUE(result.subclass_id);
+  EXPECT_EQ(static_cast<uint8_t>(*result.subclass_id), kSubclassId);
+  ASSERT_TRUE(result.protocol_id);
+  EXPECT_EQ(static_cast<uint8_t>(*result.protocol_id), kProtocolId);
+  ASSERT_TRUE(result.product_id);
+  EXPECT_EQ(static_cast<uint16_t>(*result.product_id), kProductId);
+  ASSERT_TRUE(result.vendor_id);
+  EXPECT_EQ(static_cast<uint16_t>(*result.vendor_id), kVendor);
+
+  ASSERT_EQ(result.interfaces.size(), 1UL);
+  ASSERT_TRUE(result.interfaces[0].interface_number);
+  EXPECT_EQ(static_cast<uint8_t>(*result.interfaces[0].interface_number),
+            kInterfaceNumberInterface);
+  ASSERT_TRUE(result.interfaces[0].class_id);
+  EXPECT_EQ(static_cast<uint8_t>(*result.interfaces[0].class_id),
+            kClassIdInterface);
+  ASSERT_TRUE(result.interfaces[0].subclass_id);
+  EXPECT_EQ(static_cast<uint8_t>(*result.interfaces[0].subclass_id),
+            kSubclassIdInterface);
+  ASSERT_TRUE(result.interfaces[0].protocol_id);
+  EXPECT_EQ(static_cast<uint8_t>(*result.interfaces[0].protocol_id),
+            kProtocolIdInterface);
+  ASSERT_TRUE(result.interfaces[0].driver);
+  EXPECT_EQ(result.interfaces[0].driver, kDriverInterface);
+
+  ASSERT_TRUE(result.fwupd_firmware_version_info);
+  EXPECT_EQ(result.fwupd_firmware_version_info->version, kVersion);
+  EXPECT_EQ(result.fwupd_firmware_version_info->version_format,
+            telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PAIR);
+
+  EXPECT_EQ(result.version, telemetry_api::UsbVersion::USB_VERSION_USB3);
+  EXPECT_EQ(result.spec_speed,
+            telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N20GBPS);
+}
+
 }  // namespace converters
 }  // namespace chromeos
diff --git a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h
index c4e24346..0799386c 100644
--- a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h
+++ b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CAPABILITIES_FETCHER_H_
 #define CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CAPABILITIES_FETCHER_H_
 
+#include "base/containers/flat_set.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 namespace autofill {
@@ -29,6 +30,10 @@
   virtual bool IsTriggerFormSupported(
       const url::Origin& origin,
       autofill::FormSignature form_signature) = 0;
+  // Returns form signatures cached for `origin` if present. Otherwise returns
+  // an empty set.
+  virtual base::flat_set<autofill::FormSignature> GetFormsToFill(
+      const url::Origin& origin) = 0;
 };
 
 #endif  // CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CAPABILITIES_FETCHER_H_
diff --git a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.cc b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.cc
index 27d9e57..8d09773 100644
--- a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.cc
@@ -8,7 +8,6 @@
 
 #include "base/metrics/histogram_functions.h"
 #include "chrome/browser/fast_checkout/fast_checkout_features.h"
-#include "chrome/browser/fast_checkout/fast_checkout_funnels.pb.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
@@ -150,18 +149,31 @@
 
   for (const ::fast_checkout::FastCheckoutFunnels_FastCheckoutFunnel&
            funnel_proto : funnels.funnels()) {
-    FastCheckoutFunnel funnel;
-    for (uint64_t form_signature : funnel_proto.trigger()) {
-      funnel.trigger.emplace(form_signature);
-    }
-    for (uint64_t form_signature : funnel_proto.fill()) {
-      funnel.fill.emplace(form_signature);
-    }
-    for (const std::string& domain : funnel_proto.domains()) {
-      GURL url = GURL(domain);
-      if (url.is_valid() && url.SchemeIsHTTPOrHTTPS()) {
-        cache_.emplace(url::Origin::Create(url), funnel);
-      }
+    AddFunnelToCache(funnel_proto);
+  }
+}
+
+void FastCheckoutCapabilitiesFetcherImpl::AddFunnelToCache(
+    const ::fast_checkout::FastCheckoutFunnels_FastCheckoutFunnel&
+        funnel_proto) {
+  // There has to be at least one trigger form signature for a funnel. Otherwise
+  // a run could never be triggered successfully.
+  if (funnel_proto.trigger().empty()) {
+    return;
+  }
+
+  FastCheckoutFunnel funnel;
+  for (uint64_t form_signature : funnel_proto.trigger()) {
+    funnel.trigger.emplace(form_signature);
+  }
+  for (uint64_t form_signature : funnel_proto.fill()) {
+    funnel.fill.emplace(form_signature);
+  }
+
+  for (const std::string& domain : funnel_proto.domains()) {
+    GURL url = GURL(domain);
+    if (url.is_valid() && url.SchemeIsHTTPOrHTTPS()) {
+      cache_.emplace(url::Origin::Create(url), funnel);
     }
   }
 }
@@ -191,3 +203,17 @@
                 kEntryAvailableAndFormNotSupported);
   return is_supported;
 }
+
+base::flat_set<autofill::FormSignature>
+FastCheckoutCapabilitiesFetcherImpl::GetFormsToFill(const url::Origin& origin) {
+  if (!cache_.contains(origin)) {
+    return {};
+  }
+  const FastCheckoutFunnel& funnel = cache_.at(origin);
+  // All `FastCheckoutFunnel::trigger` and `FastCheckoutFunnel::fill` forms
+  // should be attempted to be filled, in any order. For that reason, merge the
+  // two sets into one set (`forms_to_fill`) and return it.
+  base::flat_set<autofill::FormSignature> forms_to_fill = funnel.trigger;
+  forms_to_fill.insert(funnel.fill.begin(), funnel.fill.end());
+  return forms_to_fill;
+}
diff --git a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.h b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.h
index 69f8f7e..c76ecd2b 100644
--- a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.h
+++ b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.h
@@ -7,6 +7,7 @@
 
 #include "chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h"
 
+#include "chrome/browser/fast_checkout/fast_checkout_funnels.pb.h"
 #include "components/autofill/core/common/signatures.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
@@ -79,13 +80,21 @@
   void FetchCapabilities() override;
   bool IsTriggerFormSupported(const url::Origin& origin,
                               autofill::FormSignature form_signature) override;
+  base::flat_set<autofill::FormSignature> GetFormsToFill(
+      const url::Origin& origin) override;
 
  private:
   struct FastCheckoutFunnel {
     FastCheckoutFunnel();
     ~FastCheckoutFunnel();
     FastCheckoutFunnel(const FastCheckoutFunnel&);
+    // `trigger` form signatures allow a fast checkout run to start by showing
+    // the bottomsheet if an input field of their forms got focused by the user.
+    // They will also be attempted to be autofilled, just like `fill` form
+    // signatures.
     base::flat_set<autofill::FormSignature> trigger;
+    // `fill` form signatures don't trigger a fast checkout run but are
+    // attempted to be autofilled.
     base::flat_set<autofill::FormSignature> fill;
   };
   // Called when the request's response arrives.
@@ -94,6 +103,16 @@
   // Returns if the cache is stale, i.e. if `kCacheTimeout` minutes since the
   // last successful request have passed or if no request was done yet.
   bool IsCacheStale() const;
+  // Converts funnel proto message to `FastCheckoutFunnel` and adds it to
+  // `cache_`.
+  void AddFunnelToCache(
+      const ::fast_checkout::FastCheckoutFunnels_FastCheckoutFunnel&
+          funnel_proto);
+  // Converts `trigger` and `fill` fields from the funnel proto message to
+  // `FastCheckoutFunnel`
+  FastCheckoutFunnel ConvertToFunnel(
+      const ::google::protobuf::RepeatedField<uint64_t>& trigger,
+      const ::google::protobuf::RepeatedField<uint64_t>& fill) const;
   // URL loader object for the gstatic request. If `url_loader_` is not null, a
   // request is currently ongoing.
   std::unique_ptr<network::SimpleURLLoader> url_loader_ = nullptr;
diff --git a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl_unittest.cc
index f558427b..8586cecd 100644
--- a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl_unittest.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl_unittest.cc
@@ -10,12 +10,14 @@
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
 
 namespace {
 constexpr char kFastCheckoutFunnelsUrl[] =
     "https://www.gstatic.com/autofill/fast_checkout/funnels.binarypb";
 constexpr char kInvalidResponseBody[] = "invalid response body";
 constexpr char kDomain[] = "https://www.example.com";
+constexpr char kDomainWithoutTriggerForm[] = "https://www.example3.com";
 constexpr char kUnsupportedDomain[] = "https://www.example2.com";
 constexpr char kInvalidDomain[] = "invaliddomain";
 constexpr char kNonHttpSDomain[] = "file://path/to/a/file";
@@ -44,6 +46,11 @@
     funnel->add_trigger(kTriggerFormSignature.value());
     funnel->add_fill(kFillFormSignature.value());
   }
+  // Add one additional funnel with empty `trigger` field.
+  ::fast_checkout::FastCheckoutFunnels_FastCheckoutFunnel* funnel =
+      funnels.add_funnels();
+  funnel->add_domains(kDomainWithoutTriggerForm);
+  funnel->add_fill(kFillFormSignature.value());
   return funnels.SerializeAsString();
 }
 }  // namespace
@@ -58,6 +65,8 @@
     unsupported_origin_ = url::Origin::Create(GURL(kUnsupportedDomain));
     invalid_origin_ = url::Origin::Create(GURL(kInvalidDomain));
     non_http_s_origin_ = url::Origin::Create(GURL(kNonHttpSDomain));
+    origin_without_trigger_form_ =
+        url::Origin::Create(GURL(kDomainWithoutTriggerForm));
   }
 
  protected:
@@ -90,6 +99,9 @@
   const url::Origin& GetUnsupportedOrigin() { return unsupported_origin_; }
   const url::Origin& GetInvalidOrigin() { return invalid_origin_; }
   const url::Origin& GetNonHttpSOrigin() { return non_http_s_origin_; }
+  const url::Origin& GetOriginWithoutTriggerForm() {
+    return origin_without_trigger_form_;
+  }
 
   bool FetchCapabilitiesAndSimulateResponse(
       net::HttpStatusCode status = net::HTTP_OK) {
@@ -113,6 +125,7 @@
   url::Origin unsupported_origin_;
   url::Origin invalid_origin_;
   url::Origin non_http_s_origin_;
+  url::Origin origin_without_trigger_form_;
 };
 
 TEST_F(FastCheckoutCapabilitiesFetcherImplTest,
@@ -273,3 +286,34 @@
           kEntryNotAvailable,
       1u);
 }
+
+TEST_F(FastCheckoutCapabilitiesFetcherImplTest,
+       GetFormsToFill_InvalidDomain_ReturnsEmptySet) {
+  EXPECT_TRUE(FetchCapabilitiesAndSimulateResponse());
+  EXPECT_THAT(fetcher()->GetFormsToFill(GetInvalidOrigin()),
+              testing::IsEmpty());
+}
+
+TEST_F(FastCheckoutCapabilitiesFetcherImplTest,
+       GetFormsToFill_ValidDomain_ReturnsTriggerAndFillForms) {
+  EXPECT_TRUE(FetchCapabilitiesAndSimulateResponse());
+
+  base::flat_set<autofill::FormSignature> forms_to_fill =
+      fetcher()->GetFormsToFill(GetOrigin());
+  EXPECT_THAT(forms_to_fill, testing::UnorderedElementsAre(
+                                 kTriggerFormSignature, kFillFormSignature));
+}
+
+TEST_F(FastCheckoutCapabilitiesFetcherImplTest, NoTriggerForm) {
+  EXPECT_TRUE(FetchCapabilitiesAndSimulateResponse());
+
+  EXPECT_FALSE(fetcher()->IsTriggerFormSupported(GetOriginWithoutTriggerForm(),
+                                                 kTriggerFormSignature));
+  EXPECT_THAT(fetcher()->GetFormsToFill(GetOriginWithoutTriggerForm()),
+              testing::IsEmpty());
+  histogram_tester().ExpectUniqueSample(
+      kUmaKeyCacheStateIsTriggerFormSupported,
+      FastCheckoutCapabilitiesFetcherImpl::CacheStateForIsTriggerFormSupported::
+          kEntryNotAvailable,
+      1u);
+}
diff --git a/chrome/browser/fast_checkout/fast_checkout_client.h b/chrome/browser/fast_checkout/fast_checkout_client.h
index 2762ea2..c43b0848 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client.h
+++ b/chrome/browser/fast_checkout/fast_checkout_client.h
@@ -5,10 +5,12 @@
 #ifndef CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CLIENT_H_
 #define CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CLIENT_H_
 
+#include "base/memory/weak_ptr.h"
+
 class GURL;
 
 namespace autofill {
-class AutofillDriver;
+class AutofillManager;
 struct FormData;
 struct FormFieldData;
 }  // namespace autofill
@@ -28,10 +30,11 @@
       content::WebContents* web_contents);
 
   // Starts the fast checkout run. Returns true if the run was successful.
-  virtual bool TryToStart(const GURL& url,
-                          const autofill::FormData& form,
-                          const autofill::FormFieldData& field,
-                          autofill::AutofillDriver* autofill_driver) = 0;
+  virtual bool TryToStart(
+      const GURL& url,
+      const autofill::FormData& form,
+      const autofill::FormFieldData& field,
+      base::WeakPtr<autofill::AutofillManager> autofill_manager) = 0;
 
   // Stops the fast checkout run. Resets internal UI state to `kNotShownYet` if
   // `allow_further_runs == true`.
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl.cc b/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
index 737f063..a220000 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
@@ -10,7 +10,7 @@
 #include "chrome/browser/fast_checkout/fast_checkout_personal_data_helper_impl.h"
 #include "chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h"
 #include "chrome/browser/ui/autofill/chrome_autofill_client.h"
-#include "components/autofill/content/browser/content_autofill_driver.h"
+#include "components/autofill/core/browser/browser_autofill_manager.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "content/public/browser/web_contents_user_data.h"
@@ -21,12 +21,13 @@
     : content::WebContentsUserData<FastCheckoutClientImpl>(*web_contents),
       autofill_client_(
           autofill::ChromeAutofillClient::FromWebContents(web_contents)),
+      fetcher_(FastCheckoutCapabilitiesFetcherFactory::GetForBrowserContext(
+          web_contents->GetBrowserContext())),
       personal_data_helper_(
           std::make_unique<FastCheckoutPersonalDataHelperImpl>(web_contents)),
       trigger_validator_(std::make_unique<FastCheckoutTriggerValidatorImpl>(
           autofill_client_,
-          FastCheckoutCapabilitiesFetcherFactory::GetForBrowserContext(
-              web_contents->GetBrowserContext()),
+          fetcher_,
           personal_data_helper_.get())) {}
 
 FastCheckoutClientImpl::~FastCheckoutClientImpl() {
@@ -40,25 +41,23 @@
     const GURL& url,
     const autofill::FormData& form,
     const autofill::FormFieldData& field,
-    autofill::AutofillDriver* autofill_driver) {
-  autofill::ContentAutofillDriver* content_autofill_driver =
-      static_cast<autofill::ContentAutofillDriver*>(autofill_driver);
-
-  if (!content_autofill_driver) {
+    base::WeakPtr<autofill::AutofillManager> autofill_manager) {
+  if (!autofill_manager) {
     return false;
   }
 
   if (!trigger_validator_->ShouldRun(form, field, fast_checkout_ui_state_,
-                                     is_running_, content_autofill_driver)) {
+                                     is_running_, autofill_manager)) {
     return false;
   }
 
-  autofill_driver_ = content_autofill_driver;
-  url_ = url;
+  autofill_manager_ = autofill_manager;
+  origin_ = url::Origin::Create(url);
   is_running_ = true;
   personal_data_manager_observation_.Observe(
       personal_data_helper_->GetPersonalDataManager());
 
+  SetFormsToFill();
   SetShouldSuppressKeyboard(true);
 
   fast_checkout_controller_ = CreateFastCheckoutController();
@@ -78,8 +77,8 @@
 }
 
 void FastCheckoutClientImpl::SetShouldSuppressKeyboard(bool suppress) {
-  if (autofill_driver_) {
-    autofill_driver_->SetShouldSuppressKeyboard(suppress);
+  if (autofill_manager_) {
+    autofill_manager_->SetShouldSuppressKeyboard(suppress);
   }
 }
 
@@ -91,6 +90,9 @@
 
 void FastCheckoutClientImpl::Stop(bool allow_further_runs) {
   if (allow_further_runs) {
+    forms_to_fill_.clear();
+    selected_autofill_profile_.reset();
+    selected_credit_card_.reset();
     fast_checkout_ui_state_ = FastCheckoutUIState::kNotShownYet;
   } else if (IsShowing()) {
     fast_checkout_ui_state_ = FastCheckoutUIState::kWasShown;
@@ -104,12 +106,7 @@
   // stops.
   SetShouldSuppressKeyboard(false);
 
-  // There is one `ContentAutofillDriver` instance per frame but only one
-  // instance of this class per `WebContents`. Reset `autofill_driver_` here to
-  // avoid the issue of having a non-null, invalid pointer. This method is
-  // (also) called from `~BrowserAutofillManager()` which is owned by
-  // `ContentAutofillDriver`.
-  autofill_driver_ = nullptr;
+  autofill_manager_ = nullptr;
 }
 
 bool FastCheckoutClientImpl::IsShowing() const {
@@ -133,13 +130,26 @@
 void FastCheckoutClientImpl::OnOptionsSelected(
     std::unique_ptr<autofill::AutofillProfile> selected_profile,
     std::unique_ptr<autofill::CreditCard> selected_credit_card) {
-  // TODO(crbug.com/1334642): Signal that FC options have been selected.
   OnHidden();
+  selected_autofill_profile_ = std::move(selected_profile);
+  selected_credit_card_ = std::move(selected_credit_card);
+}
+
+void FastCheckoutClientImpl::SetFormsToFill() {
+  if (!fetcher_) {
+    return;
+  }
+  DCHECK(forms_to_fill_.empty());
+  for (autofill::FormSignature form_signature :
+       fetcher_->GetFormsToFill(origin_)) {
+    forms_to_fill_.emplace(form_signature, FillingState::kNotFilled);
+  }
 }
 
 void FastCheckoutClientImpl::OnDismiss() {
   OnHidden();
   Stop(/*allow_further_runs=*/false);
+  forms_to_fill_.clear();
 }
 
 void FastCheckoutClientImpl::OnPersonalDataChanged() {
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl.h b/chrome/browser/fast_checkout/fast_checkout_client_impl.h
index ed0d56ce..b6e82ee61 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client_impl.h
+++ b/chrome/browser/fast_checkout/fast_checkout_client_impl.h
@@ -6,12 +6,12 @@
 #define CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CLIENT_IMPL_H_
 
 #include "base/scoped_observation.h"
+#include "chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h"
 #include "chrome/browser/fast_checkout/fast_checkout_client.h"
 #include "chrome/browser/fast_checkout/fast_checkout_enums.h"
 #include "chrome/browser/fast_checkout/fast_checkout_personal_data_helper.h"
 #include "chrome/browser/fast_checkout/fast_checkout_trigger_validator.h"
 #include "chrome/browser/ui/fast_checkout/fast_checkout_controller_impl.h"
-#include "components/autofill/content/browser/content_autofill_driver.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_user_data.h"
@@ -36,10 +36,11 @@
   FastCheckoutClientImpl& operator=(const FastCheckoutClientImpl&) = delete;
 
   // FastCheckoutClient:
-  bool TryToStart(const GURL& url,
-                  const autofill::FormData& form,
-                  const autofill::FormFieldData& field,
-                  autofill::AutofillDriver* autofill_driver) override;
+  bool TryToStart(
+      const GURL& url,
+      const autofill::FormData& form,
+      const autofill::FormFieldData& field,
+      base::WeakPtr<autofill::AutofillManager> autofill_manager) override;
   void Stop(bool allow_further_runs) override;
   bool IsRunning() const override;
   bool IsShowing() const override;
@@ -50,6 +51,19 @@
       std::unique_ptr<autofill::CreditCard> selected_credit_card) override;
   void OnDismiss() override;
 
+  // Filling state of a form during a run.
+  enum class FillingState {
+    // Form was not attempted to be filled.
+    kNotFilled = 0,
+
+    // Autofill was invoked on the form but this clas was not notified about the
+    // form being filled.
+    kFilling = 1,
+
+    // This form has been filled.
+    kFilled = 2
+  };
+
 #if defined(UNIT_TEST)
   void set_trigger_validator_for_test(
       std::unique_ptr<FastCheckoutTriggerValidator> trigger_validator) {
@@ -60,8 +74,21 @@
     autofill_client_ = autofill_client;
   }
 
-  autofill::ContentAutofillDriver* get_autofill_driver_for_test() {
-    return autofill_driver_;
+  base::WeakPtr<autofill::AutofillManager> get_autofill_manager_for_test() {
+    return autofill_manager_;
+  }
+
+  autofill::AutofillProfile* get_autofill_profile_for_test() {
+    return selected_autofill_profile_.get();
+  }
+
+  autofill::CreditCard* get_credit_card_for_test() {
+    return selected_credit_card_.get();
+  }
+
+  const base::flat_map<autofill::FormSignature, FillingState>&
+  get_forms_to_fill_for_test() {
+    return forms_to_fill_;
   }
 #endif
 
@@ -98,15 +125,19 @@
   // Logs `message` to chrome://autofill-internals.
   void LogAutofillInternals(std::string message) const;
 
+  // Populates map with forms to fill at the beginning of the run.
+  void SetFormsToFill();
+
   // The `ChromeAutofillClient` instanced attached to the same `WebContents`.
   raw_ptr<autofill::AutofillClient> autofill_client_ = nullptr;
 
-  // The `ContentAutofillDriver` instance invoking the fast checkout run. This
-  // class generally outlives `autofill_driver_` so extra care needs to be taken
-  // with this pointer. It gets reset in `Stop(..)` which is (also) called from
-  // `~BrowserAutofillManager()` when the `ContentAutofillDriver` instance gets
-  // destroyed.
-  raw_ptr<autofill::ContentAutofillDriver> autofill_driver_ = nullptr;
+  // The `AutofillManager` instance invoking the fast checkout run. Note that
+  // `this` class generally outlives `AutofillManager`.
+  base::WeakPtr<autofill::AutofillManager> autofill_manager_ = nullptr;
+
+  // Weak reference to the `FastCheckoutCapabilitiesFetcher` instance attached
+  // to `this` web content's browser context.
+  raw_ptr<FastCheckoutCapabilitiesFetcher> fetcher_ = nullptr;
 
   // Fast Checkout UI Controller. Responsible for showing the bottomsheet and
   // handling user selections.
@@ -121,8 +152,17 @@
   // True if a run is ongoing; used to avoid multiple runs in parallel.
   bool is_running_ = false;
 
-  // The url for which `Start()` was triggered.
-  GURL url_;
+  // Autofill profile selected by the user in the bottomsheet.
+  std::unique_ptr<autofill::AutofillProfile> selected_autofill_profile_;
+
+  // Credit card selected by the user in the bottomsheet.
+  std::unique_ptr<autofill::CreditCard> selected_credit_card_;
+
+  // The origin for which `TryToStart()` was triggered.
+  url::Origin origin_;
+
+  // Maps forms to fill during the run to their filling state.
+  base::flat_map<autofill::FormSignature, FillingState> forms_to_fill_;
 
   // The current state of the bottomsheet.
   FastCheckoutUIState fast_checkout_ui_state_ =
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
index bffc1e1..eb79952 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
@@ -15,8 +15,10 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
+#include "chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_factory.h"
 #include "chrome/browser/fast_checkout/fast_checkout_features.h"
 #include "chrome/browser/fast_checkout/fast_checkout_trigger_validator.h"
+#include "chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h"
 #include "chrome/browser/ui/autofill/chrome_autofill_client.h"
 #include "chrome/browser/ui/fast_checkout/fast_checkout_controller.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
@@ -102,13 +104,13 @@
   MOCK_METHOD(gfx::NativeView, GetNativeView, (), (override));
 };
 
-class MockAutofillDriver : public autofill::TestAutofillDriver {
+class MockBrowserAutofillManager : public autofill::TestBrowserAutofillManager {
  public:
-  MockAutofillDriver() = default;
-  MockAutofillDriver(const MockAutofillDriver&) = delete;
-  MockAutofillDriver& operator=(const MockAutofillDriver&) = delete;
+  MockBrowserAutofillManager(autofill::TestAutofillDriver* driver,
+                             autofill::TestAutofillClient* client)
+      : autofill::TestBrowserAutofillManager(driver, client) {}
+  ~MockBrowserAutofillManager() override = default;
 
-  // Mock methods to enable testability.
   MOCK_METHOD(void, SetShouldSuppressKeyboard, (bool), (override));
 };
 
@@ -145,7 +147,7 @@
                const autofill::FormFieldData&,
                const FastCheckoutUIState,
                const bool,
-               const autofill::ContentAutofillDriver*),
+               const base::WeakPtr<autofill::AutofillManager>),
               (const));
   MOCK_METHOD(bool, HasValidPersonalData, (), (const));
 };
@@ -178,6 +180,11 @@
     autofill::PersonalDataManagerFactory::GetInstance()->SetTestingFactory(
         GetBrowserContext(),
         base::BindRepeating(&BuildTestPersonalDataManager));
+    FastCheckoutCapabilitiesFetcherFactory::GetInstance()
+        ->SetTestingSubclassFactoryAndUse(
+            profile(), base::BindRepeating([](content::BrowserContext*) {
+              return std::make_unique<MockFastCheckoutCapabilitiesFetcher>();
+            }));
 
     test_client_ =
         TestFastCheckoutClientImpl::CreateForWebContents(web_contents());
@@ -190,13 +197,14 @@
         std::move(fast_checkout_controller));
 
     // Prepare the AutofillDriver.
-    autofill_driver_ = std::make_unique<MockAutofillDriver>();
+    autofill_driver_ = std::make_unique<autofill::TestAutofillDriver>();
 
     // Set AutofillManager on AutofillDriver.
     autofill_client_ = std::make_unique<MockAutofillClient>();
     auto test_browser_autofill_manager =
-        std::make_unique<autofill::TestBrowserAutofillManager>(
-            autofill_driver_.get(), autofill_client_.get());
+        std::make_unique<MockBrowserAutofillManager>(autofill_driver_.get(),
+                                                     autofill_client_.get());
+    autofill_manager_ = test_browser_autofill_manager.get();
     autofill_driver_->set_autofill_manager(
         std::move(test_browser_autofill_manager));
 
@@ -220,21 +228,22 @@
     return fast_checkout_controller_;
   }
 
-  MockAutofillDriver* autofill_driver() { return autofill_driver_.get(); }
-
   MockFastCheckoutTriggerValidator* validator() { return validator_.get(); }
 
   MockAutofillClient* autofill_client() { return autofill_client_.get(); }
 
+  MockBrowserAutofillManager* autofill_manager() { return autofill_manager_; }
+
   base::test::ScopedFeatureList feature_list_;
   base::HistogramTester histogram_tester_;
 
  private:
   std::unique_ptr<MockAutofillClient> autofill_client_;
   raw_ptr<MockFastCheckoutController> fast_checkout_controller_;
-  std::unique_ptr<MockAutofillDriver> autofill_driver_;
+  std::unique_ptr<autofill::TestAutofillDriver> autofill_driver_;
   raw_ptr<TestFastCheckoutClientImpl> test_client_;
   raw_ptr<MockFastCheckoutTriggerValidator> validator_;
+  raw_ptr<MockBrowserAutofillManager> autofill_manager_;
 };
 
 TEST_F(
@@ -247,14 +256,14 @@
   EXPECT_EQ(client, fast_checkout_client());
 }
 
-TEST_F(FastCheckoutClientImplTest, Start_InvalidAutofillDriver_NoRun) {
+TEST_F(FastCheckoutClientImplTest, Start_InvalidAutofillManager_NoRun) {
   // `FastCheckoutClient` is not running initially.
   EXPECT_FALSE(fast_checkout_client()->IsRunning());
 
   // Do not expect bottomsheet to show up.
   EXPECT_CALL(*fast_checkout_controller(), Show).Times(0);
   // Do not expect keyboard to be suppressed.
-  EXPECT_CALL(*autofill_driver(), SetShouldSuppressKeyboard).Times(0);
+  EXPECT_CALL(*autofill_manager(), SetShouldSuppressKeyboard).Times(0);
   // Do not expect Autofill popups to be hidden.
   EXPECT_CALL(*autofill_client(), HideAutofillPopup).Times(0);
 
@@ -271,13 +280,13 @@
   // Do not expect bottomsheet to show up.
   EXPECT_CALL(*fast_checkout_controller(), Show).Times(0);
   // Do not expect keyboard to be suppressed.
-  EXPECT_CALL(*autofill_driver(), SetShouldSuppressKeyboard).Times(0);
+  EXPECT_CALL(*autofill_manager(), SetShouldSuppressKeyboard).Times(0);
   // Do not expect Autofill popups to be hidden.
   EXPECT_CALL(*autofill_client(), HideAutofillPopup).Times(0);
 
   EXPECT_FALSE(fast_checkout_client()->TryToStart(
       GURL(kUrl), autofill::FormData(), autofill::FormFieldData(),
-      autofill_driver()));
+      autofill_manager()->GetWeakPtr()));
 }
 
 TEST_F(FastCheckoutClientImplTest, Start_ShouldRunReturnsTrue_Run) {
@@ -291,7 +300,7 @@
                                 Pointee(kIncompleteProfile)),
            UnorderedElementsAre(Pointee(kCreditCard1), Pointee(kCreditCard2))));
   // Expect keyboard suppression.
-  EXPECT_CALL(*autofill_driver(), SetShouldSuppressKeyboard(true));
+  EXPECT_CALL(*autofill_manager(), SetShouldSuppressKeyboard(true));
   // Expect call to `HideAutofillPopup`.
   EXPECT_CALL(
       *autofill_client(),
@@ -300,7 +309,7 @@
 
   EXPECT_TRUE(fast_checkout_client()->TryToStart(
       GURL(kUrl), autofill::FormData(), autofill::FormFieldData(),
-      autofill_driver()));
+      autofill_manager()->GetWeakPtr()));
 
   EXPECT_TRUE(fast_checkout_client()->IsRunning());
   EXPECT_TRUE(fast_checkout_client()->IsShowing());
@@ -319,7 +328,7 @@
   // Starting the run successfully.
   EXPECT_TRUE(fast_checkout_client()->TryToStart(
       GURL(kUrl), autofill::FormData(), autofill::FormFieldData(),
-      autofill_driver()));
+      autofill_manager()->GetWeakPtr()));
 
   // `FastCheckoutClient` is running.
   EXPECT_TRUE(fast_checkout_client()->IsRunning());
@@ -349,7 +358,7 @@
   // Starting the run successfully.
   EXPECT_TRUE(fast_checkout_client()->TryToStart(
       GURL(kUrl), autofill::FormData(), autofill::FormFieldData(),
-      autofill_driver()));
+      autofill_manager()->GetWeakPtr()));
 
   // `FastCheckoutClient` is running.
   EXPECT_TRUE(fast_checkout_client()->IsRunning());
@@ -376,7 +385,7 @@
   // Starting the run successfully.
   EXPECT_TRUE(fast_checkout_client()->TryToStart(
       GURL(kUrl), autofill::FormData(), autofill::FormFieldData(),
-      autofill_driver()));
+      autofill_manager()->GetWeakPtr()));
 
   // Fast Checkout is running and showing the bottomsheet.
   EXPECT_TRUE(fast_checkout_client()->IsRunning());
@@ -397,7 +406,7 @@
   // Starting the run successfully.
   EXPECT_TRUE(fast_checkout_client()->TryToStart(
       GURL(kUrl), autofill::FormData(), autofill::FormFieldData(),
-      autofill_driver()));
+      autofill_manager()->GetWeakPtr()));
 
   fast_checkout_client()->OnDismiss();
 
@@ -406,15 +415,10 @@
 }
 
 TEST_F(FastCheckoutClientImplTest,
-       DestroyingAutofillDriver_ResetsAutofillDriverPointer) {
+       DestroyingAutofillDriver_ResetsAutofillManagerPointer) {
   // Set up Autofill instances so that `FastCheckoutClient::Stop(..)` will be
   // called when `autofill_driver` is destroyed below. `Stop(..)` is supposed to
-  // reset `FastCheckoutClientImpl::autofill_driver_`.
-  // The expected stack trace is:
-  //   `FastCheckoutClientImpl::Stop(/*allow_further_runs=*/true)`
-  //   `ChromeAutofillClient::HideFastCheckout(/*allow_further_runs=*/true)`
-  //   `~BrowserAutofillManager()`
-  //   `autofill_driver.reset()`
+  // reset `FastCheckoutClientImpl::autofill_manager_`.
   autofill::ChromeAutofillClient::CreateForWebContents(web_contents());
   auto autofill_router = std::make_unique<autofill::ContentAutofillRouter>();
   auto autofill_driver = std::make_unique<autofill::ContentAutofillDriver>(
@@ -424,26 +428,69 @@
           autofill_driver.get(),
           autofill::ChromeAutofillClient::FromWebContents(web_contents()),
           "en-US", autofill::AutofillManager::EnableDownloadManager(false));
+  autofill::BrowserAutofillManager* autofill_manager =
+      browser_autofill_manager.get();
   autofill_driver->set_autofill_manager(std::move(browser_autofill_manager));
 
-  // `FastCheckoutClientImpl::autofill_driver_` is `nullptr` initially.
-  EXPECT_FALSE(fast_checkout_client()->get_autofill_driver_for_test());
+  // `FastCheckoutClientImpl::autofill_manager_` is `nullptr` initially.
+  EXPECT_FALSE(fast_checkout_client()->get_autofill_manager_for_test());
 
   // Starting the run successfully.
   EXPECT_TRUE(fast_checkout_client()->TryToStart(
       GURL(kUrl), autofill::FormData(), autofill::FormFieldData(),
-      autofill_driver.get()));
+      autofill_manager->GetWeakPtr()));
 
-  // `FastCheckoutClientImpl::autofill_driver_` is not `nullptr` anymore.
-  EXPECT_TRUE(fast_checkout_client()->get_autofill_driver_for_test());
+  // `FastCheckoutClientImpl::autofill_manager_` is not `nullptr` anymore.
+  EXPECT_TRUE(fast_checkout_client()->get_autofill_manager_for_test());
 
   // Destroy `ContentAutofillDriver` instance, invoking
   // `~BrowserAutofillManager()` and thus `FastCheckoutClient::Stop(..)`.
   autofill_driver.reset();
 
-  // `FastCheckoutClientImpl::autofill_driver_` is `nullptr` again.
-  EXPECT_FALSE(fast_checkout_client()->get_autofill_driver_for_test());
+  // `FastCheckoutClientImpl::autofill_manager_` is `nullptr` again.
+  EXPECT_FALSE(fast_checkout_client()->get_autofill_manager_for_test());
 
   // Expect this `Stop(..)` call to not crash the test.
   fast_checkout_client()->Stop(/*allow_further_runs=*/true);
 }
+
+TEST_F(FastCheckoutClientImplTest,
+       OnOptionsSelected_SavesFormsAndAutofillDataSelections) {
+  auto autofill_profile = std::make_unique<autofill::AutofillProfile>(
+      autofill::test::GetFullProfile());
+  auto credit_card =
+      std::make_unique<autofill::CreditCard>(autofill::test::GetCreditCard());
+  autofill::AutofillProfile* autofill_profile_ptr = autofill_profile.get();
+  autofill::CreditCard* credit_card_ptr = credit_card.get();
+  autofill::FormSignature signature_1 = autofill::FormSignature(72322UL);
+  autofill::FormSignature signature_2 = autofill::FormSignature(1154UL);
+  base::flat_set<autofill::FormSignature> forms_to_fill{signature_1,
+                                                        signature_2};
+  const base::flat_map<autofill::FormSignature,
+                       FastCheckoutClientImpl::FillingState>
+      expected_forms_to_fill_map{
+          {signature_1, FastCheckoutClientImpl::FillingState::kNotFilled},
+          {signature_2, FastCheckoutClientImpl::FillingState::kNotFilled}};
+
+  MockFastCheckoutCapabilitiesFetcher* fetcher =
+      static_cast<MockFastCheckoutCapabilitiesFetcher*>(
+          FastCheckoutCapabilitiesFetcherFactory::GetForBrowserContext(
+              profile()));
+  EXPECT_TRUE(fetcher);
+
+  EXPECT_CALL(*fetcher, GetFormsToFill(url::Origin::Create(GURL(kUrl))))
+      .WillOnce(Return(forms_to_fill));
+  EXPECT_TRUE(fast_checkout_client()->TryToStart(
+      GURL(kUrl), autofill::FormData(), autofill::FormFieldData(),
+      autofill_manager()->GetWeakPtr()));
+
+  fast_checkout_client()->OnOptionsSelected(std::move(autofill_profile),
+                                            std::move(credit_card));
+
+  EXPECT_EQ(*fast_checkout_client()->get_autofill_profile_for_test(),
+            *autofill_profile_ptr);
+  EXPECT_EQ(*fast_checkout_client()->get_credit_card_for_test(),
+            *credit_card_ptr);
+  EXPECT_EQ(fast_checkout_client()->get_forms_to_fill_for_test(),
+            expected_forms_to_fill_map);
+}
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator.h b/chrome/browser/fast_checkout/fast_checkout_trigger_validator.h
index 51dabc8..e0ff305 100644
--- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator.h
+++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_TRIGGER_VALIDATOR_H_
 
 #include "chrome/browser/fast_checkout/fast_checkout_enums.h"
-#include "components/autofill/content/browser/content_autofill_driver.h"
+#include "components/autofill/core/browser/browser_autofill_manager.h"
 
 constexpr char kUmaKeyFastCheckoutTriggerOutcome[] =
     "Autofill.FastCheckout.TriggerOutcome";
@@ -22,12 +22,12 @@
 
   // Returns `true` if a Fast Checkout run should be permitted, `false`
   // otherwise. Logs outcome to UMA and chrome://autofill-internals.
-  virtual bool ShouldRun(
-      const autofill::FormData& form,
-      const autofill::FormFieldData& field,
-      const FastCheckoutUIState ui_state,
-      const bool is_running,
-      const autofill::ContentAutofillDriver* autofill_driver) const = 0;
+  virtual bool ShouldRun(const autofill::FormData& form,
+                         const autofill::FormFieldData& field,
+                         const FastCheckoutUIState ui_state,
+                         const bool is_running,
+                         const base::WeakPtr<autofill::AutofillManager>
+                             autofill_manager) const = 0;
 
   // Returns `true` if the current profile has Autofill data enabled and at
   // least one valid Autofill profile and credit card stored, `false` otherwise.
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc
index 6b43a0d..d91600b7 100644
--- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc
@@ -33,7 +33,7 @@
     const autofill::FormFieldData& field,
     const FastCheckoutUIState ui_state,
     const bool is_running,
-    const autofill::ContentAutofillDriver* autofill_driver) const {
+    const base::WeakPtr<autofill::AutofillManager> autofill_manager) const {
   LogAutofillInternals(
       "Start of checking whether a Fast Checkout run should be permitted.");
 
@@ -89,7 +89,7 @@
   }
 
   // Trigger only if the UI is available.
-  if (!autofill_driver->CanShowAutofillUi()) {
+  if (!autofill_manager->CanShowAutofillUi()) {
     LogUmaTriggerOutcome(
         FastCheckoutTriggerOutcome::kFailureCannotShowAutofillUi);
     LogAutofillInternals("not triggered because Autofill UI cannot be shown.");
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h
index ae2c23fe..3b08230 100644
--- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h
+++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h
@@ -9,7 +9,6 @@
 #include "chrome/browser/fast_checkout/fast_checkout_enums.h"
 #include "chrome/browser/fast_checkout/fast_checkout_personal_data_helper.h"
 #include "chrome/browser/fast_checkout/fast_checkout_trigger_validator.h"
-#include "components/autofill/content/browser/content_autofill_driver.h"
 
 class FastCheckoutTriggerValidatorImpl : public FastCheckoutTriggerValidator {
  public:
@@ -25,12 +24,12 @@
       const FastCheckoutTriggerValidatorImpl&) = delete;
 
   // FastCheckoutTriggerValidator:
-  bool ShouldRun(
-      const autofill::FormData& form,
-      const autofill::FormFieldData& field,
-      const FastCheckoutUIState ui_state,
-      const bool is_running,
-      const autofill::ContentAutofillDriver* autofill_driver) const override;
+  bool ShouldRun(const autofill::FormData& form,
+                 const autofill::FormFieldData& field,
+                 const FastCheckoutUIState ui_state,
+                 const bool is_running,
+                 const base::WeakPtr<autofill::AutofillManager>
+                     autofill_manager) const override;
   bool HasValidPersonalData() const override;
 
  private:
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc
index 0ceee02..5a1fec42 100644
--- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc
@@ -6,13 +6,14 @@
 
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h"
 #include "chrome/browser/fast_checkout/fast_checkout_features.h"
 #include "chrome/browser/fast_checkout/fast_checkout_personal_data_helper.h"
+#include "chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
-#include "components/autofill/content/browser/content_autofill_driver.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/test_autofill_client.h"
+#include "components/autofill/core/browser/test_autofill_driver.h"
+#include "components/autofill/core/browser/test_browser_autofill_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -20,11 +21,12 @@
 
 using ::testing::Return;
 
-class MockContentAutofillDriver : public autofill::ContentAutofillDriver {
+class MockBrowserAutofillManager : public autofill::TestBrowserAutofillManager {
  public:
-  MockContentAutofillDriver()
-      : autofill::ContentAutofillDriver(nullptr, nullptr) {}
-  ~MockContentAutofillDriver() override = default;
+  MockBrowserAutofillManager(autofill::TestAutofillDriver* driver,
+                             autofill::TestAutofillClient* client)
+      : autofill::TestBrowserAutofillManager(driver, client) {}
+  ~MockBrowserAutofillManager() override = default;
 
   MOCK_METHOD(bool, CanShowAutofillUi, (), (const override));
 };
@@ -35,16 +37,6 @@
   MOCK_METHOD(bool, IsContextSecure, (), (const override));
 };
 
-class MockCapabilitiesFetcher : public FastCheckoutCapabilitiesFetcher {
- public:
-  MOCK_METHOD(void, FetchCapabilities, (), ());
-  MOCK_METHOD(bool,
-              IsTriggerFormSupported,
-              (const url::Origin& origin,
-               autofill::FormSignature form_signature),
-              ());
-};
-
 class MockPersonalDataHelper : public FastCheckoutPersonalDataHelper {
  public:
   MockPersonalDataHelper() = default;
@@ -53,23 +45,23 @@
   MOCK_METHOD(std::vector<autofill::CreditCard*>,
               GetValidCreditCards,
               (),
-              (const));
+              (const override));
   MOCK_METHOD(std::vector<autofill::AutofillProfile*>,
               GetValidAddressProfiles,
               (),
-              (const));
+              (const override));
   MOCK_METHOD(autofill::PersonalDataManager*,
               GetPersonalDataManager,
               (),
-              (const));
+              (const override));
   MOCK_METHOD(std::vector<autofill::AutofillProfile*>,
               GetProfilesToSuggest,
               (),
-              (const));
+              (const override));
   MOCK_METHOD(std::vector<autofill::CreditCard*>,
               GetCreditCardsToSuggest,
               (),
-              (const));
+              (const override));
 };
 
 class MockPersonalDataManager : public autofill::PersonalDataManager {
@@ -77,8 +69,8 @@
   MockPersonalDataManager() : PersonalDataManager("en-US") {}
   ~MockPersonalDataManager() override = default;
 
-  MOCK_METHOD(bool, IsAutofillProfileEnabled, (), (const));
-  MOCK_METHOD(bool, IsAutofillCreditCardEnabled, (), (const));
+  MOCK_METHOD(bool, IsAutofillProfileEnabled, (), (const override));
+  MOCK_METHOD(bool, IsAutofillCreditCardEnabled, (), (const override));
 };
 
 class FastCheckoutTriggerValidatorTest
@@ -96,15 +88,18 @@
 
     pdm_ = std::make_unique<MockPersonalDataManager>();
     autofill_client_ = std::make_unique<MockAutofillClient>();
-    capabilities_fetcher_ = std::make_unique<MockCapabilitiesFetcher>();
+    capabilities_fetcher_ =
+        std::make_unique<MockFastCheckoutCapabilitiesFetcher>();
     personal_data_helper_ = std::make_unique<MockPersonalDataHelper>();
-    autofill_driver_ = std::make_unique<MockContentAutofillDriver>();
+    autofill_driver_ = std::make_unique<autofill::TestAutofillDriver>();
+    autofill_manager_ = std::make_unique<MockBrowserAutofillManager>(
+        autofill_driver_.get(), autofill_client_.get());
     validator_ = std::make_unique<FastCheckoutTriggerValidatorImpl>(
         autofill_client(), capabilities_fetcher(), personal_data_helper());
     credit_card_ = autofill::test::GetCreditCard();
     profile_ = autofill::test::GetFullProfile();
 
-    ON_CALL(*autofill_driver(), CanShowAutofillUi).WillByDefault(Return(true));
+    ON_CALL(*autofill_manager(), CanShowAutofillUi).WillByDefault(Return(true));
     ON_CALL(*capabilities_fetcher(), IsTriggerFormSupported)
         .WillByDefault(Return(true));
     ON_CALL(*personal_data_helper(), GetValidCreditCards)
@@ -122,20 +117,20 @@
 
   MockPersonalDataManager* pdm() { return pdm_.get(); }
   MockAutofillClient* autofill_client() { return autofill_client_.get(); }
-  MockCapabilitiesFetcher* capabilities_fetcher() {
+  MockFastCheckoutCapabilitiesFetcher* capabilities_fetcher() {
     return capabilities_fetcher_.get();
   }
   MockPersonalDataHelper* personal_data_helper() {
     return personal_data_helper_.get();
   }
-  MockContentAutofillDriver* autofill_driver() {
-    return autofill_driver_.get();
+  MockBrowserAutofillManager* autofill_manager() {
+    return autofill_manager_.get();
   }
   FastCheckoutTriggerValidatorImpl* validator() { return validator_.get(); }
 
   bool ShouldRun() {
     return validator()->ShouldRun(form_, field_, ui_state_, is_running_,
-                                  autofill_driver());
+                                  autofill_manager()->GetWeakPtr());
   }
 
   // Protected for access in tests below.
@@ -149,10 +144,11 @@
   autofill::CreditCard credit_card_;
   autofill::FormData form_;
   base::test::ScopedFeatureList feature_list_;
+  std::unique_ptr<autofill::TestAutofillDriver> autofill_driver_;
   std::unique_ptr<FastCheckoutTriggerValidatorImpl> validator_;
   std::unique_ptr<MockAutofillClient> autofill_client_;
-  std::unique_ptr<MockCapabilitiesFetcher> capabilities_fetcher_;
-  std::unique_ptr<MockContentAutofillDriver> autofill_driver_;
+  std::unique_ptr<MockFastCheckoutCapabilitiesFetcher> capabilities_fetcher_;
+  std::unique_ptr<MockBrowserAutofillManager> autofill_manager_;
   std::unique_ptr<MockPersonalDataHelper> personal_data_helper_;
   std::unique_ptr<MockPersonalDataManager> pdm_;
 };
@@ -240,7 +236,7 @@
 
 TEST_F(FastCheckoutTriggerValidatorTest,
        ShouldRun_CannotShowAutofillUi_ReturnsFalse) {
-  ON_CALL(*autofill_driver(), CanShowAutofillUi).WillByDefault(Return(false));
+  ON_CALL(*autofill_manager(), CanShowAutofillUi).WillByDefault(Return(false));
 
   EXPECT_FALSE(ShouldRun());
   histogram_tester_.ExpectUniqueSample(
diff --git a/chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h b/chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h
index 5899613..319f8c0f 100644
--- a/chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h
+++ b/chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h
@@ -22,6 +22,10 @@
               IsTriggerFormSupported,
               (const url::Origin&, autofill::FormSignature),
               (override));
+  MOCK_METHOD(base::flat_set<autofill::FormSignature>,
+              GetFormsToFill,
+              (const url::Origin& origin),
+              (override));
 };
 
 #endif  // CHROME_BROWSER_FAST_CHECKOUT_MOCK_FAST_CHECKOUT_CAPABILITIES_FETCHER_H_
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index e40c270..67bd430 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1357,7 +1357,7 @@
   {
     "name": "destroy-profile-on-browser-close",
     "owners": [ "nicolaso", "droger" ],
-    "expiry_milestone": 112
+    "expiry_milestone": 115
   },
   {
     "name": "destroy-system-profiles",
@@ -2444,7 +2444,7 @@
   {
     "name": "enable-generic-sensor-extra-classes",
     "owners": [ "reillyg@chromium.org", "raphael.kubo.da.costa@intel.com" ],
-    "expiry_milestone": 112
+    "expiry_milestone": 116
   },
   {
     "name": "enable-get-display-media-set",
@@ -3603,7 +3603,7 @@
   {
     "name": "expanded-tab-strip",
     "owners": [ "rkgibson@google.com", "gambard", "bling-flags@google.com" ],
-    "expiry_milestone": 105
+    "expiry_milestone": 116
   },
   {
     "name": "experience-kit-apple-calendar",
@@ -3667,7 +3667,7 @@
   {
     "name": "fast-checkout",
     "owners": [ "vizcay@google.com", "bwolfgang@google.com", "jkeitel@google.com" ],
-    "expiry_milestone": 112
+    "expiry_milestone": 118
   },
   {
     "name": "fast-pair",
@@ -3901,7 +3901,7 @@
   {
     "name": "force-enable-fast-checkout-capabilities",
     "owners": [ "vizcay@google.com", "bwolfgang@google.com", "jkeitel@google.com" ],
-    "expiry_milestone": 115
+    "expiry_milestone": 118
   },
   {
     "name": "force-gpu-main-thread-to-normal-priority-drdc",
@@ -4567,6 +4567,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "lacros-selection-ignore",
+    "owners": [ "asumaneev", "lacros-team@google.com" ],
+    "expiry_milestone": 130
+  },
+  {
     "name": "lacros-stability",
     "owners": [ "jamescook", "erikchen", "lacros-team@google.com" ],
     // Once Lacros is launched, this flag can be removed. Until then, this
@@ -6996,7 +7001,7 @@
   {
     "name": "use-sf-symbols",
     "owners": [ "ewannpv", "gambard", "bling-flags@google.com" ],
-    "expiry_milestone": 112
+    "expiry_milestone": 113
   },
   {
     "name": "use-sf-symbols-omnibox",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 1f8acfa..6b66061 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -5603,6 +5603,12 @@
 const char kLacrosSelectionRootfsDescription[] = "Rootfs";
 const char kLacrosSelectionStatefulDescription[] = "Stateful";
 
+const char kLacrosSelectionPolicyIgnoreName[] =
+    "Ignore lacros-selection policy";
+const char kLacrosSelectionPolicyIgnoreDescription[] =
+    "Makes the lacros-selection policy have no effect. Instead Lacros "
+    "selection will be controlled by experiment and/or user flags.";
+
 const char kLacrosSupportName[] = "Lacros support";
 const char kLacrosSupportDescription[] =
     "Support for the experimental lacros-chrome browser. Please note that the "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 1abee7b..b68a7d4 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -3233,6 +3233,9 @@
 extern const char kLacrosSelectionRootfsDescription[];
 extern const char kLacrosSelectionStatefulDescription[];
 
+extern const char kLacrosSelectionPolicyIgnoreName[];
+extern const char kLacrosSelectionPolicyIgnoreDescription[];
+
 extern const char kLacrosSupportName[];
 extern const char kLacrosSupportDescription[];
 
diff --git a/chrome/browser/lacros/browser_launcher_browsertest.cc b/chrome/browser/lacros/browser_launcher_browsertest.cc
index 132583c..8e0ceb3 100644
--- a/chrome/browser/lacros/browser_launcher_browsertest.cc
+++ b/chrome/browser/lacros/browser_launcher_browsertest.cc
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 #include "base/containers/contains.h"
-#include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
 #include "chrome/browser/lacros/browser_service_lacros.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/prefs/session_startup_pref.h"
@@ -85,12 +85,13 @@
   }
 
   void NewWindowSync(bool incognito, bool should_trigger_session_restore) {
-    base::RunLoop run_loop;
+    base::test::TestFuture<void> new_window_future;
     browser_service()->NewWindow(
         incognito, should_trigger_session_restore,
         display::Screen::GetScreen()->GetDisplayForNewWindows().id(),
-        run_loop.QuitClosure());
-    run_loop.Run();
+        new_window_future.GetCallback());
+    ASSERT_TRUE(new_window_future.Wait())
+        << "NewWindow did not trigger the callback.";
   }
 
   void DisableWelcomePages(const std::vector<Profile*>& profiles) {
@@ -281,10 +282,12 @@
   EXPECT_TRUE(base::Contains(last_opened_profiles, profile2));
 
   // Trigger Lacros full restore.
-  base::RunLoop run_loop;
-  testing::SessionsRestoredWaiter restore_waiter(run_loop.QuitClosure(), 2);
+  base::test::TestFuture<void> restore_waiter_future;
+  testing::SessionsRestoredWaiter restore_waiter(
+      restore_waiter_future.GetCallback(), 2);
   browser_service()->OpenForFullRestore(/*skip_crash_restore=*/true);
-  run_loop.Run();
+  ASSERT_TRUE(restore_waiter_future.Wait())
+      << "restore_waiter did not trigger the callback.";
 
   // The startup URLs are ignored, and instead the last open sessions are
   // restored.
@@ -375,10 +378,12 @@
 
   // Trigger Lacros full restore.
   EXPECT_FALSE(profile->restored_last_session());
-  base::RunLoop run_loop;
-  testing::SessionsRestoredWaiter restore_waiter(run_loop.QuitClosure(), 1);
+  base::test::TestFuture<void> restore_waiter_future;
+  testing::SessionsRestoredWaiter restore_waiter(
+      restore_waiter_future.GetCallback(), 1);
   browser_service()->OpenForFullRestore(/*skip_crash_restore=*/true);
-  run_loop.Run();
+  ASSERT_TRUE(restore_waiter_future.Wait())
+      << "restore_waiter did not trigger the callback.";
 
   // The last session should be logged as restored.
   EXPECT_TRUE(profile->restored_last_session());
@@ -452,10 +457,12 @@
   ASSERT_EQ(0u, BrowserList::GetInstance()->size());
 
   // Trigger a new window with session restore.
-  base::RunLoop run_loop;
-  testing::SessionsRestoredWaiter restore_waiter(run_loop.QuitClosure(), 1);
+  base::test::TestFuture<void> restore_waiter_future;
+  testing::SessionsRestoredWaiter restore_waiter(
+      restore_waiter_future.GetCallback(), 1);
   NewWindowSync(/*incognito=*/false, /*should_trigger_session_restore=*/true);
-  run_loop.Run();
+  ASSERT_TRUE(restore_waiter_future.Wait())
+      << "restore_waiter did not trigger the callback.";
 
   // The browser window should be restored but app browser should not.
   ASSERT_EQ(1u, chrome::GetBrowserCount(profile));
@@ -557,10 +564,12 @@
       ->SetLastSessionExitTypeForTest(ExitType::kCrashed);
 
   // Trigger Lacros full restore and skip crash restore prompts.
-  base::RunLoop run_loop;
-  testing::SessionsRestoredWaiter restore_waiter(run_loop.QuitClosure(), 1);
+  base::test::TestFuture<void> restore_waiter_future;
+  testing::SessionsRestoredWaiter restore_waiter(
+      restore_waiter_future.GetCallback(), 1);
   browser_service()->OpenForFullRestore(/*skip_crash_restore=*/true);
-  run_loop.Run();
+  ASSERT_TRUE(restore_waiter_future.Wait())
+      << "restore_waiter did not trigger the callback.";
 
   // The browser should be restored (ignoring startup preference).
   Browser* new_browser = nullptr;
diff --git a/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc
index 6752c1e..6593738 100644
--- a/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc
@@ -348,8 +348,7 @@
   histogram_tester().ExpectTotalCount(
       prerender_helper_.GenerateHistogramName(
           internal::kHistogramPrerenderActivationToLargestContentfulPaint2,
-          content::PrerenderTriggerType::kEmbedder,
-          prerender_utils::kDirectUrlInputMetricSuffix),
+          content::PrerenderTriggerType::kSpeculationRule, ""),
       0);
 }
 
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 58d8831..f9ab8c5 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -2521,6 +2521,12 @@
   handlers->AddHandler(
       std::make_unique<extensions::ExtensionSettingsPolicyHandler>(
           chrome_schema));
+#if !BUILDFLAG(IS_FUCHSIA)
+  handlers->AddHandler(std::make_unique<IntRangePolicyHandler>(
+      key::kExtensionUnpublishedAvailability,
+      extensions::pref_names::kExtensionUnpublishedAvailability,
+      /*min=*/0, /*max=*/2, /*clamp=*/false));
+#endif  // !BUILDFLAG(IS_FUCHSIA)
   handlers->AddHandler(std::make_unique<IntRangePolicyHandler>(
       key::kExtensionManifestV2Availability,
       extensions::pref_names::kManifestV2Availability, /*min=*/0, /*max=*/3,
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 1340e75..4855da5 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -657,6 +657,7 @@
         policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string()));
     crosapi::browser_util::CacheLacrosAvailability(map);
     crosapi::browser_util::CacheLacrosDataBackwardMigrationMode(map);
+    crosapi::browser_util::CacheLacrosSelection(map);
   }
 #endif
 }
@@ -1185,6 +1186,7 @@
           policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string()));
       crosapi::browser_util::CacheLacrosAvailability(map);
       crosapi::browser_util::CacheLacrosDataBackwardMigrationMode(map);
+      crosapi::browser_util::CacheLacrosSelection(map);
     }
 
     ash::UserSessionManager::GetInstance()->RespectLocalePreferenceWrapper(
diff --git a/chrome/browser/resources/chromeos/emoji_picker/constants.ts b/chrome/browser/resources/chromeos/emoji_picker/constants.ts
index 0febc15..f2ee677 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/constants.ts
+++ b/chrome/browser/resources/chromeos/emoji_picker/constants.ts
@@ -49,7 +49,7 @@
     `${EMOJI_PICKER_TOTAL_EMOJI_WIDTH}px`;
 export const TAB_BUTTON_MARGIN_PX = `${TAB_BUTTON_MARGIN}px`;
 export const TEXT_GROUP_BUTTON_PADDING_PX = `${TEXT_GROUP_BUTTON_PADDING}px`;
-export const TRENDING = '#trending';
+export const TRENDING = 'Trending';
 // 24 hours is equivalent to 86400000 milliseconds.
 export const TWENTY_FOUR_HOURS = 86400000;
 export const GIF_VALIDATION_DATE = 'gifValidationDate';
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
index 73c791a..ba633a2 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
@@ -347,29 +347,16 @@
   </div>
   <div class="divider"></div>
   <div class="side-padding" id="groups" on-scroll="onEmojiScroll">
-    <template is="dom-if" if="[[!isInfinite(category)]]">
-      <template is="dom-repeat" items="[[categoriesGroupElements]]">
-        <div data-group$="[[item.groupId]]">
-          <emoji-group data="[[item.emoji]]"
-            group="[[item.name]]"
-            preferred="[[item.preferences]]"
-            clearable$="[[item.isHistory]]"
-            category$="[[item.category]]"
-            class$="[[getEmojiGroupClassNames(item.isHistory,item.emoji)]]">
-          </emoji-group>
-        </div>
-      </template>
-    </template>
-    <template is="dom-if" if="[[isInfinite(category)]]">
-        <div data-group$="[[activeInfiniteGroupElements]]">
-          <emoji-group data="[[activeInfiniteGroupElements.emoji]]"
-            group="[[activeInfiniteGroupElements.name]]"
-            preferred="[[activeInfiniteGroupElements.preferences]]"
-            clearable$="[[activeInfiniteGroupElements.isHistory]]"
-            category$="[[activeInfiniteGroupElements.category]]"
-            class$="[[getEmojiGroupClassNames(activeInfiniteGroupElements.isHistory,activeInfiniteGroupElements.emoji)]]">
-          </emoji-group>
-        </div>
+    <template is="dom-repeat" items="[[categoriesGroupElements]]">
+      <div data-group$="[[item.groupId]]">
+        <emoji-group data="[[item.emoji]]"
+          group="[[item.name]]"
+          preferred="[[item.preferences]]"
+          clearable$="[[item.isHistory]]"
+          category$="[[item.category]]"
+          class$="[[getEmojiGroupClassNames(item.emoji,item,category,activeInfiniteGroupId)]]">
+        </emoji-group>
+      </div>
     </template>
   </div>
   <!-- Render invisible dummy tab to temporarily calculate width of a tab. -->
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.ts b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.ts
index d290d41..ea6e27f 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.ts
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.ts
@@ -56,7 +56,7 @@
       dummyTab: {type: Object, value: () => ({})},
       categoriesData: {type: Array, value: () => ([])},
       categoriesGroupElements: {type: Array, value: () => ([])},
-      activeInfiniteGroupElements: {type: Object, value: () => ({})},
+      activeInfiniteGroupId: {type: String, value: null},
       categoriesHistory: {type: Object, value: () => ({})},
       pagination: {type: Number, value: 1, observer: 'onPaginationChanged'},
       searchLazyIndexing: {type: Boolean, value: true},
@@ -78,7 +78,7 @@
   private allCategoryTabs: SubcategoryData[];
   categoriesData: EmojiGroupData;
   categoriesGroupElements: EmojiGroupElement[];
-  activeInfiniteGroupElements: EmojiGroupElement;
+  activeInfiniteGroupId: string|null; // null before Trending GIFs are fetched
   private categoriesHistory: {[index in CategoryEnum]: RecentlyUsedStore|null};
   private pagination: number;
   private searchLazyIndexing: boolean;
@@ -339,15 +339,33 @@
             this.gifDataInitialised = true;
 
             this.updateCategoryData(trendingGifs, CategoryEnum.GIF);
-            const trendingCategoryIndex = this.allCategoryTabs.findIndex(
-                tab => tab.name === constants.TRENDING);
-            this.activeInfiniteGroupElements = this.createEmojiGroupElement(
-                trendingGifsElement, {}, false, trendingCategoryIndex);
+            this.activeInfiniteGroupId =
+                this.allCategoryTabs
+                    .find(tab => tab.name === constants.TRENDING)
+                    ?.groupId as string;
           });
     }
   }
 
-  isInfinite(category: CategoryEnum) {
+  private canScrollToGroup(category: CategoryEnum, groupId: string): boolean {
+    if (!this.isInfiniteCategory(category)) {
+      return true;
+    }
+
+    // There will always be a trending GIF group (initialised at beginning),
+    // hence it is safe to cast as SubcategoryData
+    const trendingGifGroup =
+        this.allCategoryTabs.find(
+            tab =>
+                (tab.category === CategoryEnum.GIF &&
+                 tab.name === constants.TRENDING)) as SubcategoryData;
+    const historyGifGroup = this.categoriesGroupElements.find(
+        group => group.category === CategoryEnum.GIF && group.isHistory);
+    return [trendingGifGroup.groupId, historyGifGroup?.groupId].includes(
+        groupId);
+  }
+
+  private isInfiniteCategory(category: CategoryEnum) {
     return category === CategoryEnum.GIF;
   }
 
@@ -532,20 +550,6 @@
     });
   }
 
-  selectTextEmojiGroup(newGroup: string) {
-    // focus and scroll to selected group's first emoji.
-    const group =
-        this.shadowRoot?.querySelector(`div[data-group="${newGroup}"]`);
-
-    if (group) {
-      const target = (group.querySelector('.group')?.shadowRoot?.querySelector(
-                         '#fake-focus-target')) as HTMLElement |
-          null;
-      target?.focus();
-      group.scrollIntoView();
-    }
-  }
-
   async setGifGroupElements(activeGroupId: string) {
     // Check if GIFs have already been previously fetched and cached
     let gifGroupElements = this.categoriesGroupElements.find(
@@ -570,24 +574,33 @@
           gifElements, {}, false, activeCategoryIndex);
     }
 
-    this.setActiveInfiniteGroupElements(activeGroupId, gifGroupElements);
+    this.setActiveInfiniteGroup(activeGroupId);
     if (!isCached) {
       this.categoriesGroupElements =
           [...this.categoriesGroupElements, gifGroupElements];
     }
   }
 
-  setActiveInfiniteGroupElements(
-      activeGroupId: string, gifGroupElements: EmojiGroupElement) {
+  setActiveInfiniteGroup(activeGroupId: string) {
     this.updateActiveGroup(activeGroupId);
-    this.activeInfiniteGroupElements = gifGroupElements;
+    this.activeInfiniteGroupId = activeGroupId;
   }
 
   async selectGroup(newGroup: string) {
     if (this.category === CategoryEnum.GIF) {
       this.setGifGroupElements(newGroup);
-    } else {
-      this.selectTextEmojiGroup(newGroup);
+    }
+
+    // focus and scroll to selected group's first emoji.
+    const group =
+        this.shadowRoot?.querySelector(`div[data-group="${newGroup}"]`);
+
+    if (group) {
+      const target = (group.querySelector('.group')?.shadowRoot?.querySelector(
+                         '#fake-focus-target')) as HTMLElement |
+          null;
+      target?.focus();
+      group.scrollIntoView();
     }
   }
 
@@ -599,12 +612,8 @@
       clearTimeout(this.scrollTimeout);
     }
     this.scrollTimeout = setTimeout(() => {
-      // Active category and group will not change via scrolling
-      // for infinite emoji types e.g. GIFs
-      if (!this.isInfinite(this.category)) {
-        this.updateActiveCategory();
-        this.updateActiveGroup();
-      }
+      this.updateActiveCategory();
+      this.updateActiveGroup();
     }, 100);
   }
 
@@ -642,8 +651,6 @@
       // GIF group tabs movement isn't affected by scrolling
       this.groupTabsMoving = false;
 
-      // TODO(b/265731647) Set the GIF elements for the first GIF tab group
-      // Awaiting (b/263920562) to be merged in
     } else {
       this.scrollToGroup(nextTab?.groupId);
       this.groupTabsMoving = true;
@@ -740,18 +747,8 @@
    */
   updateActiveGroup(groupId?: string) {
     let activeGroupId = groupId;
-    if (activeGroupId == null && this.category !== CategoryEnum.GIF) {
+    if (groupId == null) {
       activeGroupId = this.getActiveGroupIdFromScrollPosition();
-    } else if (activeGroupId == null && this.category === CategoryEnum.GIF) {
-      // Set the default active group to be trending
-      // TODO(b/265594436): Display correct first group
-      // (account for recently used if there are history items)
-      activeGroupId =
-          this.categoriesGroupElements
-              .find(
-                  groupElement => groupElement.category === CategoryEnum.GIF &&
-                      groupElement.name === constants.TRENDING)
-              ?.groupId;
     }
     this.set(
         'pagination', this.getPaginationFromGroupId(activeGroupId as string));
@@ -1014,10 +1011,16 @@
   /**
    * Gets HTML classes for an emoji group element.
    *
+   * The emojis need to be passed in directly so Polymer registers changes
+   * e.g. when clearing history emojis.
+   * The currEmojiGroup is passed in for additional attribute info.
+   *
    * @returns {string} HTML element class attribute.
    */
-  getEmojiGroupClassNames(isHistory: boolean, emojis: EmojiVariants[]) {
-    const baseClassNames = isHistory ? 'group history' : 'group';
+  getEmojiGroupClassNames(
+      emojis: EmojiVariants[], currEmojiGroup: EmojiGroupElement,
+      activeCategory: CategoryEnum, activeInfiniteGroupId: string) {
+    const baseClassNames = currEmojiGroup.isHistory ? 'group history' : 'group';
 
     // Make emoji hidden if it is empty.
     // Note: Filtering empty groups in dom-repeat is expensive due to
@@ -1025,6 +1028,33 @@
     if (!emojis || emojis.length === 0) {
       return baseClassNames + ' hidden';
     }
+
+    if (!this.gifSupport) {
+      return baseClassNames;
+    }
+
+    // If the active group can be reached via scrolling (i.e. emojis, emoticons,
+    // symbols, recently used GIFs, trending GIFs), whereas the current group
+    // being rendered cannot, the current group should be hidden, and vice
+    // versa. i.e. When on an emoji group, you can scroll down to other emoticon
+    // or symbol groups, or recently used or trending GIFs, but cannot scroll
+    // past trending GIFs to view #tag GIF groups.
+    const canScrollToActiveGroup =
+        this.canScrollToGroup(activeCategory, activeInfiniteGroupId);
+    const canScrollToCurrGroup =
+        this.canScrollToGroup(currEmojiGroup.category, currEmojiGroup.groupId);
+    const canScrollToCurrGroupFromActive =
+        (canScrollToCurrGroup && canScrollToActiveGroup);
+
+    // For GIF category groups (not including history or recently used), only
+    // the currently active group should be displayed and all other groups
+    // should be hidden.
+    const currGroupIsActiveInfiniteGroup =
+        currEmojiGroup.groupId === activeInfiniteGroupId;
+
+    if (!canScrollToCurrGroupFromActive && !currGroupIsActiveInfiniteGroup) {
+      return baseClassNames + ' hidden';
+    }
     return baseClassNames;
   }
 
@@ -1033,7 +1063,7 @@
    *
    * @returns {EmojiGroupElement} Instance of emoji group element.
    */
-  createEmojiGroupElement(
+  private createEmojiGroupElement(
       emoji: EmojiVariants[], preferences: {[index: string]: string},
       isHistory: boolean, subcategoryIndex: number): EmojiGroupElement {
     const baseDetails = {
@@ -1147,28 +1177,13 @@
     this.set('category', newCategory);
     this.set('pagination', 1);
 
-    // Jump to the GIF section instead of scrolling down
-    if (newCategory === CategoryEnum.GIF) {
-      // Currently displays trending items when first clicking on GIF category
-      // TODO(b/265594436): Display correct first group
-      // (account for recently used if there are history items)
-      const trendingGifElements = this.categoriesGroupElements.find(
-          groupElement => groupElement.category === CategoryEnum.GIF &&
-              groupElement.name === constants.TRENDING);
-      if (trendingGifElements) {
-        this.updateActiveGroup(trendingGifElements.groupId);
-        this.activeInfiniteGroupElements = trendingGifElements;
-      }
-    } else {
-      // Scroll to relevant category elements if emojis, emoticons or symbols
-      if (this.getSearchContainer()?.searchNotEmpty()) {
-        this.getSearchContainer()?.setSearchQuery('');
-        afterNextRender(this, () => {
-          this.scrollToGroup(this.emojiGroupTabs[0]?.groupId);
-        });
-      } else {
+    if (this.getSearchContainer()?.searchNotEmpty()) {
+      this.getSearchContainer()?.setSearchQuery('');
+      afterNextRender(this, () => {
         this.scrollToGroup(this.emojiGroupTabs[0]?.groupId);
-      }
+      });
+    } else {
+      this.scrollToGroup(this.emojiGroupTabs[0]?.groupId);
     }
   }
 
diff --git a/chrome/browser/resources/chromeos/emoji_picker/types.ts b/chrome/browser/resources/chromeos/emoji_picker/types.ts
index 70b014bf..a6e29a5e 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/types.ts
+++ b/chrome/browser/resources/chromeos/emoji_picker/types.ts
@@ -55,7 +55,7 @@
 
 export interface EmojiGroupElement {
   name: string;
-  category: string;
+  category: CategoryEnum;
   emoji: EmojiVariants[];
   groupId: string;
   active: boolean;
diff --git a/chrome/browser/resources/password_manager/password_manager_proxy.ts b/chrome/browser/resources/password_manager/password_manager_proxy.ts
index a7861fc..441f0cf 100644
--- a/chrome/browser/resources/password_manager/password_manager_proxy.ts
+++ b/chrome/browser/resources/password_manager/password_manager_proxy.ts
@@ -203,6 +203,12 @@
    * Cancels the export in progress.
    */
   cancelExportPasswords(): void;
+
+  /**
+   * Switches Biometric authentication before filling state after
+   * successful authentication.
+   */
+  switchBiometricAuthBeforeFillingState(): void;
 }
 
 /**
@@ -333,6 +339,10 @@
     chrome.passwordsPrivate.cancelExportPasswords();
   }
 
+  switchBiometricAuthBeforeFillingState() {
+    chrome.passwordsPrivate.switchBiometricAuthBeforeFillingState();
+  }
+
   static getInstance(): PasswordManagerProxy {
     return instance || (instance = new PasswordManagerImpl());
   }
diff --git a/chrome/browser/resources/password_manager/prefs/pref_toggle_button.ts b/chrome/browser/resources/password_manager/prefs/pref_toggle_button.ts
index 50c0564..d69b21d 100644
--- a/chrome/browser/resources/password_manager/prefs/pref_toggle_button.ts
+++ b/chrome/browser/resources/password_manager/prefs/pref_toggle_button.ts
@@ -49,6 +49,16 @@
         notify: true,
         reflectToAttribute: true,
       },
+
+      /**
+       * If true, do not automatically set the preference value on user click.
+       * Confirm the change first then call either sendPrefChange or
+       * resetToPrefValue accordingly.
+       */
+      changeRequiresValidation: {
+        type: Boolean,
+        value: false,
+      },
     };
   }
 
@@ -59,6 +69,7 @@
   label: string;
   subLabel: string;
   checked: boolean;
+  changeRequiresValidation: boolean;
 
   override ready() {
     super.ready();
@@ -78,6 +89,12 @@
   private onHostClick_(e: Event) {
     e.stopPropagation();
 
+    if (this.changeRequiresValidation) {
+      this.dispatchEvent(new CustomEvent(
+          'validate-and-change-pref', {bubbles: true, composed: true}));
+      return;
+    }
+
     this.checked = !this.checked;
     this.updatePrefValue_();
   }
diff --git a/chrome/browser/resources/password_manager/settings_section.html b/chrome/browser/resources/password_manager/settings_section.html
index 623d3b5..3f9f5f6 100644
--- a/chrome/browser/resources/password_manager/settings_section.html
+++ b/chrome/browser/resources/password_manager/settings_section.html
@@ -41,6 +41,17 @@
       sub-label="$i18n{autosigninDescription}"
       pref-key="credentials_enable_autosignin">
   </pref-toggle-button>
+  <if expr="is_win or is_macosx">
+    <template is="dom-if"
+        if="[[isBiometricAuthenticationForFillingToggleVisible_]]">
+      <pref-toggle-button id="biometricAuthenticationToggle" class="hr"
+          label="$i18n{biometricAuthenticaionForFillingLabel}"
+          pref-key="biometric_authentication_filling"
+          change-requires-validation
+          on-validate-and-change-pref="switchBiometricAuthBeforeFillingState_">
+      </pref-toggle-button>
+    </template>
+  </if>
   <cr-link-row id="trustedVaultBanner" class="cr-row settings-cr-link-row"
       label="$i18n{trustedVaultBannerLabelOfferOptIn}"
       sub-label="$i18n{trustedVaultBannerSubLabelOfferOptIn}" external>
diff --git a/chrome/browser/resources/password_manager/settings_section.ts b/chrome/browser/resources/password_manager/settings_section.ts
index 04f7c38..7d64ea12 100644
--- a/chrome/browser/resources/password_manager/settings_section.ts
+++ b/chrome/browser/resources/password_manager/settings_section.ts
@@ -48,6 +48,16 @@
           return loadTimeData.getBoolean('isPasswordManagerShortcutInstalled');
         },
       },
+
+      // <if expr="is_win or is_macosx">
+      isBiometricAuthenticationForFillingToggleVisible_: {
+        type: Boolean,
+        value() {
+          return loadTimeData.getBoolean(
+              'biometricAuthenticationForFillingToggleVisible');
+        },
+      },
+      // </if>
     };
   }
 
@@ -96,6 +106,15 @@
       event: DomRepeatEvent<chrome.passwordsPrivate.ExceptionEntry>) {
     PasswordManagerImpl.getInstance().removeBlockedSite(event.model.item.id);
   }
+
+  // <if expr="is_win or is_macosx">
+  private switchBiometricAuthBeforeFillingState_(e: Event) {
+    const biometricAuthenticationForFillingToggle =
+        e!.target as PrefToggleButtonElement;
+    assert(biometricAuthenticationForFillingToggle);
+    PasswordManagerImpl.getInstance().switchBiometricAuthBeforeFillingState();
+  }
+  // </if>
 }
 
 declare global {
diff --git a/chrome/browser/sharesheet/sharesheet_service.cc b/chrome/browser/sharesheet/sharesheet_service.cc
index 3c4f12ed..043c7a0 100644
--- a/chrome/browser/sharesheet/sharesheet_service.cc
+++ b/chrome/browser/sharesheet/sharesheet_service.cc
@@ -11,15 +11,17 @@
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/apps/app_service/app_icon/app_icon_util.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/launch_utils.h"
 #include "chrome/browser/sharesheet/share_action/share_action.h"
 #include "chrome/browser/sharesheet/sharesheet_service_delegator.h"
+#include "chrome/browser/sharesheet/sharesheet_types.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
-#include "components/services/app_service/public/cpp/intent_util.h"
+#include "components/services/app_service/public/cpp/intent.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -278,7 +280,7 @@
     if ((*iter)->ShouldShowAction(intent, contains_hosted_document)) {
       targets.emplace_back(TargetType::kAction, absl::nullopt,
                            (*iter)->GetActionName(), (*iter)->GetActionName(),
-                           absl::nullopt, absl::nullopt);
+                           absl::nullopt, absl::nullopt, false);
     }
     ++iter;
   }
@@ -297,13 +299,24 @@
 
   // Making a copy because we move |intent_launch_info| out below.
   auto app_id = intent_launch_info[index].app_id;
-  app_service_proxy_->LoadIcon(
-      app_service_proxy_->AppRegistryCache().GetAppType(app_id), app_id,
-      apps::IconType::kStandard, kIconSize,
-      /*allow_placeholder_icon=*/false,
-      base::BindOnce(&SharesheetService::OnIconLoaded,
-                     weak_factory_.GetWeakPtr(), std::move(intent_launch_info),
-                     std::move(targets), index, std::move(callback)));
+  absl::optional<apps::IconKey> icon_key =
+      app_service_proxy_->GetIconKey(app_id);
+  if (icon_key.has_value()) {
+    if (intent_launch_info[index].is_dlp_blocked) {
+      icon_key->icon_effects |= apps::IconEffects::kBlocked;
+    }
+    app_service_proxy_->LoadIconFromIconKey(
+        app_service_proxy_->AppRegistryCache().GetAppType(app_id), app_id,
+        icon_key.value(), apps::IconType::kStandard, kIconSize,
+        /*allow_placeholder_icon=*/false,
+        base::BindOnce(&SharesheetService::OnIconLoaded,
+                       weak_factory_.GetWeakPtr(),
+                       std::move(intent_launch_info), std::move(targets), index,
+                       std::move(callback)));
+  } else {
+    OnIconLoaded(std::move(intent_launch_info), std::move(targets), index,
+                 std::move(callback), std::make_unique<apps::IconValue>());
+  }
 }
 
 void SharesheetService::OnIconLoaded(
@@ -330,11 +343,11 @@
             (icon_value && icon_value->icon_type == apps::IconType::kStandard)
                 ? icon_value->uncompressed
                 : gfx::ImageSkia();
-        targets.emplace_back(target_type, image_skia,
-                             base::UTF8ToUTF16(launch_entry.app_id),
-                             base::UTF8ToUTF16(update.Name()),
-                             base::UTF8ToUTF16(launch_entry.activity_label),
-                             launch_entry.activity_name);
+        targets.emplace_back(
+            target_type, image_skia, base::UTF8ToUTF16(launch_entry.app_id),
+            base::UTF8ToUTF16(update.Name()),
+            base::UTF8ToUTF16(launch_entry.activity_label),
+            launch_entry.activity_name, launch_entry.is_dlp_blocked);
       });
 
   LoadAppIcons(std::move(intent_launch_info), std::move(targets), index + 1,
diff --git a/chrome/browser/sharesheet/sharesheet_types.cc b/chrome/browser/sharesheet/sharesheet_types.cc
index 35a3386..1622dc89 100644
--- a/chrome/browser/sharesheet/sharesheet_types.cc
+++ b/chrome/browser/sharesheet/sharesheet_types.cc
@@ -12,13 +12,15 @@
     const std::u16string& launch_name,
     const std::u16string& display_name,
     const absl::optional<std::u16string>& secondary_display_name,
-    const absl::optional<std::string>& activity_name)
+    const absl::optional<std::string>& activity_name,
+    bool is_dlp_blocked)
     : type(type),
       icon(icon),
       launch_name(launch_name),
       display_name(display_name),
       secondary_display_name(secondary_display_name),
-      activity_name(activity_name) {}
+      activity_name(activity_name),
+      is_dlp_blocked(is_dlp_blocked) {}
 
 TargetInfo::~TargetInfo() = default;
 
diff --git a/chrome/browser/sharesheet/sharesheet_types.h b/chrome/browser/sharesheet/sharesheet_types.h
index 86379fd..31c5b4cb 100644
--- a/chrome/browser/sharesheet/sharesheet_types.h
+++ b/chrome/browser/sharesheet/sharesheet_types.h
@@ -32,7 +32,8 @@
              const std::u16string& launch_name,
              const std::u16string& display_name,
              const absl::optional<std::u16string>& secondary_display_name,
-             const absl::optional<std::string>& activity_name);
+             const absl::optional<std::string>& activity_name,
+             bool is_dlp_blocked);
   ~TargetInfo();
 
   // Allow move.
@@ -67,6 +68,9 @@
   // The activity of the app for the target. This only applies when the app type
   // is kArc.
   absl::optional<std::string> activity_name;
+
+  // Whether the target is blocked by Data Leak Prevention (DLP).
+  bool is_dlp_blocked;
 };
 
 using DeliveredCallback = base::OnceCallback<void(SharesheetResult success)>;
diff --git a/chrome/browser/signin/chrome_signin_helper.cc b/chrome/browser/signin/chrome_signin_helper.cc
index 2d0e3e25..83b5c8d 100644
--- a/chrome/browser/signin/chrome_signin_helper.cc
+++ b/chrome/browser/signin/chrome_signin_helper.cc
@@ -48,6 +48,12 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "components/account_manager_core/chromeos/account_manager_facade_factory.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
+#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
+
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -203,18 +209,21 @@
   signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
       account_reconcilor->GetState());
 
-  bool should_ignore_guest_webview = true;
+  bool should_process_guest_webview_request = false;
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   // The mirror headers from some guest web views need to be processed.
-  should_ignore_guest_webview =
-      HeaderModificationDelegateImpl::ShouldIgnoreGuestWebViewRequest(
+  bool is_guest = extensions::WebViewRendererState::GetInstance()->IsGuest(
+      web_contents->GetPrimaryMainFrame()->GetProcess()->GetID());
+  should_process_guest_webview_request =
+      is_guest &&
+      !HeaderModificationDelegateImpl::ShouldIgnoreGuestWebViewRequest(
           web_contents);
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
   // Do not do anything if the navigation happened in the "background".
   if ((!browser || !browser->window()->IsActive()) &&
-      should_ignore_guest_webview) {
+      !should_process_guest_webview_request) {
     return;
   }
 
diff --git a/chrome/browser/signin/mirror_browsertest.cc b/chrome/browser/signin/mirror_browsertest.cc
index 7b4501e..ef0211e 100644
--- a/chrome/browser/signin/mirror_browsertest.cc
+++ b/chrome/browser/signin/mirror_browsertest.cc
@@ -9,16 +9,13 @@
 
 #include "base/base_switches.h"
 #include "base/command_line.h"
-#include "base/containers/flat_map.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
-#include "base/strings/string_util.h"
 #include "base/test/bind.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/extensions/api/identity/web_auth_flow.h"
@@ -36,7 +33,6 @@
 #include "components/signin/public/base/signin_pref_names.h"
 #include "content/public/common/content_client.h"
 #include "content/public/test/browser_test.h"
-#include "google_apis/gaia/gaia_switches.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -45,10 +41,6 @@
 #include "third_party/blink/public/common/loader/url_loader_throttle.h"
 #include "url/gurl.h"
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-#include "chrome/browser/lacros/account_manager/fake_account_manager_ui_dialog_waiter.h"
-#endif
-
 namespace {
 
 // A delegate to insert a user generated X-Chrome-Connected header to a specific
@@ -283,109 +275,4 @@
   RunExtensionConsentTest(extensions::WebAuthFlow::GET_AUTH_TOKEN, true);
 }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-
-// Tests the behavior of Chrome when it receives a Mirror response from Gaia:
-// - listens to all network responses coming from Gaia with
-//   `signin::HeaderModificationDelegate`.
-// - parses the Mirror response header with
-// `signin::BuildManageAccountsParams()`
-// - triggers dialogs based on the action specified in the header, with
-//   `ProcessMirrorHeader`
-// The tests don't display real dialogs. Instead they use the
-// `FakeAccountManagerUI` and only check that the dialogs were triggered.
-class MirrorResponseBrowserTest : public InProcessBrowserTest {
- public:
-  MirrorResponseBrowserTest(const MirrorResponseBrowserTest&) = delete;
-  MirrorResponseBrowserTest& operator=(const MirrorResponseBrowserTest&) =
-      delete;
-
- protected:
-  ~MirrorResponseBrowserTest() override = default;
-
-  MirrorResponseBrowserTest()
-      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
-
-  // Navigates to Gaia and receives a response with the specified
-  // "X-Chrome-Manage-Accounts" header.
-  void ReceiveManageAccountsHeader(
-      const base::flat_map<std::string, std::string>& header_params) {
-    std::vector<std::string> parts;
-    for (const auto& [key, value] : header_params) {
-      // "=" must be escaped as "%3D" for the embedded server.
-      const char kEscapedEquals[] = "%3D";
-      parts.push_back(key + kEscapedEquals + value);
-    }
-    std::string path = std::string("/set-header?X-Chrome-Manage-Accounts: ") +
-                       base::JoinString(parts, ",");
-    ASSERT_TRUE(
-        ui_test_utils::NavigateToURL(browser(), https_server_.GetURL(path)));
-  }
-
-  // InProcessBrowserTest:
-  void SetUp() override {
-    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
-    ASSERT_TRUE(https_server_.InitializeAndListen());
-    InProcessBrowserTest::SetUp();
-  }
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    InProcessBrowserTest::SetUpCommandLine(command_line);
-    const GURL& base_url = https_server_.base_url();
-    command_line->AppendSwitchASCII(switches::kGaiaUrl, base_url.spec());
-    command_line->AppendSwitchASCII(switches::kGoogleApisUrl, base_url.spec());
-    command_line->AppendSwitchASCII(switches::kLsoUrl, base_url.spec());
-  }
-
-  void SetUpOnMainThread() override {
-    https_server_.StartAcceptingConnections();
-    InProcessBrowserTest::SetUpOnMainThread();
-  }
-
-  net::EmbeddedTestServer https_server_;
-  net::test_server::EmbeddedTestServerHandle https_server_handle_;
-};
-
-// Tests that the "Add Account" dialog is shown when receiving "ADDSESSION" from
-// Gaia.
-IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, AddSession) {
-  FakeAccountManagerUIDialogWaiter dialog_waiter(
-      GetFakeAccountManagerUI(),
-      FakeAccountManagerUIDialogWaiter::Event::kAddAccount);
-  ReceiveManageAccountsHeader({{"action", "ADDSESSION"}});
-  dialog_waiter.Wait();
-}
-
-// Tests that the "Settings"" dialog is shown when receiving "DEFAULT" from
-// Gaia.
-IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Settings) {
-  FakeAccountManagerUIDialogWaiter dialog_waiter(
-      GetFakeAccountManagerUI(),
-      FakeAccountManagerUIDialogWaiter::Event::kSettings);
-  ReceiveManageAccountsHeader({{"action", "DEFAULT"}});
-  dialog_waiter.Wait();
-}
-
-// Tests that the "Reauth" dialog is shown when receiving an email from Gaia.
-IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Reauth) {
-  FakeAccountManagerUIDialogWaiter dialog_waiter(
-      GetFakeAccountManagerUI(),
-      FakeAccountManagerUIDialogWaiter::Event::kReauth);
-  ReceiveManageAccountsHeader(
-      {{"action", "ADDSESSION"}, {"email", "user@example.com"}});
-  dialog_waiter.Wait();
-}
-
-// Tests that incognito browser is opened when receiving "INCOGNITO" from Gaia.
-IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Incognito) {
-  ui_test_utils::BrowserChangeObserver browser_change_observer(
-      /*browser=*/nullptr,
-      ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
-  ReceiveManageAccountsHeader({{"action", "INCOGNITO"}});
-  Browser* incognito_browser = browser_change_observer.Wait();
-  EXPECT_TRUE(incognito_browser->profile()->IsIncognitoProfile());
-}
-
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 }  // namespace
diff --git a/chrome/browser/signin/mirror_interactive_uitest.cc b/chrome/browser/signin/mirror_interactive_uitest.cc
new file mode 100644
index 0000000..f6d6e620
--- /dev/null
+++ b/chrome/browser/signin/mirror_interactive_uitest.cc
@@ -0,0 +1,169 @@
+// 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 <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/containers/flat_map.h"
+#include "base/functional/callback.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/signin_util.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/browser/ui/browser_navigator_params.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/browser_test.h"
+#include "google_apis/gaia/gaia_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/request_handler_util.h"
+#include "third_party/blink/public/common/loader/url_loader_throttle.h"
+#include "ui/base/window_open_disposition.h"
+#include "url/gurl.h"
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/lacros/account_manager/fake_account_manager_ui_dialog_waiter.h"
+#endif
+
+// Tests the behavior of Chrome when it receives a Mirror response from Gaia:
+// - listens to all network responses coming from Gaia with
+//   `signin::HeaderModificationDelegate`.
+// - parses the Mirror response header with
+// `signin::BuildManageAccountsParams()`
+// - triggers dialogs based on the action specified in the header, with
+//   `ProcessMirrorHeader`
+// The tests don't display real dialogs. Instead they use the
+// `FakeAccountManagerUI` and only check that the dialogs were triggered.
+// The tests are interactive_ui_tests because they depend on browser's window
+// activation state.
+class MirrorResponseBrowserTest : public InProcessBrowserTest {
+ public:
+  MirrorResponseBrowserTest(const MirrorResponseBrowserTest&) = delete;
+  MirrorResponseBrowserTest& operator=(const MirrorResponseBrowserTest&) =
+      delete;
+
+ protected:
+  ~MirrorResponseBrowserTest() override = default;
+
+  MirrorResponseBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  // Navigates to Gaia and receives a response with the specified
+  // "X-Chrome-Manage-Accounts" header.
+  void ReceiveManageAccountsHeader(
+      const base::flat_map<std::string, std::string>& header_params) {
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(
+        browser(), GetUrlWithManageAccountsHeader(header_params)));
+  }
+
+  GURL GetUrlWithManageAccountsHeader(
+      const base::flat_map<std::string, std::string>& header_params) {
+    std::vector<std::string> parts;
+    for (const auto& [key, value] : header_params) {
+      // "=" must be escaped as "%3D" for the embedded server.
+      const char kEscapedEquals[] = "%3D";
+      parts.push_back(key + kEscapedEquals + value);
+    }
+    std::string path = std::string("/set-header?X-Chrome-Manage-Accounts: ") +
+                       base::JoinString(parts, ",");
+    return https_server_.GetURL(path);
+  }
+
+  // InProcessBrowserTest:
+  void SetUp() override {
+    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
+    ASSERT_TRUE(https_server_.InitializeAndListen());
+    InProcessBrowserTest::SetUp();
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    InProcessBrowserTest::SetUpCommandLine(command_line);
+    const GURL& base_url = https_server_.base_url();
+    command_line->AppendSwitchASCII(switches::kGaiaUrl, base_url.spec());
+    command_line->AppendSwitchASCII(switches::kGoogleApisUrl, base_url.spec());
+    command_line->AppendSwitchASCII(switches::kLsoUrl, base_url.spec());
+  }
+
+  void SetUpOnMainThread() override {
+    https_server_.StartAcceptingConnections();
+    InProcessBrowserTest::SetUpOnMainThread();
+  }
+
+  net::EmbeddedTestServer https_server_;
+  net::test_server::EmbeddedTestServerHandle https_server_handle_;
+};
+
+// Following tests try to display the ChromeOS account manager dialogs. They can
+// currently be tested only on Lacros which injects a `FakeAccountManagerUI`.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+
+// Tests that the "Add Account" dialog is shown when receiving "ADDSESSION" from
+// Gaia.
+IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, AddSession) {
+  FakeAccountManagerUIDialogWaiter dialog_waiter(
+      GetFakeAccountManagerUI(),
+      FakeAccountManagerUIDialogWaiter::Event::kAddAccount);
+  ReceiveManageAccountsHeader({{"action", "ADDSESSION"}});
+  dialog_waiter.Wait();
+}
+
+// Tests that the "Settings"" dialog is shown when receiving "DEFAULT" from
+// Gaia.
+IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Settings) {
+  FakeAccountManagerUIDialogWaiter dialog_waiter(
+      GetFakeAccountManagerUI(),
+      FakeAccountManagerUIDialogWaiter::Event::kSettings);
+  ReceiveManageAccountsHeader({{"action", "DEFAULT"}});
+  dialog_waiter.Wait();
+}
+
+// Tests that the "Reauth" dialog is shown when receiving an email from Gaia.
+IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Reauth) {
+  FakeAccountManagerUIDialogWaiter dialog_waiter(
+      GetFakeAccountManagerUI(),
+      FakeAccountManagerUIDialogWaiter::Event::kReauth);
+  ReceiveManageAccountsHeader(
+      {{"action", "ADDSESSION"}, {"email", "user@example.com"}});
+  dialog_waiter.Wait();
+}
+
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+// Tests that incognito browser is opened when receiving "INCOGNITO" from Gaia.
+IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Incognito) {
+  ui_test_utils::BrowserChangeObserver browser_change_observer(
+      /*browser=*/nullptr,
+      ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
+  ReceiveManageAccountsHeader({{"action", "INCOGNITO"}});
+  Browser* incognito_browser = browser_change_observer.Wait();
+  EXPECT_TRUE(incognito_browser->profile()->IsIncognitoProfile());
+}
+
+// Tests that the response is coming from a background browser is ignored.
+IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, BackgroundResponseIgnored) {
+  // Minimize the browser window to disactivate it.
+  browser()->window()->Minimize();
+  EXPECT_FALSE(browser()->window()->IsActive());
+
+  size_t browser_count = chrome::GetTotalBrowserCount();
+  GURL url = GetUrlWithManageAccountsHeader({{"action", "INCOGNITO"}});
+  NavigateParams params(browser(), url, ui::PAGE_TRANSITION_FROM_API);
+  // Use `NEW_BACKGROUND_TAB` to avoid activating `browser()`.
+  params.disposition = WindowOpenDisposition::NEW_BACKGROUND_TAB;
+  Navigate(&params);
+  EXPECT_TRUE(content::WaitForLoadStop(params.navigated_or_inserted_contents));
+
+  // Incognito window should not have been displayed, the browser count stays
+  // the same.
+  EXPECT_EQ(chrome::GetTotalBrowserCount(), browser_count);
+}
diff --git a/chrome/browser/support_tool/data_collection_module.proto b/chrome/browser/support_tool/data_collection_module.proto
index ed97da40..00680ab 100644
--- a/chrome/browser/support_tool/data_collection_module.proto
+++ b/chrome/browser/support_tool/data_collection_module.proto
@@ -37,6 +37,27 @@
   CHROMEOS_NETWORK_HEALTH = 23;
 }
 
+// PiiType represent the different known categories of PII that can exist in
+// logs. The types here are based on PII types listed in
+// components/feedback/pii_types.h.
+enum PiiType {
+  PII_TYPE_UNSPECIFIED = 0;
+  ANDROID_APP_STORAGE_PATH = 1;
+  EMAIL = 2;
+  GAIA_ID = 3;
+  IPP_ADDRESS = 4;
+  IP_ADDRESS = 5;
+  LOCATION_INFO = 6;
+  MAC_ADDRESS = 7;
+  UI_HIEARCHY_WINDOW_TITLE = 8;
+  URL = 9;
+  SERIAL = 10;
+  SSID = 11;
+  STABLE_IDENTIFIER = 12;
+  VOLUME_LABEL = 13;
+  EAP = 14;
+}
+
 // Used for storing the information about which data colllectors will be used
 // when Support Tool creates the support packet.
 message DataCollectionModule {
diff --git a/chrome/browser/sync/sync_startup_tracker.cc b/chrome/browser/sync/sync_startup_tracker.cc
index b97ecab..15179cd 100644
--- a/chrome/browser/sync/sync_startup_tracker.cc
+++ b/chrome/browser/sync/sync_startup_tracker.cc
@@ -5,8 +5,8 @@
 #include "chrome/browser/sync/sync_startup_tracker.h"
 
 #include "base/functional/bind.h"
+#include "base/notreached.h"
 #include "components/sync/driver/sync_service.h"
-#include "google_apis/gaia/google_service_auth_error.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace {
@@ -69,24 +69,30 @@
     return ServiceStartupState::kError;
   }
 
-  // If the sync engine has started up, notify the callback.
-  if (sync_service->IsEngineInitialized()) {
-    return ServiceStartupState::kComplete;
+  // Unrecoverable errors return false for CanSyncFeatureStart(), handled above.
+  DCHECK(!sync_service->HasUnrecoverableError());
+
+  switch (sync_service->GetTransportState()) {
+    case syncer::SyncService::TransportState::DISABLED:
+      NOTREACHED();
+      break;
+    case syncer::SyncService::TransportState::START_DEFERRED:
+    case syncer::SyncService::TransportState::INITIALIZING:
+      // No error detected yet, but the sync engine hasn't started up yet, so
+      // we're in the pending state.
+      return ServiceStartupState::kPending;
+    case syncer::SyncService::TransportState::PAUSED:
+      // Persistent auth errors lead to sync pausing.
+      return ServiceStartupState::kError;
+    case syncer::SyncService::TransportState::PENDING_DESIRED_CONFIGURATION:
+    case syncer::SyncService::TransportState::CONFIGURING:
+    case syncer::SyncService::TransportState::ACTIVE:
+      DCHECK(sync_service->IsEngineInitialized());
+      return ServiceStartupState::kComplete;
   }
 
-  // If the sync service has some kind of error, report to the user.
-  if (sync_service->HasUnrecoverableError()) {
-    return ServiceStartupState::kError;
-  }
-
-  // If we have an auth error, exit.
-  if (sync_service->GetAuthError().state() != GoogleServiceAuthError::NONE) {
-    return ServiceStartupState::kError;
-  }
-
-  // No error detected yet, but the sync engine hasn't started up yet, so
-  // we're in the pending state.
-  return ServiceStartupState::kPending;
+  NOTREACHED();
+  return ServiceStartupState::kError;
 }
 
 namespace testing {
diff --git a/chrome/browser/sync/test/integration/single_client_contact_info_sync_test.cc b/chrome/browser/sync/test/integration/single_client_contact_info_sync_test.cc
index c1326c9..6f69759 100644
--- a/chrome/browser/sync/test/integration/single_client_contact_info_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_contact_info_sync_test.cc
@@ -6,9 +6,11 @@
 
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/sync/test/integration/contact_info_helper.h"
+#include "chrome/browser/sync/test/integration/encryption_helper.h"
 #include "chrome/browser/sync/test/integration/fake_server_match_status_checker.h"
 #include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
+#include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
 #include "components/autofill/core/browser/contact_info_sync_util.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/common/autofill_features.h"
@@ -133,6 +135,41 @@
           .Wait());
 }
 
+// Specialized fixture to test the behavior for custom passphrase users with and
+// without kSyncEnableContactInfoDataTypeForCustomPassphraseUsers enabled.
+class SingleClientContactInfoPassphraseSyncTest
+    : public SingleClientContactInfoSyncTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  SingleClientContactInfoPassphraseSyncTest() {
+    feature_.InitWithFeatureState(
+        syncer::kSyncEnableContactInfoDataTypeForCustomPassphraseUsers,
+        EnabledForPassphraseUsersTestParam());
+  }
+
+  bool EnabledForPassphraseUsersTestParam() const { return GetParam(); }
+
+ private:
+  base::test::ScopedFeatureList feature_;
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientContactInfoPassphraseSyncTest,
+                         testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(SingleClientContactInfoPassphraseSyncTest, Passphrase) {
+  ASSERT_TRUE(SetupSync());
+  ASSERT_TRUE(
+      GetSyncService(0)->GetActiveDataTypes().Has(syncer::CONTACT_INFO));
+  GetSyncService(0)->GetUserSettings()->SetEncryptionPassphrase("123456");
+  ASSERT_TRUE(
+      ServerPassphraseTypeChecker(syncer::PassphraseType::kCustomPassphrase)
+          .Wait());
+  ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
+  EXPECT_EQ(GetSyncService(0)->GetActiveDataTypes().Has(syncer::CONTACT_INFO),
+            EnabledForPassphraseUsersTestParam());
+}
+
 // Specialized fixture that enables AutofillAccountProfilesOnSignIn.
 class SingleClientContactInfoTransportSyncTest
     : public SingleClientContactInfoSyncTest {
diff --git a/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java b/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java
index 51ce2fb3..a46dae5 100644
--- a/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java
+++ b/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java
@@ -35,7 +35,6 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
@@ -149,7 +148,6 @@
 
     @Test
     @MediumTest
-    @DisabledTest(message = "crbug.com/1348345")
     public void testBackDismissesAndCallsCallback() {
         runOnUiThreadBlocking(() -> {
             mTouchToFill.showCredentials(
diff --git a/chrome/browser/ui/ash/media_client_impl.cc b/chrome/browser/ui/ash/media_client_impl.cc
index 26ceccb7..1a603ae2 100644
--- a/chrome/browser/ui/ash/media_client_impl.cc
+++ b/chrome/browser/ui/ash/media_client_impl.cc
@@ -263,11 +263,10 @@
 }
 
 // Small helper to make sure that `kCameraPrivacySwitchOnNotificationIdPrefix`
-// combined with `device_name` always produce the same identifier.
+// combined with `device_id` always produce the same identifier.
 std::string PrivacySwitchOnNotificationIdForDevice(
-    const std::string& device_name) {
-  return base::StrCat(
-      {kCameraPrivacySwitchOnNotificationIdPrefix, device_name});
+    const std::string& device_id) {
+  return base::StrCat({kCameraPrivacySwitchOnNotificationIdPrefix, device_id});
 }
 
 }  // namespace
@@ -477,10 +476,10 @@
         weak_ptr_factory_.GetWeakPtr()));
   } else if (state == cros::mojom::CameraPrivacySwitchState::ON) {
     // The software switch is ON. Clear all hardware switch notifications.
-    for (const auto& notification_id : existing_notifications_) {
-      SystemNotificationHelper::GetInstance()->Close(notification_id);
+    for (auto it = devices_having_visible_notification_.begin();
+         it != devices_having_visible_notification_.end();) {
+      it = RemoveCameraOffNotificationForDevice(*it);
     }
-    existing_notifications_.clear();
   }
 }
 
@@ -541,8 +540,7 @@
   if (active_camera_client_count_ > 0) {
     ShowCameraOffNotification(device_id, device_name, /*resurface=*/false);
   } else {
-    RemoveCameraOffNotificationByID(
-        PrivacySwitchOnNotificationIdForDevice(device_name));
+    RemoveCameraOffNotificationForDevice(device_id);
   }
 }
 
@@ -699,28 +697,31 @@
         app_name, std::make_pair(device_id, device_name));
   }
 
-  const std::string notification_id =
-      PrivacySwitchOnNotificationIdForDevice(device_name);
-
   if (resurface) {
-    RemoveCameraOffNotificationByID(notification_id);
+    RemoveCameraOffNotificationForDevice(device_id);
   }
 
   SystemNotificationHelper::GetInstance()->Display(
       notification_.builder()
-          .SetId(notification_id)
+          .SetId(PrivacySwitchOnNotificationIdForDevice(device_id))
           .SetTitleWithArgs(IDS_CAMERA_PRIVACY_SWITCH_ON_NOTIFICATION_TITLE,
                             {device_name_u16})
           .SetMessageWithArgs(IDS_CAMERA_PRIVACY_SWITCH_ON_NOTIFICATION_MESSAGE,
                               {device_name_u16})
           .Build());
-  existing_notifications_.insert(notification_id);
+  devices_having_visible_notification_.insert(device_id);
 }
 
-void MediaClientImpl::RemoveCameraOffNotificationByID(
-    const std::string& notification_id) {
-  SystemNotificationHelper::GetInstance()->Close(notification_id);
-  existing_notifications_.erase(notification_id);
+base::flat_set<std::string>::iterator
+MediaClientImpl::RemoveCameraOffNotificationForDevice(
+    const std::string& device_id) {
+  auto it = devices_having_visible_notification_.find(device_id);
+  if (it != devices_having_visible_notification_.end()) {
+    SystemNotificationHelper::GetInstance()->Close(
+        PrivacySwitchOnNotificationIdForDevice(device_id));
+    return devices_having_visible_notification_.erase(it);
+  }
+  return it;
 }
 
 void MediaClientImpl::OnGetSourceInfosByCameraHWPrivacySwitchStateChanged(
@@ -810,8 +811,7 @@
   }
 
   if (state == cros::mojom::CameraPrivacySwitchState::OFF) {
-    RemoveCameraOffNotificationByID(
-        PrivacySwitchOnNotificationIdForDevice(device_name));
+    RemoveCameraOffNotificationForDevice(device_id);
   }
 }
 
@@ -827,11 +827,20 @@
       // As the device is being actively used by the client, display a
       // notification.
       ShowCameraOffNotification(device_id, device_name);
-    } else if (active_camera_client_count_ == 0) {
-      // Clear the notification for this device as no client is trying to use
-      // this camera anymore.
-      RemoveCameraOffNotificationByID(
-          PrivacySwitchOnNotificationIdForDevice(device_name));
+    } else if (!IsDeviceActive(device_id)) {
+      // No application is actively using this camera. Remove the notification
+      // for the device if exists.
+      RemoveCameraOffNotificationForDevice(device_id);
+    }
+  }
+
+  // Remove notifications for detached devices if any.
+  for (auto it = devices_having_visible_notification_.begin();
+       it != devices_having_visible_notification_.end();) {
+    if (IsDeviceActive(*it)) {
+      ++it;
+    } else {
+      it = RemoveCameraOffNotificationForDevice(*it);
     }
   }
 }
diff --git a/chrome/browser/ui/ash/media_client_impl.h b/chrome/browser/ui/ash/media_client_impl.h
index 63bc745d..27c3ea25 100644
--- a/chrome/browser/ui/ash/media_client_impl.h
+++ b/chrome/browser/ui/ash/media_client_impl.h
@@ -144,9 +144,10 @@
                                  const std::string& device_name,
                                  bool resurface = true);
 
-  // Removes the camera notification with the give id `notification_id` and
-  // updates the internal data structures of the class accordingly.
-  void RemoveCameraOffNotificationByID(const std::string& notification_id);
+  // Removes the camera notification for device with id `device_id` and returns
+  // iterator to the next device id in `devices_having_visible_notification_`.
+  base::flat_set<std::string>::iterator RemoveCameraOffNotificationForDevice(
+      const std::string& device_id);
 
   void OnGetSourceInfosByCameraHWPrivacySwitchStateChanged(
       const std::string& device_id,
@@ -204,9 +205,9 @@
   base::flat_map<cros::mojom::CameraClientType, base::flat_set<std::string>>
       devices_used_by_client_;
 
-  // IDs of the existing camera hardware switch notifications in the message
-  // center.
-  base::flat_set<std::string> existing_notifications_;
+  // Set of IDs of the camera devices having a visible notification in the
+  // message center.
+  base::flat_set<std::string> devices_having_visible_notification_;
 
   // Stores the state of the camera software privacy switch state locally.
   cros::mojom::CameraPrivacySwitchState camera_sw_privacy_switch_state_ =
diff --git a/chrome/browser/ui/ash/media_client_impl_unittest.cc b/chrome/browser/ui/ash/media_client_impl_unittest.cc
index 30637c0..8a08d21 100644
--- a/chrome/browser/ui/ash/media_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/media_client_impl_unittest.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/ui/ash/media_client_impl.h"
 
 #include <memory>
+#include <string>
+#include <vector>
 
 #include "ash/public/cpp/media_controller.h"
 #include "ash/public/cpp/test/test_new_window_delegate.h"
@@ -24,6 +26,7 @@
 #include "components/services/app_service/public/cpp/capability_access.h"
 #include "components/services/app_service/public/cpp/capability_access_update.h"
 #include "components/user_manager/fake_user_manager.h"
+#include "media/capture/video/video_capture_device_info.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/accelerators/media_keys_listener.h"
@@ -107,11 +110,14 @@
   void RemoveObserver(NotificationDisplayService::Observer* observer) override {
   }
 
-  bool HasNotificationMessageContaining(const std::string& app_name) const {
-    const std::u16string app_name_u16 = base::UTF8ToUTF16(app_name);
+  // Returns true if any existing notification contains `keywords` as a
+  // substring.
+  bool HasNotificationMessageContaining(const std::string& keywords) const {
+    const std::u16string keywords_u16 = base::UTF8ToUTF16(keywords);
     for (const auto& [notification_id, notification] : active_notifications_) {
-      if (notification.message().find(app_name_u16) != std::u16string::npos)
+      if (notification.message().find(keywords_u16) != std::u16string::npos) {
         return true;
+      }
     }
     return false;
   }
@@ -318,6 +324,29 @@
         device_id};
   }
 
+  void OnActiveClientChange(
+      cros::mojom::CameraClientType type,
+      const base::flat_set<std::string>& active_device_ids,
+      int active_client_count) {
+    media_client_.devices_used_by_client_.insert_or_assign(type,
+                                                           active_device_ids);
+    media_client_.active_camera_client_count_ = active_client_count;
+
+    media_client_.OnGetSourceInfosByActiveClientChanged(active_device_ids,
+                                                        video_capture_devices_);
+  }
+
+  void AttachCamera(const std::string& device_id,
+                    const std::string& device_name) {
+    media::VideoCaptureDeviceInfo device_info;
+    device_info.descriptor.device_id = device_id;
+    device_info.descriptor.set_display_name(device_name);
+    video_capture_devices_.push_back(device_info);
+  }
+
+  // Detaches the most recently attached camera.
+  void DetachCamera() { video_capture_devices_.pop_back(); }
+
   void ShowCameraOffNotification(const std::string& device_id,
                                  const std::string& device_name) {
     media_client_.ShowCameraOffNotification(device_id, device_name);
@@ -355,6 +384,7 @@
   user_manager::FakeUserManager user_manager_;
   base::raw_ptr<MockNewWindowDelegate> new_window_delegate_ = nullptr;
   std::unique_ptr<ash::TestNewWindowDelegateProvider> window_delegate_provider_;
+  std::vector<media::VideoCaptureDeviceInfo> video_capture_devices_;
 };
 
 TEST_F(MediaClientTest, HandleMediaAccelerators) {
@@ -595,7 +625,62 @@
   EXPECT_CALL(*new_window_delegate_, OpenUrl).Times(1);
 
   notification_display_service->SimulateClick(
-      "ash.media.camera.activity_with_privacy_switch_on.device_name", 0);
+      "ash.media.camera.activity_with_privacy_switch_on.device_id", 0);
 
   EXPECT_EQ(notification_display_service->NumberOfActiveNotifications(), 0u);
 }
+
+TEST_F(MediaClientAppUsingCameraInBrowserEnvironmentTest,
+       NotificationRemovedWhenCameraDetachedOrInactive) {
+  FakeNotificationDisplayService* notification_display_service =
+      SetSystemNotificationService();
+
+  // No notification initially.
+  EXPECT_EQ(0u, notification_display_service->NumberOfActiveNotifications());
+
+  const std::string camera1 = "camera1";
+  const std::string camera1_name = "Fake camera 1";
+  const std::string camera2 = "camera2";
+  const std::string camera2_name = "Fake camera 2";
+
+  // Attach two cameras to the device. Both of the cameras have HW switch. Turn
+  // the HW switch ON for both of the devices.
+  AttachCamera(camera1, camera1_name);
+  SetCameraHWPrivacySwitchState(camera1,
+                                cros::mojom::CameraPrivacySwitchState::ON);
+  AttachCamera(camera2, camera2_name);
+  SetCameraHWPrivacySwitchState(camera2,
+                                cros::mojom::CameraPrivacySwitchState::ON);
+
+  // Still no notification.
+  EXPECT_EQ(notification_display_service->NumberOfActiveNotifications(), 0u);
+
+  // `CHROME` client starts accessing camera1. A hardware switch notification
+  // for camera1 should be displayed.
+  OnActiveClientChange(cros::mojom::CameraClientType::CHROME, {camera1}, 1);
+  EXPECT_EQ(1u, notification_display_service->NumberOfActiveNotifications());
+  EXPECT_TRUE(notification_display_service->HasNotificationMessageContaining(
+      camera1_name));
+
+  // `CHROME` client starts accessing camera2 as well. A hardware switch
+  // notification for camera2 should be displayed.
+  OnActiveClientChange(cros::mojom::CameraClientType::CHROME,
+                       {camera1, camera2}, 1);
+  EXPECT_EQ(2u, notification_display_service->NumberOfActiveNotifications());
+  EXPECT_TRUE(notification_display_service->HasNotificationMessageContaining(
+      camera2_name));
+
+  // `CHROME` client stops accessing camera1. The respective notification should
+  // be removed.
+  OnActiveClientChange(cros::mojom::CameraClientType::CHROME, {camera2}, 1);
+  EXPECT_EQ(1u, notification_display_service->NumberOfActiveNotifications());
+  EXPECT_FALSE(notification_display_service->HasNotificationMessageContaining(
+      camera1_name));
+
+  // Detach camera2.
+  DetachCamera();
+  // `CHROME` client stops accessing camera2 as the camera is detached. The
+  // respective notification should be removed.
+  OnActiveClientChange(cros::mojom::CameraClientType::CHROME, {}, 0);
+  EXPECT_EQ(0u, notification_display_service->NumberOfActiveNotifications());
+}
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc
index 3341c10..20b076a 100644
--- a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc
@@ -377,7 +377,7 @@
         base::BindRepeating(&SharesheetBubbleView::TargetButtonPressed,
                             base::Unretained(this), target),
         display_name, secondary_display_name, icon,
-        delegator_->GetVectorIcon(display_name)));
+        delegator_->GetVectorIcon(display_name), target.is_dlp_blocked));
   }
 }
 
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_browsertest.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_browsertest.cc
index 3c8075f..4c93e420 100644
--- a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_browsertest.cc
@@ -9,21 +9,31 @@
 #include "ash/shell.h"
 #include "base/functional/callback_helpers.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/apps/app_service/app_service_test.h"
+#include "chrome/browser/ash/policy/dlp/dlp_files_controller.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h"
+#include "chrome/browser/chromeos/policy/dlp/mock_dlp_rules_manager.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sharesheet/sharesheet_metrics.h"
 #include "chrome/browser/sharesheet/sharesheet_service.h"
 #include "chrome/browser/sharesheet/sharesheet_service_factory.h"
 #include "chrome/browser/sharesheet/sharesheet_types.h"
+#include "chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h"
+#include "chrome/browser/ui/ash/sharesheet/sharesheet_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chromeos/components/sharesheet/constants.h"
-#include "components/services/app_service/public/cpp/intent_filter_util.h"
 #include "components/services/app_service/public/cpp/intent_test_util.h"
 #include "components/services/app_service/public/cpp/intent_util.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/window.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
@@ -84,9 +94,11 @@
     ASSERT_FALSE(sharesheet_widget_->IsVisible());
   }
 
+ protected:
+  views::Widget* sharesheet_widget_;
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
-  views::Widget* sharesheet_widget_;
 };
 
 INSTANTIATE_TEST_SUITE_P(All,
@@ -99,5 +111,131 @@
   DismissUi();
 }
 
+class SharesheetBubbleViewPolicyBrowserTest
+    : public SharesheetBubbleViewBrowserTest {
+ public:
+  class MockFilesController : public policy::DlpFilesController {
+   public:
+    explicit MockFilesController(const policy::DlpRulesManager& rules_manager)
+        : DlpFilesController(rules_manager) {}
+    ~MockFilesController() override = default;
+
+    MOCK_METHOD(bool,
+                IsLaunchBlocked,
+                (const apps::AppUpdate&, const apps::IntentPtr&),
+                (override));
+  };
+
+  void SetupRulesManager(bool is_dlp_blocked) {
+    policy::DlpRulesManagerFactory::GetInstance()->SetTestingFactory(
+        browser()->profile(),
+        base::BindRepeating(
+            &SharesheetBubbleViewPolicyBrowserTest::SetDlpRulesManager,
+            base::Unretained(this)));
+    ASSERT_TRUE(policy::DlpRulesManagerFactory::GetForPrimaryProfile());
+
+    ON_CALL(*rules_manager_, IsFilesPolicyEnabled)
+        .WillByDefault(testing::Return(true));
+    mock_files_controller_ =
+        std::make_unique<MockFilesController>(*rules_manager_);
+    ON_CALL(*rules_manager_, GetDlpFilesController)
+        .WillByDefault(testing::Return(mock_files_controller_.get()));
+
+    EXPECT_CALL(*mock_files_controller_.get(), IsLaunchBlocked)
+        .WillOnce(testing::Return(is_dlp_blocked));
+  }
+
+  void SetupAppService() {
+    app_service_test_.SetUp(browser()->profile());
+
+    AddAppServiceAppsForTesting("arcAppId", apps::AppType::kArc, "text/plain",
+                                "https://example.com");
+  }
+
+  void AddAppServiceAppsForTesting(std::string app_id,
+                                   apps::AppType app_type,
+                                   std::string mime_type,
+                                   absl::optional<std::string> publisher_id) {
+    apps::AppServiceProxy* app_service_proxy =
+        apps::AppServiceProxyFactory::GetForProfile(browser()->profile());
+
+    std::vector<apps::AppPtr> fake_apps;
+    apps::AppPtr fake_app =
+        std::make_unique<apps::App>(apps::AppType::kArc, app_id);
+    fake_app->name = "xyz";
+    fake_app->show_in_management = true;
+    fake_app->readiness = apps::Readiness::kReady;
+    if (publisher_id.has_value()) {
+      fake_app->publisher_id = publisher_id.value();
+    }
+    std::vector<apps::PermissionPtr> fake_permissions;
+    fake_app->permissions = std::move(fake_permissions);
+    fake_app->handles_intents = true;
+    apps::IntentFilterPtr filter =
+        apps_util::MakeIntentFilterForMimeType(mime_type);
+    fake_app->intent_filters.push_back(std::move(filter));
+
+    fake_apps.push_back(std::move(fake_app));
+
+    app_service_proxy->AppRegistryCache().OnApps(
+        std::move(fake_apps), app_type,
+        /*should_notify_initialized=*/false);
+  }
+
+  bool VerifyDlp(bool is_dlp_blocked) {
+    if (!sharesheet_widget_) {
+      return false;
+    }
+    SharesheetBubbleView* sharesheet_bubble_view_ =
+        static_cast<SharesheetBubbleView*>(
+            sharesheet_widget_->GetContentsView());
+    views::View* targets = sharesheet_bubble_view_->GetViewByID(
+        SharesheetViewID::TARGETS_DEFAULT_VIEW_ID);
+    SharesheetTargetButton* button = static_cast<SharesheetTargetButton*>(
+        targets->children()[targets->children().size() - 1]);
+    return is_dlp_blocked ==
+           (button->GetState() == SharesheetTargetButton::STATE_DISABLED);
+  }
+
+  MockFilesController* mock_files_controller() {
+    return mock_files_controller_.get();
+  }
+
+ private:
+  std::unique_ptr<KeyedService> SetDlpRulesManager(
+      content::BrowserContext* context) {
+    auto dlp_rules_manager =
+        std::make_unique<testing::NiceMock<policy::MockDlpRulesManager>>();
+    rules_manager_ = dlp_rules_manager.get();
+    return dlp_rules_manager;
+  }
+
+  apps::AppServiceTest app_service_test_;
+  policy::MockDlpRulesManager* rules_manager_ = nullptr;
+  std::unique_ptr<MockFilesController> mock_files_controller_ = nullptr;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         SharesheetBubbleViewPolicyBrowserTest,
+                         ::testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(SharesheetBubbleViewPolicyBrowserTest,
+                       InvokeUi_DlpAllowed) {
+  SetupRulesManager(/*is_dlp_blocked*/ false);
+  SetupAppService();
+  ShowUi();
+  ASSERT_TRUE(VerifyDlp(/*is_dlp_blocked*/ false));
+  DismissUi();
+}
+
+IN_PROC_BROWSER_TEST_P(SharesheetBubbleViewPolicyBrowserTest,
+                       InvokeUi_DlpBlocked) {
+  SetupRulesManager(/*is_dlp_blocked*/ true);
+  SetupAppService();
+  ShowUi();
+  ASSERT_TRUE(VerifyDlp(/*is_dlp_blocked*/ true));
+  DismissUi();
+}
+
 }  // namespace sharesheet
 }  // namespace ash
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.cc
index 016ca08..3b37abd 100644
--- a/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.cc
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.cc
@@ -50,7 +50,8 @@
     const std::u16string& display_name,
     const std::u16string& secondary_display_name,
     const absl::optional<gfx::ImageSkia> icon,
-    const gfx::VectorIcon* vector_icon)
+    const gfx::VectorIcon* vector_icon,
+    bool is_dlp_blocked)
     : Button(std::move(callback)), vector_icon_(vector_icon) {
   SetFocusBehavior(View::FocusBehavior::ALWAYS);
   // TODO(crbug.com/1097623) Margins shouldn't be within
@@ -102,6 +103,10 @@
 
   AddChildView(std::move(label_view));
   SetAccessibleName(accessible_name);
+
+  if (is_dlp_blocked) {
+    SetState(ButtonState::STATE_DISABLED);
+  }
 }
 
 void SharesheetTargetButton::OnThemeChanged() {
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h b/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h
index b87b1bb..b043c25 100644
--- a/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h
@@ -23,7 +23,8 @@
 // a single target (either app or action) in the |sharesheet_bubble_view|. The
 // target is comprised of an image (made from |icon| for apps or from
 // |vector_icon| for actions), a |display_name| and an optional
-// |secondary_display_name| below it. When pressed this button launches the
+// |secondary_display_name| below it. If |is_dlp_blocked| is set to true, the
+// button is disabled. Otherwise, when pressed this button launches the
 // associated target.
 class SharesheetTargetButton : public views::Button {
  public:
@@ -33,7 +34,8 @@
                          const std::u16string& display_name,
                          const std::u16string& secondary_display_name,
                          const absl::optional<gfx::ImageSkia> icon,
-                         const gfx::VectorIcon* vector_icon);
+                         const gfx::VectorIcon* vector_icon,
+                         bool is_dlp_blocked);
   SharesheetTargetButton(const SharesheetTargetButton&) = delete;
   SharesheetTargetButton& operator=(const SharesheetTargetButton&) = delete;
 
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 58cf6244..c7fcc6a 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -711,13 +711,14 @@
                                               std::move(callback));
 }
 
-bool ChromeAutofillClient::TryToShowFastCheckout(const FormData& form,
-                                                 const FormFieldData& field,
-                                                 AutofillDriver* driver) {
+bool ChromeAutofillClient::TryToShowFastCheckout(
+    const FormData& form,
+    const FormFieldData& field,
+    base::WeakPtr<AutofillManager> autofill_manager) {
 #if BUILDFLAG(IS_ANDROID)
   const GURL& url = web_contents()->GetLastCommittedURL();
   return FastCheckoutClient::GetOrCreateForWebContents(web_contents())
-      ->TryToStart(url, form, field, driver);
+      ->TryToStart(url, form, field, autofill_manager);
 #else
   return false;
 #endif
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index 4d274bf..1ff6f910 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -182,9 +182,10 @@
       AddressProfileSavePromptCallback callback) override;
   bool HasCreditCardScanFeature() override;
   void ScanCreditCard(CreditCardScanCallback callback) override;
-  bool TryToShowFastCheckout(const FormData& form,
-                             const FormFieldData& field,
-                             AutofillDriver* driver) override;
+  bool TryToShowFastCheckout(
+      const FormData& form,
+      const FormFieldData& field,
+      base::WeakPtr<AutofillManager> autofill_manager) override;
   void HideFastCheckout(bool allow_further_runs) override;
   bool IsFastCheckoutSupported() override;
   bool IsShowingFastCheckoutUI() override;
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc b/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc
index f2187e0..a1b942f 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/fast_checkout/fast_checkout_client_impl.h"
 #include "chrome/browser/fast_checkout/fast_checkout_features.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/autofill/content/browser/content_autofill_router.h"
 #include "components/autofill/core/browser/test_autofill_clock.h"
 #include "components/autofill/core/browser/test_autofill_driver.h"
 #include "components/autofill/core/browser/test_personal_data_manager.h"
@@ -31,11 +32,13 @@
       : FastCheckoutClientImpl(web_contents) {}
   ~MockFastCheckoutClient() override = default;
 
-  MOCK_METHOD(
-      bool,
-      TryToStart,
-      (const GURL&, const FormData&, const FormFieldData&, AutofillDriver*),
-      (override));
+  MOCK_METHOD(bool,
+              TryToStart,
+              (const GURL&,
+               const FormData&,
+               const FormFieldData&,
+               base::WeakPtr<AutofillManager>),
+              (override));
   MOCK_METHOD(void, Stop, (bool reset), (override));
   MOCK_METHOD(bool, IsRunning, (), (const override));
   MOCK_METHOD(bool, IsShowing, (), (const override));
@@ -87,6 +90,7 @@
 
     personal_data_manager_->SetAutofillProfileEnabled(true);
     personal_data_manager_->SetAutofillCreditCardEnabled(true);
+    personal_data_manager_->SetAutofillWalletImportEnabled(false);
 
     // Enable MSBB by default. If MSBB has been explicitly turned off, Fast
     // Checkout is not supported.
@@ -177,10 +181,19 @@
 }
 
 TEST_F(ChromeAutofillClientTest, TryToShowFastCheckout) {
+  auto router = std::make_unique<autofill::ContentAutofillRouter>();
+  auto driver = std::make_unique<autofill::ContentAutofillDriver>(
+      web_contents()->GetPrimaryMainFrame(), router.get());
+  auto manager = std::make_unique<BrowserAutofillManager>(
+      driver.get(), client(), "en-US",
+      AutofillManager::EnableDownloadManager(false));
+  BrowserAutofillManager* manager_ptr = manager.get();
+  driver->set_autofill_manager(std::move(manager));
+
   EXPECT_CALL(*fast_checkout_client(), TryToStart)
       .WillOnce(testing::Return(true));
   EXPECT_TRUE(client()->TryToShowFastCheckout(FormData(), FormFieldData(),
-                                              autofill_driver()));
+                                              manager_ptr->GetWeakPtr()));
 }
 #endif  // BUILDFLAG(IS_ANDROID)
 
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
index 0aa2370..790d91a2 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
@@ -109,10 +109,6 @@
   return GetHorizontalMargin();
 }
 
-SkColor AutofillPopupBaseView::GetBackgroundColor() const {
-  return GetColorProvider()->GetColor(ui::kColorDropdownBackground);
-}
-
 SkColor AutofillPopupBaseView::GetForegroundColor() const {
   return GetColorProvider()->GetColor(ui::kColorDropdownForeground);
 }
@@ -478,7 +474,6 @@
 }
 
 BEGIN_METADATA(AutofillPopupBaseView, views::WidgetDelegateView)
-ADD_READONLY_PROPERTY_METADATA(SkColor, BackgroundColor)
 ADD_READONLY_PROPERTY_METADATA(SkColor, ForegroundColor)
 ADD_READONLY_PROPERTY_METADATA(SkColor, SelectedBackgroundColor)
 ADD_READONLY_PROPERTY_METADATA(SkColor, SelectedForegroundColor)
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view.h b/chrome/browser/ui/views/autofill/autofill_popup_base_view.h
index 78b76cd..21a7620 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_base_view.h
+++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view.h
@@ -66,7 +66,6 @@
 
   // Get colors used throughout various popup UIs, based on the current native
   // theme.
-  SkColor GetBackgroundColor() const;
   SkColor GetForegroundColor() const;
   SkColor GetSelectedBackgroundColor() const;
   SkColor GetSelectedForegroundColor() const;
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
index 39bc82f..8bbd234 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
@@ -372,7 +372,6 @@
   // AutofillPopupRowView:
   void CreateContent() override;
   void RefreshStyle() override;
-  std::unique_ptr<views::Background> CreateBackground() final;
 
   int GetFrontendId() const;
 
@@ -554,8 +553,6 @@
  protected:
   // AutofillPopupRowView:
   void CreateContent() override;
-  void RefreshStyle() override;
-  std::unique_ptr<views::Background> CreateBackground() override;
 
  private:
   AutofillPopupSeparatorView(AutofillPopupViewNativeViews* popup_view,
@@ -586,7 +583,6 @@
   // AutofillPopupRowView:
   void CreateContent() override;
   void RefreshStyle() override {}
-  std::unique_ptr<views::Background> CreateBackground() override;
 
  private:
   AutofillPopupWarningView(AutofillPopupViewNativeViews* popup_view,
@@ -783,15 +779,13 @@
 }
 
 void AutofillPopupItemView::RefreshStyle() {
-  SetBackground(CreateBackground());
-  SkColor bk_color = GetSelected() ? popup_view()->GetSelectedBackgroundColor()
-                                   : popup_view()->GetBackgroundColor();
+  SetBackground(views::CreateThemedSolidBackground(GetBackgroundColorId()));
 
   // Set style for each label in this view depending on current state since the
   // style isn't automatically adjusted after creation of the label.
   for (views::Label* label : inner_labels_) {
     label->SetAutoColorReadabilityEnabled(false);
-    label->SetBackgroundColor(bk_color);
+    label->SetBackgroundColorId(GetBackgroundColorId());
 
     if (!label->GetEnabled()) {
       label->SetEnabledColor(views::style::GetColor(
@@ -809,12 +803,6 @@
   SchedulePaint();
 }
 
-std::unique_ptr<views::Background> AutofillPopupItemView::CreateBackground() {
-  return views::CreateSolidBackground(
-      GetSelected() ? popup_view()->GetSelectedBackgroundColor()
-                    : popup_view()->GetBackgroundColor());
-}
-
 std::unique_ptr<views::Label> AutofillPopupItemView::CreateMainTextView() {
   // TODO(crbug.com/831603): Remove elision responsibilities from controller.
   const Suggestion::Text& main_text =
@@ -1218,16 +1206,7 @@
 void AutofillPopupSeparatorView::CreateContent() {
   SetUseDefaultFillLayout(true);
   AddChildView(std::make_unique<PopupSeparator>(popup_view()));
-}
-
-void AutofillPopupSeparatorView::RefreshStyle() {
-  SetBackground(CreateBackground());
-  SchedulePaint();
-}
-
-std::unique_ptr<views::Background>
-AutofillPopupSeparatorView::CreateBackground() {
-  return views::CreateSolidBackground(popup_view()->GetBackgroundColor());
+  SetBackground(views::CreateThemedSolidBackground(GetBackgroundColorId()));
 }
 
 AutofillPopupSeparatorView::AutofillPopupSeparatorView(
@@ -1276,11 +1255,6 @@
       controller->GetSuggestionMainTextAt(GetLineNumber()), popup_view()));
 }
 
-std::unique_ptr<views::Background>
-AutofillPopupWarningView::CreateBackground() {
-  return nullptr;
-}
-
 }  // namespace
 
 /************** AutofillPopupRowView **************/
@@ -1345,6 +1319,11 @@
   return selected_;
 }
 
+ui::ColorId AutofillPopupRowView::GetBackgroundColorId() const {
+  return GetSelected() ? ui::kColorDropdownBackgroundSelected
+                       : ui::kColorDropdownBackground;
+}
+
 bool AutofillPopupRowView::HandleAccessibleAction(
     const ui::AXActionData& action_data) {
   base::WeakPtr<AutofillPopupController> controller =
@@ -1391,11 +1370,12 @@
 
 void AutofillPopupViewNativeViews::OnThemeChanged() {
   AutofillPopupBaseView::OnThemeChanged();
-  SetBackground(views::CreateSolidBackground(GetBackgroundColor()));
-  // |scroll_view_| and |footer_container_| will be null if there is no body
+  SetBackground(
+      views::CreateThemedSolidBackground(ui::kColorDropdownBackground));
+  // `scroll_view_` and `footer_container_` will be null if there is no body
   // or footer content, respectively.
   if (scroll_view_) {
-    scroll_view_->SetBackgroundColor(GetBackgroundColor());
+    scroll_view_->SetBackgroundThemeColorId(ui::kColorDropdownBackground);
   }
   if (footer_container_) {
     footer_container_->SetBackground(
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.h b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.h
index 931f6bf..308e86f 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.h
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.h
@@ -16,6 +16,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/accessibility/ax_action_data.h"
 #include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/color/color_id.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/font_list.h"
 #include "ui/views/bubble/bubble_border.h"
@@ -67,10 +68,10 @@
   AutofillPopupViewNativeViews* popup_view() { return popup_view_; }
   int GetLineNumber() const;
   bool GetSelected() const;
+  ui::ColorId GetBackgroundColorId() const;
 
   virtual void CreateContent() = 0;
-  virtual void RefreshStyle() = 0;
-  virtual std::unique_ptr<views::Background> CreateBackground() = 0;
+  virtual void RefreshStyle() {}
 
  private:
   raw_ptr<AutofillPopupViewNativeViews> popup_view_;
diff --git a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
index d68bcd1..054262e 100644
--- a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
+++ b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
@@ -25,6 +25,7 @@
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/color/color_id.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/geometry/insets.h"
@@ -135,12 +136,12 @@
     password_label_->SetText(password);
   }
 
-  void UpdateBackground(SkColor color) {
-    SetBackground(views::CreateSolidBackground(color));
+  void UpdateBackground(ui::ColorId color) {
+    SetBackground(views::CreateThemedSolidBackground(color));
     // Setting a background color on the labels may change the text color to
     // improve contrast.
-    password_label_->SetBackgroundColor(color);
-    suggestion_label_->SetBackgroundColor(color);
+    password_label_->SetBackgroundColorId(color);
+    suggestion_label_->SetBackgroundColorId(color);
   }
 
   void reset_controller() { controller_ = nullptr; }
@@ -296,12 +297,14 @@
     return;
 
   password_view_->UpdateBackground(controller_->password_selected()
-                                       ? GetSelectedBackgroundColor()
-                                       : GetBackgroundColor());
+                                       ? ui::kColorDropdownBackgroundSelected
+                                       : ui::kColorDropdownBackground);
   SchedulePaint();
 }
 
 void PasswordGenerationPopupViewViews::CreateLayoutAndChildren() {
+  SetBackground(
+      views::CreateThemedSolidBackground(ui::kColorDropdownBackground));
   if (controller_->IsStateMinimized()) {
     SetLayoutManager(std::make_unique<views::FillLayout>());
     auto warning_icon = std::make_unique<views::ImageView>();
@@ -367,12 +370,6 @@
 
 void PasswordGenerationPopupViewViews::OnThemeChanged() {
   autofill::AutofillPopupBaseView::OnThemeChanged();
-  SetBackground(views::CreateSolidBackground(GetBackgroundColor()));
-  if (FullPopupVisible()) {
-    password_view_->UpdateBackground(controller_->password_selected()
-                                         ? GetSelectedBackgroundColor()
-                                         : GetBackgroundColor());
-  }
   if (help_styled_label_) {
     help_styled_label_->SetDisplayedOnBackgroundColor(
         GetFooterBackgroundColor());
diff --git a/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.cc b/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.cc
index bdac7cc..2e794adb 100644
--- a/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.cc
+++ b/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.cc
@@ -183,6 +183,7 @@
 
 void GifTenorApiFetcher::TenorGifsApiResponseHandler(
     TenorGifsApiCallback callback,
+    std::unique_ptr<EndpointFetcher> endpoint_fetcher,
     std::unique_ptr<EndpointResponse> response) {
   data_decoder::DataDecoder::ParseJsonIsolated(
       response->response,
@@ -194,6 +195,11 @@
     TenorGifsApiCallback callback,
     data_decoder::DataDecoder::ValueOrError result) {
   const auto* gifs = FindList(result, "results");
+  if (!gifs) {
+    std::move(callback).Run(emoji_picker::mojom::TenorGifResponse::New(
+        "", std::vector<emoji_picker::mojom::GifResponsePtr>{}));
+    return;
+  }
   const auto* next = result->FindStringKey("next");
   std::move(callback).Run(emoji_picker::mojom::TenorGifResponse::New(
       next ? *next : "", ParseGifs(gifs)));
@@ -229,17 +235,20 @@
       }
   )");
 
-  endpoint_fetcher_ = endpoint_fetcher_creator_.Run(
+  auto endpoint_fetcher = endpoint_fetcher_creator_.Run(
       url_loader_factory, GURL(kTenorBaseUrl).Resolve(kCategoriesApi),
       kTrafficAnnotation);
-  endpoint_fetcher_->PerformRequest(
+  auto* const endpoint_fetcher_ptr = endpoint_fetcher.get();
+  endpoint_fetcher_ptr->PerformRequest(
       base::BindOnce(&GifTenorApiFetcher::FetchCategoriesResponseHandler,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                     std::move(endpoint_fetcher)),
       nullptr);
 }
 
 void GifTenorApiFetcher::FetchCategoriesResponseHandler(
     PageHandler::GetCategoriesCallback callback,
+    std::unique_ptr<EndpointFetcher> endpoint_fetcher,
     std::unique_ptr<EndpointResponse> response) {
   data_decoder::DataDecoder::ParseJsonIsolated(
       response->response,
@@ -252,6 +261,7 @@
     data_decoder::DataDecoder::ValueOrError result) {
   const auto* tags = FindList(result, "tags");
   if (!tags) {
+    std::move(callback).Run({});
     return;
   }
 
@@ -306,11 +316,13 @@
       }
   )");
 
-  endpoint_fetcher_ = endpoint_fetcher_creator_.Run(
+  auto endpoint_fetcher = endpoint_fetcher_creator_.Run(
       url_loader_factory, GetUrl(kFeaturedApi, pos), kTrafficAnnotation);
-  endpoint_fetcher_->PerformRequest(
+  auto* const endpoint_fetcher_ptr = endpoint_fetcher.get();
+  endpoint_fetcher_ptr->PerformRequest(
       base::BindOnce(&GifTenorApiFetcher::TenorGifsApiResponseHandler,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                     std::move(endpoint_fetcher)),
       nullptr);
 }
 
@@ -352,11 +364,13 @@
   GURL url = GetUrl(kSearchApi, pos);
   url = net::AppendQueryParameter(url, "q", query);
 
-  endpoint_fetcher_ = endpoint_fetcher_creator_.Run(url_loader_factory, url,
-                                                    kTrafficAnnotation);
-  endpoint_fetcher_->PerformRequest(
+  auto endpoint_fetcher = endpoint_fetcher_creator_.Run(url_loader_factory, url,
+                                                        kTrafficAnnotation);
+  auto* const endpoint_fetcher_ptr = endpoint_fetcher.get();
+  endpoint_fetcher_ptr->PerformRequest(
       base::BindOnce(&GifTenorApiFetcher::TenorGifsApiResponseHandler,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                     std::move(endpoint_fetcher)),
       nullptr);
 }
 
@@ -392,19 +406,22 @@
       }
   )");
 
-  endpoint_fetcher_ = endpoint_fetcher_creator_.Run(
+  auto endpoint_fetcher = endpoint_fetcher_creator_.Run(
       url_loader_factory,
       net::AppendQueryParameter(GURL(kTenorBaseUrl).Resolve(kPostsApi), "ids",
                                 base::JoinString(ids, ",")),
       kTrafficAnnotation);
-  endpoint_fetcher_->PerformRequest(
+  auto* const endpoint_fetcher_ptr = endpoint_fetcher.get();
+  endpoint_fetcher_ptr->PerformRequest(
       base::BindOnce(&GifTenorApiFetcher::FetchGifsByIdsResponseHandler,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                     std::move(endpoint_fetcher)),
       nullptr);
 }
 
 void GifTenorApiFetcher::FetchGifsByIdsResponseHandler(
     emoji_picker::mojom::PageHandler::GetGifsByIdsCallback callback,
+    std::unique_ptr<EndpointFetcher> endpoint_fetcher,
     std::unique_ptr<EndpointResponse> response) {
   data_decoder::DataDecoder::ParseJsonIsolated(
       response->response,
diff --git a/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.h b/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.h
index 49e64c9..3ec8f16 100644
--- a/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.h
+++ b/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.h
@@ -58,26 +58,29 @@
       const std::vector<std::string>& ids);
 
  private:
-  std::unique_ptr<EndpointFetcher> endpoint_fetcher_;
   const EndpointFetcherCreator endpoint_fetcher_creator_;
   base::WeakPtrFactory<GifTenorApiFetcher> weak_ptr_factory_{this};
 
   void FetchCategoriesResponseHandler(
       emoji_picker::mojom::PageHandler::GetCategoriesCallback callback,
+      std::unique_ptr<EndpointFetcher> endpoint_fetcher,
       std::unique_ptr<EndpointResponse> response);
 
   void OnCategoriesJsonParsed(
       emoji_picker::mojom::PageHandler::GetCategoriesCallback callback,
       data_decoder::DataDecoder::ValueOrError result);
 
-  void TenorGifsApiResponseHandler(TenorGifsApiCallback callback,
-                                   std::unique_ptr<EndpointResponse> response);
+  void TenorGifsApiResponseHandler(
+      TenorGifsApiCallback callback,
+      std::unique_ptr<EndpointFetcher> endpoint_fetcher,
+      std::unique_ptr<EndpointResponse> response);
 
   void OnGifsJsonParsed(TenorGifsApiCallback callback,
                         data_decoder::DataDecoder::ValueOrError result);
 
   void FetchGifsByIdsResponseHandler(
       emoji_picker::mojom::PageHandler::GetGifsByIdsCallback callback,
+      std::unique_ptr<EndpointFetcher> endpoint_fetcher,
       std::unique_ptr<EndpointResponse> response);
 
   void OnGifsByIdsJsonParsed(
diff --git a/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc
index 43245da..cac1a31 100644
--- a/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc
@@ -559,7 +559,7 @@
     }
   }
 
-  if (is_reauth && !gaia_reauth_request_token_.empty()) {
+  if (!gaia_reauth_request_token_.empty()) {
     params.Set("rart", gaia_reauth_request_token_);
   }
 
diff --git a/chrome/browser/ui/webui/password_manager/password_manager_ui.cc b/chrome/browser/ui/webui/password_manager/password_manager_ui.cc
index 96df881..b87b2b3 100644
--- a/chrome/browser/ui/webui/password_manager/password_manager_ui.cc
+++ b/chrome/browser/ui/webui/password_manager/password_manager_ui.cc
@@ -29,6 +29,12 @@
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/webui/web_ui_util.h"
 
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part.h"
+#include "components/password_manager/core/browser/password_manager_util.h"
+#endif
+
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #include "chrome/grit/chrome_unscaled_resources.h"
 #endif
@@ -47,110 +53,115 @@
       IDR_PASSWORD_MANAGER_PASSWORD_MANAGER_HTML);
 
   static constexpr webui::LocalizedString kStrings[] = {
-      {"addPassword", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD_BUTTON},
-      {"addPasswordFooter", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD_FOOTNOTE},
-      {"addPasswordTitle", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD},
-      {"addShortcut", IDS_PASSWORD_MANAGER_UI_ADD_SHORTCUT_TITLE},
-      {"addShortcutDescription",
-       IDS_PASSWORD_MANAGER_UI_ADD_SHORTCUT_DESCRIPTION},
-      {"autosigninDescription", IDS_PASSWORD_MANAGER_UI_AUTOSIGNIN_TOGGLE_DESC},
-      {"autosigninLabel", IDS_PASSWORD_MANAGER_UI_AUTOSIGNIN_TOGGLE_LABEL},
-      {"blockedSitesDescription",
-       IDS_PASSWORD_MANAGER_UI_BLOCKED_SITES_DESCRIPTION},
-      {"blockedSitesEmptyDescription",
-       IDS_PASSWORD_MANAGER_UI_NO_BLOCKED_SITES_DESCRIPTION},
-      {"blockedSitesTitle", IDS_PASSWORD_MANAGER_UI_BLOCKED_SITES_TITLE},
-      {"cancel", IDS_CANCEL},
-      {"changePassword", IDS_PASSWORD_MANAGER_UI_CHANGE_PASSWORD_BUTTON},
-      {"checkup", IDS_PASSWORD_MANAGER_UI_CHECKUP},
-      {"checkupCanceled", IDS_PASSWORD_MANAGER_UI_CHECKUP_CANCELED},
-      {"checkupErrorGeneric", IDS_PASSWORD_MANAGER_UI_CHECKUP_OTHER_ERROR},
-      {"checkupErrorNoPasswords", IDS_PASSWORD_MANAGER_UI_CHECKUP_NO_PASSWORDS},
-      {"checkupErrorOffline", IDS_PASSWORD_MANAGER_UI_CHECKUP_OFFLINE},
-      {"checkupErrorQuota", IDS_PASSWORD_MANAGER_UI_CHECKUP_QUOTA_LIMIT},
-      {"checkupErrorSignedOut", IDS_PASSWORD_MANAGER_UI_CHECKUP_SIGNED_OUT},
-      {"compromisedRowWithError",
-       IDS_PASSWORD_MANAGER_UI_CHECKUP_COMPROMISED_SECTION},
-      {"checkupProgress", IDS_PASSWORD_MANAGER_UI_CHECKUP_PROGRESS},
-      {"checkupTitle", IDS_PASSWORD_MANAGER_UI_CHECKUP_TITLE},
-      {"clearSearch", IDS_CLEAR_SEARCH},
-      {"close", IDS_CLOSE},
-      {"compromisedPasswordsDescription",
-       IDS_PASSWORD_MANAGER_UI_COMPROMISED_PASSWORDS_DESCRIPTION},
-      {"compromisedPasswordsEmpty",
-       IDS_PASSWORD_MANAGER_UI_NO_COMPROMISED_PASSWORDS},
-      {"compromisedPasswordsTitle",
-       IDS_PASSWORD_MANAGER_UI_HAS_COMPROMISED_PASSWORDS},
-      {"copyPassword", IDS_PASSWORD_MANAGER_UI_COPY_PASSWORD},
-      {"copyUsername", IDS_PASSWORD_MANAGER_UI_COPY_USERNAME},
-      {"deletePassword", IDS_DELETE},
-      {"downloadFile", IDS_PASSWORD_MANAGER_UI_DOWNLOAD_FILE},
-      {"editPassword", IDS_EDIT},
-      {"emptyNote", IDS_PASSWORD_MANAGER_UI_NO_NOTE_SAVED},
-      {"exportPasswords", IDS_PASSWORD_MANAGER_UI_EXPORT_TITLE},
-      {"exportPasswordsDescription",
-       IDS_PASSWORD_MANAGER_UI_EXPORT_BANNER_DESCRIPTION},
-      {"exportPasswordsFailTips",
-       IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIPS},
-      {"exportPasswordsFailTipsAnotherFolder",
-       IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIP_ANOTHER_FOLDER},
-      {"exportPasswordsFailTipsEnoughSpace",
-       IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIP_ENOUGH_SPACE},
-      {"exportPasswordsFailTitle",
-       IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TITLE},
-      {"exportPasswordsTryAgain", IDS_PASSWORD_MANAGER_UI_EXPORT_TRY_AGAIN},
-      {"exportingPasswordsTitle", IDS_PASSWORD_MANAGER_UI_EXPORTING_TITLE},
-      {"federationLabel", IDS_PASSWORD_MANAGER_UI_FEDERATION_LABEL},
-      {"help", IDS_PASSWORD_MANAGER_UI_HELP},
-      {"hidePassword", IDS_PASSWORD_MANAGER_UI_HIDE_PASSWORD},
-      {"importPasswords", IDS_PASSWORD_MANAGER_UI_IMPORT_BANNER_TITLE},
-      {"importPasswordsDescription",
-       IDS_PASSWORD_MANAGER_UI_IMPORT_BANNER_DESCRIPTION},
-      {"justNow", IDS_PASSWORD_MANAGER_UI_JUST_NOW},
-      {"leakedPassword", IDS_PASSWORD_MANAGER_UI_PASSWORD_LEAKED},
-      {"localPasswordManager",
-       IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SAVING_ON_DEVICE},
-      {"menu", IDS_MENU},
-      {"moreActions", IDS_PASSWORD_MANAGER_UI_MORE_ACTIONS},
-      {"muteCompromisedPassword", IDS_PASSWORD_MANAGER_UI_MUTE_ISSUE},
-      {"mutedCompromisedCredentials",
-       IDS_PASSWORD_MANAGER_UI_MUTED_COMPROMISED_PASSWORDS},
-      {"notesLabel", IDS_PASSWORD_MANAGER_UI_NOTES_LABEL},
-      {"passwordCopiedToClipboard",
-       IDS_PASSWORD_MANAGER_UI_PASSWORD_COPIED_TO_CLIPBOARD},
-      {"passwordLabel", IDS_PASSWORD_MANAGER_UI_PASSWORD_LABEL},
-      {"passwordManager",
-       IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SYNCED_TO_ACCOUNT},
-      {"passwords", IDS_PASSWORD_MANAGER_UI_PASSWORDS},
-      {"phishedAndLeakedPassword",
-       IDS_PASSWORD_MANAGER_UI_PASSWORD_PHISHED_AND_LEAKED},
-      {"phishedPassword", IDS_PASSWORD_MANAGER_UI_PASSWORD_PHISHED},
-      {"reusedPasswordsDescription",
-       IDS_PASSWORD_MANAGER_UI_REUSED_PASSWORDS_DESCRIPTION},
-      {"reusedPasswordsEmpty", IDS_PASSWORD_MANAGER_UI_NO_REUSED_PASSWORDS},
-      {"reusedPasswordsTitle", IDS_PASSWORD_MANAGER_UI_HAS_REUSED_PASSWORDS},
-      {"save", IDS_SAVE},
-      {"savePasswordsLabel",
-       IDS_PASSWORD_MANAGER_UI_SAVE_PASSWORDS_TOGGLE_LABEL},
-      {"searchPrompt", IDS_PASSWORD_MANAGER_UI_SEARCH_PROMPT},
-      {"settings", IDS_PASSWORD_MANAGER_UI_SETTINGS},
-      {"showPassword", IDS_PASSWORD_MANAGER_UI_SHOW_PASSWORD},
-      {"sitesLabel", IDS_PASSWORD_MANAGER_UI_SITES_LABEL},
-      {"title", IDS_PASSWORD_MANAGER_UI_TITLE},
-      {"trustedVaultBannerLabelOfferOptIn",
-       IDS_PASSWORD_MANAGER_UI_TRUSTED_VAULT_OPT_IN_TITLE},
-      {"trustedVaultBannerSubLabelOfferOptIn",
-       IDS_PASSWORD_MANAGER_UI_RUSTED_VAULT_OPT_IN_DESCRIPTION},
-      {"tryAgain", IDS_PASSWORD_MANAGER_UI_CHECK_PASSWORDS_AFTER_ERROR},
-      {"unmuteCompromisedPassword", IDS_PASSWORD_MANAGER_UI_UNMUTE_ISSUE},
-      {"usernameCopiedToClipboard",
-       IDS_PASSWORD_MANAGER_UI_USERNAME_COPIED_TO_CLIPBOARD},
-      {"usernameLabel", IDS_PASSWORD_MANAGER_UI_USERNAME_LABEL},
-      {"weakPasswordsDescription",
-       IDS_PASSWORD_MANAGER_UI_WEAK_PASSWORDS_DESCRIPTION},
-      {"weakPasswordsEmpty", IDS_PASSWORD_MANAGER_UI_NO_WEAK_PASSWORDS},
-      {"weakPasswordsTitle", IDS_PASSWORD_MANAGER_UI_HAS_WEAK_PASSWORDS},
-      {"websiteLabel", IDS_PASSWORD_MANAGER_UI_WEBSITE_LABEL},
+    {"addPassword", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD_BUTTON},
+    {"addPasswordFooter", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD_FOOTNOTE},
+    {"addPasswordTitle", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD},
+    {"addShortcut", IDS_PASSWORD_MANAGER_UI_ADD_SHORTCUT_TITLE},
+    {"addShortcutDescription",
+     IDS_PASSWORD_MANAGER_UI_ADD_SHORTCUT_DESCRIPTION},
+    {"autosigninDescription", IDS_PASSWORD_MANAGER_UI_AUTOSIGNIN_TOGGLE_DESC},
+    {"autosigninLabel", IDS_PASSWORD_MANAGER_UI_AUTOSIGNIN_TOGGLE_LABEL},
+    {"blockedSitesDescription",
+     IDS_PASSWORD_MANAGER_UI_BLOCKED_SITES_DESCRIPTION},
+    {"blockedSitesEmptyDescription",
+     IDS_PASSWORD_MANAGER_UI_NO_BLOCKED_SITES_DESCRIPTION},
+    {"blockedSitesTitle", IDS_PASSWORD_MANAGER_UI_BLOCKED_SITES_TITLE},
+    {"cancel", IDS_CANCEL},
+    {"changePassword", IDS_PASSWORD_MANAGER_UI_CHANGE_PASSWORD_BUTTON},
+    {"checkup", IDS_PASSWORD_MANAGER_UI_CHECKUP},
+    {"checkupCanceled", IDS_PASSWORD_MANAGER_UI_CHECKUP_CANCELED},
+    {"checkupErrorGeneric", IDS_PASSWORD_MANAGER_UI_CHECKUP_OTHER_ERROR},
+    {"checkupErrorNoPasswords", IDS_PASSWORD_MANAGER_UI_CHECKUP_NO_PASSWORDS},
+    {"checkupErrorOffline", IDS_PASSWORD_MANAGER_UI_CHECKUP_OFFLINE},
+    {"checkupErrorQuota", IDS_PASSWORD_MANAGER_UI_CHECKUP_QUOTA_LIMIT},
+    {"checkupErrorSignedOut", IDS_PASSWORD_MANAGER_UI_CHECKUP_SIGNED_OUT},
+    {"compromisedRowWithError",
+     IDS_PASSWORD_MANAGER_UI_CHECKUP_COMPROMISED_SECTION},
+    {"checkupProgress", IDS_PASSWORD_MANAGER_UI_CHECKUP_PROGRESS},
+    {"checkupTitle", IDS_PASSWORD_MANAGER_UI_CHECKUP_TITLE},
+    {"clearSearch", IDS_CLEAR_SEARCH},
+    {"close", IDS_CLOSE},
+    {"compromisedPasswordsDescription",
+     IDS_PASSWORD_MANAGER_UI_COMPROMISED_PASSWORDS_DESCRIPTION},
+    {"compromisedPasswordsEmpty",
+     IDS_PASSWORD_MANAGER_UI_NO_COMPROMISED_PASSWORDS},
+    {"compromisedPasswordsTitle",
+     IDS_PASSWORD_MANAGER_UI_HAS_COMPROMISED_PASSWORDS},
+    {"copyPassword", IDS_PASSWORD_MANAGER_UI_COPY_PASSWORD},
+    {"copyUsername", IDS_PASSWORD_MANAGER_UI_COPY_USERNAME},
+    {"deletePassword", IDS_DELETE},
+    {"downloadFile", IDS_PASSWORD_MANAGER_UI_DOWNLOAD_FILE},
+    {"editPassword", IDS_EDIT},
+    {"emptyNote", IDS_PASSWORD_MANAGER_UI_NO_NOTE_SAVED},
+    {"exportPasswords", IDS_PASSWORD_MANAGER_UI_EXPORT_TITLE},
+    {"exportPasswordsDescription",
+     IDS_PASSWORD_MANAGER_UI_EXPORT_BANNER_DESCRIPTION},
+    {"exportPasswordsFailTips", IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIPS},
+    {"exportPasswordsFailTipsAnotherFolder",
+     IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIP_ANOTHER_FOLDER},
+    {"exportPasswordsFailTipsEnoughSpace",
+     IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIP_ENOUGH_SPACE},
+    {"exportPasswordsFailTitle",
+     IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TITLE},
+    {"exportPasswordsTryAgain", IDS_PASSWORD_MANAGER_UI_EXPORT_TRY_AGAIN},
+    {"exportingPasswordsTitle", IDS_PASSWORD_MANAGER_UI_EXPORTING_TITLE},
+    {"federationLabel", IDS_PASSWORD_MANAGER_UI_FEDERATION_LABEL},
+    {"help", IDS_PASSWORD_MANAGER_UI_HELP},
+    {"hidePassword", IDS_PASSWORD_MANAGER_UI_HIDE_PASSWORD},
+    {"importPasswords", IDS_PASSWORD_MANAGER_UI_IMPORT_BANNER_TITLE},
+    {"importPasswordsDescription",
+     IDS_PASSWORD_MANAGER_UI_IMPORT_BANNER_DESCRIPTION},
+    {"justNow", IDS_PASSWORD_MANAGER_UI_JUST_NOW},
+    {"leakedPassword", IDS_PASSWORD_MANAGER_UI_PASSWORD_LEAKED},
+    {"localPasswordManager",
+     IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SAVING_ON_DEVICE},
+    {"menu", IDS_MENU},
+    {"moreActions", IDS_PASSWORD_MANAGER_UI_MORE_ACTIONS},
+    {"muteCompromisedPassword", IDS_PASSWORD_MANAGER_UI_MUTE_ISSUE},
+    {"mutedCompromisedCredentials",
+     IDS_PASSWORD_MANAGER_UI_MUTED_COMPROMISED_PASSWORDS},
+    {"notesLabel", IDS_PASSWORD_MANAGER_UI_NOTES_LABEL},
+    {"passwordCopiedToClipboard",
+     IDS_PASSWORD_MANAGER_UI_PASSWORD_COPIED_TO_CLIPBOARD},
+    {"passwordLabel", IDS_PASSWORD_MANAGER_UI_PASSWORD_LABEL},
+    {"passwordManager",
+     IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SYNCED_TO_ACCOUNT},
+    {"passwords", IDS_PASSWORD_MANAGER_UI_PASSWORDS},
+    {"phishedAndLeakedPassword",
+     IDS_PASSWORD_MANAGER_UI_PASSWORD_PHISHED_AND_LEAKED},
+    {"phishedPassword", IDS_PASSWORD_MANAGER_UI_PASSWORD_PHISHED},
+    {"reusedPasswordsDescription",
+     IDS_PASSWORD_MANAGER_UI_REUSED_PASSWORDS_DESCRIPTION},
+    {"reusedPasswordsEmpty", IDS_PASSWORD_MANAGER_UI_NO_REUSED_PASSWORDS},
+    {"reusedPasswordsTitle", IDS_PASSWORD_MANAGER_UI_HAS_REUSED_PASSWORDS},
+    {"save", IDS_SAVE},
+    {"savePasswordsLabel", IDS_PASSWORD_MANAGER_UI_SAVE_PASSWORDS_TOGGLE_LABEL},
+    {"searchPrompt", IDS_PASSWORD_MANAGER_UI_SEARCH_PROMPT},
+    {"settings", IDS_PASSWORD_MANAGER_UI_SETTINGS},
+    {"showPassword", IDS_PASSWORD_MANAGER_UI_SHOW_PASSWORD},
+    {"sitesLabel", IDS_PASSWORD_MANAGER_UI_SITES_LABEL},
+    {"title", IDS_PASSWORD_MANAGER_UI_TITLE},
+    {"trustedVaultBannerLabelOfferOptIn",
+     IDS_PASSWORD_MANAGER_UI_TRUSTED_VAULT_OPT_IN_TITLE},
+    {"trustedVaultBannerSubLabelOfferOptIn",
+     IDS_PASSWORD_MANAGER_UI_RUSTED_VAULT_OPT_IN_DESCRIPTION},
+    {"tryAgain", IDS_PASSWORD_MANAGER_UI_CHECK_PASSWORDS_AFTER_ERROR},
+    {"unmuteCompromisedPassword", IDS_PASSWORD_MANAGER_UI_UNMUTE_ISSUE},
+    {"usernameCopiedToClipboard",
+     IDS_PASSWORD_MANAGER_UI_USERNAME_COPIED_TO_CLIPBOARD},
+    {"usernameLabel", IDS_PASSWORD_MANAGER_UI_USERNAME_LABEL},
+    {"weakPasswordsDescription",
+     IDS_PASSWORD_MANAGER_UI_WEAK_PASSWORDS_DESCRIPTION},
+    {"weakPasswordsEmpty", IDS_PASSWORD_MANAGER_UI_NO_WEAK_PASSWORDS},
+    {"weakPasswordsTitle", IDS_PASSWORD_MANAGER_UI_HAS_WEAK_PASSWORDS},
+    {"websiteLabel", IDS_PASSWORD_MANAGER_UI_WEBSITE_LABEL},
+#if BUILDFLAG(IS_MAC)
+    {"biometricAuthenticaionForFillingLabel",
+     IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC},
+#elif BUILDFLAG(IS_WIN)
+    {"biometricAuthenticaionForFillingLabel",
+     IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_WIN},
+#endif
   };
   for (const auto& str : kStrings)
     webui::AddLocalizedString(source, str.name, str.id);
@@ -173,6 +184,13 @@
               password_manager::PasswordCheckupReferrer::kPasswordCheck)
               .spec()));
 
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+  source->AddBoolean("biometricAuthenticationForFillingToggleVisible",
+                     password_manager_util::
+                         ShouldBiometricAuthenticationForFillingToggleBeVisible(
+                             g_browser_process->local_state()));
+#endif
+
   source->AddString("passwordManagerLearnMoreURL",
                     chrome::kPasswordManagerLearnMoreURL);
 
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc
index 97d7640..aa31ce5d 100644
--- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc
@@ -18,12 +18,10 @@
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
-#include "components/content_settings/core/common/content_settings_types.h"
 #include "components/content_settings/core/common/features.h"
 #include "components/permissions/constants.h"
 #include "components/permissions/unused_site_permissions_service.h"
 #include "url/gurl.h"
-#include "url/origin.h"
 
 namespace {
 // Reflects the maximum number of days between a permissions being revoked and
@@ -59,6 +57,7 @@
 void SiteSettingsPermissionsHandler::HandleAllowPermissionsAgainForUnusedSite(
     const base::Value::List& args) {
   CHECK_EQ(1U, args.size());
+  CHECK(args[0].is_string());
   const std::string& origin_str = args[0].GetString();
 
   permissions::UnusedSitePermissionsService* service =
@@ -84,6 +83,22 @@
 }
 
 void SiteSettingsPermissionsHandler::
+    HandleUndoAllowPermissionsAgainForUnusedSite(
+        const base::Value::List& args) {
+  CHECK_EQ(1U, args.size());
+  CHECK(args[0].is_dict());
+
+  auto [origin, permissions, constraints] =
+      GetUnusedSitePermissionsFromDict(args[0].GetDict());
+  permissions::UnusedSitePermissionsService* service =
+      UnusedSitePermissionsServiceFactory::GetForProfile(profile_);
+
+  service->UndoRegrantPermissionsForOrigin(permissions, constraints, origin);
+
+  SendUnusedSitePermissionsReviewList();
+}
+
+void SiteSettingsPermissionsHandler::
     HandleAcknowledgeRevokedUnusedSitePermissionsList(
         const base::Value::List& args) {
   permissions::UnusedSitePermissionsService* service =
@@ -103,35 +118,13 @@
   permissions::UnusedSitePermissionsService* service =
       UnusedSitePermissionsServiceFactory::GetForProfile(profile_);
 
-  for (const auto& unused_site_permissions : unused_site_permissions_list) {
-    CHECK(unused_site_permissions.is_dict());
-    const std::string* origin_str =
-        unused_site_permissions.GetDict().FindString(site_settings::kOrigin);
-    CHECK(origin_str);
-    const auto url = GURL(*origin_str);
-    CHECK(url.is_valid());
+  for (const auto& unused_site_permissions_js : unused_site_permissions_list) {
+    CHECK(unused_site_permissions_js.is_dict());
+    auto [origin, permissions, constraints] =
+        GetUnusedSitePermissionsFromDict(unused_site_permissions_js.GetDict());
 
-    const base::Value::List* permissions =
-        unused_site_permissions.GetDict().FindList(site_settings::kPermissions);
-    CHECK(permissions);
-    std::list<ContentSettingsType> permission_types;
-    for (const auto& permission : *permissions) {
-      CHECK(permission.is_string());
-      const std::string& type = permission.GetString();
-      permission_types.push_back(
-          site_settings::ContentSettingsTypeFromGroupName(type));
-    }
-
-    const base::Value* js_expiration =
-        unused_site_permissions.GetDict().Find(kExpirationKey);
-    CHECK(js_expiration);
-    auto expiration = base::ValueToTime(js_expiration);
-
-    const content_settings::ContentSettingConstraints constraint{
-        .expiration = *expiration};
-
-    service->StorePermissionInRevokedPermissionSetting(
-        permission_types, constraint, url::Origin::Create(url));
+    service->StorePermissionInRevokedPermissionSetting(permissions, constraints,
+                                                       origin);
   }
 
   SendUnusedSitePermissionsReviewList();
@@ -184,6 +177,40 @@
   return result;
 }
 
+std::tuple<url::Origin,
+           std::set<ContentSettingsType>,
+           content_settings::ContentSettingConstraints>
+SiteSettingsPermissionsHandler::GetUnusedSitePermissionsFromDict(
+    const base::Value::Dict& unused_site_permissions) {
+  const std::string* origin_str =
+      unused_site_permissions.FindString(site_settings::kOrigin);
+  CHECK(origin_str);
+  const auto url = GURL(*origin_str);
+  CHECK(url.is_valid());
+  const url::Origin origin = url::Origin::Create(url);
+
+  const base::Value::List* permissions =
+      unused_site_permissions.FindList(site_settings::kPermissions);
+  CHECK(permissions);
+  std::set<ContentSettingsType> permission_types;
+  for (const auto& permission : *permissions) {
+    CHECK(permission.is_string());
+    const std::string& type = permission.GetString();
+    permission_types.insert(
+        site_settings::ContentSettingsTypeFromGroupName(type));
+  }
+
+  const base::Value* js_expiration =
+      unused_site_permissions.Find(kExpirationKey);
+  CHECK(js_expiration);
+  auto expiration = base::ValueToTime(js_expiration);
+
+  const content_settings::ContentSettingConstraints constraints{
+      .expiration = *expiration};
+
+  return std::make_tuple(origin, permission_types, constraints);
+}
+
 void SiteSettingsPermissionsHandler::RegisterMessages() {
   // Usage of base::Unretained(this) is safe, because web_ui() owns `this` and
   // won't release ownership until destruction.
@@ -198,6 +225,11 @@
                               HandleAllowPermissionsAgainForUnusedSite,
                           base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
+      "undoAllowPermissionsAgainForUnusedSite",
+      base::BindRepeating(&SiteSettingsPermissionsHandler::
+                              HandleUndoAllowPermissionsAgainForUnusedSite,
+                          base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
       "acknowledgeRevokedUnusedSitePermissionsList",
       base::BindRepeating(&SiteSettingsPermissionsHandler::
                               HandleAcknowledgeRevokedUnusedSitePermissionsList,
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
index 6f122dcc..d9b5711 100644
--- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
+++ b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
@@ -9,6 +9,9 @@
 #include "base/time/clock.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
+#include "components/content_settings/core/common/content_settings_constraints.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "url/origin.h"
 
 /**
  * This handler deals with the permission-related operations on the site
@@ -41,17 +44,23 @@
   // "Unused site permissions" module.
   void HandleGetRevokedUnusedSitePermissionsList(const base::Value::List& args);
 
-  // Re-grant the revoked permissions and remove the origin from the revoked
-  // permissions list.
+  // Re-grant the revoked permissions and remove the given origin from the
+  // revoked permissions list.
   void HandleAllowPermissionsAgainForUnusedSite(const base::Value::List& args);
 
+  // Reverse the changes made by |HandleAllowPermissionsAgainForUnusedSite| for
+  // the given |UnusedSitePermission| object.
+  void HandleUndoAllowPermissionsAgainForUnusedSite(
+      const base::Value::List& args);
+
   // Clear the list of revoked permissions so they are not shown again.
   // Permission settings themselves are not affected by this.
   void HandleAcknowledgeRevokedUnusedSitePermissionsList(
       const base::Value::List& args);
 
   // Reverse the changes made by
-  // |HandleAcknowledgeRevokedUnusedSitePermissionsList|. List of revoked
+  // |HandleAcknowledgeRevokedUnusedSitePermissionsList| for the given list of
+  // |UnusedSitePermission| objects. List of revoked
   // permissions is repopulated. Permission settings are not changed.
   void HandleUndoAcknowledgeRevokedUnusedSitePermissionsList(
       const base::Value::List& args);
@@ -63,6 +72,14 @@
   // Sends the list of unused site permissions to review to the WebUI.
   void SendUnusedSitePermissionsReviewList();
 
+  // Get values from |UnusedSitePermission| object in
+  // site_settings_permissions_browser_proxy.ts.
+  std::tuple<url::Origin,
+             std::set<ContentSettingsType>,
+             content_settings::ContentSettingConstraints>
+  GetUnusedSitePermissionsFromDict(
+      const base::Value::Dict& unused_site_permissions);
+
   const raw_ptr<Profile> profile_;
 
   base::Clock* clock_;
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc
index f0a1ebff..3fbd56e 100644
--- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc
@@ -30,6 +30,8 @@
 
 constexpr char kUnusedTestSite[] = "https://example1.com";
 constexpr char kUsedTestSite[] = "https://example2.com";
+constexpr ContentSettingsType kUnusedPermission =
+    ContentSettingsType::GEOLOCATION;
 
 class SiteSettingsPermissionsHandlerTest : public testing::Test {
  public:
@@ -93,6 +95,18 @@
     }
   }
 
+  void ExpectRevokedPermission() {
+    ContentSettingsForOneType revoked_permissions_list;
+    hcsm()->GetSettingsForOneType(
+        ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
+        &revoked_permissions_list);
+    EXPECT_EQ(1U, revoked_permissions_list.size());
+    EXPECT_EQ(
+        ContentSetting::CONTENT_SETTING_ASK,
+        hcsm()->GetContentSetting(GURL(kUnusedTestSite), GURL(kUnusedTestSite),
+                                  kUnusedPermission));
+  }
+
   TestingProfile* profile() { return profile_.get(); }
   content::TestWebUI* web_ui() { return &web_ui_; }
   SiteSettingsPermissionsHandler* handler() { return handler_.get(); }
@@ -133,6 +147,9 @@
   // Advance 14 days; this will be the expected histogram sample.
   clock()->Advance(base::Days(14));
   base::HistogramTester histogram_tester;
+  base::Value::List initial_unused_site_permissions =
+      handler()->PopulateUnusedSitePermissionsData();
+  ExpectRevokedPermission();
 
   // Allow the revoked permission for the unused site again.
   base::Value::List args;
@@ -157,7 +174,12 @@
   EXPECT_EQ(
       ContentSetting::CONTENT_SETTING_ALLOW,
       hcsm()->GetContentSetting(GURL(kUnusedTestSite), GURL(kUnusedTestSite),
-                                ContentSettingsType::GEOLOCATION));
+                                kUnusedPermission));
+
+  // Undoing restores the initial state.
+  handler()->HandleUndoAllowPermissionsAgainForUnusedSite(
+      std::move(initial_unused_site_permissions));
+  ExpectRevokedPermission();
 }
 
 TEST_F(SiteSettingsPermissionsHandlerTest,
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
index b382bd43..c3b1e723 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
@@ -48,32 +48,31 @@
 constexpr char kStartUrl[] = "https://example.com/start/?u=1";
 constexpr char kManifestUrl[] = "https://example.com/install/manifest.json";
 
-base::Value GetForceInstalledAppItem() {
-  base::Value item(base::Value::Type::DICT);
-  item.SetKey(kUrlKey, base::Value(kInstallUrl));
-  item.SetKey(kDefaultLaunchContainerKey,
-              base::Value(kDefaultLaunchContainerWindowValue));
+base::Value::Dict GetForceInstalledAppItem() {
+  base::Value::Dict item;
+  item.Set(kUrlKey, kInstallUrl);
+  item.Set(kDefaultLaunchContainerKey, kDefaultLaunchContainerWindowValue);
   return item;
 }
 
-base::Value GetCustomAppNameItem() {
-  base::Value item = GetForceInstalledAppItem();
-  item.SetKey(kCustomNameKey, base::Value(kDefaultCustomName));
+base::Value::Dict GetCustomAppNameItem() {
+  base::Value::Dict item = GetForceInstalledAppItem();
+  item.Set(kCustomNameKey, kDefaultCustomName);
   return item;
 }
 
-base::Value GetCustomAppIconItem() {
-  base::Value item = GetForceInstalledAppItem();
-  base::Value sub_item(base::Value::Type::DICT);
-  sub_item.SetKey(kCustomIconURLKey, base::Value(kDefaultCustomIconUrl));
-  sub_item.SetKey(kCustomIconHashKey, base::Value(kDefaultCustomIconHash));
-  item.SetKey(kCustomIconKey, std::move(sub_item));
+base::Value::Dict GetCustomAppIconItem() {
+  base::Value::Dict item = GetForceInstalledAppItem();
+  base::Value::Dict sub_item;
+  sub_item.Set(kCustomIconURLKey, kDefaultCustomIconUrl);
+  sub_item.Set(kCustomIconHashKey, kDefaultCustomIconHash);
+  item.Set(kCustomIconKey, std::move(sub_item));
   return item;
 }
 
-base::Value GetCustomAppIconAndNameItem() {
-  base::Value item = GetCustomAppIconItem();
-  item.SetKey(kCustomNameKey, base::Value(kDefaultCustomName));
+base::Value::Dict GetCustomAppIconAndNameItem() {
+  base::Value::Dict item = GetCustomAppIconItem();
+  item.Set(kCustomNameKey, kDefaultCustomName);
   return item;
 }
 
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 3edbdb2..a6ff40a 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1675036157-2a352d548f125d4ddfa254d9ff226e80d9f9799f.profdata
+chrome-linux-main-1675057898-7fe3eba43fcaaecd815af7fc771049ceac93c406.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index af91563..b68117d 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1675036157-de5577c6796a1bae18ce319db757618a629d2e71.profdata
+chrome-mac-arm-main-1675079896-7024c936ef87f65ff9d525b0e7866c3d936a0791.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 1584026..c3af4ab 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1675036157-e555aeb936bea0d1b9f27d276ecf828dcb9f34b8.profdata
+chrome-mac-main-1675057898-0ce58d35aa925a78369db708279eb3b0b31be706.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 2c68003..0f62bb0 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1675036157-9739a839008de7437c454eb8e749518c73a3ba0f.profdata
+chrome-win32-main-1675069017-7ea10c02f2205e73a5db0d2ea573fb614129421e.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index d52e1a6..f580431 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1675036157-176866fa78a28cac409a46b4c32fbd9099017b45.profdata
+chrome-win64-main-1675069017-00a4a655ec9ddeddfdde1dbc7a93aa14f660cb02.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 22653f14..8f15ebe 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -650,7 +650,7 @@
 // When enabled, users will see a warning when downloading from Incognito.
 BASE_FEATURE(kIncognitoDownloadsWarning,
              "IncognitoDownloadsWarning",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 #endif
 
 // When enabled, users will see updated UI in Incognito NTP
diff --git a/chrome/common/chromeos/extensions/api/_api_features.json b/chrome/common/chromeos/extensions/api/_api_features.json
index b0bebb5..d48745c 100644
--- a/chrome/common/chromeos/extensions/api/_api_features.json
+++ b/chrome/common/chromeos/extensions/api/_api_features.json
@@ -29,5 +29,17 @@
       "lacros"
     ],
     "channel": "stable"
+  },
+  "os.telemetry.getUsbBusInfo": {
+    "dependencies": [ "permission:os.telemetry" ],
+    "contexts": [
+      "blessed_extension"
+    ],
+    "platforms": [
+      "chromeos",
+      "lacros"
+    ],
+    "channel": "stable",
+    "feature_flag": "TelemetryExtensionPendingApprovalApi"
   }
 }
diff --git a/chrome/common/chromeos/extensions/api/telemetry.idl b/chrome/common/chromeos/extensions/api/telemetry.idl
index 4823150f..e91c7b1 100644
--- a/chrome/common/chromeos/extensions/api/telemetry.idl
+++ b/chrome/common/chromeos/extensions/api/telemetry.idl
@@ -178,6 +178,121 @@
 
   callback OsVersionInfoCallback = void (OsVersionInfo osVersionInfo);
 
+  // The info related to usb interfaces.
+  dictionary UsbBusInterfaceInfo {
+    // The zero-based number (index) of the interface.
+    double? interfaceNumber;
+    // These fields can be used to classify / identify the usb interfaces. See the
+    // usb.ids database for the values. (https://github.com/gentoo/hwids)
+    double? classId;
+    double? subclassId;
+    double? protocolId;
+    // The driver used by the device. This is the name of the matched driver which
+    // is registered in the kernel. See "{kernel root}/drivers/" for the list of
+    // the built in drivers.
+    DOMString? driver;
+  };
+
+  // An enumeration of the formats of firmware version in fwpud. See the fwupd
+  // repo for the values. (https://github.com/fwupd/fwupd)
+  enum FwupdVersionFormat {
+    // An unidentified format text string.
+    plain,
+    // A single integer version number.
+    number,
+    // Two AABB.CCDD version numbers.
+    pair,
+    // Microsoft-style AA.BB.CCDD version numbers.
+    triplet,
+    // UEFI-style AA.BB.CC.DD version numbers.
+    quad,
+    // Binary coded decimal notation.
+    bcd,
+    // Intel ME-style bitshifted notation.
+    intelMe,
+    // Intel ME-style A.B.CC.DDDD notation.
+    intelMe2,
+    // Legacy Microsoft Surface 10b.12b.10b.
+    surfaceLegacy,
+    // Microsoft Surface 8b.16b.8b.
+    surface,
+    // Dell BIOS BB.CC.DD style.
+    dellBios,
+    // Hexadecimal 0xAABCCDD style.
+    hex
+  };
+
+  // The info related to a firmware version obtained from fwupd.
+  dictionary FwupdFirmwareVersionInfo {
+    // The string form of the firmware version.
+    DOMString? version;
+    // The format for parsing the version string.
+    FwupdVersionFormat? version_format;
+  };
+
+  enum UsbVersion {
+    // Can't determine the usb version.
+    unknown,
+    // Usb 1.
+    usb1,
+    // Usb 2.
+    usb2,
+    // Usb 3.
+    usb3
+  };
+
+  // An enumeration of the usb spec speed in Mbps.
+  // Source:
+  //   - https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-usb
+  //   - https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-bus-usb
+  //   - https://en.wikipedia.org/wiki/USB
+  //
+  enum UsbSpecSpeed {
+    // Unknown speed.
+    unknown,
+    // Low speed.
+    n1_5Mbps,
+    // Full speed.
+    n12Mbps,
+    // High Speed.
+    n480Mbps,
+    // Super Speed.
+    n5Gbps,
+    // Super Speed+.
+    n10Gbps,
+    // Super Speed+ Gen 2x2.
+    n20Gbps
+  };
+
+  // The info related to usb.
+  dictionary UsbBusInfo {
+    // These fields can be used to classify / identify the usb devices. See the
+    // usb.ids database for the values. (https://github.com/gentoo/hwids)
+    double? classId;
+    double? subclassId;
+    double? protocolId;
+    double? vendorId;
+    double? productId;
+    // The usb interfaces under the device. A usb device has at least one
+    // interface. Each interface may or may not work independently, based on each
+    // device. This allows a usb device to provide multiple features.
+    // The interfaces are sorted by the `interface_number` field.
+    UsbBusInterfaceInfo[] interfaces;
+    // The firmware version obtained from fwupd.
+    FwupdFirmwareVersionInfo? fwupdFirmwareVersionInfo;
+    // The recognized usb version. It may not be the highest USB version supported
+    // by the hardware.
+    UsbVersion? version;
+    // The spec usb speed.
+    UsbSpecSpeed? spec_speed;
+  };
+
+  dictionary UsbBusDevices {
+    UsbBusInfo[] devices;
+  };
+
+  callback UsbBusDevicesCallback = void (UsbBusDevices UsbBusDevices);
+
   dictionary VpdInfo {
     // Device activate date. Format: YYYY-WW.
     DOMString? activateDate;
@@ -276,6 +391,8 @@
 
     [supportsPromises] static void getOsVersionInfo(OsVersionInfoCallback callback);
 
+    [supportsPromises] static void getUsbBusInfo(UsbBusDevicesCallback callback);
+
     [supportsPromises] static void getVpdInfo(VpdInfoCallback callback);
 
     [supportsPromises] static void getStatefulPartitionInfo(StatefulPartitionInfoCallback callback);
diff --git a/chrome/common/extensions/api/file_manager_private.idl b/chrome/common/extensions/api/file_manager_private.idl
index b32e8cd..215adb9 100644
--- a/chrome/common/extensions/api/file_manager_private.idl
+++ b/chrome/common/extensions/api/file_manager_private.idl
@@ -1333,7 +1333,8 @@
 
   // Gets the list of tasks that can be performed over selected files.
   // |entries| Array of selected entries
-  // |dlpSourceUrls| Array of source URLs corresponding to the entries, used to check Data Leak Prevention (DLP) restrictions
+  // |dlpSourceUrls| Array of source URLs corresponding to the entries, used to
+  // check Data Leak Prevention (DLP) restrictions
   // |callback|
   [nocompile]
   static void getFileTasks([instanceof=Entry] object[] entries,
@@ -1782,10 +1783,13 @@
   // Invoke Sharesheet for selected files.
   // |entries| Array of selected entries.
   // |launchSource| Source from which sharesheet was invoked.
+  // |dlpSourceUrls| Array of source URLs corresponding to the entries, used to
+  // check Data Leak Prevention (DLP) restrictions
   // |callback|
   [nocompile]
   static void invokeSharesheet([instanceof=Entry] object[] entries,
                                SharesheetLaunchSource launchSource,
+                               DOMString[] dlpSourceUrls,
                                SimpleCallback callback);
 
   // Adds or removes a list of entries to temporary holding space. Any entries
diff --git a/chrome/common/extensions/api/file_manager_private_internal.idl b/chrome/common/extensions/api/file_manager_private_internal.idl
index 40cc397..70be908 100644
--- a/chrome/common/extensions/api/file_manager_private_internal.idl
+++ b/chrome/common/extensions/api/file_manager_private_internal.idl
@@ -161,6 +161,7 @@
     static void invokeSharesheet(
         DOMString[] urls,
         fileManagerPrivate.SharesheetLaunchSource launchSource,
+        DOMString[] dlpSourceUrls,
         SimpleCallback callback);
     static void toggleAddedToHoldingSpace(DOMString[] urls, boolean add,
                                           optional SimpleCallback callback);
diff --git a/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js b/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
index f764374..6b80b8a 100644
--- a/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
@@ -368,12 +368,13 @@
       });
 
   apiFunctions.setHandleRequest(
-      'invokeSharesheet', function(entries, launchSource, callback) {
+      'invokeSharesheet',
+      function(entries, launchSource, dlpSourceUrls, callback) {
         var urls = entries.map(function(entry) {
           return getEntryURL(entry);
         });
         fileManagerPrivateInternal.invokeSharesheet(
-            urls, launchSource, callback);
+            urls, launchSource, dlpSourceUrls, callback);
       });
 
   apiFunctions.setHandleRequest(
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index ada7e67..683e620 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -9754,7 +9754,10 @@
         deps += [ "../browser/pdf:pdf_extension_test_utils" ]
       }
       if (is_chromeos) {
-        sources += [ "../browser/ui/views/toolbar/browser_app_menu_button_interactive_uitest_chromeos.cc" ]
+        sources += [
+          "../browser/signin/mirror_interactive_uitest.cc",
+          "../browser/ui/views/toolbar/browser_app_menu_button_interactive_uitest_chromeos.cc",
+        ]
       }
       if (is_linux || is_chromeos_lacros) {
         # Desktop linux.
@@ -9877,6 +9880,8 @@
       # These tests require a MessageCenter which isn't available on Lacros.
       sources -=
           [ "../browser/notifications/notification_interactive_uitest.cc" ]
+
+      deps += [ ":lacros_test_support_ui" ]
     }
 
     if (is_win) {
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 996e49fd..86195d6 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -3935,6 +3935,55 @@
       }
     ]
   },
+  "ExtensionUnpublishedAvailability": {
+    "os": [
+      "win",
+      "linux",
+      "mac",
+      "chromeos_ash",
+      "crhomeos_lacros"
+    ],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {
+          "ExtensionUnpublishedAvailability": 0
+        },
+        "prefs": {
+          "extensions.unpublished_availability": {
+            "value": 0
+          }
+        }
+      },
+      {
+        "policies": {
+          "ExtensionUnpublishedAvailability": 1
+        },
+        "prefs": {
+          "extensions.unpublished_availability": {
+            "value": 1
+          }
+        }
+      },
+      {
+        "policies": {
+          "ExtensionUnpublishedAvailability": 2
+        },
+        "prefs": {
+          "extensions.unpublished_availability": {
+            "value": 2
+          }
+        }
+      },
+      {
+        "policies": {},
+        "prefs": {
+          "extensions.unpublished_availability": {
+            "default_value": 0
+          }
+        }
+      }
+    ]
+  },
   "BlockExternalExtensions": {
     "os": [
       "win",
diff --git a/chrome/test/data/webui/password_manager/settings_section_test.ts b/chrome/test/data/webui/password_manager/settings_section_test.ts
index e540bc8..de2df2fc 100644
--- a/chrome/test/data/webui/password_manager/settings_section_test.ts
+++ b/chrome/test/data/webui/password_manager/settings_section_test.ts
@@ -14,6 +14,13 @@
 import {TestPrefsBrowserProxy} from './test_prefs_browser_proxy.js';
 import {createBlockedSiteEntry, makePasswordManagerPrefs} from './test_util.js';
 
+// Disable clang format to keep OS-specific includes.
+// clang-format off
+// <if expr="is_win or is_macosx">
+import {PrefToggleButtonElement} from 'chrome://password-manager/password_manager.js';
+ // </if>
+// clang-format on
+
 /**
  * Helper method that validates a that elements in the exception list match
  * the expected data.
@@ -81,6 +88,50 @@
     assertFalse(settings.$.autosigninToggle.checked);
   });
 
+  // <if expr="is_win or is_macosx">
+  // Tests that biometric auth pref is visible, and clicking on it triggers
+  // biometric auth validation instead of directly updating the pref value.
+  test('biometric auth prefs when feature is available', async function() {
+    await prefsProxy.setPref('biometric_authentication_filling', false);
+    loadTimeData.overrideValues(
+        {biometricAuthenticationForFillingToggleVisible: true});
+
+    const settings = document.createElement('settings-section');
+    document.body.appendChild(settings);
+    await prefsProxy.whenCalled('getPref');
+    await flushTasks();
+
+    const biometricAuthenticationToggle =
+        settings.shadowRoot!.querySelector<PrefToggleButtonElement>(
+            '#biometricAuthenticationToggle') as PrefToggleButtonElement;
+    assertTrue(!!biometricAuthenticationToggle);
+    assertFalse(biometricAuthenticationToggle.checked);
+    biometricAuthenticationToggle.click();
+
+    // Pref settings should not change until authentication succeeds.
+    await passwordManager.whenCalled('switchBiometricAuthBeforeFillingState');
+    assertFalse(biometricAuthenticationToggle.checked);
+
+    // Imitate prefs changing after successful identification.
+    prefsProxy.prefs = makePasswordManagerPrefs();
+    await prefsProxy.setPref('biometric_authentication_filling', true);
+    assertTrue(biometricAuthenticationToggle.checked);
+  });
+
+  // Tests that biometric auth pref is not shown, if biometric auth is
+  // unavailable.
+  test('biometric auth prefs when feature is unavailable', async function() {
+    loadTimeData.overrideValues(
+        {biometricAuthenticationForFillingToggleVisible: false});
+    const settings = document.createElement('settings-section');
+    document.body.appendChild(settings);
+    await prefsProxy.whenCalled('getPref');
+    await flushTasks();
+    assertFalse(!!settings.shadowRoot!.querySelector<PrefToggleButtonElement>(
+        '#biometricAuthenticationToggle'));
+  });
+  // </if>
+
   test('settings section shows blockedSites', async function() {
     passwordManager.data.blockedSites = [
       createBlockedSiteEntry('test.com', 0),
diff --git a/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts b/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts
index b134f0e..d83055f 100644
--- a/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts
+++ b/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts
@@ -53,6 +53,7 @@
       'requestPlaintextPassword',
       'showAddShortcutDialog',
       'startBulkPasswordCheck',
+      'switchBiometricAuthBeforeFillingState',
       'unmuteInsecureCredential',
     ]);
 
@@ -216,4 +217,8 @@
   cancelExportPasswords() {
     this.methodCalled('cancelExportPasswords');
   }
+
+  switchBiometricAuthBeforeFillingState() {
+    this.methodCalled('switchBiometricAuthBeforeFillingState');
+  }
 }
diff --git a/chrome/test/data/webui/password_manager/test_util.ts b/chrome/test/data/webui/password_manager/test_util.ts
index 650860f..2675256 100644
--- a/chrome/test/data/webui/password_manager/test_util.ts
+++ b/chrome/test/data/webui/password_manager/test_util.ts
@@ -129,6 +129,13 @@
       type: chrome.settingsPrivate.PrefType.BOOLEAN,
       value: true,
     },
+    // <if expr="is_win or is_macosx">
+    {
+      key: 'biometric_authentication_filling',
+      type: chrome.settingsPrivate.PrefType.BOOLEAN,
+      value: true,
+    },
+    // </if>
   ];
 }
 
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 1f37f7a..b711bcd 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-15332.0.0
\ No newline at end of file
+15333.0.0
\ No newline at end of file
diff --git a/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc b/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc
index 3b9ce67..707b97f 100644
--- a/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc
+++ b/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc
@@ -5,8 +5,9 @@
 #include "chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.h"
 
 #include "base/functional/bind.h"
-#include "base/run_loop.h"
+#include "base/test/repeating_test_future.h"
 #include "base/test/task_environment.h"
+#include "base/test/test_future.h"
 #include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
 #include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
 #include "components/account_id/account_id.h"
@@ -88,8 +89,7 @@
   }
 
   void OnWaitForServiceToBeAvailableCalled(bool is_service_available) {
-    EXPECT_TRUE(is_service_available);
-    service_is_available_called_num_++;
+    service_available_future_.AddValue(is_service_available);
   }
 
   void LockDevice() {
@@ -97,7 +97,7 @@
                                                          "device_id");
   }
 
-  int service_is_available_called_num_ = 0;
+  base::test::RepeatingTestFuture<bool> service_available_future_;
 
  private:
   ScopedStubInstallAttributes install_attributes_;
@@ -131,17 +131,11 @@
                      EXPECT_EQ(authpolicy::ERROR_INVALID_MACHINE_NAME, error);
                      EXPECT_TRUE(domain.empty());
                    }));
-  base::RunLoop loop;
-  JoinAdDomain(">nvalidname", kCorrectUserName,
-               base::BindOnce(
-                   [](base::OnceClosure closure, authpolicy::ErrorType error,
-                      const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_INVALID_MACHINE_NAME, error);
-                     EXPECT_TRUE(domain.empty());
-                     std::move(closure).Run();
-                   },
-                   loop.QuitClosure()));
-  loop.Run();
+
+  base::test::TestFuture<authpolicy::ErrorType, const std::string&> future;
+  JoinAdDomain(">nvalidname", kCorrectUserName, future.GetCallback());
+  EXPECT_EQ(authpolicy::ERROR_INVALID_MACHINE_NAME, future.Get<0>());
+  EXPECT_TRUE(future.Get<1>().empty());
 }
 
 // Tests join to a different machine domain.
@@ -154,18 +148,13 @@
                                   EXPECT_EQ(authpolicy::ERROR_NONE, error);
                                   EXPECT_EQ(kMachineDomain, domain);
                                 }));
-  base::RunLoop loop;
-  JoinAdDomainWithMachineDomain(
-      kCorrectMachineName, "", kCorrectUserName,
-      base::BindOnce(
-          [](base::OnceClosure closure, authpolicy::ErrorType error,
-             const std::string& domain) {
-            EXPECT_EQ(authpolicy::ERROR_NONE, error);
-            EXPECT_EQ(kCorrectUserDomain, domain);
-            std::move(closure).Run();
-          },
-          loop.QuitClosure()));
-  loop.Run();
+
+  base::test::TestFuture<authpolicy::ErrorType, const std::string&> future;
+  JoinAdDomainWithMachineDomain(kCorrectMachineName, "", kCorrectUserName,
+                                future.GetCallback());
+
+  EXPECT_EQ(authpolicy::ERROR_NONE, future.Get<0>());
+  EXPECT_EQ(kCorrectUserDomain, future.Get<1>());
 }
 
 // Tests parsing user name.
@@ -201,40 +190,28 @@
                      EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, error);
                      EXPECT_TRUE(domain.empty());
                    }));
-  base::RunLoop loop;
-  JoinAdDomain(kCorrectMachineName, "user@realm@com",
-               base::BindOnce(
-                   [](base::OnceClosure closure, authpolicy::ErrorType error,
-                      const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, error);
-                     EXPECT_TRUE(domain.empty());
-                     std::move(closure).Run();
-                   },
-                   loop.QuitClosure()));
-  loop.Run();
+
+  base::test::TestFuture<authpolicy::ErrorType, const std::string&> future;
+  JoinAdDomain(kCorrectMachineName, "user@realm@com", future.GetCallback());
+  EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, future.Get<0>());
+  EXPECT_TRUE(future.Get<1>().empty());
 }
 
 // Tests that fake server does not support legacy encryption types.
 TEST_F(FakeAuthPolicyClientTest, JoinAdDomain_NotSupportedEncType) {
   authpolicy_client()->SetStarted(true);
-  base::RunLoop loop;
   authpolicy::JoinDomainRequest request;
   request.set_machine_name(kCorrectMachineName);
   request.set_user_principal_name(kCorrectUserName);
   request.set_kerberos_encryption_types(
       authpolicy::KerberosEncryptionTypes::ENC_TYPES_LEGACY);
-  authpolicy_client()->JoinAdDomain(
-      request, /* password_fd */ -1,
-      base::BindOnce(
-          [](base::OnceClosure closure, authpolicy::ErrorType error,
-             const std::string& domain) {
-            EXPECT_EQ(authpolicy::ERROR_KDC_DOES_NOT_SUPPORT_ENCRYPTION_TYPE,
-                      error);
-            EXPECT_TRUE(domain.empty());
-            std::move(closure).Run();
-          },
-          loop.QuitClosure()));
-  loop.Run();
+
+  base::test::TestFuture<authpolicy::ErrorType, const std::string&> future;
+  authpolicy_client()->JoinAdDomain(request, /* password_fd */ -1,
+                                    future.GetCallback());
+  EXPECT_EQ(authpolicy::ERROR_KDC_DOES_NOT_SUPPORT_ENCRYPTION_TYPE,
+            future.Get<0>());
+  EXPECT_TRUE(future.Get<1>().empty());
 }
 
 // Test AuthenticateUser.
@@ -271,16 +248,10 @@
       base::BindOnce([](authpolicy::ErrorType error) {
         EXPECT_EQ(authpolicy::ERROR_DBUS_FAILURE, error);
       }));
-  base::RunLoop loop;
+  base::test::TestFuture<authpolicy::ErrorType> future;
   authpolicy_client()->RefreshUserPolicy(
-      AccountId::FromUserEmail(kCorrectUserName),
-      base::BindOnce(
-          [](base::OnceClosure closure, authpolicy::ErrorType error) {
-            EXPECT_EQ(authpolicy::ERROR_DBUS_FAILURE, error);
-            std::move(closure).Run();
-          },
-          loop.QuitClosure()));
-  loop.Run();
+      AccountId::FromUserEmail(kCorrectUserName), future.GetCallback());
+  EXPECT_EQ(authpolicy::ERROR_DBUS_FAILURE, future.Get());
 }
 
 // Tests RefreshDevicePolicy. On a not locked device it should cache policy. On
@@ -292,14 +263,9 @@
         EXPECT_EQ(authpolicy::ERROR_DEVICE_POLICY_CACHED_BUT_NOT_SENT, error);
       }));
   LockDevice();
-  base::RunLoop loop;
-  authpolicy_client()->RefreshDevicePolicy(base::BindOnce(
-      [](base::OnceClosure closure, authpolicy::ErrorType error) {
-        EXPECT_EQ(authpolicy::ERROR_NONE, error);
-        std::move(closure).Run();
-      },
-      loop.QuitClosure()));
-  loop.Run();
+  base::test::TestFuture<authpolicy::ErrorType> future;
+  authpolicy_client()->RefreshDevicePolicy(future.GetCallback());
+  EXPECT_EQ(authpolicy::ERROR_NONE, future.Get());
 }
 
 // Tests that RefreshDevicePolicy stores device policy in the session manager.
@@ -309,17 +275,12 @@
 
   {
     // Call RefreshDevicePolicy.
-    base::RunLoop loop;
+    base::test::TestFuture<authpolicy::ErrorType> future;
     em::ChromeDeviceSettingsProto policy;
     policy.mutable_allow_new_users()->set_allow_new_users(true);
     authpolicy_client()->set_device_policy(policy);
-    authpolicy_client()->RefreshDevicePolicy(base::BindOnce(
-        [](base::OnceClosure closure, authpolicy::ErrorType error) {
-          EXPECT_EQ(authpolicy::ERROR_NONE, error);
-          std::move(closure).Run();
-        },
-        loop.QuitClosure()));
-    loop.Run();
+    authpolicy_client()->RefreshDevicePolicy(future.GetCallback());
+    EXPECT_EQ(authpolicy::ERROR_NONE, future.Get());
   }
 
   {
@@ -343,16 +304,16 @@
 }
 
 TEST_F(FakeAuthPolicyClientTest, WaitForServiceToBeAvailableCalled) {
+  // Start waiting for service before starting the client.
   WaitForServiceToBeAvailable();
   WaitForServiceToBeAvailable();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(0, service_is_available_called_num_);
   authpolicy_client()->SetStarted(true);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(2, service_is_available_called_num_);
   WaitForServiceToBeAvailable();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(3, service_is_available_called_num_);
+
+  // Wait for the future to catch all three callbacks.
+  EXPECT_TRUE(service_available_future_.Take());
+  EXPECT_TRUE(service_available_future_.Take());
+  EXPECT_TRUE(service_available_future_.Take());
 }
 
 }  // namespace ash
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager.cc b/chromeos/ash/components/drivefs/drivefs_pin_manager.cc
index adc0296..0b3030b 100644
--- a/chromeos/ash/components/drivefs/drivefs_pin_manager.cc
+++ b/chromeos/ash/components/drivefs/drivefs_pin_manager.cc
@@ -6,8 +6,6 @@
 
 #include <locale>
 #include <type_traits>
-#include <utility>
-#include <vector>
 
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
@@ -301,28 +299,41 @@
 // queue.
 constexpr base::TimeDelta kPeriodicRemovalInterval = base::Seconds(10);
 
-bool PinManager::Add(const Id id, const std::string& path, const int64_t size) {
+bool PinManager::Add(const Id id,
+                     const std::string& path,
+                     const int64_t size,
+                     const bool pinned) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_GE(size, 0) << " for " << id << " " << Quote(path);
 
-  const auto [it, ok] =
-      files_to_pin_.try_emplace(id, File{.path = path, .total = size});
+  const auto [it, ok] = files_to_track_.try_emplace(
+      id, File{.path = path, .total = size, .pinned = pinned});
   DCHECK_EQ(id, it->first);
   if (!ok) {
     LOG_IF(ERROR, !ok) << "Cannot add " << id << " " << Quote(path)
                        << " with size " << HumanReadableSize(size)
-                       << " to the files to pin: Conflicting entry "
+                       << " to the files to track: Conflicting entry "
                        << it->second;
     return false;
   }
 
   VLOG(3) << "Added " << id << " " << Quote(path) << " with size "
-          << HumanReadableSize(size) << " to the files to pin";
+          << HumanReadableSize(size) << " to the files to track";
   progress_.bytes_to_pin += size;
   progress_.required_space += RoundToBlockSize(size);
   progress_.files_to_pin++;
   DCHECK_EQ(static_cast<size_t>(progress_.files_to_pin),
-            files_to_pin_.size() + files_to_track_.size());
+            files_to_track_.size());
+
+  if (pinned) {
+    progress_.syncing_files++;
+    DCHECK_EQ(progress_.syncing_files, CountPinnedFiles());
+  } else {
+    files_to_pin_.push_back(id);
+    DCHECK_LE(files_to_pin_.size(),
+              static_cast<size_t>(progress_.files_to_pin));
+  }
+
   return true;
 }
 
@@ -343,10 +354,12 @@
     Update(*it, path, transferred, transferred);
   }
 
+  if (it->second.pinned) {
+    progress_.syncing_files--;
+  }
+
   files_to_track_.erase(it);
-  progress_.syncing_files--;
-  DCHECK_EQ(static_cast<size_t>(progress_.syncing_files),
-            files_to_track_.size());
+  DCHECK_EQ(progress_.syncing_files, CountPinnedFiles());
   VLOG(3) << "Stopped tracking " << id << " " << Quote(path);
   return true;
 }
@@ -541,26 +554,13 @@
 
       VLOG(1) << "Already pinned but not available offline yet: " << id << " "
               << Quote(path);
-      const auto [it, ok] = files_to_track_.try_emplace(
-          id, File{.path = path.value(), .total = size, .pinned = true});
-      DCHECK(ok);
-      DCHECK_EQ(it->first, id);
-      progress_.syncing_files++;
-      DCHECK_EQ(static_cast<size_t>(progress_.syncing_files),
-                files_to_track_.size());
-      progress_.bytes_to_pin += size;
-      progress_.required_space += RoundToBlockSize(size);
-      progress_.files_to_pin++;
-      DCHECK_EQ(static_cast<size_t>(progress_.files_to_pin),
-                files_to_pin_.size() + files_to_track_.size());
-      continue;
+    } else {
+      VLOG_IF(1, md.available_offline)
+          << "Not pinned yet but already available offline: " << id << " "
+          << Quote(path) << ": " << Quote(md);
     }
 
-    VLOG_IF(1, md.available_offline)
-        << "Not pinned yet but already available offline: " << id << " "
-        << Quote(path) << ": " << Quote(md);
-
-    Add(id, path.value(), size);
+    Add(id, path.value(), size, md.pinned);
   }
 
   NotifyProgress();
@@ -637,13 +637,14 @@
     return Complete(Stage::kSuccess);
   }
 
-  if (files_to_track_.empty() && files_to_pin_.empty()) {
+  if (files_to_track_.empty()) {
     VLOG(1) << "Nothing to pin or track";
+    DCHECK(files_to_pin_.empty());
+    DCHECK_EQ(progress_.files_to_pin, 0);
+    DCHECK_EQ(progress_.syncing_files, 0);
     return Complete(Stage::kSuccess);
   }
 
-  VLOG(1) << "Pinning and tracking "
-          << (files_to_pin_.size() + files_to_track_.size()) << " files...";
   timer_ = base::ElapsedTimer();
   progress_.stage = Stage::kSyncing;
   NotifyProgress();
@@ -665,27 +666,33 @@
     return Complete(Stage::kSuccess);
   }
 
-  while (files_to_track_.size() < 50 && !files_to_pin_.empty()) {
-    Files::node_type node = files_to_pin_.extract(files_to_pin_.begin());
-    DCHECK(node);
-    const Id id = node.key();
-    File& file = node.mapped();
+  while (progress_.syncing_files < 50 && !files_to_pin_.empty()) {
+    const Id id = files_to_pin_.back();
+    files_to_pin_.pop_back();
+
+    const Files::iterator it = files_to_track_.find(id);
+    if (it == files_to_track_.end()) {
+      VLOG(2) << "Not tracked: " << id;
+      continue;
+    }
+
+    DCHECK_EQ(it->first, id);
+    File& file = it->second;
     const std::string& path = file.path;
 
+    if (file.pinned) {
+      VLOG(2) << "Already pinned: " << id << " " << Quote(path);
+      continue;
+    }
+
     VLOG(2) << "Pinning " << id << " " << Quote(path);
     drivefs_->SetPinnedByStableId(
         static_cast<int64_t>(id), true,
         base::BindOnce(&PinManager::OnFilePinned, GetWeakPtr(), id, path));
 
-    DCHECK(!file.pinned);
-    DCHECK(!file.in_progress);
     file.pinned = true;
-    const Files::insert_return_type ir =
-        files_to_track_.insert(std::move(node));
-    DCHECK(ir.inserted) << " for " << id << " " << path;
     progress_.syncing_files++;
-    DCHECK_EQ(static_cast<size_t>(progress_.syncing_files),
-              files_to_track_.size());
+    DCHECK_EQ(progress_.syncing_files, CountPinnedFiles());
   }
 
   VLOG(1) << "Progress "
@@ -794,7 +801,21 @@
 
   if (progress_.stage != Stage::kSyncing) {
     for (const mojom::FileChange& change : changes) {
-      VLOG(1) << "Ignored FileChange " << Quote(change);
+      switch (change.type) {
+        case mojom::FileChange::Type::kDelete:
+          VLOG(1) << "Got FileChange " << Quote(change);
+          drivefs_->SetPinnedByStableId(
+              change.stable_id, /*pinned=*/false,
+              base::BindOnce([](drive::FileError status) {
+                LOG_IF(ERROR, status != drive::FILE_ERROR_OK)
+                    << "Failed to unpin file: "
+                    << drive::FileErrorToString(status);
+              }));
+          break;
+        default:
+          VLOG(1) << "Ignored FileChange " << Quote(change);
+          break;
+      }
     }
     return;
   }
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager.h b/chromeos/ash/components/drivefs/drivefs_pin_manager.h
index effd7243..ce52530 100644
--- a/chromeos/ash/components/drivefs/drivefs_pin_manager.h
+++ b/chromeos/ash/components/drivefs/drivefs_pin_manager.h
@@ -5,6 +5,7 @@
 #ifndef CHROMEOS_ASH_COMPONENTS_DRIVEFS_DRIVEFS_PIN_MANAGER_H_
 #define CHROMEOS_ASH_COMPONENTS_DRIVEFS_DRIVEFS_PIN_MANAGER_H_
 
+#include <algorithm>
 #include <ostream>
 #include <utility>
 #include <vector>
@@ -203,7 +204,7 @@
   }
 
  private:
-  // Struct keeping track of the progress of a file being synced.
+  // Progress of a file being synced or to be synced.
   struct File {
     // Path inside the Drive folder.
     // TODO(b/265209836) Remove this field when not needed anymore.
@@ -230,22 +231,21 @@
 
   using Files = std::map<Id, File>;
 
-  // Adds an item to the files to pin.  Does nothing if an item with the same ID
-  // already exists in files_to_pin_. Updates the total number of bytes to
-  // transfer and the required space. Returns whether an item was actually
-  // added.
-  bool Add(Id id, const std::string& path, int64_t size);
+  // Adds an item to the files to track.  Does nothing if an item with the same
+  // ID already exists in the map. Updates the total number of bytes to transfer
+  // and the required space. Returns whether an item was actually added.
+  bool Add(Id id, const std::string& path, int64_t size, bool pinned);
 
-  // Removes an item from the map. Does nothing if the item is not in the map.
-  // Updates the total number of bytes transferred so far. If `transferred` is
-  // negative, use the total expected size. Returns whether an item was actually
-  // removed.
+  // Removes an item from the files to track. Does nothing if the item is not in
+  // the map. Updates the total number of bytes transferred so far. If
+  // `transferred` is negative, use the total expected size. Returns whether an
+  // item was actually removed.
   bool Remove(Id id, const std::string& path, int64_t transferred = -1);
 
-  // Updates an item in the map. Does nothing if the item is not in the map.
-  // Updates the total number of bytes transferred so far. Updates the required
-  // space. If `transferred` or `total` is negative, then the matching argument
-  // is ignored. Returns whether anything has actually been updated.
+  // Updates an item in the files to track. Does nothing if the item is not in
+  // the map. Updates the total number of bytes transferred so far. Updates the
+  // required space. If `transferred` or `total` is negative, then the matching
+  // argument is ignored. Returns whether anything has actually been updated.
   bool Update(Id id,
               const std::string& path,
               int64_t transferred,
@@ -300,6 +300,16 @@
   // Report progress to all the observers.
   void NotifyProgress();
 
+  // Counts the files that have been marked as pinned and that are still being
+  // tracked. Should always be equal to progress_.syncing_files. For debugging
+  // only.
+  int CountPinnedFiles() const {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return std::count_if(
+        files_to_track_.cbegin(), files_to_track_.cend(),
+        [](const Files::value_type& entry) { return entry.second.pinned; });
+  }
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   // Should the feature actually pin files, or should it stop after checking the
@@ -321,8 +331,12 @@
   mojo::Remote<mojom::SearchQuery> search_query_;
   base::ElapsedTimer timer_;
 
-  // Map that tracks the in-progress files indexed by their stable ID.
-  Files files_to_pin_ GUARDED_BY_CONTEXT(sequence_checker_);
+  // Stable IDs of the files to pin, and which are not already marked as pinned.
+  std::vector<Id> files_to_pin_ GUARDED_BY_CONTEXT(sequence_checker_);
+
+  // Map that tracks the in-progress files indexed by their stable ID. This
+  // contains all the files, either pinned or not, that are not completely
+  // cached yet.
   Files files_to_track_ GUARDED_BY_CONTEXT(sequence_checker_);
 
   base::WeakPtrFactory<PinManager> weak_ptr_factory_{this};
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc b/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc
index c851ace2..7ba3e4b 100644
--- a/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc
+++ b/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc
@@ -48,6 +48,7 @@
 using testing::_;
 using testing::AnyNumber;
 using testing::DoAll;
+using testing::ElementsAre;
 using testing::Field;
 using testing::IsEmpty;
 using testing::Return;
@@ -265,24 +266,25 @@
   EXPECT_THAT(manager.files_to_track_, IsEmpty());
 
   // Add an item.
-  EXPECT_TRUE(manager.Add(id1, path1, size1));
-  EXPECT_THAT(manager.files_to_pin_, SizeIs(1));
-  EXPECT_THAT(manager.files_to_track_, IsEmpty());
+  EXPECT_TRUE(manager.Add(id1, path1, size1, false));
+  EXPECT_THAT(manager.files_to_pin_, ElementsAre(id1));
+  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
 
   // Try to add a conflicting item with the same ID, but different path and
   // size.
-  EXPECT_FALSE(manager.Add(id1, path2, size2));
-  EXPECT_THAT(manager.files_to_pin_, SizeIs(1));
-  EXPECT_THAT(manager.files_to_track_, IsEmpty());
+  EXPECT_FALSE(manager.Add(id1, path2, size2, true));
+  EXPECT_THAT(manager.files_to_pin_, ElementsAre(id1));
+  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
 
   {
-    const auto it = manager.files_to_pin_.find(id1);
-    ASSERT_NE(it, manager.files_to_pin_.end());
+    const auto it = manager.files_to_track_.find(id1);
+    ASSERT_NE(it, manager.files_to_track_.end());
     const auto& [id, file] = *it;
     EXPECT_EQ(id, id1);
     EXPECT_EQ(file.path, path1);
     EXPECT_EQ(file.total, size1);
     EXPECT_EQ(file.transferred, 0);
+    EXPECT_FALSE(file.pinned);
     EXPECT_FALSE(file.in_progress);
   }
 
@@ -292,22 +294,25 @@
     EXPECT_EQ(progress.pinned_bytes, 0);
     EXPECT_EQ(progress.bytes_to_pin, size1);
     EXPECT_EQ(progress.required_space, 698249216);
+    EXPECT_EQ(progress.syncing_files, 0);
+    EXPECT_EQ(progress.files_to_pin, 1);
   }
 
-  // Add a second item.
-  EXPECT_TRUE(manager.Add(id2, path2, size2));
-  EXPECT_THAT(manager.files_to_pin_, SizeIs(2));
-  EXPECT_THAT(manager.files_to_track_, IsEmpty());
+  // Add a second item, but which is already pinned this time.
+  EXPECT_TRUE(manager.Add(id2, path2, size2, true));
+  EXPECT_THAT(manager.files_to_pin_, ElementsAre(id1));
+  EXPECT_THAT(manager.files_to_track_, SizeIs(2));
 
   {
-    const auto it = manager.files_to_pin_.find(id2);
-    ASSERT_NE(it, manager.files_to_pin_.end());
+    const auto it = manager.files_to_track_.find(id2);
+    ASSERT_NE(it, manager.files_to_track_.end());
     const auto& [id, file] = *it;
     EXPECT_EQ(id, id2);
     EXPECT_EQ(file.path, path2);
     EXPECT_EQ(file.total, size2);
     EXPECT_EQ(file.transferred, 0);
     EXPECT_FALSE(file.in_progress);
+    EXPECT_TRUE(file.pinned);
   }
 
   {
@@ -316,6 +321,8 @@
     EXPECT_EQ(progress.pinned_bytes, 0);
     EXPECT_EQ(progress.bytes_to_pin, size1 + size2);
     EXPECT_EQ(progress.required_space, 777216000);
+    EXPECT_EQ(progress.syncing_files, 1);
+    EXPECT_EQ(progress.files_to_pin, 2);
   }
 }
 
@@ -548,6 +555,7 @@
         id1, PinManager::File{.path = path1,
                               .transferred = 1200,
                               .total = 3000,
+                              .pinned = true,
                               .in_progress = true});
     ASSERT_TRUE(ok);
     manager.progress_.syncing_files++;
@@ -600,9 +608,9 @@
         id1, PinManager::File{.path = path1,
                               .transferred = 1200,
                               .total = 3000,
+                              .pinned = false,
                               .in_progress = true});
     ASSERT_TRUE(ok);
-    manager.progress_.syncing_files++;
   }
 
   EXPECT_THAT(manager.files_to_track_, SizeIs(1));
@@ -626,6 +634,7 @@
         id1, PinManager::File{.path = path1,
                               .transferred = 5000,
                               .total = 6000,
+                              .pinned = true,
                               .in_progress = true});
     ASSERT_TRUE(ok);
     manager.progress_.syncing_files++;
@@ -673,13 +682,13 @@
   // Put in place a couple of files to track.
   {
     const auto [it, ok] = manager.files_to_track_.try_emplace(
-        id1, PinManager::File{.path = path1, .total = 10000});
+        id1, PinManager::File{.path = path1, .total = 10000, .pinned = true});
     ASSERT_TRUE(ok);
     manager.progress_.syncing_files++;
   }
   {
     const auto [it, ok] = manager.files_to_track_.try_emplace(
-        id2, PinManager::File{.path = path2, .total = 20000});
+        id2, PinManager::File{.path = path2, .total = 20000, .pinned = true});
     ASSERT_TRUE(ok);
     manager.progress_.syncing_files++;
   }
@@ -740,6 +749,7 @@
     EXPECT_EQ(file.path, path1);
     EXPECT_EQ(file.total, 10000);
     EXPECT_EQ(file.transferred, 0);
+    EXPECT_TRUE(file.pinned);
     EXPECT_TRUE(file.in_progress);
   }
 
@@ -775,6 +785,7 @@
     EXPECT_EQ(file.path, path1);
     EXPECT_EQ(file.total, 10000);
     EXPECT_EQ(file.transferred, 5000);
+    EXPECT_TRUE(file.pinned);
     EXPECT_TRUE(file.in_progress);
   }
 
diff --git a/chromeos/ash/components/drivefs/fake_drivefs.cc b/chromeos/ash/components/drivefs/fake_drivefs.cc
index 1ae5657..dc00f4c1 100644
--- a/chromeos/ash/components/drivefs/fake_drivefs.cc
+++ b/chromeos/ash/components/drivefs/fake_drivefs.cc
@@ -90,20 +90,6 @@
   return std::move(bootstrap_);
 }
 
-struct FakeDriveFs::FileMetadata {
-  std::string mime_type;
-  bool pinned = false;
-  bool hosted = false;
-  bool shared = false;
-  bool available_offline = false;
-  std::string original_name;
-  mojom::Capabilities capabilities;
-  mojom::FolderFeature folder_feature;
-  std::string doc_id;
-  int64_t stable_id = 0;
-  std::string alternate_url;
-};
-
 class FakeDriveFs::SearchQuery : public mojom::SearchQuery {
  public:
   SearchQuery(base::WeakPtr<FakeDriveFs> drive_fs,
@@ -258,6 +244,13 @@
   base::WeakPtrFactory<SearchQuery> weak_ptr_factory_{this};
 };
 
+FakeDriveFs::FileMetadata::FileMetadata() = default;
+FakeDriveFs::FileMetadata::FileMetadata(const FakeDriveFs::FileMetadata&) =
+    default;
+FakeDriveFs::FileMetadata& FakeDriveFs::FileMetadata::operator=(
+    const FakeDriveFs::FileMetadata&) = default;
+FakeDriveFs::FileMetadata::~FileMetadata() = default;
+
 FakeDriveFs::FakeDriveFs(const base::FilePath& mount_path)
     : mount_path_(mount_path) {
   CHECK(mount_path.IsAbsolute());
@@ -331,6 +324,15 @@
   return absl::nullopt;
 }
 
+absl::optional<FakeDriveFs::FileMetadata> FakeDriveFs::GetItemMetadata(
+    const base::FilePath& path) {
+  const auto& metadata = metadata_.find(path);
+  if (metadata == metadata_.end()) {
+    return absl::nullopt;
+  }
+  return metadata->second;
+}
+
 void FakeDriveFs::Init(
     drivefs::mojom::DriveFsConfigurationPtr config,
     mojo::PendingReceiver<drivefs::mojom::DriveFs> receiver,
diff --git a/chromeos/ash/components/drivefs/fake_drivefs.h b/chromeos/ash/components/drivefs/fake_drivefs.h
index 680e555..050d7ff 100644
--- a/chromeos/ash/components/drivefs/fake_drivefs.h
+++ b/chromeos/ash/components/drivefs/fake_drivefs.h
@@ -84,8 +84,29 @@
 
   absl::optional<bool> IsItemPinned(const std::string& path);
 
+  struct FileMetadata {
+    FileMetadata();
+    FileMetadata(const FileMetadata&);
+    FileMetadata& operator=(const FileMetadata&);
+    ~FileMetadata();
+
+    std::string mime_type;
+    bool pinned = false;
+    bool hosted = false;
+    bool shared = false;
+    bool available_offline = false;
+    std::string original_name;
+    mojom::Capabilities capabilities;
+    mojom::FolderFeature folder_feature;
+    std::string doc_id;
+    int64_t stable_id = 0;
+    std::string alternate_url;
+  };
+
+  absl::optional<FakeDriveFs::FileMetadata> GetItemMetadata(
+      const base::FilePath& path);
+
  private:
-  struct FileMetadata;
   class SearchQuery;
 
   // drivefs::mojom::DriveFsBootstrap:
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index 81b5d91f..52b741815 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -65,6 +65,7 @@
 class AutofillAblationStudy;
 class AutofillDriver;
 struct AutofillErrorDialogContext;
+class AutofillManager;
 class AutofillOfferData;
 class AutofillOfferManager;
 class AutofillPopupDelegate;
@@ -638,9 +639,10 @@
   // extensive than `IsFastCheckoutSupported()`.
   // If it is, shows the FastCheckout surface (for autofilling information
   // during the checkout flow) and returns `true` on success.
-  virtual bool TryToShowFastCheckout(const FormData& form,
-                                     const FormFieldData& field,
-                                     AutofillDriver* driver) = 0;
+  virtual bool TryToShowFastCheckout(
+      const FormData& form,
+      const FormFieldData& field,
+      base::WeakPtr<AutofillManager> autofill_manager) = 0;
 
   // Hides the Fast Checkout surface (for autofilling information during the
   // checkout flow) if one is currently shown.
diff --git a/components/autofill/core/browser/autofill_experiments.cc b/components/autofill/core/browser/autofill_experiments.cc
index e9ae5a1..e21558be 100644
--- a/components/autofill/core/browser/autofill_experiments.cc
+++ b/components/autofill/core/browser/autofill_experiments.cc
@@ -35,7 +35,6 @@
 #include "components/sync/driver/sync_user_settings.h"
 #include "components/variations/variations_associated_data.h"
 #include "google_apis/gaia/gaia_auth_util.h"
-#include "google_apis/gaia/google_service_auth_error.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/ui_base_features.h"
 
@@ -116,13 +115,11 @@
     return false;
   }
 
-  // TODO(crbug.com/1156584): This should simply check SyncService::
-  // GetTransportState() is PAUSED.
-  if (sync_service->GetAuthError().IsPersistentError()) {
+  if (sync_service->GetTransportState() ==
+      syncer::SyncService::TransportState::PAUSED) {
     autofill_metrics::LogCardUploadEnabledMetric(
-        autofill_metrics::CardUploadEnabled::kSyncServicePersistentAuthError,
-        sync_state);
-    LogCardUploadDisabled(log_manager, "SYNC_SERVICE_PERSISTENT_ERROR");
+        autofill_metrics::CardUploadEnabled::kSyncServicePaused, sync_state);
+    LogCardUploadDisabled(log_manager, "SYNC_SERVICE_PAUSED");
     return false;
   }
 
diff --git a/components/autofill/core/browser/autofill_experiments_unittest.cc b/components/autofill/core/browser/autofill_experiments_unittest.cc
index 9b789cd..9b70ac6 100644
--- a/components/autofill/core/browser/autofill_experiments_unittest.cc
+++ b/components/autofill/core/browser/autofill_experiments_unittest.cc
@@ -109,10 +109,10 @@
   EXPECT_FALSE(IsCreditCardUploadEnabled(AutofillSyncSigninState::kSyncPaused));
   histogram_tester.ExpectUniqueSample(
       "Autofill.CardUploadEnabled",
-      autofill_metrics::CardUploadEnabled::kSyncServicePersistentAuthError, 1);
+      autofill_metrics::CardUploadEnabled::kSyncServicePaused, 1);
   histogram_tester.ExpectUniqueSample(
       "Autofill.CardUploadEnabled.SyncPaused",
-      autofill_metrics::CardUploadEnabled::kSyncServicePersistentAuthError, 1);
+      autofill_metrics::CardUploadEnabled::kSyncServicePaused, 1);
 }
 
 TEST_F(AutofillExperimentsTest,
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index f6e937a..c8c1168 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -645,6 +645,18 @@
   return it != form_structures_.end() ? it->second.get() : nullptr;
 }
 
+void AutofillManager::SetShouldSuppressKeyboard(bool suppress) {
+  driver_->SetShouldSuppressKeyboard(suppress);
+}
+
+bool AutofillManager::CanShowAutofillUi() const {
+  return driver_->CanShowAutofillUi();
+}
+
+void AutofillManager::TriggerReparseInAllFrames() {
+  driver_->TriggerReparseInAllFrames();
+}
+
 void AutofillManager::ParseFormsAsync(
     const std::vector<FormData>& forms,
     base::OnceCallback<void(AutofillManager&, const std::vector<FormData>&)>
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index 5a2a51f2..c8ce26f 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -287,6 +287,15 @@
   // Returns the number of forms this Autofill handler is aware of.
   size_t NumFormsDetected() const { return form_structures_.size(); }
 
+  // Forwards call to the same-named `AutofillDriver` function.
+  virtual void SetShouldSuppressKeyboard(bool suppress);
+
+  // Forwards call to the same-named `AutofillDriver` function.
+  virtual bool CanShowAutofillUi() const;
+
+  // Forwards call to the same-named `AutofillDriver` function.
+  virtual void TriggerReparseInAllFrames();
+
   void AddObserver(Observer* observer) { observers_.AddObserver(observer); }
 
   void RemoveObserver(Observer* observer) {
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc
index 559670f..7c27f3f 100644
--- a/components/autofill/core/browser/autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -47,6 +47,10 @@
   MockAutofillDriver(const MockAutofillDriver&) = delete;
   MockAutofillDriver& operator=(const MockAutofillDriver&) = delete;
   ~MockAutofillDriver() override = default;
+
+  MOCK_METHOD(void, SetShouldSuppressKeyboard, (bool), ());
+  MOCK_METHOD(bool, CanShowAutofillUi, (), (const));
+  MOCK_METHOD(void, TriggerReparseInAllFrames, (), ());
 };
 
 class MockAutofillManager : public AutofillManager {
@@ -359,4 +363,19 @@
   manager_->OnTextFieldDidChange(form, field, bounds, time);
 }
 
+TEST_F(AutofillManagerTest, SetShouldSuppressKeyboard) {
+  EXPECT_CALL(*driver_, SetShouldSuppressKeyboard(true));
+  manager_->SetShouldSuppressKeyboard(true);
+}
+
+TEST_F(AutofillManagerTest, CanShowAutofillUi) {
+  EXPECT_CALL(*driver_, CanShowAutofillUi).WillOnce(Return(true));
+  EXPECT_TRUE(manager_->CanShowAutofillUi());
+}
+
+TEST_F(AutofillManagerTest, TriggerReparseInAllFrames) {
+  EXPECT_CALL(*driver_, TriggerReparseInAllFrames);
+  manager_->TriggerReparseInAllFrames();
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index 3ff24b0..2291ec2 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -1162,7 +1162,7 @@
   auto ShouldShowSuggestion = [&] {
     if (client()->IsShowingFastCheckoutUI() ||
         (form_element_was_clicked &&
-         client()->TryToShowFastCheckout(form, field, driver()))) {
+         client()->TryToShowFastCheckout(form, field, GetWeakPtr()))) {
       // The Fast Checkout surface is shown, so abort showing regular Autofill
       // UI. Now the flow is controlled by the `FastCheckoutClient` instead of
       // `external_delegate_`.
@@ -3328,18 +3328,6 @@
   credit_card_form_event_logger_->set_autofill_suggestion_method(method);
 }
 
-void BrowserAutofillManager::SetShouldSuppressKeyboard(bool suppress) {
-  driver()->SetShouldSuppressKeyboard(suppress);
-}
-
-bool BrowserAutofillManager::CanShowAutofillUi() const {
-  return driver()->CanShowAutofillUi();
-}
-
-void BrowserAutofillManager::TriggerReparseInAllFrames() {
-  driver()->TriggerReparseInAllFrames();
-}
-
 void BrowserAutofillManager::ProcessFieldLogEventsInForm(
     const FormStructure& form_structure) {
   // TODO(crbug.com/1325851): Log metrics if at least one field in the form was
diff --git a/components/autofill/core/browser/browser_autofill_manager.h b/components/autofill/core/browser/browser_autofill_manager.h
index 4b5555b..d266080e 100644
--- a/components/autofill/core/browser/browser_autofill_manager.h
+++ b/components/autofill/core/browser/browser_autofill_manager.h
@@ -312,15 +312,6 @@
   // keyboard accessory, etc.
   virtual void SetAutofillSuggestionMethod(AutofillSuggestionMethod state);
 
-  // Forwards call to the same-named `AutofillDriver` function.
-  virtual void SetShouldSuppressKeyboard(bool suppress);
-
-  // Forwards call to the same-named `AutofillDriver` function.
-  virtual bool CanShowAutofillUi() const;
-
-  // Forwards call to the same-named `AutofillDriver` function.
-  virtual void TriggerReparseInAllFrames();
-
   void SetExternalDelegateForTest(
       std::unique_ptr<AutofillExternalDelegate> external_delegate) {
     external_delegate_ = std::move(external_delegate);
diff --git a/components/autofill/core/browser/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
index 8cbb611..e1f820d 100644
--- a/components/autofill/core/browser/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
@@ -381,9 +381,6 @@
               SendFieldsEligibleForManualFillingToRenderer,
               (const std::vector<FieldGlobalId>& fields),
               (override));
-  MOCK_METHOD(void, SetShouldSuppressKeyboard, (bool), ());
-  MOCK_METHOD(bool, CanShowAutofillUi, (), (const));
-  MOCK_METHOD(void, TriggerReparseInAllFrames, (), ());
 };
 
 }  // namespace
@@ -9817,21 +9814,6 @@
   EXPECT_FALSE(external_delegate_->on_suggestions_returned_seen());
 }
 
-TEST_F(BrowserAutofillManagerTest, SetShouldSuppressKeyboard) {
-  EXPECT_CALL(*autofill_driver_, SetShouldSuppressKeyboard(true));
-  browser_autofill_manager_->SetShouldSuppressKeyboard(true);
-}
-
-TEST_F(BrowserAutofillManagerTest, CanShowAutofillUi) {
-  EXPECT_CALL(*autofill_driver_, CanShowAutofillUi).WillOnce(Return(true));
-  EXPECT_TRUE(browser_autofill_manager_->CanShowAutofillUi());
-}
-
-TEST_F(BrowserAutofillManagerTest, TriggerReparseInAllFrames) {
-  EXPECT_CALL(*autofill_driver_, TriggerReparseInAllFrames);
-  browser_autofill_manager_->TriggerReparseInAllFrames();
-}
-
 // Desktop only tests.
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 class BrowserAutofillManagerTestForVirtualCardOption
diff --git a/components/autofill/core/browser/metrics/autofill_metrics.cc b/components/autofill/core/browser/metrics/autofill_metrics.cc
index 34fccd7..4f46e58 100644
--- a/components/autofill/core/browser/metrics/autofill_metrics.cc
+++ b/components/autofill/core/browser/metrics/autofill_metrics.cc
@@ -2576,12 +2576,19 @@
       had_value_before_filling |= event->had_value_before_filling;
       autofill_skipped_status.insert(event->autofill_skipped_status);
       had_value_after_filling = event->had_value_after_filling;
+      if (was_autofilled == OptionalBoolean::kTrue &&
+          filled_value_was_modified == OptionalBoolean::kUndefined) {
+        // Only switch from unknown to false on the first filling.
+        filled_value_was_modified = OptionalBoolean::kFalse;
+      }
       ++autofill_count;
     }
 
     if (auto* event = absl::get_if<TypingFieldLogEvent>(&log_event)) {
       user_typed_into_field = OptionalBoolean::kTrue;
-      filled_value_was_modified |= was_autofilled;
+      if (was_autofilled == OptionalBoolean::kTrue) {
+        filled_value_was_modified = OptionalBoolean::kTrue;
+      }
       has_value_after_typing = event->has_value_after_typing;
     }
 
@@ -2674,9 +2681,12 @@
   }
 
   if (user_typed_into_field == OptionalBoolean::kTrue) {
-    builder.SetUserTypedIntoField(OptionalBooleanToBool(user_typed_into_field))
-        .SetFilledValueWasModified(
-            OptionalBooleanToBool(filled_value_was_modified));
+    builder.SetUserTypedIntoField(OptionalBooleanToBool(user_typed_into_field));
+  }
+
+  if (filled_value_was_modified != OptionalBoolean::kUndefined) {
+    builder.SetFilledValueWasModified(
+        OptionalBooleanToBool(filled_value_was_modified));
   }
 
   if (had_typed_or_filled_value_at_submission != OptionalBoolean::kUndefined) {
diff --git a/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc b/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
index b94cfb67..7fc309b 100644
--- a/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
+++ b/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
@@ -10599,6 +10599,7 @@
            DenseSet<SkipStatus>{SkipStatus::kNotSkipped}.to_uint64()},
           {UFIT::kWasRefillName, false},
           {UFIT::kHadValueBeforeFillingName, false},
+          {UFIT::kFilledValueWasModifiedName, false},
           {UFIT::kHadTypedOrFilledValueAtSubmissionName, true},
       };
       if (i == 0) {
@@ -10619,7 +10620,7 @@
 
 // Test if we have recorded UKM metrics correctly about field types after
 // parsing the form by the local heuristic prediction.
-TEST_F(AutofillMetricsFromLogEventsTest, AutofillFieldInfoMetrics_FieldType) {
+TEST_F(AutofillMetricsFromLogEventsTest, AutofillFieldInfoMetricsFieldType) {
   FormData form = CreateForm(
       {// Heuristic value will match with Autocomplete attribute.
        CreateField("Last Name", "lastname", "", "text", "family-name"),
@@ -10729,6 +10730,64 @@
   }
 }
 
+// Test if we have recorded FieldInfo UKM metrics correctly after typing in
+// fields without autofilling first.
+TEST_F(AutofillMetricsFromLogEventsTest,
+       AutofillFieldInfoMetricsEditedFieldWithoutFill) {
+  test::FormDescription form_description = {
+      .description_for_logging = "NumberOfAutofilledFields",
+      .fields = {{.role = NAME_FULL,
+                  .value = u"Elvis Aaron Presley",
+                  .is_autofilled = false},
+                 {.role = EMAIL_ADDRESS,
+                  .value = u"buddy@gmail.com",
+                  .is_autofilled = false},
+                 {.role = PHONE_HOME_CITY_AND_NUMBER, .is_autofilled = true}},
+      .unique_renderer_id = test::MakeFormRendererId(),
+      .main_frame_origin =
+          url::Origin::Create(autofill_client_->form_origin())};
+
+  FormData form = GetAndAddSeenForm(form_description);
+
+  base::HistogramTester histogram_tester;
+  // Simulate text input in the first and second fields.
+  SimulateUserChangedTextField(form, form.fields[0]);
+  SimulateUserChangedTextField(form, form.fields[1]);
+
+  SubmitForm(form);
+
+  // Record Autofill2.FieldInfo UKM event at autofill manager reset.
+  autofill_manager().Reset();
+
+  auto entries =
+      test_ukm_recorder_->GetEntriesByName(UkmFieldInfoType::kEntryName);
+  ASSERT_EQ(2u, entries.size());
+
+  for (size_t i = 0; i < entries.size(); ++i) {
+    SCOPED_TRACE(testing::Message() << i);
+    using UFIT = UkmFieldInfoType;
+    const auto* const entry = entries[i];
+
+    std::map<std::string, int64_t> expected = {
+        {UFIT::kFormSessionIdentifierName,
+         AutofillMetrics::FormGlobalIdToHash64Bit(form.global_id())},
+        {UFIT::kFieldSessionIdentifierName,
+         AutofillMetrics::FieldGlobalIdToHash64Bit(form.fields[i].global_id())},
+        {UFIT::kFieldSignatureName,
+         Collapse(CalculateFieldSignatureForField(form.fields[i])).value()},
+        {UFIT::kWasFocusedName, false},
+        {UFIT::kIsFocusableName, true},
+        {UFIT::kUserTypedIntoFieldName, true},
+        {UFIT::kHadTypedOrFilledValueAtSubmissionName, true},
+    };
+
+    EXPECT_EQ(expected.size(), entry->metrics.size());
+    for (const auto& [metric, value] : expected) {
+      test_ukm_recorder_->ExpectEntryMetric(entry, metric, value);
+    }
+  }
+}
+
 // TODO(crbug.com/1352826) Delete this after collecting the metrics.
 struct LaxLocalHeuristicsTestCase {
   test::FormDescription form;
diff --git a/components/autofill/core/browser/metrics/payments/credit_card_save_metrics.h b/components/autofill/core/browser/metrics/payments/credit_card_save_metrics.h
index c4c6062..f11cf47 100644
--- a/components/autofill/core/browser/metrics/payments/credit_card_save_metrics.h
+++ b/components/autofill/core/browser/metrics/payments/credit_card_save_metrics.h
@@ -89,7 +89,7 @@
   // numeric values should never be reused.
 
   kSyncServiceNull = 0,
-  kSyncServicePersistentAuthError = 1,
+  kSyncServicePaused = 1,
   kSyncServiceMissingAutofillWalletDataActiveType = 2,
   kSyncServiceMissingAutofillProfileActiveType = 3,
   // Deprecated: kAccountWalletStorageUploadDisabled = 4,
diff --git a/components/autofill/core/browser/test_autofill_client.cc b/components/autofill/core/browser/test_autofill_client.cc
index c1527d4..2ff4581 100644
--- a/components/autofill/core/browser/test_autofill_client.cc
+++ b/components/autofill/core/browser/test_autofill_client.cc
@@ -302,9 +302,10 @@
 
 void TestAutofillClient::ScanCreditCard(CreditCardScanCallback callback) {}
 
-bool TestAutofillClient::TryToShowFastCheckout(const FormData& form,
-                                               const FormFieldData& field,
-                                               AutofillDriver* driver) {
+bool TestAutofillClient::TryToShowFastCheckout(
+    const FormData& form,
+    const FormFieldData& field,
+    base::WeakPtr<AutofillManager> autofill_manager) {
   return false;
 }
 
diff --git a/components/autofill/core/browser/test_autofill_client.h b/components/autofill/core/browser/test_autofill_client.h
index ba7c8e1..0a1b2eb 100644
--- a/components/autofill/core/browser/test_autofill_client.h
+++ b/components/autofill/core/browser/test_autofill_client.h
@@ -164,9 +164,10 @@
       AddressProfileSavePromptCallback callback) override;
   bool HasCreditCardScanFeature() override;
   void ScanCreditCard(CreditCardScanCallback callback) override;
-  bool TryToShowFastCheckout(const FormData& form,
-                             const FormFieldData& field,
-                             AutofillDriver* driver) override;
+  bool TryToShowFastCheckout(
+      const FormData& form,
+      const FormFieldData& field,
+      base::WeakPtr<AutofillManager> autofill_manager) override;
   void HideFastCheckout(bool allow_further_runs) override;
   bool IsFastCheckoutSupported() override;
   bool IsShowingFastCheckoutUI() override;
diff --git a/components/autofill/core/browser/webdata/contact_info_model_type_controller.cc b/components/autofill/core/browser/webdata/contact_info_model_type_controller.cc
index 7c971c7..9866ad1 100644
--- a/components/autofill/core/browser/webdata/contact_info_model_type_controller.cc
+++ b/components/autofill/core/browser/webdata/contact_info_model_type_controller.cc
@@ -4,9 +4,12 @@
 
 #include "components/autofill/core/browser/webdata/contact_info_model_type_controller.h"
 
+#include "base/check.h"
 #include "base/feature_list.h"
 #include "components/autofill/core/common/autofill_features.h"
+#include "components/sync/base/features.h"
 #include "components/sync/base/model_type.h"
+#include "components/sync/driver/sync_user_settings.h"
 
 namespace autofill {
 
@@ -14,14 +17,35 @@
     std::unique_ptr<syncer::ModelTypeControllerDelegate>
         delegate_for_full_sync_mode,
     std::unique_ptr<syncer::ModelTypeControllerDelegate>
-        delegate_for_transport_mode)
+        delegate_for_transport_mode,
+    syncer::SyncService* sync_service)
     : ModelTypeController(syncer::CONTACT_INFO,
                           std::move(delegate_for_full_sync_mode),
-                          std::move(delegate_for_transport_mode)) {}
+                          std::move(delegate_for_transport_mode)),
+      sync_service_(sync_service) {
+  sync_service_observation_.Observe(sync_service_);
+}
+
+ContactInfoModelTypeController::~ContactInfoModelTypeController() = default;
 
 bool ContactInfoModelTypeController::ShouldRunInTransportOnlyMode() const {
   return base::FeatureList::IsEnabled(
       autofill::features::kAutofillAccountProfilesOnSignIn);
 }
 
+syncer::DataTypeController::PreconditionState
+ContactInfoModelTypeController::GetPreconditionState() const {
+  return !sync_service_->GetUserSettings()->IsUsingExplicitPassphrase() ||
+                 base::FeatureList::IsEnabled(
+                     syncer::
+                         kSyncEnableContactInfoDataTypeForCustomPassphraseUsers)
+             ? PreconditionState::kPreconditionsMet
+             : PreconditionState::kMustStopAndClearData;
+}
+
+void ContactInfoModelTypeController::OnStateChanged(syncer::SyncService* sync) {
+  DCHECK(CalledOnValidThread());
+  sync_service_->DataTypePreconditionChanged(type());
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/webdata/contact_info_model_type_controller.h b/components/autofill/core/browser/webdata/contact_info_model_type_controller.h
index 5c3bf18..95c84fd 100644
--- a/components/autofill/core/browser/webdata/contact_info_model_type_controller.h
+++ b/components/autofill/core/browser/webdata/contact_info_model_type_controller.h
@@ -5,17 +5,24 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_CONTACT_INFO_MODEL_TYPE_CONTROLLER_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_CONTACT_INFO_MODEL_TYPE_CONTROLLER_H_
 
+#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
 #include "components/sync/driver/model_type_controller.h"
+#include "components/sync/driver/sync_service.h"
+#include "components/sync/driver/sync_service_observer.h"
 
 namespace autofill {
 
-class ContactInfoModelTypeController : public syncer::ModelTypeController {
+class ContactInfoModelTypeController : public syncer::ModelTypeController,
+                                       public syncer::SyncServiceObserver {
  public:
   ContactInfoModelTypeController(
       std::unique_ptr<syncer::ModelTypeControllerDelegate>
           delegate_for_full_sync_mode,
       std::unique_ptr<syncer::ModelTypeControllerDelegate>
-          delegate_for_transport_mode);
+          delegate_for_transport_mode,
+      syncer::SyncService* sync_service);
+  ~ContactInfoModelTypeController() override;
 
   ContactInfoModelTypeController(const ContactInfoModelTypeController&) =
       delete;
@@ -24,6 +31,15 @@
 
   // ModelTypeController overrides.
   bool ShouldRunInTransportOnlyMode() const override;
+  PreconditionState GetPreconditionState() const override;
+
+  // SyncServiceObserver overrides.
+  void OnStateChanged(syncer::SyncService* sync) override;
+
+ private:
+  const raw_ptr<syncer::SyncService> sync_service_;
+  base::ScopedObservation<syncer::SyncService, syncer::SyncServiceObserver>
+      sync_service_observation_{this};
 };
 
 }  // namespace autofill
diff --git a/components/browser_sync/sync_api_component_factory_impl.cc b/components/browser_sync/sync_api_component_factory_impl.cc
index 83fe0dd..ce8da7c 100644
--- a/components/browser_sync/sync_api_component_factory_impl.cc
+++ b/components/browser_sync/sync_api_component_factory_impl.cc
@@ -245,7 +245,8 @@
                   db_thread_,
                   base::BindRepeating(
                       &ContactInfoDelegateFromDataService,
-                      base::RetainedRef(web_data_service_on_disk_)))));
+                      base::RetainedRef(web_data_service_on_disk_))),
+              sync_service));
     }
 
     // Wallet data sync is enabled by default. Register unless explicitly
diff --git a/components/browsing_data/content/service_worker_helper.cc b/components/browsing_data/content/service_worker_helper.cc
index 9b8382c..68e794f 100644
--- a/components/browsing_data/content/service_worker_helper.cc
+++ b/components/browsing_data/content/service_worker_helper.cc
@@ -25,7 +25,7 @@
 namespace browsing_data {
 namespace {
 
-void GetAllOriginsInfoForServiceWorkerCallback(
+void OnGotAllStorageKeysInfoForServiceWorker(
     ServiceWorkerHelper::FetchCallback callback,
     const std::vector<StorageUsageInfo>& infos) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -55,7 +55,7 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(!callback.is_null());
   service_worker_context_->GetAllStorageKeysInfo(base::BindOnce(
-      &GetAllOriginsInfoForServiceWorkerCallback, std::move(callback)));
+      &OnGotAllStorageKeysInfoForServiceWorker, std::move(callback)));
 }
 
 void ServiceWorkerHelper::DeleteServiceWorkers(const url::Origin& origin) {
diff --git a/components/password_manager/core/browser/password_manager_util_unittest.cc b/components/password_manager/core/browser/password_manager_util_unittest.cc
index 8ecb6b7..8e82d66 100644
--- a/components/password_manager/core/browser/password_manager_util_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_util_unittest.cc
@@ -236,7 +236,7 @@
               TryToShowFastCheckout,
               (const autofill::FormData&,
                const autofill::FormFieldData&,
-               autofill::AutofillDriver*),
+               base::WeakPtr<autofill::AutofillManager>),
               (override));
   MOCK_METHOD(void, HideFastCheckout, (bool), (override));
   MOCK_METHOD(bool, IsShowingFastCheckoutUI, (), (override));
diff --git a/components/permissions/unused_site_permissions_service.cc b/components/permissions/unused_site_permissions_service.cc
index c57a533..0bdcefc 100644
--- a/components/permissions/unused_site_permissions_service.cc
+++ b/components/permissions/unused_site_permissions_service.cc
@@ -191,6 +191,21 @@
       ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, {});
 }
 
+void UnusedSitePermissionsService::UndoRegrantPermissionsForOrigin(
+    const std::set<ContentSettingsType> permissions,
+    const absl::optional<content_settings::ContentSettingConstraints>
+        constraint,
+    const url::Origin origin) {
+  for (const auto& permission : permissions) {
+    hcsm_->SetContentSettingCustomScope(
+        ContentSettingsPattern::FromURLNoWildcard(origin.GetURL()),
+        ContentSettingsPattern::Wildcard(), permission,
+        ContentSetting::CONTENT_SETTING_DEFAULT);
+  }
+
+  StorePermissionInRevokedPermissionSetting(permissions, constraint, origin);
+}
+
 void UnusedSitePermissionsService::ClearRevokedPermissionsList() {
   ContentSettingsForOneType settings;
   hcsm_->GetSettingsForOneType(
@@ -293,7 +308,7 @@
     ContentSettingsPattern secondary_pattern =
         unused_site_permissions.front().source.secondary_pattern;
 
-    std::list<ContentSettingsType> revoked_permissions;
+    std::set<ContentSettingsType> revoked_permissions;
     for (auto permission_itr = unused_site_permissions.begin();
          permission_itr != unused_site_permissions.end();) {
       const ContentSettingEntry& entry = *permission_itr;
@@ -315,7 +330,7 @@
       if (entry.source.metadata.last_visited < threshold &&
           entry.source.secondary_pattern ==
               ContentSettingsPattern::Wildcard()) {
-        revoked_permissions.push_back(entry.type);
+        revoked_permissions.insert(entry.type);
         hcsm_->SetContentSettingCustomScope(
             entry.source.primary_pattern, entry.source.secondary_pattern,
             entry.type, ContentSetting::CONTENT_SETTING_DEFAULT);
@@ -351,7 +366,7 @@
 }
 
 void UnusedSitePermissionsService::StorePermissionInRevokedPermissionSetting(
-    const std::list<ContentSettingsType> permissions,
+    const std::set<ContentSettingsType> permissions,
     const absl::optional<content_settings::ContentSettingConstraints>
         constraint,
     const url::Origin origin) {
@@ -364,7 +379,7 @@
 }
 
 void UnusedSitePermissionsService::StorePermissionInRevokedPermissionSetting(
-    const std::list<ContentSettingsType> permissions,
+    const std::set<ContentSettingsType> permissions,
     const absl::optional<content_settings::ContentSettingConstraints>
         constraint,
     const ContentSettingsPattern& primary_pattern,
diff --git a/components/permissions/unused_site_permissions_service.h b/components/permissions/unused_site_permissions_service.h
index dadd2a45..ec5a691 100644
--- a/components/permissions/unused_site_permissions_service.h
+++ b/components/permissions/unused_site_permissions_service.h
@@ -87,13 +87,21 @@
   // from revoked permissions list.
   void RegrantPermissionsForOrigin(const url::Origin& origin);
 
+  // Reverse changes made by |RegrantPermissionsForOrigin|. Adds this origin to
+  // the removed permissions list and resets its permissions.
+  void UndoRegrantPermissionsForOrigin(
+      const std::set<ContentSettingsType> permissions,
+      const absl::optional<content_settings::ContentSettingConstraints>
+          constraint,
+      const url::Origin origin);
+
   // Clear the list of revoked permissions so they will no longer be shown to
   // the user. Does not change permissions themselves.
   void ClearRevokedPermissionsList();
 
   // Stores revoked permissions data on HCSM.
   void StorePermissionInRevokedPermissionSetting(
-      const std::list<ContentSettingsType> permissions,
+      const std::set<ContentSettingsType> permissions,
       const absl::optional<content_settings::ContentSettingConstraints>
           constraint,
       const url::Origin origin);
@@ -123,7 +131,7 @@
 
   // Stores revoked permissions data on HCSM.
   void StorePermissionInRevokedPermissionSetting(
-      const std::list<ContentSettingsType> permissions,
+      const std::set<ContentSettingsType> permissions,
       const absl::optional<content_settings::ContentSettingConstraints>
           constraint,
       const ContentSettingsPattern& primary_pattern,
diff --git a/components/permissions/unused_site_permissions_service_unittest.cc b/components/permissions/unused_site_permissions_service_unittest.cc
index 85ec54e..d89b16e 100644
--- a/components/permissions/unused_site_permissions_service_unittest.cc
+++ b/components/permissions/unused_site_permissions_service_unittest.cc
@@ -372,6 +372,89 @@
   // Check if the permissions of url1 is regranted.
   EXPECT_EQ(ContentSetting::CONTENT_SETTING_ALLOW,
             hcsm()->GetContentSetting(GURL(url1), GURL(url1), type));
+
+  // Undoing the changes should add url1 back to the list of revoked permissions
+  // and reset its permissions.
+  service()->UndoRegrantPermissionsForOrigin({type}, absl::nullopt,
+                                             url::Origin::Create(GURL(url1)));
+
+  hcsm()->GetSettingsForOneType(
+      ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
+      &revoked_permissions_list);
+  EXPECT_EQ(2U, revoked_permissions_list.size());
+  EXPECT_EQ(ContentSetting::CONTENT_SETTING_ASK,
+            hcsm()->GetContentSetting(GURL(url1), GURL(url1), type));
+}
+
+TEST_F(UnusedSitePermissionsServiceTest, RegrantPreventsAutorevoke) {
+  base::test::ScopedFeatureList scoped_feature;
+  scoped_feature.InitAndEnableFeature(
+      content_settings::features::kSafetyCheckUnusedSitePermissions);
+
+  const GURL url1 = GURL("https://example1.com:443");
+  const content_settings::ContentSettingConstraints constraint{
+      .track_last_visit_for_autoexpiration = true};
+
+  hcsm()->SetContentSettingDefaultScope(
+      url1, url1, ContentSettingsType::GEOLOCATION,
+      ContentSetting::CONTENT_SETTING_ALLOW, constraint);
+  EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 0u);
+
+  // Travel 70 days through time so that the granted permission is revoked.
+  clock()->Advance(base::Days(70));
+  service()->UpdateUnusedPermissionsForTesting();
+  EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 1u);
+
+  // After regranting permissions they are not revoked again even after >60 days
+  // pass.
+  service()->RegrantPermissionsForOrigin(url::Origin::Create(url1));
+  EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 0u);
+
+  clock()->Advance(base::Days(70));
+  service()->UpdateUnusedPermissionsForTesting();
+  EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 0u);
+}
+
+TEST_F(UnusedSitePermissionsServiceTest, UndoRegrantPermissionsForOrigin) {
+  base::test::ScopedFeatureList scoped_feature;
+  scoped_feature.InitAndEnableFeature(
+      content_settings::features::kSafetyCheckUnusedSitePermissions);
+
+  const GURL url1 = GURL("https://example1.com:443");
+  const ContentSettingsType type = ContentSettingsType::GEOLOCATION;
+  const content_settings::ContentSettingConstraints constraint{
+      .track_last_visit_for_autoexpiration = true};
+
+  hcsm()->SetContentSettingDefaultScope(
+      url1, url1, type, ContentSetting::CONTENT_SETTING_ALLOW, constraint);
+  EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 0u);
+
+  // Travel 70 days through time so that the granted permission is revoked.
+  clock()->Advance(base::Days(70));
+  service()->UpdateUnusedPermissionsForTesting();
+  EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 1u);
+  const ContentSettingPatternSource revoked_permission =
+      GetRevokedUnusedPermissions(hcsm())[0];
+
+  // Permission remains revoked after regrant and undo.
+  content_settings::ContentSettingConstraints expiration_constraint = {
+      .expiration = revoked_permission.metadata.expiration};
+  service()->RegrantPermissionsForOrigin(url::Origin::Create(url1));
+  service()->UndoRegrantPermissionsForOrigin({type}, expiration_constraint,
+                                             url::Origin::Create(url1));
+  EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 1u);
+
+  // Revoked permission is cleaned up after >30 days.
+  clock()->Advance(base::Days(40));
+  service()->UpdateUnusedPermissionsForTesting();
+  EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 0u);
+
+  // If that permission is granted again, it will still be autorevoked.
+  hcsm()->SetContentSettingDefaultScope(
+      url1, url1, type, ContentSetting::CONTENT_SETTING_ALLOW, constraint);
+  clock()->Advance(base::Days(70));
+  service()->UpdateUnusedPermissionsForTesting();
+  EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 1u);
 }
 
 TEST_F(UnusedSitePermissionsServiceTest, NotRevokeNotificationPermission) {
diff --git a/components/policy/resources/templates/policies.yaml b/components/policy/resources/templates/policies.yaml
index b99a981..e7bcb11 100644
--- a/components/policy/resources/templates/policies.yaml
+++ b/components/policy/resources/templates/policies.yaml
@@ -1069,6 +1069,7 @@
   1068: WindowManagementAllowedForUrls
   1069: WindowManagementBlockedForUrls
   1070: OutOfProcessSystemDnsResolutionEnabled
+  1071: ExtensionUnpublishedAvailability
 atomic_groups:
   1: Homepage
   2: RemoteAccess
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionUnpublishedAvailability.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionUnpublishedAvailability.yaml
new file mode 100644
index 0000000..4c9a84a
--- /dev/null
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionUnpublishedAvailability.yaml
@@ -0,0 +1,43 @@
+owners:
+- anunoy@chromium.org
+- file://components/policy/OWNERS
+caption: Control availability of extensions unpublished on the Chrome Web Store.
+desc: |-
+  If this policy is enabled, extensions that are unpublished on the Chrome Web
+  Store will be disabled on <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>.
+  This policy will ignore developer-mode, command-line installed and other
+  extensions that are not installed from Chrome Web Store (eg. self-hosting).
+
+  If the policy is set to <ph name="ALLOW_UNPUBLISHED">AllowUnpublished</ph> (0) or not set, unpublished extensions are allowed.
+  If the policy is set to <ph name="DISABLE_UNPUBLISHED_EXCEPT_FORCED_OR_VERSION_PINNED">DisableUnpublishedExceptForcedOrVersionPinned</ph> (1), extensions that are unpublished on the Chrome Wesb Store will be disabled, but not if the extension is force-installed or version-pinned by enterprise policy (See Note).
+  If the policy is set to <ph name="DISABLE_UNPUBLISHED">DisableUnpublished</ph> (2), extensions that are unpublished on the Chrome Web Store will be disabled, even if the extension is force-installed or version-pinned by enterprise policy (See Note).
+
+  Note: Force-installation and version-pinning are configured using the <ph name="EXTENSION_INSTALL_FORCELIST_POLICY_NAME">ExtensionInstallForcelist</ph> and
+  <ph name="EXTENSION_SETTINGS_POLICY_NAME">ExtensionSettings</ph> policies.
+
+future_on:
+- chrome.*
+- chrome_os
+features:
+  dynamic_refresh: true
+  per_profile: true
+type: int-enum
+schema:
+   type: integer
+   enum:
+   - 0
+   - 1
+   - 2
+items:
+- caption: Allow unpublished extensions
+  name: AllowUnpublished
+  value: 0
+- caption: Disable unpublished extensions except force-installed or version-pinned
+  name: DisableUnpublishedExceptForcedOrVersionPinned
+  value: 1
+- caption: Disable unpublished extensions including force-installed or version-pinned
+  name: DisableUnpublished
+  value: 2
+default: 0
+example_value: 1
+tags: []
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/policy_atomic_groups.yaml b/components/policy/resources/templates/policy_definitions/Extensions/policy_atomic_groups.yaml
index 7246e0fc..67980cb 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/policy_atomic_groups.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/policy_atomic_groups.yaml
@@ -9,3 +9,4 @@
   - ExtensionAllowInsecureUpdates
   - ExtensionSettings
   - ExtensionManifestV2Availability
+  - ExtensionUnpublishedAvailability
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml
index 0850325..69bbebbd 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml
@@ -61,6 +61,6 @@
   - stateful
   type: string
 supported_on:
-- chrome_os:110-
+- chrome_os:112-
 tags: []
 type: string-enum
diff --git a/components/services/app_service/public/cpp/intent_util.cc b/components/services/app_service/public/cpp/intent_util.cc
index f47a62f..8be4dc8 100644
--- a/components/services/app_service/public/cpp/intent_util.cc
+++ b/components/services/app_service/public/cpp/intent_util.cc
@@ -122,6 +122,19 @@
   return intent;
 }
 
+apps::IntentPtr MakeShareIntent(
+    const std::vector<GURL>& filesystem_urls,
+    const std::vector<std::string>& mime_types,
+    const std::vector<std::string>& dlp_source_urls) {
+  auto intent = MakeShareIntent(filesystem_urls, mime_types);
+
+  DCHECK_EQ(filesystem_urls.size(), dlp_source_urls.size());
+  for (size_t i = 0; i < filesystem_urls.size(); i++) {
+    intent->files[i]->dlp_source_url = dlp_source_urls[i];
+  }
+  return intent;
+}
+
 apps::IntentPtr MakeEditIntent(const GURL& filesystem_url,
                                const std::string& mime_type) {
   auto intent = std::make_unique<apps::Intent>(kIntentActionEdit);
diff --git a/components/services/app_service/public/cpp/intent_util.h b/components/services/app_service/public/cpp/intent_util.h
index 8d36e52..0fcf4cf7 100644
--- a/components/services/app_service/public/cpp/intent_util.h
+++ b/components/services/app_service/public/cpp/intent_util.h
@@ -63,6 +63,12 @@
 apps::IntentPtr MakeShareIntent(const std::string& text,
                                 const std::string& title);
 
+// Creates an intent for sharing |filesystem_urls|, with |dlpSourceUrls|.
+apps::IntentPtr MakeShareIntent(
+    const std::vector<GURL>& filesystem_urls,
+    const std::vector<std::string>& mime_types,
+    const std::vector<std::string>& dlp_source_urls);
+
 // Create an edit intent for the file with a given |filesystem_url| and
 // |mime_type|.
 apps::IntentPtr MakeEditIntent(const GURL& filesystem_url,
diff --git a/components/services/app_service/public/cpp/intent_util_unittest.cc b/components/services/app_service/public/cpp/intent_util_unittest.cc
index 687b4e8..17079dfb 100644
--- a/components/services/app_service/public/cpp/intent_util_unittest.cc
+++ b/components/services/app_service/public/cpp/intent_util_unittest.cc
@@ -704,6 +704,23 @@
   EXPECT_TRUE(intent->MatchFilter(filter));
 }
 
+TEST_F(IntentUtilTest, FileWithDlpSourceUrls) {
+  const std::string mime_type = "image/jpeg";
+  const GURL file_url = GURL("https://www.google.com/");
+  const std::string dlp_source_url = "https://www.example.com/";
+
+  auto filter = apps_util::MakeIntentFilterForSend(mime_type);
+  const std::vector<GURL> urls{file_url};
+  const std::vector<std::string> mime_types{mime_type};
+  const std::vector<std::string> dlp_source_urls{dlp_source_url};
+
+  auto intent = apps_util::MakeShareIntent(urls, mime_types, dlp_source_urls);
+  ASSERT_EQ(1u, intent->files.size());
+  EXPECT_EQ(file_url, intent->files[0]->url);
+  EXPECT_EQ(dlp_source_url, intent->files[0]->dlp_source_url);
+  EXPECT_TRUE(intent->MatchFilter(filter));
+}
+
 TEST_F(IntentUtilTest, TextMatch) {
   std::string mime_type1 = "text/plain";
   std::string mime_type2 = "image/jpeg";
diff --git a/components/sync/base/features.cc b/components/sync/base/features.cc
index f9eac4e..396fb04 100644
--- a/components/sync/base/features.cc
+++ b/components/sync/base/features.cc
@@ -118,6 +118,10 @@
              "SyncEnableContactInfoDataType",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kSyncEnableContactInfoDataTypeForCustomPassphraseUsers,
+             "kSyncEnableContactInfoDataTypeForCustomPassphraseUsers",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kSyncEnforceBookmarksCountLimit,
              "SyncEnforceBookmarksCountLimit",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/components/sync/base/features.h b/components/sync/base/features.h
index 509c47e..ddbdc7ec 100644
--- a/components/sync/base/features.h
+++ b/components/sync/base/features.h
@@ -155,6 +155,8 @@
 
 BASE_DECLARE_FEATURE(kSyncEnableContactInfoDataType);
 
+BASE_DECLARE_FEATURE(kSyncEnableContactInfoDataTypeForCustomPassphraseUsers);
+
 // If enabled, issues error and disables bookmarks sync when limit is crossed.
 BASE_DECLARE_FEATURE(kSyncEnforceBookmarksCountLimit);
 
diff --git a/components/viz/service/display_embedder/image_context_impl.cc b/components/viz/service/display_embedder/image_context_impl.cc
index cc077bc..ac9e782 100644
--- a/components/viz/service/display_embedder/image_context_impl.cc
+++ b/components/viz/service/display_embedder/image_context_impl.cc
@@ -325,9 +325,8 @@
   // we need check the texture image state, and bind the image to the
   // texture if necessary.
   auto* texture = gpu::gles2::Texture::CheckedCast(texture_base);
-  gpu::gles2::Texture::ImageState image_state;
-  auto* image = texture->GetLevelImage(GL_TEXTURE_2D, 0, &image_state);
-  if (image && image_state == gpu::gles2::Texture::UNBOUND) {
+  if (texture->HasUnboundLevelImage(GL_TEXTURE_2D, 0)) {
+    auto* image = texture->GetLevelImage(GL_TEXTURE_2D, 0);
     glBindTexture(texture_base->target(), texture_base->service_id());
     if (!image->BindTexImage(texture_base->target())) {
       LOG(ERROR) << "Failed to bind a gl image to texture.";
diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc
index a0d021a..2d6ac5d 100644
--- a/content/browser/renderer_host/media/media_stream_manager.cc
+++ b/content/browser/renderer_host/media/media_stream_manager.cc
@@ -3094,10 +3094,8 @@
   // while the current task was hopping between threads/queues, an event will
   // be fired by the CaptureHandleManager.
   if (video_device.has_value()) {
-    MaybeStartTrackingCaptureHandleConfig(
-        label, video_device.value(),
-        GlobalRenderFrameHostId(request->requesting_process_id,
-                                request->requesting_frame_id));
+    MaybeStartTrackingCaptureHandleConfig(label, video_device.value(),
+                                          *request);
   }
 }
 
@@ -3156,13 +3154,12 @@
 void MediaStreamManager::FinalizeChangeDevice(const std::string& label,
                                               DeviceRequest* request) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK(request);
 
   request->FinalizeChangeDevice(label);
 
-  MaybeUpdateTrackedCaptureHandleConfigs(
-      label, request->stream_devices_set,
-      GlobalRenderFrameHostId(request->requesting_process_id,
-                              request->requesting_frame_id));
+  MaybeUpdateTrackedCaptureHandleConfigs(label, request->stream_devices_set,
+                                         *request);
 }
 
 void MediaStreamManager::FinalizeMediaAccessRequest(
@@ -4376,7 +4373,7 @@
 void MediaStreamManager::MaybeStartTrackingCaptureHandleConfig(
     const std::string& label,
     const MediaStreamDevice& captured_device,
-    GlobalRenderFrameHostId capturer) {
+    DeviceRequest& request) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   if (!blink::IsVideoInputMediaType(captured_device.type) ||
@@ -4384,10 +4381,8 @@
     return;
   }
 
-  DeviceRequest* request = FindRequest(label);
-  if (!request) {
-    return;
-  }
+  const GlobalRenderFrameHostId capturer(request.requesting_process_id,
+                                         request.requesting_frame_id);
 
   // It is safe to bind base::Unretained(this) because MediaStreamManager is
   // owned by BrowserMainLoop.
@@ -4399,7 +4394,7 @@
                      base::Unretained(&capture_handle_manager_), label,
                      captured_device, capturer,
                      base::BindPostTask(GetIOThreadTaskRunner({}),
-                                        request->OnCaptureHandleChangeCb())));
+                                        request.OnCaptureHandleChangeCb())));
 }
 
 void MediaStreamManager::MaybeStopTrackingCaptureHandleConfig(
@@ -4424,14 +4419,12 @@
 void MediaStreamManager::MaybeUpdateTrackedCaptureHandleConfigs(
     const std::string& label,
     const blink::mojom::StreamDevicesSet& new_devices_set,
-    GlobalRenderFrameHostId capturer) {
+    DeviceRequest& request) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK_EQ(1u, new_devices_set.stream_devices.size());
 
-  DeviceRequest* request = FindRequest(label);
-  if (!request) {
-    return;
-  }
+  const GlobalRenderFrameHostId capturer(request.requesting_process_id,
+                                         request.requesting_frame_id);
 
   const blink::mojom::StreamDevices& new_devices =
       *new_devices_set.stream_devices[0];
@@ -4455,7 +4448,7 @@
                      base::Unretained(&capture_handle_manager_), label,
                      std::move(filtered_new_devices_set), capturer,
                      base::BindPostTask(GetIOThreadTaskRunner({}),
-                                        request->OnCaptureHandleChangeCb())));
+                                        request.OnCaptureHandleChangeCb())));
 }
 
 bool MediaStreamManager::ShouldUseFakeUIProxy(
diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h
index 0aaecc2c..fc2407b 100644
--- a/content/browser/renderer_host/media/media_stream_manager.h
+++ b/content/browser/renderer_host/media/media_stream_manager.h
@@ -738,7 +738,7 @@
   void MaybeStartTrackingCaptureHandleConfig(
       const std::string& label,
       const blink::MediaStreamDevice& captured_device,
-      GlobalRenderFrameHostId capturer);
+      DeviceRequest& request);
 
   // Stop tracking capture-handle changes for tab-capture.
   void MaybeStopTrackingCaptureHandleConfig(
@@ -749,7 +749,7 @@
   void MaybeUpdateTrackedCaptureHandleConfigs(
       const std::string& label,
       const blink::mojom::StreamDevicesSet& new_stream_devices_set,
-      GlobalRenderFrameHostId capturer);
+      DeviceRequest& request);
 
   bool ShouldUseFakeUIProxy(blink::mojom::MediaStreamType stream_type) const;
 
diff --git a/content/browser/renderer_host/page_lifecycle_state_manager.cc b/content/browser/renderer_host/page_lifecycle_state_manager.cc
index 612aff3..9b24c54 100644
--- a/content/browser/renderer_host/page_lifecycle_state_manager.cc
+++ b/content/browser/renderer_host/page_lifecycle_state_manager.cc
@@ -15,10 +15,12 @@
 #include "services/service_manager/public/cpp/interface_provider.h"
 
 namespace {
-// ASAN builds are slow and we see flakes caused by reaching this timeout.
+// ASAN builds are slow and we see flakes caused by reaching this timeout. 6s
+// was not enough to stop the flakes. Trying 12s just to ensure that there isn't
+// something else going on that we don't understand.
 // See https://crbug.com/1224355.
 #if defined(ADDRESS_SANITIZER)
-constexpr base::TimeDelta kBackForwardCacheTimeout = base::Seconds(6);
+constexpr base::TimeDelta kBackForwardCacheTimeout = base::Seconds(12);
 #else
 constexpr base::TimeDelta kBackForwardCacheTimeout = base::Seconds(3);
 #endif
diff --git a/content/browser/utility_process_host.h b/content/browser/utility_process_host.h
index c0abba24..64ca6bc 100644
--- a/content/browser/utility_process_host.h
+++ b/content/browser/utility_process_host.h
@@ -77,6 +77,11 @@
     virtual void OnProcessCrashed() {}
   };
 
+  // This class is self-owned. It must be instantiated using new, and shouldn't
+  // be deleted manually.
+  // TODO(https://crbug.com/1411101): Make it clearer the caller of the
+  // constructor do not own memory. A static method to create them + private
+  // constructor could be better.
   UtilityProcessHost();
   explicit UtilityProcessHost(std::unique_ptr<Client> client);
 
diff --git a/content/browser/utility_process_host_browsertest.cc b/content/browser/utility_process_host_browsertest.cc
index 96acba8c..b5d7376 100644
--- a/content/browser/utility_process_host_browsertest.cc
+++ b/content/browser/utility_process_host_browsertest.cc
@@ -65,11 +65,16 @@
     DCHECK_CURRENTLY_ON(BrowserThread::UI);
     BrowserChildProcessObserver::Add(this);
 
-    host_ = new UtilityProcessHost();
+    host_ = new UtilityProcessHost();  // Owned by a global list.
     host_->SetName(u"TestProcess");
     host_->SetMetricsName(kTestProcessName);
   }
 
+  void TearDownOnMainThread() override {
+    // `host_` is about to be deleted during BrowserMainRunnerImpl::Shutdown().
+    host_ = nullptr;
+  }
+
   void SetExpectFailLaunch() {
     DCHECK_CURRENTLY_ON(BrowserThread::UI);
     expect_failed_launch_ = true;
diff --git a/docs/enterprise/add_new_policy.md b/docs/enterprise/add_new_policy.md
index 133d13c..5698f3c 100644
--- a/docs/enterprise/add_new_policy.md
+++ b/docs/enterprise/add_new_policy.md
@@ -306,10 +306,15 @@
 - Make sure the policy metadata is up-to-date, in particular `supported_on`, and
 the feature flags.
 - In general, don't change policy semantics in a way that is incompatible
-(as determined by user/admin-visible behavior) with previous semantics. **In
-particular, consider that existing policy deployments may affect both old and
-new browser versions, and both should behave according to the admin's
-intentions**.
+(as determined by user/admin-visible behavior) with previous semantics.
+    - **In particular, consider that existing policy deployments may affect**
+    **both old and new browser versions, and both should behave according to**
+    **the admin's intentions.**
+    - **Do not modify the behavior when the policy is not set**. To be more
+    specific: values `default`, `default_for_enterprise_users` and
+    `default_policy_level` must (likely) never change after the launch. Contact
+    cros-policy-muc-eng@google.com for guidance if you need to make such
+    changes.
 - **An important pitfall is that adding an additional allowed
 value to an enum policy may cause compatibility issues.** Specifically, an
 administrator may use the new policy value, which makes older Chrome versions
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 749360df..cb09570 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1819,6 +1819,7 @@
   PASSWORDSPRIVATE_GETCREDENTIALSWITHREUSEDPASSWORD = 1756,
   OS_TELEMETRY_GETAUDIOINFO = 1757,
   OS_TELEMETRY_GETMARKETINGINFO = 1758,
+  OS_TELEMETRY_GETUSBBUSINFO = 1759,
   // Last entry: Add new entries above, then run:
   // tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc
index 9724736..d9aaa7b 100644
--- a/extensions/browser/extension_prefs.cc
+++ b/extensions/browser/extension_prefs.cc
@@ -2227,6 +2227,8 @@
 #endif
 
   registry->RegisterBooleanPref(pref_names::kBlockExternalExtensions, false);
+  registry->RegisterIntegerPref(pref_names::kExtensionUnpublishedAvailability,
+                                0);
 }
 
 template <class ExtensionIdContainer>
diff --git a/extensions/browser/pref_names.cc b/extensions/browser/pref_names.cc
index 59c7f809..d0b2a43 100644
--- a/extensions/browser/pref_names.cc
+++ b/extensions/browser/pref_names.cc
@@ -35,6 +35,8 @@
 const char kBlockExternalExtensions[] = "extensions.block_external_extensions";
 const char kExtensions[] = "extensions.settings";
 const char kExtensionManagement[] = "extensions.management";
+const char kExtensionUnpublishedAvailability[] =
+    "extensions.unpublished_availability";
 const char kInstallAllowList[] = "extensions.install.allowlist";
 const char kInstallDenyList[] = "extensions.install.denylist";
 const char kInstallForceList[] = "extensions.install.forcelist";
diff --git a/extensions/browser/pref_names.h b/extensions/browser/pref_names.h
index 2ea0bec..7aa07123 100644
--- a/extensions/browser/pref_names.h
+++ b/extensions/browser/pref_names.h
@@ -52,6 +52,11 @@
 // policy.
 extern const char kExtensionManagement[];
 
+// An integer that indicates the availability of extensions that are unpublished
+// on the Chrome Web Store. More details can be found at
+// ExtensionUnpublishedAvailability.yaml
+extern const char kExtensionUnpublishedAvailability[];
+
 // Policy that indicates whether CRX2 extension updates are allowed.
 extern const char kInsecureExtensionUpdatesEnabled[];
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 07d6a4d..21e8ae0 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -10494,9 +10494,8 @@
                                               GLuint texture_unit) {
   // Image is already in use if texture is attached to a framebuffer.
   if (texture && !texture->IsAttachedToFramebuffer()) {
-    Texture::ImageState old_image_state;
-    gl::GLImage* image = texture->GetLevelImage(textarget, 0, &old_image_state);
-    if (image && old_image_state == Texture::UNBOUND) {
+    if (texture->HasUnboundLevelImage(textarget, 0)) {
+      gl::GLImage* image = texture->GetLevelImage(textarget, 0);
       UMA_HISTOGRAM_BOOLEAN(
           "GPU.GLES2DecoderImplLazyBindingCheck.WasBindNecessary", true);
 
@@ -10507,7 +10506,7 @@
       api()->glBindTextureFn(textarget, texture->service_id());
       bool rv = image->BindTexImage(textarget);
       DCHECK(rv) << "BindTexImage() failed";
-      texture->SetLevelImageState(textarget, 0, Texture::BOUND);
+      texture->MarkLevelImageBound(textarget, 0);
       if (!texture_unit) {
         RestoreCurrentTextureBindings(&state_, textarget,
                                       state_.active_texture_unit);
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
index 2cbce8e..ca6a302 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
@@ -282,14 +282,12 @@
   auto* validating_texture =
       static_cast<ValidatingAbstractTextureImpl*>(abstract_texture.get());
   TextureRef* texture_ref = validating_texture->GetTextureRefForTesting();
-  Texture::ImageState state;
-  EXPECT_EQ(texture_ref->texture()->GetLevelImage(target, 0, &state),
-            image.get());
+  EXPECT_EQ(texture_ref->texture()->GetLevelImage(target, 0), image.get());
 
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
-  EXPECT_EQ(state, Texture::ImageState::UNBOUND);
+  EXPECT_TRUE(texture_ref->texture()->HasUnboundLevelImage(target, 0));
 #else
-  EXPECT_EQ(state, Texture::ImageState::BOUND);
+  EXPECT_FALSE(texture_ref->texture()->HasUnboundLevelImage(target, 0));
 #endif
 
   EXPECT_CALL(*gl_, DeleteTextures(1, _)).Times(1).RetiresOnSaturation();
diff --git a/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory_unittest.cc
index ab1fb6e..b3015feb 100644
--- a/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory_unittest.cc
@@ -5,6 +5,7 @@
 #include "gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.h"
 
 #include "base/functional/callback_helpers.h"
+#include "build/build_config.h"
 #include "cc/test/pixel_comparator.h"
 #include "cc/test/pixel_test_utils.h"
 #include "components/viz/common/resources/resource_format_utils.h"
@@ -59,11 +60,19 @@
   return pixmaps;
 }
 
-class WrappedSkImageBackingFactoryTest
+// WrappedSkImageBackingFactoryTest is failing on Android emulator bots:
+// https://crbug.com/1411266
+#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86_FAMILY)
+#define MAYBE_WrappedSkImageBackingFactoryTest \
+  DISABLED_WrappedSkImageBackingFactoryTest
+#else
+#define MAYBE_WrappedSkImageBackingFactoryTest WrappedSkImageBackingFactoryTest
+#endif  // BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86_FAMILY)
+class MAYBE_WrappedSkImageBackingFactoryTest
     : public testing::TestWithParam<viz::SharedImageFormat> {
  public:
-  WrappedSkImageBackingFactoryTest() = default;
-  ~WrappedSkImageBackingFactoryTest() override {
+  MAYBE_WrappedSkImageBackingFactoryTest() = default;
+  ~MAYBE_WrappedSkImageBackingFactoryTest() override {
     // |context_state_| must be destroyed while current.
     context_state_->MakeCurrent(surface_.get(), /*needs_gl=*/true);
   }
@@ -111,7 +120,7 @@
 };
 
 // Verify creation and Skia access works as expected.
-TEST_P(WrappedSkImageBackingFactoryTest, Basic) {
+TEST_P(MAYBE_WrappedSkImageBackingFactoryTest, Basic) {
   auto format = GetFormat();
   auto mailbox = Mailbox::GenerateForSharedImage();
   gfx::Size size(100, 100);
@@ -172,7 +181,7 @@
 }
 
 // Verify that pixel upload works as expected.
-TEST_P(WrappedSkImageBackingFactoryTest, Upload) {
+TEST_P(MAYBE_WrappedSkImageBackingFactoryTest, Upload) {
   auto format = GetFormat();
   auto mailbox = Mailbox::GenerateForSharedImage();
   gfx::Size size(100, 100);
@@ -254,7 +263,7 @@
                       viz::MultiPlaneFormat::kYVU_420);
 
 INSTANTIATE_TEST_SUITE_P(,
-                         WrappedSkImageBackingFactoryTest,
+                         MAYBE_WrappedSkImageBackingFactoryTest,
                          kFormats,
                          TestParamToString);
 
diff --git a/gpu/command_buffer/service/texture_manager.cc b/gpu/command_buffer/service/texture_manager.cc
index f54f4764..75d86b9b 100644
--- a/gpu/command_buffer/service/texture_manager.cc
+++ b/gpu/command_buffer/service/texture_manager.cc
@@ -735,19 +735,7 @@
   }
 }
 
-Texture::LevelInfo::LevelInfo()
-    : target(0),
-      level(-1),
-      internal_format(0),
-      width(0),
-      height(0),
-      depth(0),
-      border(0),
-      format(0),
-      type(0),
-      image_state(UNBOUND),
-      estimated_size(0),
-      internal_workaround(false) {}
+Texture::LevelInfo::LevelInfo() = default;
 
 Texture::LevelInfo::LevelInfo(const LevelInfo& rhs)
     : cleared_rect(rhs.cleared_rect),
@@ -761,9 +749,9 @@
       format(rhs.format),
       type(rhs.type),
       image(rhs.image),
-      image_state(rhs.image_state),
       estimated_size(rhs.estimated_size),
-      internal_workaround(rhs.internal_workaround) {}
+      internal_workaround(rhs.internal_workaround),
+      image_state(rhs.image_state) {}
 
 Texture::LevelInfo::~LevelInfo() = default;
 
@@ -1924,7 +1912,7 @@
 }
 #endif
 
-void Texture::SetLevelImageState(GLenum target, GLint level, ImageState state) {
+void Texture::MarkLevelImageBound(GLenum target, GLint level) {
   DCHECK_GE(level, 0);
   size_t face_index = GLES2Util::GLTargetToFaceIndex(target);
   DCHECK_LT(face_index, face_infos_.size());
@@ -1933,7 +1921,7 @@
   Texture::LevelInfo& info = face_infos_[face_index].level_infos[level];
   DCHECK_EQ(info.target, target);
   DCHECK_EQ(info.level, level);
-  info.image_state = state;
+  info.image_state = ImageState::BOUND;
 }
 
 const Texture::LevelInfo* Texture::GetLevelInfo(GLint target,
@@ -1953,20 +1941,25 @@
   return nullptr;
 }
 
-gl::GLImage* Texture::GetLevelImage(GLint target,
-                                    GLint level,
-                                    ImageState* state) const {
+gl::GLImage* Texture::GetLevelImage(GLint target, GLint level) const {
   const LevelInfo* info = GetLevelInfo(target, level);
   if (!info)
     return nullptr;
 
-  if (state)
-    *state = info->image_state;
   return info->image.get();
 }
 
-gl::GLImage* Texture::GetLevelImage(GLint target, GLint level) const {
-  return GetLevelImage(target, level, nullptr);
+bool Texture::HasUnboundLevelImage(GLint target, GLint level) const {
+  const LevelInfo* info = GetLevelInfo(target, level);
+  if (!info) {
+    return false;
+  }
+
+  if (!info->image.get()) {
+    return false;
+  }
+
+  return info->image_state == ImageState::UNBOUND;
 }
 
 void Texture::DumpLevelMemory(base::trace_event::ProcessMemoryDump* pmd,
diff --git a/gpu/command_buffer/service/texture_manager.h b/gpu/command_buffer/service/texture_manager.h
index c7c7b6a2..4557611 100644
--- a/gpu/command_buffer/service/texture_manager.h
+++ b/gpu/command_buffer/service/texture_manager.h
@@ -191,9 +191,15 @@
     GLenum format = 0;
     GLenum type = 0;
     scoped_refptr<gl::GLImage> image;
-    ImageState image_state = UNBOUND;
     uint32_t estimated_size = 0;
     bool internal_workaround = false;
+
+   private:
+    friend class Texture;
+
+    // Nothing outside of Texture should directly access the binding state of
+    // the image; clients can use Texture::HasUnboundLevelImage().
+    ImageState image_state = UNBOUND;
   };
 
   explicit Texture(GLuint service_id);
@@ -332,8 +338,8 @@
   void BindToServiceId(GLuint service_id);
 #endif
 
-  // Set the ImageState for the image bound to the given level.
-  void SetLevelImageState(GLenum target, GLint level, ImageState state);
+  // Marks the image for the given level as bound.
+  void MarkLevelImageBound(GLenum target, GLint level);
 
   bool CompatibleWithSamplerUniformType(
       GLenum type,
@@ -341,11 +347,12 @@
 
   // Get the image associated with a particular level. Returns NULL if level
   // does not exist.
-  gl::GLImage* GetLevelImage(GLint target,
-                             GLint level,
-                             ImageState* state) const;
   gl::GLImage* GetLevelImage(GLint target, GLint level) const;
 
+  // Returns true iff (a) there is an image associated with the particular
+  // level, and (b) the image is unbound.
+  bool HasUnboundLevelImage(GLint target, GLint level) const;
+
   bool HasImages() const {
     return has_images_;
   }
diff --git a/gpu/command_buffer/service/texture_manager_unittest.cc b/gpu/command_buffer/service/texture_manager_unittest.cc
index b5caa32..ba7700a 100644
--- a/gpu/command_buffer/service/texture_manager_unittest.cc
+++ b/gpu/command_buffer/service/texture_manager_unittest.cc
@@ -1691,25 +1691,25 @@
   EXPECT_TRUE(texture->GetLevelImage(GL_TEXTURE_2D, 1) == nullptr);
 }
 
-TEST_F(TextureTest, SetLevelImageState) {
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
+TEST_F(TextureTest, MarkLevelImageBound) {
   manager_->SetTarget(texture_ref_.get(), GL_TEXTURE_2D);
   manager_->SetLevelInfo(texture_ref_.get(), GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 1,
                          0, GL_RGBA, GL_UNSIGNED_BYTE, gfx::Rect(2, 2));
   Texture* texture = texture_ref_->texture();
-  // Set image, initially BOUND.
+  // Set image, initially unbound.
   scoped_refptr<gl::GLImage> image(new gl::GLImageStub);
-  manager_->SetBoundLevelImage(texture_ref_.get(), GL_TEXTURE_2D, 0,
-                               image.get());
-  Texture::ImageState state;
-  texture->GetLevelImage(GL_TEXTURE_2D, 0, &state);
-  EXPECT_EQ(state, Texture::BOUND);
-  // Change the state.
-  texture->SetLevelImageState(GL_TEXTURE_2D, 0, Texture::COPIED);
-  texture->GetLevelImage(GL_TEXTURE_2D, 0, &state);
-  EXPECT_EQ(state, Texture::COPIED);
+  manager_->SetUnboundLevelImage(texture_ref_.get(), GL_TEXTURE_2D, 0,
+                                 image.get());
+  EXPECT_TRUE(texture->HasUnboundLevelImage(GL_TEXTURE_2D, 0));
+  // Mark the image as bound and verify that the state updates.
+  texture->MarkLevelImageBound(GL_TEXTURE_2D, 0);
+  EXPECT_FALSE(texture->HasUnboundLevelImage(GL_TEXTURE_2D, 0));
 }
 #endif
 
+#endif
+
 #if BUILDFLAG(IS_ANDROID)
 TEST_F(TextureTest, SetStreamTextureImageServiceID) {
   manager_->SetTarget(texture_ref_.get(), GL_TEXTURE_EXTERNAL_OES);
diff --git a/gpu/command_buffer/service/validating_abstract_texture_impl.cc b/gpu/command_buffer/service/validating_abstract_texture_impl.cc
index 62f8f17..4fd32a2 100644
--- a/gpu/command_buffer/service/validating_abstract_texture_impl.cc
+++ b/gpu/command_buffer/service/validating_abstract_texture_impl.cc
@@ -99,7 +99,7 @@
 
   const GLuint target = texture_ref_->texture()->target();
   const GLint level = 0;
-  return texture_ref_->texture()->GetLevelImage(target, level, nullptr);
+  return texture_ref_->texture()->GetLevelImage(target, level);
 }
 
 void ValidatingAbstractTextureImpl::SetCleared() {
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 2ae29a1d..56451f5b 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
@@ -108,9 +108,10 @@
       AddressProfileSavePromptCallback callback) override;
   bool HasCreditCardScanFeature() override;
   void ScanCreditCard(CreditCardScanCallback callback) override;
-  bool TryToShowFastCheckout(const FormData& form,
-                             const FormFieldData& field,
-                             AutofillDriver* driver) override;
+  bool TryToShowFastCheckout(
+      const FormData& form,
+      const FormFieldData& field,
+      base::WeakPtr<AutofillManager> autofill_manager) override;
   void HideFastCheckout(bool allow_further_runs) override;
   bool IsFastCheckoutSupported() override;
   bool IsShowingFastCheckoutUI() override;
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 e6bbdff..cf6c1f4 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
@@ -391,9 +391,10 @@
   return false;
 }
 
-bool ChromeAutofillClientIOS::TryToShowFastCheckout(const FormData& form,
-                                                    const FormFieldData& field,
-                                                    AutofillDriver* driver) {
+bool ChromeAutofillClientIOS::TryToShowFastCheckout(
+    const FormData& form,
+    const FormFieldData& field,
+    base::WeakPtr<AutofillManager> autofill_manager) {
   return false;
 }
 
diff --git a/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm b/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm
index 15f8b918..bdd3f3ff 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm
@@ -465,11 +465,6 @@
 
 #pragma mark - BookmarksEditorViewControllerDelegate
 
-- (BOOL)bookmarkEditor:(BookmarksEditorViewController*)controller
-    shoudDeleteAllOccurencesOfBookmark:(const BookmarkNode*)bookmark {
-  return YES;
-}
-
 - (void)bookmarkEditorWantsDismissal:
     (BookmarksEditorViewController*)controller {
   [self dismissBookmarkEditorAnimated:YES];
diff --git a/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.h b/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.h
index ae746fa..46cad06 100644
--- a/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.h
+++ b/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.h
@@ -19,15 +19,6 @@
 
 @protocol BookmarksEditorViewControllerDelegate
 
-// Called when the edited bookmark is set for deletion.
-// If the delegate returns `YES`, all nodes matching the URL of `bookmark` will
-// be deleted.
-// If the delegate returns `NO`, only `bookmark` will be deleted.
-// If the delegate doesn't implement this method, the default behavior is to
-// delete all nodes matching the URL of `bookmark`.
-- (BOOL)bookmarkEditor:(BookmarksEditorViewController*)controller
-    shoudDeleteAllOccurencesOfBookmark:(const bookmarks::BookmarkNode*)bookmark;
-
 // Called when the controller should be dismissed.
 - (void)bookmarkEditorWantsDismissal:(BookmarksEditorViewController*)controller;
 
diff --git a/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.mm b/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.mm
index 547c2bae..b471a1d 100644
--- a/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.mm
@@ -420,21 +420,11 @@
     // ignore bookmark model updates notifications.
     base::AutoReset<BOOL> autoReset(&_ignoresBookmarkModelChanges, YES);
 
-    std::set<const BookmarkNode*> nodes;
-    if ([self.delegate bookmarkEditor:self
-            shoudDeleteAllOccurencesOfBookmark:self.bookmark]) {
-      // When launched from the star button, removing the current bookmark
-      // removes all matching nodes.
-      std::vector<const BookmarkNode*> nodesVector;
-      self.bookmarkModel->GetNodesByURL(self.bookmark->url(), &nodesVector);
-      for (const BookmarkNode* node : nodesVector) {
-        nodes.insert(node);
-      }
-    } else {
-      // When launched from the info button, removing the current bookmark only
-      // removes the current node.
-      nodes.insert(self.bookmark);
-    }
+    // When launched from the star button, removing the current bookmark
+    // removes all matching nodes.
+    std::vector<const BookmarkNode*> nodesVector;
+    self.bookmarkModel->GetNodesByURL(self.bookmark->url(), &nodesVector);
+    std::set<const BookmarkNode*> nodes(nodesVector.begin(), nodesVector.end());
 
     [self.snackbarCommandsHandler
         showSnackbarMessage:bookmark_utils_ios::DeleteBookmarksWithUndoToast(
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index 6bd4508..3af17838 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -831,6 +831,7 @@
   _primaryToolbarCoordinator.popupPresenterDelegate = self.viewController;
   [_primaryToolbarCoordinator start];
 
+  _legacyTabStripCoordinator.baseViewController = self.viewController;
   _NTPCoordinator.baseViewController = self.viewController;
 
   [_dispatcher startDispatchingToTarget:self.viewController
diff --git a/ios/chrome/browser/ui/menu/menu_histograms.h b/ios/chrome/browser/ui/menu/menu_histograms.h
index 5dabbc1..a00a91c 100644
--- a/ios/chrome/browser/ui/menu/menu_histograms.h
+++ b/ios/chrome/browser/ui/menu/menu_histograms.h
@@ -27,7 +27,8 @@
   kThumbStrip = 15,
   kOmniboxMostVisitedEntry = 16,
   kPinnedTabsEntry = 17,
-  kMaxValue = kPinnedTabsEntry,
+  kTabStripEntry = 18,
+  kMaxValue = kTabStripEntry,
 };
 
 // Records a menu shown histogram metric for the `scenario`.
diff --git a/ios/chrome/browser/ui/menu/menu_histograms.mm b/ios/chrome/browser/ui/menu/menu_histograms.mm
index 74ba1d61..a1d67a8 100644
--- a/ios/chrome/browser/ui/menu/menu_histograms.mm
+++ b/ios/chrome/browser/ui/menu/menu_histograms.mm
@@ -48,6 +48,8 @@
     "Mobile.ContextMenu.OmniboxMostVisitedEntry.Actions";
 const char kPinnedTabsEntryActionsHistogram[] =
     "Mobile.ContextMenu.PinnedTabsEntry.Actions";
+const char kTabStripEntryActionsHistogram[] =
+    "Mobile.ContextMenu.TabStrip.Actions";
 }  // namespace
 
 void RecordMenuShown(MenuScenarioHistogram scenario) {
@@ -91,5 +93,7 @@
       return kOmniboxMostVisitedEntryActionsHistogram;
     case MenuScenarioHistogram::kPinnedTabsEntry:
       return kPinnedTabsEntryActionsHistogram;
+    case MenuScenarioHistogram::kTabStripEntry:
+      return kTabStripEntryActionsHistogram;
   }
 }
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_constants.h b/ios/chrome/browser/ui/omnibox/omnibox_constants.h
index a5aed66..f73c145 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_constants.h
+++ b/ios/chrome/browser/ui/omnibox/omnibox_constants.h
@@ -9,4 +9,11 @@
 
 extern const CGFloat kOmniboxPlaceholderAlpha;
 
+extern NSString* const kOmniboxLeadingImageDefaultAccessibilityIdentifier;
+
+extern NSString* const kOmniboxLeadingImageEmptyTextAccessibilityIdentifier;
+
+extern NSString* const
+    kOmniboxLeadingImageSuggestionImageAccessibilityIdentifier;
+
 #endif  // IOS_CHROME_BROWSER_UI_OMNIBOX_OMNIBOX_CONSTANTS_H_
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_constants.mm b/ios/chrome/browser/ui/omnibox/omnibox_constants.mm
index b686238..8ba7137 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_constants.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_constants.mm
@@ -9,3 +9,12 @@
 #endif
 
 const CGFloat kOmniboxPlaceholderAlpha = 0.3;
+
+NSString* const kOmniboxLeadingImageDefaultAccessibilityIdentifier =
+    @"OmniboxLeadingImageDefaultAccessibilityIdentifier";
+
+NSString* const kOmniboxLeadingImageEmptyTextAccessibilityIdentifier =
+    @"OmniboxLeadingImageEmptyTextAccessibilityIdentifier";
+
+NSString* const kOmniboxLeadingImageSuggestionImageAccessibilityIdentifier =
+    @"OmniboxLeadingImageSuggestionImageAccessibilityIdentifier";
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_consumer.h b/ios/chrome/browser/ui/omnibox/omnibox_consumer.h
index a782b2d..c3a8ff2 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_consumer.h
+++ b/ios/chrome/browser/ui/omnibox/omnibox_consumer.h
@@ -11,7 +11,12 @@
 
 // Notifies the consumer to update the autocomplete icon for the currently
 // highlighted autocomplete result.
-- (void)updateAutocompleteIcon:(UIImage*)icon;
+- (void)updateAutoCompleteIconWithoutAccessibilityIdentifier:(UIImage*)icon;
+
+// Notifies the consumer to update the autocomplete icon for the currently
+// highlighted autocomplete result with given accessibility identifier.
+- (void)updateAutocompleteIcon:(UIImage*)icon
+    withAccessibilityIdentifier:(NSString*)accessibilityIdentifier;
 
 // Notifies the consumer to update after the search-by-image support status
 // changes. (This is usually when the default search engine changes).
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_container_view.h b/ios/chrome/browser/ui/omnibox/omnibox_container_view.h
index 752eaef..d914247 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_container_view.h
+++ b/ios/chrome/browser/ui/omnibox/omnibox_container_view.h
@@ -37,8 +37,12 @@
 // The layout guide center to use to refer to the omnibox leading image.
 @property(nonatomic, strong) LayoutGuideCenter* layoutGuideCenter;
 
+// Sets the leading button's image and sets its accessibility identifier.
+- (void)setLeadingImage:(UIImage*)image
+    withAccessibilityIdentifier:(NSString*)accessibilityIdentifier;
+
 // Sets the leading button's image.
-- (void)setLeadingImage:(UIImage*)image;
+- (void)setLeadingImageWithoutAccessibilityIdentifier:(UIImage*)image;
 
 // Sets the alpha level of the leading image view.
 - (void)setLeadingImageAlpha:(CGFloat)alpha;
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_container_view.mm b/ios/chrome/browser/ui/omnibox/omnibox_container_view.mm
index d0e2ebfb..17cbfcb 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_container_view.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_container_view.mm
@@ -102,7 +102,17 @@
   return self;
 }
 
-- (void)setLeadingImage:(UIImage*)image {
+- (void)setLeadingImage:(UIImage*)image
+    withAccessibilityIdentifier:(NSString*)accessibilityIdentifier {
+  DCHECK(base::FeatureList::IsEnabled(
+      kEnableAccessibilityIdentifierToOmniboxLeadingImage));
+  [self.leadingImageView setImage:image];
+  [self.leadingImageView setAccessibilityIdentifier:accessibilityIdentifier];
+}
+
+- (void)setLeadingImageWithoutAccessibilityIdentifier:(UIImage*)image {
+  DCHECK(!base::FeatureList::IsEnabled(
+      kEnableAccessibilityIdentifierToOmniboxLeadingImage));
   [self.leadingImageView setImage:image];
 }
 
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm b/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm
index b7961b9..37fc1366 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm
@@ -23,6 +23,7 @@
 #import "ios/chrome/browser/ui/lens/lens_entrypoint.h"
 #import "ios/chrome/browser/ui/main/default_browser_scene_agent.h"
 #import "ios/chrome/browser/ui/main/scene_state_browser_agent.h"
+#import "ios/chrome/browser/ui/omnibox/omnibox_constants.h"
 #import "ios/chrome/browser/ui/omnibox/omnibox_consumer.h"
 #import "ios/chrome/browser/ui/omnibox/omnibox_suggestion_icon_util.h"
 #import "ios/chrome/browser/ui/omnibox/omnibox_util.h"
@@ -152,38 +153,89 @@
   }
 
   // Set the suggestion image, or load it if necessary.
-  [self.consumer updateAutocompleteIcon:suggestion.matchTypeIcon];
+  if (base::FeatureList::IsEnabled(
+          kEnableAccessibilityIdentifierToOmniboxLeadingImage)) {
+    [self.consumer updateAutocompleteIcon:suggestion.matchTypeIcon
+              withAccessibilityIdentifier:
+                  kOmniboxLeadingImageSuggestionImageAccessibilityIdentifier];
+  } else {
+    [self.consumer updateAutoCompleteIconWithoutAccessibilityIdentifier:
+                       suggestion.matchTypeIcon];
+  }
+
   __weak OmniboxMediator* weakSelf = self;
   if ([suggestion isMatchTypeSearch]) {
     // Show Default Search Engine favicon.
     [self loadDefaultSearchEngineFaviconWithCompletion:^(UIImage* image) {
-      [weakSelf.consumer updateAutocompleteIcon:image];
+      if (base::FeatureList::IsEnabled(
+              kEnableAccessibilityIdentifierToOmniboxLeadingImage)) {
+        [weakSelf.consumer updateAutocompleteIcon:image
+                      withAccessibilityIdentifier:
+                          kOmniboxLeadingImageDefaultAccessibilityIdentifier];
+      } else {
+        [weakSelf.consumer
+            updateAutoCompleteIconWithoutAccessibilityIdentifier:image];
+      }
     }];
   } else if (suggestion.destinationUrl.gurl.is_valid()) {
     // Show url favicon when it's valid.
-    [self loadFaviconByPageURL:suggestion.destinationUrl.gurl
-                    completion:^(UIImage* image) {
-                      [weakSelf.consumer updateAutocompleteIcon:image];
-                    }];
+    [self
+        loadFaviconByPageURL:suggestion.destinationUrl.gurl
+                  completion:^(UIImage* image) {
+                    if (base::FeatureList::IsEnabled(
+                            kEnableAccessibilityIdentifierToOmniboxLeadingImage)) {
+                      NSString* webPageUrl = base::SysUTF8ToNSString(
+                          suggestion.destinationUrl.gurl.spec());
+                      [weakSelf.consumer updateAutocompleteIcon:image
+                                    withAccessibilityIdentifier:webPageUrl];
+                    } else {
+                      [weakSelf.consumer
+                          updateAutoCompleteIconWithoutAccessibilityIdentifier:
+                              image];
+                    }
+                  }];
   } else if (isFirstUpdate) {
     // When no suggestion is highlighted (aka. isFirstUpdate) show the default
     // browser icon.
     [self setDefaultLeftImage];
   } else {
     // When a suggestion is highlighted, show the same icon as in the popup.
-    [self.consumer updateAutocompleteIcon:suggestion.matchTypeIcon];
+    if (base::FeatureList::IsEnabled(
+            kEnableAccessibilityIdentifierToOmniboxLeadingImage)) {
+      [self.consumer updateAutocompleteIcon:suggestion.matchTypeIcon
+                withAccessibilityIdentifier:
+                    suggestion.matchTypeIconAccessibilityIdentifier];
+    } else {
+      [weakSelf.consumer updateAutoCompleteIconWithoutAccessibilityIdentifier:
+                             suggestion.matchTypeIcon];
+    }
   }
 }
 
 - (void)setDefaultLeftImage {
   UIImage* image = GetOmniboxSuggestionIconForAutocompleteMatchType(
       AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, /* is_starred */ false);
-  [self.consumer updateAutocompleteIcon:image];
+  if (base::FeatureList::IsEnabled(
+          kEnableAccessibilityIdentifierToOmniboxLeadingImage)) {
+    [self.consumer updateAutocompleteIcon:image
+              withAccessibilityIdentifier:
+                  kOmniboxLeadingImageDefaultAccessibilityIdentifier];
+  } else {
+    [self.consumer updateAutoCompleteIconWithoutAccessibilityIdentifier:image];
+  }
 
   __weak OmniboxMediator* weakSelf = self;
   // Show Default Search Engine favicon.
   [self loadDefaultSearchEngineFaviconWithCompletion:^(UIImage* icon) {
-    [weakSelf.consumer updateAutocompleteIcon:icon];
+    if (base::FeatureList::IsEnabled(
+            kEnableAccessibilityIdentifierToOmniboxLeadingImage)) {
+      [weakSelf.consumer updateAutocompleteIcon:icon
+                    withAccessibilityIdentifier:
+                        kOmniboxLeadingImageDefaultAccessibilityIdentifier];
+    } else {
+      [weakSelf.consumer
+          updateAutoCompleteIconWithoutAccessibilityIdentifier:icon];
+    }
   }];
 }
 
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
index 29436b6..f24e457d 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
@@ -256,9 +256,18 @@
 }
 
 - (void)textFieldDidChange:(id)sender {
+  BOOL accessibilityIdentifierFlagEnabled = base::FeatureList::IsEnabled(
+      kEnableAccessibilityIdentifierToOmniboxLeadingImage);
   // If the text is empty, update the leading image.
   if (self.textField.text.length == 0) {
-    [self.view setLeadingImage:self.emptyTextLeadingImage];
+    if (accessibilityIdentifierFlagEnabled) {
+      [self.view setLeadingImage:self.emptyTextLeadingImage
+          withAccessibilityIdentifier:
+              kOmniboxLeadingImageEmptyTextAccessibilityIdentifier];
+    } else {
+      [self.view setLeadingImageWithoutAccessibilityIdentifier:
+                     self.emptyTextLeadingImage];
+    }
   }
 
   [self updateClearButtonVisibility];
@@ -303,9 +312,21 @@
 
   // Update the clear button state.
   [self updateClearButtonVisibility];
-  [self.view setLeadingImage:self.textField.text.length
-                                 ? self.defaultLeadingImage
-                                 : self.emptyTextLeadingImage];
+  UIImage* image = self.textField.text.length ? self.defaultLeadingImage
+                                              : self.emptyTextLeadingImage;
+
+  NSString* accessibilityID =
+      self.textField.text.length
+          ? kOmniboxLeadingImageDefaultAccessibilityIdentifier
+          : kOmniboxLeadingImageEmptyTextAccessibilityIdentifier;
+
+  if (base::FeatureList::IsEnabled(
+          kEnableAccessibilityIdentifierToOmniboxLeadingImage)) {
+    [self.view setLeadingImage:image
+        withAccessibilityIdentifier:accessibilityID];
+  } else {
+    [self.view setLeadingImageWithoutAccessibilityIdentifier:image];
+  }
 
   self.semanticContentAttribute = [self.textField bestSemanticContentAttribute];
   self.isTextfieldEditing = YES;
@@ -420,10 +441,18 @@
 
 #pragma mark - OmniboxConsumer
 
-- (void)updateAutocompleteIcon:(UIImage*)icon {
-  [self.view setLeadingImage:icon];
+- (void)updateAutoCompleteIconWithoutAccessibilityIdentifier:(UIImage*)icon {
+  DCHECK(!base::FeatureList::IsEnabled(
+      kEnableAccessibilityIdentifierToOmniboxLeadingImage));
+  [self.view setLeadingImageWithoutAccessibilityIdentifier:icon];
 }
-
+- (void)updateAutocompleteIcon:(UIImage*)icon
+    withAccessibilityIdentifier:(NSString*)accessibilityIdentifier {
+  DCHECK(base::FeatureList::IsEnabled(
+      kEnableAccessibilityIdentifierToOmniboxLeadingImage));
+  [self.view setLeadingImage:icon
+      withAccessibilityIdentifier:accessibilityIdentifier];
+}
 - (void)updateSearchByImageSupported:(BOOL)searchByImageSupported {
   self.searchByImageEnabled = searchByImageSupported;
 }
diff --git a/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm b/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm
index 4a8804e..1e1f9b6 100644
--- a/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm
@@ -247,6 +247,10 @@
       _match.type, /* is_starred */ false);
 }
 
+- (NSString*)matchTypeIconAccessibilityIdentifier {
+  return base::SysUTF8ToNSString(AutocompleteMatchType::ToString(_match.type));
+}
+
 - (BOOL)isMatchTypeSearch {
   return AutocompleteMatch::IsSearchType(_match.type);
 }
diff --git a/ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion.h b/ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion.h
index dc84cb2..5d95f72a 100644
--- a/ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion.h
+++ b/ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion.h
@@ -57,6 +57,9 @@
 /// History, Search, or Stock.
 /// Ignores `starred` status of the suggestion.
 @property(nonatomic, readonly) UIImage* matchTypeIcon;
+/// Accessibility identifier of the icon corresponding to the suggestion's
+/// autocomplete match type.
+@property(nonatomic, readonly) NSString* matchTypeIconAccessibilityIdentifier;
 /// Whether this is a search suggestion (as opposed to URL suggestion)
 @property(nonatomic, readonly, getter=isMatchTypeSearch) BOOL matchTypeSearch;
 /// For URL suggestions, the URL that the match represents.
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell_unittest.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell_unittest.mm
index 6edcc18c..020d6fd 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell_unittest.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell_unittest.mm
@@ -30,6 +30,7 @@
 @property(nonatomic, weak) id<OmniboxPedal, OmniboxIcon> pedal;
 @property(nonatomic, strong) NSAttributedString* omniboxPreviewText;
 @property(nonatomic, strong) UIImage* matchTypeIcon;
+@property(nonatomic, strong) NSString* matchTypeIconAccessibilityIdentifier;
 @property(nonatomic, getter=isMatchTypeSearch) BOOL matchTypeSearch;
 @property(nonatomic, strong) CrURL* destinationUrl;
 @property(nonatomic, strong) NSNumber* suggestionGroupId;
diff --git a/ios/chrome/browser/ui/omnibox/popup/pedal_suggestion_wrapper.mm b/ios/chrome/browser/ui/omnibox/popup/pedal_suggestion_wrapper.mm
index bce6c019..ac20abf 100644
--- a/ios/chrome/browser/ui/omnibox/popup/pedal_suggestion_wrapper.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/pedal_suggestion_wrapper.mm
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #import "ios/chrome/browser/ui/omnibox/popup/pedal_suggestion_wrapper.h"
+#import "base/notreached.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -90,6 +91,11 @@
   return nil;
 }
 
+- (NSString*)matchTypeIconAccessibilityIdentifier {
+  NOTREACHED();
+  return nil;
+}
+
 - (BOOL)isMatchTypeSearch {
   return true;
 }
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
index 8dfe6bf7..4f39efe 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
@@ -169,8 +169,6 @@
 @property(nonatomic, strong) UIControl* scrimView;
 @property(nonatomic, weak) TabGridTopToolbar* topToolbar;
 @property(nonatomic, weak) TabGridBottomToolbar* bottomToolbar;
-// Bool informing if the confirmation action sheet is displayed.
-@property(nonatomic, assign) BOOL closeAllConfirmationDisplayed;
 @property(nonatomic, assign) TabGridConfiguration configuration;
 // Setting the current page doesn't scroll the scroll view; use
 // -scrollToPage:animated: for that.
@@ -226,7 +224,6 @@
   self = [super initWithNibName:nil bundle:nil];
   if (self) {
     _pageConfiguration = tabGridPageConfiguration;
-    _closeAllConfirmationDisplayed = NO;
     _dragSeesionInProgress = NO;
 
     switch (_pageConfiguration) {
@@ -1645,17 +1642,14 @@
 - (void)configureDoneButtonBasedOnPage:(TabGridPage)page {
   const BOOL tabsPresent = [self tabsPresentForPage:page];
 
-  if (!self.closeAllConfirmationDisplayed)
-    self.topToolbar.pageControl.userInteractionEnabled = YES;
+  self.topToolbar.pageControl.userInteractionEnabled = YES;
 
   // The Done button should have the same behavior as the other buttons on the
   // top Toolbar.
   BOOL incognitoTabsNeedsAuth =
       (self.currentPage == TabGridPageIncognitoTabs &&
        self.incognitoTabsViewController.contentNeedsAuthentication);
-  BOOL doneEnabled = tabsPresent &&
-                     self.topToolbar.pageControl.userInteractionEnabled &&
-                     !incognitoTabsNeedsAuth;
+  BOOL doneEnabled = tabsPresent && !incognitoTabsNeedsAuth;
   [self.topToolbar setDoneButtonEnabled:doneEnabled];
   [self.bottomToolbar setDoneButtonEnabled:doneEnabled];
 }
@@ -1675,8 +1669,7 @@
 // Disables the done button on bottom toolbar if a disabled tab view is
 // presented.
 - (void)configureDoneButtonOnDisabledPage {
-  if (!self.closeAllConfirmationDisplayed)
-    self.topToolbar.pageControl.userInteractionEnabled = YES;
+  self.topToolbar.pageControl.userInteractionEnabled = YES;
   [self.bottomToolbar setDoneButtonEnabled:NO];
   [self.topToolbar setDoneButtonEnabled:NO];
 }
diff --git a/ios/chrome/browser/ui/tabs/BUILD.gn b/ios/chrome/browser/ui/tabs/BUILD.gn
index 7a101ee..c0afaaa 100644
--- a/ios/chrome/browser/ui/tabs/BUILD.gn
+++ b/ios/chrome/browser/ui/tabs/BUILD.gn
@@ -14,6 +14,10 @@
     "tab_strip_container_view.h",
     "tab_strip_container_view.mm",
     "tab_strip_containing.h",
+    "tab_strip_context_menu_delegate.h",
+    "tab_strip_context_menu_helper.h",
+    "tab_strip_context_menu_helper.mm",
+    "tab_strip_context_menu_provider.h",
     "tab_strip_controller.h",
     "tab_strip_controller.mm",
     "tab_strip_view.h",
@@ -38,14 +42,19 @@
     "resources:tabstrip_toggle_button_gradient",
     "//base",
     "//base:i18n",
+    "//components/bookmarks/browser",
+    "//components/bookmarks/common",
     "//components/favicon/ios",
+    "//components/prefs",
     "//ios/chrome/app/strings",
+    "//ios/chrome/browser/bookmarks",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/drag_and_drop",
     "//ios/chrome/browser/flags:system_flags",
     "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/ui:feature_flags",
+    "//ios/chrome/browser/ui/bookmarks",
     "//ios/chrome/browser/ui/bubble",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/elements",
@@ -54,11 +63,12 @@
     "//ios/chrome/browser/ui/icons:symbols",
     "//ios/chrome/browser/ui/image_util",
     "//ios/chrome/browser/ui/main:scene_state_header",
-    "//ios/chrome/browser/ui/ntp:logo",
+    "//ios/chrome/browser/ui/menu",
     "//ios/chrome/browser/ui/ntp:util",
-    "//ios/chrome/browser/ui/open_in",
     "//ios/chrome/browser/ui/popup_menu/public",
-    "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid/resources:grid_cell_close_button",
+    "//ios/chrome/browser/ui/tab_switcher:tab_utils",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs:features",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/tab_context_menu:tab_item",
     "//ios/chrome/browser/ui/tabs/requirements",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/url_loading",
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_context_menu_delegate.h b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_delegate.h
new file mode 100644
index 0000000..4a17910
--- /dev/null
+++ b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_delegate.h
@@ -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.
+
+#ifndef IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_DELEGATE_H_
+#define IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_DELEGATE_H_
+
+#import <Foundation/Foundation.h>
+
+class GURL;
+
+// Methods used to create context menu actions for tab strip items.
+@protocol TabStripContextMenuDelegate <NSObject>
+
+// Tells the delegate to add `URL` and `title` to the reading list.
+- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
+
+// Tells the delegate to create a bookmark for `URL` with `title`.
+- (void)bookmarkURL:(const GURL&)URL title:(NSString*)title;
+
+// Tells the delegate to edit the bookmark for `URL`.
+- (void)editBookmarkWithURL:(const GURL&)URL;
+
+// Tells the delegate to pin a tab with the item identifier `identifier`.
+- (void)pinTabWithIdentifier:(NSString*)identifier;
+
+// Tells the delegate to unpin a tab with the item identifier `identifier`.
+- (void)unpinTabWithIdentifier:(NSString*)identifier;
+
+// Tells the delegate to close the tab with the item identifier `identifier`.
+- (void)closeTabWithIdentifier:(NSString*)identifier;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.h b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.h
new file mode 100644
index 0000000..a92063f69
--- /dev/null
+++ b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.h
@@ -0,0 +1,27 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_HELPER_H_
+#define IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_HELPER_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_provider.h"
+
+class Browser;
+@protocol TabStripContextMenuDelegate;
+
+//  TabStripContextMenuHelper controls the creation of context menus for tab
+//  strip items.
+@interface TabStripContextMenuHelper : NSObject <TabStripContextMenuProvider>
+- (instancetype)initWithBrowser:(Browser*)browser
+    tabStripContextMenuDelegate:
+        (id<TabStripContextMenuDelegate>)tabStripContextMenuDelegate
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_HELPER_H_
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.mm b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.mm
new file mode 100644
index 0000000..825cce5
--- /dev/null
+++ b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.mm
@@ -0,0 +1,208 @@
+// 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/tabs/tab_strip_context_menu_helper.h"
+
+#import "base/metrics/histogram_functions.h"
+#import "components/bookmarks/browser/bookmark_model.h"
+#import "components/bookmarks/common/bookmark_pref_names.h"
+#import "components/prefs/pref_service.h"
+#import "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/main/browser.h"
+#import "ios/chrome/browser/main/browser_list.h"
+#import "ios/chrome/browser/main/browser_list_factory.h"
+#import "ios/chrome/browser/main/browser_observer_bridge.h"
+#import "ios/chrome/browser/ui/menu/action_factory.h"
+#import "ios/chrome/browser/ui/menu/menu_histograms.h"
+#import "ios/chrome/browser/ui/ntp/new_tab_page_util.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs/features.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_context_menu/tab_item.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_utils.h"
+#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_delegate.h"
+#import "ios/web/public/web_state.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface TabStripContextMenuHelper () <BrowserObserving,
+                                         TabStripContextMenuProvider> {
+  // Observe BrowserObserver to prevent any access to Browser before its
+  // destroyed.
+  std::unique_ptr<BrowserObserverBridge> _browserObserver;
+}
+
+@property(nonatomic, assign) Browser* browser;
+@property(nonatomic, weak) id<TabStripContextMenuDelegate> delegate;
+
+@end
+
+@implementation TabStripContextMenuHelper
+
+#pragma mark - TabStripContextMenuProvider
+
+- (instancetype)initWithBrowser:(Browser*)browser
+    tabStripContextMenuDelegate:
+        (id<TabStripContextMenuDelegate>)tabStripContextMenuDelegate {
+  self = [super init];
+  if (self) {
+    _browser = browser;
+    _browserObserver = std::make_unique<BrowserObserverBridge>(_browser, self);
+    _delegate = tabStripContextMenuDelegate;
+  }
+  return self;
+}
+
+- (void)dealloc {
+  _browserObserver.reset();
+  _browser = nullptr;
+}
+
+#pragma mark - TabStripContextMenuProvider
+
+- (UIContextMenuConfiguration*)
+    contextMenuConfigurationForWebStateIdentifier:(NSString*)identifier
+                                      pinnedState:(BOOL)pinnedState {
+  __weak __typeof(self) weakSelf = self;
+
+  UIContextMenuActionProvider actionProvider =
+      ^(NSArray<UIMenuElement*>* suggestedActions) {
+        TabStripContextMenuHelper* strongSelf = weakSelf;
+        if (!strongSelf) {
+          // Return an empty menu.
+          return [UIMenu menuWithTitle:@"" children:@[]];
+        }
+
+        NSArray<UIMenuElement*>* menuElements =
+            [strongSelf menuElementsForWebstateIdentifier:identifier
+                                              pinnedState:pinnedState];
+        return [UIMenu menuWithTitle:@"" children:menuElements];
+      };
+
+  return
+      [UIContextMenuConfiguration configurationWithIdentifier:nil
+                                              previewProvider:nil
+                                               actionProvider:actionProvider];
+}
+
+#pragma mark - Private
+
+// Returns context menu actions for the given `webStateIndex` and `pinnedState`.
+- (NSArray<UIMenuElement*>*)
+    menuElementsForWebstateIdentifier:(NSString*)identifier
+                          pinnedState:(BOOL)pinnedState {
+  // Record that this context menu was shown to the user.
+  MenuScenarioHistogram scenario = MenuScenarioHistogram::kTabStripEntry;
+  RecordMenuShown(scenario);
+
+  ActionFactory* actionFactory =
+      [[ActionFactory alloc] initWithScenario:scenario];
+
+  TabItem* item = [self tabItemForIdentifier:identifier
+                                 pinnedState:pinnedState];
+
+  if (!item) {
+    return @[];
+  }
+
+  NSMutableArray<UIMenuElement*>* menuElements = [[NSMutableArray alloc] init];
+
+  BOOL pinnedActionsAvailable =
+      IsPinnedTabsEnabled() && !_browser->GetBrowserState()->IsOffTheRecord();
+  if (pinnedActionsAvailable) {
+    if (pinnedState) {
+      [menuElements addObject:[actionFactory actionToUnpinTabWithBlock:^{
+                      [self.delegate unpinTabWithIdentifier:identifier];
+                    }]];
+    } else {
+      [menuElements addObject:[actionFactory actionToPinTabWithBlock:^{
+                      [self.delegate pinTabWithIdentifier:identifier];
+                    }]];
+    }
+  }
+
+  BOOL addToReadingListActionAvailable =
+      !IsURLNewTabPage(item.URL) && item.URL.SchemeIsHTTPOrHTTPS();
+  if (addToReadingListActionAvailable) {
+    [menuElements addObject:[actionFactory actionToAddToReadingListWithBlock:^{
+                    [self.delegate addToReadingListURL:item.URL
+                                                 title:item.title];
+                  }]];
+  }
+
+  BOOL bookmarksActionsAvailable =
+      !IsURLNewTabPage(item.URL) && item.URL.SchemeIsHTTPOrHTTPS();
+  if (bookmarksActionsAvailable) {
+    UIAction* bookmarkAction;
+    const BOOL currentlyBookmarked = [self isTabItemBookmarked:item];
+    if (currentlyBookmarked) {
+      bookmarkAction = [actionFactory actionToEditBookmarkWithBlock:^{
+        [self.delegate editBookmarkWithURL:item.URL];
+      }];
+    } else {
+      bookmarkAction = [actionFactory actionToBookmarkWithBlock:^{
+        [self.delegate bookmarkURL:item.URL title:item.title];
+      }];
+    }
+    // Bookmarking can be disabled from prefs (from an enterprise policy),
+    // if that's the case grey out the option in the menu.
+    if (self.browser) {
+      BOOL isEditBookmarksEnabled =
+          self.browser->GetBrowserState()->GetPrefs()->GetBoolean(
+              bookmarks::prefs::kEditBookmarksEnabled);
+      if (!isEditBookmarksEnabled && bookmarkAction) {
+        bookmarkAction.attributes = UIMenuElementAttributesDisabled;
+      }
+      if (bookmarkAction) {
+        [menuElements addObject:bookmarkAction];
+      }
+    }
+  }
+
+  [menuElements addObject:[actionFactory actionToCloseTabWithBlock:^{
+                  [self.delegate closeTabWithIdentifier:identifier];
+                }]];
+  return menuElements;
+}
+
+#pragma mark - BrowserObserving
+
+- (void)browserDestroyed:(Browser*)browser {
+  DCHECK_EQ(browser, self.browser);
+  _browserObserver.reset();
+  self.browser = nullptr;
+}
+
+#pragma mark - Private
+
+// Returns `YES` if the tab `item` is already bookmarked.
+- (BOOL)isTabItemBookmarked:(TabItem*)item {
+  bookmarks::BookmarkModel* bookmarkModel =
+      ios::BookmarkModelFactory::GetForBrowserState(
+          _browser->GetBrowserState());
+  return item && bookmarkModel &&
+         bookmarkModel->GetMostRecentlyAddedUserNodeForURL(item.URL);
+}
+
+// Returns the TabItem object representing the tab with `identifier`.
+// `pinnedState` tracks the pinned state of the tab we are looking for.
+- (TabItem*)tabItemForIdentifier:(NSString*)identifier
+                     pinnedState:(BOOL)pinnedState {
+  BrowserList* browserList =
+      BrowserListFactory::GetForBrowserState(_browser->GetBrowserState());
+  std::set<Browser*> browsers = _browser->GetBrowserState()->IsOffTheRecord()
+                                    ? browserList->AllIncognitoBrowsers()
+                                    : browserList->AllRegularBrowsers();
+  for (Browser* browser : browsers) {
+    WebStateList* webStateList = browser->GetWebStateList();
+    TabItem* item = GetTabItem(webStateList, identifier, pinnedState);
+    if (item) {
+      return item;
+    }
+  }
+  return nil;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_context_menu_provider.h b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_provider.h
new file mode 100644
index 0000000..338427b
--- /dev/null
+++ b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_provider.h
@@ -0,0 +1,21 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_PROVIDER_H_
+#define IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_PROVIDER_H_
+
+#import <UIKit/UIKit.h>
+
+// Protocol that provides tab strip context menus.
+@protocol TabStripContextMenuProvider
+
+// Returns a context menu configuration instance for the given
+// `identifier` and `pinnedState`.
+- (UIContextMenuConfiguration*)
+    contextMenuConfigurationForWebStateIdentifier:(NSString*)identifier
+                                      pinnedState:(BOOL)pinnedState;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_PROVIDER_H_
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_controller.h b/ios/chrome/browser/ui/tabs/tab_strip_controller.h
index 0ca47d1..2be9f86 100644
--- a/ios/chrome/browser/ui/tabs/tab_strip_controller.h
+++ b/ios/chrome/browser/ui/tabs/tab_strip_controller.h
@@ -42,9 +42,11 @@
 // it needs to be run in sync with BVC.
 @property(nonatomic, readonly, strong) id<ViewRevealingAnimatee> animatee;
 
-// Designated initializer, `dispatcher` is not retained.
-- (instancetype)initWithBrowser:(Browser*)browser
-                          style:(TabStripStyle)style NS_DESIGNATED_INITIALIZER;
+// Designated initializer.
+- (instancetype)initWithBaseViewController:(UIViewController*)baseViewController
+                                   browser:(Browser*)browser
+                                     style:(TabStripStyle)style
+    NS_DESIGNATED_INITIALIZER;
 
 - (instancetype)init NS_UNAVAILABLE;
 
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_controller.mm b/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
index c2b3993..eb345aee 100644
--- a/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
+++ b/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
@@ -8,6 +8,7 @@
 #import <memory>
 #import <vector>
 
+#import "base/feature_list.h"
 #import "base/i18n/rtl.h"
 #import "base/mac/bundle_locations.h"
 #import "base/mac/foundation_util.h"
@@ -15,7 +16,9 @@
 #import "base/metrics/user_metrics_action.h"
 #import "base/numerics/safe_conversions.h"
 #import "base/strings/sys_string_conversions.h"
+#import "components/bookmarks/browser/bookmark_model.h"
 #import "components/favicon/ios/web_favicon_driver.h"
+#import "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
 #import "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/drag_and_drop/drag_item_util.h"
 #import "ios/chrome/browser/drag_and_drop/url_drag_drop_handler.h"
@@ -23,12 +26,16 @@
 #import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
 #import "ios/chrome/browser/tabs/tab_title_util.h"
+#import "ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.h"
 #import "ios/chrome/browser/ui/bubble/bubble_util.h"
 #import "ios/chrome/browser/ui/bubble/bubble_view.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
+#import "ios/chrome/browser/ui/commands/bookmarks_commands.h"
+#import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/commands/command_dispatcher.h"
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
 #import "ios/chrome/browser/ui/commands/popup_menu_commands.h"
+#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
 #import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
 #import "ios/chrome/browser/ui/fullscreen/scoped_fullscreen_disabler.h"
 #import "ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h"
@@ -37,10 +44,14 @@
 #import "ios/chrome/browser/ui/main/scene_state_browser_agent.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_util.h"
 #import "ios/chrome/browser/ui/popup_menu/public/popup_menu_long_press_delegate.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_utils.h"
 #import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
 #import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
 #import "ios/chrome/browser/ui/tabs/tab_strip_constants.h"
 #import "ios/chrome/browser/ui/tabs/tab_strip_container_view.h"
+#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_delegate.h"
+#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.h"
+#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_provider.h"
 #import "ios/chrome/browser/ui/tabs/tab_strip_view.h"
 #import "ios/chrome/browser/ui/tabs/tab_view.h"
 #import "ios/chrome/browser/ui/tabs/target_frame_cache.h"
@@ -80,7 +91,8 @@
 const NSTimeInterval kTabStripFadeAnimationDuration = 0.15;
 
 // Amount of time needed to trigger drag and drop mode when long pressing.
-const NSTimeInterval kDragAndDropLongPressDuration = 0.4;
+const NSTimeInterval kDragAndDropLongPressDuration = 0.1;
+const NSTimeInterval kDragAndDropLongPressLegacyDuration = 0.4;
 
 // Tab dimensions.
 const CGFloat kTabOverlapStacked = 32.0;
@@ -130,6 +142,12 @@
   return UIColor.blackColor;
 }
 
+// Convenience method for determining if the TabStripContextMenu feature is
+// enabled.
+bool IsTabStripContextMenuEnabled() {
+  return base::FeatureList::IsEnabled(kTabStripContextMenu);
+}
+
 const CGFloat kSymbolSize = 18;
 
 }  // namespace
@@ -169,11 +187,13 @@
 @end
 
 @interface TabStripController () <CRWWebStateObserver,
+                                  TabStripContextMenuDelegate,
                                   TabStripViewLayoutDelegate,
                                   TabViewDelegate,
                                   ViewRevealingAnimatee,
-                                  WebStateListObserving,
                                   WebStateFaviconDriverObserver,
+                                  WebStateListObserving,
+                                  UIContextMenuInteractionDelegate,
                                   UIGestureRecognizerDelegate,
                                   UIScrollViewDelegate,
                                   URLDropDelegate> {
@@ -271,6 +291,17 @@
 // YES if the controller has been disconnected.
 @property(nonatomic) BOOL disconnected;
 
+// The base view controller from which to present UI.
+@property(nonatomic, readwrite, weak) UIViewController* baseViewController;
+
+// Provider of context menu configurations.
+@property(nonatomic, strong) id<TabStripContextMenuProvider>
+    contextMenuProvider;
+
+// Coordinator that manages the various pieces of UI used to create, remove and
+// edit a bookmark.
+@property(nonatomic, strong) BookmarksCoordinator* bookmarksCoordinator;
+
 // If set to `YES`, tabs at either end of the tabstrip are "collapsed" into a
 // stack, such that the visible width of the tabstrip is constant.  If set to
 // `NO`, tabs are never collapsed and the tabstrip scrolls horizontally as a
@@ -431,12 +462,14 @@
 @synthesize animationWaitDuration = _animationWaitDuration;
 @synthesize panGestureHandler = _panGestureHandler;
 
-- (instancetype)initWithBrowser:(Browser*)browser style:(TabStripStyle)style {
+- (instancetype)initWithBaseViewController:(UIViewController*)baseViewController
+                                   browser:(Browser*)browser
+                                     style:(TabStripStyle)style {
   if ((self = [super init])) {
     _tabArray = [[NSMutableArray alloc] initWithCapacity:10];
     _closingTabs = [[NSMutableSet alloc] initWithCapacity:5];
     DCHECK(browser);
-
+    _baseViewController = baseViewController;
     _browser = browser;
     _webStateList = _browser->GetWebStateList();
     _webStateListObserver = std::make_unique<WebStateListObserverBridge>(self);
@@ -477,6 +510,15 @@
     [_view addSubview:_tabStripView];
     _view.tabStripView = _tabStripView;
 
+    if (IsTabStripContextMenuEnabled()) {
+      _contextMenuProvider =
+          [[TabStripContextMenuHelper alloc] initWithBrowser:_browser
+                                 tabStripContextMenuDelegate:self];
+      _bookmarksCoordinator =
+          [[BookmarksCoordinator alloc] initWithBrowser:_browser];
+      _bookmarksCoordinator.baseViewController = _baseViewController;
+    }
+
     // `self.buttonNewTab` setup.
     CGRect buttonNewTabFrame = tabStripFrame;
     buttonNewTabFrame.size.width = kNewTabButtonWidth;
@@ -550,10 +592,16 @@
 - (void)disconnect {
   [_tabStripView setDelegate:nil];
   [_tabStripView setLayoutDelegate:nil];
+
+  self.presentationProvider = nil;
+  self.baseViewController = nil;
+  self.bookmarksCoordinator = nil;
+
   _allWebStateObservationForwarder.reset();
   _webStateListFaviconObserver.reset();
   _webStateList->RemoveObserver(_webStateListObserver.get());
   [[NSNotificationCenter defaultCenter] removeObserver:self];
+
   self.disconnected = YES;
 }
 
@@ -628,7 +676,15 @@
       [[UILongPressGestureRecognizer alloc]
           initWithTarget:self
                   action:@selector(handleLongPress:)];
-  [longPress setMinimumPressDuration:kDragAndDropLongPressDuration];
+
+  if (IsTabStripContextMenuEnabled()) {
+    [view addInteraction:[[UIContextMenuInteraction alloc]
+                             initWithDelegate:self]];
+    [longPress setMinimumPressDuration:kDragAndDropLongPressDuration];
+
+  } else {
+    [longPress setMinimumPressDuration:kDragAndDropLongPressLegacyDuration];
+  }
   [longPress setDelegate:self];
   [view addGestureRecognizer:longPress];
 
@@ -831,8 +887,84 @@
   UrlLoadingBrowserAgent::FromBrowser(_browser)->Load(params);
 }
 
-#pragma mark -
-#pragma mark Tab Drag and Drop methods
+#pragma mark - UIContextMenuInteractionDelegate
+
+- (UIContextMenuConfiguration*)contextMenuInteraction:
+                                   (UIContextMenuInteraction*)interaction
+                       configurationForMenuAtLocation:(CGPoint)location {
+  DCHECK(IsTabStripContextMenuEnabled());
+
+  int webStateIndex = [self webStateListIndexForTabView:interaction.view];
+  NSString* identifier =
+      _webStateList->GetWebStateAt(webStateIndex)->GetStableIdentifier();
+  BOOL pinnedState = _webStateList->IsWebStatePinnedAt(webStateIndex);
+
+  return [self.contextMenuProvider
+      contextMenuConfigurationForWebStateIdentifier:identifier
+                                        pinnedState:pinnedState];
+}
+
+- (UITargetedPreview*)contextMenuInteraction:
+                          (UIContextMenuInteraction*)interaction
+                               configuration:
+                                   (UIContextMenuConfiguration*)configuration
+       highlightPreviewForItemWithIdentifier:(id<NSCopying>)identifier {
+  // TODO(crbug.com/1409893): Update the targeted preview.
+  UIPreviewParameters* previewParameters = [[UIPreviewParameters alloc] init];
+  previewParameters.backgroundColor = UIColor.clearColor;
+  return [[UITargetedPreview alloc] initWithView:interaction.view
+                                      parameters:previewParameters];
+}
+
+#pragma mark - TabStripContextMenuDelegate
+
+- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
+  ReadingListAddCommand* command =
+      [[ReadingListAddCommand alloc] initWithURL:URL title:title];
+  // TODO(crbug.com/1045047): Use HandlerForProtocol after commands
+  // protocol clean up.
+  id<BrowserCommands> readingListAdder =
+      static_cast<id<BrowserCommands>>(_browser->GetCommandDispatcher());
+  [readingListAdder addToReadingList:command];
+}
+
+- (void)bookmarkURL:(const GURL&)URL title:(NSString*)title {
+  bookmarks::BookmarkModel* bookmarkModel =
+      ios::BookmarkModelFactory::GetForBrowserState(
+          _browser->GetBrowserState());
+  bool currentlyBookmarked =
+      bookmarkModel && bookmarkModel->GetMostRecentlyAddedUserNodeForURL(URL);
+
+  if (currentlyBookmarked) {
+    [self editBookmarkWithURL:URL];
+  } else {
+    [self.bookmarksCoordinator bookmarkURL:URL title:title];
+  }
+}
+
+- (void)editBookmarkWithURL:(const GURL&)URL {
+  [self.bookmarksCoordinator presentBookmarkEditorForURL:URL];
+}
+
+- (void)pinTabWithIdentifier:(NSString*)identifier {
+  SetWebStatePinnedState(_webStateList, identifier, YES);
+}
+
+- (void)unpinTabWithIdentifier:(NSString*)identifier {
+  SetWebStatePinnedState(_webStateList, identifier, NO);
+}
+
+- (void)closeTabWithIdentifier:(NSString*)identifier {
+  for (int index = 0; index < static_cast<int>(_tabArray.count); ++index) {
+    web::WebState* web_state = _webStateList->GetWebStateAt(index);
+    if ([identifier isEqualToString:web_state->GetStableIdentifier()]) {
+      _webStateList->CloseWebStateAt(index, WebStateList::CLOSE_USER_ACTION);
+      return;
+    }
+  }
+}
+
+#pragma mark - Tab Drag and Drop methods
 
 - (void)beginDrag:(UILongPressGestureRecognizer*)gesture {
   DCHECK([[gesture view] isKindOfClass:[TabView class]]);
@@ -978,8 +1110,7 @@
   [self insertNewItemAtIndex:_webStateList->count() withURL:URL];
 }
 
-#pragma mark -
-#pragma mark Autoscroll methods
+#pragma mark - Autoscroll methods
 
 - (void)installAutoscrollTimerIfNeeded {
   if (_autoscrollTimer)
@@ -1069,7 +1200,6 @@
     _autoscrollDistance = -offset.x;
 }
 
-#pragma mark -
 #pragma mark - CRWWebStateObserver methods
 
 - (void)webStateDidStartLoading:(web::WebState*)webState {
@@ -1118,8 +1248,7 @@
   [view setNeedsDisplay];
 }
 
-#pragma mark -
-#pragma mark WebStateListObserving methods
+#pragma mark - WebStateListObserving methods
 
 // Observer method, active WebState changed.
 - (void)webStateList:(WebStateList*)webStateList
@@ -1234,8 +1363,7 @@
   [self layoutTabStripSubviews];
 }
 
-#pragma mark -
-#pragma mark WebStateFaviconDriverObserver
+#pragma mark - WebStateFaviconDriverObserver
 
 // Observer method. `webState` got a favicon update.
 - (void)faviconDriver:(favicon::FaviconDriver*)driver
@@ -1261,8 +1389,7 @@
   }
 }
 
-#pragma mark -
-#pragma mark Views and Layout
+#pragma mark - Views and Layout
 
 - (TabView*)tabViewForWebState:(web::WebState*)webState {
   int listIndex = _webStateList->GetIndexOfWebState(webState);
@@ -1361,7 +1488,6 @@
   return frame;
 }
 
-#pragma mark -
 #pragma mark - Unstacked layout
 
 - (int)maxNumCollapsedTabs {
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm b/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm
index 3e0261c..db9607a 100644
--- a/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm
+++ b/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm
@@ -69,8 +69,10 @@
 
     SceneStateBrowserAgent::CreateForBrowser(browser_.get(), scene_state_);
 
-    controller_ = [[TabStripController alloc] initWithBrowser:browser_.get()
-                                                        style:NORMAL];
+    controller_ =
+        [[TabStripController alloc] initWithBaseViewController:nil
+                                                       browser:browser_.get()
+                                                         style:NORMAL];
   }
 
   void TearDown() override {
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h b/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h
index da0daa6..2a67d7e1 100644
--- a/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h
+++ b/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h
@@ -26,6 +26,10 @@
 - (instancetype)initWithBaseViewController:(UIViewController*)viewController
                                    browser:(Browser*)browser NS_UNAVAILABLE;
 
+// The base view controller for this coordinator. This is required because the
+// TabStripLegacyCoordinator is instantiated before the BrowserViewController.
+@property(nonatomic, weak, readwrite) UIViewController* baseViewController;
+
 // Delegate for the long press gesture recognizer triggering popup menu.
 @property(nonatomic, weak) id<PopupMenuLongPressDelegate> longPressDelegate;
 
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.mm b/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.mm
index 0e89c3b..8808ec95 100644
--- a/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.mm
+++ b/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.mm
@@ -26,6 +26,7 @@
 @synthesize started = _started;
 @synthesize tabStripController = _tabStripController;
 @synthesize animationWaitDuration = _animationWaitDuration;
+@synthesize baseViewController = _baseViewController;
 
 - (instancetype)initWithBrowser:(Browser*)browser {
   DCHECK(browser);
@@ -81,8 +82,10 @@
   DCHECK(self.presentationProvider);
   TabStripStyle style =
       self.browser->GetBrowserState()->IsOffTheRecord() ? INCOGNITO : NORMAL;
-  self.tabStripController =
-      [[TabStripController alloc] initWithBrowser:self.browser style:style];
+  self.tabStripController = [[TabStripController alloc]
+      initWithBaseViewController:self.baseViewController
+                         browser:self.browser
+                           style:style];
   self.tabStripController.presentationProvider = self.presentationProvider;
   self.tabStripController.animationWaitDuration = self.animationWaitDuration;
   self.tabStripController.longPressDelegate = self.longPressDelegate;
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index 3bd57ae..255a9c0 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-aaa617af2315b22a11ec7b5159225b7f14a66f0f
\ No newline at end of file
+d894d40effa7748a1de9e1d1ec9cebf5b03c22dd
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index a4ec38dc..9878abfc 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-d686f9789f916117ea8260147305816099dc179d
\ No newline at end of file
+b75de431ad6973e7a0384d11dd35b51ed42e7c74
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index 5cb8c00..2b02dc1c 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-507fc52fed5af89dd8420a9f95c7231ffff873b5
\ No newline at end of file
+0c4fb9c620b74fc961cf7409cf10b4423af79e31
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 29783da..f50b81b8 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-77ae8aa36c7ef47ed238a7de580069eb70da14e2
\ No newline at end of file
+66e4cf96d69f2015c698309e082cba0c9fe80b28
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index cb79a2d..63d105b 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-ca29b5f1fad1e839c576563a4b5f452731fd55f2
\ No newline at end of file
+3633971426c95914abdb888879b02980dee987e8
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index ea95448..a5acaec 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-a107e498fe69969c35eddb5709866fd3a22eaa0e
\ No newline at end of file
+942903c48e04de927169db3378e1c71112f39b18
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index 7a3091c..ac758c550 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-ae8261e31c286e9a590a7cdf2b6313467bd93eff
\ No newline at end of file
+c0330d1834e15ae64395c5233526cd8dc2b91298
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index 036b934..8ae26e5 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-f9b11ace8a8cef1033dc240658ef61131ac46056
\ No newline at end of file
+4bf2fb24ad97da6078bac249e8dd236c28d0ea14
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 7fc628d2..d8561ad 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-2ec45101e881357c6c80ffd79b78d1793adac7e7
\ No newline at end of file
+251681ecc02ab3c87cea4a47e78311bc0d630d05
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index a810dcf2..47c5475 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2dfb4a3792d08e09aaec815bbd6d95b06570d08b
\ No newline at end of file
+cb037170f23a783c287d6495e2ab71746553113c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index 252818895..3aec8a6 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-0257b25830500946a5135486adc73e2d5dc744fc
\ No newline at end of file
+cbc62028e7099c3b3b756877f678ca209917900c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 02b18d1..49f3f8d 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-0729d392259fd40afd4565841016ba863917267f
\ No newline at end of file
+105f0d8c1cc774e5eb18b2f68d2220eb699445e4
\ No newline at end of file
diff --git a/ios/showcase/omnibox_popup/fake_autocomplete_suggestion.h b/ios/showcase/omnibox_popup/fake_autocomplete_suggestion.h
index 09d90c0..f1382c3 100644
--- a/ios/showcase/omnibox_popup/fake_autocomplete_suggestion.h
+++ b/ios/showcase/omnibox_popup/fake_autocomplete_suggestion.h
@@ -32,6 +32,7 @@
 
 @property(nonatomic) NSAttributedString* omniboxPreviewText;
 @property(nonatomic) UIImage* matchTypeIcon;
+@property(nonatomic) NSString* matchTypeIconAccessibilityIdentifier;
 @property(nonatomic, getter=isMatchTypeSearch) BOOL matchTypeSearch;
 @property(nonatomic) CrURL* destinationUrl;
 
diff --git a/ios/web_view/internal/autofill/web_view_autofill_client_ios.h b/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
index a0285c45..cbdef07 100644
--- a/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
+++ b/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
@@ -101,9 +101,10 @@
       AddressProfileSavePromptCallback callback) override;
   bool HasCreditCardScanFeature() override;
   void ScanCreditCard(CreditCardScanCallback callback) override;
-  bool TryToShowFastCheckout(const FormData& form,
-                             const FormFieldData& field,
-                             AutofillDriver* driver) override;
+  bool TryToShowFastCheckout(
+      const FormData& form,
+      const FormFieldData& field,
+      base::WeakPtr<AutofillManager> autofill_manager) override;
   void HideFastCheckout(bool allow_further_runs) override;
   bool IsFastCheckoutSupported() override;
   bool IsShowingFastCheckoutUI() override;
diff --git a/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm b/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
index 3b37fcc..21e6ebc 100644
--- a/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
+++ b/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
@@ -271,9 +271,10 @@
   return false;
 }
 
-bool WebViewAutofillClientIOS::TryToShowFastCheckout(const FormData& form,
-                                                     const FormFieldData& field,
-                                                     AutofillDriver* driver) {
+bool WebViewAutofillClientIOS::TryToShowFastCheckout(
+    const FormData& form,
+    const FormFieldData& field,
+    base::WeakPtr<AutofillManager> autofill_manager) {
   return false;
 }
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 8fa91e1..fb07b47 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -6315,21 +6315,6 @@
             ]
         }
     ],
-    "IncognitoDownloadsWarning": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "IncognitoDownloadsWarning"
-                    ]
-                }
-            ]
-        }
-    ],
     "IncognitoNtpRevamp": [
         {
             "platforms": [
@@ -13479,6 +13464,28 @@
             ]
         }
     ],
+    "WebRtcEncoderAsyncEncode": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "chromeos",
+                "chromeos_lacros",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "WebRtcEncoderAsyncEncode"
+                    ]
+                }
+            ]
+        }
+    ],
     "WebRtcMetronome": [
         {
             "platforms": [
diff --git a/third_party/.gitignore b/third_party/.gitignore
index dd0dd2f8..93ef0a6 100644
--- a/third_party/.gitignore
+++ b/third_party/.gitignore
@@ -68,6 +68,7 @@
 /chromeos_text_input
 /chromevox/third_party/sre/src
 /chromite
+/crossbench
 /cld_2/src
 /cld_3/src
 /colorama/src
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
index 86c3bf8c..bfee4e1 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
@@ -413,6 +413,12 @@
         ConsumeComplexSelector(argument, /*in_nested_style_rule=*/false,
                                /*first_in_complex_selector_list=*/false);
     if (selector.empty() || failed_parsing_ || !argument.AtEnd()) {
+      for (const CSSParserToken& token : argument) {
+        if (token.GetType() == kDelimiterToken && token.Delimiter() == '&') {
+          dropped_nest_token_during_forgiving_parsing_ = true;
+          break;
+        }
+      }
       if (in_supports_parsing_) {
         at_supports_drop_invalid_counter.Count();
       }
@@ -766,7 +772,8 @@
     // of SelectorListIsNestContaining().
     wtf_size_t last_index = output_.size() - 1;
     output_[last_index].SetLastInSelectorList(true);
-    if (!SelectorListIsNestContaining(reset_vector.AddedElements().data())) {
+    if (!dropped_nest_token_during_forgiving_parsing_ &&
+        !SelectorListIsNestContaining(reset_vector.AddedElements().data())) {
       output_.back().SetRelation(CSSSelector::kDescendant);
       output_.push_back(
           CSSSelector(parent_rule_for_nesting_, /*is_implicit=*/true));
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.h b/third_party/blink/renderer/core/css/parser/css_selector_parser.h
index 9365fcb..3214cc5 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser.h
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.h
@@ -209,6 +209,11 @@
   // PseudoType of the pseudo-element causing the restriction.
   CSSSelector::PseudoType restricting_pseudo_element_ =
       CSSSelector::kPseudoUnknown;
+  // Whether at any point we dropped an & token during forgiving parsing
+  // (e.g. in :is(!&, .foo)); such selectors are still considered as
+  // nest-containing. Since every top-level selector gets a new CSSParser
+  // instance, we don't need to worry about resetting it.
+  bool dropped_nest_token_during_forgiving_parsing_ = false;
   // If we're _resisting_ the default namespace, it means that we are inside
   // a nested selector (:is(), :where(), etc) where we should _consider_
   // ignoring the default namespace (depending on circumstance). See the
diff --git a/third_party/blink/renderer/core/css/rule_set.cc b/third_party/blink/renderer/core/css/rule_set.cc
index 6afba63..1e1fd36 100644
--- a/third_party/blink/renderer/core/css/rule_set.cc
+++ b/third_party/blink/renderer/core/css/rule_set.cc
@@ -45,6 +45,7 @@
 #include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
 #include "third_party/blink/renderer/core/html/track/text_track_cue.h"
 #include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 
@@ -80,13 +81,13 @@
       case CSSSelector::kPseudoTargetText:
       case CSSSelector::kPseudoGrammarError:
       case CSSSelector::kPseudoSpellingError:
-        if (RuntimeEnabledFeatures::HighlightInheritanceEnabled()) {
+      case CSSSelector::kPseudoHighlight:
+        if (UsesHighlightPseudoInheritance(
+                component->GetPseudoId(component->GetPseudoType()))) {
           return ValidPropertyFilter::kHighlight;
         } else {
           return ValidPropertyFilter::kHighlightLegacy;
         }
-      case CSSSelector::kPseudoHighlight:
-        return ValidPropertyFilter::kHighlight;
       default:
         break;
     }
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index 4ac0f72d..59759861 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -5357,7 +5357,7 @@
   UpdateAllLifecyclePhases();
   unsigned element_count =
       GetStyleEngine().StyleForElementCount() - start_count;
-  ASSERT_EQ(2U, element_count);
+  ASSERT_EQ(1U, element_count);
 
   start_count = GetStyleEngine().StyleForElementCount();
   GetDocument().getElementById("div1")->RemoveChild(
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc
index 7bfbdddb..6904487c 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element.cc
@@ -647,8 +647,12 @@
 
 bool HTMLMediaElement::ShouldReusePlayer(Document& old_document,
                                          Document& new_document) const {
+  // Don't reuse player if the Document Picture-in-Picture API is disabled for
+  // both documents.
   if (!RuntimeEnabledFeatures::DocumentPictureInPictureAPIEnabled(
-          GetExecutionContext())) {
+          old_document.domWindow()->GetExecutionContext()) &&
+      !RuntimeEnabledFeatures::DocumentPictureInPictureAPIEnabled(
+          new_document.domWindow()->GetExecutionContext())) {
     return false;
   }
 
diff --git a/third_party/blink/renderer/core/html/resources/html.css b/third_party/blink/renderer/core/html/resources/html.css
index a2102e7..f0fb658 100644
--- a/third_party/blink/renderer/core/html/resources/html.css
+++ b/third_party/blink/renderer/core/html/resources/html.css
@@ -1295,12 +1295,6 @@
   outline-offset: 1px;
 }
 
-/* ensure that selected links are underlined.*/
-a:-webkit-any-link::target-text {
-    text-decoration:underline;
-    background-color:#E9D2FD;
-}
-
 /* HTML5 ruby elements */
 
 ruby, rt {
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index f706c4b..39c6e0f 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -113,6 +113,7 @@
 #include "third_party/blink/renderer/core/paint/timing/image_element_timing.h"
 #include "third_party/blink/renderer/core/paint/timing/paint_timing_detector.h"
 #include "third_party/blink/renderer/core/scroll/scroll_into_view_util.h"
+#include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/core/style/content_data.h"
 #include "third_party/blink/renderer/core/style/cursor_data.h"
 #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
@@ -2569,41 +2570,12 @@
 
       if (style_->HasPseudoElementStyle(pseudo) ||
           style->HasPseudoElementStyle(pseudo)) {
-        const ComputedStyle* pseudo_old_style = nullptr;
-        const ComputedStyle* pseudo_new_style = nullptr;
-
-        // TODO(rego): Refactor this code so we can call something like
-        // HighlightData()->PseudoStyle(pseudo) and avoid the switch (we could
-        // also avoid the switch in
-        // HighlightPaintingUtils::HighlightPseudoStyle().
-        switch (pseudo) {
-          case kPseudoIdTargetText:
-            pseudo_old_style = style_->HighlightData()
-                                   ? style_->HighlightData()->TargetText()
+        const ComputedStyle* pseudo_old_style =
+            style_->HighlightData() ? style_->HighlightData()->Style(pseudo)
+                                    : nullptr;
+        const ComputedStyle* pseudo_new_style =
+            style->HighlightData() ? style->HighlightData()->Style(pseudo)
                                    : nullptr;
-            pseudo_new_style = style->HighlightData()
-                                   ? style->HighlightData()->TargetText()
-                                   : nullptr;
-            break;
-          case kPseudoIdSpellingError:
-            pseudo_old_style = style_->HighlightData()
-                                   ? style_->HighlightData()->SpellingError()
-                                   : nullptr;
-            pseudo_new_style = style->HighlightData()
-                                   ? style->HighlightData()->SpellingError()
-                                   : nullptr;
-            break;
-          case kPseudoIdGrammarError:
-            pseudo_old_style = style_->HighlightData()
-                                   ? style_->HighlightData()->GrammarError()
-                                   : nullptr;
-            pseudo_new_style = style->HighlightData()
-                                   ? style->HighlightData()->GrammarError()
-                                   : nullptr;
-            break;
-          default:
-            NOTREACHED();
-        }
 
         if (pseudo_old_style && pseudo_new_style) {
           diff.Merge(pseudo_old_style->VisualInvalidationDiff(
@@ -2614,16 +2586,19 @@
       }
     };
 
-    if (RuntimeEnabledFeatures::HighlightInheritanceEnabled()) {
-      // TODO(rego): We don't do anything regarding ::selection, as ::selection
-      // uses its own mechanism for this (see
-      // LayoutObject::InvalidateSelectedChildrenOnStyleChange()). Maybe in the
-      // future we could detect changes here for ::selection too.
+    // See HighlightRegistry for ::highlight() paint invalidation.
+    // TODO(rego): We don't do anything regarding ::selection, as ::selection
+    // uses its own mechanism for this (see
+    // LayoutObject::InvalidateSelectedChildrenOnStyleChange()). Maybe in the
+    // future we could detect changes here for ::selection too.
+    if (UsesHighlightPseudoInheritance(kPseudoIdTargetText)) {
       HighlightPseudoUpdateDiff(kPseudoIdTargetText);
-      if (RuntimeEnabledFeatures::CSSSpellingGrammarErrorsEnabled()) {
-        HighlightPseudoUpdateDiff(kPseudoIdSpellingError);
-        HighlightPseudoUpdateDiff(kPseudoIdGrammarError);
-      }
+    }
+    if (UsesHighlightPseudoInheritance(kPseudoIdSpellingError)) {
+      HighlightPseudoUpdateDiff(kPseudoIdSpellingError);
+    }
+    if (UsesHighlightPseudoInheritance(kPseudoIdGrammarError)) {
+      HighlightPseudoUpdateDiff(kPseudoIdGrammarError);
     }
   }
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_constraints_impl.cc b/third_party/blink/renderer/modules/mediastream/media_constraints_impl.cc
index 26493647..f70677f 100644
--- a/third_party/blink/renderer/modules/mediastream/media_constraints_impl.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_constraints_impl.cc
@@ -110,10 +110,6 @@
 // Google-specific constraint keys for a local video source (getUserMedia).
 const char kNoiseReduction[] = "googNoiseReduction";
 
-// Names used for testing.
-const char kTestConstraint1[] = "valid_and_supported_1";
-const char kTestConstraint2[] = "valid_and_supported_2";
-
 static bool ParseMandatoryConstraintsDictionary(
     const Dictionary& mandatory_constraints_dictionary,
     Vector<NameValueStringConstraint>& mandatory) {
@@ -179,8 +175,7 @@
 static void ParseOldStyleNames(
     ExecutionContext* context,
     const Vector<NameValueStringConstraint>& old_names,
-    MediaTrackConstraintSetPlatform& result,
-    MediaErrorState& error_state) {
+    MediaTrackConstraintSetPlatform& result) {
   if (old_names.size() > 0) {
     UseCounter::Count(context, WebFeature::kOldConstraintsParsed);
   }
@@ -240,14 +235,6 @@
       result.goog_da_echo_cancellation.SetExact(ToBoolean(constraint.value_));
     } else if (constraint.name_.Equals(kNoiseReduction)) {
       result.goog_noise_reduction.SetExact(ToBoolean(constraint.value_));
-    } else if (constraint.name_.Equals(kTestConstraint1) ||
-               constraint.name_.Equals(kTestConstraint2)) {
-      // These constraints are only for testing parsing.
-      // Values 0 and 1 are legal, all others are a ConstraintError.
-      if (!constraint.value_.Equals("0") && !constraint.value_.Equals("1")) {
-        error_state.ThrowConstraintError("Illegal value for constraint",
-                                         constraint.name_);
-      }
     }
     // else: Nothing. Unrecognized constraints are simply ignored.
   }
@@ -256,22 +243,17 @@
 static MediaConstraints CreateFromNamedConstraints(
     ExecutionContext* context,
     Vector<NameValueStringConstraint>& mandatory,
-    const Vector<NameValueStringConstraint>& optional,
-    MediaErrorState& error_state) {
+    const Vector<NameValueStringConstraint>& optional) {
   MediaTrackConstraintSetPlatform basic;
   MediaTrackConstraintSetPlatform advanced;
   MediaConstraints constraints;
-  ParseOldStyleNames(context, mandatory, basic, error_state);
-  if (error_state.HadException())
-    return constraints;
-  // We ignore unknow names and syntax errors in optional constraints.
-  MediaErrorState ignored_error_state;
+  ParseOldStyleNames(context, mandatory, basic);
+  // We ignore unknown names and syntax errors in optional constraints.
   Vector<MediaTrackConstraintSetPlatform> advanced_vector;
   for (const auto& optional_constraint : optional) {
     MediaTrackConstraintSetPlatform advanced_element;
     Vector<NameValueStringConstraint> element_as_list(1, optional_constraint);
-    ParseOldStyleNames(context, element_as_list, advanced_element,
-                       ignored_error_state);
+    ParseOldStyleNames(context, element_as_list, advanced_element);
     if (!advanced_element.IsUnconstrained())
       advanced_vector.push_back(advanced_element);
   }
@@ -367,7 +349,7 @@
   DCHECK(!error_state.HadException());
 
   if (str.length() > kMaxConstraintStringLength) {
-    error_state.ThrowTypeError("Constraint string too long.");
+    error_state.MarkTypeError("Constraint string too long.");
     return false;
   }
   return true;
@@ -378,7 +360,7 @@
   DCHECK(!error_state.HadException());
 
   if (strs.size() > kMaxConstraintStringSeqLength) {
-    error_state.ThrowTypeError("Constraint string sequence too long.");
+    error_state.MarkTypeError("Constraint string sequence too long.");
     return false;
   }
 
@@ -699,7 +681,7 @@
   if (constraints_in->hasOptional() || constraints_in->hasMandatory()) {
     if (!standard_form.IsUnconstrained()) {
       UseCounter::Count(context, WebFeature::kMediaStreamConstraintsOldAndNew);
-      error_state.ThrowTypeError(
+      error_state.MarkTypeError(
           "Malformed constraint: Cannot use both optional/mandatory and "
           "specific or advanced constraints.");
       return MediaConstraints();
@@ -707,12 +689,11 @@
     Vector<NameValueStringConstraint> optional;
     Vector<NameValueStringConstraint> mandatory;
     if (!Parse(constraints_in, optional, mandatory)) {
-      error_state.ThrowTypeError("Malformed constraints object.");
+      error_state.MarkTypeError("Malformed constraints object.");
       return MediaConstraints();
     }
     UseCounter::Count(context, WebFeature::kMediaStreamConstraintsNameValue);
-    return CreateFromNamedConstraints(context, mandatory, optional,
-                                      error_state);
+    return CreateFromNamedConstraints(context, mandatory, optional);
   }
   UseCounter::Count(context, WebFeature::kMediaStreamConstraintsConformant);
   return standard_form;
diff --git a/third_party/blink/renderer/modules/mediastream/media_devices.cc b/third_party/blink/renderer/modules/mediastream/media_devices.cc
index 5efec04..584586c95 100644
--- a/third_party/blink/renderer/modules/mediastream/media_devices.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_devices.cc
@@ -42,7 +42,6 @@
 #include "third_party/blink/renderer/modules/mediastream/crop_target.h"
 #include "third_party/blink/renderer/modules/mediastream/identifiability_metrics.h"
 #include "third_party/blink/renderer/modules/mediastream/input_device_info.h"
-#include "third_party/blink/renderer/modules/mediastream/media_error_state.h"
 #include "third_party/blink/renderer/modules/mediastream/media_stream.h"
 #include "third_party/blink/renderer/modules/mediastream/navigator_media_stream.h"
 #include "third_party/blink/renderer/modules/mediastream/user_media_client.h"
@@ -429,24 +428,17 @@
         surface_type, TokenFromConstraints(options));
   }
   ScriptPromise promise = resolver->Promise();
-  MediaErrorState error_state;
   UserMediaRequest* request =
       UserMediaRequest::Create(window, user_media_client, media_type, options,
-                               callbacks, error_state, surface);
+                               callbacks, exception_state, surface);
   if (!request) {
-    DCHECK(error_state.HadException());
-    if (error_state.CanGenerateException()) {
-      // TODO(crbug.com/1373398): Change this to use
-      // ScriptPromiseResolverWithTracker.
-      error_state.RaiseException(exception_state);
-      return ScriptPromise();
-    }
+    DCHECK(exception_state.HadException());
+    resolver->RecordResultAndLatency(
+        UserMediaRequestResult::kInvalidConstraints);
     RecordIdentifiabilityMetric(
         surface, GetExecutionContext(),
-        IdentifiabilityBenignStringToken(error_state.GetErrorMessage()));
-    resolver->Reject(error_state.CreateError(),
-                     UserMediaRequestResult::kInvalidConstraints);
-    return promise;
+        IdentifiabilityBenignStringToken(exception_state.Message()));
+    return ScriptPromise();
   }
 
   String error_message;
diff --git a/third_party/blink/renderer/modules/mediastream/media_error_state.cc b/third_party/blink/renderer/modules/mediastream/media_error_state.cc
index a3a0167..f316dc42 100644
--- a/third_party/blink/renderer/modules/mediastream/media_error_state.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_error_state.cc
@@ -30,35 +30,18 @@
 
 #include "third_party/blink/renderer/modules/mediastream/media_error_state.h"
 
-#include "third_party/blink/renderer/bindings/modules/v8/v8_union_domexception_overconstrainederror.h"
-#include "third_party/blink/renderer/modules/mediastream/overconstrained_error.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
 
-MediaErrorState::MediaErrorState()
-    : error_type_(kNoError), code_(DOMExceptionCode::kNoError) {}
+MediaErrorState::MediaErrorState() = default;
 
-void MediaErrorState::ThrowTypeError(const String& message) {
+void MediaErrorState::MarkTypeError(const String& message) {
   error_type_ = kTypeError;
   message_ = message;
 }
 
-void MediaErrorState::ThrowDOMException(DOMExceptionCode code,
-                                        const String& message) {
-  error_type_ = kDOMException;
-  code_ = code;
-  message_ = message;
-}
-
-void MediaErrorState::ThrowConstraintError(const String& message,
-                                           const String& constraint) {
-  error_type_ = kConstraintError;
-  message_ = message;
-  constraint_ = constraint;
-}
-
 void MediaErrorState::Reset() {
   error_type_ = kNoError;
 }
@@ -67,61 +50,14 @@
   return error_type_ != kNoError;
 }
 
-bool MediaErrorState::CanGenerateException() {
-  return error_type_ == kTypeError || error_type_ == kDOMException;
-}
-
-void MediaErrorState::RaiseException(ExceptionState& target) {
-  switch (error_type_) {
-    case kNoError:
-      NOTREACHED();
-      break;
-    case kTypeError:
-      target.ThrowTypeError(message_);
-      break;
-    case kDOMException:
-      target.ThrowDOMException(code_, message_);
-      break;
-    case kConstraintError:
-      // This is for the cases where we can't pass back a
-      // NavigatorUserMediaError.
-      // So far, we have this in the constructor of RTCPeerConnection,
-      // which is due to be deprecated.
-      // TODO(hta): Remove this code. https://crbug.com/576581
-      target.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
-                               "Unsatisfiable constraint " + constraint_);
-      break;
-    default:
-      NOTREACHED();
-  }
+void MediaErrorState::Throw(ExceptionState& target) {
+  DCHECK_EQ(error_type_, kTypeError);
+  target.ThrowTypeError(message_);
 }
 
 String MediaErrorState::GetErrorMessage() {
-  switch (error_type_) {
-    case kNoError:
-      NOTREACHED();
-      break;
-    case kTypeError:
-    case kDOMException:
-      return message_;
-    case kConstraintError:
-      // This is for the cases where we can't pass back a
-      // NavigatorUserMediaError.
-      // So far, we have this in the constructor of RTCPeerConnection,
-      // which is due to be deprecated.
-      // TODO(hta): Remove this code. https://crbug.com/576581
-      return "Unsatisfiable constraint " + constraint_;
-    default:
-      NOTREACHED();
-  }
-
-  return String();
-}
-
-V8UnionDOMExceptionOrOverconstrainedError* MediaErrorState::CreateError() {
-  DCHECK_EQ(error_type_, kConstraintError);
-  return MakeGarbageCollected<V8UnionDOMExceptionOrOverconstrainedError>(
-      MakeGarbageCollected<OverconstrainedError>(constraint_, message_));
+  DCHECK_EQ(error_type_, kTypeError);
+  return message_;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/media_error_state.h b/third_party/blink/renderer/modules/mediastream/media_error_state.h
index 0325944..297323d 100644
--- a/third_party/blink/renderer/modules/mediastream/media_error_state.h
+++ b/third_party/blink/renderer/modules/mediastream/media_error_state.h
@@ -32,15 +32,12 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_MEDIA_ERROR_STATE_H_
 
 #include "third_party/blink/renderer/modules/modules_export.h"
-#include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
 
 class ExceptionState;
-class OverconstrainedError;
-class V8UnionDOMExceptionOrOverconstrainedError;
 
 // A class that is able to be used like ExceptionState for carrying
 // information about an error up the stack, but it is up to the higher
@@ -50,24 +47,18 @@
 
  public:
   MediaErrorState();
-  void ThrowTypeError(const String& message);
-  void ThrowDOMException(DOMExceptionCode, const String& message);
-  void ThrowConstraintError(const String& message, const String& constraint);
+  void MarkTypeError(const String& message);
   void Reset();
 
   bool HadException();
-  bool CanGenerateException();
-  void RaiseException(ExceptionState&);
+  void Throw(ExceptionState&);
   String GetErrorMessage();
-  V8UnionDOMExceptionOrOverconstrainedError* CreateError();
 
  private:
-  enum ErrorType { kNoError, kTypeError, kDOMException, kConstraintError };
-  ErrorType error_type_;
+  enum ErrorType { kNoError, kTypeError };
+  ErrorType error_type_ = kNoError;
   String name_;
-  DOMExceptionCode code_;
   String message_;
-  String constraint_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/navigator_media_stream.cc b/third_party/blink/renderer/modules/mediastream/navigator_media_stream.cc
index feb2f2f..f81568eb 100644
--- a/third_party/blink/renderer/modules/mediastream/navigator_media_stream.cc
+++ b/third_party/blink/renderer/modules/mediastream/navigator_media_stream.cc
@@ -35,7 +35,6 @@
 #include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/modules/mediastream/identifiability_metrics.h"
-#include "third_party/blink/renderer/modules/mediastream/media_error_state.h"
 #include "third_party/blink/renderer/modules/mediastream/user_media_client.h"
 #include "third_party/blink/renderer/modules/mediastream/user_media_request.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -105,23 +104,17 @@
     surface = IdentifiableSurface::FromTypeAndToken(
         surface_type, TokenFromConstraints(options));
   }
-  MediaErrorState error_state;
+
   UserMediaRequest* request = UserMediaRequest::Create(
       navigator.DomWindow(), user_media, UserMediaRequestType::kUserMedia,
       options,
       MakeGarbageCollected<V8Callbacks>(success_callback, error_callback),
-      error_state, surface);
+      exception_state, surface);
   if (!request) {
-    DCHECK(error_state.HadException());
-    if (error_state.CanGenerateException()) {
-      error_state.RaiseException(exception_state);
-    } else {
-      error_callback->InvokeAndReportException(nullptr,
-                                               error_state.CreateError());
-    }
+    DCHECK(exception_state.HadException());
     RecordIdentifiabilityMetric(
         surface, navigator.GetExecutionContext(),
-        IdentifiabilityBenignStringToken(error_state.GetErrorMessage()));
+        IdentifiabilityBenignStringToken(exception_state.Message()));
     return;
   }
 
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_request.cc b/third_party/blink/renderer/modules/mediastream/user_media_request.cc
index fd73b86..ceb32c1 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_request.cc
+++ b/third_party/blink/renderer/modules/mediastream/user_media_request.cc
@@ -385,7 +385,7 @@
 MediaConstraints ParseOptions(
     ExecutionContext* execution_context,
     const V8UnionBooleanOrMediaTrackConstraints* options,
-    MediaErrorState& error_state) {
+    ExceptionState& exception_state) {
   if (!options)
     return MediaConstraints();
   switch (options->GetContentType()) {
@@ -396,9 +396,14 @@
         return MediaConstraints();
     case V8UnionBooleanOrMediaTrackConstraints::ContentType::
         kMediaTrackConstraints:
-      return media_constraints_impl::Create(
+      MediaErrorState error_state;
+      auto constraints = media_constraints_impl::Create(
           execution_context, options->GetAsMediaTrackConstraints(),
           error_state);
+      if (error_state.HadException()) {
+        error_state.Throw(exception_state);
+      }
+      return constraints;
   }
   NOTREACHED();
   return MediaConstraints();
@@ -412,29 +417,33 @@
     UserMediaRequestType media_type,
     const MediaStreamConstraints* options,
     Callbacks* callbacks,
-    MediaErrorState& error_state,
+    ExceptionState& exception_state,
     IdentifiableSurface surface) {
-  MediaConstraints audio = ParseOptions(context, options->audio(), error_state);
-  if (error_state.HadException())
+  MediaConstraints audio =
+      ParseOptions(context, options->audio(), exception_state);
+  if (exception_state.HadException()) {
     return nullptr;
+  }
 
-  MediaConstraints video = ParseOptions(context, options->video(), error_state);
-  if (error_state.HadException())
+  MediaConstraints video =
+      ParseOptions(context, options->video(), exception_state);
+  if (exception_state.HadException()) {
     return nullptr;
+  }
 
   std::string display_surface_constraint;
   absl::optional<bool> suppress_local_audio_playback;
 
   if (media_type == UserMediaRequestType::kUserMedia) {
     if (audio.IsNull() && video.IsNull()) {
-      error_state.ThrowTypeError(
+      exception_state.ThrowTypeError(
           "At least one of audio and video must be requested");
       return nullptr;
     } else if (!video.IsNull()) {
       if (video.Basic().pan.HasMandatory() ||
           video.Basic().tilt.HasMandatory() ||
           video.Basic().zoom.HasMandatory()) {
-        error_state.ThrowTypeError(
+        exception_state.ThrowTypeError(
             "Mandatory pan-tilt-zoom constraints are not supported");
         return nullptr;
       }
@@ -456,33 +465,33 @@
     // either a dictionary value or a value of true.
     if (media_type == UserMediaRequestType::kDisplayMediaSet) {
       if (!audio.IsNull()) {
-        error_state.ThrowTypeError("Audio requests are not supported");
+        exception_state.ThrowTypeError("Audio requests are not supported");
         return nullptr;
       } else if (options->preferCurrentTab()) {
-        error_state.ThrowTypeError("preferCurrentTab is not supported");
+        exception_state.ThrowTypeError("preferCurrentTab is not supported");
         return nullptr;
       }
     }
 
     if (video.IsNull()) {
-      error_state.ThrowTypeError("video must be requested");
+      exception_state.ThrowTypeError("video must be requested");
       return nullptr;
     }
 
     if ((!audio.IsNull() && !audio.Advanced().empty()) ||
         !video.Advanced().empty()) {
-      error_state.ThrowTypeError("Advanced constraints are not supported");
+      exception_state.ThrowTypeError("Advanced constraints are not supported");
       return nullptr;
     }
 
     if ((!audio.IsNull() && audio.Basic().HasMin()) || video.Basic().HasMin()) {
-      error_state.ThrowTypeError("min constraints are not supported");
+      exception_state.ThrowTypeError("min constraints are not supported");
       return nullptr;
     }
 
     if ((!audio.IsNull() && audio.Basic().HasExact()) ||
         video.Basic().HasExact()) {
-      error_state.ThrowTypeError("exact constraints are not supported");
+      exception_state.ThrowTypeError("exact constraints are not supported");
       return nullptr;
     }
 
@@ -524,7 +533,7 @@
       options->selfBrowserSurface().AsEnum() ==
           V8SelfCapturePreferenceEnum::Enum::kExclude;
   if (exclude_self_browser_surface && options->preferCurrentTab()) {
-    error_state.ThrowTypeError(
+    exception_state.ThrowTypeError(
         "Self-contradictory configuration (preferCurrentTab and "
         "selfBrowserSurface=exclude).");
     return nullptr;
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_request.h b/third_party/blink/renderer/modules/mediastream/user_media_request.h
index 6adde6a7..8bff462 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_request.h
+++ b/third_party/blink/renderer/modules/mediastream/user_media_request.h
@@ -45,7 +45,6 @@
 namespace blink {
 
 class LocalDOMWindow;
-class MediaErrorState;
 class MediaStreamConstraints;
 class ScriptWrappable;
 class TransferredMediaStreamTrack;
@@ -98,7 +97,7 @@
                                   UserMediaRequestType media_type,
                                   const MediaStreamConstraints* options,
                                   Callbacks*,
-                                  MediaErrorState&,
+                                  ExceptionState&,
                                   IdentifiableSurface surface);
   static UserMediaRequest* CreateForTesting(const MediaConstraints& audio,
                                             const MediaConstraints& video);
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc
index f78877e4..de62aaa 100644
--- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc
+++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc
@@ -414,9 +414,6 @@
   if (frame_deliverer_)
     video_task_runner_->DeleteSoon(FROM_HERE, frame_deliverer_.release());
 
-  if (compositor_)
-    compositor_->StopUsingProvider();
-
   if (video_frame_provider_)
     video_frame_provider_->Stop();
 
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc
index 08cf1df..cbde42a 100644
--- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc
+++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc
@@ -239,13 +239,8 @@
 WebMediaPlayerMSCompositor::~WebMediaPlayerMSCompositor() {
   // Ensured by destructor traits.
   DCHECK(video_frame_compositor_task_runner_->BelongsToCurrentThread());
-
-  if (submitter_) {
-    video_frame_compositor_task_runner_->DeleteSoon(FROM_HERE,
-                                                    std::move(submitter_));
-  } else {
-    DCHECK(!video_frame_provider_client_)
-        << "Must call StopUsingProvider() before dtor!";
+  if (video_frame_provider_client_) {
+    video_frame_provider_client_->StopUsingProvider();
   }
 }
 
@@ -265,7 +260,7 @@
 
 void WebMediaPlayerMSCompositor::InitializeSubmitter() {
   DCHECK(video_frame_compositor_task_runner_->BelongsToCurrentThread());
-  submitter_->Initialize(this, /* is_media_stream = */ true);
+  submitter_->Initialize(this, /*is_media_stream=*/true);
 }
 
 void WebMediaPlayerMSCompositor::SetIsSurfaceVisible(
@@ -632,15 +627,6 @@
           WrapRefCounted(this))));
 }
 
-void WebMediaPlayerMSCompositor::StopUsingProvider() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  PostCrossThreadTask(
-      *video_frame_compositor_task_runner_, FROM_HERE,
-      CrossThreadBindOnce(
-          &WebMediaPlayerMSCompositor::StopUsingProviderInternal,
-          WrapRefCounted(this)));
-}
-
 bool WebMediaPlayerMSCompositor::MapTimestampsToRenderTimeTicks(
     const std::vector<base::TimeDelta>& timestamps,
     std::vector<base::TimeTicks>* wall_clock_times) {
@@ -874,13 +860,6 @@
     video_frame_provider_client_->StopRendering();
 }
 
-void WebMediaPlayerMSCompositor::StopUsingProviderInternal() {
-  DCHECK(video_frame_compositor_task_runner_->BelongsToCurrentThread());
-  if (video_frame_provider_client_)
-    video_frame_provider_client_->StopUsingProvider();
-  video_frame_provider_client_ = nullptr;
-}
-
 void WebMediaPlayerMSCompositor::ReplaceCurrentFrameWithACopyInternal() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   scoped_refptr<media::VideoFrame> current_frame_ref;
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.h b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.h
index 5b75c4aa..735de30 100644
--- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.h
+++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.h
@@ -121,10 +121,6 @@
   void StopRendering();
   void ReplaceCurrentFrameWithACopy();
 
-  // Tell |video_frame_provider_client_| to stop using this instance in
-  // preparation for dtor.
-  void StopUsingProvider();
-
   // Sets a hook to be notified when a new frame is presented, to fulfill a
   // prending video.requestAnimationFrame() request.
   // Can be called from any thread.
@@ -210,7 +206,6 @@
 
   void StartRenderingInternal();
   void StopRenderingInternal();
-  void StopUsingProviderInternal();
   void ReplaceCurrentFrameWithACopyInternal();
 
   void SetAlgorithmEnabledForTesting(bool algorithm_enabled);
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 93008247..7d4cd2d 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -4417,6 +4417,7 @@
 
 # DevTools roll
 crbug.com/1006759 http/tests/devtools/elements/styles-1/edit-value-url-with-color.js [ Crash Failure Pass Timeout ]
+crbug.com/1400243 http/tests/devtools/extensions/extensions-api.js [ Failure Pass ]
 
 #Mixed content autoupgrades make these tests not applicable, since they check for mixed content audio/video
 crbug.com/1025274 external/wpt/mixed-content/gen/top.meta/unset/audio-tag.https.html [ Failure ]
@@ -5266,9 +5267,6 @@
 crbug.com/1237640 http/tests/inspector-protocol/network/disabled-cache-navigation.js [ Pass Timeout ]
 crbug.com/1239175 http/tests/navigation/same-and-different-back.html [ Failure Pass ]
 crbug.com/1239164 http/tests/inspector-protocol/network/navigate-iframe-in2in.js [ Failure Pass ]
-crbug.com/1237909 external/wpt/webrtc-svc/RTCRtpParameters-scalability.html [ Crash Failure Pass Timeout ]
-crbug.com/1237909 [ Mac11-arm64 Release ] external/wpt/webrtc-svc/RTCRtpParameters-scalability-h264.html [ Crash Pass Timeout ]
-crbug.com/1237909 [ Mac12-arm64 Release ] external/wpt/webrtc-svc/RTCRtpParameters-scalability-h264.html [ Crash Pass Timeout ]
 
 # Sheriff 2021-08-19
 crbug.com/1234315 [ Mac ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-overflow-hidden.https.html [ Failure Timeout ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 3441b7b..0e391e3 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -121933,7 +121933,7 @@
       ]
      ],
      "font-synthesis-style-first-letter.html": [
-      "32f037bdb721dc3315787d37dce5c66b7cf1e37c",
+      "1296baf05f9c430cda6c52e5a63e813c79862b9a",
       [
        null,
        [
@@ -121985,7 +121985,7 @@
       ]
      ],
      "font-synthesis-weight-first-letter.html": [
-      "52865540a791189e0adf89fb17f8b43fb830334b",
+      "3d60b2a22847e1b99f1ee53d27c231a41bbc60da",
       [
        null,
        [
@@ -263212,16 +263212,6 @@
    }
   },
   "support": {
-   ".cache": {
-    "gitignore2.json": [
-     "0049f3e5247e64b948c41f5edbd906d61b954feb",
-     []
-    ],
-    "mtime.json": [
-     "0c7d29e6a9cdda61de450dcb3990409922abc5b9",
-     []
-    ]
-   },
    ".gitignore": [
     "d93e645d547894b50149d3726de2654957b6e06f",
     []
@@ -292001,7 +291991,7 @@
       []
      ],
      "font-synthesis-style-first-letter-ref.html": [
-      "f1abcb7bb3f4681ecfc2b135dc5ca6dd6b6b9896",
+      "c4f9b175de79052ad0f29df3be07a33c1072f245",
       []
      ],
      "font-synthesis-style-first-line-ref.html": [
@@ -292017,7 +292007,7 @@
       []
      ],
      "font-synthesis-weight-first-letter-ref.html": [
-      "7ec42f07b7c2bfbd329534bc8401acb5ee33d5f2",
+      "fd29f4134d67d22b6e344dcc7ec8315a32dac2e5",
       []
      ],
      "font-synthesis-weight-first-line-ref.html": [
@@ -347844,7 +347834,7 @@
          []
         ],
         "rc-helper.js": [
-         "ef0da2da1ed8f9684637d48991b2ea9abfb986c9",
+         "7b9e83a1f42afeadf1705607465b8c65aaaeb721",
          []
         ],
         "service-worker.js": [
@@ -370766,11 +370756,11 @@
        []
       ],
       "early-hints-helpers.sub.js": [
-       "3991e8fe9da479dd0f5b4cea537542ba948a8540",
+       "faf6119cf1edd5454aaabd643ffe40d33c589463",
        []
       ],
       "early-hints-test-loader.h2.py": [
-       "aa9188c6dbc3920055095421bc880ec5595b0bbb",
+       "bb987209c50f41f260cc7e5d43901c8777ee8779",
        []
       ],
       "empty-corp-absent.js": [
@@ -370789,6 +370779,14 @@
        "1738466bcb3225df2ec2dc7f58459784ce426a84",
        []
       ],
+      "empty.json": [
+       "0967ef424bce6791893e9a57bb952f80fd536e93",
+       []
+      ],
+      "empty.json.headers": [
+       "1738466bcb3225df2ec2dc7f58459784ce426a84",
+       []
+      ],
       "example.pdf": [
        "7bad251ba7e08efc9c383d88518367a6f204cdd6",
        []
@@ -370849,6 +370847,10 @@
        "daea33160ae4e7b7bd074fb1849f1ba40ce8e715",
        []
       ],
+      "preload-fetch.html": [
+       "2e90f76af143fce7f05eb77bb71f00e0d880ff88",
+       []
+      ],
       "preload-finished-before-final-response.h2.py": [
        "d0b12408d9cff01352380cec5727145dbed6dded",
        []
@@ -402873,7 +402875,7 @@
      ]
     ],
     "back-forward-cache-open-connection.window.js": [
-     "397eadab8763fe884f877da63b75a06c8dcc023a",
+     "10c8482230938d4e4e917d43b924710bd913e574",
      [
       "IndexedDB/back-forward-cache-open-connection.window.html",
       {
@@ -562980,6 +562982,20 @@
        }
       ]
      ],
+     "preload-fetch.h2.window.js": [
+      "121913ed3175d552acf83ba3cb4b19e54b51d75a",
+      [
+       "loading/early-hints/preload-fetch.h2.window.html",
+       {
+        "script_metadata": [
+         [
+          "script",
+          "resources/early-hints-helpers.sub.js"
+         ]
+        ]
+       }
+      ]
+     ],
      "preload-finished-before-final-response.h2.window.js": [
       "c63239be1f59426b35760c26ef47453c9248c59b",
       [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter-ref.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter-ref.html
index f1abcb7..c4f9b17 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter-ref.html
@@ -10,6 +10,7 @@
     .test {
         font-family: "Lato-Medium";
         font-size: 3em;
+        font-kerning: none;
     }
 </style>
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter.html
index 32f037bd..1296baf 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter.html
@@ -16,6 +16,7 @@
     .test {
         font-family: "Lato-Medium";
         font-size: 3em;
+        font-kerning: none;
     }
     p.nosynth::first-letter {
         font-style: italic;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter-ref.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter-ref.html
index 7ec42f0..fd29f413 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter-ref.html
@@ -10,6 +10,7 @@
     .test {
         font-family: "Lato-Medium";
         font-size: 3em;
+        font-kerning: none;
     }
 </style>
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter.html
index 5286554..3d60b2a2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter.html
@@ -16,6 +16,7 @@
     .test {
         font-family: "Lato-Medium";
         font-size: 3em;
+        font-kerning: none;
     }
     p.nosynth::first-letter {
         font-weight: bold;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving-ref.html b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving-ref.html
new file mode 100644
index 0000000..36b07c92
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>Nest-containing in forgiving parsing</title>
+<style>
+  .test {
+    background-color: green;
+    width: 100px;
+    height: 100px;
+    display: grid;
+  }
+
+  body * + * {
+    margin-top: 8px;
+  }
+</style>
+<body>
+  <p>Tests pass if <strong>block is green</strong></p>
+  <div class="test"></div>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html
new file mode 100644
index 0000000..d399142
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Nest-containing in forgiving parsing</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<link rel="match" href="nest-containing-forgiving-ref.html">
+<style>
+  .test {
+    background-color: red;
+    width: 100px;
+    height: 100px;
+    display: grid;
+  }
+
+  .does-not-exist {
+    :is(.test-1, !&) {
+      background-color: green;
+    }
+  }
+
+  body * + * {
+    margin-top: 8px;
+  }
+</style>
+<body>
+  <p>Tests pass if <strong>block is green</strong></p>
+  <div class="test test-1"></div>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-extensions/RTCRtpParameters-adaptivePtime.html b/third_party/blink/web_tests/external/wpt/webrtc-extensions/RTCRtpParameters-adaptivePtime.html
index a0cc989..8a7a8b6b 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc-extensions/RTCRtpParameters-adaptivePtime.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc-extensions/RTCRtpParameters-adaptivePtime.html
@@ -6,12 +6,6 @@
 <script>
   'use strict';
 
-  function getFirstEncoding(param) {
-    const { encodings } = param;
-    assert_equals(encodings.length, 1);
-    return encodings[0];
-  }
-
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
@@ -20,14 +14,14 @@
     });
 
     let param = sender.getParameters();
-    let encoding = getFirstEncoding(param);
+    let encoding = param.encodings[0];
 
     assert_true(encoding.adaptivePtime);
 
     encoding.adaptivePtime = false;
     await sender.setParameters(param);
     param = sender.getParameters();
-    encoding = getFirstEncoding(param);
+    encoding = param.encodings[0];
 
     assert_false(encoding.adaptivePtime);
 
@@ -39,7 +33,7 @@
     const { sender } = pc.addTransceiver('audio', { sendEncodings: [{}] });
 
     const param = sender.getParameters();
-    const encoding = getFirstEncoding(param);
+    const encoding = param.encodings[0];
 
     assert_false(encoding.adaptivePtime);
 
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings-expected.txt
deleted file mode 100644
index 0d37e5e..0000000
--- a/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings-expected.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-This is a testharness.js-based test.
-FAIL sender.getParameters() should return sendEncodings set by addTransceiver() promise_test: Unhandled rejection with value: object "ReferenceError: getFirstEncoding is not defined"
-PASS setParameters() with modified encoding.active should succeed with RTCRtpTransceiverInit
-PASS setParameters() with modified encoding.active should succeed without RTCRtpTransceiverInit
-PASS setParameters() with modified encoding.priority should succeed with RTCRtpTransceiverInit
-PASS setParameters() with modified encoding.priority should succeed without RTCRtpTransceiverInit
-PASS setParameters() with modified encoding.networkPriority should succeed with RTCRtpTransceiverInit
-PASS setParameters() with modified encoding.networkPriority should succeed without RTCRtpTransceiverInit
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings.html b/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings.html
index 37c1b91..1519ee8 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings.html
@@ -25,7 +25,7 @@
 
     const param = sender.getParameters();
     validateSenderRtpParameters(param);
-    const encoding = getFirstEncoding(param);
+    const encoding = param.encodings[0];
 
     assert_equals(encoding.active, false);
     assert_equals(encoding.priority, 'low');
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability-expected.txt
deleted file mode 100644
index 816a2534..0000000
--- a/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability-expected.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-This is a testharness.js-based test.
-PASS Setting and updating scalabilityMode to a legal value should be accepted
-PASS Sender capabilities should include at least some scalability modes
-PASS Not setting sendEncodings results in no mode info before negotiation
-PASS Not setting a scalability mode results in no mode set before negotiation
-FAIL Not setting a scalability mode results in some mode set after negotiation assert_true: expected true got false
-PASS Setting a scalability mode to nonsense throws an exception
-FAIL L3T3 on VP8 should return something other than L3T3 assert_not_equals: got disallowed value "L3T3"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability.html b/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability.html
index d8b1c6e..ff28c2b5e 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability.html
@@ -21,7 +21,7 @@
     });
 
     const param = sender.getParameters();
-    const encoding = getFirstEncoding(param);
+    const encoding = param.encodings[0];
 
     assert_equals(encoding.scalabilityMode, 'L1T3');
 
@@ -29,28 +29,18 @@
     await sender.setParameters(param);
 
     const updatedParam = sender.getParameters();
-    const updatedEncoding = getFirstEncoding(updatedParam);
+    const updatedEncoding = updatedParam.encodings[0];
 
     assert_equals(updatedEncoding.scalabilityMode, 'L1T2');
   }, `Setting and updating scalabilityMode to a legal value should be accepted`);
 
   promise_test(async t => {
-    const capabilities = RTCRtpSender.getCapabilities('video');
-    var svcSupported = false;
-    for (const codec of capabilities.codecs) {
-      if ('scalabilityModes' in codec && codec.scalabilityModes.length > 0) {
-        svcSupported = true;
-      }
-    }
-    assert_true(svcSupported);
-  }, `Sender capabilities should include at least some scalability modes`);
-
-  promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
     const { sender } = pc.addTransceiver('video');
     const param = sender.getParameters();
-    assert_equals(param.encodings.length, 0);
+    const encoding = param.encodings[0];
+    assert_true(!('scalabilityMode' in encoding));
   }, 'Not setting sendEncodings results in no mode info before negotiation');
 
   promise_test(async t => {
@@ -60,30 +50,11 @@
       sendEncodings: [{}],
     });
     const param = sender.getParameters();
-    const encoding = getFirstEncoding(param);
-
+    const encoding = param.encodings[0];
     assert_true(!('scalabilityMode' in encoding));
   }, 'Not setting a scalability mode results in no mode set before negotiation');
 
   promise_test(async t => {
-    const pc1 = new RTCPeerConnection();
-    const pc2 = new RTCPeerConnection();
-    t.add_cleanup(() => pc1.close());
-    t.add_cleanup(() => pc2.close());
-    const { sender } = pc1.addTransceiver('video', {
-      sendEncodings: [{}],
-    });
-    const param = sender.getParameters();
-    const encoding = getFirstEncoding(param);
-
-    exchangeIceCandidates(pc1, pc2);
-    await exchangeOfferAnswer(pc1, pc2);
-    const param2 = sender.getParameters();
-    const encoding2 = getFirstEncoding(param);
-    assert_true('scalabilityMode' in encoding2);
-  }, 'Not setting a scalability mode results in some mode set after negotiation');
-
-  promise_test(async t => {
       const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
     assert_throws_dom('OperationError', () => {
@@ -105,7 +76,7 @@
     });
     // Before negotiation, the mode should be preserved.
     const param = transceiver.sender.getParameters();
-    const encoding = getFirstEncoding(param);
+    const encoding = param.encodings[0];
     assert_true('scalabilityMode' in encoding);
     // If L3T3 is not supported at all, abort test.
     assert_implements_optional(encoding.scalabilityMode === 'L3T3');
diff --git a/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo-expected.txt b/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo-expected.txt
index 1db80ae..d721354 100644
--- a/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo-expected.txt
+++ b/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo-expected.txt
@@ -3,7 +3,7 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 PASS getComputedStyle(link).backgroundColor is red
-PASS internals.updateStyleAndReturnAffectedElementCount() is 2
+PASS internals.updateStyleAndReturnAffectedElementCount() is 1
 PASS getComputedStyle(link).backgroundColor is green
 PASS successfullyParsed is true
 
diff --git a/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo.html b/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo.html
index 615de079..5abc1668 100644
--- a/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo.html
+++ b/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo.html
@@ -24,7 +24,7 @@
 link.href = "not-visited.html";
 
 if (window.internals)
-    shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "2");
+    shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "1");
 
 shouldBe("getComputedStyle(link).backgroundColor", "green");
 </script>
diff --git a/third_party/blink/web_tests/fast/css/invalidation/link-pseudo-expected.txt b/third_party/blink/web_tests/fast/css/invalidation/link-pseudo-expected.txt
index 2686b7a..80aa8db 100644
--- a/third_party/blink/web_tests/fast/css/invalidation/link-pseudo-expected.txt
+++ b/third_party/blink/web_tests/fast/css/invalidation/link-pseudo-expected.txt
@@ -3,7 +3,7 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 PASS internals.computedStyleIncludingVisitedInfo(match).backgroundColor is red
-PASS internals.updateStyleAndReturnAffectedElementCount() is 6
+PASS internals.updateStyleAndReturnAffectedElementCount() is 5
 PASS internals.computedStyleIncludingVisitedInfo(match).backgroundColor is green
 PASS successfullyParsed is true
 
diff --git a/third_party/blink/web_tests/fast/css/invalidation/link-pseudo.html b/third_party/blink/web_tests/fast/css/invalidation/link-pseudo.html
index 5919793..c2f609fb 100644
--- a/third_party/blink/web_tests/fast/css/invalidation/link-pseudo.html
+++ b/third_party/blink/web_tests/fast/css/invalidation/link-pseudo.html
@@ -38,7 +38,7 @@
 if (window.internals) {
     // Ideally, the affected element count should be 2, but there is a _insideLink that
     // causes inheritance to recalc all elements inside <a>.
-    shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "6");
+    shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "5");
     shouldBe("internals.computedStyleIncludingVisitedInfo(match).backgroundColor", "green");
 }
 
diff --git a/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo-expected.txt b/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo-expected.txt
index 5b7de406..8b9254c 100644
--- a/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo-expected.txt
+++ b/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo-expected.txt
@@ -3,7 +3,7 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 PASS internals.computedStyleIncludingVisitedInfo(match).backgroundColor is red
-PASS internals.updateStyleAndReturnAffectedElementCount() is 6
+PASS internals.updateStyleAndReturnAffectedElementCount() is 5
 PASS internals.computedStyleIncludingVisitedInfo(match).backgroundColor is green
 PASS successfullyParsed is true
 
diff --git a/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo.html b/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo.html
index b0b9b9a..8c3af56 100644
--- a/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo.html
+++ b/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo.html
@@ -38,7 +38,7 @@
 if (window.internals) {
     // Ideally, the affected element count should be 2, but there is a _insideLink that
     // causes inheritance to recalc all elements inside <a>.
-    shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "6");
+    shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "5");
     shouldBe("internals.computedStyleIncludingVisitedInfo(match).backgroundColor", "green");
 }
 
diff --git a/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt b/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt
index c8584e1..51ba955 100644
--- a/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt
+++ b/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt
@@ -3,7 +3,7 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 PASS getComputedStyle(link).backgroundColor is red
-PASS internals.updateStyleAndReturnAffectedElementCount() is 2
+PASS internals.updateStyleAndReturnAffectedElementCount() is 1
 PASS getComputedStyle(link).backgroundColor is green
 PASS successfullyParsed is true
 
diff --git a/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo.html b/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo.html
index 1dad263..b9daa2e 100644
--- a/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo.html
+++ b/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo.html
@@ -24,7 +24,7 @@
 link.href = "not-visited.html";
 
 if (window.internals)
-    shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "2");
+    shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "1");
 
 shouldBe("getComputedStyle(link).backgroundColor", "green");
 </script>
diff --git a/third_party/blink/web_tests/http/tests/devtools/extensions/extensions-api-expected.txt b/third_party/blink/web_tests/http/tests/devtools/extensions/extensions-api-expected.txt
index 974a354..e3a66ff 100644
--- a/third_party/blink/web_tests/http/tests/devtools/extensions/extensions-api-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/extensions/extensions-api-expected.txt
@@ -70,6 +70,7 @@
         themeName : "themeNameForTest"
     }
     recorder : {
+        createView : <function>
         registerRecorderExtensionPlugin : <function>
         unregisterRecorderExtensionPlugin : <function>
     }
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html
deleted file mode 100644
index 7f7bda7..0000000
--- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>CSS Pseudo-Elements Test: ::target-text - ensure links are distinct when highlighted.</title>
-<link rel="help" href="https://drafts.csswg.org/css-pseudo/#selectordef-target-text">
-<link rel="match" href="target-text-lime-green-link-ref.html">
-<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
-<style>
-  .ahem { font-family: Ahem; }
-  :any-link {
-    /* The default ::target-text should add back in underlines for links */
-    text-decoration: none;
-  }
-  :any-link::target-text {
-    color: lime;
-    background-color: green;
-  }
-</style>
-<p>PASS if there are two segments of lime squares with an underlined green square between below.</p>
-<a class="ahem" href="example.html"> match me </a>
-<script>
-  window.location.hash = "#:~:text=match%20me";
-</script>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html.ini b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html.ini
deleted file mode 100644
index 8254e87..0000000
--- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[target-text-001.html]
-  expected:
-    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html
deleted file mode 100644
index 36c3f17..0000000
--- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>CSS Pseudo-Elements Test: ::target-text - This test ensures link style can be controlled by author.</title>
-<link rel="help" href="https://drafts.csswg.org/css-pseudo/#selectordef-target-text">
-<link rel="match" href="target-text-lime-green-link-no-underline-ref.html">
-<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
-<style>
-  .ahem { font-family: Ahem; }
-  :any-link::target-text {
-    /* The default ::target-text should add back in underlines for links, but author style should be able to remove it" */
-    color: lime;
-    background-color: green;
-    text-decoration: none;
-  }
-  a:any-link{
-    text-decoration: none;
-  }
-</style>
-<p>PASS if there are two segments of lime squares with a green square, without an underline, between them below.</p>
-<a class="ahem" href="example.html"> match me </a>
-<script>
-  window.location.hash = "#:~:text=match%20me";
-</script>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html.ini b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html.ini
deleted file mode 100644
index 03989af..0000000
--- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[target-text-002.html]
-  expected:
-    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-no-underline-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-no-underline-ref.html
deleted file mode 100644
index bf57235..0000000
--- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-no-underline-ref.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!doctype html>
-<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
-<p>PASS if there are two segments of lime squares with a green square, without an underline, between them below.</p>
-<a style="font-family:Ahem"><span style="color:lime;background:green">match me</span></a>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-ref.html
deleted file mode 100644
index f9eb405..0000000
--- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-ref.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!doctype html>
-<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
-<p>PASS if there are two segments of lime squares with an underlined green square between below.</p>
-<a style="font-family:Ahem; color:lime;background:green;text-decoration: underline;" href="example.html" >match me</span>
diff --git a/third_party/closure_compiler/externs/file_manager_private.js b/third_party/closure_compiler/externs/file_manager_private.js
index 58d6bf00..a19fddc5 100644
--- a/third_party/closure_compiler/externs/file_manager_private.js
+++ b/third_party/closure_compiler/externs/file_manager_private.js
@@ -859,15 +859,16 @@
 
 /**
  * Gets the list of tasks that can be performed over selected files. |entries|
- * Array of selected entries. |sourceUrls| Array of source URLs corresponding to
- * the entries  |callback|
+ * Array of selected entries. |dlpSourceUrls| Array of source URLs corresponding
+ * to the entries, used to check Data Leak Prevention (DLP) restrictions
+ * |callback|
  * @param {!Array<!Entry>} entries
- * @param {!Array<string>} sourceUrls
+ * @param {!Array<string>} dlpSourceUrls
  * @param {function((!chrome.fileManagerPrivate.ResultingTasks|undefined))}
  *     callback The list of matched file tasks for the entries.
  */
 chrome.fileManagerPrivate.getFileTasks = function(
-    entries, sourceUrls, callback) {};
+    entries, dlpSourceUrls, callback) {};
 
 /**
  * Gets the MIME type of an entry.
@@ -1490,12 +1491,16 @@
 /**
  * Invoke Sharesheet for selected files. If not possible, then returns
  * an error via chrome.runtime.lastError. |entries| Array of selected entries.
+ * |launchSource| Source from which sharesheet was invoked. |dlpSourceUrls|
+ * Array of source URLs corresponding to the entries, used to check Data Leak
+ * Prevention (DLP) restrictions.
  * @param {!Array<!Entry>} entries
  * @param {chrome.fileManagerPrivate.SharesheetLaunchSource} launchSource
+ * @param {!Array<string>} dlpSourceUrls
  * @param {function()} callback
  */
 chrome.fileManagerPrivate.invokeSharesheet = function(
-    entries, launchSource, callback) {};
+    entries, launchSource, dlpSourceUrls, callback) {};
 
 /**
  * Adds or removes a list of entries to temporary holding space. Any entries
diff --git a/tools/clang/plugins/RawPtrHelpers.h b/tools/clang/plugins/RawPtrHelpers.h
index 7536268..e56a2872 100644
--- a/tools/clang/plugins/RawPtrHelpers.h
+++ b/tools/clang/plugins/RawPtrHelpers.h
@@ -5,6 +5,8 @@
 #ifndef TOOLS_CLANG_PLUGINS_RAWPTRHELPERS_H_
 #define TOOLS_CLANG_PLUGINS_RAWPTRHELPERS_H_
 
+#include <optional>
+
 #include "Util.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/ASTMatchers/ASTMatchersMacros.h"
@@ -51,11 +53,11 @@
   // be matched by the filter. The exclusion lines specify what to force exclude
   // from the filter. Lazily-constructed regex that matches strings that contain
   // any of the inclusion lines in |file_lines_|.
-  mutable llvm::Optional<llvm::Regex> inclusion_substring_regex_;
+  mutable std::optional<llvm::Regex> inclusion_substring_regex_;
 
   // Lazily-constructed regex that matches strings that contain any of the
   // exclusion lines in |file_lines_|.
-  mutable llvm::Optional<llvm::Regex> exclusion_substring_regex_;
+  mutable std::optional<llvm::Regex> exclusion_substring_regex_;
 };
 
 AST_MATCHER(clang::Type, anyCharType) {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 12e3f6f..7d28710 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -32508,6 +32508,7 @@
   <int value="1068" label="WindowManagementAllowedForUrls"/>
   <int value="1069" label="WindowManagementBlockedForUrls"/>
   <int value="1070" label="OutOfProcessSystemDnsResolutionEnabled"/>
+  <int value="1071" label="ExtensionUnpublishedAvailability"/>
 </enum>
 
 <enum name="EnterprisePoliciesSources">
@@ -35728,6 +35729,7 @@
   <int value="1756" label="PASSWORDSPRIVATE_GETCREDENTIALSWITHREUSEDPASSWORD"/>
   <int value="1757" label="OS_TELEMETRY_GETAUDIOINFO"/>
   <int value="1758" label="OS_TELEMETRY_GETMARKETINGINFO"/>
+  <int value="1759" label="OS_TELEMETRY_GETUSBBUSINFO"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -53306,6 +53308,7 @@
   <int value="15" label="Thumb Strip"/>
   <int value="16" label="Omnibox Most Visited Entry"/>
   <int value="17" label="Pinned Tabs entry"/>
+  <int value="18" label="Tab Strip entry"/>
 </enum>
 
 <enum name="IOSMultiWindowConfiguration">
@@ -64815,6 +64818,7 @@
       label="RemoveUsageOfDeprecatedGaiaSigninEndpoint:enabled"/>
   <int value="2032880973" label="U2FSecurityKeyAPI:disabled"/>
   <int value="2034198538" label="TabHoverCards:enabled"/>
+  <int value="2035351945" label="lacros-selection-policy-ignore"/>
   <int value="2035415906" label="Win11StyleMenus:enabled"/>
   <int value="2035524582" label="WebViewRecordAppDataDirectorySize:disabled"/>
   <int value="2037562553" label="OmniboxBubbleUrlSuggestions:disabled"/>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 7925e7db..bf7bc99 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -263,10 +263,9 @@
 
 <histogram
     name="Media.Audio.Capture.Mac.MicSystemPermission.FixedTime.SinceFirstFailure"
-    units="ms" expires_after="2023-07-16">
-  <owner>grunell@chromium.org</owner>
-  <owner>engedy@chromium.org</owner>
-  <owner>hkamila@chromium.org</owner>
+    units="ms" expires_after="2023-07-01">
+  <owner>toprice@chromium.org</owner>
+  <owner>video-cmi-apis@google.com</owner>
   <owner>webrtc-audio-uma@google.com</owner>
   <summary>
     The time from the first microphone access failure due to blocked system
@@ -279,10 +278,9 @@
 
 <histogram
     name="Media.Audio.Capture.Mac.MicSystemPermission.FixedTime.SinceLastFailure"
-    units="ms" expires_after="2023-03-01">
-  <owner>grunell@chromium.org</owner>
-  <owner>engedy@chromium.org</owner>
-  <owner>hkamila@chromium.org</owner>
+    units="ms" expires_after="2023-07-01">
+  <owner>toprice@chromium.org</owner>
+  <owner>video-cmi-apis@google.com</owner>
   <owner>webrtc-audio-uma@google.com</owner>
   <summary>
     The time from the last microphone access failure due to blocked system
@@ -294,10 +292,9 @@
 </histogram>
 
 <histogram name="Media.Audio.Capture.Mac.MicSystemPermission.Startup"
-    enum="SystemMediaCapturePermission" expires_after="2023-07-02">
-  <owner>grunell@chromium.org</owner>
-  <owner>engedy@chromium.org</owner>
-  <owner>hkamila@chromium.org</owner>
+    enum="SystemMediaCapturePermission" expires_after="2023-07-01">
+  <owner>toprice@chromium.org</owner>
+  <owner>video-cmi-apis@google.com</owner>
   <owner>webrtc-audio-uma@google.com</owner>
   <summary>
     The Mac system permission state for microphone. Logged once at browser
@@ -310,10 +307,9 @@
 
 <histogram
     name="Media.Audio.Capture.Mac.MicSystemPermission.StartupAfterFailure"
-    enum="SystemMediaCapturePermission" expires_after="2023-07-16">
-  <owner>grunell@chromium.org</owner>
-  <owner>engedy@chromium.org</owner>
-  <owner>hkamila@chromium.org</owner>
+    enum="SystemMediaCapturePermission" expires_after="2023-07-01">
+  <owner>toprice@chromium.org</owner>
+  <owner>video-cmi-apis@google.com</owner>
   <owner>webrtc-audio-uma@google.com</owner>
   <summary>
     The Mac system permission state for microphone. Logged once at browser
@@ -327,10 +323,9 @@
 </histogram>
 
 <histogram name="Media.Audio.Capture.Mac.MicSystemPermission.UserMedia"
-    enum="SystemMediaCapturePermission" expires_after="2023-07-02">
-  <owner>grunell@chromium.org</owner>
-  <owner>engedy@chromium.org</owner>
-  <owner>hkamila@chromium.org</owner>
+    enum="SystemMediaCapturePermission" expires_after="2023-07-01">
+  <owner>toprice@chromium.org</owner>
+  <owner>video-cmi-apis@google.com</owner>
   <owner>webrtc-audio-uma@google.com</owner>
   <summary>
     The Mac system permission state for microphone at the time of a user media
@@ -4749,10 +4744,9 @@
 
 <histogram
     name="Media.Video.Capture.Mac.CameraSystemPermission.FixedTime.SinceFirstFailure"
-    units="ms" expires_after="2023-03-01">
-  <owner>grunell@chromium.org</owner>
-  <owner>engedy@chromium.org</owner>
-  <owner>hkamila@chromium.org</owner>
+    units="ms" expires_after="2023-07-01">
+  <owner>toprice@chromium.org</owner>
+  <owner>video-cmi-apis@google.com</owner>
   <summary>
     The time from the first camera access failure due to blocked system
     permission before the last browser restart to startup after browser restart.
@@ -4764,10 +4758,9 @@
 
 <histogram
     name="Media.Video.Capture.Mac.CameraSystemPermission.FixedTime.SinceLastFailure"
-    units="ms" expires_after="2023-03-01">
-  <owner>grunell@chromium.org</owner>
-  <owner>engedy@chromium.org</owner>
-  <owner>hkamila@chromium.org</owner>
+    units="ms" expires_after="2023-07-01">
+  <owner>toprice@chromium.org</owner>
+  <owner>video-cmi-apis@google.com</owner>
   <summary>
     The time from the last camera access failure due to blocked system
     permission before the last browser restart to startup after browser restart.
@@ -4779,9 +4772,8 @@
 
 <histogram name="Media.Video.Capture.Mac.CameraSystemPermission.Startup"
     enum="SystemMediaCapturePermission" expires_after="2023-07-23">
-  <owner>grunell@chromium.org</owner>
-  <owner>engedy@chromium.org</owner>
-  <owner>hkamila@chromium.org</owner>
+  <owner>toprice@chromium.org</owner>
+  <owner>video-cmi-apis@google.com</owner>
   <summary>
     The Mac system permission state for camera. Logged once at browser startup.
     For more information on the values, see
@@ -4793,10 +4785,9 @@
 
 <histogram
     name="Media.Video.Capture.Mac.CameraSystemPermission.StartupAfterFailure"
-    enum="SystemMediaCapturePermission" expires_after="2023-03-01">
-  <owner>grunell@chromium.org</owner>
-  <owner>engedy@chromium.org</owner>
-  <owner>hkamila@chromium.org</owner>
+    enum="SystemMediaCapturePermission" expires_after="2023-07-01">
+  <owner>toprice@chromium.org</owner>
+  <owner>video-cmi-apis@google.com</owner>
   <summary>
     The Mac system permission state for camera. Logged once at browser startup
     if there was a failure accessing the camera due to blocked system permission
@@ -4808,10 +4799,9 @@
 </histogram>
 
 <histogram name="Media.Video.Capture.Mac.CameraSystemPermission.UserMedia"
-    enum="SystemMediaCapturePermission" expires_after="2023-07-16">
-  <owner>grunell@chromium.org</owner>
-  <owner>engedy@chromium.org</owner>
-  <owner>hkamila@chromium.org</owner>
+    enum="SystemMediaCapturePermission" expires_after="2023-07-01">
+  <owner>toprice@chromium.org</owner>
+  <owner>video-cmi-apis@google.com</owner>
   <summary>
     The Mac system permission state for camera at the time of a user media
     request. Logged when the system permission is checked. If the permission is
diff --git a/tools/perf/core/path_util.py b/tools/perf/core/path_util.py
index c4b009e..37fbcb6 100644
--- a/tools/perf/core/path_util.py
+++ b/tools/perf/core/path_util.py
@@ -46,6 +46,10 @@
       GetChromiumSrcDir(), 'third_party', 'catapult', 'common', 'py_utils')
 
 
+def GetCrossBenchDir():
+  return os.path.join(GetChromiumSrcDir(), 'third_party', 'crossbench')
+
+
 def GetPerfDir():
   return os.path.join(GetChromiumSrcDir(), 'tools', 'perf')
 
@@ -100,5 +104,11 @@
     sys.path.insert(1, android_pylib_path)
 
 
+def AddCrossBenchToPath():
+  crossbench_path = GetCrossBenchDir()
+  if crossbench_path not in sys.path:
+    sys.path.insert(1, crossbench_path)
+
+
 def GetExpectationsPath():
   return os.path.join(GetPerfDir(), 'expectations.config')
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index d6c82b9..efaa873 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "4ce6d14e7b3c5e7dbde313802b17a5f317072a83",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/07e128e9a2a37507a7db1de743b7b1b3e27cc930/trace_processor_shell.exe"
+            "hash": "b52651258872fb9c264701e9bf7529166c5764b3",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/45275bed1173d8a0cd0995560ca2882eb7d45e19/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "6373f26144aad58f230d11d6a91efda5a09c9873",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "420c16164a428421562ecd28f7333a9412670bca",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/07e128e9a2a37507a7db1de743b7b1b3e27cc930/trace_processor_shell"
+            "hash": "970b1569ef0444a994cf2e26196d918ce827f65b",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/446be1d134d3b94b33a8f7039fbff1177388da4e/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "5f47ee79e59d00bf3889d30ca52315522c158040",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "2d33a469e9e944fe61887e391ed91fd684034d37",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/07e128e9a2a37507a7db1de743b7b1b3e27cc930/trace_processor_shell"
+            "hash": "ff61c5dcaf3f861e7848c098d91e69d95651b234",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/446be1d134d3b94b33a8f7039fbff1177388da4e/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/crossbench b/tools/perf/crossbench
new file mode 100755
index 0000000..98c79c8
--- /dev/null
+++ b/tools/perf/crossbench
@@ -0,0 +1,15 @@
+#!/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.
+
+import sys
+
+from core import path_util
+path_util.AddCrossBenchToPath()
+
+from crossbench import cli
+
+if __name__ == '__main__':
+  argv = sys.argv
+  cli.CrossBenchCLI().run(argv[1:])
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 90473a4..e4475f6 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -374,13 +374,9 @@
 crbug.com/1297360 [ fuchsia-chrome ] system_health.common_desktop/browse:tools:sheets:2019 [ Skip ]
 crbug.com/1306274 [ fuchsia-chrome ] system_health.common_desktop/browse:media:imgur [ Skip ]
 crbug.com/1306280 [ fuchsia-chrome ] system_health.common_desktop/load:media:google_images:2018 [ Skip ]
-crbug.com/1302694 [ mac ] system_health.common_desktop/browse:tools:photoshop:2021 [ Skip ]
-crbug.com/1302694 [ mac ] system_health.common_desktop/browse:tools:photoshop_warm:2021 [ Skip ]
 crbug.com/1348057 [ mac ] system_health.common_desktop/browse:tools:docs_scrolling [ Skip ]
 crbug.com/1360312 [ win-laptop ] system_health.common_desktop/browse:tools:photoshop:2021 [ Skip ]
 crbug.com/1368494 [ chromeos ] system_health.common_desktop/browse:tools:photoshop:2021 [ Skip ]
-crbug.com/1360314 [ win ] system_health.common_desktop/browse:tools:photoshop_warm:2021 [ Skip ]
-crbug.com/1360314 [ linux ] system_health.common_desktop/browse:tools:photoshop_warm:2021 [ Skip ]
 crbug.com/1362017 system_health.common_desktop/browse:tech:discourse_infinite_scroll:2018 [ Skip ]
 crbug.com/1393414 [ linux ] system_health.common_desktop/browse:tools:autocad:2021 [ Skip ]
 
@@ -625,11 +621,7 @@
 crbug.com/1211795 [ mac ] v8.browsing_desktop/browse:media:pinterest:2018 [ Skip ]
 crbug.com/1211795 [ mac ] v8.browsing_desktop/browse:social:twitter_infinite_scroll:2018 [ Skip ]
 crbug.com/1211795 [ mac ] v8.browsing_desktop/browse:social:facebook_infinite_scroll:2018 [ Skip ]
-crbug.com/1302694 [ mac ] v8.browsing_desktop/browse:tools:photoshop:2021 [ Skip ]
-crbug.com/1302694 [ mac ] v8.browsing_desktop/browse:tools:photoshop_warm:2021 [ Skip ]
 crbug.com/1360312 [ win-laptop ] v8.browsing_desktop/browse:tools:photoshop:2021 [ Skip ]
-crbug.com/1360314 [ win ] v8.browsing_desktop/browse:tools:photoshop_warm:2021 [ Skip ]
-crbug.com/1360314 [ linux ] v8.browsing_desktop/browse:tools:photoshop_warm:2021 [ Skip ]
 crbug.com/1362017 v8.browsing_desktop/browse:tech:discourse_infinite_scroll:2018 [ Skip ]
 crbug.com/1393414 [ linux ] v8.browsing_desktop/browse:tools:autocad:2021 [ Skip ]
 
@@ -663,12 +655,8 @@
 crbug.com/1211795 [ mac ] v8.browsing_desktop-future/browse:media:pinterest:2018 [ Skip ]
 crbug.com/1211795 [ mac ] v8.browsing_desktop-future/browse:social:twitter_infinite_scroll:2018 [ Skip ]
 crbug.com/1211795 [ mac ] v8.browsing_desktop-future/browse:social:facebook_infinite_scroll:2018 [ Skip ]
-crbug.com/1302694 [ mac ] v8.browsing_desktop-future/browse:tools:photoshop:2021 [ Skip ]
 crbug.com/1380407 [ chromeos ] v8.browsing_desktop-future/browse:tools:photoshop:2021 [ Skip ]
-crbug.com/1302694 [ mac ] v8.browsing_desktop-future/browse:tools:photoshop_warm:2021 [ Skip ]
 crbug.com/1360312 [ win-laptop ] v8.browsing_desktop-future/browse:tools:photoshop:2021 [ Skip ]
-crbug.com/1360314 [ win ] v8.browsing_desktop-future/browse:tools:photoshop_warm:2021 [ Skip ]
-crbug.com/1360314 [ linux ] v8.browsing_desktop-future/browse:tools:photoshop_warm:2021 [ Skip ]
 crbug.com/1362017 v8.browsing_desktop-future/browse:tech:discourse_infinite_scroll:2018 [ Skip ]
 crbug.com/1393414 [ linux ] v8.browsing_desktop-future/browse:tools:autocad:2021 [ Skip ]
 
diff --git a/ui/chromeos/file_manager_strings.grdp b/ui/chromeos/file_manager_strings.grdp
index 967c5dab..cd1ff8d 100644
--- a/ui/chromeos/file_manager_strings.grdp
+++ b/ui/chromeos/file_manager_strings.grdp
@@ -354,6 +354,9 @@
   <message name="IDS_FILE_BROWSER_CONFIGURE_VOLUME_BUTTON_LABEL" desc="Title of the action for configuring the selected volume.">
     Configure
   </message>
+  <message name="IDS_FILE_BROWSER_UNMOUNT_BUTTON_LABEL" desc="Title of the action for unmounting a given filesystem.">
+    Eject <ph name="TARGET_NAME">$1<ex>zipfile.zip</ex></ph>
+  </message>
   <message name="IDS_FILE_BROWSER_UNMOUNT_DEVICE_BUTTON_LABEL" desc="Title of the action for unmounting removable device.">
     Eject device
   </message>
diff --git a/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_UNMOUNT_BUTTON_LABEL.png.sha1 b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_UNMOUNT_BUTTON_LABEL.png.sha1
new file mode 100644
index 0000000..1e5407a
--- /dev/null
+++ b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_UNMOUNT_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@
+db01c284f5acb6629f37cab811e366b25ab3f434
\ No newline at end of file
diff --git a/ui/file_manager/file_manager/foreground/js/BUILD.gn b/ui/file_manager/file_manager/foreground/js/BUILD.gn
index 83a2dfd..677e1f2 100644
--- a/ui/file_manager/file_manager/foreground/js/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/BUILD.gn
@@ -845,6 +845,8 @@
     "//ui/file_manager/file_manager/common/js:api",
     "//ui/file_manager/file_manager/common/js:files_app_entry_types",
     "//ui/file_manager/file_manager/common/js:util",
+    "//ui/file_manager/file_manager/common/js:volume_manager_types",
+    "//ui/file_manager/file_manager/externs:volume_manager",
   ]
 }
 
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager.js b/ui/file_manager/file_manager/foreground/js/file_manager.js
index 76f3719..4a3d4375 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager.js
@@ -1273,8 +1273,7 @@
     if (util.isGuestOsEnabled()) {
       this.guestOsController_ = new GuestOsController(
           this.directoryModel_, assert(this.directoryTree),
-          this.volumeManager_.isDisabled(
-              VolumeManagerCommon.VolumeType.GUEST_OS));
+          this.volumeManager_);
       await this.guestOsController_.refresh();
     }
   }
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
index 7bcf54c..1d1d11e 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
@@ -1897,8 +1897,11 @@
       execute(event, fileManager) {
         const entries = fileManager.selectionHandler.selection.entries;
         const launchSource = CommandUtil.getSharesheetLaunchSource(event);
+        const dlpSourceUrls =
+            fileManager.metadataModel.getCache(entries, ['sourceUrl'])
+                .map(m => m.sourceUrl || '');
         chrome.fileManagerPrivate.invokeSharesheet(
-            entries, launchSource, () => {
+            entries, launchSource, dlpSourceUrls, () => {
               if (chrome.runtime.lastError) {
                 console.warn(chrome.runtime.lastError.message);
                 return;
diff --git a/ui/file_manager/file_manager/foreground/js/guest_os_controller.js b/ui/file_manager/file_manager/foreground/js/guest_os_controller.js
index d5f5118..4e130427 100644
--- a/ui/file_manager/file_manager/foreground/js/guest_os_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/guest_os_controller.js
@@ -5,6 +5,8 @@
 import {listMountableGuests} from '../../common/js/api.js';
 import {GuestOsPlaceholder} from '../../common/js/files_app_entry_types.js';
 import {util} from '../../common/js/util.js';
+import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js';
+import {VolumeManager} from '../../externs/volume_manager.js';
 
 import {DirectoryModel} from './directory_model.js';
 import {NavigationModelFakeItem, NavigationModelItemType} from './navigation_list_model.js';
@@ -18,10 +20,9 @@
   /**
    * @param {!DirectoryModel} directoryModel DirectoryModel.
    * @param {!DirectoryTree} directoryTree DirectoryTree.
-   * @param {boolean} disabled Whether the Guest OS item should be disabled.
-   *     Defaults to false.
+   * @param {!VolumeManager} volumeManager VolumeManager.
    */
-  constructor(directoryModel, directoryTree, disabled = false) {
+  constructor(directoryModel, directoryTree, volumeManager) {
     if (!util.isGuestOsEnabled()) {
       console.warn('Created a guest os controller when it\'s not enabled');
     }
@@ -31,8 +32,8 @@
     /** @private @const */
     this.directoryTree_ = directoryTree;
 
-    /** @private @const {boolean} */
-    this.disabled_ = disabled;
+    /** @private @const {!VolumeManager} */
+    this.volumeManager_ = volumeManager;
 
     chrome.fileManagerPrivate.onMountableGuestsChanged.addListener(
         this.onMountableGuestsChanged.bind(this));
@@ -58,7 +59,13 @@
       const navigationModelItem = new NavigationModelFakeItem(
           guest.displayName, NavigationModelItemType.GUEST_OS,
           new GuestOsPlaceholder(guest.displayName, guest.id, guest.vmType));
-      navigationModelItem.disabled = this.disabled_;
+      if (guest.vmType == chrome.fileManagerPrivate.VmType.ARCVM) {
+        navigationModelItem.disabled = this.volumeManager_.isDisabled(
+            VolumeManagerCommon.VolumeType.ANDROID_FILES);
+      } else {
+        navigationModelItem.disabled = this.volumeManager_.isDisabled(
+            VolumeManagerCommon.VolumeType.GUEST_OS);
+      }
       return navigationModelItem;
     });
 
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
index d3d6b56..c628789 100644
--- a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
+++ b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
@@ -701,6 +701,8 @@
         const androidVolume =
             getSingleVolume(VolumeManagerCommon.VolumeType.ANDROID_FILES);
         if (androidVolume) {
+          androidVolume.disabled = this.volumeManager_.isDisabled(
+              VolumeManagerCommon.VolumeType.ANDROID_FILES);
           guestOsVolumes = guestOsVolumes.concat(androidVolume);
         }
       }
diff --git a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
index ada33cd..8d35a9e 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {dispatchSimpleEvent, getPropertyDescriptor, PropertyKind} from 'chrome://resources/ash/common/cr_deprecated.js';
 import {assert, assertNotReached} from 'chrome://resources/ash/common/assert.js';
+import {dispatchSimpleEvent, getPropertyDescriptor, PropertyKind} from 'chrome://resources/ash/common/cr_deprecated.js';
 
 import {maybeShowTooltip} from '../../../common/js/dom_utils.js';
 import {FileType} from '../../../common/js/file_type.js';
 import {VolumeEntry} from '../../../common/js/files_app_entry_types.js';
 import {vmTypeToIconName} from '../../../common/js/icon_util.js';
 import {metrics} from '../../../common/js/metrics.js';
-import {str, util} from '../../../common/js/util.js';
+import {strf, util} from '../../../common/js/util.js';
 import {VolumeManagerCommon} from '../../../common/js/volume_manager_types.js';
 import {FileOperationManager} from '../../../externs/background/file_operation_manager.js';
 import {FilesAppDirEntry} from '../../../externs/files_app_entry_interfaces.js';
@@ -790,13 +790,15 @@
    * Set up eject button. It is placed as the last element of the elements that
    * compose the tree row content.
    * @param {!HTMLElement} rowElement Tree row element.
+   * @param {string} targetLabel Label for the ejectable target.
    * @private
    */
-  setupEjectButton_(rowElement) {
+  setupEjectButton_(rowElement, targetLabel) {
     const ejectButton = document.createElement('cr-button');
 
     ejectButton.className = 'root-eject align-right-icon';
-    ejectButton.setAttribute('aria-label', str('UNMOUNT_DEVICE_BUTTON_LABEL'));
+    ejectButton.setAttribute(
+        'aria-label', strf('UNMOUNT_BUTTON_LABEL', targetLabel));
     ejectButton.setAttribute('tabindex', '0');
 
     // Block mouse handlers, handle click.
@@ -987,7 +989,7 @@
     this.disabled = modelItem.disabled;
 
     if (rootType === VolumeManagerCommon.RootType.REMOVABLE) {
-      this.setupEjectButton_(this.rowElement);
+      this.setupEjectButton_(this.rowElement, modelItem.label);
 
       // For removable add menus for roots to be able to unmount, format, etc.
       if (tree.contextMenuForRootItems) {
@@ -1151,7 +1153,7 @@
       // This placeholder is added to allow to put textbox before eject button
       // while executing renaming action on external drive.
       this.setupRenamePlaceholder_(this.rowElement);
-      this.setupEjectButton_(this.rowElement);
+      this.setupEjectButton_(this.rowElement, modelItem.label);
     }
 
     // Sets up context menu of the item.
diff --git a/ui/file_manager/integration_tests/file_manager/dlp.js b/ui/file_manager/integration_tests/file_manager/dlp.js
index b6b6ea9..1cbafcf 100644
--- a/ui/file_manager/integration_tests/file_manager/dlp.js
+++ b/ui/file_manager/integration_tests/file_manager/dlp.js
@@ -7,6 +7,7 @@
 import {testcase} from '../testcase.js';
 
 import {navigateWithDirectoryTree, openAndWaitForClosingDialog, remoteCall, setupAndWaitUntilReady} from './background.js';
+import {FakeTask} from './tasks.js';
 import {BASIC_ANDROID_ENTRY_SET, BASIC_LOCAL_ENTRY_SET} from './test_data.js';
 
 /**
@@ -141,56 +142,70 @@
           ':not([hidden]):not([disabled])');
 };
 
+// Filters used for the following save-as and file-open tests.
+// Rows in `My files`
+const downloadsRow = ['Downloads', '--', 'Folder'];
+const playFilesRow = ['Play files', '--', 'Folder'];
+const linuxFilesRow = ['Linux files', '--', 'Folder'];
+// Dialog buttons
+const okButton = '.button-panel button.ok:enabled';
+const disabledOkButton = '.button-panel button.ok:disabled';
+const cancelButton = '.button-panel button.cancel';
+
 /**
- * Tests the save dialogs properly show DLP blocked volumes/directories. If a
- * volume is blocked by DLP, it should be marked as disabled both in the
- * navigation list and in the details list. If such a volume is selected, the
- * "Open" dialog button should be disabled, i.e. changing the directory is
- * prevented.
+ * Tests the save dialogs properly show DLP blocked Play files, before
+ * and after being mounted, both in the navigation list and in
+ * the details list.
  */
-testcase.saveAsDlpRestrictedDirectory = async () => {
+testcase.saveAsDlpRestrictedAndroid = async () => {
   // Setup the restrictions.
   await sendTestMessage({name: 'setBlockedArc'});
 
-  const okButton = '.button-panel button.ok:enabled';
-  const disabledOkButton = '.button-panel button.ok:disabled';
-  const cancelButton = '.button-panel button.cancel';
-
-  // Add entries to Downloads.
-  await addEntries(['local'], [ENTRIES.hello]);
-
   const closer = async (dialog) => {
-    // Verify that the button is enabled when a file is selected.
-    await remoteCall.waitUntilSelected(dialog, ENTRIES.hello.targetPath);
-    await remoteCall.waitForElement(dialog, okButton);
-
     // Select My Files folder and wait for file list to display Downloads, Play
     // files, and Linux files.
     await navigateWithDirectoryTree(dialog, '/My files');
-    const downloadsRow = ['Downloads', '--', 'Folder'];
-    const playFilesRow = ['Play files', '--', 'Folder'];
-    const linuxFilesRow = ['Linux files', '--', 'Folder'];
+
     await remoteCall.waitForFiles(
         dialog, [downloadsRow, playFilesRow, linuxFilesRow],
         {ignoreFileSize: true, ignoreLastModifiedTime: true});
 
     // Only one directory, Android files, should be disabled, both as the tree
     // item and the directory in the main list.
-    const playFilesInFileList = '.directory[disabled][file-name="Play files"]';
-    const playFilesInDirectoryTree = '#directory-tree .tree-item[disabled] ' +
+    const guestName = 'Play files';
+    const disabledDirectory = `.directory[disabled][file-name="${guestName}"]`;
+    const disabledRealTreeItem = '#directory-tree .tree-item[disabled] ' +
         '.icon[volume-type-icon="android_files"]';
-    await remoteCall.waitForElementsCount(dialog, [playFilesInFileList], 1);
-    await remoteCall.waitForElementsCount(
-        dialog, [playFilesInDirectoryTree], 1);
+    const disabledFakeTreeItem = '#directory-tree .tree-item[disabled] ' +
+        '[root-type-icon=android_files]';
+    await remoteCall.waitForElement(dialog, disabledDirectory);
+    await remoteCall.waitForElement(dialog, disabledRealTreeItem);
 
     // Verify that the button is enabled when a non-blocked volume is selected.
     await remoteCall.waitUntilSelected(dialog, 'Downloads');
     await remoteCall.waitForElement(dialog, okButton);
 
     // Verify that the button is disabled when a blocked volume is selected.
-    await remoteCall.waitUntilSelected(dialog, 'Play files');
+    await remoteCall.waitUntilSelected(dialog, guestName);
     await remoteCall.waitForElement(dialog, disabledOkButton);
 
+    // Unmount Play files and mount ARCVM.
+    await sendTestMessage({name: 'unmountPlayFiles'});
+    const guestId = await sendTestMessage({
+      name: 'registerMountableGuest',
+      displayName: guestName,
+      canMount: true,
+      vmType: 'arcvm',
+    });
+
+    // Wait for the placeholder to appear, which should be disabled. The
+    // directory shouldn't appear in the details list.
+    // TODO(b/267133288): Check why the dir doesn't appear and fix if necessary.
+    await remoteCall.waitAndClickElement(dialog, disabledFakeTreeItem);
+    await remoteCall.waitForFiles(
+        dialog, [downloadsRow, linuxFilesRow],
+        {ignoreFileSize: true, ignoreLastModifiedTime: true});
+
     // Click the close button to dismiss the dialog.
     await remoteCall.waitAndClickElement(dialog, [cancelButton]);
   };
@@ -198,7 +213,7 @@
   chrome.test.assertEq(
       undefined,
       await openAndWaitForClosingDialog(
-          {type: 'saveFile'}, 'downloads', [ENTRIES.hello], closer));
+          {type: 'saveFile'}, 'downloads', [], closer));
 };
 
 /**
@@ -211,10 +226,6 @@
   // Setup the restrictions.
   await sendTestMessage({name: 'setBlockedPluginVM'});
 
-  const okButton = '.button-panel button.ok:enabled';
-  const disabledOkButton = '.button-panel button.ok:disabled';
-  const cancelButton = '.button-panel button.cancel';
-
   const guestName = 'JennyAnyDots';
   const guestId = await sendTestMessage({
     name: 'registerMountableGuest',
@@ -226,9 +237,6 @@
   const closer = async (dialog) => {
     // Select My Files folder and wait for file list.
     await navigateWithDirectoryTree(dialog, '/My files');
-    const downloadsRow = ['Downloads', '--', 'Folder'];
-    const playFilesRow = ['Play files', '--', 'Folder'];
-    const linuxFilesRow = ['Linux files', '--', 'Folder'];
     const guestFilesRow = [guestName, '--', 'Folder'];
     await remoteCall.waitForFiles(
         dialog, [downloadsRow, playFilesRow, linuxFilesRow, guestFilesRow],
@@ -293,10 +301,6 @@
   // Setup the restrictions.
   await sendTestMessage({name: 'setBlockedCrostini'});
 
-  const okButton = '.button-panel button.ok:enabled';
-  const disabledOkButton = '.button-panel button.ok:disabled';
-  const cancelButton = '.button-panel button.cancel';
-
   // Add entries to Downloads.
   await addEntries(['local'], [ENTRIES.hello]);
 
@@ -308,9 +312,6 @@
     // Select My Files folder and wait for file list to display Downloads, Play
     // files, and Linux files.
     await navigateWithDirectoryTree(dialog, '/My files');
-    const downloadsRow = ['Downloads', '--', 'Folder'];
-    const playFilesRow = ['Play files', '--', 'Folder'];
-    const linuxFilesRow = ['Linux files', '--', 'Folder'];
     await remoteCall.waitForFiles(
         dialog, [downloadsRow, playFilesRow, linuxFilesRow],
         {ignoreFileSize: true, ignoreLastModifiedTime: true});
@@ -359,8 +360,6 @@
  * opened in the requested path.
  */
 testcase.saveAsNonDlpRestricted = async () => {
-  const cancelButton = '.button-panel button.cancel';
-
   // Add entries to Play files.
   await addEntries(['android_files'], BASIC_ANDROID_ENTRY_SET);
 
@@ -386,8 +385,6 @@
  * but rather in the default display root.
  */
 testcase.saveAsDlpRestrictedRedirectsToMyFiles = async () => {
-  const cancelButton = '.button-panel button.cancel';
-
   // Add entries to Downloads and Play files.
   await addEntries(['local'], [ENTRIES.hello]);
   await addEntries(['android_files'], BASIC_ANDROID_ENTRY_SET);
@@ -438,10 +435,6 @@
   await sendTestMessage({name: 'setIsRestrictedByAnyRuleRestrictions'});
   await sendTestMessage({name: 'setIsRestrictedDestinationRestriction'});
 
-  const okButton = '.button-panel button.ok:enabled';
-  const disabledOkButton = '.button-panel button.ok:disabled';
-  const cancelButton = '.button-panel button.cancel';
-
   const closer = async (dialog) => {
     // Wait for the file list to appear.
     await remoteCall.waitForElement(dialog, '#file-list');
@@ -507,10 +500,6 @@
   await sendTestMessage({name: 'setIsRestrictedByAnyRuleRestrictions'});
   await sendTestMessage({name: 'setIsRestrictedDestinationRestriction'});
 
-  const enabledOkButton = '.button-panel button.ok:enabled';
-  const disabledOkButton = '.button-panel button.ok:disabled';
-  const cancelButton = '.button-panel button.cancel';
-
   const closer = async (dialog) => {
     // Wait for directoryA to appear.
     await remoteCall.waitForElement(
@@ -564,5 +553,47 @@
     fileNames: [ENTRIES.directoryA.targetPath],
     openType: 'open',
   });
-  await remoteCall.waitAndClickElement(dialog, enabledOkButton);
-};
\ No newline at end of file
+  await remoteCall.waitAndClickElement(dialog, okButton);
+};
+
+/**
+ * Tests that DLP disabled file tasks are shown as disabled in the menu.
+ */
+testcase.fileTasksDlpRestricted = async () => {
+  const entry = ENTRIES.hello;
+  // Open Files app.
+  const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, [entry], []);
+  // Override file tasks so that some are DLP disabled.
+  const fakeTasks = [
+    new FakeTask(
+        true, {appId: 'dummyId1', taskType: 'file', actionId: 'open-with'},
+        'DummyTask1', false, true),
+    new FakeTask(
+        false, {appId: 'dummyId2', taskType: 'file', actionId: 'open-with'},
+        'DummyTask2', false, false),
+    new FakeTask(
+        false, {appId: 'dummyId3', taskType: 'file', actionId: 'open-with'},
+        'DummyTask3', false, true),
+  ];
+  await remoteCall.callRemoteTestUtil('overrideTasks', appId, [fakeTasks]);
+
+  // Select file.
+  await remoteCall.waitUntilSelected(appId, entry.nameText);
+
+  // Check that multiple tasks are available and then click on "Open ▼" button.
+  await remoteCall.waitAndClickElement(appId, '#tasks[multiple]');
+
+  // Wait for dropdown menu to show.
+  await remoteCall.waitForElement(
+      appId, '#tasks-menu:not([hidden]) cr-menu-item');
+
+  // Verify that the first and third tasks are disabled, and the second one is
+  // not.
+  await remoteCall.waitForElement(
+      appId, ['#tasks-menu:not([hidden]) cr-menu-item[disabled]:nth-child(1)']);
+  await remoteCall.waitForElement(
+      appId,
+      ['#tasks-menu:not([hidden]) cr-menu-item:not([disabled]):nth-child(2)']);
+  await remoteCall.waitForElement(
+      appId, ['#tasks-menu:not([hidden]) cr-menu-item[disabled]:nth-child(3)']);
+};
diff --git a/ui/file_manager/integration_tests/file_manager/drive_specific.js b/ui/file_manager/integration_tests/file_manager/drive_specific.js
index bac8e36..5bf6b66 100644
--- a/ui/file_manager/integration_tests/file_manager/drive_specific.js
+++ b/ui/file_manager/integration_tests/file_manager/drive_specific.js
@@ -1298,3 +1298,27 @@
   // Ensure the file was unpinned prior to deleting.
   await remoteCall.expectDriveItemPinnedStatus(appId, '/root/test.txt', false);
 };
+
+/**
+ * Test that when files get deleted in the cloud, they get unpinned after being
+ * deleted.
+ */
+testcase.driveCloudDeleteUnpinsItem = async () => {
+  const appId = await setupAndWaitUntilReady(RootPath.DRIVE);
+
+  // Select test.txt which is already pinned.
+  await remoteCall.waitAndClickElement(
+      appId, '#file-list [file-name="test.txt"]');
+  await remoteCall.waitForElement(
+      appId, '[file-name="test.txt"][selected] xf-icon[type=offline]');
+
+  // Ensure the metadata for the file is set to pinned.
+  await remoteCall.expectDriveItemPinnedStatus(appId, '/root/test.txt', true);
+
+  await remoteCall.sendDriveCloudDeleteEvent(appId, '/root/test.txt');
+  await remoteCall.waitForElementLost(
+      appId, '#file-list [file-name="test.txt"]');
+
+  // Ensure the file was unpinned prior to deleting.
+  await remoteCall.expectDriveItemPinnedStatus(appId, '/root/test.txt', false);
+};
diff --git a/ui/file_manager/integration_tests/file_manager/tasks.js b/ui/file_manager/integration_tests/file_manager/tasks.js
index 3e730dd..dddb9c3 100644
--- a/ui/file_manager/integration_tests/file_manager/tasks.js
+++ b/ui/file_manager/integration_tests/file_manager/tasks.js
@@ -19,14 +19,18 @@
    * @param {string=} opt_title Title of the task.
    * @param {boolean=} opt_isGenericFileHandler Whether the task is a generic
    *     file handler.
+   * @param {boolean=} opt_isDlpBlocked Whether the task is blocked by DLP.
    */
-  constructor(isDefault, descriptor, opt_title, opt_isGenericFileHandler) {
+  constructor(
+      isDefault, descriptor, opt_title, opt_isGenericFileHandler,
+      opt_isDlpBlocked) {
     this.driveApp = false;
     this.iconUrl = 'chrome://theme/IDR_DEFAULT_FAVICON';  // Dummy icon
     this.isDefault = isDefault;
     this.descriptor = descriptor;
     this.title = opt_title;
     this.isGenericFileHandler = opt_isGenericFileHandler || false;
+    this.isDlpBlocked = opt_isDlpBlocked || false;
     Object.freeze(this);
   }
 }
diff --git a/ui/file_manager/integration_tests/remote_call.js b/ui/file_manager/integration_tests/remote_call.js
index 4199ec8..1ea7f3d 100644
--- a/ui/file_manager/integration_tests/remote_call.js
+++ b/ui/file_manager/integration_tests/remote_call.js
@@ -998,4 +998,18 @@
         }),
         String(status));
   }
+
+  /**
+   * Send a delete event via the `OnFilesChanged` drivefs delegate method.
+   * @param {string} appId app window ID.
+   * @param {string} path Path from the drive mount point, e.g. /root/test.txt
+   */
+  async sendDriveCloudDeleteEvent(appId, path) {
+    await this.waitFor('isFileManagerLoaded', appId, true);
+    await sendTestMessage({
+      appId,
+      name: 'sendDriveCloudDeleteEvent',
+      path,
+    });
+  }
 }
diff --git a/ui/ozone/platform/wayland/OWNERS b/ui/ozone/platform/wayland/OWNERS
index 72721318..6700cfc 100644
--- a/ui/ozone/platform/wayland/OWNERS
+++ b/ui/ozone/platform/wayland/OWNERS
@@ -3,3 +3,9 @@
 tonikitoo@igalia.com
 adunaev@igalia.com
 fangzhoug@chromium.org
+
+# For Keyboard / TextInput (IME)
+hidehiko@chromium.org
+
+# For Events / WindowManagement / Screen Management
+oshima@chromium.org
diff --git a/weblayer/browser/autofill_client_impl.cc b/weblayer/browser/autofill_client_impl.cc
index 665dcd3f..55f1ac3 100644
--- a/weblayer/browser/autofill_client_impl.cc
+++ b/weblayer/browser/autofill_client_impl.cc
@@ -265,7 +265,7 @@
 bool AutofillClientImpl::TryToShowFastCheckout(
     const autofill::FormData& form,
     const autofill::FormFieldData& field,
-    autofill::AutofillDriver* driver) {
+    base::WeakPtr<autofill::AutofillManager> autofill_manager) {
   return false;
 }
 
diff --git a/weblayer/browser/autofill_client_impl.h b/weblayer/browser/autofill_client_impl.h
index 08f5467..dc4abee 100644
--- a/weblayer/browser/autofill_client_impl.h
+++ b/weblayer/browser/autofill_client_impl.h
@@ -112,9 +112,10 @@
       AddressProfileSavePromptCallback callback) override;
   bool HasCreditCardScanFeature() override;
   void ScanCreditCard(CreditCardScanCallback callback) override;
-  bool TryToShowFastCheckout(const autofill::FormData& form,
-                             const autofill::FormFieldData& field,
-                             autofill::AutofillDriver* driver) override;
+  bool TryToShowFastCheckout(
+      const autofill::FormData& form,
+      const autofill::FormFieldData& field,
+      base::WeakPtr<autofill::AutofillManager> autofill_manager) override;
   void HideFastCheckout(bool allow_further_runs) override;
   bool IsFastCheckoutSupported() override;
   bool IsShowingFastCheckoutUI() override;