diff --git a/DEPS b/DEPS
index 6502b86..6efd4d07 100644
--- a/DEPS
+++ b/DEPS
@@ -208,7 +208,7 @@
   # 2 Run additional a few optional tryjobs:
   #   lacros-amd64-generic-chrome-skylab
   #   lacros-arm-generic-chrome-skylab
-  'lacros_sdk_version': '14748.0.0',
+  'lacros_sdk_version': '14803.0.0',
 
   # Generate location tag metadata to include in tests result data uploaded
   # to ResultDB. This isn't needed on some configs and the tool that generates
@@ -220,7 +220,7 @@
   # luci-go CIPD package version.
   # Make sure the revision is uploaded by infra-packagers builder.
   # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console
-  'luci_go': 'git_revision:9193dc92fa28cd5faf6b2d7c6e0a7f2ec174898e',
+  'luci_go': 'git_revision:93b14d0f421647ad3bebf0ab4261c38f78bd1a8d',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -269,7 +269,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': 'eb48a2483d5f65a2c1941a4362de666f92d3ef9c',
+  'skia_revision': '2816ce0dea7c6ac08d9df804c205a18c6efc2362',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -296,7 +296,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:8.20220512.1.1',
+  'fuchsia_version': 'version:8.20220512.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.
@@ -340,7 +340,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '6240594199af227b802483b3689bdac749a6be1d',
+  'catapult_revision': '925e601986fb75043a6351e9da4d9aecfa77be42',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -384,7 +384,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'e2d7f86872bf7c7c47cb2fc1c540feb3c42e1471',
+  'dawn_revision': 'ab9757036bd6b2fe86c4e937db0524463ba0c0f8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -741,7 +741,7 @@
     Var('chromium_git') + '/external/github.com/toji/webvr.info.git' + '@' + 'c58ae99b9ff9e2aa4c524633519570bf33536248',
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + '9edef17474dc2fef6ac0b0540515d9eaab7f2e45',
+    'url': Var('chromium_git') + '/website.git' + '@' + 'b6c4996a9afba3f2921d2d72e489521f0201ab8c',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -914,7 +914,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'PNBrshbagcx1GOHkHBhYzkuMPFqjoNIacmyt8tKhmfwC',
+          'version': 'lgU7MqXGbA9T3_l-cHfgKoUImTeSXU2y6zGL1fc65poC',
       },
     ],
     'condition': 'checkout_android',
@@ -1133,7 +1133,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '8b707654318d0b2b24c0b0bbeeef0ee8b0865007',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '7d20ae05d5f2a3ab611dd4b536f33db4ac476284',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1702,10 +1702,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'cf04aebdf9b53bb2853f22a81465688daf879ec6',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'b32ac31a0eab7386807f01c7cb8d77a6e1104c23',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '2f5bb704fdb8fff1805e309b0d54960a850c040d',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'f279c01b1ce9197f77ba71ac916f0f1b4a112e18',
+    Var('webrtc_git') + '/src.git' + '@' + '5ef44ac686ca9e2dede1a00887703ff5639aa6f1',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1778,7 +1778,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@433f3ddfad9891a4d490d8620b5e64ae4f98ef39',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6392596483469135f78d28e29283191d75cacbce',
     'condition': 'checkout_src_internal',
   },
 
@@ -1830,7 +1830,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'wCS0Eg6U0v0Is-GGaz_xn2dU-UXdsXbiRerVC2g2RcQC',
+        'version': 'B4alsyAwe8BsqOn2MN39CtK4KJNF8D7c7C2sjERsgrIC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 9a47b40..be65042 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -2246,6 +2246,8 @@
                     '--tbr was specified, skipping OWNERS check for DEPS additions'
                 )
             ]
+        # TODO(dcheng): Make this generate an error on dry runs if the reviewer
+        # is not added, to prevent review serialization.
         if input_api.dry_run:
             return [
                 output_api.PresubmitNotifyResult(
@@ -2768,7 +2770,9 @@
     """
     owner_email, reviewers = (
         input_api.canned_checks.GetCodereviewOwnerAndReviewers(
-            input_api, None, approval_needed=input_api.is_committing))
+            input_api,
+            None,
+            approval_needed=input_api.is_committing and not input_api.dry_run))
 
     security_owners = input_api.owners_client.ListOwners(owners_file)
     return any(owner in reviewers for owner in security_owners)
@@ -3010,10 +3014,6 @@
         output_api.AppendCC('ipc-security-reviews@chromium.org')
 
     results = []
-    if input_api.is_committing:
-        make_presubmit_message = output_api.PresubmitError
-    else:
-        make_presubmit_message = output_api.PresubmitPromptWarning
 
     # Ensure that a security reviewer is included as a CL reviewer. This is a
     # hack, but is needed because the OWNERS check (by design) ignores new
@@ -3024,6 +3024,12 @@
     missing_reviewer_errors.extend(fuchsia_results.missing_reviewer_errors)
 
     if missing_reviewer_errors:
+        # Missing reviewers are only a warning at upload time; otherwise, it'd
+        # be impossible to upload a change.
+        if input_api.is_committing:
+            make_presubmit_message = output_api.PresubmitError
+        else:
+            make_presubmit_message = output_api.PresubmitPromptWarning
         results.append(
             make_presubmit_message(
                 'Found missing security reviewers:',
@@ -3034,8 +3040,13 @@
     owners_file_errors.extend(fuchsia_results.owners_file_errors)
 
     if owners_file_errors:
+        # Missing per-file rules are always an error. While swarming and caching
+        # means that uploading a patchset with updated OWNERS files and sending
+        # it to the CQ again should not have a large incremental cost, it is
+        # still frustrating to discover the error only after the change has
+        # already been uploaded.
         results.append(
-            make_presubmit_message(
+            output_api.PresubmitError(
                 'Found OWNERS files with missing per-file rules for '
                 'security-sensitive files.\nPlease update the OWNERS files '
                 'below to add the missing rules:',
diff --git a/PRESUBMIT_test.py b/PRESUBMIT_test.py
index ec156d3..ca9f2750 100755
--- a/PRESUBMIT_test.py
+++ b/PRESUBMIT_test.py
@@ -2323,6 +2323,71 @@
 
   # TODO(dcheng): add tests for when there are no missing per-file rules. These
   # are currently missing because `open()` is not injected.
+  def testMissingSecurityReviewerAtUpload(self):
+    mock_input_api = MockInputApi()
+    mock_input_api.files = [
+      MockAffectedFile(f'services/goat/public/goat.mojom',
+                       ['// Scary contents.'])]
+    self._injectFakeOwnersClient(
+        mock_input_api,
+        ['apple@chromium.org', 'orange@chromium.org'])
+    self._injectFakeChangeOwnerAndReviewers(
+        mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
+    mock_input_api.is_committing = False
+    mock_input_api.dry_run = False
+    mock_output_api = MockOutputApi()
+    results = PRESUBMIT.CheckSecurityOwners(
+        mock_input_api, mock_output_api)
+    # TODO(dcheng): This should be 1, but the PRESUBMIT currently opens the
+    # OWNERS file in an unmockable way.
+    self.assertEqual(2, len(results))
+    self.assertEqual('warning', results[0].type)
+    self.assertEqual(
+        'Found missing security reviewers:', results[0].message)
+
+  def testMissingSecurityReviewerAtDryRunCommit(self):
+    mock_input_api = MockInputApi()
+    mock_input_api.files = [
+      MockAffectedFile(f'services/goat/public/goat.mojom',
+                       ['// Scary contents.'])]
+    self._injectFakeOwnersClient(
+        mock_input_api,
+        ['apple@chromium.org', 'orange@chromium.org'])
+    self._injectFakeChangeOwnerAndReviewers(
+        mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
+    mock_input_api.is_committing = True
+    mock_input_api.dry_run = True
+    mock_output_api = MockOutputApi()
+    results = PRESUBMIT.CheckSecurityOwners(
+        mock_input_api, mock_output_api)
+    # TODO(dcheng): This should be 1, but the PRESUBMIT currently opens the
+    # OWNERS file in an unmockable way.
+    self.assertEqual(2, len(results))
+    self.assertEqual('error', results[0].type)
+    self.assertEqual(
+        'Found missing security reviewers:', results[0].message)
+
+  def testmissingSecurityApprovalAtRealCommit(self):
+    mock_input_api = MockInputApi()
+    mock_input_api.files = [
+      MockAffectedFile(f'services/goat/public/goat.mojom',
+                       ['// Scary contents.'])]
+    self._injectFakeOwnersClient(
+        mock_input_api,
+        ['apple@chromium.org', 'orange@chromium.org'])
+    self._injectFakeChangeOwnerAndReviewers(
+        mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
+    mock_input_api.is_committing = True
+    mock_input_api.dry_run = False
+    mock_output_api = MockOutputApi()
+    results = PRESUBMIT.CheckSecurityOwners(
+        mock_input_api, mock_output_api)
+    # TODO(dcheng): This should be 1, but the PRESUBMIT currently opens the
+    # OWNERS file in an unmockable way.
+    self.assertEqual(2, len(results))
+    self.assertEqual('error', results[0].type)
+    self.assertEqual(
+        'Found missing security reviewers:', results[0].message)
 
   def testMissingPerFileRulesButNotSecurityReviewer(self):
     for pattern, filename in self._test_cases:
@@ -2348,29 +2413,38 @@
                          mock_output_api.more_cc)
 
   def testIpcChangeNeedsSecurityOwner(self):
-    for pattern, filename in self._test_cases:
-      with self.subTest(line=filename):
-        mock_input_api = MockInputApi()
-        mock_input_api.files = [
-          MockAffectedFile(f'services/goat/public/{filename}',
-                           ['// Scary contents.'])]
-        self._injectFakeOwnersClient(
-            mock_input_api,
-            ['apple@chromium.org', 'orange@chromium.org'])
-        self._injectFakeChangeOwnerAndReviewers(
-            mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
-        mock_output_api = MockOutputApi()
-        errors = PRESUBMIT.CheckSecurityOwners(
-            mock_input_api, mock_output_api)
-        self.assertEqual(2, len(errors))
-        self.assertEqual(
-            'Found missing security reviewers:', errors[0].message)
-        self.assertEqual(
-            'Found OWNERS files with missing per-file rules for '
-            'security-sensitive files.\nPlease update the OWNERS files below '
-            'to add the missing rules:', errors[1].message)
-        self.assertEqual(['ipc-security-reviews@chromium.org'],
-                         mock_output_api.more_cc)
+    for is_committing in [True, False]:
+      for pattern, filename in self._test_cases:
+        with self.subTest(
+            line=f'is_committing={is_committing}, filename={filename}'):
+          mock_input_api = MockInputApi()
+          mock_input_api.files = [
+            MockAffectedFile(f'services/goat/public/{filename}',
+                             ['// Scary contents.'])]
+          self._injectFakeOwnersClient(
+              mock_input_api,
+              ['apple@chromium.org', 'orange@chromium.org'])
+          self._injectFakeChangeOwnerAndReviewers(
+              mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
+          mock_input_api.is_committing = is_committing
+          mock_input_api.dry_run = False
+          mock_output_api = MockOutputApi()
+          results = PRESUBMIT.CheckSecurityOwners(
+              mock_input_api, mock_output_api)
+          self.assertEqual(2, len(results))
+          if is_committing:
+            self.assertEqual('error', results[0].type)
+          else:
+            self.assertEqual('warning', results[0].type)
+          self.assertEqual(
+              'Found missing security reviewers:', results[0].message)
+          self.assertEqual('error', results[1].type)
+          self.assertEqual(
+              'Found OWNERS files with missing per-file rules for '
+              'security-sensitive files.\nPlease update the OWNERS files below '
+              'to add the missing rules:', results[1].message)
+          self.assertEqual(['ipc-security-reviews@chromium.org'],
+                           mock_output_api.more_cc)
 
 
   def testServiceManifestChangeNeedsSecurityOwner(self):
@@ -2590,6 +2664,7 @@
     self._injectFakeChangeOwnerAndReviewers(
         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
     mock_input_api.is_committing = True
+    mock_input_api.dry_run = False
     mock_input_api.files = [
         MockAffectedFile('file.cc', ['GetServiceSandboxType<mojom::Goat>()'])
     ]
diff --git a/android_webview/browser/gfx/test/invalidate_test.cc b/android_webview/browser/gfx/test/invalidate_test.cc
index 0a82016..0e293db 100644
--- a/android_webview/browser/gfx/test/invalidate_test.cc
+++ b/android_webview/browser/gfx/test/invalidate_test.cc
@@ -469,10 +469,9 @@
     Sync();
     DrawOnRT(bf_args, /*invalidated=*/true);
 
-    // This draw must always succeed.
-    ASSERT_EQ(child_client_timings_.size(), 1u);
-    ASSERT_FALSE(
-        child_client_timings_.begin()->second.presentation_feedback.failed());
+    // Note, that client is always one frame behind, so there is no client
+    // timings yet.
+    ASSERT_EQ(child_client_timings_.size(), 0u);
   }
 
   viz::BeginFrameArgs NextBeginFrameArgs() {
@@ -547,24 +546,26 @@
       UpdateFrameTimingDetails();
     }
 
-    // Draw one more frame without client submitting to make sure all
-    // presentation feedback was processed.
-    auto args = NextBeginFrameArgs();
-    bool invalidate = BeginFrame(args);
+    // Draw two more frame without client submitting to make sure all
+    // frames are drawns and presentation feedback was processed.
+    for (int i = 0; i < 2; i++) {
+      auto args = NextBeginFrameArgs();
+      bool invalidate = BeginFrame(args);
 
-    // Finish draw if it's still pending
-    if (delayed_draw_args.IsValid()) {
-      DrawOnRT(delayed_draw_args, delayed_draw_invalidate);
+      // Finish draw if it's still pending
+      if (delayed_draw_args.IsValid()) {
+        DrawOnRT(delayed_draw_args, delayed_draw_invalidate);
+      }
+
+      if (invalidate) {
+        DrawOnUI(CreateChildFrame(nullptr, args, /*invalidated=*/invalidate));
+      }
+
+      Sync();
+
+      DrawOnRT(args, invalidate);
     }
 
-    if (invalidate) {
-      DrawOnUI(CreateChildFrame(nullptr, args, /*invalidated=*/invalidate));
-    }
-
-    Sync();
-
-    DrawOnRT(args, invalidate);
-
     // Make sure we received all presentation feedback.
     ASSERT_EQ(client_->frames_submitted(), child_client_timings_.size());
   }
@@ -651,20 +652,9 @@
     TestParamToString);
 
 TEST_P(InvalidateTest, LowFpsWithMaxFrame1) {
-  auto always_draw = testing::get<2>(GetParam());
   auto client_slow = testing::get<0>(GetParam());
   auto hwui_slow = testing::get<1>(GetParam());
 
-  // Always draw case is broken because viz runs ahead of hwui.
-  if (always_draw == AlwaysDrawType::kAlwaysDraw) {
-    GTEST_SKIP();
-  }
-
-  // If client is faster than hwui, it runs ahead of hwui.
-  if (client_slow.IsNever() && !hwui_slow.IsNever()) {
-    GTEST_SKIP();
-  }
-
   SetUpAndDrawFirstFrame(/*max_pending_frames=*/1, /*frame_rate=*/30);
   DrawLoop(client_slow, hwui_slow);
 
@@ -672,28 +662,14 @@
   // of 30. Total 30 counting frame from first draw.
   ASSERT_EQ(child_client_timings_.size(), 30u);
   EXPECT_EQ(CountDroppedFrames(), 0);
-
-  // If either client or hwui slow we currently invalidate more than we need.
-  if (client_slow.IsNever() || hwui_slow.IsNever())
-    EXPECT_LE(invalidate_count_, 31);
+  EXPECT_LE(invalidate_count_, 31);
 }
 
 // Currently we can't reach 60fps with max pending frames 1.
-TEST_P(InvalidateTest, DISABLED_HighFpsWithMaxFrame1) {
-  auto always_draw = testing::get<2>(GetParam());
+TEST_P(InvalidateTest, HighFpsWithMaxFrame1) {
   auto client_slow = testing::get<0>(GetParam());
   auto hwui_slow = testing::get<1>(GetParam());
 
-  // Always draw case is broken because viz runs ahead of hwui.
-  if (always_draw == AlwaysDrawType::kAlwaysDraw) {
-    GTEST_SKIP();
-  }
-
-  // If client is faster than hwui, it runs ahead of hwui.
-  if (client_slow.IsNever() && !hwui_slow.IsNever()) {
-    GTEST_SKIP();
-  }
-
   SetUpAndDrawFirstFrame(/*max_pending_frames=*/1, /*frame_rate=*/60);
   DrawLoop(client_slow, hwui_slow);
 
@@ -703,59 +679,32 @@
 }
 
 TEST_P(InvalidateTest, HighFpsWithMaxFrame2) {
-  auto always_draw = testing::get<2>(GetParam());
   auto client_slow = testing::get<0>(GetParam());
   auto hwui_slow = testing::get<1>(GetParam());
 
-  // Always draw case is broken because viz runs ahead of hwui.
-  if (always_draw == AlwaysDrawType::kAlwaysDraw) {
-    GTEST_SKIP();
-  }
-
-  // If client is faster than hwui, it runs ahead of hwui.
-  if (client_slow.IsNever() && !hwui_slow.IsNever()) {
-    GTEST_SKIP();
-  }
-
   SetUpAndDrawFirstFrame(/*max_pending_frames=*/2, /*frame_rate=*/60);
   DrawLoop(client_slow, hwui_slow);
 
   // We should have submitted 60 frames + 1 from initial draw.
   ASSERT_EQ(child_client_timings_.size(), 61u);
-
-  // Except the case when client is slower than hwui we currently drop first
-  // frame always.
-  EXPECT_LE(CountDroppedFrames(), 1);
+  EXPECT_EQ(CountDroppedFrames(), 0);
 }
 
 TEST_P(InvalidateTest, LastFrameNotLost) {
-  auto always_draw = testing::get<2>(GetParam());
   auto client_slow = testing::get<0>(GetParam());
   auto hwui_slow = testing::get<1>(GetParam());
 
-  // Always draw case is broken because viz runs ahead of hwui.
-  if (always_draw == AlwaysDrawType::kAlwaysDraw) {
-    GTEST_SKIP();
-  }
-
-  // If client is faster than hwui, it runs ahead of hwui.
-  if (client_slow.IsNever() && !hwui_slow.IsNever()) {
-    GTEST_SKIP();
-  }
-
   SetUpAndDrawFirstFrame(/*max_pending_frames=*/1, /*frame_rate=*/30);
   DrawLoop(client_slow, hwui_slow, /*stop_submitting_after_frames=*/30);
 
   ASSERT_EQ(child_client_timings_.size(), 16u);
   EXPECT_EQ(CountDroppedFrames(), 0);
 
-  // With AlwaysInvalidate we currently trigger more BeginFrames than we need.
-
-  // Note, that client unsubscribes at frame 30 leading to 31 BF + 1 from
-  // initial draw + 1 extra to deliver presentation feedback if we didn't fetch
-  // it right after draw.
-  if (always_draw != AlwaysDrawType::kAlwaysInvalidate)
-    EXPECT_LE(root_begin_frames_count_, 33);
+  // Note, that client unsubscribes at frame 30 leading to 31 BF + 4 from:
+  // initial draw, one for presentation feedback delivery, one because client is
+  // always behind and one because we keep BF until we don't have anything to
+  // draw.
+  EXPECT_LE(root_begin_frames_count_, 35);
 }
 
 TEST_P(InvalidateTest, VeryLateFrame) {
diff --git a/android_webview/lib/webview_tests.cc b/android_webview/lib/webview_tests.cc
index a2cdd39..ee9aa31 100644
--- a/android_webview/lib/webview_tests.cc
+++ b/android_webview/lib/webview_tests.cc
@@ -15,7 +15,7 @@
   command_line->AppendSwitch(switches::kSingleProcess);
   command_line->AppendSwitchASCII(switches::kDisableFeatures, ",Vulkan");
   command_line->AppendSwitchASCII(switches::kEnableFeatures,
-                                  ",UseSkiaRenderer");
+                                  ",WebViewNewInvalidateHeuristic");
 
   gl::GLSurfaceTestSupport::InitializeNoExtensionsOneOff();
   base::TestSuite test_suite(argc, argv);
diff --git a/ash/ambient/ui/ambient_animation_view.cc b/ash/ambient/ui/ambient_animation_view.cc
index d1c9a09..ba5a830 100644
--- a/ash/ambient/ui/ambient_animation_view.cc
+++ b/ash/ambient/ui/ambient_animation_view.cc
@@ -38,6 +38,7 @@
 #include "ui/compositor/compositor.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/outsets.h"
 #include "ui/gfx/geometry/vector2d.h"
 #include "ui/gfx/shadow_value.h"
 #include "ui/views/background.h"
@@ -67,10 +68,9 @@
 // AmbientAnimationView to the top-left of the weather/time content views.
 constexpr int kWeatherTimeBorderPaddingDip = 28;
 
-// Amount of padding from the left and bottom of the AmbientAnimationView's
-// bounds to the bottom-left of the media string content views.
-constexpr int kMediaStringPaddingFromLeftDip = 28;
-constexpr int kMediaStringPaddingFromBottomDip = 24;
+// Amount of padding from the top-right of the AmbientAnimationView's
+// bounds to the top-right of the media string content views.
+constexpr int kMediaStringPaddingDip = 28;
 
 constexpr int kMediaStringTextElevation = 1;
 
@@ -108,12 +108,38 @@
                    abs(kAnimationJitterConfig.y_max_translation)});
 }
 
+// When text with shadows requires X pixels of padding from the edges of its
+// bounding view, it is not always sufficient to simply create a border within
+// the view that is X pixels wide. In the event that the text's shadow extends
+// past the text in a given direction, the text's shadow ends up with X pixels
+// of padding from the edge rather than the text itself.
+//
+// This returns the amount to *subtract* from each side of a text view's border
+// such that the text ultimately has X pixels of padding from the view's edge,
+// and the shadow may extend into the padding.
+gfx::Outsets GetTextShadowCorrection(const gfx::ShadowValues& text_shadows) {
+  // A positive shadow outset means the shadow extends past the text in that
+  // direction. A negative shadow outset means the shadow is "behind" the text
+  // in that direction. In this case, subtracting the negative outset value
+  // will result in padding that is too large (X + <shadow offset>). Hence,
+  // impose a "floor" of 0 pixels here.
+  static constexpr gfx::Outsets kZeroOutsetsFloor;
+  gfx::Outsets shadow_outsets =
+      gfx::ShadowValue::GetMargin(text_shadows).ToOutsets();
+  shadow_outsets.SetToMax(kZeroOutsetsFloor);
+  return shadow_outsets;
+}
+
 // The border serves as padding between the GlanceableInfoView and its
 // parent view's bounds.
 std::unique_ptr<views::Border> CreateGlanceableInfoBorder(
     const gfx::Vector2d& jitter = gfx::Vector2d()) {
-  int top_padding = kWeatherTimeBorderPaddingDip + jitter.y();
-  int left_padding = kWeatherTimeBorderPaddingDip + jitter.x();
+  gfx::Outsets shadow_text_correction =
+      GetTextShadowCorrection(ambient::util::GetTextShadowValues(nullptr));
+  int top_padding =
+      kWeatherTimeBorderPaddingDip - shadow_text_correction.top() + jitter.y();
+  int left_padding =
+      kWeatherTimeBorderPaddingDip - shadow_text_correction.left() + jitter.x();
   DCHECK_GE(top_padding, 0);
   DCHECK_GE(left_padding, 0);
   return views::CreateEmptyBorder(
@@ -124,16 +150,16 @@
 // parent view's bounds.
 std::unique_ptr<views::Border> CreateMediaStringBorder(
     const gfx::Vector2d& jitter = gfx::Vector2d()) {
-  gfx::Insets shadow_insets = gfx::ShadowValue::GetMargin(
+  gfx::Outsets shadow_text_correction = GetTextShadowCorrection(
       ambient::util::GetTextShadowValues(nullptr, kMediaStringTextElevation));
-  int bottom_padding =
-      kMediaStringPaddingFromBottomDip + shadow_insets.bottom() + jitter.y();
-  int left_padding =
-      kMediaStringPaddingFromLeftDip + shadow_insets.left() + jitter.x();
-  DCHECK_GE(bottom_padding, 0);
-  DCHECK_GE(left_padding, 0);
+  int top_padding =
+      kMediaStringPaddingDip - shadow_text_correction.top() + jitter.y();
+  int right_padding =
+      kMediaStringPaddingDip - shadow_text_correction.right() + jitter.x();
+  DCHECK_GE(top_padding, 0);
+  DCHECK_GE(right_padding, 0);
   return views::CreateEmptyBorder(
-      gfx::Insets::TLBR(0, left_padding, bottom_padding, 0));
+      gfx::Insets::TLBR(top_padding, 0, 0, right_padding));
 }
 
 }  // namespace
@@ -244,16 +270,16 @@
       view_delegate, kTimeFontSizeDip,
       /*time_temperature_font_color=*/gfx::kGoogleGrey900));
 
-  // Media string should appear in the bottom-left corner of the
+  // Media string should appear in the top-right corner of the
   // AmbientAnimationView's bounds.
   media_string_container_ =
       AddChildView(std::make_unique<views::BoxLayoutView>());
   media_string_container_->SetOrientation(
       views::BoxLayout::Orientation::kVertical);
   media_string_container_->SetMainAxisAlignment(
-      views::BoxLayout::MainAxisAlignment::kEnd);
+      views::BoxLayout::MainAxisAlignment::kStart);
   media_string_container_->SetCrossAxisAlignment(
-      views::BoxLayout::CrossAxisAlignment::kStart);
+      views::BoxLayout::CrossAxisAlignment::kEnd);
   media_string_container_->SetBorder(CreateMediaStringBorder());
   MediaStringView* media_string_view = media_string_container_->AddChildView(
       std::make_unique<MediaStringView>(MediaStringView::Settings(
diff --git a/ash/capture_mode/capture_mode_session.cc b/ash/capture_mode/capture_mode_session.cc
index d826804..fea14c24 100644
--- a/ash/capture_mode/capture_mode_session.cc
+++ b/ash/capture_mode/capture_mode_session.cc
@@ -1966,29 +1966,6 @@
   }
 
   DCHECK(is_capture_region);
-  // Allow events that are located on the capture mode bar or settings menu to
-  // pass through so we can click the buttons.
-  if (!is_event_on_capture_bar &&
-      !(capture_mode_settings_widget_ &&
-        capture_mode_settings_widget_->GetWindowBoundsInScreen().Contains(
-            screen_location))) {
-    if (capture_mode_settings_widget_ &&
-        located_press_event_on_settings_menu_) {
-      capture_mode_settings_widget_->GetNativeWindow()->delegate()->OnEvent(
-          event);
-    }
-    event->SetHandled();
-    event->StopPropagation();
-  }
-
-  // OnLocatedEventPressed() and OnLocatedEventDragged used root locations since
-  // CaptureModeController::user_capture_region() is stored in root
-  // coordinates..
-  // TODO(sammiequon): Update CaptureModeController::user_capture_region() to
-  // store screen coordinates.
-  gfx::Point location_in_root = event->location();
-  aura::Window::ConvertPointToTarget(event_target, current_root_,
-                                     &location_in_root);
 
   // Early return if the given event is targeted on the capture bar or settings
   // menu, since the switch block below is for updating the user capture region
@@ -2003,11 +1980,21 @@
     return;
   }
 
+  event->SetHandled();
+  event->StopPropagation();
+
+  // OnLocatedEventPressed() and OnLocatedEventDragged used root locations since
+  // CaptureModeController::user_capture_region() is stored in root
+  // coordinates..
+  // TODO(sammiequon): Update CaptureModeController::user_capture_region() to
+  // store screen coordinates.
+  gfx::Point location_in_root = event->location();
+  aura::Window::ConvertPointToTarget(event_target, current_root_,
+                                     &location_in_root);
+
   switch (event->type()) {
     case ui::ET_MOUSE_PRESSED:
     case ui::ET_TOUCH_PRESSED:
-      if (is_event_on_settings_menu)
-        located_press_event_on_settings_menu_ = true;
       old_mouse_warp_status_ = SetMouseWarpEnabled(false);
       OnLocatedEventPressed(location_in_root, is_touch);
       break;
@@ -2022,7 +2009,6 @@
         SetMouseWarpEnabled(*old_mouse_warp_status_);
       old_mouse_warp_status_.reset();
       OnLocatedEventReleased(location_in_root);
-      located_press_event_on_settings_menu_ = false;
       break;
     default:
       break;
diff --git a/ash/capture_mode/capture_mode_session.h b/ash/capture_mode/capture_mode_session.h
index 5c3cfab..8916f1e 100644
--- a/ash/capture_mode/capture_mode_session.h
+++ b/ash/capture_mode/capture_mode_session.h
@@ -538,14 +538,6 @@
   // The object which handles tab focus while in a capture session.
   std::unique_ptr<CaptureModeSessionFocusCycler> focus_cycler_;
 
-  // This helps indicating whether located events should be handled by the
-  // capture mode settings menu view or the capture mode Pre-EventHandler. When
-  // it's true, settings menu view should handle the event. Set it to true when
-  // the event is a press event and is located on the settings menu view. Set it
-  // to false when the event is a release event and "event_on_settings_menu_" is
-  // true.
-  bool located_press_event_on_settings_menu_ = false;
-
   // True if a located event should be passed to camera preview to be handled.
   bool should_pass_located_event_to_camera_preview_ = false;
 
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index 97152609..3bb16787c 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -1064,6 +1064,31 @@
   EXPECT_GT(GetCaptureModeLabelWidget()->GetWindowBoundsInScreen().x(), 20);
 }
 
+// Tests that pressing on the capture bar and releasing the press outside of the
+// capture bar, the capture region could still be draggable and set. Regression
+// test for https://crbug.com/1325028.
+TEST_F(CaptureModeTest, SetCaptureRegionAfterPressOnCaptureBar) {
+  UpdateDisplay("800x600");
+
+  auto* controller =
+      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
+  auto* settings_button = GetSettingsButton();
+
+  // Press on the settings button without release.
+  auto* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(
+      settings_button->GetBoundsInScreen().CenterPoint());
+  event_generator->PressLeftButton();
+  // Move mouse to the outside of the capture bar and then release the press.
+  event_generator->MoveMouseTo({300, 300});
+  event_generator->ReleaseLeftButton();
+
+  // Set the capture region, and verify it's set successfully.
+  const gfx::Rect region_bounds(100, 100, 200, 200);
+  SelectRegion(region_bounds);
+  EXPECT_EQ(controller->user_capture_region(), region_bounds);
+}
+
 TEST_F(CaptureModeTest, WindowCapture) {
   // Create 2 windows that overlap with each other.
   const gfx::Rect bounds1(0, 0, 200, 200);
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index d1bd87dd..40bcc8b 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -494,6 +494,10 @@
 const base::Feature kDriveFsMirroring{"DriveFsMirroring",
                                       base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables authenticating to Wi-Fi networks using EAP-GTC.
+const base::Feature kEapGtcWifiAuthentication{
+    "EapGtcWifiAuthentication", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables the Eche Phone Hub permission onboarding.
 const base::Feature kEchePhoneHubPermissionsOnboarding{
     "EchePhoneHubPermissionsOnboarding", base::FEATURE_DISABLED_BY_DEFAULT};
@@ -1364,7 +1368,7 @@
 // Enables or disables using the system input engine for physical typing in
 // Chinese.
 const base::Feature kSystemChinesePhysicalTyping{
-    "SystemChinesePhysicalTyping", base::FEATURE_ENABLED_BY_DEFAULT};
+    "SystemChinesePhysicalTyping", base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Enables or disables the System Extensions platform.
 const base::Feature kSystemExtensions{"SystemExtensions",
@@ -1464,6 +1468,10 @@
 const base::Feature kVirtualKeyboardMultitouch{
     "VirtualKeyboardMultitouch", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enable or disable round corners for virtual keyboard on ChromeOS.
+const base::Feature kVirtualKeyboardRoundCorners{
+    "VirtualKeyboardRoundCorners", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls whether to allow enabling wake on WiFi features in shill.
 const base::Feature kWakeOnWifiAllowed{"WakeOnWifiAllowed",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
@@ -1731,6 +1739,10 @@
   return base::FeatureList::IsEnabled(kDriveFsMirroring);
 }
 
+bool IsEapGtcWifiAuthenticationEnabled() {
+  return base::FeatureList::IsEnabled(kEapGtcWifiAuthentication);
+}
+
 bool IsEchePhoneHubPermissionsOnboarding() {
   return base::FeatureList::IsEnabled(kEchePhoneHubPermissionsOnboarding);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 92afcf9..0cbf912 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -203,6 +203,8 @@
 extern const base::Feature kDriveFsBidirectionalNativeMessaging;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kDriveFsMirroring;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kEapGtcWifiAuthentication;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kEchePhoneHubPermissionsOnboarding;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kEcheSWA;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kEcheSWADebugMode;
@@ -571,6 +573,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kVirtualKeyboardMultitouch;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kVirtualKeyboardRoundCorners;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kWakeOnWifiAllowed;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kWallpaperWebUI;
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -639,6 +643,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDragUnpinnedAppToPinEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDragWindowToNewDeskEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDriveFsMirroringEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEapGtcWifiAuthenticationEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEchePhoneHubPermissionsOnboarding();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEcheSWAEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEcheSWADebugModeEnabled();
diff --git a/ash/public/cpp/autotest_desks_api.h b/ash/public/cpp/autotest_desks_api.h
index 6579f9b..86ca3fa9 100644
--- a/ash/public/cpp/autotest_desks_api.h
+++ b/ash/public/cpp/autotest_desks_api.h
@@ -46,6 +46,9 @@
 
   // Check whether a window belongs to a desk at |desk_index| or not.
   bool IsWindowInDesk(aura::Window* window, int desk_index);
+
+  // Returns the number of currently created desks.
+  int GetDeskCount() const;
 };
 
 }  // namespace ash
diff --git a/ash/style/dark_light_mode_nudge_controller_unittests.cc b/ash/style/dark_light_mode_nudge_controller_unittests.cc
index f3078c9..c8fafd4 100644
--- a/ash/style/dark_light_mode_nudge_controller_unittests.cc
+++ b/ash/style/dark_light_mode_nudge_controller_unittests.cc
@@ -68,7 +68,9 @@
   EXPECT_EQ(0, DarkLightModeNudgeController::GetRemainingShownCount());
 }
 
-TEST_F(DarkLightModeNudgeControllerTest, NoNudgeAfterColorModeToggled) {
+// Flaky. https://crbug.com/1325224
+TEST_F(DarkLightModeNudgeControllerTest,
+       DISABLED_NoNudgeAfterColorModeToggled) {
   SimulateUserLogin(account_id);
   UnifiedSystemTray* system_tray = GetPrimaryUnifiedSystemTray();
   system_tray->ShowBubble();
diff --git a/ash/system/tray/tray_info_label.h b/ash/system/tray/tray_info_label.h
index 1eadfa6..f5611e4 100644
--- a/ash/system/tray/tray_info_label.h
+++ b/ash/system/tray/tray_info_label.h
@@ -30,6 +30,8 @@
   // views::View:
   const char* GetClassName() const override;
 
+  const views::Label* label() { return label_; }
+
  private:
   views::Label* const label_;
 };
diff --git a/ash/webui/camera_app_ui/resources.h b/ash/webui/camera_app_ui/resources.h
index 61f0490..c10bbbf 100644
--- a/ash/webui/camera_app_ui/resources.h
+++ b/ash/webui/camera_app_ui/resources.h
@@ -61,9 +61,11 @@
     {"error_msg_video_too_short", IDS_ERROR_MSG_VIDEO_TOO_SHORT},
     {"expert_custom_video_parameters", IDS_EXPERT_CUSTOM_VIDEO_PARAMETERS},
     {"expert_enable_expert_mode", IDS_EXPERT_ENABLE_EXPERT_MODE},
-    {"expert_enable_fps_picker_for_builtin", IDS_EXPERT_ENABLE_FPS_PICKER_FOR_BUILTIN},
+    {"expert_enable_fps_picker_for_builtin",
+     IDS_EXPERT_ENABLE_FPS_PICKER_FOR_BUILTIN},
     {"expert_enable_full_sized_video_snapshot",
      IDS_EXPERT_ENABLE_FULL_SIZED_VIDEO_SNAPSHOT},
+    {"expert_enable_ptz_for_builtin", IDS_EXPERT_ENABLE_PTZ_FOR_BUILTIN},
     {"expert_multistream_recording", IDS_EXPERT_MULTISTREAM_RECORDING},
     {"expert_mode_button", IDS_EXPERT_MODE_BUTTON},
     {"expert_preview_metadata", IDS_EXPERT_PREVIEW_METADATA},
diff --git a/ash/webui/camera_app_ui/resources/.eslintrc.js b/ash/webui/camera_app_ui/resources/.eslintrc.js
index e334bc7..fe96ad19 100644
--- a/ash/webui/camera_app_ui/resources/.eslintrc.js
+++ b/ash/webui/camera_app_ui/resources/.eslintrc.js
@@ -697,6 +697,9 @@
 
     // go/tsstyle#switch-statements
     'default-case': 'error',
+
+    // go/tsstyle#return-types
+    '@typescript-eslint/explicit-module-boundary-types': 'error',
   }),
   overrides: [{
     files: ['**/*.ts'],
diff --git a/ash/webui/camera_app_ui/resources/js/device/preview.ts b/ash/webui/camera_app_ui/resources/js/device/preview.ts
index 1f0dd1f..70181ef 100644
--- a/ash/webui/camera_app_ui/resources/js/device/preview.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/preview.ts
@@ -181,13 +181,15 @@
         // Enable PTZ on fake camera for testing.
         return true;
       }
-      if (this.facing !== Facing.EXTERNAL) {
-        // PTZ function is excluded from builtin camera until we set up
-        // its AVL calibration standard.
-        return false;
+      if (this.facing === Facing.EXTERNAL) {
+        return true;
+      } else if (
+          state.get(state.State.EXPERT) &&
+          state.get(state.State.ENABLE_PTZ_FOR_BUILTIN)) {
+        return true;
       }
 
-      return true;
+      return false;
     })();
 
     if (!this.isSupportPTZInternal) {
diff --git a/ash/webui/camera_app_ui/resources/js/i18n_string.ts b/ash/webui/camera_app_ui/resources/js/i18n_string.ts
index 65dec05..9f5670f8 100644
--- a/ash/webui/camera_app_ui/resources/js/i18n_string.ts
+++ b/ash/webui/camera_app_ui/resources/js/i18n_string.ts
@@ -55,6 +55,7 @@
   EXPERT_ENABLE_FPS_PICKER_FOR_BUILTIN = 'expert_enable_fps_picker_for_builtin',
   EXPERT_ENABLE_FULL_SIZED_VIDEO_SNAPSHOT =
       'expert_enable_full_sized_video_snapshot',
+  EXPERT_ENABLE_PTZ_FOR_BUILTIN = 'expert_enable_ptz_for_builtin',
   EXPERT_MODE_BUTTON = 'expert_mode_button',
   EXPERT_MULTISTREAM_RECORDING = 'expert_multistream_recording',
   EXPERT_PREVIEW_METADATA = 'expert_preview_metadata',
@@ -113,7 +114,7 @@
   LABEL_TIMER_3S = 'label_timer_3s',
   LABEL_OTHER_ASPECT_RATIO = 'label_other_aspect_ratio',
   LABEL_VIDEO_RESOLUTION = 'label_video_resolution',
-  LABEL_VIDEO_RESOLUTION_4K = 'label_video_resolution_4k',
+  LABEL_VIDEO_RESOLUTION_4K = 'label_video_resolution_4K',
   LABEL_VIDEO_RESOLUTION_FULL_HD = 'label_video_resolution_full_hd',
   LABEL_VIDEO_RESOLUTION_HD = 'label_video_resolution_hd',
   LABEL_VIDEO_RESOLUTION_QUAD_HD = 'label_video_resolution_quad_hd',
diff --git a/ash/webui/camera_app_ui/resources/js/state.ts b/ash/webui/camera_app_ui/resources/js/state.ts
index 641726a..8439cad 100644
--- a/ash/webui/camera_app_ui/resources/js/state.ts
+++ b/ash/webui/camera_app_ui/resources/js/state.ts
@@ -19,6 +19,7 @@
   ENABLE_GIF_RECORDING = 'enable-gif-recording',
   ENABLE_MULTISTREAM_RECORDING = 'enable-multistream-recording',
   ENABLE_PTZ = 'enable-ptz',
+  ENABLE_PTZ_FOR_BUILTIN = 'enable-ptz-for-builtin',
   ENABLE_SCAN_BARCODE = 'enable-scan-barcode',
   EXPERT = 'expert',
   FPS_30 = 'fps-30',
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera.ts b/ash/webui/camera_app_ui/resources/js/views/camera.ts
index c9cf221..4ceacc7 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera.ts
@@ -272,6 +272,9 @@
     state.addObserver(state.State.ENABLE_MULTISTREAM_RECORDING, () => {
       this.cameraManager.reconfigure();
     });
+    for (const s of [state.State.EXPERT, state.State.ENABLE_PTZ_FOR_BUILTIN]) {
+      state.addObserver(s, () => this.cameraManager.reconfigure());
+    }
 
     this.initVideoEncoderOptions();
     await this.initScanMode();
diff --git a/ash/webui/camera_app_ui/resources/strings/camera_strings.grd b/ash/webui/camera_app_ui/resources/strings/camera_strings.grd
index 6d9b5742..f570ddb 100644
--- a/ash/webui/camera_app_ui/resources/strings/camera_strings.grd
+++ b/ash/webui/camera_app_ui/resources/strings/camera_strings.grd
@@ -357,6 +357,9 @@
       <message desc="Label for expert mode option: enable expert mode." name="IDS_EXPERT_ENABLE_EXPERT_MODE">
         Enable expert mode
       </message>
+      <message desc="Label for expert mode option: enable PTZ for builtin camera." name="IDS_EXPERT_ENABLE_PTZ_FOR_BUILTIN">
+        Enable pan tilt zoom
+      </message>
       <message desc="Label for expert mode option: enable full-sized video snapshot." name="IDS_EXPERT_ENABLE_FULL_SIZED_VIDEO_SNAPSHOT">
         Enable full-sized video snapshot
       </message>
diff --git a/ash/webui/camera_app_ui/resources/strings/camera_strings_grd/IDS_EXPERT_ENABLE_PTZ_FOR_BUILTIN.png.sha1 b/ash/webui/camera_app_ui/resources/strings/camera_strings_grd/IDS_EXPERT_ENABLE_PTZ_FOR_BUILTIN.png.sha1
new file mode 100644
index 0000000..b81f355
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/strings/camera_strings_grd/IDS_EXPERT_ENABLE_PTZ_FOR_BUILTIN.png.sha1
@@ -0,0 +1 @@
+a52301841cbc96aef06ae796c5a2e3fcc302e147
\ No newline at end of file
diff --git a/ash/webui/camera_app_ui/resources/views/main.html b/ash/webui/camera_app_ui/resources/views/main.html
index ae605c27..46b76acc 100644
--- a/ash/webui/camera_app_ui/resources/views/main.html
+++ b/ash/webui/camera_app_ui/resources/views/main.html
@@ -470,6 +470,13 @@
           <span i18n-text="expert_show_all_resolutions"></span>
         </label>
         <label class="menu-item inkdrop">
+          <input id="expert-enable-ptz-for-builtin" class="icon"
+                 type="checkbox" tabindex="0"
+                 data-state="enable-ptz-for-builtin"
+                 data-key="enablePTZForBuiltin">
+          <span i18n-text="expert_enable_ptz_for_builtin"></span>
+        </label>
+        <label class="menu-item inkdrop">
           <input id="expert-enable-expert-mode" class="icon" type="checkbox"
                  tabindex="0" data-state="expert"
                  data-key="expert">
diff --git a/ash/webui/personalization_app/resources/common/ambient_mode_disabled.svg b/ash/webui/personalization_app/resources/common/ambient_mode_disabled.svg
index e6819dea..6cca7edd 100644
--- a/ash/webui/personalization_app/resources/common/ambient_mode_disabled.svg
+++ b/ash/webui/personalization_app/resources/common/ambient_mode_disabled.svg
@@ -16,7 +16,7 @@
 	.st11{fill:none;stroke:#231F20;stroke-width:0.3;stroke-miterlimit:10;}
 	.st12{fill:#D2E3FC;}
 	.st13{fill:#4285F4;}
-	.st14{font-family:'GoogleSans-Regular';}
+	.st14{font-family:'Google Sans', 'Noto Sans', sans-serif;}
 	.st15{font-size:14px;}
 	.st16{fill:#8AB4F8;}
 	.st17{fill:#FBBC04;}
diff --git a/ash/webui/personalization_app/resources/common/ambient_mode_disabled_dark.svg b/ash/webui/personalization_app/resources/common/ambient_mode_disabled_dark.svg
index b964466..7d4d2e1 100644
--- a/ash/webui/personalization_app/resources/common/ambient_mode_disabled_dark.svg
+++ b/ash/webui/personalization_app/resources/common/ambient_mode_disabled_dark.svg
@@ -4,7 +4,7 @@
 <style type="text/css">
 	.st0{fill:#404D64;}
 	.st1{fill:#669DF6;}
-	.st2{font-family:'GoogleSans-Regular';}
+	.st2{font-family:'Google Sans', 'Noto Sans', sans-serif;}
 	.st3{font-size:14px;}
 	.st4{fill:#1E3A5F;}
 	.st5{fill:#FCC934;}
diff --git a/ash/webui/personalization_app/resources/common/styles.html b/ash/webui/personalization_app/resources/common/styles.html
index fcd9d12..366a9dc 100644
--- a/ash/webui/personalization_app/resources/common/styles.html
+++ b/ash/webui/personalization_app/resources/common/styles.html
@@ -257,10 +257,7 @@
 
     .preview-text-container > span:nth-child(n+3) {
       color: var(--cros-text-color-secondary);
-      font-family: var(--personalization-app-font-google-sans);
-      font-size: 14px;
-      font-weight: 400;
-      line-height: 20px;
+      font: var(--cros-body-1-font);
     }
 
     .preview-text-placeholder > .placeholder:nth-child(n+3) {
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html
index f1ae773..c2f842d 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html
@@ -116,7 +116,7 @@
 
   :host([main-page]) #albumTitle {
     color: var(--cros-text-color-primary);
-    font: var(--cros-body-1-font);
+    font: var(--cros-display-7-font);
   }
 
   :host([main-page]) #albumDescription {
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_theme_element.html b/ash/webui/personalization_app/resources/trusted/personalization_theme_element.html
index 253ad3ab..eb73fadc5 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_theme_element.html
+++ b/ash/webui/personalization_app/resources/trusted/personalization_theme_element.html
@@ -22,7 +22,7 @@
   cr-button .text,
   cr-button:hover .text  {
     color: var(--cros-text-color-secondary);
-    font: var(--cros-body-1-font);
+    font: var(--personalization-app-label-font);
     padding-top: 4px;
   }
 
diff --git a/ash/wm/desks/autotest_desks_api.cc b/ash/wm/desks/autotest_desks_api.cc
index d34af7ac..4e3bed1 100644
--- a/ash/wm/desks/autotest_desks_api.cc
+++ b/ash/wm/desks/autotest_desks_api.cc
@@ -215,4 +215,8 @@
   return desk_container->Contains(window);
 }
 
+int AutotestDesksApi::GetDeskCount() const {
+  return DesksController::Get()->desks().size();
+}
+
 }  // namespace ash
diff --git a/ash/wm/window_animations_unittest.cc b/ash/wm/window_animations_unittest.cc
index 74feb8d..8c5ccad 100644
--- a/ash/wm/window_animations_unittest.cc
+++ b/ash/wm/window_animations_unittest.cc
@@ -493,7 +493,8 @@
 // opacity of the new layer, but only the opacity of the old layer. The old
 // layer transform is updated manually when the animation ticks so that it
 // has the same visible bounds as the new layer.
-TEST_F(WindowAnimationsTest, CrossFadeAnimateNewLayerOnly) {
+// Flaky on Chrome OS. https://crbug.com/1113901
+TEST_F(WindowAnimationsTest, DISABLED_CrossFadeAnimateNewLayerOnly) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
 
diff --git a/base/allocator/partition_alloc_features.cc b/base/allocator/partition_alloc_features.cc
index fb231c08..e64b3e4 100644
--- a/base/allocator/partition_alloc_features.cc
+++ b/base/allocator/partition_alloc_features.cc
@@ -47,8 +47,14 @@
 const BASE_EXPORT Feature kPartitionAllocLargeEmptySlotSpanRing{
     "PartitionAllocLargeEmptySlotSpanRing", FEATURE_DISABLED_BY_DEFAULT};
 
-const Feature kPartitionAllocBackupRefPtr{"PartitionAllocBackupRefPtr",
-                                          FEATURE_DISABLED_BY_DEFAULT};
+const Feature kPartitionAllocBackupRefPtr {
+  "PartitionAllocBackupRefPtr",
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN)
+      FEATURE_ENABLED_BY_DEFAULT
+#else
+      FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
 
 constexpr FeatureParam<BackupRefPtrEnabledProcesses>::Option
     kBackupRefPtrEnabledProcessesOptions[] = {
diff --git a/base/allocator/partition_allocator/DEPS b/base/allocator/partition_allocator/DEPS
index a452c9a..a7cfc5a 100644
--- a/base/allocator/partition_allocator/DEPS
+++ b/base/allocator/partition_allocator/DEPS
@@ -1,10 +1,9 @@
-# It's planned that PartitionAlloc will be a stand-alone third party library
-# and dependencies need to be strictly controlled and minimized.
+# PartitionAlloc is planned to be extracted into a standalone library, and
+# therefore dependencies need to be strictly controlled and minimized.
 
 noparent = True
 
 include_rules = [
-    "+base/allocator/allocator_shim_default_dispatch_to_partition_alloc.h",
     "+base/allocator/buildflags.h",
     "+base/base_export.h",
     "+base/bind.h",
@@ -13,34 +12,41 @@
     "+base/check_op.h",
     "+base/compiler_specific.h",
     "+base/dcheck_is_on.h",
-    "+base/debug/proc_maps_linux.h",
     "+base/fuchsia/fuchsia_logging.h",
     "+base/immediate_crash.h",
     "+base/lazy_instance.h",
-    "+base/location.h",
     "+base/logging_buildflags.h",
     "+base/mac/foundation_util.h",
     "+base/mac/mac_util.h",
     "+base/mac/scoped_cftyperef.h",
     "+base/process/memory.h",
+    "+base/thread_annotations.h",
+    "+base/threading/platform_thread.h",
+    "+base/time/time.h",
+    "+base/win/windows_types.h",
+    "+build/build_config.h",
+    "+build/buildflag.h",
+    "+build/chromecast_buildflags.h",
+    "+third_party/lss/linux_syscall_support.h",
+]
+
+# These are dependencies included only from tests, which means we can tackle
+# them at a later point of time.
+#
+# CAUTION! This list can't be verified automatically and thus can easily get out
+# of date.
+include_rules += [
+    "+base/allocator/allocator_shim_default_dispatch_to_partition_alloc.h",
+    "+base/debug/proc_maps_linux.h",
     "+base/strings/stringprintf.h",
     "+base/system/sys_info.h",
     "+base/test/bind.h",
     "+base/test/gtest_util.h",
-    "+base/thread_annotations.h",
-    "+base/threading/platform_thread.h",
-    "+base/time/time.h",
     "+base/time/time_override.h",
     "+base/timer/lap_timer.h",
-    "+base/trace_event/base_tracing.h",
-    "+base/win/windows_types.h",
     "+base/win/windows_version.h",
-    "+build/build_config.h",
-    "+build/buildflag.h",
-    "+build/chromecast_buildflags.h",
     "+testing/gmock/include/gmock/gmock.h",
     "+testing/gtest/include/gtest/gtest.h",
     "+testing/gtest/include/gtest/gtest_prod.h",
     "+testing/perf/perf_result_reporter.h",
-    "+third_party/lss/linux_syscall_support.h",
 ]
diff --git a/base/substring_set_matcher/substring_set_matcher.cc b/base/substring_set_matcher/substring_set_matcher.cc
index e110047..d2c66ea 100644
--- a/base/substring_set_matcher/substring_set_matcher.cc
+++ b/base/substring_set_matcher/substring_set_matcher.cc
@@ -424,7 +424,12 @@
     edges_.inline_edges[num_edges()] = AhoCorasickEdge{label, node};
     if (label == kFailureNodeLabel) {
       // Make sure that kFailureNodeLabel is first.
-      std::swap(edges_.inline_edges[0], edges_.inline_edges[num_edges()]);
+      // NOTE: We don't use std::swap here, because the compiler doesn't
+      // understand that inline_edges[] is 4-aligned and can give
+      // a warning or error.
+      AhoCorasickEdge temp = edges_.inline_edges[0];
+      edges_.inline_edges[0] = edges_.inline_edges[num_edges()];
+      edges_.inline_edges[num_edges()] = temp;
     }
     --num_free_edges_;
     return;
diff --git a/base/substring_set_matcher/substring_set_matcher.h b/base/substring_set_matcher/substring_set_matcher.h
index 13b01971..47f913f 100644
--- a/base/substring_set_matcher/substring_set_matcher.h
+++ b/base/substring_set_matcher/substring_set_matcher.h
@@ -154,8 +154,9 @@
   static constexpr uint32_t kEmptyLabel = 0x103;
 
   // A node in the trie, packed tightly together so that it occupies 12 bytes
-  // (both on 32- and 64-bit platforms).
-  class AhoCorasickNode {
+  // (both on 32- and 64-bit platforms), but aligned to at least 4 (see the
+  // comment on edges_).
+  class alignas(AhoCorasickEdge) AhoCorasickNode {
    public:
     AhoCorasickNode();
     ~AhoCorasickNode();
@@ -178,6 +179,10 @@
     NodeID GetEdgeNoInline(uint32_t label) const;
     void SetEdge(uint32_t label, NodeID node);
     const AhoCorasickEdge* edges() const {
+      // NOTE: Returning edges_.inline_edges here is fine, because it's
+      // the first thing in the struct (see the comment on edges_).
+      DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(edges_.inline_edges) %
+                        alignof(AhoCorasickEdge));
       return edges_capacity_ == 0 ? edges_.inline_edges : edges_.edges;
     }
 
@@ -258,6 +263,11 @@
     // in the first slot if it exists (ie., is not equal to kRootID), since we
     // need to access that label during every single node we look at during
     // traversal.
+    //
+    // NOTE: Keep this the first member in the struct, so that inline_edges gets
+    // 4-aligned (since the class is marked as such, despite being packed.
+    // Otherwise, edges() can return an unaligned pointer marked as aligned
+    // (the unalignedness gets lost).
     static constexpr int kNumInlineEdges = 2;
     union {
       // Out-of-line edge storage, having room for edges_capacity_ elements.
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 8d1b7a97..d1bda51 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-8.20220512.2.1
+8.20220512.3.1
diff --git a/build/toolchain/win/ml.py b/build/toolchain/win/ml.py
new file mode 100755
index 0000000..6a1b6e57
--- /dev/null
+++ b/build/toolchain/win/ml.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Wraps ml.exe or ml64.exe and postprocesses the output to be deterministic.
+Sets timestamp in .obj file to 0, hence incompatible with link.exe /incremental.
+
+Use by prefixing the ml(64).exe invocation with this script:
+    python ml.py ml.exe [args...]"""
+
+import array
+import collections
+import struct
+import subprocess
+import sys
+
+
+class Struct(object):
+  """A thin wrapper around the struct module that returns a namedtuple"""
+  def __init__(self, name, *args):
+    """Pass the name of the return type, and then an interleaved list of
+    format strings as used by the struct module and of field names."""
+    self.fmt = '<' + ''.join(args[0::2])
+    self.type = collections.namedtuple(name, args[1::2])
+
+  def pack_into(self, buffer, offset, data):
+    return struct.pack_into(self.fmt, buffer, offset, *data)
+
+  def unpack_from(self, buffer, offset=0):
+    return self.type(*struct.unpack_from(self.fmt, buffer, offset))
+
+  def size(self):
+    return struct.calcsize(self.fmt)
+
+
+def Subtract(nt, **kwargs):
+  """Subtract(nt, f=2) returns a new namedtuple with 2 subtracted from nt.f"""
+  return nt._replace(**{k: getattr(nt, k) - v for k, v in kwargs.items()})
+
+
+def MakeDeterministic(objdata):
+  # Takes data produced by ml(64).exe (without any special flags) and
+  # 1. Sets the timestamp to 0
+  # 2. Strips the .debug$S section (which contains an unwanted absolute path)
+
+  # This makes several assumptions about ml's output:
+  # - Section data is in the same order as the corresponding section headers:
+  #   section headers preceding the .debug$S section header have their data
+  #   preceding the .debug$S section data; likewise for section headers
+  #   following the .debug$S section.
+  # - The .debug$S section contains only the absolute path to the obj file and
+  #   nothing else, in particular there's only a single entry in the symbol
+  #   table referring to the .debug$S section.
+  # - There are no COFF line number entries.
+  # - There's no IMAGE_SYM_CLASS_CLR_TOKEN symbol.
+  # These seem to hold in practice; if they stop holding this script needs to
+  # become smarter.
+
+  objdata = array.array('b', objdata)  # Writable, e.g. via struct.pack_into.
+
+  # Read coff header.
+  COFFHEADER = Struct('COFFHEADER',
+                      'H', 'Machine',
+                      'H', 'NumberOfSections',
+                      'I', 'TimeDateStamp',
+                      'I', 'PointerToSymbolTable',
+                      'I', 'NumberOfSymbols',
+
+                      'H', 'SizeOfOptionalHeader',
+                      'H', 'Characteristics')
+  coff_header = COFFHEADER.unpack_from(objdata)
+  assert coff_header.SizeOfOptionalHeader == 0  # Only set for binaries.
+
+  # Read section headers following coff header.
+  SECTIONHEADER = Struct('SECTIONHEADER',
+                         '8s', 'Name',
+                         'I', 'VirtualSize',
+                         'I', 'VirtualAddress',
+
+                         'I', 'SizeOfRawData',
+                         'I', 'PointerToRawData',
+                         'I', 'PointerToRelocations',
+                         'I', 'PointerToLineNumbers',
+
+                         'H', 'NumberOfRelocations',
+                         'H', 'NumberOfLineNumbers',
+                         'I', 'Characteristics')
+  section_headers = []
+  debug_section_index = -1
+  for i in range(0, coff_header.NumberOfSections):
+    section_header = SECTIONHEADER.unpack_from(
+        objdata, offset=COFFHEADER.size() + i * SECTIONHEADER.size())
+    assert not section_header[0].startswith(b'/')  # Support short names only.
+    section_headers.append(section_header)
+
+    if section_header.Name == b'.debug$S':
+      assert debug_section_index == -1
+      debug_section_index = i
+  assert debug_section_index != -1
+
+  data_start = COFFHEADER.size() + len(section_headers) * SECTIONHEADER.size()
+
+  # Verify the .debug$S section looks like we expect.
+  assert section_headers[debug_section_index].Name == b'.debug$S'
+  assert section_headers[debug_section_index].VirtualSize == 0
+  assert section_headers[debug_section_index].VirtualAddress == 0
+  debug_size = section_headers[debug_section_index].SizeOfRawData
+  debug_offset = section_headers[debug_section_index].PointerToRawData
+  assert section_headers[debug_section_index].PointerToRelocations == 0
+  assert section_headers[debug_section_index].PointerToLineNumbers == 0
+  assert section_headers[debug_section_index].NumberOfRelocations == 0
+  assert section_headers[debug_section_index].NumberOfLineNumbers == 0
+
+  # Make sure sections in front of .debug$S have their data preceding it.
+  for header in section_headers[:debug_section_index]:
+    assert header.PointerToRawData < debug_offset
+    assert header.PointerToRelocations < debug_offset
+    assert header.PointerToLineNumbers < debug_offset
+
+  # Make sure sections after of .debug$S have their data following it.
+  for header in section_headers[debug_section_index + 1:]:
+    # Make sure the .debug$S data is at the very end of section data:
+    assert header.PointerToRawData > debug_offset
+    assert header.PointerToRelocations == 0
+    assert header.PointerToLineNumbers == 0
+
+  # Make sure the first non-empty section's data starts right after the section
+  # headers.
+  for section_header in section_headers:
+    if section_header.PointerToRawData == 0:
+      assert section_header.PointerToRelocations == 0
+      assert section_header.PointerToLineNumbers == 0
+      continue
+    assert section_header.PointerToRawData == data_start
+    break
+
+  # Make sure the symbol table (and hence, string table) appear after the last
+  # section:
+  assert (coff_header.PointerToSymbolTable >=
+      section_headers[-1].PointerToRawData + section_headers[-1].SizeOfRawData)
+
+  # The symbol table contains a symbol for the no-longer-present .debug$S
+  # section. If we leave it there, lld-link will complain:
+  #
+  #    lld-link: error: .debug$S should not refer to non-existent section 5
+  #
+  # so we need to remove that symbol table entry as well. This shifts symbol
+  # entries around and we need to update symbol table indices in:
+  # - relocations
+  # - line number records (never present)
+  # - one aux symbol entry (IMAGE_SYM_CLASS_CLR_TOKEN; not present in ml output)
+  SYM = Struct('SYM',
+               '8s', 'Name',
+               'I', 'Value',
+               'h', 'SectionNumber',  # Note: Signed!
+               'H', 'Type',
+
+               'B', 'StorageClass',
+               'B', 'NumberOfAuxSymbols')
+  i = 0
+  debug_sym = -1
+  while i < coff_header.NumberOfSymbols:
+    sym_offset = coff_header.PointerToSymbolTable + i * SYM.size()
+    sym = SYM.unpack_from(objdata, sym_offset)
+
+    # 107 is IMAGE_SYM_CLASS_CLR_TOKEN, which has aux entry "CLR Token
+    # Definition", which contains a symbol index. Check it's never present.
+    assert sym.StorageClass != 107
+
+    # Note: sym.SectionNumber is 1-based, debug_section_index is 0-based.
+    if sym.SectionNumber - 1 == debug_section_index:
+      assert debug_sym == -1, 'more than one .debug$S symbol found'
+      debug_sym = i
+      # Make sure the .debug$S symbol looks like we expect.
+      # In particular, it should have exactly one aux symbol.
+      assert sym.Name == b'.debug$S'
+      assert sym.Value == 0
+      assert sym.Type == 0
+      assert sym.StorageClass == 3
+      assert sym.NumberOfAuxSymbols == 1
+    elif sym.SectionNumber > debug_section_index:
+      sym = Subtract(sym, SectionNumber=1)
+      SYM.pack_into(objdata, sym_offset, sym)
+    i += 1 + sym.NumberOfAuxSymbols
+  assert debug_sym != -1, '.debug$S symbol not found'
+
+  # Note: Usually the .debug$S section is the last, but for files saying
+  # `includelib foo.lib`, like safe_terminate_process.asm in 32-bit builds,
+  # this isn't true: .drectve is after .debug$S.
+
+  # Update symbol table indices in relocations.
+  # There are a few processor types that have one or two relocation types
+  # where SymbolTableIndex has a different meaning, but not for x86.
+  REL = Struct('REL',
+               'I', 'VirtualAddress',
+               'I', 'SymbolTableIndex',
+               'H', 'Type')
+  for header in section_headers[0:debug_section_index]:
+    for j in range(0, header.NumberOfRelocations):
+      rel_offset = header.PointerToRelocations + j * REL.size()
+      rel = REL.unpack_from(objdata, rel_offset)
+      assert rel.SymbolTableIndex != debug_sym
+      if rel.SymbolTableIndex > debug_sym:
+        rel = Subtract(rel, SymbolTableIndex=2)
+        REL.pack_into(objdata, rel_offset, rel)
+
+  # Update symbol table indices in line numbers -- just check they don't exist.
+  for header in section_headers:
+    assert header.NumberOfLineNumbers == 0
+
+  # Now that all indices are updated, remove the symbol table entry referring to
+  # .debug$S and its aux entry.
+  del objdata[coff_header.PointerToSymbolTable + debug_sym * SYM.size():
+              coff_header.PointerToSymbolTable + (debug_sym + 2) * SYM.size()]
+
+  # Now we know that it's safe to write out the input data, with just the
+  # timestamp overwritten to 0, the last section header cut out (and the
+  # offsets of all other section headers decremented by the size of that
+  # one section header), and the last section's data cut out. The symbol
+  # table offset needs to be reduced by one section header and the size of
+  # the missing section.
+  # (The COFF spec only requires on-disk sections to be aligned in image files,
+  # for obj files it's not required. If that wasn't the case, deleting slices
+  # if data would not generally be safe.)
+
+  # Update section offsets and remove .debug$S section data.
+  for i in range(0, debug_section_index):
+    header = section_headers[i]
+    if header.SizeOfRawData:
+      header = Subtract(header, PointerToRawData=SECTIONHEADER.size())
+    if header.NumberOfRelocations:
+      header = Subtract(header, PointerToRelocations=SECTIONHEADER.size())
+    if header.NumberOfLineNumbers:
+      header = Subtract(header, PointerToLineNumbers=SECTIONHEADER.size())
+    SECTIONHEADER.pack_into(
+        objdata, COFFHEADER.size() + i * SECTIONHEADER.size(), header)
+  for i in range(debug_section_index + 1, len(section_headers)):
+    header = section_headers[i]
+    shift = SECTIONHEADER.size() + debug_size
+    if header.SizeOfRawData:
+      header = Subtract(header, PointerToRawData=shift)
+    if header.NumberOfRelocations:
+      header = Subtract(header, PointerToRelocations=shift)
+    if header.NumberOfLineNumbers:
+      header = Subtract(header, PointerToLineNumbers=shift)
+    SECTIONHEADER.pack_into(
+        objdata, COFFHEADER.size() + i * SECTIONHEADER.size(), header)
+
+  del objdata[debug_offset:debug_offset + debug_size]
+
+  # Finally, remove .debug$S section header and update coff header.
+  coff_header = coff_header._replace(TimeDateStamp=0)
+  coff_header = Subtract(coff_header,
+                         NumberOfSections=1,
+                         PointerToSymbolTable=SECTIONHEADER.size() + debug_size,
+                         NumberOfSymbols=2)
+  COFFHEADER.pack_into(objdata, 0, coff_header)
+
+  del objdata[
+      COFFHEADER.size() + debug_section_index * SECTIONHEADER.size():
+      COFFHEADER.size() + (debug_section_index + 1) * SECTIONHEADER.size()]
+
+  # All done!
+  if sys.version_info.major == 2:
+    return objdata.tostring()
+  else:
+    return objdata.tobytes()
+
+
+def main():
+  ml_result = subprocess.call(sys.argv[1:])
+  if ml_result != 0:
+    return ml_result
+
+  objfile = None
+  for i in range(1, len(sys.argv)):
+    if sys.argv[i].startswith('/Fo'):
+      objfile = sys.argv[i][len('/Fo'):]
+  assert objfile, 'failed to find ml output'
+
+  with open(objfile, 'rb') as f:
+    objdata = f.read()
+  objdata = MakeDeterministic(objdata)
+  with open(objfile, 'wb') as f:
+    f.write(objdata)
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/build/toolchain/win/toolchain.gni b/build/toolchain/win/toolchain.gni
index 9cdcc6bf..e7fd6209 100644
--- a/build/toolchain/win/toolchain.gni
+++ b/build/toolchain/win/toolchain.gni
@@ -254,12 +254,11 @@
           ml = "armasm64.exe"
         }
       } else {
-        prefix = rebase_path("$clang_base_path/bin", root_build_dir)
-        ml = "$prefix/llvm-ml.exe"
+        # x86/x64 builds always use the MSVC assembler.
         if (toolchain_args.current_cpu == "x64") {
-          ml += " --m64"
+          ml = "ml64.exe"
         } else {
-          ml += " --m32"
+          ml = "ml.exe"
         }
       }
 
@@ -271,6 +270,16 @@
         if (toolchain_args.current_cpu != "arm64") {
           ml += " /c"
         }
+        if (use_lld) {
+          # Wrap ml(64).exe with a script that makes its output deterministic.
+          # It's lld only because the script zaps obj Timestamp which
+          # link.exe /incremental looks at.
+          # TODO(https://crbug.com/762167): If we end up writing an llvm-ml64,
+          # make sure it has deterministic output (maybe with /Brepro or
+          # something) and remove this wrapper.
+          ml_py = rebase_path("//build/toolchain/win/ml.py", root_build_dir)
+          ml = "$python_path $ml_py $ml"
+        }
       }
       if (toolchain_args.current_cpu != "arm64" || toolchain_is_clang) {
         command = "$python_path $_tool_wrapper_path asm-wrapper $env $ml {{defines}} {{include_dirs}} {{asmflags}} {{source}}"
diff --git a/chrome/MAJOR_BRANCH_DATE b/chrome/MAJOR_BRANCH_DATE
index 11d291b..7e606ca 100644
--- a/chrome/MAJOR_BRANCH_DATE
+++ b/chrome/MAJOR_BRANCH_DATE
@@ -1 +1 @@
-MAJOR_BRANCH_DATE=2022-04-15
+MAJOR_BRANCH_DATE=2022-05-13
diff --git a/chrome/VERSION b/chrome/VERSION
index fcee37e..3a56067c 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
-MAJOR=103
+MAJOR=104
 MINOR=0
-BUILD=5060
+BUILD=5061
 PATCH=0
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 3708e30..a5b9767 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5636,7 +5636,7 @@
           Use Strong Password
         </message>
       </if>
-      
+
        <message name="IDS_PASSWORD_GENERATION_EDITING_SUGGESTION" desc="Notification text next to the generated password assuring the user that the password has been saved.">
         Saved password
       </message>
@@ -7003,7 +7003,7 @@
         <message name="IDS_INTENT_PICKER_BUBBLE_VIEW_INITIATING_ORIGIN" desc="The label for the initiating origin of the intent picker.">
           From <ph name="ORIGIN">$1<ex>https://google.com</ex></ph>
         </message>
-        <message name="IDS_INTENT_CHIP_LABEL" desc="The label of a button to open the current webpage in an installed app.">
+        <message name="IDS_INTENT_CHIP_OPEN_IN_APP" desc="The label of a button or dialog to open the current webpage in an installed app.">
           Open in app
         </message>
         <message name="IDS_INTENT_CHIP_IPH" desc="The text for the In-Product Help bubble for the Intent Chip.">
@@ -7011,12 +7011,22 @@
         </message>
 
         <if expr="chromeos_ash or chromeos_lacros">
-          <message name="IDR_INTENT_PICKER_SUPPORTED_LINKS_INFOBAR_MESSAGE" desc="The text of an infobar asking if the user wants to open web links supported by a particular app (e.g. https://www.youtube.com) in that app.">
-            Always use the <ph name="APP">$1<ex>YouTube</ex></ph> app to open supported web links?
-          </message>
-          <message name="IDR_INTENT_PICKER_SUPPORTED_LINKS_INFOBAR_OK_LABEL" desc="The label of a confirm button for an infobar asking if the user wants to open web links in an app. If clicked, the selected app will always be opened when clicking supported web links.">
-            Always use
-          </message>
+          <then>
+            <message name="IDR_INTENT_PICKER_SUPPORTED_LINKS_INFOBAR_MESSAGE" desc="The text of an infobar asking if the user wants to open web links supported by a particular app (e.g. https://www.youtube.com) in that app.">
+              Always use the <ph name="APP">$1<ex>YouTube</ex></ph> app to open supported web links?
+            </message>
+            <message name="IDR_INTENT_PICKER_SUPPORTED_LINKS_INFOBAR_OK_LABEL" desc="The label of a confirm button for an infobar asking if the user wants to open web links in an app. If clicked, the selected app will always be opened when clicking supported web links.">
+              Always use
+            </message>
+            <message name="IDS_INTENT_PICKER_SELECT_AN_APP_SUBTITLE" desc="The subtitle of a dialog containing a list of installed applications. The dialog allows the user to select an app to open a web link.">
+              Select an app on your <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph> to open this link
+            </message>
+          </then>
+          <else>
+            <message name="IDS_INTENT_PICKER_SELECT_AN_APP_GENERIC_SUBTITLE" desc="The subtitle of a dialog containing a list of installed applications. The dialog allows the user to select an app to open a web link.">
+              Select an app on your device to open this link
+            </message>
+          </else>
         </if>
       </if>
 
diff --git a/chrome/app/generated_resources_grd/IDS_INTENT_CHIP_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_INTENT_CHIP_OPEN_IN_APP.png.sha1
similarity index 100%
rename from chrome/app/generated_resources_grd/IDS_INTENT_CHIP_LABEL.png.sha1
rename to chrome/app/generated_resources_grd/IDS_INTENT_CHIP_OPEN_IN_APP.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_INTENT_PICKER_SELECT_AN_APP_GENERIC_SUBTITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_INTENT_PICKER_SELECT_AN_APP_GENERIC_SUBTITLE.png.sha1
new file mode 100644
index 0000000..03ca583
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_INTENT_PICKER_SELECT_AN_APP_GENERIC_SUBTITLE.png.sha1
@@ -0,0 +1 @@
+38c6ccba20a07f5e1d9890c8c65ca6dce2a9dae3
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_INTENT_PICKER_SELECT_AN_APP_SUBTITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_INTENT_PICKER_SELECT_AN_APP_SUBTITLE.png.sha1
new file mode 100644
index 0000000..e78b87ec
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_INTENT_PICKER_SELECT_AN_APP_SUBTITLE.png.sha1
@@ -0,0 +1 @@
+3f6403ffcca9533e415c39b1c7c6c9edae1edbbc
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 77f2245..961d6f7 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3250,6 +3250,7 @@
       "password_manager/android/password_accessory_controller_impl.cc",
       "password_manager/android/password_accessory_controller_impl.h",
       "password_manager/android/password_accessory_metrics_util.h",
+      "password_manager/android/password_change_success_tracker_bridge.cc",
       "password_manager/android/password_checkup_launcher_helper.cc",
       "password_manager/android/password_checkup_launcher_helper.h",
       "password_manager/android/password_generation_controller.h",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 16c83115..3f9a777 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4660,6 +4660,10 @@
      flag_descriptions::kVirtualKeyboardMultitouchName,
      flag_descriptions::kVirtualKeyboardMultitouchDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kVirtualKeyboardMultitouch)},
+    {"enable-cros-virtual-keyboard-round-corners",
+     flag_descriptions::kVirtualKeyboardRoundCornersName,
+     flag_descriptions::kVirtualKeyboardRoundCornersDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kVirtualKeyboardRoundCorners)},
     {"enable-experimental-accessibility-dictation-with-pumpkin",
      flag_descriptions::kExperimentalAccessibilityDictationWithPumpkinName,
      flag_descriptions::
@@ -4894,6 +4898,10 @@
     {"spectre-v2-mitigation", flag_descriptions::kSpectreVariant2MitigationName,
      flag_descriptions::kSpectreVariant2MitigationDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(sandbox::policy::features::kSpectreVariant2Mitigation)},
+    {"eap-gtc-wifi-authentication",
+     flag_descriptions::kEapGtcWifiAuthenticationName,
+     flag_descriptions::kEapGtcWifiAuthenticationDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kEapGtcWifiAuthentication)},
     {"eche-phone-hub-permissions-onboarding",
      flag_descriptions::kEchePhoneHubPermissionsOnboardingName,
      flag_descriptions::kEchePhoneHubPermissionsOnboardingDescription, kOsCrOS,
diff --git a/chrome/browser/ash/crostini/crostini_installer.cc b/chrome/browser/ash/crostini/crostini_installer.cc
index 29939bb..7ddf34f 100644
--- a/chrome/browser/ash/crostini/crostini_installer.cc
+++ b/chrome/browser/ash/crostini/crostini_installer.cc
@@ -180,8 +180,8 @@
 
 void CrostiniInstaller::Shutdown() {
   if (restart_id_ != CrostiniManager::kUninitializedRestartId) {
-    CrostiniManager::GetForProfile(profile_)->AbortRestartCrostini(
-        restart_id_, base::DoNothing());
+    CrostiniManager::GetForProfile(profile_)->CancelRestartCrostini(
+        restart_id_);
     restart_id_ = CrostiniManager::kUninitializedRestartId;
   }
 }
@@ -275,7 +275,7 @@
   // Abort the long-running flow, and RestartObserver methods will not be called
   // again until next installation.
   auto* crostini_manager = crostini::CrostiniManager::GetForProfile(profile_);
-  crostini_manager->AbortRestartCrostini(restart_id_, base::DoNothing());
+  crostini_manager->CancelRestartCrostini(restart_id_);
   restart_id_ = CrostiniManager::kUninitializedRestartId;
   RecordSetupResult(InstallStateToCancelledSetupResult(installing_state_));
 
diff --git a/chrome/browser/ash/crostini/crostini_manager.cc b/chrome/browser/ash/crostini/crostini_manager.cc
index d7a6503..f6b9f962 100644
--- a/chrome/browser/ash/crostini/crostini_manager.cc
+++ b/chrome/browser/ash/crostini/crostini_manager.cc
@@ -194,6 +194,8 @@
 
 const char kCrostiniStabilityHistogram[] = "Crostini.Stability";
 
+CrostiniManager::RestartId CrostiniManager::next_restart_id_ = 0;
+
 CrostiniManager::RestartOptions::RestartOptions() = default;
 CrostiniManager::RestartOptions::RestartOptions(RestartOptions&&) = default;
 CrostiniManager::RestartOptions::~RestartOptions() = default;
@@ -204,49 +206,45 @@
     : public ash::VmShutdownObserver,
       public chromeos::SchedulerConfigurationManagerBase::Observer {
  public:
+  struct RestartRequest {
+    RestartId restart_id;
+    RestartOptions options;
+    CrostiniResultCallback callback;
+    RestartObserver* observer;  // optional
+  };
+
   CrostiniRestarter(Profile* profile,
                     CrostiniManager* crostini_manager,
                     ContainerId container_id,
-                    RestartOptions options,
-                    CrostiniManager::CrostiniResultCallback callback);
+                    RestartRequest request);
   ~CrostiniRestarter() override;
 
-  void Restart();
-  void AddObserver(CrostiniManager::RestartObserver* observer) {
-    observer_list_.AddObserver(observer);
-  }
+  void AddRequest(RestartRequest request);
 
-  // This gets called exactly once, at the end of the restart, either from
-  // Abort() or from CrostiniManager::FinishRestart().
-  void RunCallback(CrostiniResult result);
+  // Start the restart flow. This should only be called once. This cannot be
+  // called directly from the constructor as in some cases it immediately
+  // (synchronously) fails and causes |this| to be deleted.
+  void Restart();
 
   // ash::VmShutdownObserver
   void OnVmShutdown(const std::string& vm_name) override;
 
   void Timeout(mojom::InstallerState state);
 
-  // On abort, |completed_callback_| is immediately invoked via RunCallback().
-  // We wait for the current stage to complete and then quit the flow and call
-  // |callback|.
+  // Cancel an individual request and fire its callback immediately. If there
+  // are no other outstanding requests, stop the restarter once possible.
+  void CancelRequest(RestartId restart_id);
+  // Abort the entire restart. Pending requests are immediately completed, and
+  // |callback| is called once the current operation has finished. Requests
+  // should not be added to an aborted restarter.
   void Abort(base::OnceClosure callback);
-  // If this method returns true, then |this| may have been deleted and it is
-  // unsafe to refer to any member variables.
-  bool ReturnEarlyIfAborted();
 
   // These are called directly from CrostiniManager.
   void OnContainerDownloading(int download_percent);
   void OnLxdContainerStarting(
       vm_tools::cicerone::LxdContainerStartingSignal_Status status);
 
-  CrostiniManager::RestartId restart_id() const { return restart_id_; }
   const ContainerId& container_id() { return container_id_; }
-  bool is_aborted() const { return !abort_callbacks_.empty(); }
-  // If the restarter was not aborted early (either via Abort() or an option
-  // like start_vm_only), the result can be used to complete other restarters
-  // for the same ContainerId.
-  bool RestartAppliesToEquivalentRestarters() {
-    return restart_applies_to_equivalent_restarters_;
-  }
 
   // This is public so CallRestarterStartLxdContainerFinishedForTesting can call
   // it.
@@ -256,9 +254,25 @@
   void StartStage(mojom::InstallerState stage);
   void EmitMetricIfInIncorrectState(mojom::InstallerState expected);
 
+  using RequestFilter = base::RepeatingCallback<bool(const RestartRequest&)>;
+  // Removes matched requests and returns a closure which will run the
+  // corresponding completion callbacks.
+  base::OnceClosure ExtractRequests(RequestFilter filter,
+                                    CrostiniResult result);
+  void FinishRequests(RequestFilter filter, CrostiniResult result) {
+    return ExtractRequests(filter, result).Run();
+  }
+
+  // The restarter flow ends early if Abort() is called or all requests have
+  // been cancelled or otherwise fulfilled (e.g. when start_vm_only is set).
+  // If this method returns true, then FinishRestart() is called and |this|
+  // gets deleted so it is unsafe to refer to any member variables.
+  bool ReturnEarlyIfNeeded();
+
   // In a successful complete restart, every function in the below list in
-  // called in order, from Restart() to FinishRestart(). If the restarter is
-  // aborted or an operation fails or times out, it proceeds directly to
+  // called in order, from Restart() to FinishRestart(). If the restarter
+  // finishes early (i.e. restarter aborted, all requests cancelled or
+  // completed, operation fails or times out), it proceeds directly to
   // FinishRestart().
 
   // Public function - Restart();
@@ -277,11 +291,16 @@
   void CreateLxdContainerFinished(CrostiniResult result);
   void SetUpLxdContainerUserFinished(bool success);
   // Public function - StartLxdContainerFinished(CrostiniResult result);
-  // FinishRestart function can cause |this| to be deleted, so callers should
-  // return immediately after calling this. This should be called exactly once
-  // per restarter.
+  // FinishRestart() causes |this| to be deleted, so callers should return
+  // immediately after calling this.
   void FinishRestart(CrostiniResult result);
 
+  // If the current operation can be cancelled, cancel it, complete the
+  // restart (deleting |this|), and return true.
+  bool MaybeCancelCurrentOperation();
+
+  void LogRestarterResult(CrostiniResult result);
+
   base::OneShotTimer stage_timeout_timer_;
   base::TimeTicks stage_start_;
 
@@ -312,53 +331,45 @@
   CrostiniManager* crostini_manager_;
 
   const ContainerId container_id_;
-  base::FilePath disk_path_;
-  RestartOptions options_;
-  std::string source_path_;
-  base::FilePath container_homedir_;
   bool is_initial_install_ = false;
-  CrostiniManager::CrostiniResultCallback completed_callback_;
   std::vector<base::OnceClosure> abort_callbacks_;
+  // Options which only affect new containers will be taken from the first
+  // request.
+  std::vector<RestartRequest> requests_;
+  // Pulled out of requests_ for convenience.
   base::ObserverList<CrostiniManager::RestartObserver>::Unchecked
       observer_list_;
-  CrostiniManager::RestartId restart_id_;
+  // TODO(timloh): This should just be an extra state at the start of the flow.
   bool is_running_ = false;
-  bool restart_applies_to_equivalent_restarters_ = true;
-  size_t num_cores_disabled_ = 0;
-  mojom::InstallerState stage_ = mojom::InstallerState::kStart;
-  CrostiniResult result_ = CrostiniResult::NEVER_FINISHED;
 
-  static CrostiniManager::RestartId next_restart_id_;
+  // Data passed between different steps of the restart flow.
+  base::FilePath disk_path_;
+  size_t num_cores_disabled_ = 0;
+
+  mojom::InstallerState stage_ = mojom::InstallerState::kStart;
 
   base::WeakPtrFactory<CrostiniRestarter> weak_ptr_factory_{this};
 };
 
-CrostiniManager::RestartId
-    CrostiniManager::CrostiniRestarter::next_restart_id_ = 0;
-
 CrostiniManager::CrostiniRestarter::CrostiniRestarter(
     Profile* profile,
     CrostiniManager* crostini_manager,
     ContainerId container_id,
-    RestartOptions options,
-    CrostiniManager::CrostiniResultCallback callback)
+    RestartRequest request)
     : profile_(profile),
       crostini_manager_(crostini_manager),
-      container_id_(std::move(container_id)),
-      options_(std::move(options)),
-      completed_callback_(std::move(callback)),
-      restart_id_(next_restart_id_++) {}
+      container_id_(std::move(container_id)) {
+  AddRequest(std::move(request));
+}
 
 CrostiniManager::CrostiniRestarter::~CrostiniRestarter() {
-  // Do not record results if this restart was triggered by the installer.
-  // The crostini installer has its own histograms that should be kept
-  // separate.
-  if (!is_initial_install_) {
-    base::UmaHistogramEnumeration("Crostini.RestarterResult", result_);
-  }
   crostini_manager_->RemoveVmShutdownObserver(this);
-  if (completed_callback_) {
-    LOG(ERROR) << "Destroying without having called the callback.";
+  if (!requests_.empty()) {
+    // This is triggered by logging out when restarts are in progress.
+    LOG(WARNING) << "Destroying with outstanding requests.";
+    for (int i = 0; i < requests_.size(); i++) {
+      LogRestarterResult(CrostiniResult::NEVER_FINISHED);
+    }
   }
 }
 
@@ -372,11 +383,16 @@
   }
 
   crostini_manager_->AddVmShutdownObserver(this);
-
-  StartStage(mojom::InstallerState::kStart);
+  // TODO(timloh): This is currently false for additional containers created via
+  // settings, but we probably don't want those to be bucketed in the same
+  // histograms as other non-install restarts.
+  // TODO(b/205650706): It is possible to invoke a CrostiniRestarter to install
+  // Crostini without using the actual installer. We should handle these better.
   is_initial_install_ =
       crostini_manager_->GetCrostiniDialogStatus(DialogType::INSTALLER);
-  if (ReturnEarlyIfAborted()) {
+
+  StartStage(mojom::InstallerState::kStart);
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
 
@@ -389,21 +405,19 @@
   }
 }
 
-void CrostiniManager::CrostiniRestarter::RunCallback(CrostiniResult result) {
-  // Observer should not be called if we have completed.
-  observer_list_.Clear();
+void CrostiniManager::CrostiniRestarter::AddRequest(RestartRequest request) {
+  // CrostiniManager doesn't add requests to aborted restarts.
+  DCHECK(abort_callbacks_.empty());
 
-  DCHECK_EQ(result_, CrostiniResult::NEVER_FINISHED);
-  result_ = result;
-
-  if (completed_callback_) {
-    std::move(completed_callback_).Run(result_);
+  if (request.observer) {
+    observer_list_.AddObserver(request.observer);
   }
+  requests_.push_back(std::move(request));
 }
 
 void CrostiniManager::CrostiniRestarter::OnVmShutdown(
     const std::string& vm_name) {
-  if (ReturnEarlyIfAborted()) {
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
   if (vm_name == container_id_.vm_name) {
@@ -451,47 +465,39 @@
     case mojom::InstallerState::kStart:
       NOTREACHED();
   }
-  // Note: FinishRestart may delete |this|.
+  // Note: FinishRestart deletes |this|.
   FinishRestart(result);
 }
 
+void CrostiniManager::CrostiniRestarter::CancelRequest(RestartId restart_id) {
+  FinishRequests(
+      base::BindRepeating(
+          [](RestartId restart_id, const RestartRequest& request) -> bool {
+            return request.restart_id == restart_id;
+          },
+          restart_id),
+      CrostiniResult::RESTART_ABORTED);
+
+  if (requests_.empty()) {
+    // May delete |this|.
+    MaybeCancelCurrentOperation();
+  }
+}
+
 void CrostiniManager::CrostiniRestarter::Abort(base::OnceClosure callback) {
-  restart_applies_to_equivalent_restarters_ = false;
   abort_callbacks_.push_back(std::move(callback));
   if (abort_callbacks_.size() > 1) {
     // The subsequent steps only need to be run once.
     return;
   }
 
-  // Run the main callback immediately, but wait for the current step to
+  // Run the result callbacks immediately, but wait for the current step to
   // finish before invoking the abort callback.
-  RunCallback(CrostiniResult::RESTART_ABORTED);
-  if (stage_ == mojom::InstallerState::kInstallImageLoader) {
-    // TerminaInstaller offers a way to cancel installation, which also
-    // prevents any callback from running. In this case we can proceed
-    // directly to running the abort callbacks.
-    crostini_manager_->CancelInstallTermina();
-    // Callers may not expect their callback to be run within the same task.
-    base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(
-                       [](base::WeakPtr<CrostiniRestarter> weak_this) {
-                         if (weak_this) {
-                           weak_this->ReturnEarlyIfAborted();
-                         }
-                       },
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-}
-
-bool CrostiniManager::CrostiniRestarter::ReturnEarlyIfAborted() {
-  if (!is_aborted())
-    return false;
-
-  for (auto& abort_callback : abort_callbacks_) {
-    std::move(abort_callback).Run();
-  }
-  FinishRestart(CrostiniResult::RESTART_ABORTED);
-  return true;
+  FinishRequests(
+      base::BindRepeating([](const RestartRequest& request) { return true; }),
+      CrostiniResult::RESTART_ABORTED);
+  // May delete |this|.
+  MaybeCancelCurrentOperation();
 }
 
 void CrostiniManager::CrostiniRestarter::OnContainerDownloading(
@@ -530,7 +536,7 @@
   for (auto& observer : observer_list_) {
     observer.OnContainerStarted(result);
   }
-  if (ReturnEarlyIfAborted()) {
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
   EmitMetricIfInIncorrectState(mojom::InstallerState::kStartContainer);
@@ -585,6 +591,41 @@
   }
 }
 
+base::OnceClosure CrostiniManager::CrostiniRestarter::ExtractRequests(
+    RequestFilter filter,
+    CrostiniResult result) {
+  std::vector<CrostiniResultCallback> callbacks;
+  for (auto it = requests_.begin(); it != requests_.end();) {
+    if (!filter.Run(*it)) {
+      it++;
+      continue;
+    }
+
+    crostini_manager_->RemoveRestartId(it->restart_id);
+    if (it->observer)
+      observer_list_.RemoveObserver(it->observer);
+    callbacks.push_back(std::move(it->callback));
+    requests_.erase(it);
+
+    LogRestarterResult(result);
+  }
+
+  return base::BindOnce(
+      [](std::vector<CrostiniResultCallback> callbacks, CrostiniResult result) {
+        for (auto& callback : callbacks)
+          std::move(callback).Run(result);
+      },
+      std::move(callbacks), result);
+}
+
+bool CrostiniManager::CrostiniRestarter::ReturnEarlyIfNeeded() {
+  if (!requests_.empty())
+    return false;
+  // The result is ignored since there are no requests left.
+  FinishRestart(CrostiniResult::UNKNOWN_ERROR);
+  return true;
+}
+
 void CrostiniManager::CrostiniRestarter::ContinueRestart() {
   is_running_ = true;
   // Skip to the end immediately if testing.
@@ -608,7 +649,7 @@
   for (auto& observer : observer_list_) {
     observer.OnComponentLoaded(result);
   }
-  if (ReturnEarlyIfAborted()) {
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
   EmitMetricIfInIncorrectState(mojom::InstallerState::kInstallImageLoader);
@@ -620,7 +661,7 @@
   profile_->GetPrefs()->SetBoolean(crostini::prefs::kCrostiniEnabled, true);
 
   // Allow concierge to choose an appropriate disk image size.
-  int64_t disk_size_bytes = options_.disk_size_bytes.value_or(0);
+  int64_t disk_size_bytes = requests_[0].options.disk_size_bytes.value_or(0);
   // If we have an already existing disk, CreateDiskImage will just return its
   // path so we can pass it to StartTerminaVm.
   StartStage(mojom::InstallerState::kCreateDiskImage);
@@ -641,7 +682,7 @@
   for (auto& observer : observer_list_) {
     observer.OnDiskImageCreated(success, status, disk_size_bytes);
   }
-  if (ReturnEarlyIfAborted()) {
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
   EmitMetricIfInIncorrectState(mojom::InstallerState::kCreateDiskImage);
@@ -707,7 +748,7 @@
   for (auto& observer : observer_list_) {
     observer.OnVmStarted(success);
   }
-  if (ReturnEarlyIfAborted()) {
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
   EmitMetricIfInIncorrectState(mojom::InstallerState::kStartTerminaVm);
@@ -724,15 +765,23 @@
     crostini_manager_->UpdateTerminaVmKernelVersion();
   }
 
-  if (options_.start_vm_only) {
-    restart_applies_to_equivalent_restarters_ = false;
-    FinishRestart(CrostiniResult::SUCCESS);
+  // TODO(timloh): Requests with start_vm_only added too late will miss this and
+  // thus fail if any later step fails. Perhaps they should be completed
+  // immediately.
+  FinishRequests(base::BindRepeating([](const RestartRequest& request) {
+                   return request.options.start_vm_only;
+                 }),
+                 CrostiniResult::SUCCESS);
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
 
   // Share any non-persisted paths for the VM.
+  // TODO(timloh): This should probably share paths from all requests. Requests
+  // added too late will also miss this.
   guest_os::GuestOsSharePath::GetForProfile(profile_)->SharePaths(
-      container_id_.vm_name, options_.share_paths, /*persist=*/false,
+      container_id_.vm_name, requests_[0].options.share_paths,
+      /*persist=*/false,
       base::BindOnce(&CrostiniRestarter::SharePathsFinished,
                      weak_ptr_factory_.GetWeakPtr()));
 }
@@ -756,7 +805,7 @@
   for (auto& observer : observer_list_) {
     observer.OnLxdStarted(result);
   }
-  if (ReturnEarlyIfAborted()) {
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
   EmitMetricIfInIncorrectState(mojom::InstallerState::kStartLxd);
@@ -764,14 +813,19 @@
     FinishRestart(result);
     return;
   }
-  if (options_.stop_after_lxd_available) {
-    restart_applies_to_equivalent_restarters_ = false;
-    FinishRestart(CrostiniResult::SUCCESS);
+
+  FinishRequests(base::BindRepeating([](const RestartRequest& request) {
+                   return request.options.stop_after_lxd_available;
+                 }),
+                 CrostiniResult::SUCCESS);
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
+
   StartStage(mojom::InstallerState::kCreateContainer);
   crostini_manager_->CreateLxdContainer(
-      container_id_, options_.image_server_url, options_.image_alias,
+      container_id_, requests_[0].options.image_server_url,
+      requests_[0].options.image_alias,
       base::BindOnce(&CrostiniRestarter::CreateLxdContainerFinished,
                      weak_ptr_factory_.GetWeakPtr()));
 }
@@ -782,7 +836,7 @@
   for (auto& observer : observer_list_) {
     observer.OnContainerCreated(result);
   }
-  if (ReturnEarlyIfAborted()) {
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
   EmitMetricIfInIncorrectState(mojom::InstallerState::kCreateContainer);
@@ -794,7 +848,7 @@
   StartStage(mojom::InstallerState::kSetupContainer);
   crostini_manager_->SetUpLxdContainerUser(
       container_id_,
-      options_.container_username.value_or(
+      requests_[0].options.container_username.value_or(
           DefaultContainerUserNameForProfile(profile_)),
       base::BindOnce(&CrostiniRestarter::SetUpLxdContainerUserFinished,
                      weak_ptr_factory_.GetWeakPtr()));
@@ -807,7 +861,7 @@
   for (auto& observer : observer_list_) {
     observer.OnContainerSetup(success);
   }
-  if (ReturnEarlyIfAborted()) {
+  if (ReturnEarlyIfNeeded()) {
     return;
   }
   EmitMetricIfInIncorrectState(mojom::InstallerState::kSetupContainer);
@@ -824,14 +878,64 @@
 }
 
 void CrostiniManager::CrostiniRestarter::FinishRestart(CrostiniResult result) {
-  // RunCallback() is usually invoked from CrostiniManager::FinishRestart()
-  // but when aborted is explicitly called earlier.
-  DCHECK(result_ == CrostiniResult::NEVER_FINISHED ||
-         result_ == CrostiniResult::RESTART_ABORTED);
   EmitTimeInStageHistogram(base::TimeTicks::Now() - stage_start_, stage_);
 
-  // CrostiniManager::FinishRestart deletes |this|, sometimes synchronously.
-  crostini_manager_->FinishRestart(this, result);
+  base::OnceClosure closure;
+  if (abort_callbacks_.empty()) {
+    closure = ExtractRequests(
+        base::BindRepeating([](const RestartRequest& request) { return true; }),
+        result);
+  } else {
+    // Requests have already been completed, and new requests are not allowed.
+    for (auto& abort_callback : abort_callbacks_) {
+      std::move(abort_callback).Run();
+    }
+    abort_callbacks_.clear();
+    closure = base::DoNothing();
+  }
+
+  DCHECK(requests_.empty());
+  DCHECK(observer_list_.empty());
+
+  // CrostiniManager::RestartCompleted deletes |this|
+  crostini_manager_->RestartCompleted(this, std::move(closure));
+}
+
+bool CrostiniManager::CrostiniRestarter::MaybeCancelCurrentOperation() {
+  if (stage_ == mojom::InstallerState::kInstallImageLoader) {
+    // TerminaInstaller offers a way to cancel installation, which also
+    // prevents any callback from running.
+    crostini_manager_->CancelInstallTermina();
+
+    // Not specific to kInstallImageLoader, this will also need to be run if
+    // any other steps are made cancellable.
+    // TODO(timloh): This posts a task because unit tests synchronously cancel
+    // restart requests from observer methods. If we remove this behaviour,
+    // we could just call ReturnEarlyIfNeeded() directly.
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(
+                       [](base::WeakPtr<CrostiniRestarter> weak_this) {
+                         if (weak_this) {
+                           weak_this->ReturnEarlyIfNeeded();
+                         }
+                       },
+                       weak_ptr_factory_.GetWeakPtr()));
+    return true;
+  }
+
+  // Current stage can not be cancelled.
+  return false;
+}
+
+void CrostiniManager::CrostiniRestarter::LogRestarterResult(
+    CrostiniResult result) {
+  // Separate Crostini installer restarts from already-installed restarts.
+  // The installer has separate histograms in Crostini.SetupResult.
+  // TODO(timloh): The installer histograms are less granular, we might want to
+  // also log something here.
+  if (!is_initial_install_) {
+    base::UmaHistogramEnumeration("Crostini.RestarterResult", result);
+  }
 }
 
 // Unit tests need this initialized to true. In Browser tests and real life,
@@ -2170,50 +2274,53 @@
     return kUninitializedRestartId;
   }
 
-  auto restarter = std::make_unique<CrostiniRestarter>(
-      profile_, this, container_id, std::move(options), std::move(callback));
-  auto restart_id = restarter->restart_id();
-  restarters_by_container_.emplace(container_id, restart_id);
-  restarters_by_id_[restart_id] = std::move(restarter);
+  RestartId restart_id = next_restart_id_++;
+  restarters_by_id_.emplace(restart_id, container_id);
 
-  // Observers will watch this restarter and any others that run before it.
-  if (observer) {
-    auto range = restarters_by_container_.equal_range(container_id);
-    for (auto it = range.first; it != range.second; ++it) {
-      restarters_by_id_[it->second]->AddObserver(observer);
-    }
-  }
+  CrostiniRestarter::RestartRequest request = {restart_id, std::move(options),
+                                               std::move(callback), observer};
 
-  if (restarters_by_container_.count(container_id) > 1) {
-    VLOG(1) << "Already restarting " << container_id;
+  auto it = restarters_by_container_.find(container_id);
+  if (it == restarters_by_container_.end()) {
+    VLOG(1) << "Creating new restarter for " << container_id;
+    restarters_by_container_[container_id] =
+        std::make_unique<CrostiniRestarter>(profile_, this, container_id,
+                                            std::move(request));
+    // In some cases this will synchronously finish the restart and cause it to
+    // be deleted and removed from the map.
+    restarters_by_container_[container_id]->Restart();
   } else {
-    // Restart() needs to be called after the restarter is inserted into
-    // restarters_by_id_ because some tests will make the restart process
-    // complete before Restart() returns.
-    restarters_by_id_[restart_id]->Restart();
+    VLOG(1) << "Already restarting " << container_id;
+    if (request.options.container_username || request.options.disk_size_bytes ||
+        request.options.image_server_url || request.options.image_alias) {
+      LOG(ERROR)
+          << "Crostini restart options for new containers will be ignored "
+             "as a restart is already in progress.";
+    }
+    it->second->AddRequest(std::move(request));
   }
 
   return restart_id;
 }
 
-void CrostiniManager::AbortRestartCrostini(
-    CrostiniManager::RestartId restart_id,
-    base::OnceClosure callback) {
-  auto restarter_it = restarters_by_id_.find(restart_id);
-  if (restarter_it == restarters_by_id_.end()) {
-    // This can happen if a user cancels the install flow at the exact right
-    // moment, for example.
-    LOG(ERROR) << "Aborting a restarter that already finished";
-    content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
-                                                 std::move(callback));
+void CrostiniManager::CancelRestartCrostini(
+    CrostiniManager::RestartId restart_id) {
+  auto container_it = restarters_by_id_.find(restart_id);
+  if (container_it == restarters_by_id_.end()) {
+    // Only tests execute this path at the time of writing but be defensive
+    // just in case.
+    LOG(ERROR)
+        << "Cancelling a restarter that does not exist (already finished?)"
+        << ", id = " << restart_id;
     return;
   }
-  restarter_it->second->Abort(std::move(callback));
+  auto restarter_it = restarters_by_container_.find(container_it->second);
+  DCHECK(restarter_it != restarters_by_container_.end());
+  restarter_it->second->CancelRequest(restart_id);
 }
 
 bool CrostiniManager::IsRestartPending(RestartId restart_id) {
-  auto it = restarters_by_id_.find(restart_id);
-  return it != restarters_by_id_.end() && !it->second->is_aborted();
+  return restarters_by_id_.find(restart_id) != restarters_by_id_.end();
 }
 
 void CrostiniManager::AddShutdownContainerCallback(
@@ -2441,6 +2548,11 @@
           << vm_name;
   UpdateVmState(vm_name, VmState::STARTED);
 
+  // TODO(timloh): These should probably either be in CrostiniRestarter
+  // alongside sharing non-persisted paths, or separated entirely from the
+  // restart flow and instead run for all Guest OS types whenever they start
+  // up. For fonts, this could be done directly in concierge (b/231252066).
+
   // Share fonts directory with the VM but don't persist as a shared path.
   guest_os::GuestOsSharePath::GetForProfile(profile_)->SharePath(
       vm_name, base::FilePath(file_manager::util::kSystemFontsPath),
@@ -3088,10 +3200,9 @@
     return;
   }
   ContainerId container_id(signal.vm_name(), signal.container_name());
-  auto range = restarters_by_container_.equal_range(container_id);
-  for (auto it = range.first; it != range.second; ++it) {
-    restarters_by_id_[it->second]->OnContainerDownloading(
-        signal.download_progress());
+  auto iter = restarters_by_container_.find(container_id);
+  if (iter != restarters_by_container_.end()) {
+    iter->second->OnContainerDownloading(signal.download_progress());
   }
 }
 
@@ -3115,9 +3226,6 @@
     return;
   ContainerId container_id(signal.vm_name(), signal.container_name());
   CrostiniResult result;
-  std::pair<std::multimap<crostini::ContainerId, int>::iterator,
-            std::multimap<crostini::ContainerId, int>::iterator>
-      range;
 
   switch (signal.status()) {
     case vm_tools::cicerone::LxdContainerStartingSignal::UNKNOWN:
@@ -3132,12 +3240,13 @@
     case vm_tools::cicerone::LxdContainerStartingSignal::FAILED:
       result = CrostiniResult::CONTAINER_START_FAILED;
       break;
-    case vm_tools::cicerone::LxdContainerStartingSignal::STARTING:
-      range = restarters_by_container_.equal_range(container_id);
-      for (auto it = range.first; it != range.second; ++it) {
-        restarters_by_id_[it->second]->OnLxdContainerStarting(signal.status());
+    case vm_tools::cicerone::LxdContainerStartingSignal::STARTING: {
+      auto iter = restarters_by_container_.find(container_id);
+      if (iter != restarters_by_container_.end()) {
+        iter->second->OnLxdContainerStarting(signal.status());
       }
       return;
+    }
     default:
       result = CrostiniResult::UNKNOWN_ERROR;
       break;
@@ -3337,7 +3446,7 @@
                      weak_ptr_factory_.GetWeakPtr()));
 
   auto abort_callback = base::BarrierClosure(
-      restarters_by_id_.size(),
+      restarters_by_container_.size(),
       base::BindOnce(
           [](scoped_refptr<CrostiniRemover> remover) {
             content::GetUIThreadTaskRunner({})->PostTask(
@@ -3346,8 +3455,8 @@
           },
           crostini_remover));
 
-  for (const auto& restarter_it : restarters_by_id_) {
-    AbortRestartCrostini(restarter_it.first, abort_callback);
+  for (const auto& iter : restarters_by_container_) {
+    iter.second->Abort(abort_callback);
   }
 }
 
@@ -3358,74 +3467,19 @@
   remove_crostini_callbacks_.clear();
 }
 
-void CrostiniManager::FinishRestart(CrostiniRestarter* restarter,
-                                    CrostiniResult result) {
-  if (restarter->RestartAppliesToEquivalentRestarters()) {
-    // Invoke callbacks for all restarters of that container and then delete
-    // the restarters.
-    auto range =
-        restarters_by_container_.equal_range(restarter->container_id());
-    std::vector<std::unique_ptr<CrostiniRestarter>> pending_restarters;
+void CrostiniManager::RemoveRestartId(RestartId restart_id) {
+  // restarters_by_container_ is handled in RestartCompleted()
+  restarters_by_id_.erase(restart_id);
+}
 
-    // Erase first, because restarter->RunCallback() may modify our maps, and
-    // because the upgrade process will want to run more restarters.
-    for (auto it = range.first; it != range.second; ++it) {
-      CrostiniManager::RestartId restart_id = it->second;
-      pending_restarters.emplace_back(std::move(restarters_by_id_[restart_id]));
-      restarters_by_id_.erase(restart_id);
-    }
-    restarters_by_container_.erase(range.first, range.second);
-
-    std::vector<base::OnceClosure> callbacks;
-    for (auto&& restarter : pending_restarters) {
-      callbacks.push_back(base::BindOnce(
-          [](std::unique_ptr<CrostiniRestarter> restarter,
-             CrostiniResult result) { restarter->RunCallback(result); },
-          std::move(restarter), result));
-    }
-
-    if (ShouldWarnAboutExpiredVersion(profile_, restarter->container_id())) {
-      CrostiniExpiredContainerWarningView::Show(profile_, std::move(callbacks));
-    } else {
-      for (auto&& callback : callbacks) {
-        std::move(callback).Run();
-      }
-    }
-    return;
-  }
-
-  // Restart did not fully complete (aborted or only only a partial restart
-  // was requested).
-
-  // Aborted restarts have the callback run immediately when Abort() is called.
-  if (result != CrostiniResult::RESTART_ABORTED) {
-    restarter->RunCallback(result);
-  }
-
-  CrostiniManager::RestartId restart_id = restarter->restart_id();
-  ContainerId key = restarter->container_id();
-
-  {
-    auto restarter_it = restarters_by_id_.find(restart_id);
-    DCHECK(restarter_it != restarters_by_id_.end());
-
-    auto range =
-        restarters_by_container_.equal_range(restarter->container_id());
-    for (auto it = range.first; it != range.second; ++it) {
-      if (it->second == restart_id) {
-        restarters_by_container_.erase(it);
-        break;
-      }
-    }
-    // This destroys the restarter.
-    restarters_by_id_.erase(restarter_it);
-    restarter = nullptr;
-  }
-
-  // Kick off the "next" (in order of arrival) pending Restart() if any.
-  auto range = restarters_by_container_.equal_range(key);
-  if (range.first != range.second) {
-    restarters_by_id_[range.first->second]->Restart();
+void CrostiniManager::RestartCompleted(CrostiniRestarter* restarter,
+                                       base::OnceClosure closure) {
+  ContainerId container_id = restarter->container_id();
+  restarters_by_container_.erase(container_id);
+  if (ShouldWarnAboutExpiredVersion(profile_, restarter->container_id())) {
+    CrostiniExpiredContainerWarningView::Show(profile_, std::move(closure));
+  } else {
+    std::move(closure).Run();
   }
 }
 
@@ -3883,8 +3937,10 @@
 void CrostiniManager::CallRestarterStartLxdContainerFinishedForTesting(
     CrostiniManager::RestartId id,
     CrostiniResult result) {
-  auto restarter_it = restarters_by_id_.find(id);
-  DCHECK(restarter_it != restarters_by_id_.end());
+  auto container_it = restarters_by_id_.find(id);
+  DCHECK(container_it != restarters_by_id_.end());
+  auto restarter_it = restarters_by_container_.find(container_it->second);
+  DCHECK(restarter_it != restarters_by_container_.end());
   restarter_it->second->StartLxdContainerFinished(result);
 }
 
diff --git a/chrome/browser/ash/crostini/crostini_manager.h b/chrome/browser/ash/crostini/crostini_manager.h
index d60e841..a14d117 100644
--- a/chrome/browser/ash/crostini/crostini_manager.h
+++ b/chrome/browser/ash/crostini/crostini_manager.h
@@ -449,8 +449,8 @@
 
   // Runs all the steps required to restart the given crostini vm and container.
   // The optional |observer| tracks progress. If provided, it must be alive
-  // until the restart completes (i.e. when |callback| is called) or the restart
-  // is aborted via |AbortRestartCrostini|.
+  // until the restart completes (i.e. when |callback| is called) or the request
+  // is cancelled via |CancelRestartCrostini|.
   RestartId RestartCrostini(ContainerId container_id,
                             CrostiniResultCallback callback,
                             RestartObserver* observer = nullptr);
@@ -460,10 +460,11 @@
                                        CrostiniResultCallback callback,
                                        RestartObserver* observer = nullptr);
 
-  // Aborts a restart. A "next" restarter with the same ContainerId will run, if
-  // there is one. |callback| will be called once the restart has finished
-  // aborting
-  void AbortRestartCrostini(RestartId restart_id, base::OnceClosure callback);
+  // Cancel a restart request. The associated result callback will be fired
+  // immediately and the observer will be removed. If there were multiple
+  // restart requests for the same container id, the restart may actually keep
+  // going.
+  void CancelRestartCrostini(RestartId restart_id);
 
   // Returns true if the Restart corresponding to |restart_id| is not yet
   // complete.
@@ -825,7 +826,12 @@
   // checking component registration code may block.
   void MaybeUpdateCrostiniAfterChecks();
 
-  void FinishRestart(CrostiniRestarter* restarter, CrostiniResult result);
+  // Called by CrostiniRestarter once it's done with a specific restart request.
+  void RemoveRestartId(RestartId restart_id);
+  // Called by CrostiniRestarter once it's finished. |closure| encapsulates any
+  // outstanding callbacks passed to RestartCrostini*().
+  void RestartCompleted(CrostiniRestarter* restarter,
+                        base::OnceClosure closure);
 
   // Callback for CrostiniManager::RemoveCrostini.
   void OnRemoveCrostini(CrostiniResult result);
@@ -905,14 +911,12 @@
   base::ObserverList<ash::VmShutdownObserver> vm_shutdown_observers_;
   base::ObserverList<ash::VmStartingObserver> vm_starting_observers_;
 
-  // Only one restarter flow is actually running for a given container, other
-  // restarters will just have their callback called when the running restarter
-  // completes.
-  std::multimap<ContainerId, CrostiniManager::RestartId>
+  // RestartIds present in |restarters_by_id_| will always have a restarter in
+  // |restarters_by_container_| for the corresponding ContainerId.
+  std::map<CrostiniManager::RestartId, ContainerId> restarters_by_id_;
+  std::map<ContainerId, std::unique_ptr<CrostiniRestarter>>
       restarters_by_container_;
-
-  std::map<CrostiniManager::RestartId, std::unique_ptr<CrostiniRestarter>>
-      restarters_by_id_;
+  static RestartId next_restart_id_;
 
   base::ObserverList<CrostiniDialogStatusObserver>
       crostini_dialog_status_observers_;
diff --git a/chrome/browser/ash/crostini/crostini_manager_unittest.cc b/chrome/browser/ash/crostini/crostini_manager_unittest.cc
index 7c47d95..26e9b99 100644
--- a/chrome/browser/ash/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_manager_unittest.cc
@@ -711,41 +711,39 @@
   }
 
   void OnComponentLoaded(CrostiniResult result) override {
-    if (abort_on_component_loaded_) {
-      Abort();
+    if (cancel_on_component_loaded_) {
+      Cancel();
     }
   }
 
   void OnDiskImageCreated(bool success,
                           vm_tools::concierge::DiskImageStatus status,
                           int64_t disk_size_available) override {
-    if (abort_on_disk_image_created_) {
-      Abort();
+    if (cancel_on_disk_image_created_) {
+      Cancel();
     }
   }
 
   void OnVmStarted(bool success) override {
-    if (abort_on_vm_started_) {
-      Abort();
+    if (cancel_on_vm_started_) {
+      Cancel();
     }
   }
 
   void OnLxdStarted(CrostiniResult result) override {
-    if (abort_on_lxd_started_) {
-      Abort();
+    if (cancel_on_lxd_started_) {
+      Cancel();
     }
   }
 
   void OnContainerCreated(CrostiniResult result) override {
-    if (abort_on_container_created_) {
-      Abort();
+    if (cancel_on_container_created_) {
+      Cancel();
     }
-    if (abort_then_stop_vm_) {
-      auto barrier_closure = base::BarrierClosure(2, run_loop()->QuitClosure());
-
-      // Don't use the Abort() method because it terminates the run loop
+    if (cancel_then_stop_vm_) {
+      // Don't use the Cancel() method because it terminates the run loop
       // immediately, and we want to wait for the OnVmStopped task to complete.
-      crostini_manager()->AbortRestartCrostini(restart_id_, barrier_closure);
+      crostini_manager()->CancelRestartCrostini(restart_id_);
 
       // Signal that the VM has stopped by posting a task to avoid deleting
       // CrostiniRestarter inside a CrostiniRestarter call.
@@ -756,19 +754,19 @@
           FROM_HERE,
           base::BindOnce(&CrostiniManager::OnVmStopped,
                          base::Unretained(crostini_manager()), signal),
-          barrier_closure);
+          run_loop()->QuitClosure());
     }
   }
 
   void OnContainerStarted(CrostiniResult result) override {
-    if (abort_on_container_started_) {
-      Abort();
+    if (cancel_on_container_started_) {
+      Cancel();
     }
   }
 
   void OnContainerSetup(bool success) override {
-    if (abort_on_container_setup_) {
-      Abort();
+    if (cancel_on_container_setup_) {
+      Cancel();
     }
   }
 
@@ -778,8 +776,8 @@
     EXPECT_EQ(result, last_crostini_callback_result_);
   }
 
-  void Abort() {
-    crostini_manager()->AbortRestartCrostini(restart_id_, base::DoNothing());
+  void Cancel() {
+    crostini_manager()->CancelRestartCrostini(restart_id_);
     run_loop()->Quit();
   }
 
@@ -793,14 +791,14 @@
       CrostiniManager::kUninitializedRestartId;
   const CrostiniManager::RestartId uninitialized_id_ =
       CrostiniManager::kUninitializedRestartId;
-  bool abort_on_component_loaded_ = false;
-  bool abort_on_disk_image_created_ = false;
-  bool abort_on_vm_started_ = false;
-  bool abort_on_lxd_started_ = false;
-  bool abort_on_container_created_ = false;
-  bool abort_on_container_started_ = false;
-  bool abort_on_container_setup_ = false;
-  bool abort_then_stop_vm_ = false;
+  bool cancel_on_component_loaded_ = false;
+  bool cancel_on_disk_image_created_ = false;
+  bool cancel_on_vm_started_ = false;
+  bool cancel_on_lxd_started_ = false;
+  bool cancel_on_container_created_ = false;
+  bool cancel_on_container_started_ = false;
+  bool cancel_on_container_setup_ = false;
+  bool cancel_then_stop_vm_ = false;
 
   int restart_crostini_callback_count_ = 0;
   CrostiniResult last_crostini_callback_result_ = CrostiniResult::SUCCESS;
@@ -917,8 +915,8 @@
   ExpectRestarterUmaCount(1);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortOnComponentLoaded) {
-  abort_on_component_loaded_ = true;
+TEST_F(CrostiniManagerRestartTest, CancelOnComponentLoaded) {
+  cancel_on_component_loaded_ = true;
   restart_id_ = crostini_manager()->RestartCrostini(
       container_id(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
@@ -951,8 +949,8 @@
       "Crostini.RestarterTimeInState2.CreateDiskImage", 0);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortOnDiskImageCreated) {
-  abort_on_disk_image_created_ = true;
+TEST_F(CrostiniManagerRestartTest, CancelOnDiskImageCreated) {
+  cancel_on_disk_image_created_ = true;
   restart_id_ = crostini_manager()->RestartCrostini(
       container_id(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
@@ -1005,8 +1003,8 @@
                                        1);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortOnVmStarted) {
-  abort_on_vm_started_ = true;
+TEST_F(CrostiniManagerRestartTest, CancelOnVmStarted) {
+  cancel_on_vm_started_ = true;
   restart_id_ = crostini_manager()->RestartCrostini(
       container_id(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
@@ -1054,8 +1052,8 @@
   ExpectRestarterUmaCount(1);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortOnLxdStarted) {
-  abort_on_lxd_started_ = true;
+TEST_F(CrostiniManagerRestartTest, CancelOnLxdStarted) {
+  cancel_on_lxd_started_ = true;
   restart_id_ = crostini_manager()->RestartCrostini(
       container_id(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
@@ -1101,8 +1099,8 @@
   ExpectRestarterUmaCount(1);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortOnContainerCreated) {
-  abort_on_container_created_ = true;
+TEST_F(CrostiniManagerRestartTest, CancelOnContainerCreated) {
+  cancel_on_container_created_ = true;
   restart_id_ = crostini_manager()->RestartCrostini(
       ContainerId::GetDefault(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
@@ -1173,8 +1171,8 @@
   ExpectRestarterUmaCount(1);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortOnContainerCreatedError) {
-  abort_on_container_started_ = true;
+TEST_F(CrostiniManagerRestartTest, CancelOnContainerCreatedError) {
+  cancel_on_container_started_ = true;
   fake_cicerone_client_->set_lxd_container_created_signal_status(
       vm_tools::cicerone::LxdContainerCreatedSignal::UNKNOWN);
   restart_id_ = crostini_manager()->RestartCrostini(
@@ -1190,8 +1188,8 @@
   ExpectRestarterUmaCount(1);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortOnContainerStarted) {
-  abort_on_container_started_ = true;
+TEST_F(CrostiniManagerRestartTest, CancelOnContainerStarted) {
+  cancel_on_container_started_ = true;
   restart_id_ = crostini_manager()->RestartCrostini(
       ContainerId::GetDefault(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
@@ -1204,8 +1202,8 @@
   ExpectRestarterUmaCount(1);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortOnContainerSetup) {
-  abort_on_container_setup_ = true;
+TEST_F(CrostiniManagerRestartTest, CancelOnContainerSetup) {
+  cancel_on_container_setup_ = true;
   restart_id_ = crostini_manager()->RestartCrostini(
       ContainerId::GetDefault(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
@@ -1294,8 +1292,8 @@
   ExpectRestarterUmaCount(1);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortThenStopVm) {
-  abort_then_stop_vm_ = true;
+TEST_F(CrostiniManagerRestartTest, CancelThenStopVm) {
+  cancel_then_stop_vm_ = true;
   restart_id_ = crostini_manager()->RestartCrostini(
       container_id(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
@@ -1308,7 +1306,7 @@
   ExpectRestarterUmaCount(1);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortFinishedRestartIsSafe) {
+TEST_F(CrostiniManagerRestartTest, CancelFinishedRestartIsSafe) {
   restart_id_ = crostini_manager()->RestartCrostini(
       container_id(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
@@ -1319,27 +1317,20 @@
   ExpectCrostiniRestartResult(CrostiniResult::SUCCESS);
 
   base::RunLoop run_loop;
-  crostini_manager()->AbortRestartCrostini(restart_id_, run_loop.QuitClosure());
-  run_loop.Run();
+  crostini_manager()->CancelRestartCrostini(restart_id_);
+  run_loop.RunUntilIdle();
+  // Just make sure nothing crashes.
 }
 
-TEST_F(CrostiniManagerRestartTest, DoubleAbortIsSafe) {
+TEST_F(CrostiniManagerRestartTest, DoubleCancelIsSafe) {
   restart_id_ = crostini_manager()->RestartCrostini(
       container_id(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
-                     base::Unretained(this), base::DoNothing()),
+                     base::Unretained(this), run_loop()->QuitClosure()),
       this);
 
-  // When abort is called multiple times, the callback set for each abort should
-  // be called at the same time. We test this here by blocking the runloop until
-  // they have been called the expected number of times.
-  int kAbortCount = 2;
-  auto barrier_closure =
-      base::BarrierClosure(kAbortCount, run_loop()->QuitClosure());
-  for (int i = 0; i < kAbortCount; i++) {
-    crostini_manager()->AbortRestartCrostini(restart_id_, barrier_closure);
-  }
-
+  crostini_manager()->CancelRestartCrostini(restart_id_);
+  crostini_manager()->CancelRestartCrostini(restart_id_);
   run_loop()->Run();
   ExpectCrostiniRestartResult(CrostiniResult::RESTART_ABORTED);
 }
@@ -1484,9 +1475,6 @@
 
   run_loop()->Run();
 
-  // Aborts don't call the restart callback. If that changes, everything that
-  // calls RestartCrostini will need to be checked to make sure they handle it
-  // in a sensible way.
   ExpectCrostiniRestartResult(CrostiniResult::RESTART_ABORTED);
   EXPECT_EQ(1, remove_crostini_callback_count_);
   ExpectRestarterUmaCount(1);
@@ -1527,6 +1515,31 @@
   ExpectRestarterUmaCount(3);
 }
 
+TEST_F(CrostiniManagerRestartTest, UninstallWithRestarterTimeout) {
+  fake_concierge_client_->set_send_start_vm_response_delay(
+      base::TimeDelta::Max());
+  on_stage_started_ =
+      base::BindLambdaForTesting([&](mojom::InstallerState state) {
+        if (state == mojom::InstallerState::kStartTerminaVm)
+          run_loop()->Quit();
+      });
+  restart_id_ = crostini_manager()->RestartCrostini(container_id(),
+                                                    base::DoNothing(), this);
+  run_loop()->Run();
+
+  // In the kStartTerminaVm state now. Start an uninstall and then wait for
+  // the timeout to be hit.
+
+  crostini_manager()->RemoveCrostini(
+      kVmName,
+      base::BindOnce(&CrostiniManagerRestartTest::RemoveCrostiniCallback,
+                     base::Unretained(this), base::DoNothing()));
+
+  task_environment_.FastForwardBy(kLongTime);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(1, remove_crostini_callback_count_);
+}
+
 TEST_F(CrostiniManagerRestartTest, UninstallThenRestart) {
   // Install crostini first so that the uninstaller doesn't terminate before we
   // can call the installer again
@@ -1723,10 +1736,6 @@
   EXPECT_EQ(std::vector<crostini::mojom::InstallerState>({
                 crostini::mojom::InstallerState::kCreateDiskImage,
                 crostini::mojom::InstallerState::kStartTerminaVm,
-                crostini::mojom::InstallerState::kStart,
-                crostini::mojom::InstallerState::kInstallImageLoader,
-                crostini::mojom::InstallerState::kCreateDiskImage,
-                crostini::mojom::InstallerState::kStartTerminaVm,
                 crostini::mojom::InstallerState::kStartLxd,
                 crostini::mojom::InstallerState::kCreateContainer,
                 crostini::mojom::InstallerState::kSetupContainer,
@@ -1741,14 +1750,14 @@
   restart_id_ = crostini_manager()->RestartCrostini(
       container_id(),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
-                     base::Unretained(this), base::DoNothing()),
+                     base::Unretained(this), run_loop()->QuitClosure()),
       &observer1);
   CrostiniManager::RestartOptions options;
   options.start_vm_only = true;
   restart_id_ = crostini_manager()->RestartCrostiniWithOptions(
       container_id(), std::move(options),
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
-                     base::Unretained(this), run_loop()->QuitClosure()),
+                     base::Unretained(this), base::DoNothing()),
       &observer2);
   run_loop()->Run();
   EXPECT_EQ(2, restart_crostini_callback_count_);
@@ -1766,10 +1775,6 @@
   EXPECT_EQ(std::vector<crostini::mojom::InstallerState>({
                 crostini::mojom::InstallerState::kCreateDiskImage,
                 crostini::mojom::InstallerState::kStartTerminaVm,
-                crostini::mojom::InstallerState::kStartLxd,
-                crostini::mojom::InstallerState::kCreateContainer,
-                crostini::mojom::InstallerState::kSetupContainer,
-                crostini::mojom::InstallerState::kStartContainer,
             }),
             observer2.stages);
 }
@@ -1803,10 +1808,6 @@
   EXPECT_EQ(std::vector<crostini::mojom::InstallerState>({
                 crostini::mojom::InstallerState::kCreateDiskImage,
                 crostini::mojom::InstallerState::kStartTerminaVm,
-                crostini::mojom::InstallerState::kStart,
-                crostini::mojom::InstallerState::kInstallImageLoader,
-                crostini::mojom::InstallerState::kCreateDiskImage,
-                crostini::mojom::InstallerState::kStartTerminaVm,
             }),
             observer2.stages);
 }
@@ -1840,11 +1841,6 @@
                 crostini::mojom::InstallerState::kCreateDiskImage,
                 crostini::mojom::InstallerState::kStartTerminaVm,
                 crostini::mojom::InstallerState::kStartLxd,
-                crostini::mojom::InstallerState::kStart,
-                crostini::mojom::InstallerState::kInstallImageLoader,
-                crostini::mojom::InstallerState::kCreateDiskImage,
-                crostini::mojom::InstallerState::kStartTerminaVm,
-                crostini::mojom::InstallerState::kStartLxd,
                 crostini::mojom::InstallerState::kCreateContainer,
                 crostini::mojom::InstallerState::kSetupContainer,
                 crostini::mojom::InstallerState::kStartContainer,
diff --git a/chrome/browser/ash/file_manager/snapshot_manager.cc b/chrome/browser/ash/file_manager/snapshot_manager.cc
index b5a7758..7541434 100644
--- a/chrome/browser/ash/file_manager/snapshot_manager.cc
+++ b/chrome/browser/ash/file_manager/snapshot_manager.cc
@@ -201,6 +201,7 @@
     std::move(callback).Run(base::FilePath());
     return;
   }
+
   storage::FileSystemURL filesystem_url =
       context->CrackURLInFirstPartyContext(url);
 
@@ -211,6 +212,27 @@
                      std::move(callback)));
 }
 
+void SnapshotManager::CreateManagedSnapshot(
+    const storage::FileSystemURL& filesystem_url,
+    LocalPathCallback callback) {
+  scoped_refptr<storage::FileSystemContext> context(
+      util::GetFileManagerFileSystemContext(profile_));
+  DCHECK(context.get());
+
+  storage::ExternalFileSystemBackend* const backend =
+      context->external_backend();
+  if (!backend || !backend->CanHandleType(filesystem_url.type())) {
+    std::move(callback).Run(base::FilePath());
+    return;
+  }
+
+  ComputeSpaceNeedToBeFreed(
+      profile_, context, filesystem_url,
+      base::BindOnce(&SnapshotManager::CreateManagedSnapshotAfterSpaceComputed,
+                     weak_ptr_factory_.GetWeakPtr(), filesystem_url,
+                     std::move(callback)));
+}
+
 void SnapshotManager::CreateManagedSnapshotAfterSpaceComputed(
     const storage::FileSystemURL& filesystem_url,
     LocalPathCallback callback,
diff --git a/chrome/browser/ash/file_manager/snapshot_manager.h b/chrome/browser/ash/file_manager/snapshot_manager.h
index 57d1f81..6db32e41 100644
--- a/chrome/browser/ash/file_manager/snapshot_manager.h
+++ b/chrome/browser/ash/file_manager/snapshot_manager.h
@@ -39,11 +39,17 @@
 
   ~SnapshotManager();
 
-  // Creates a snapshot file copy of a file system file |absolute_file_path| and
-  // returns back to |callback|. Returns empty path for failure.
+  // Creates a snapshot file copy of a file system file |absolute_file_path|,
+  // and returns its path to |callback|. Returns an empty path on failure.
   void CreateManagedSnapshot(const base::FilePath& absolute_file_path,
                              LocalPathCallback callback);
 
+  // Creates a snapshot file copy of an external backend file system file at
+  // |file_system_url|, and returns its path to |callback|. Returns an empty
+  // path on failure.
+  void CreateManagedSnapshot(const storage::FileSystemURL& file_system_url,
+                             LocalPathCallback callback);
+
  private:
   class FileRefsHolder;
 
diff --git a/chrome/browser/ash/login/screens/mock_update_screen.cc b/chrome/browser/ash/login/screens/mock_update_screen.cc
index 48b3a27..a0239ffd 100644
--- a/chrome/browser/ash/login/screens/mock_update_screen.cc
+++ b/chrome/browser/ash/login/screens/mock_update_screen.cc
@@ -21,18 +21,8 @@
   ExitUpdate(result);
 }
 
-MockUpdateView::MockUpdateView() {
-  EXPECT_CALL(*this, MockBind(_)).Times(AtLeast(1));
-}
+MockUpdateView::MockUpdateView() = default;
 
 MockUpdateView::~MockUpdateView() = default;
 
-void MockUpdateView::Bind(UpdateScreen* screen) {
-  MockBind(screen);
-}
-
-void MockUpdateView::Unbind() {
-  MockUnbind();
-}
-
 }  // namespace ash
diff --git a/chrome/browser/ash/login/screens/mock_update_screen.h b/chrome/browser/ash/login/screens/mock_update_screen.h
index c512e25..5730f4d8 100644
--- a/chrome/browser/ash/login/screens/mock_update_screen.h
+++ b/chrome/browser/ash/login/screens/mock_update_screen.h
@@ -30,13 +30,7 @@
   MockUpdateView();
   virtual ~MockUpdateView();
 
-  void Bind(UpdateScreen* screen) override;
-  void Unbind() override;
-
   MOCK_METHOD(void, Show, (bool is_opt_out_enabled));
-  MOCK_METHOD(void, Hide, ());
-  MOCK_METHOD(void, MockBind, (UpdateScreen * screen));
-  MOCK_METHOD(void, MockUnbind, ());
 
   MOCK_METHOD(void, SetUpdateState, (UpdateView::UIState value));
   MOCK_METHOD(void,
diff --git a/chrome/browser/ash/login/screens/update_screen.cc b/chrome/browser/ash/login/screens/update_screen.cc
index c35229e..b818609 100644
--- a/chrome/browser/ash/login/screens/update_screen.cc
+++ b/chrome/browser/ash/login/screens/update_screen.cc
@@ -117,15 +117,9 @@
           std::make_unique<ErrorScreensHistogramHelper>("Update")),
       version_updater_(std::make_unique<VersionUpdater>(this)),
       wait_before_reboot_time_(kWaitBeforeRebootTime),
-      tick_clock_(base::DefaultTickClock::GetInstance()) {
-  if (view_)
-    view_->Bind(this);
-}
+      tick_clock_(base::DefaultTickClock::GetInstance()) {}
 
-UpdateScreen::~UpdateScreen() {
-  if (view_)
-    view_->Unbind();
-}
+UpdateScreen::~UpdateScreen() = default;
 
 bool UpdateScreen::MaybeSkip(WizardContext* context) {
   if (context->enrollment_triggered_early) {
@@ -190,19 +184,21 @@
   accessibility_subscription_ = {};
   power_manager_subscription_.Reset();
   show_timer_.Stop();
-  if (view_)
-    view_->Hide();
   is_shown_ = false;
 }
 
-void UpdateScreen::OnUserActionDeprecated(const std::string& action_id) {
+void UpdateScreen::OnUserAction(const base::Value::List& args) {
+  const std::string& action_id = args[0].GetString();
   bool is_chrome_branded_build = false;
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   is_chrome_branded_build = true;
 #endif
 
-  if (!is_chrome_branded_build &&
-      action_id == kUserActionCancelUpdateShortcut) {
+  if (action_id == kUserActionCancelUpdateShortcut) {
+    if (is_chrome_branded_build) {
+      VLOG(1) << "Ignore update cancel in branded build";
+      return;
+    }
     // Skip update UI, usually used only in debug builds/tests.
     VLOG(1) << "Forced update cancel";
     ExitUpdate(Result::UPDATE_NOT_REQUIRED);
@@ -214,7 +210,7 @@
   } else if (action_id == kUserActionOptOutInfoNext) {
     FinishExitUpdate(Result::UPDATE_OPT_OUT_INFO_SHOWN);
   } else {
-    BaseScreen::OnUserActionDeprecated(action_id);
+    BaseScreen::OnUserAction(args);
   }
 }
 
diff --git a/chrome/browser/ash/login/screens/update_screen.h b/chrome/browser/ash/login/screens/update_screen.h
index 47ff0ef..a6feef10 100644
--- a/chrome/browser/ash/login/screens/update_screen.h
+++ b/chrome/browser/ash/login/screens/update_screen.h
@@ -122,7 +122,7 @@
   bool MaybeSkip(WizardContext* context) override;
   void ShowImpl() override;
   void HideImpl() override;
-  void OnUserActionDeprecated(const std::string& action_id) override;
+  void OnUserAction(const base::Value::List& args) override;
 
   void ExitUpdate(Result result);
 
diff --git a/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.cc b/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.cc
index a704db82..777e3d5 100644
--- a/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.cc
+++ b/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.cc
@@ -18,7 +18,8 @@
         const base::SafeBaseName& system_extension_dir_name,
         InstallSystemExtensionFromDownloadsDirCallback callback) {
   base::FilePath downloads_path;
-  if (!base::PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &downloads_path)) {
+  if (!base::PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS_SAFE,
+                              &downloads_path)) {
     std::move(callback).Run(false);
     return;
   }
diff --git a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
index ebd4976..53a5f1a 100644
--- a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
+++ b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
@@ -73,6 +73,13 @@
 #include "chromeos/ash/components/dbus/system_proxy/system_proxy_client.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/startup/browser_init_params.h"
+#include "components/account_manager_core/account.h"
+#include "components/account_manager_core/account_manager_util.h"
+#include "components/signin/public/identity_manager/identity_test_utils.h"
+#endif
+
 using content::BrowserThread;
 using content::BrowsingDataFilterBuilder;
 
@@ -117,6 +124,22 @@
     InitFeatureList(std::move(enabled_features));
   }
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  void CreatedBrowserMainParts(
+      content::BrowserMainParts* browser_main_parts) override {
+    crosapi::mojom::BrowserInitParamsPtr init_params =
+        crosapi::mojom::BrowserInitParams::New();
+    std::string device_account_email = "primaryaccount@gmail.com";
+    account_manager::AccountKey key(
+        signin::GetTestGaiaIdForEmail(device_account_email),
+        ::account_manager::AccountType::kGaia);
+    init_params->device_account =
+        account_manager::ToMojoAccount({key, device_account_email});
+    chromeos::BrowserInitParams::SetInitParamsForTests(std::move(init_params));
+    InProcessBrowserTest::CreatedBrowserMainParts(browser_main_parts);
+  }
+#endif
+
   void SetUpOnMainThread() override {
     BrowsingDataRemoverBrowserTestBase::SetUpOnMainThread();
     host_resolver()->AddRule(kExampleHost, "127.0.0.1");
diff --git a/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc b/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc
index 6a03db0e..118776f 100644
--- a/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc
+++ b/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc
@@ -373,7 +373,7 @@
           pos < 30 ? 0 : pos - 30,
           std::min(content.size() - 1, pos + hostname.size() + 30));
       LOG(WARNING) << "Found file content: " << file << "\n"
-                   << partial_content << "\n";
+                   << partial_content << "\n" << found;
     }
   }
   return found;
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 4b3f602..67c2161 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -6472,7 +6472,11 @@
   if (!web_request_api || web_request_api->MayHaveProxies())
     return false;
 #endif
-  return prefetch::IsSomePreloadingEnabled(
+  // Preloading is sometimes disabled globally in Chrome via Finch to monitor
+  // its impact. However, we do not want to evaluate the impact of preconnecting
+  // at the start of navigation as part of the preloading holdback, so ignore
+  // the Finch setting here.
+  return prefetch::IsSomePreloadingEnabledIgnoringFinch(
       *Profile::FromBrowserContext(browser_context)->GetPrefs());
 }
 
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
index 595a5439..020b40c2 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
@@ -395,13 +395,31 @@
                       .AppendASCII("mount_path1")
                       .AsUTF8Unsafe(),
                   _))
-      .Times(1);
+      .WillOnce(
+          testing::Invoke([](const std::string&,
+                             DiskMountManager::UnmountPathCallback callback) {
+            std::move(callback).Run(chromeos::MOUNT_ERROR_NONE);
+          }))
+      .WillOnce(
+          testing::Invoke([](const std::string&,
+                             DiskMountManager::UnmountPathCallback callback) {
+            std::move(callback).Run(chromeos::MOUNT_ERROR_CANCELLED);
+          }));
   EXPECT_CALL(*disk_mount_manager_mock_,
               UnmountPath(chromeos::CrosDisksClient::GetArchiveMountPoint()
                               .AppendASCII("archive_mount_path")
                               .AsUTF8Unsafe(),
                           _))
-      .Times(1);
+      .WillOnce(
+          testing::Invoke([](const std::string&,
+                             DiskMountManager::UnmountPathCallback callback) {
+            std::move(callback).Run(chromeos::MOUNT_ERROR_NONE);
+          }))
+      .WillOnce(
+          testing::Invoke([](const std::string&,
+                             DiskMountManager::UnmountPathCallback callback) {
+            std::move(callback).Run(chromeos::MOUNT_ERROR_NEED_PASSWORD);
+          }));
 
   ASSERT_TRUE(RunExtensionTest("file_browser/mount_test", {},
                                {.load_as_component = true}))
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
index 7604bb0d..8fe3b832 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/file_manager_private.h"
+#include "chromeos/dbus/cros_disks/cros_disks_client.h"
 #include "components/drive/event_logger.h"
 #include "components/services/unzip/content/unzip_service.h"
 #include "components/services/unzip/public/cpp/unzip.h"
@@ -195,8 +196,9 @@
     case file_manager::VOLUME_TYPE_MOUNTED_ARCHIVE_FILE: {
       DiskMountManager::GetInstance()->UnmountPath(
           volume->mount_path().value(),
-          DiskMountManager::UnmountPathCallback());
-      break;
+          base::BindOnce(
+              &FileManagerPrivateRemoveMountFunction::OnDiskUnmounted, this));
+      return RespondLater();
     }
     case file_manager::VOLUME_TYPE_PROVIDED: {
       auto* service =
@@ -207,27 +209,44 @@
                                    volume->file_system_id())) {
         return RespondNow(Error("Unmount failed"));
       }
-      break;
+      return RespondNow(NoArguments());
     }
     case file_manager::VOLUME_TYPE_CROSTINI:
       file_manager::VolumeManager::Get(profile)->RemoveSshfsCrostiniVolume(
-          volume->mount_path(), base::DoNothing());
-      break;
+          volume->mount_path(),
+          base::BindOnce(
+              &FileManagerPrivateRemoveMountFunction::OnSshFsUnmounted, this));
+      return RespondLater();
     case file_manager::VOLUME_TYPE_SMB:
       ash::smb_client::SmbServiceFactory::Get(profile)->UnmountSmbFs(
           volume->mount_path());
-      break;
+      return RespondNow(NoArguments());
     case file_manager::VOLUME_TYPE_GUEST_OS:
       // TODO(crbug/1293229): Figure out if we need to support unmounting. I'm
       // not actually sure if it's possible to reach here.
       NOTREACHED();
-      break;
+      [[fallthrough]];
     default:
       // Requested unmounting a device which is not unmountable.
       return RespondNow(Error("Invalid volume type"));
   }
+}
 
-  return RespondNow(NoArguments());
+void FileManagerPrivateRemoveMountFunction::OnSshFsUnmounted(bool ok) {
+  if (ok) {
+    return Respond(NoArguments());
+  }
+  return Respond(Error(file_manager_private::ToString(
+      api::file_manager_private::MOUNT_COMPLETED_STATUS_ERROR_UNKNOWN)));
+}
+
+void FileManagerPrivateRemoveMountFunction::OnDiskUnmounted(
+    chromeos::MountError error) {
+  if (error == chromeos::MOUNT_ERROR_NONE) {
+    return Respond(NoArguments());
+  }
+  return Respond(Error(file_manager_private::ToString(
+      file_manager::MountErrorToMountCompletedStatus(error))));
 }
 
 ExtensionFunction::ResponseAction
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h
index 040065f7..cc6f96f8 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h
@@ -14,6 +14,7 @@
 #include "chromeos/dbus/cros_disks/cros_disks_client.h"
 #include "components/drive/file_errors.h"
 #include "third_party/ced/src/util/encodings/encodings.h"
+#include "third_party/cros_system_api/dbus/cros-disks/dbus-constants.h"
 
 namespace extensions {
 
@@ -79,6 +80,10 @@
 
   // ExtensionFunction overrides.
   ResponseAction Run() override;
+
+  void OnDiskUnmounted(chromeos::MountError error);
+
+  void OnSshFsUnmounted(bool ok);
 };
 
 // Implements chrome.fileManagerPrivate.getVolumeMetadataList method.
diff --git a/chrome/browser/chromeos/tablet_mode/chrome_content_browser_client_tablet_mode_part.cc b/chrome/browser/chromeos/tablet_mode/chrome_content_browser_client_tablet_mode_part.cc
index 9bdf7e9..450168a1 100644
--- a/chrome/browser/chromeos/tablet_mode/chrome_content_browser_client_tablet_mode_part.cc
+++ b/chrome/browser/chromeos/tablet_mode/chrome_content_browser_client_tablet_mode_part.cc
@@ -36,7 +36,7 @@
 }
 
 // Returns true if |contents| is of an internal pages (such as
-// chrome://settings, chrome://extensions, ... etc) or the New Tab Page.
+// chrome://settings, chrome://extensions, ... etc).
 bool IsInternalPage(content::WebContents* contents) {
   DCHECK(contents);
 
@@ -49,7 +49,7 @@
 
   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
   if (profile && search::IsNTPOrRelatedURL(url, profile))
-    return true;
+    return false;
 
   return url.SchemeIs(content::kChromeUIScheme);
 }
@@ -68,7 +68,6 @@
   if (!browser || browser->is_type_app() || browser->is_type_app_popup())
     return;
 
-  // Also exclude internal pages and NTPs.
   if (IsInternalPage(contents))
     return;
 
diff --git a/chrome/browser/chromeos/tablet_mode/tablet_mode_page_behavior_browsertest.cc b/chrome/browser/chromeos/tablet_mode/tablet_mode_page_behavior_browsertest.cc
index 6db2dcf..e674fd3f 100644
--- a/chrome/browser/chromeos/tablet_mode/tablet_mode_page_behavior_browsertest.cc
+++ b/chrome/browser/chromeos/tablet_mode/tablet_mode_page_behavior_browsertest.cc
@@ -206,7 +206,7 @@
   ValidateWebPrefs(web_contents, false /* tablet_mode_enabled */);
 }
 
-IN_PROC_BROWSER_TEST_F(TabletModePageBehaviorTest, ExcludeNTPs) {
+IN_PROC_BROWSER_TEST_F(TabletModePageBehaviorTest, IncludeNTPs) {
   ASSERT_TRUE(AddTabAtIndexToBrowser(
       browser(), 0, GURL(chrome::kChromeUINewTabPageURL),
       ui::PAGE_TRANSITION_LINK, false /* check_navigation_success */));
@@ -215,10 +215,10 @@
   EXPECT_STREQ(web_contents->GetLastCommittedURL().spec().c_str(),
                chrome::kChromeUINewTabPageURL);
 
-  // NTPs should not be affected in tablet mode.
+  // Mobile-style Blink prefs should be applied to the NTP in tablet mode.
   SetTabletMode(true);
   ASSERT_TRUE(InTabletMode());
-  ValidateWebPrefs(web_contents, false /* tablet_mode_enabled */);
+  ValidateWebPrefs(web_contents, true /* tablet_mode_enabled */);
 }
 
 }  // namespace
diff --git a/chrome/browser/component_updater/DEPS b/chrome/browser/component_updater/DEPS
index 5b16412..0c8676b1 100644
--- a/chrome/browser/component_updater/DEPS
+++ b/chrome/browser/component_updater/DEPS
@@ -4,6 +4,7 @@
   "+chrome/browser/share",
   "+chrome/updater",
   "+components/cdm/common",
+  "+components/feed",
   "+components/live_caption",
   "+components/soda",
   "+media/cdm",
diff --git a/chrome/browser/component_updater/crow_domain_list_component_installer.cc b/chrome/browser/component_updater/crow_domain_list_component_installer.cc
index 81a5172..a019363 100644
--- a/chrome/browser/component_updater/crow_domain_list_component_installer.cc
+++ b/chrome/browser/component_updater/crow_domain_list_component_installer.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/flags/android/chrome_feature_list.h"
 #include "components/component_updater/component_installer.h"
 #include "components/component_updater/component_updater_paths.h"
+#include "components/feed/feed_feature_list.h"
 
 using component_updater::ComponentUpdateService;
 
@@ -125,7 +126,7 @@
 }
 
 void RegisterCrowDomainListComponent(ComponentUpdateService* cus) {
-  if (!base::FeatureList::IsEnabled(chrome::android::kShareCrowButton)) {
+  if (!base::FeatureList::IsEnabled(feed::kShareCrowButton)) {
     return;
   }
 
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc
index 94122455..aa7f18a 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -1515,8 +1515,8 @@
   if (!registry)
     return;
 
-  base::ListValue results;
-  base::ListValue component_extension_origins;
+  base::Value::List results;
+  base::Value::List component_extension_origins;
   bool have_user_installed_devtools_extensions = false;
   for (const scoped_refptr<const extensions::Extension>& extension :
        registry->enabled_extensions()) {
@@ -1540,14 +1540,12 @@
         web_contents_->GetMainFrame()->GetProcess()->GetID(),
         url::Origin::Create(extension->url()));
 
-    std::unique_ptr<base::DictionaryValue> extension_info(
-        new base::DictionaryValue());
-    extension_info->SetStringKey("startPage", url.spec());
-    extension_info->SetStringKey("name", extension->name());
-    extension_info->SetBoolKey(
-        "exposeExperimentalAPIs",
-        extension->permissions_data()->HasAPIPermission(
-            extensions::mojom::APIPermissionID::kExperimental));
+    base::Value::Dict extension_info;
+    extension_info.Set("startPage", url.spec());
+    extension_info.Set("name", extension->name());
+    extension_info.Set("exposeExperimentalAPIs",
+                       extension->permissions_data()->HasAPIPermission(
+                           extensions::mojom::APIPermissionID::kExperimental));
     results.Append(std::move(extension_info));
 
     if (!(extensions::Manifest::IsPolicyLocation(extension->location()) ||
@@ -1564,8 +1562,9 @@
   }
 
   CallClientMethod("DevToolsAPI", "setOriginsForbiddenForExtensions",
-                   std::move(component_extension_origins));
-  CallClientMethod("DevToolsAPI", "addExtensions", std::move(results));
+                   base::Value(std::move(component_extension_origins)));
+  CallClientMethod("DevToolsAPI", "addExtensions",
+                   base::Value(std::move(results)));
 }
 
 void DevToolsUIBindings::RegisterExtensionsAPI(const std::string& origin,
diff --git a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
index f34056f..de3f198 100644
--- a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
+++ b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
@@ -580,6 +580,9 @@
       "multitouch", base::FeatureList::IsEnabled(
                         chromeos::features::kVirtualKeyboardMultitouch)));
   features.Append(GenerateFeatureFlag(
+      "roundCorners", base::FeatureList::IsEnabled(
+                          chromeos::features::kVirtualKeyboardRoundCorners)));
+  features.Append(GenerateFeatureFlag(
       "systemchinesephysicaltyping",
       base::FeatureList::IsEnabled(
           chromeos::features::kSystemChinesePhysicalTyping)));
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 37de4b1..f7c42ce1 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1450,6 +1450,11 @@
     "expiry_milestone": 103
   },
   {
+    "name": "eap-gtc-wifi-authentication",
+    "owners": [ "cros-connectivity@google.com" ],
+    "expiry_milestone": 110
+  },
+  {
     "name": "eche-phone-hub-permissions-onboarding",
     "owners": [ "dhnishi" ],
     "expiry_milestone": 104
@@ -1835,6 +1840,11 @@
     "expiry_milestone" : 110
   },
   {
+    "name": "enable-cros-virtual-keyboard-round-corners",
+    "owners": [ "jopalmer", "essential-inputs-team@google.com" ],
+    "expiry_milestone": 115
+  },
+  {
     "name": "enable-css-selector-fragment-anchor",
     "owners": [ "mehdika", "blink-interactions-team@google.com" ],
     "expiry_milestone": 102
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 8f9c2b5..717dd62 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4747,6 +4747,10 @@
 const char kTrimOnMemoryPressureDescription[] =
     "Trim Working Set periodically on memory pressure";
 
+const char kEapGtcWifiAuthenticationName[] = "EAP-GTC WiFi Authentication";
+const char kEapGtcWifiAuthenticationDescription[] =
+    "Allows configuration of WiFi networks using EAP-GTC authentication";
+
 const char kEchePhoneHubPermissionsOnboardingName[] =
     "Enable Eche Phone Hub Permissions Onboarding";
 const char kEchePhoneHubPermissionsOnboardingDescription[] =
@@ -5462,6 +5466,11 @@
 const char kVirtualKeyboardMultitouchDescription[] =
     "Enables multitouch on the virtual keyboard.";
 
+const char kVirtualKeyboardRoundCornersName[] =
+    "Virtual Keyboard Round Corners";
+const char kVirtualKeyboardRoundCornersDescription[] =
+    "Enables round corners on the virtual keyboard.";
+
 const char kWakeOnWifiAllowedName[] = "Allow enabling wake on WiFi features";
 const char kWakeOnWifiAllowedDescription[] =
     "Allows wake on WiFi features in shill to be enabled.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 2a0dfc8..ac0f4c53 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2706,6 +2706,9 @@
 extern const char kTrimOnMemoryPressureName[];
 extern const char kTrimOnMemoryPressureDescription[];
 
+extern const char kEapGtcWifiAuthenticationName[];
+extern const char kEapGtcWifiAuthenticationDescription[];
+
 extern const char kEchePhoneHubPermissionsOnboardingName[];
 extern const char kEchePhoneHubPermissionsOnboardingDescription[];
 
@@ -3128,6 +3131,9 @@
 extern const char kVirtualKeyboardMultitouchName[];
 extern const char kVirtualKeyboardMultitouchDescription[];
 
+extern const char kVirtualKeyboardRoundCornersName[];
+extern const char kVirtualKeyboardRoundCornersDescription[];
+
 extern const char kWakeOnWifiAllowedName[];
 extern const char kWakeOnWifiAllowedDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 8f47d3506..aaf50c1 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -148,6 +148,7 @@
     &feed::kInterestFeedV2,
     &feed::kInterestFeedV2Autoplay,
     &feed::kInterestFeedV2Hearts,
+    &feed::kShareCrowButton,
     &feed::kReliabilityLogging,
     &feed::kWebFeed,
     &feed::kWebFeedAwareness,
@@ -267,7 +268,6 @@
     &kServiceManagerForBackgroundPrefetch,
     &kServiceManagerForDownload,
     &kShareButtonInTopToolbar,
-    &kShareCrowButton,
     &kSharedClipboardUI,
     &kShowScrollableMVTOnNTPAndroid,
     &kSpannableInlineAutocomplete,
@@ -741,9 +741,6 @@
 const base::Feature kShareButtonInTopToolbar{"ShareButtonInTopToolbar",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kShareCrowButton{"ShareCrowButton",
-                                     base::FEATURE_DISABLED_BY_DEFAULT};
-
 const base::Feature kShowScrollableMVTOnNTPAndroid{
     "ShowScrollableMVTOnNTPAndroid", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index be419f2f..c197753c 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -131,7 +131,6 @@
 extern const base::Feature kServiceManagerForBackgroundPrefetch;
 extern const base::Feature kServiceManagerForDownload;
 extern const base::Feature kShareButtonInTopToolbar;
-extern const base::Feature kShareCrowButton;
 extern const base::Feature kSharingHubLinkToggle;
 extern const base::Feature kShowScrollableMVTOnNTPAndroid;
 extern const base::Feature kSpannableInlineAutocomplete;
diff --git a/chrome/browser/lacros/account_manager/signin_helper_lacros_unittest.cc b/chrome/browser/lacros/account_manager/signin_helper_lacros_unittest.cc
index 3c3cf38..28c5446f7 100644
--- a/chrome/browser/lacros/account_manager/signin_helper_lacros_unittest.cc
+++ b/chrome/browser/lacros/account_manager/signin_helper_lacros_unittest.cc
@@ -63,8 +63,9 @@
       : AccountReconcilor(
             identity_manager,
             client,
-            std::make_unique<
-                signin::MirrorLandingAccountReconcilorDelegate>()) {}
+            std::make_unique<signin::MirrorLandingAccountReconcilorDelegate>(
+                identity_manager,
+                client->GetInitialPrimaryAccount().has_value())) {}
 
   void SimulateSetCookiesFinished() {
     OnSetAccountsInCookieCompleted(signin::SetAccountsInCookieResult::kSuccess);
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
index e33e309..b710fce 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -2543,8 +2543,9 @@
   ExpectFirstPaintMetricsTotalCount(0);
 }
 
+// Flaky. https://crbug.com/1325208
 IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
-                       SingleTabSessionRestore) {
+                       DISABLED_SingleTabSessionRestore) {
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
 
   SessionRestorePaintWaiter session_restore_paint_waiter;
@@ -2554,8 +2555,9 @@
   ExpectFirstPaintMetricsTotalCount(1);
 }
 
+// Flaky. https://crbug.com/1325208
 IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
-                       MultipleTabsSessionRestore) {
+                       DISABLED_MultipleTabsSessionRestore) {
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
   ui_test_utils::NavigateToURLWithDisposition(
       browser(), GetTestURL(), WindowOpenDisposition::NEW_BACKGROUND_TAB,
@@ -2590,8 +2592,9 @@
   ExpectFirstPaintMetricsTotalCount(0);
 }
 
+// Flaky. https://crbug.com/1325208
 IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
-                       LoadingAfterSessionRestore) {
+                       DISABLED_LoadingAfterSessionRestore) {
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
 
   Browser* new_browser = nullptr;
@@ -2642,8 +2645,9 @@
   ExpectFirstPaintMetricsTotalCount(0);
 }
 
+// Flaky. https://crbug.com/1325208
 IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
-                       MultipleSessionRestores) {
+                       DISABLED_MultipleSessionRestores) {
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
 
   Browser* current_browser = browser();
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java
index 6ce79f5..46a8efc5 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java
@@ -174,26 +174,6 @@
     }
 
     /**
-     * Register the start of an automated password change flow. Notifies the
-     * password change success tracker.
-     * @param credential The credential for which the flow was started.
-     */
-    void onAutomatedPasswordChangeStarted(CompromisedCredential credential) {
-        PasswordCheckBridgeJni.get().onAutomatedPasswordChangeStarted(
-                mNativePasswordCheckBridge, credential);
-    }
-
-    /**
-     * Register the start of a manual password change flow. Notifies the
-     * password change success tracker.
-     * @param credential The credential for which the flow was started.
-     */
-    void onManualPasswordChangeStarted(CompromisedCredential credential) {
-        PasswordCheckBridgeJni.get().onManualPasswordChangeStarted(
-                mNativePasswordCheckBridge, credential);
-    }
-
-    /**
      * Destroys its C++ counterpart.
      */
     void destroy() {
@@ -224,10 +204,6 @@
         void onEditCredential(long nativePasswordCheckBridge, CompromisedCredential credential,
                 Context context, SettingsLauncher settingsLauncher);
         void removeCredential(long nativePasswordCheckBridge, CompromisedCredential credentials);
-        void onAutomatedPasswordChangeStarted(
-                long nativePasswordCheckBridge, CompromisedCredential credential);
-        void onManualPasswordChangeStarted(
-                long nativePasswordCheckBridge, CompromisedCredential credential);
         void destroy(long nativePasswordCheckBridge);
     }
 }
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java
index bea957da..93775e33 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java
@@ -10,6 +10,7 @@
 
 import org.chromium.base.ObserverList;
 import org.chromium.chrome.browser.password_check.PasswordCheckBridge.PasswordCheckObserver;
+import org.chromium.chrome.browser.password_manager.PasswordChangeSuccessTrackerBridge;
 import org.chromium.chrome.browser.password_manager.PasswordCheckReferrer;
 import org.chromium.components.browser_ui.settings.SettingsLauncher;
 
@@ -169,11 +170,13 @@
 
     @Override
     public void onAutomatedPasswordChangeStarted(CompromisedCredential credential) {
-        mPasswordCheckBridge.onAutomatedPasswordChangeStarted(credential);
+        PasswordChangeSuccessTrackerBridge.onAutomatedPasswordChangeStarted(
+                credential.getAssociatedUrl(), credential.getUsername());
     }
 
     @Override
     public void onManualPasswordChangeStarted(CompromisedCredential credential) {
-        mPasswordCheckBridge.onManualPasswordChangeStarted(credential);
+        PasswordChangeSuccessTrackerBridge.onManualPasswordChangeStarted(
+                credential.getAssociatedUrl(), credential.getUsername());
     }
 }
diff --git a/chrome/browser/password_check/android/password_check_bridge.cc b/chrome/browser/password_check/android/password_check_bridge.cc
index 7c086a9..e2e0b1f 100644
--- a/chrome/browser/password_check/android/password_check_bridge.cc
+++ b/chrome/browser/password_check/android/password_check_bridge.cc
@@ -8,14 +8,11 @@
 #include <string>
 
 #include "base/android/jni_string.h"
-#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/password_check/android/jni_headers/CompromisedCredential_jni.h"
 #include "chrome/browser/password_check/android/jni_headers/PasswordCheckBridge_jni.h"
 #include "chrome/browser/password_manager/android/password_checkup_launcher_helper.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "components/password_manager/content/browser/password_change_success_tracker_factory.h"
 #include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
-#include "components/password_manager/core/browser/password_change_success_tracker.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
 #include "components/password_manager/core/browser/ui/insecure_credentials_manager.h"
 #include "url/android/gurl_android.h"
@@ -210,30 +207,3 @@
       base::android::AttachCurrentThread(), java_bridge_, already_processed,
       remaining_in_queue);
 }
-
-void PasswordCheckBridge::OnAutomatedPasswordChangeStarted(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& credential) {
-  password_manager::CredentialView credential_view =
-      ConvertJavaObjectToCredentialView(env, credential);
-  GetPasswordChangeSuccessTracker()->OnChangePasswordFlowStarted(
-      credential_view.url, base::UTF16ToUTF8(credential_view.username),
-      PasswordChangeSuccessTracker::StartEvent::kAutomatedFlow,
-      PasswordChangeSuccessTracker::EntryPoint::kLeakCheckInSettings);
-}
-
-void PasswordCheckBridge::OnManualPasswordChangeStarted(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& credential) {
-  password_manager::CredentialView credential_view =
-      ConvertJavaObjectToCredentialView(env, credential);
-  GetPasswordChangeSuccessTracker()->OnManualChangePasswordFlowStarted(
-      credential_view.url, base::UTF16ToUTF8(credential_view.username),
-      PasswordChangeSuccessTracker::EntryPoint::kLeakCheckInSettings);
-}
-
-password_manager::PasswordChangeSuccessTracker*
-PasswordCheckBridge::GetPasswordChangeSuccessTracker() {
-  return password_manager::PasswordChangeSuccessTrackerFactory::GetInstance()
-      ->GetForBrowserContext(ProfileManager::GetLastUsedProfile());
-}
diff --git a/chrome/browser/password_check/android/password_check_bridge.h b/chrome/browser/password_check/android/password_check_bridge.h
index 92c28812..9f3ac11 100644
--- a/chrome/browser/password_check/android/password_check_bridge.h
+++ b/chrome/browser/password_check/android/password_check_bridge.h
@@ -11,10 +11,6 @@
 #include "chrome/browser/password_check/android/password_check_ui_status.h"
 #include "chrome/browser/profiles/profile_manager.h"
 
-namespace password_manager {
-class PasswordChangeSuccessTracker;
-}  // namespace password_manager
-
 // C++ counterpart of |PasswordCheckBridge.java|. Used to mediate the
 // communication between the UI and the password check logic.
 class PasswordCheckBridge : public PasswordCheckManager::Observer {
@@ -97,22 +93,7 @@
   void OnPasswordCheckProgressChanged(int already_processed,
                                       int remaining_in_queue) override;
 
-  // Called by Java to register the start of an automated password change flow.
-  void OnAutomatedPasswordChangeStarted(
-      JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& credential);
-
-  // Called by Java to register the start of a manual password change flow.
-  void OnManualPasswordChangeStarted(
-      JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& credential);
-
  private:
-  // Wraps the call to the factory function to obtain the |KeyedService|
-  // instance for |PasswordChangeSuccessTracker|.
-  password_manager::PasswordChangeSuccessTracker*
-  GetPasswordChangeSuccessTracker();
-
   // The corresponding java object.
   base::android::ScopedJavaGlobalRef<jobject> java_bridge_;
 
diff --git a/chrome/browser/password_manager/android/BUILD.gn b/chrome/browser/password_manager/android/BUILD.gn
index 171e75f..6b3ae69 100644
--- a/chrome/browser/password_manager/android/BUILD.gn
+++ b/chrome/browser/password_manager/android/BUILD.gn
@@ -118,6 +118,7 @@
 
   sources = [
     "java/src/org/chromium/chrome/browser/password_manager/ConfirmationDialogHelper.java",
+    "java/src/org/chromium/chrome/browser/password_manager/PasswordChangeSuccessTrackerBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordManagerAndroidBackendUtil.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordManagerLifecycleHelper.java",
@@ -144,6 +145,7 @@
     "//chrome/browser",
   ]
   sources = [
+    "java/src/org/chromium/chrome/browser/password_manager/PasswordChangeSuccessTrackerBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordManagerLifecycleHelper.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordSettingsUpdaterBridge.java",
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordChangeSuccessTrackerBridge.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordChangeSuccessTrackerBridge.java
new file mode 100644
index 0000000..b96b285
--- /dev/null
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordChangeSuccessTrackerBridge.java
@@ -0,0 +1,44 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.password_manager;
+
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.url.GURL;
+
+/**
+ * Class handling the communication with the C++ part of the password change
+ * success tracker feature. It forwards messages to and from its C++
+ * counterpart.
+ */
+public class PasswordChangeSuccessTrackerBridge {
+    /**
+     * Register the start of an automated password change flow. Notifies the
+     * password change success tracker.
+     * @param url The URL associated with the credential that is to be changed.
+     * @param username The username of the credential that is to be changed.
+     */
+    public static void onAutomatedPasswordChangeStarted(GURL url, String username) {
+        PasswordChangeSuccessTrackerBridgeJni.get().onAutomatedPasswordChangeStarted(url, username);
+    }
+
+    /**
+     * Register the start of a manual password change flow. Notifies the
+     * password change success tracker.
+     * @param url The URL associated with the credential that is to be changed.
+     * @param username The username of the credential that is to be changed.
+     */
+    public static void onManualPasswordChangeStarted(GURL url, String username) {
+        PasswordChangeSuccessTrackerBridgeJni.get().onManualPasswordChangeStarted(url, username);
+    }
+
+    /**
+     * C++ method signatures.
+     */
+    @NativeMethods
+    interface Natives {
+        void onAutomatedPasswordChangeStarted(GURL url, String username);
+        void onManualPasswordChangeStarted(GURL url, String username);
+    }
+}
diff --git a/chrome/browser/password_manager/android/password_change_success_tracker_bridge.cc b/chrome/browser/password_manager/android/password_change_success_tracker_bridge.cc
new file mode 100644
index 0000000..c283b32
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_change_success_tracker_bridge.cc
@@ -0,0 +1,53 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <jni.h>
+#include <memory>
+
+#include "base/android/jni_string.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/password_manager/android/jni_headers/PasswordChangeSuccessTrackerBridge_jni.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "components/password_manager/content/browser/password_change_success_tracker_factory.h"
+#include "components/password_manager/core/browser/password_change_success_tracker.h"
+#include "url/android/gurl_android.h"
+
+using password_manager::PasswordChangeSuccessTracker;
+
+namespace {
+// Wraps the call to the factory function to obtain the `KeyedService`
+// instance for `PasswordChangeSuccessTracker`.
+PasswordChangeSuccessTracker* GetPasswordChangeSuccessTracker() {
+  return password_manager::PasswordChangeSuccessTrackerFactory::GetInstance()
+      ->GetForBrowserContext(ProfileManager::GetLastUsedProfile());
+}
+
+}  // namespace
+
+// Called by Java to register the start of an automated password change flow.
+void JNI_PasswordChangeSuccessTrackerBridge_OnAutomatedPasswordChangeStarted(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& url,
+    const base::android::JavaParamRef<jstring>& username) {
+  std::unique_ptr<GURL> native_gurl = url::GURLAndroid::ToNativeGURL(env, url);
+  if (!native_gurl->is_empty()) {
+    GetPasswordChangeSuccessTracker()->OnChangePasswordFlowStarted(
+        *native_gurl, ConvertJavaStringToUTF8(env, username),
+        PasswordChangeSuccessTracker::StartEvent::kAutomatedFlow,
+        PasswordChangeSuccessTracker::EntryPoint::kLeakCheckInSettings);
+  }
+}
+
+// Called by Java to register the start of a manual password change flow.
+void JNI_PasswordChangeSuccessTrackerBridge_OnManualPasswordChangeStarted(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& url,
+    const base::android::JavaParamRef<jstring>& username) {
+  std::unique_ptr<GURL> native_gurl = url::GURLAndroid::ToNativeGURL(env, url);
+  if (!native_gurl->is_empty()) {
+    GetPasswordChangeSuccessTracker()->OnManualChangePasswordFlowStarted(
+        *native_gurl, ConvertJavaStringToUTF8(env, username),
+        PasswordChangeSuccessTracker::EntryPoint::kLeakCheckInSettings);
+  }
+}
diff --git a/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc b/chrome/browser/prefetch/no_state_prefetch/no_state_prefetch_unittest.cc
similarity index 95%
rename from chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc
rename to chrome/browser/prefetch/no_state_prefetch/no_state_prefetch_unittest.cc
index ebf8d91..2ae54dde 100644
--- a/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc
+++ b/chrome/browser/prefetch/no_state_prefetch/no_state_prefetch_unittest.cc
@@ -346,12 +346,12 @@
   NotifyPrefetchStart();
 }
 
-class PrerenderTest : public testing::Test {
+class NoStatePrefetchTest : public testing::Test {
  public:
   static const int kDefaultChildId = -1;
   static const int kDefaultRenderViewRouteId = -1;
 
-  PrerenderTest()
+  NoStatePrefetchTest()
       : no_state_prefetch_manager_(
             new UnitTestNoStatePrefetchManager(&profile_)),
         no_state_prefetch_link_manager_(
@@ -359,7 +359,7 @@
     no_state_prefetch_manager()->SetIsLowEndDevice(false);
   }
 
-  ~PrerenderTest() override {
+  ~NoStatePrefetchTest() override {
     no_state_prefetch_link_manager_->Shutdown();
     no_state_prefetch_manager_->Shutdown();
   }
@@ -502,7 +502,7 @@
   base::HistogramTester histogram_tester_;
 };
 
-TEST_F(PrerenderTest, RespectsThirdPartyCookiesPref) {
+TEST_F(NoStatePrefetchTest, RespectsThirdPartyCookiesPref) {
   GURL url("http://www.google.com/");
   profile()->GetPrefs()->SetInteger(
       prefs::kCookieControlsMode,
@@ -512,14 +512,15 @@
       "Prerender.FinalStatus", FINAL_STATUS_BLOCK_THIRD_PARTY_COOKIES, 1);
 }
 
-class PrerenderGWSPrefetchHoldbackTest : public PrerenderTest {
+class NoStatePrefetchGWSPrefetchHoldbackTest : public NoStatePrefetchTest {
  public:
-  PrerenderGWSPrefetchHoldbackTest() {
+  NoStatePrefetchGWSPrefetchHoldbackTest() {
     feature_list_.InitAndEnableFeature(kGWSPrefetchHoldback);
   }
 };
 
-TEST_F(PrerenderGWSPrefetchHoldbackTest, GWSPrefetchHoldbackNonGWSSReferrer) {
+TEST_F(NoStatePrefetchGWSPrefetchHoldbackTest,
+       GWSPrefetchHoldbackNonGWSSReferrer) {
   GURL url("http://www.notgoogle.com/");
   no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
       url, FINAL_STATUS_PROFILE_DESTROYED);
@@ -527,7 +528,7 @@
   EXPECT_TRUE(AddSimpleLinkTrigger(url));
 }
 
-TEST_F(PrerenderGWSPrefetchHoldbackTest, GWSPrefetchHoldbackGWSReferrer) {
+TEST_F(NoStatePrefetchGWSPrefetchHoldbackTest, GWSPrefetchHoldbackGWSReferrer) {
   GURL url("http://www.notgoogle.com/");
   no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
       url, url::Origin::Create(GURL("www.google.com")), ORIGIN_GWS_PRERENDER,
@@ -536,14 +537,14 @@
   EXPECT_FALSE(AddSimpleGWSLinkTrigger(url));
 }
 
-class PrerenderGWSPrefetchHoldbackOffTest : public PrerenderTest {
+class NoStatePrefetchGWSPrefetchHoldbackOffTest : public NoStatePrefetchTest {
  public:
-  PrerenderGWSPrefetchHoldbackOffTest() {
+  NoStatePrefetchGWSPrefetchHoldbackOffTest() {
     feature_list_.InitAndDisableFeature(kGWSPrefetchHoldback);
   }
 };
 
-TEST_F(PrerenderGWSPrefetchHoldbackOffTest,
+TEST_F(NoStatePrefetchGWSPrefetchHoldbackOffTest,
        GWSPrefetchHoldbackOffNonGWSReferrer) {
   GURL url("http://www.notgoogle.com/");
   no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
@@ -552,7 +553,8 @@
   EXPECT_TRUE(AddSimpleLinkTrigger(url));
 }
 
-TEST_F(PrerenderGWSPrefetchHoldbackOffTest, GWSPrefetchHoldbackOffGWSReferrer) {
+TEST_F(NoStatePrefetchGWSPrefetchHoldbackOffTest,
+       GWSPrefetchHoldbackOffGWSReferrer) {
   GURL url("http://www.notgoogle.com/");
   no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
       url, url::Origin::Create(GURL("www.google.com")), ORIGIN_GWS_PRERENDER,
@@ -562,7 +564,7 @@
 }
 
 class PrerendererNavigationPredictorPrefetchHoldbackTest
-    : public PrerenderTest {
+    : public NoStatePrefetchTest {
  public:
   PrerendererNavigationPredictorPrefetchHoldbackTest() {
     feature_list_.InitAndEnableFeature(kNavigationPredictorPrefetchHoldback);
@@ -592,7 +594,7 @@
 }
 
 // Verify that link-rel:next URLs are not prefetched.
-TEST_F(PrerenderTest, LinkRelNextWithNSPDisabled) {
+TEST_F(NoStatePrefetchTest, LinkRelNextWithNSPDisabled) {
   GURL url("http://www.notgoogle.com/");
   no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
       url, url::Origin::Create(GURL("www.notgoogle.com")), ORIGIN_LINK_REL_NEXT,
@@ -606,7 +608,7 @@
 }
 
 class PrerendererNavigationPredictorPrefetchHoldbackDisabledTest
-    : public PrerenderTest {
+    : public NoStatePrefetchTest {
  public:
   PrerendererNavigationPredictorPrefetchHoldbackDisabledTest() {
     feature_list_.InitAndDisableFeature(kNavigationPredictorPrefetchHoldback);
@@ -637,7 +639,7 @@
 }
 
 // Flaky on Android and Mac, crbug.com/1087876.
-TEST_F(PrerenderTest, DISABLED_PrerenderDisabledOnLowEndDevice) {
+TEST_F(NoStatePrefetchTest, DISABLED_PrerenderDisabledOnLowEndDevice) {
   GURL url("http://www.google.com/");
   no_state_prefetch_manager()->SetIsLowEndDevice(true);
   EXPECT_FALSE(AddSimpleLinkTrigger(url));
@@ -645,7 +647,7 @@
                                         FINAL_STATUS_LOW_END_DEVICE, 1);
 }
 
-TEST_F(PrerenderTest, FoundTest) {
+TEST_F(NoStatePrefetchTest, FoundTest) {
   base::TimeDelta prefetch_age;
   FinalStatus final_status;
   Origin origin;
@@ -685,7 +687,7 @@
 // same URL comes in, that the second request attaches to the first prerender,
 // and we don't use the second prerender contents.
 // This test is the same as the "DuplicateTest" above, but for NoStatePrefetch.
-TEST_F(PrerenderTest, DISABLED_DuplicateTest_NoStatePrefetch) {
+TEST_F(NoStatePrefetchTest, DISABLED_DuplicateTest_NoStatePrefetch) {
   SetConcurrency(2);
   GURL url("http://www.google.com/");
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
@@ -709,7 +711,7 @@
 }
 
 // Ensure that we expire a prerendered page after the max. permitted time.
-TEST_F(PrerenderTest, ExpireTest) {
+TEST_F(NoStatePrefetchTest, ExpireTest) {
   no_state_prefetch_manager()->SetTickClockForTesting(tick_clock());
   GURL url("http://www.google.com/");
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
@@ -725,7 +727,7 @@
 
 // Ensure that we don't launch prerenders of bad urls (in this case, a mailto:
 // url)
-TEST_F(PrerenderTest, BadURLTest) {
+TEST_F(NoStatePrefetchTest, BadURLTest) {
   GURL url("mailto:test@gmail.com");
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
       no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
@@ -738,7 +740,7 @@
 
 // When the user navigates away from a page, the prerenders it launched should
 // have their time to expiry shortened from the default time to live.
-TEST_F(PrerenderTest, LinkManagerNavigateAwayExpire) {
+TEST_F(NoStatePrefetchTest, LinkManagerNavigateAwayExpire) {
   no_state_prefetch_manager()->SetTickClockForTesting(tick_clock());
   const base::TimeDelta time_to_live = base::Seconds(300);
   const base::TimeDelta abandon_time_to_live = base::Seconds(20);
@@ -770,7 +772,7 @@
 
 // But when we navigate away very close to the original expiry of a prerender,
 // we shouldn't expect it to be extended.
-TEST_F(PrerenderTest, LinkManagerNavigateAwayNearExpiry) {
+TEST_F(NoStatePrefetchTest, LinkManagerNavigateAwayNearExpiry) {
   no_state_prefetch_manager()->SetTickClockForTesting(tick_clock());
   const base::TimeDelta time_to_live = base::Seconds(300);
   const base::TimeDelta abandon_time_to_live = base::Seconds(20);
@@ -816,7 +818,7 @@
 // When the user navigates away from a page, and then launches a new prerender,
 // the new prerender should preempt the abandoned prerender even if the
 // abandoned prerender hasn't expired.
-TEST_F(PrerenderTest, LinkManagerNavigateAwayLaunchAnother) {
+TEST_F(NoStatePrefetchTest, LinkManagerNavigateAwayLaunchAnother) {
   no_state_prefetch_manager()->SetTickClockForTesting(tick_clock());
   const base::TimeDelta time_to_live = base::Seconds(300);
   const base::TimeDelta abandon_time_to_live = base::Seconds(20);
@@ -847,7 +849,7 @@
 
 // Prefetching the same URL twice during |time_to_live| results in a duplicate
 // and is aborted.
-TEST_F(PrerenderTest, NoStatePrefetchDuplicate) {
+TEST_F(NoStatePrefetchTest, NoStatePrefetchDuplicate) {
   const GURL kUrl("http://www.google.com/");
   predictors::LoadingPredictorConfig config;
   PopulateTestConfig(&config);
@@ -887,7 +889,7 @@
 // them in the order given up until we reach MaxConcurrency, at which point we
 // queue them and launch them in the order given. As well, insure that limits
 // are enforced for the system as a whole and on a per launcher basis.
-TEST_F(PrerenderTest, MaxConcurrencyTest) {
+TEST_F(NoStatePrefetchTest, MaxConcurrencyTest) {
   struct TestConcurrency {
     size_t max_link_concurrency;
     size_t max_link_concurrency_per_launcher;
@@ -976,7 +978,7 @@
 }
 
 // Flaky on Android: https://crbug.com/1105908
-TEST_F(PrerenderTest, DISABLED_AliasURLTest) {
+TEST_F(NoStatePrefetchTest, DISABLED_AliasURLTest) {
   SetConcurrency(7);
 
   GURL url("http://www.google.com/");
@@ -1029,7 +1031,7 @@
 // Tests that prerendering is cancelled when the source render view does not
 // exist.  On failure, the DCHECK in CreateNoStatePrefetchContents() above
 // should be triggered.
-TEST_F(PrerenderTest, SourceRenderViewClosed) {
+TEST_F(NoStatePrefetchTest, SourceRenderViewClosed) {
   GURL url("http://www.google.com/");
   no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
       url, FINAL_STATUS_PROFILE_DESTROYED);
@@ -1039,7 +1041,7 @@
 
 // Tests that prerendering is cancelled when we launch a second prerender of
 // the same target within a short time interval.
-TEST_F(PrerenderTest, RecentlyVisited) {
+TEST_F(NoStatePrefetchTest, RecentlyVisited) {
   GURL url("http://www.google.com/");
 
   no_state_prefetch_manager()->RecordNavigation(url);
@@ -1051,7 +1053,7 @@
   EXPECT_FALSE(no_state_prefetch_contents->prerendering_has_started());
 }
 
-TEST_F(PrerenderTest, NotSoRecentlyVisited) {
+TEST_F(NoStatePrefetchTest, NotSoRecentlyVisited) {
   no_state_prefetch_manager()->SetTickClockForTesting(tick_clock());
   GURL url("http://www.google.com/");
 
@@ -1070,7 +1072,7 @@
 }
 
 // Tests that the prerender manager matches include the fragment.
-TEST_F(PrerenderTest, FragmentMatchesTest) {
+TEST_F(NoStatePrefetchTest, FragmentMatchesTest) {
   GURL fragment_url("http://www.google.com/#test");
 
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
@@ -1085,7 +1087,7 @@
 
 // Tests that the prerender manager uses fragment references when matching
 // prerender URLs in the case a different fragment is in both URLs.
-TEST_F(PrerenderTest, FragmentsDifferTest) {
+TEST_F(NoStatePrefetchTest, FragmentsDifferTest) {
   GURL fragment_url("http://www.google.com/#test");
   GURL other_fragment_url("http://www.google.com/#other_test");
 
@@ -1103,7 +1105,7 @@
 }
 
 // Make sure that clearing works as expected.
-TEST_F(PrerenderTest, ClearTest) {
+TEST_F(NoStatePrefetchTest, ClearTest) {
   GURL url("http://www.google.com/");
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
       no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
@@ -1116,7 +1118,7 @@
 }
 
 // Make sure canceling works as expected.
-TEST_F(PrerenderTest, CancelAllTest) {
+TEST_F(NoStatePrefetchTest, CancelAllTest) {
   GURL url("http://www.google.com/");
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
       no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
@@ -1129,7 +1131,7 @@
 
 // Test that when prefetch is enabled, a prefetch initiated by omnibox is
 // successful.
-TEST_F(PrerenderTest, OmniboxAllowedWhenNotDisabled) {
+TEST_F(NoStatePrefetchTest, OmniboxAllowedWhenNotDisabled) {
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
       no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
           GURL("http://www.example.com"), absl::nullopt, ORIGIN_OMNIBOX,
@@ -1140,7 +1142,7 @@
   EXPECT_TRUE(no_state_prefetch_contents->prerendering_has_started());
 }
 
-class PrerenderFallbackToPreconnectDisabledTest : public PrerenderTest {
+class PrerenderFallbackToPreconnectDisabledTest : public NoStatePrefetchTest {
  public:
   PrerenderFallbackToPreconnectDisabledTest() {
     feature_list_.InitAndDisableFeature(
@@ -1170,7 +1172,7 @@
   EXPECT_EQ(0u, loading_predictor->GetActiveHintsSizeForTesting());
 }
 
-class PrerenderFallbackToPreconnectEnabledTest : public PrerenderTest {
+class PrerenderFallbackToPreconnectEnabledTest : public NoStatePrefetchTest {
  public:
   PrerenderFallbackToPreconnectEnabledTest() {
     feature_list_.InitAndEnableFeature(
@@ -1258,7 +1260,7 @@
   EXPECT_EQ(0u, loading_predictor->GetActiveHintsSizeForTesting());
 }
 
-TEST_F(PrerenderTest, LinkRelStillAllowedWhenDisabled) {
+TEST_F(NoStatePrefetchTest, LinkRelStillAllowedWhenDisabled) {
   DisablePrerender();
   GURL url("http://www.google.com/");
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
@@ -1272,7 +1274,7 @@
   ASSERT_EQ(no_state_prefetch_contents, entry.get());
 }
 
-TEST_F(PrerenderTest, LinkRelAllowedOnCellular) {
+TEST_F(NoStatePrefetchTest, LinkRelAllowedOnCellular) {
   EnablePrerender();
   GURL url("http://www.example.com");
   std::unique_ptr<net::NetworkChangeNotifier> mock(
@@ -1295,7 +1297,7 @@
 // Verify that the external prefetch requests are not allowed on cellular
 // connection when kPredictivePrefetchingAllowedOnAllConnectionTypes feature is
 // not enabled.
-TEST_F(PrerenderTest, PrerenderNotAllowedOnCellularWithExternalOrigin) {
+TEST_F(NoStatePrefetchTest, PrerenderNotAllowedOnCellularWithExternalOrigin) {
   EnablePrerender();
   std::unique_ptr<net::NetworkChangeNotifier> mock(
       new MockNetworkChangeNotifier4GMetered);
@@ -1319,7 +1321,8 @@
 // Verify that the external prefetch requests are allowed on unmetered cellular
 // connection when kPredictivePrefetchingAllowedOnAllConnectionTypes feature is
 // not enabled.
-TEST_F(PrerenderTest, PrerenderAllowedOnUnmeteredCellularWithExternalOrigin) {
+TEST_F(NoStatePrefetchTest,
+       PrerenderAllowedOnUnmeteredCellularWithExternalOrigin) {
   EnablePrerender();
   std::unique_ptr<net::NetworkChangeNotifier> mock(
       new MockNetworkChangeNotifier4GUnmetered);
@@ -1343,7 +1346,8 @@
 // Verify that the external prefetch requests are not allowed on metered wifi
 // connection when kPredictivePrefetchingAllowedOnAllConnectionTypes feature is
 // not enabled.
-TEST_F(PrerenderTest, PrerenderNotAllowedOnMeteredWifiWithExternalOrigin) {
+TEST_F(NoStatePrefetchTest,
+       PrerenderNotAllowedOnMeteredWifiWithExternalOrigin) {
   EnablePrerender();
   std::unique_ptr<net::NetworkChangeNotifier> mock(
       new MockNetworkChangeNotifierWifiMetered);
@@ -1364,7 +1368,7 @@
   histogram_tester().ExpectTotalCount("Prerender.FinalStatus", 0);
 }
 
-class PrerenderPrefetchingAllowedOnAllTest : public PrerenderTest {
+class PrerenderPrefetchingAllowedOnAllTest : public NoStatePrefetchTest {
  public:
   PrerenderPrefetchingAllowedOnAllTest() {
     feature_list_.InitAndEnableFeature(
@@ -1398,7 +1402,7 @@
   ASSERT_EQ(no_state_prefetch_contents, entry.get());
 }
 
-TEST_F(PrerenderTest, PrerenderAllowedForForcedCellular) {
+TEST_F(NoStatePrefetchTest, PrerenderAllowedForForcedCellular) {
   EnablePrerender();
   std::unique_ptr<net::NetworkChangeNotifier> mock(
       new MockNetworkChangeNotifier4GMetered);
@@ -1425,7 +1429,7 @@
   ASSERT_EQ(no_state_prefetch_contents, entry.get());
 }
 
-TEST_F(PrerenderTest, LinkManagerCancel) {
+TEST_F(NoStatePrefetchTest, LinkManagerCancel) {
   EXPECT_TRUE(IsEmptyNoStatePrefetchLinkManager());
   GURL url("http://www.myexample.com");
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
@@ -1446,7 +1450,7 @@
   EXPECT_TRUE(IsEmptyNoStatePrefetchLinkManager());
 }
 
-TEST_F(PrerenderTest, LinkManagerAbandon) {
+TEST_F(NoStatePrefetchTest, LinkManagerAbandon) {
   EXPECT_TRUE(IsEmptyNoStatePrefetchLinkManager());
   GURL url("http://www.myexample.com");
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
@@ -1468,7 +1472,7 @@
   ASSERT_EQ(no_state_prefetch_contents, entry.get());
 }
 
-TEST_F(PrerenderTest, LinkManagerAbandonThenCancel) {
+TEST_F(NoStatePrefetchTest, LinkManagerAbandonThenCancel) {
   EXPECT_TRUE(IsEmptyNoStatePrefetchLinkManager());
   GURL url("http://www.myexample.com");
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
@@ -1503,7 +1507,7 @@
 #else
 #define MAYBE_LinkManagerAddTwiceCancelTwice LinkManagerAddTwiceCancelTwice
 #endif
-TEST_F(PrerenderTest, MAYBE_LinkManagerAddTwiceCancelTwice) {
+TEST_F(NoStatePrefetchTest, MAYBE_LinkManagerAddTwiceCancelTwice) {
   SetConcurrency(2);
   EXPECT_TRUE(IsEmptyNoStatePrefetchLinkManager());
   GURL url("http://www.myexample.com");
@@ -1538,7 +1542,7 @@
 // TODO(gavinp): Update this test after abandon has an effect on Prerenders,
 // like shortening the timeouts.
 // Flaky on Android and Linux, crbug.com/1087876 & crbug.com/1087736.
-TEST_F(PrerenderTest, DISABLED_LinkManagerAddTwiceAbandonTwiceUseTwice) {
+TEST_F(NoStatePrefetchTest, DISABLED_LinkManagerAddTwiceAbandonTwiceUseTwice) {
   SetConcurrency(2);
   EXPECT_TRUE(IsEmptyNoStatePrefetchLinkManager());
   GURL url("http://www.myexample.com");
@@ -1576,7 +1580,7 @@
 // add a series of tests testing advancing the time by either the abandon
 // or normal expire, and verifying the expected behaviour with groups
 // of links.
-TEST_F(PrerenderTest, LinkManagerExpireThenCancel) {
+TEST_F(NoStatePrefetchTest, LinkManagerExpireThenCancel) {
   no_state_prefetch_manager()->SetTickClockForTesting(tick_clock());
   EXPECT_TRUE(IsEmptyNoStatePrefetchLinkManager());
   GURL url("http://www.myexample.com");
@@ -1602,7 +1606,7 @@
   ASSERT_FALSE(no_state_prefetch_manager()->FindEntry(url));
 }
 
-TEST_F(PrerenderTest, LinkManagerExpireThenAddAgain) {
+TEST_F(NoStatePrefetchTest, LinkManagerExpireThenAddAgain) {
   no_state_prefetch_manager()->SetTickClockForTesting(tick_clock());
   EXPECT_TRUE(IsEmptyNoStatePrefetchLinkManager());
   GURL url("http://www.myexample.com");
@@ -1630,7 +1634,7 @@
 }
 
 // Flaky on Android, crbug.com/1087876.
-TEST_F(PrerenderTest, DISABLED_LinkManagerCancelThenAddAgain) {
+TEST_F(NoStatePrefetchTest, DISABLED_LinkManagerCancelThenAddAgain) {
   EXPECT_TRUE(IsEmptyNoStatePrefetchLinkManager());
   GURL url("http://www.myexample.com");
   DummyNoStatePrefetchContents* first_no_state_prefetch_contents =
@@ -1659,7 +1663,7 @@
 // Creates two prerenders, one of which should be blocked by the
 // max_link_concurrency; abandons both of them and waits to make sure both
 // are cleared from the NoStatePrefetchLinkManager.
-TEST_F(PrerenderTest, DISABLED_LinkManagerAbandonInactivePrerender) {
+TEST_F(NoStatePrefetchTest, DISABLED_LinkManagerAbandonInactivePrerender) {
   no_state_prefetch_manager()->SetTickClockForTesting(tick_clock());
   SetConcurrency(1);
   ASSERT_LT(no_state_prefetch_manager()->config().abandon_time_to_live,
@@ -1693,7 +1697,7 @@
 // Creates two prerenders, one of which should be blocked by the
 // max_link_concurrency; uses one after the max wait to launch, and
 // ensures the second prerender does not start.
-TEST_F(PrerenderTest, LinkManagerWaitToLaunchNotLaunched) {
+TEST_F(NoStatePrefetchTest, LinkManagerWaitToLaunchNotLaunched) {
   no_state_prefetch_manager()->SetTickClockForTesting(tick_clock());
   SetConcurrency(1);
   ASSERT_LT(no_state_prefetch_manager()->config().max_wait_to_launch,
@@ -1730,7 +1734,7 @@
 }
 
 // Creates two prerenders, one of which should start when the first one expires.
-TEST_F(PrerenderTest, LinkManagerExpireRevealingLaunch) {
+TEST_F(NoStatePrefetchTest, LinkManagerExpireRevealingLaunch) {
   no_state_prefetch_manager()->SetTickClockForTesting(tick_clock());
   SetConcurrency(1);
   ASSERT_LT(no_state_prefetch_manager()->config().max_wait_to_launch,
@@ -1780,7 +1784,7 @@
   EXPECT_EQ(second_no_state_prefetch_contents, entry.get());
 }
 
-TEST_F(PrerenderTest, NoStatePrefetchContentsIsValidHttpMethod) {
+TEST_F(NoStatePrefetchTest, NoStatePrefetchContentsIsValidHttpMethod) {
   EXPECT_TRUE(IsValidHttpMethod("GET"));
   EXPECT_TRUE(IsValidHttpMethod("HEAD"));
   EXPECT_FALSE(IsValidHttpMethod("OPTIONS"));
@@ -1789,7 +1793,7 @@
   EXPECT_FALSE(IsValidHttpMethod("WHATEVER"));
 }
 
-TEST_F(PrerenderTest, NoStatePrefetchContentsIncrementsByteCount) {
+TEST_F(NoStatePrefetchTest, NoStatePrefetchContentsIncrementsByteCount) {
   GURL url("http://www.google.com/");
   DummyNoStatePrefetchContents* no_state_prefetch_contents =
       no_state_prefetch_manager()->CreateNextNoStatePrefetchContents(
@@ -1807,7 +1811,7 @@
   EXPECT_EQ(12, no_state_prefetch_contents->network_bytes());
 }
 
-TEST_F(PrerenderTest, NoPrerenderInSingleProcess) {
+TEST_F(NoStatePrefetchTest, NoPrerenderInSingleProcess) {
   GURL url("http://www.google.com/");
   auto* command_line = base::CommandLine::ForCurrentProcess();
   ASSERT_TRUE(command_line != nullptr);
diff --git a/chrome/browser/prefetch/prefetch_prefs.cc b/chrome/browser/prefetch/prefetch_prefs.cc
index 7a04a63..18875924 100644
--- a/chrome/browser/prefetch/prefetch_prefs.cc
+++ b/chrome/browser/prefetch/prefetch_prefs.cc
@@ -9,6 +9,9 @@
 
 namespace prefetch {
 
+const base::Feature kPreloadingHoldback{"PreloadingHoldback",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
 void RegisterPredictionOptionsProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
   registry->RegisterIntegerPref(
@@ -56,6 +59,13 @@
 }
 
 bool IsSomePreloadingEnabled(const PrefService& prefs) {
+  if (base::FeatureList::IsEnabled(kPreloadingHoldback)) {
+    return false;
+  }
+  return IsSomePreloadingEnabledIgnoringFinch(prefs);
+}
+
+bool IsSomePreloadingEnabledIgnoringFinch(const PrefService& prefs) {
   return GetPreloadPagesState(prefs) != PreloadPagesState::kNoPreloading;
 }
 
diff --git a/chrome/browser/prefetch/prefetch_prefs.h b/chrome/browser/prefetch/prefetch_prefs.h
index 77e3601..138289e 100644
--- a/chrome/browser/prefetch/prefetch_prefs.h
+++ b/chrome/browser/prefetch/prefetch_prefs.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_PREFETCH_PREFETCH_PREFS_H_
 #define CHROME_BROWSER_PREFETCH_PREFETCH_PREFS_H_
 
+#include "base/feature_list.h"
+
 namespace user_prefs {
 class PrefRegistrySyncable;
 }
@@ -12,6 +14,7 @@
 class PrefService;
 
 namespace prefetch {
+extern const base::Feature kPreloadingHoldback;
 
 // Enum describing when to allow network predictions.  The numerical value is
 // stored in the prefs file, therefore the same enum with the same order must be
@@ -45,16 +48,23 @@
 };
 
 // Returns the PreloadPagesState corresponding to the NetworkPredictionOptions
-// setting persisted in prefs.
+// setting persisted in prefs. Note that this will return the pref value
+// regardless of whether preloading is disabled via Finch. Prefer using
+// IsSomePreloadingEnabled in most cases.
 PreloadPagesState GetPreloadPagesState(const PrefService& prefs);
 
 // Converts the given PreloadPagesState to a NetworkPredictionOptions and
 // persist it in prefs.
 void SetPreloadPagesState(PrefService* prefs, PreloadPagesState state);
 
-// Returns true if preloading is not entirely disabled.
+// Returns true if preloading is not entirely disabled. Preloading might be
+// disabled by the user via UI settings stored in prefs or by Finch.
 bool IsSomePreloadingEnabled(const PrefService& prefs);
 
+// Returns true if preloading is not entirely disabled via the UI settings.
+// Ignores the PreloadingHoldback Finch feature.
+bool IsSomePreloadingEnabledIgnoringFinch(const PrefService& prefs);
+
 void RegisterPredictionOptionsProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry);
 
diff --git a/chrome/browser/prefetch/prefetch_prefs_unittest.cc b/chrome/browser/prefetch/prefetch_prefs_unittest.cc
index bc893295..51cc1c0f 100644
--- a/chrome/browser/prefetch/prefetch_prefs_unittest.cc
+++ b/chrome/browser/prefetch/prefetch_prefs_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/prefetch/prefetch_prefs.h"
 
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/prefetch/pref_names.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -96,3 +97,63 @@
       static_cast<int>(prefetch::NetworkPredictionOptions::kExtended));
   EXPECT_TRUE(prefetch::IsSomePreloadingEnabled(prefs));
 }
+
+TEST(PrefetchPrefsTest, IsSomePreloadingEnabled_PreloadingHoldback) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeature(prefetch::kPreloadingHoldback);
+  TestingPrefServiceSimple prefs;
+  prefs.registry()->RegisterIntegerPref(
+      prefs::kNetworkPredictionOptions,
+      static_cast<int>(prefetch::NetworkPredictionOptions::kDefault));
+
+  prefs.SetInteger(
+      prefs::kNetworkPredictionOptions,
+      static_cast<int>(prefetch::NetworkPredictionOptions::kDisabled));
+  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(prefs));
+
+  prefs.SetInteger(
+      prefs::kNetworkPredictionOptions,
+      static_cast<int>(prefetch::NetworkPredictionOptions::kStandard));
+  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(prefs));
+
+  prefs.SetInteger(
+      prefs::kNetworkPredictionOptions,
+      static_cast<int>(
+          prefetch::NetworkPredictionOptions::kWifiOnlyDeprecated));
+  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(prefs));
+
+  prefs.SetInteger(
+      prefs::kNetworkPredictionOptions,
+      static_cast<int>(prefetch::NetworkPredictionOptions::kExtended));
+  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(prefs));
+}
+
+TEST(PrefetchPrefsTest, IsSomePreloadingEnabledIgnoringFinch) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeature(prefetch::kPreloadingHoldback);
+  TestingPrefServiceSimple prefs;
+  prefs.registry()->RegisterIntegerPref(
+      prefs::kNetworkPredictionOptions,
+      static_cast<int>(prefetch::NetworkPredictionOptions::kDefault));
+
+  prefs.SetInteger(
+      prefs::kNetworkPredictionOptions,
+      static_cast<int>(prefetch::NetworkPredictionOptions::kDisabled));
+  EXPECT_FALSE(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs));
+
+  prefs.SetInteger(
+      prefs::kNetworkPredictionOptions,
+      static_cast<int>(prefetch::NetworkPredictionOptions::kStandard));
+  EXPECT_TRUE(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs));
+
+  prefs.SetInteger(
+      prefs::kNetworkPredictionOptions,
+      static_cast<int>(
+          prefetch::NetworkPredictionOptions::kWifiOnlyDeprecated));
+  EXPECT_TRUE(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs));
+
+  prefs.SetInteger(
+      prefs::kNetworkPredictionOptions,
+      static_cast<int>(prefetch::NetworkPredictionOptions::kExtended));
+  EXPECT_TRUE(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs));
+}
diff --git a/chrome/browser/prefetch/zero_suggest_prefetch/zero_suggest_prefetch_tab_helper.cc b/chrome/browser/prefetch/zero_suggest_prefetch/zero_suggest_prefetch_tab_helper.cc
index 6f2b60d..4d31023 100644
--- a/chrome/browser/prefetch/zero_suggest_prefetch/zero_suggest_prefetch_tab_helper.cc
+++ b/chrome/browser/prefetch/zero_suggest_prefetch/zero_suggest_prefetch_tab_helper.cc
@@ -49,12 +49,10 @@
 
 ZeroSuggestPrefetchTabHelper::~ZeroSuggestPrefetchTabHelper() = default;
 
-void ZeroSuggestPrefetchTabHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
-  if (!navigation_handle->IsInPrimaryMainFrame() ||
-      navigation_handle->GetURL() != GURL(chrome::kChromeUINewTabPageURL)) {
+void ZeroSuggestPrefetchTabHelper::PrimaryPageChanged(content::Page& page) {
+  if (page.GetMainDocument().GetLastCommittedURL() !=
+      GURL(chrome::kChromeUINewTabPageURL))
     return;
-  }
 
   // Make sure to observe the TabStripModel, if not already, in order to get
   // notified when a New Tab Page is switched to.
diff --git a/chrome/browser/prefetch/zero_suggest_prefetch/zero_suggest_prefetch_tab_helper.h b/chrome/browser/prefetch/zero_suggest_prefetch/zero_suggest_prefetch_tab_helper.h
index 33e5144..1ea221c 100644
--- a/chrome/browser/prefetch/zero_suggest_prefetch/zero_suggest_prefetch_tab_helper.h
+++ b/chrome/browser/prefetch/zero_suggest_prefetch/zero_suggest_prefetch_tab_helper.h
@@ -22,8 +22,7 @@
       delete;
 
   // content::WebContentsObserver:
-  void DidFinishNavigation(
-      content::NavigationHandle* navigation_handle) override;
+  void PrimaryPageChanged(content::Page& page) override;
 
   // TabStripModelObserver:
   void OnTabStripModelChanged(
diff --git a/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn b/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
index 6683796f..877235f 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
+++ b/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
@@ -32,6 +32,7 @@
     ":emoji_data",
     ":emoji_data_remaining",
     ":emoticon_data",
+    ":symbol_data",
   ]
   grd_prefix = "emoji_picker"
   out_grd = resources_grd_file
@@ -177,6 +178,17 @@
   ]
 }
 
+action("symbol_data") {
+  script = "tools/symbol_data.py"
+
+  outputs = [ "$target_gen_dir/symbol_ordering.json" ]
+
+  args = [
+    "--output",
+    rebase_path(outputs[0], root_build_dir),
+  ]
+}
+
 js_library("emoji_picker") {
   deps = [
     ":constants",
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_ordering.grdp b/chrome/browser/resources/chromeos/emoji_picker/emoji_ordering.grdp
index 7692f18..540d3c2 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_ordering.grdp
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_ordering.grdp
@@ -6,4 +6,5 @@
       <include name="IDR_EMOJI_PICKER_EMOJI_TEST_ORDERING_JSON_REMAINING" compress="gzip" file="${root_gen_dir}/chrome/browser/resources/chromeos/emoji_picker/emoji_test_ordering_remaining.json" resource_path="emoji_test_ordering_remaining.json" use_base_dir="false" type="chrome_html" />
       <include name="IDR_EMOJI_PICKER_EMOTICON_ORDERING_JSON" compress="gzip" file="${root_gen_dir}/chrome/browser/resources/chromeos/emoji_picker/emoticon_ordering.json" resource_path="emoticon_ordering.json" use_base_dir="false" type="chrome_html" />
       <include name="IDR_EMOJI_PICKER_EMOTICON_TEST_ORDERING_JSON" compress="gzip" file="${root_gen_dir}/chrome/browser/resources/chromeos/emoji_picker/emoticon_test_ordering.json" resource_path="emoticon_test_ordering.json" use_base_dir="false" type="chrome_html" />
+      <include name="IDR_EMOJI_PICKER_SYMBOL_ORDERING_JSON" compress="gzip" file="${root_gen_dir}/chrome/browser/resources/chromeos/emoji_picker/symbol_ordering.json" resource_path="symbol_ordering.json" use_base_dir="false" type="chrome_html" />
 </grit-part>
diff --git a/chrome/browser/resources/chromeos/emoji_picker/tools/symbol_data.py b/chrome/browser/resources/chromeos/emoji_picker/tools/symbol_data.py
new file mode 100755
index 0000000..fedba5e
--- /dev/null
+++ b/chrome/browser/resources/chromeos/emoji_picker/tools/symbol_data.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python3
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import dataclasses
+import json
+import logging
+import os
+import sys
+import unicodedata
+from typing import Generator, List, Tuple
+
+# Add extra dependencies to the python path.
+_SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__))
+_CHROME_SOURCE = os.path.realpath(
+    os.path.join(_SCRIPT_DIR, *[os.path.pardir] * 6))
+sys.path.append(os.path.join(_CHROME_SOURCE, 'build/android/gyp'))
+
+from util import build_utils
+
+# Initialize logger.
+logging.basicConfig(stream=sys.stdout, level=logging.ERROR)
+LOGGER = logging.getLogger(__name__)
+
+# List of unicode ranges for each symbol group (ranges are inclusive).
+SYMBOLS_GROUPS = {
+    'Arrows': [
+        # Arrows Unicode Block.
+        (0x2190, 0x21ff),
+        # Supplemental Arrows-C Unicode Block.
+        # Note: There are unassigned code points in the block which are
+        # automatically skipped by the script.
+        (0x1f800, 0x1f8b2),
+    ],
+    'Bullet/Stars': [
+        # Some rows from Miscellaneous Symbols and Arrows Unicode block.
+        (0x2b20, 0x2b2f),
+        (0x2b50, 0x2b5f),
+        (0x2b90, 0x2b9f),
+        (0x2bb0, 0x2bbf),
+        (0x2bc0, 0x2bcf),
+    ],
+    'Currency': [
+        # Currency Unicode Block.
+        (0x20a0, 0x20bf),
+    ],
+    'Letterlike': [
+        # Letterlike Symbols Unicode Block.
+        (0x2100, 0x210f),
+    ],
+    'Math': [
+        # Greek Letters and Symbols from Mathematical and Alphanumeric
+        # Symbols Unicode Block.
+        # Normal Capital Letters.
+        (0x0391, 0x0391 + 25),
+        # Normal Small Letters.
+        (0x03b1, 0x03b1 + 25),
+    ],
+    'Miscellaneous': [
+        # Miscellaneous Symbols Unicode Block.
+        (0x2600, 0x26ff)
+    ]
+}
+
+
+@dataclasses.dataclass
+class EmojiPickerChar:
+    """A type representing a single character in EmojiPicker."""
+    # Unicode character.
+    string: str
+    # Name of the unicode character.
+    name: str
+    # Search keywords related to the unicode character.
+    keywords: List[str] = dataclasses.field(
+        default_factory=list)
+
+
+@dataclasses.dataclass
+class EmojiPickerEmoji:
+    """A type representing an emoji/emoticon/symbol in EmojiPicker."""
+    # Base Emoji.
+    base: EmojiPickerChar
+    # Base Emoji's variants and alternative emojis.
+    alternates: List[EmojiPickerChar] = dataclasses.field(
+        default_factory=list)
+
+
+@dataclasses.dataclass
+class EmojiPickerGroup:
+    """A type representing a group of emoji/emoticon/symbols."""
+    # Name of the group.
+    group: str
+    # List of the emojis in the group.
+    emoji: List[EmojiPickerEmoji]
+
+
+def _convert_unicode_ranges_to_emoji_chars(
+        unicode_ranges: List[Tuple[int, int]],
+        ignore_errors: bool = True) -> Generator[EmojiPickerChar, None, None]:
+    """Converts unicode ranges to `EmojiPickerChar` instances.
+
+    Given a list of unicode ranges, it iterates over all characters in all the
+    ranges and creates and yields an instance of `EmojiPickerChar` for each
+    one.
+
+    Args:
+        unicode_ranges: A list of unicode ranges.
+        ignore_errors: If True, any exceptions raised during processing
+            unicode characters is silently ignored.
+
+    Raises:
+        ValueError: If a unicode character does not exist in the data source
+            and `ignore_errors` is true, an exception is raised.
+
+    Yields:
+        The converted version of each unicode character in the input ranges.
+    """
+
+    LOGGER.info(
+        'generating EmojiPickerChar instances for ranges: [%s].',
+        ', '.join(
+            '(U+{:02x}, U+{:02x})'.format(*rng)
+            for rng in unicode_ranges))
+
+    num_chars = 0
+    num_ignored = 0
+
+    # Iterate over the input unicode ranges.
+    for (start_code_point, end_code_point) in unicode_ranges:
+        LOGGER.debug(
+            'generating EmojiPickerChar instances '
+            'for range (U+%02x to U+%02x).',
+            start_code_point,
+            end_code_point)
+
+        num_chars += end_code_point + 1 - start_code_point
+        # Iterate over all code points in the range.
+        for code_point in range(start_code_point, end_code_point + 1):
+            try:
+                # For the current code point, create the corresponding
+                # character and lookup its name in the unicodedata. Then,
+                # create an instance of  `EmojiPickerChar` from the data.
+                unicode_character = chr(code_point)
+                yield EmojiPickerChar(
+                    string=unicode_character,
+                    name=unicodedata.name(unicode_character).lower())
+            except ValueError:
+                # If ignore_errors is False, raise the exception.
+                if not ignore_errors:
+                    raise
+                else:
+                    num_ignored += 1
+                    LOGGER.warning(
+                        'invalid code point U+%02x.', code_point)
+
+    LOGGER.info(
+        'stats: #returned instances: %d, #ignored code points: %d',
+        num_chars,
+        num_ignored)
+
+
+def get_symbols_groups(ignore_errors: bool = True) -> List[EmojiPickerGroup]:
+    """Creates symbols data from predefined groups and their unicode ranges.
+
+    Args:
+        ignore_errors: If True, any exceptions raised during processing
+            unicode characters is silently ignored.
+
+    Raises:
+        ValueError: If a unicode character does not exist in the data source
+            and `ignore_errors` is true, the exception is raised.
+    """
+    # TODO(b/232160008): Exclude symbols that are in emoji/emoticon lists.
+
+    emoji_groups = list()
+    for (group_name, unicode_ranges) in SYMBOLS_GROUPS.items():
+        LOGGER.info('generating symbols for group %s.', group_name)
+        emoji_chars = _convert_unicode_ranges_to_emoji_chars(
+            unicode_ranges, ignore_errors=ignore_errors)
+        emoji = [
+            EmojiPickerEmoji(base=emoji_char)
+            for emoji_char in emoji_chars]
+
+        emoji_groups.append(EmojiPickerGroup(group=group_name, emoji=emoji))
+    return emoji_groups
+
+
+def main(argv: List[str]) -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        '--output', required=True, type=str,
+        help='Path to write the output JSON file.')
+    parser.add_argument(
+        '--verbose', required=False, default=False,
+        action='store_true',
+        help="Set the logging level to Debug.")
+    args = parser.parse_args(argv)
+
+    if args.verbose:
+        LOGGER.setLevel(level=logging.DEBUG)
+
+    symbols_groups = get_symbols_groups()
+
+    # Create the data and convert them to dict.
+    symbols_groups_dicts = [
+        dataclasses.asdict(symbol_group)
+        for symbol_group in symbols_groups]
+
+    # Write the result to output path as json file.
+    with build_utils.AtomicOutput(args.output) as tmp_file:
+        tmp_file.write(
+            json.dumps(
+                symbols_groups_dicts,
+                separators=(',', ':'),
+                ensure_ascii=False).encode('utf-8'))
+
+
+if __name__ == "__main__":
+    main(sys.argv[1:])
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_guide/privacy_guide_fragment_shared_css.html b/chrome/browser/resources/settings/privacy_page/privacy_guide/privacy_guide_fragment_shared_css.html
index c737fd506..8faf525 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_guide/privacy_guide_fragment_shared_css.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_guide/privacy_guide_fragment_shared_css.html
@@ -110,7 +110,7 @@
     :host-context([is-privacy-guide-v2]) .header-phase2 picture,
     :host-context([is-privacy-guide-v2]) .fragment-content {
       animation: fade-in var(--privacy-guide-animation-duration),
-          slide-in var(--privacy-guide-animation-duration);
+          slide-in 450ms;
     }
 
     :host-context([is-privacy-guide-v2]) .header-label-phase2 {
diff --git a/chrome/browser/resources/settings/route.ts b/chrome/browser/resources/settings/route.ts
index fdba8d4..0872802 100644
--- a/chrome/browser/resources/settings/route.ts
+++ b/chrome/browser/resources/settings/route.ts
@@ -190,7 +190,8 @@
 
     r.ACCESSIBILITY = r.ADVANCED.createSection('/accessibility', 'a11y');
 
-    // <if expr="is_linux">
+    // TODO(crbug.com/1307455): Make this just "is_linux" when fixing.
+    // <if expr="is_linux and not chromeos_ash and not chromeos_lacros">
     r.CAPTIONS = r.ACCESSIBILITY.createChild('/captions');
     // </if>
 
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_config.cc b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
index de7046c..ee7afb7 100644
--- a/chrome/browser/segmentation_platform/segmentation_platform_config.cc
+++ b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
@@ -39,7 +39,7 @@
 
 constexpr int kDummyFeatureSelectionTTLDays = 1;
 
-constexpr int kChromeLowUserEngagementSelectionTTLDays = 7;
+constexpr int kChromeLowUserEngagementSelectionTTLDays = 30;
 
 #if BUILDFLAG(IS_ANDROID)
 
@@ -154,7 +154,7 @@
 std::unique_ptr<ModelProvider> GetLowEngagementDefaultModel() {
   if (!base::GetFieldTrialParamByFeatureAsBool(
           features::kSegmentationPlatformLowEngagementFeature,
-          kDefaultModelEnabledParam, true)) {
+          kDefaultModelEnabledParam, false)) {
     return nullptr;
   }
   return std::make_unique<LowUserEngagementModel>();
@@ -186,9 +186,7 @@
       feature_guide::features::kSegmentationModelLowEngagedUsers,
       "segment_selection_ttl_days", kChromeLowUserEngagementSelectionTTLDays);
 #else
-  int segment_selection_ttl_days = base::GetFieldTrialParamByFeatureAsInt(
-      features::kSegmentationPlatformLowEngagementFeature,
-      "segment_selection_ttl_days", kChromeLowUserEngagementSelectionTTLDays);
+  int segment_selection_ttl_days = kChromeLowUserEngagementSelectionTTLDays;
 #endif
 
   config->segment_selection_ttl = base::Days(segment_selection_ttl_days);
diff --git a/chrome/browser/sessions/better_session_restore_browsertest.cc b/chrome/browser/sessions/better_session_restore_browsertest.cc
index c970161..3b8b238 100644
--- a/chrome/browser/sessions/better_session_restore_browsertest.cc
+++ b/chrome/browser/sessions/better_session_restore_browsertest.cc
@@ -65,6 +65,13 @@
 #include "base/mac/scoped_nsautorelease_pool.h"
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/startup/browser_init_params.h"
+#include "components/account_manager_core/account.h"
+#include "components/account_manager_core/account_manager_util.h"
+#include "components/signin/public/identity_manager/identity_test_utils.h"
+#endif
+
 namespace {
 
 const char kTestHeaders[] = "HTTP/1.1 200 OK\nContent-type: text/html\n\n";
@@ -156,6 +163,22 @@
   BetterSessionRestoreTest(const BetterSessionRestoreTest&) = delete;
   BetterSessionRestoreTest& operator=(const BetterSessionRestoreTest&) = delete;
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  void CreatedBrowserMainParts(
+      content::BrowserMainParts* browser_main_parts) override {
+    crosapi::mojom::BrowserInitParamsPtr init_params =
+        crosapi::mojom::BrowserInitParams::New();
+    std::string device_account_email = "primaryaccount@gmail.com";
+    account_manager::AccountKey key(
+        signin::GetTestGaiaIdForEmail(device_account_email),
+        ::account_manager::AccountType::kGaia);
+    init_params->device_account =
+        account_manager::ToMojoAccount({key, device_account_email});
+    chromeos::BrowserInitParams::SetInitParamsForTests(std::move(init_params));
+    InProcessBrowserTest::CreatedBrowserMainParts(browser_main_parts);
+  }
+#endif
+
  protected:
   void SetUpOnMainThread() override {
     SessionServiceTestHelper helper(browser()->profile());
diff --git a/chrome/browser/signin/account_reconcilor_factory.cc b/chrome/browser/signin/account_reconcilor_factory.cc
index c07b7423..abdbdbd6 100644
--- a/chrome/browser/signin/account_reconcilor_factory.cc
+++ b/chrome/browser/signin/account_reconcilor_factory.cc
@@ -197,8 +197,11 @@
           IdentityManagerFactory::GetForProfile(profile));
 #elif BUILDFLAG(IS_CHROMEOS_LACROS)
       if (base::FeatureList::IsEnabled(switches::kLacrosNonSyncingProfiles)) {
-        return std::make_unique<
-            signin::MirrorLandingAccountReconcilorDelegate>();
+        bool is_main_profile = ChromeSigninClientFactory::GetForProfile(profile)
+                                   ->GetInitialPrimaryAccount()
+                                   .has_value();
+        return std::make_unique<signin::MirrorLandingAccountReconcilorDelegate>(
+            IdentityManagerFactory::GetForProfile(profile), is_main_profile);
       }
       return std::make_unique<signin::MirrorAccountReconcilorDelegate>(
           IdentityManagerFactory::GetForProfile(profile));
diff --git a/chrome/browser/signin/identity_manager_factory.cc b/chrome/browser/signin/identity_manager_factory.cc
index f55ff84..66ed056c1 100644
--- a/chrome/browser/signin/identity_manager_factory.cc
+++ b/chrome/browser/signin/identity_manager_factory.cc
@@ -26,14 +26,17 @@
 #include "components/signin/public/webdata/token_web_data.h"
 #include "content/public/browser/network_service_instance.h"
 
-#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
-#include "components/keyed_service/core/service_access_type.h"
 #include "components/signin/core/browser/cookie_settings_util.h"
 #endif
 
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+#include "chrome/browser/web_data_service_factory.h"
+#include "components/keyed_service/core/service_access_type.h"
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process_platform_part.h"
@@ -125,13 +128,16 @@
   params.profile_path = profile->GetPath();
   params.signin_client = ChromeSigninClientFactory::GetForProfile(profile);
 
-#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
   params.delete_signin_cookies_on_exit =
       signin::SettingsDeleteSigninCookiesOnExit(
           CookieSettingsFactory::GetForProfile(profile).get());
+#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
+
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
   params.token_web_data = WebDataServiceFactory::GetTokenWebDataForProfile(
       profile, ServiceAccessType::EXPLICIT_ACCESS);
-#endif
+#endif  // #if BUILDFLAG(ENABLE_DICE_SUPPORT)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   params.account_manager_facade =
diff --git a/chrome/browser/ui/android/webid/account_selection_view_android.cc b/chrome/browser/ui/android/webid/account_selection_view_android.cc
index 7c02b3e8..e662ad2 100644
--- a/chrome/browser/ui/android/webid/account_selection_view_android.cc
+++ b/chrome/browser/ui/android/webid/account_selection_view_android.cc
@@ -45,13 +45,13 @@
 ScopedJavaLocalRef<jobject> ConvertToJavaIdentityProviderMetadata(
     JNIEnv* env,
     const content::IdentityProviderMetadata& metadata) {
-  base::android::ScopedJavaLocalRef<jobject> java_brand_icon;
-  if (!metadata.brand_icon.isNull())
-    java_brand_icon = gfx::ConvertToJavaBitmap(metadata.brand_icon);
+  ScopedJavaLocalRef<jstring> java_brand_icon_url =
+      base::android::ConvertUTF8ToJavaString(env,
+                                             metadata.brand_icon_url.spec());
   return Java_IdentityProviderMetadata_Constructor(
       env, ui::OptionalSkColorToJavaColor(metadata.brand_text_color),
       ui::OptionalSkColorToJavaColor(metadata.brand_background_color),
-      java_brand_icon);
+      java_brand_icon_url);
 }
 
 ScopedJavaLocalRef<jobject> ConvertToJavaClientIdMetadata(
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java
index 6b10a5b..dacd85fc 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java
@@ -38,7 +38,7 @@
     }
 
     @CalledByNative
-    private static int getBrandIconMinimumSize() {
+    static int getBrandIconMinimumSize() {
         // Icon needs to be big enough for the smallest screen density (1x).
         Resources resources = ContextUtils.getApplicationContext().getResources();
         // Density < 1.0f on ldpi devices. Adjust density to ensure that
@@ -48,7 +48,7 @@
     }
 
     @CalledByNative
-    private static int getBrandIconIdealSize() {
+    static int getBrandIconIdealSize() {
         Resources resources = ContextUtils.getApplicationContext().getResources();
         return Math.round(resources.getDimension(R.dimen.account_selection_sheet_icon_size));
     }
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java
index fb63015..5f66b5d0 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java
@@ -13,7 +13,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -39,6 +39,8 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.shadows.ShadowLooper;
@@ -130,7 +132,8 @@
 
     public AccountSelectionControllerTest() {
         MockitoAnnotations.initMocks(this);
-        IDP_METADATA = new IdentityProviderMetadata(Color.BLACK, Color.BLACK, mock(Bitmap.class));
+        IDP_METADATA =
+                new IdentityProviderMetadata(Color.BLACK, Color.BLACK, "https://icon-url.example");
     }
 
     @Before
@@ -154,6 +157,20 @@
 
     @Test
     public void testShowAccountSignInHeader() {
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                Callback<Bitmap> callback = (Callback<Bitmap>) invocation.getArguments()[1];
+
+                Bitmap brandIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+                brandIcon.eraseColor(Color.RED);
+                callback.onResult(brandIcon);
+                return null;
+            }
+        })
+                .when(mMockImageFetcher)
+                .fetchImage(any(), any(Callback.class));
+
         mMediator.showAccounts(TEST_ETLD_PLUS_ONE, TEST_ETLD_PLUS_ONE_1, Arrays.asList(ANA),
                 IDP_METADATA, CLIENT_ID_METADATA, false /* isAutoSignIn */);
 
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java
index ffae130a..be3316e 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.ui.android.webid;
 
+import android.graphics.Bitmap;
 import android.os.Handler;
 
 import androidx.annotation.Px;
@@ -55,9 +56,11 @@
     // proceed. Eventually this should be specified by IDPs.
     private static final int AUTO_SIGN_IN_CANCELLATION_TIMER_MS = 5000;
 
+    private HeaderType mHeaderType;
     private String mRpEtldPlusOne;
     private String mIdpEtldPlusOne;
     private IdentityProviderMetadata mIdpMetadata;
+    private Bitmap mBrandIcon;
     private ClientIdMetadata mClientMetadata;
 
     // All of the user's accounts.
@@ -133,7 +136,7 @@
         };
 
         return new PropertyModel.Builder(HeaderProperties.ALL_KEYS)
-                .with(HeaderProperties.IDP_BRAND_ICON, idpMetadata.getBrandIcon())
+                .with(HeaderProperties.IDP_BRAND_ICON, mBrandIcon)
                 .with(HeaderProperties.CLOSE_ON_CLICK_LISTENER, closeOnClickRunnable)
                 .with(HeaderProperties.FORMATTED_IDP_ETLD_PLUS_ONE, formattedIdpEtldPlusOne)
                 .with(HeaderProperties.FORMATTED_RP_ETLD_PLUS_ONE, formattedRpEtldPlusOne)
@@ -154,7 +157,8 @@
     }
 
     void showVerifySheet(Account account) {
-        updateSheet(HeaderType.VERIFY, Arrays.asList(account), /*areAccountsClickable=*/false,
+        mHeaderType = HeaderType.VERIFY;
+        updateSheet(Arrays.asList(account), /*areAccountsClickable=*/false,
                 /* focusItem=*/ItemProperties.HEADER);
     }
 
@@ -168,6 +172,21 @@
         mSelectedAccount = accounts.size() == 1 ? accounts.get(0) : null;
         showAccountsInternal(rpEtldPlusOne, idpEtldPlusOne, accounts, idpMetadata, clientMetadata,
                 isAutoSignIn, /*focusItem=*/ItemProperties.HEADER);
+
+        if (idpMetadata.getBrandIconUrl() != null) {
+            int brandIconIdealSize = AccountSelectionBridge.getBrandIconIdealSize();
+            ImageFetcher.Params params = ImageFetcher.Params.create(idpMetadata.getBrandIconUrl(),
+                    ImageFetcher.WEB_ID_ACCOUNT_SELECTION_UMA_CLIENT_NAME, brandIconIdealSize,
+                    brandIconIdealSize);
+
+            mImageFetcher.fetchImage(params, bitmap -> {
+                if (bitmap.getWidth() == bitmap.getHeight()
+                        && bitmap.getWidth() >= AccountSelectionBridge.getBrandIconMinimumSize()) {
+                    mBrandIcon = bitmap;
+                    updateHeader();
+                }
+            });
+        }
     }
 
     private void showAccountsInternal(String rpEtldPlusOne, String idpEtldPlusOne,
@@ -183,28 +202,24 @@
             accounts = Arrays.asList(mSelectedAccount);
         }
 
-        HeaderType headerType = isAutoSignIn ? HeaderType.AUTO_SIGN_IN : HeaderType.SIGN_IN;
-        updateSheet(
-                headerType, accounts, /*areAccountsClickable=*/mSelectedAccount == null, focusItem);
+        mHeaderType = isAutoSignIn ? HeaderType.AUTO_SIGN_IN : HeaderType.SIGN_IN;
+        updateSheet(accounts, /*areAccountsClickable=*/mSelectedAccount == null, focusItem);
     }
 
-    private void updateSheet(HeaderType headerType, List<Account> accounts,
-            boolean areAccountsClickable, PropertyKey focusItem) {
+    private void updateSheet(
+            List<Account> accounts, boolean areAccountsClickable, PropertyKey focusItem) {
         updateAccounts(mIdpEtldPlusOne, accounts, areAccountsClickable);
-
-        PropertyModel headerModel =
-                createHeaderItem(headerType, mRpEtldPlusOne, mIdpEtldPlusOne, mIdpMetadata);
-        mModel.set(ItemProperties.HEADER, headerModel);
+        updateHeader();
 
         boolean isContinueButtonVisible = false;
         boolean isDataSharingConsentVisible = false;
-        if (headerType == HeaderType.SIGN_IN && mSelectedAccount != null) {
+        if (mHeaderType == HeaderType.SIGN_IN && mSelectedAccount != null) {
             isContinueButtonVisible = true;
             // Only show the user data sharing consent text for sign up.
             isDataSharingConsentVisible = !mSelectedAccount.isSignIn();
         }
 
-        if (headerType == HeaderType.AUTO_SIGN_IN) {
+        if (mHeaderType == HeaderType.AUTO_SIGN_IN) {
             assert mSelectedAccount != null;
             assert mSelectedAccount.isSignIn();
 
@@ -227,6 +242,12 @@
         mBottomSheetContent.focusForAccessibility(focusItem);
     }
 
+    private void updateHeader() {
+        PropertyModel headerModel =
+                createHeaderItem(mHeaderType, mRpEtldPlusOne, mIdpEtldPlusOne, mIdpMetadata);
+        mModel.set(ItemProperties.HEADER, headerModel);
+    }
+
     /**
      * Requests to show the bottom sheet. If it is not possible to immediately show the content
      * (e.g., higher priority content is being shown) it removes the request from the bottom sheet
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewTest.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewTest.java
index c11055a..9a8e4ab 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewTest.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewTest.java
@@ -15,7 +15,6 @@
 
 import static java.util.Arrays.asList;
 
-import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.text.Spanned;
 import android.text.style.ClickableSpan;
@@ -271,12 +270,9 @@
     @MediumTest
     public void testContinueButtonBranding() {
         final int expectedTextColor = Color.BLUE;
-        final int expectedIconColor = Color.RED;
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            Bitmap brandIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-            brandIcon.eraseColor(expectedIconColor);
-            IdentityProviderMetadata idpMetadata = new IdentityProviderMetadata(
-                    expectedTextColor, /*brandBackgroundColor*/ Color.GREEN, brandIcon);
+            IdentityProviderMetadata idpMetadata = new IdentityProviderMetadata(expectedTextColor,
+                    /*brandBackgroundColor*/ Color.GREEN, "https://icon-url.example");
 
             mModel.set(ItemProperties.CONTINUE_BUTTON, buildContinueButton(ANA, idpMetadata));
         });
diff --git a/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/data/IdentityProviderMetadata.java b/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/data/IdentityProviderMetadata.java
index 86ac1f8..c7c6a51 100644
--- a/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/data/IdentityProviderMetadata.java
+++ b/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/data/IdentityProviderMetadata.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.ui.android.webid.data;
 
-import android.graphics.Bitmap;
-
 import androidx.annotation.Nullable;
 
 import org.chromium.base.annotations.CalledByNative;
@@ -17,18 +15,18 @@
 public class IdentityProviderMetadata {
     private final Integer mBrandTextColor;
     private final Integer mBrandBackgroundColor;
-    private final Bitmap mBrandIcon;
+    private final String mBrandIconUrl;
 
     @CalledByNative
     public IdentityProviderMetadata(
-            long brandTextColor, long brandBackgroundColor, Bitmap brandIcon) {
+            long brandTextColor, long brandBackgroundColor, String brandIconUrl) {
         // Parameters are longs because ColorUtils.INVALID_COLOR does not fit in an int.
         mBrandTextColor =
                 (brandTextColor == ColorUtils.INVALID_COLOR) ? null : (int) brandTextColor;
         mBrandBackgroundColor = (brandBackgroundColor == ColorUtils.INVALID_COLOR)
                 ? null
                 : (int) brandBackgroundColor;
-        mBrandIcon = brandIcon;
+        mBrandIconUrl = brandIconUrl;
     }
 
     public @Nullable Integer getBrandTextColor() {
@@ -39,7 +37,7 @@
         return mBrandBackgroundColor;
     }
 
-    public Bitmap getBrandIcon() {
-        return mBrandIcon;
+    public String getBrandIconUrl() {
+        return mBrandIconUrl;
     }
 }
diff --git a/chrome/browser/ui/omnibox/chrome_omnibox_client.cc b/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
index 73bae079..e1605cc7 100644
--- a/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
+++ b/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
@@ -385,12 +385,14 @@
   // values (kHitFinished, kUnused, kCancelled) are recorded in
   // PrerenderManager.
   content::WebContents* web_contents = controller_->GetWebContents();
-  auto* prerender_manager = PrerenderManager::FromWebContents(web_contents);
-  if (!prerender_manager ||
-      !prerender_manager->HasSearchResultPagePrerendered()) {
-    base::UmaHistogramEnumeration(
-        internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine,
-        PrerenderPredictionStatus::kNotStarted);
+  if (web_contents) {
+    auto* prerender_manager = PrerenderManager::FromWebContents(web_contents);
+    if (!prerender_manager ||
+        !prerender_manager->HasSearchResultPagePrerendered()) {
+      base::UmaHistogramEnumeration(
+          internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine,
+          PrerenderPredictionStatus::kNotStarted);
+    }
   }
 
   predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_)
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
index 662d5fd..08393ae4 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
@@ -284,13 +284,9 @@
   HideImpl();
 }
 
-void PasswordGenerationPopupControllerImpl::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
-  if (navigation_handle->HasCommitted() &&
-      navigation_handle->IsInPrimaryMainFrame() &&
-      !navigation_handle->IsSameDocument()) {
-    HideImpl();
-  }
+void PasswordGenerationPopupControllerImpl::PrimaryPageChanged(
+    content::Page& page) {
+  HideImpl();
 }
 
 #if !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
index 3a26c571..428b776 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
@@ -108,8 +108,7 @@
 
   // content::WebContentsObserver overrides
   void WebContentsDestroyed() override;
-  void DidFinishNavigation(
-      content::NavigationHandle* navigation_handle) override;
+  void PrimaryPageChanged(content::Page& page) override;
 
 #if !BUILDFLAG(IS_ANDROID)
   // ZoomObserver implementation.
diff --git a/chrome/browser/ui/side_search/side_search_side_contents_helper.cc b/chrome/browser/ui/side_search/side_search_side_contents_helper.cc
index 6da14c5..c7ad3d9 100644
--- a/chrome/browser/ui/side_search/side_search_side_contents_helper.cc
+++ b/chrome/browser/ui/side_search/side_search_side_contents_helper.cc
@@ -90,17 +90,8 @@
   return std::make_unique<SideSearchContentsThrottle>(handle);
 }
 
-void SideSearchSideContentsHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
-  // DidFinishNavigation triggers even if the navigation has been cancelled by
-  // the throttle. Early return if this is the case.
-  if (!navigation_handle->IsInPrimaryMainFrame() ||
-      navigation_handle->IsSameDocument() ||
-      !navigation_handle->HasCommitted()) {
-    return;
-  }
-
-  const auto& url = navigation_handle->GetURL();
+void SideSearchSideContentsHelper::PrimaryPageChanged(content::Page& page) {
+  const auto& url = page.GetMainDocument().GetLastCommittedURL();
   DCHECK(GetConfig()->ShouldNavigateInSidePanel(url));
   DCHECK(delegate_);
   delegate_->LastSearchURLUpdated(url);
diff --git a/chrome/browser/ui/side_search/side_search_side_contents_helper.h b/chrome/browser/ui/side_search/side_search_side_contents_helper.h
index ecf9afdc..4cbad0a 100644
--- a/chrome/browser/ui/side_search/side_search_side_contents_helper.h
+++ b/chrome/browser/ui/side_search/side_search_side_contents_helper.h
@@ -58,8 +58,7 @@
       content::NavigationHandle* handle);
 
   // content::WebContentsObserver:
-  void DidFinishNavigation(
-      content::NavigationHandle* navigation_handle) override;
+  void PrimaryPageChanged(content::Page& page) override;
   void PrimaryMainFrameRenderProcessGone(
       base::TerminationStatus status) override;
 
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc
index edd4c47a..217aaf1 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc
@@ -80,9 +80,10 @@
   // posted in CloseBubble() completes, but we need to fix references sooner.
   CloseBubble();
 
-  if (controller_)
-    controller_->OnBubbleClosed(closed_reason_);
-
+  if (controller_) {
+    controller_->OnBubbleClosed(
+        GetPaymentsBubbleClosedReasonFromWidget(GetWidget()));
+  }
   controller_ = nullptr;
 }
 
@@ -145,17 +146,18 @@
 
 void LocalCardMigrationBubbleViews::WindowClosing() {
   if (controller_) {
-    controller_->OnBubbleClosed(closed_reason_);
+    controller_->OnBubbleClosed(
+        GetPaymentsBubbleClosedReasonFromWidget(GetWidget()));
     controller_ = nullptr;
   }
 }
 
-void LocalCardMigrationBubbleViews::OnWidgetClosing(views::Widget* widget) {
-  LocationBarBubbleDelegateView::OnWidgetClosing(widget);
+void LocalCardMigrationBubbleViews::OnWidgetDestroying(views::Widget* widget) {
+  LocationBarBubbleDelegateView::OnWidgetDestroying(widget);
+  if (!widget->IsClosed())
+    return;
   DCHECK_NE(widget->closed_reason(),
             views::Widget::ClosedReason::kCancelButtonClicked);
-  closed_reason_ = GetPaymentsBubbleClosedReasonFromWidgetClosedReason(
-      widget->closed_reason());
 }
 
 LocalCardMigrationBubbleViews::~LocalCardMigrationBubbleViews() = default;
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.h b/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.h
index 9503f78..191d3f8 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.h
@@ -42,7 +42,7 @@
   void AddedToWidget() override;
   std::u16string GetWindowTitle() const override;
   void WindowClosing() override;
-  void OnWidgetClosing(views::Widget* widget) override;
+  void OnWidgetDestroying(views::Widget* widget) override;
 
  private:
   friend class LocalCardMigrationBrowserTest;
@@ -55,9 +55,6 @@
   // views::BubbleDialogDelegateView:
   void Init() override;
 
-  PaymentsBubbleClosedReason closed_reason_ =
-      PaymentsBubbleClosedReason::kUnknown;
-
   raw_ptr<LocalCardMigrationBubbleController> controller_;
 };
 
diff --git a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.cc
index 5af367b..09229f3 100644
--- a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.cc
@@ -46,8 +46,10 @@
 
 void OfferNotificationBubbleViews::Hide() {
   CloseBubble();
-  if (controller_)
-    controller_->OnBubbleClosed(closed_reason_);
+  if (controller_) {
+    controller_->OnBubbleClosed(
+        GetPaymentsBubbleClosedReasonFromWidget(GetWidget()));
+  }
   controller_ = nullptr;
 }
 
@@ -89,17 +91,18 @@
 
 void OfferNotificationBubbleViews::WindowClosing() {
   if (controller_) {
-    controller_->OnBubbleClosed(closed_reason_);
+    controller_->OnBubbleClosed(
+        GetPaymentsBubbleClosedReasonFromWidget(GetWidget()));
     controller_ = nullptr;
   }
 }
 
-void OfferNotificationBubbleViews::OnWidgetClosing(views::Widget* widget) {
-  LocationBarBubbleDelegateView::OnWidgetClosing(widget);
+void OfferNotificationBubbleViews::OnWidgetDestroying(views::Widget* widget) {
+  LocationBarBubbleDelegateView::OnWidgetDestroying(widget);
+  if (!widget->IsClosed())
+    return;
   DCHECK_NE(widget->closed_reason(),
             views::Widget::ClosedReason::kCancelButtonClicked);
-  closed_reason_ = GetPaymentsBubbleClosedReasonFromWidgetClosedReason(
-      widget->closed_reason());
 }
 
 void OfferNotificationBubbleViews::InitWithCardLinkedOfferContent() {
diff --git a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.h b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.h
index 1e6a371..d188ea0 100644
--- a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.h
@@ -48,7 +48,7 @@
   void AddedToWidget() override;
   std::u16string GetWindowTitle() const override;
   void WindowClosing() override;
-  void OnWidgetClosing(views::Widget* widget) override;
+  void OnWidgetDestroying(views::Widget* widget) override;
 
   void InitWithCardLinkedOfferContent();
   void InitWithPromoCodeOfferContent();
@@ -59,9 +59,6 @@
 
   void UpdateButtonTooltipsAndAccessibleNames();
 
-  PaymentsBubbleClosedReason closed_reason_ =
-      PaymentsBubbleClosedReason::kUnknown;
-
   raw_ptr<OfferNotificationBubbleController> controller_;
 
   raw_ptr<PromoCodeLabelButton> promo_code_label_button_ = nullptr;
diff --git a/chrome/browser/ui/views/autofill/payments/payments_view_util.cc b/chrome/browser/ui/views/autofill/payments/payments_view_util.cc
index a6d20cfa..c6f3087e 100644
--- a/chrome/browser/ui/views/autofill/payments/payments_view_util.cc
+++ b/chrome/browser/ui/views/autofill/payments/payments_view_util.cc
@@ -184,9 +184,13 @@
 BEGIN_METADATA(LegalMessageView, views::View)
 END_METADATA
 
-PaymentsBubbleClosedReason GetPaymentsBubbleClosedReasonFromWidgetClosedReason(
-    views::Widget::ClosedReason reason) {
-  switch (reason) {
+PaymentsBubbleClosedReason GetPaymentsBubbleClosedReasonFromWidget(
+    const views::Widget* widget) {
+  DCHECK(widget);
+  if (!widget->IsClosed())
+    return PaymentsBubbleClosedReason::kUnknown;
+
+  switch (widget->closed_reason()) {
     case views::Widget::ClosedReason::kUnspecified:
       return PaymentsBubbleClosedReason::kNotInteracted;
     case views::Widget::ClosedReason::kEscKeyPressed:
diff --git a/chrome/browser/ui/views/autofill/payments/payments_view_util.h b/chrome/browser/ui/views/autofill/payments/payments_view_util.h
index ab60d6d..ad9b2d8f 100644
--- a/chrome/browser/ui/views/autofill/payments/payments_view_util.h
+++ b/chrome/browser/ui/views/autofill/payments/payments_view_util.h
@@ -67,8 +67,8 @@
   ~LegalMessageView() override;
 };
 
-PaymentsBubbleClosedReason GetPaymentsBubbleClosedReasonFromWidgetClosedReason(
-    views::Widget::ClosedReason reason);
+PaymentsBubbleClosedReason GetPaymentsBubbleClosedReasonFromWidget(
+    const views::Widget* widget);
 
 // TODO(crbug.com/1249665): Replace all payments' progress bar usages with this.
 // Creates a progress bar with an explanatory text below.
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.cc
index 68d4866..818dbdc 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.cc
@@ -64,9 +64,10 @@
   // do that here. This will clear out |controller_|'s reference to |this|. Note
   // that WindowClosing() happens only after the _asynchronous_ Close() task
   // posted in CloseBubble() completes, but we need to fix references sooner.
-  if (controller_)
-    controller_->OnBubbleClosed(closed_reason_);
-
+  if (controller_) {
+    controller_->OnBubbleClosed(
+        GetPaymentsBubbleClosedReasonFromWidget(GetWidget()));
+  }
   controller_ = nullptr;
 }
 
@@ -100,17 +101,12 @@
 
 void SaveCardBubbleViews::WindowClosing() {
   if (controller_) {
-    controller_->OnBubbleClosed(closed_reason_);
+    controller_->OnBubbleClosed(
+        GetPaymentsBubbleClosedReasonFromWidget(GetWidget()));
     controller_ = nullptr;
   }
 }
 
-void SaveCardBubbleViews::OnWidgetClosing(views::Widget* widget) {
-  LocationBarBubbleDelegateView::OnWidgetDestroying(widget);
-  closed_reason_ = GetPaymentsBubbleClosedReasonFromWidgetClosedReason(
-      widget->closed_reason());
-}
-
 views::View* SaveCardBubbleViews::GetFootnoteViewForTesting() {
   return footnote_view_;
 }
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.h b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.h
index 8e96e5f..3d68a98 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.h
@@ -46,7 +46,6 @@
   void AddedToWidget() override;
   std::u16string GetWindowTitle() const override;
   void WindowClosing() override;
-  void OnWidgetClosing(views::Widget* widget) override;
 
   // Returns the footnote view, so it can be searched for clickable views.
   // Exists for testing (specifically, browsertests).
@@ -81,9 +80,6 @@
   raw_ptr<views::View> footnote_view_ = nullptr;
 
   raw_ptr<SaveCardBubbleController> controller_;  // Weak reference.
-
-  PaymentsBubbleClosedReason closed_reason_ =
-      PaymentsBubbleClosedReason::kUnknown;
 };
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/payments/virtual_card_enroll_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/virtual_card_enroll_bubble_views.cc
index 67fb976..39cf209 100644
--- a/chrome/browser/ui/views/autofill/payments/virtual_card_enroll_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/virtual_card_enroll_bubble_views.cc
@@ -62,8 +62,10 @@
 
 void VirtualCardEnrollBubbleViews::Hide() {
   CloseBubble();
-  if (controller_)
-    controller_->OnBubbleClosed(closed_reason_);
+  if (controller_) {
+    controller_->OnBubbleClosed(
+        GetPaymentsBubbleClosedReasonFromWidget(GetWidget()));
+  }
   controller_ = nullptr;
 }
 
@@ -105,17 +107,12 @@
 
 void VirtualCardEnrollBubbleViews::WindowClosing() {
   if (controller_) {
-    controller_->OnBubbleClosed(closed_reason_);
+    controller_->OnBubbleClosed(
+        GetPaymentsBubbleClosedReasonFromWidget(GetWidget()));
     controller_ = nullptr;
   }
 }
 
-void VirtualCardEnrollBubbleViews::OnWidgetClosing(views::Widget* widget) {
-  LocationBarBubbleDelegateView::OnWidgetDestroying(widget);
-  closed_reason_ = GetPaymentsBubbleClosedReasonFromWidgetClosedReason(
-      widget->closed_reason());
-}
-
 void VirtualCardEnrollBubbleViews::Init() {
   ChromeLayoutProvider* const provider = ChromeLayoutProvider::Get();
   SetLayoutManager(std::make_unique<views::BoxLayout>(
diff --git a/chrome/browser/ui/views/autofill/payments/virtual_card_enroll_bubble_views.h b/chrome/browser/ui/views/autofill/payments/virtual_card_enroll_bubble_views.h
index c76aebe8..fed184d 100644
--- a/chrome/browser/ui/views/autofill/payments/virtual_card_enroll_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/payments/virtual_card_enroll_bubble_views.h
@@ -49,7 +49,6 @@
   void AddedToWidget() override;
   std::u16string GetWindowTitle() const override;
   void WindowClosing() override;
-  void OnWidgetClosing(views::Widget* widget) override;
 
  protected:
   VirtualCardEnrollBubbleController* controller() const { return controller_; }
@@ -71,9 +70,6 @@
 
   raw_ptr<VirtualCardEnrollBubbleController> controller_;
 
-  PaymentsBubbleClosedReason closed_reason_ =
-      PaymentsBubbleClosedReason::kUnknown;
-
   raw_ptr<views::ImageView> card_network_icon_ = nullptr;
 
   base::WeakPtrFactory<VirtualCardEnrollBubbleViews> weak_ptr_factory_{this};
diff --git a/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.cc
index 9f06b82..e6dc978 100644
--- a/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.cc
@@ -65,8 +65,10 @@
 
 void VirtualCardManualFallbackBubbleViews::Hide() {
   CloseBubble();
-  if (controller_)
-    controller_->OnBubbleClosed(closed_reason_);
+  if (controller_) {
+    controller_->OnBubbleClosed(
+        GetPaymentsBubbleClosedReasonFromWidget(GetWidget()));
+  }
   controller_ = nullptr;
 }
 
@@ -186,20 +188,21 @@
 
 void VirtualCardManualFallbackBubbleViews::WindowClosing() {
   if (controller_) {
-    controller_->OnBubbleClosed(closed_reason_);
+    controller_->OnBubbleClosed(
+        GetPaymentsBubbleClosedReasonFromWidget(GetWidget()));
     controller_ = nullptr;
   }
 }
 
-void VirtualCardManualFallbackBubbleViews::OnWidgetClosing(
+void VirtualCardManualFallbackBubbleViews::OnWidgetDestroying(
     views::Widget* widget) {
-  LocationBarBubbleDelegateView::OnWidgetClosing(widget);
+  LocationBarBubbleDelegateView::OnWidgetDestroying(widget);
+  if (!widget->IsClosed())
+    return;
   DCHECK_NE(widget->closed_reason(),
             views::Widget::ClosedReason::kAcceptButtonClicked);
   DCHECK_NE(widget->closed_reason(),
             views::Widget::ClosedReason::kCancelButtonClicked);
-  closed_reason_ = GetPaymentsBubbleClosedReasonFromWidgetClosedReason(
-      widget->closed_reason());
 }
 
 std::unique_ptr<views::MdTextButton>
diff --git a/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.h b/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.h
index ce3da3c6..2e6e997 100644
--- a/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.h
@@ -51,7 +51,7 @@
   ui::ImageModel GetWindowIcon() override;
   std::u16string GetWindowTitle() const override;
   void WindowClosing() override;
-  void OnWidgetClosing(views::Widget* widget) override;
+  void OnWidgetDestroying(views::Widget* widget) override;
 
   // Creates a button for the |field|. If the button is pressed, the text of it
   // will be copied to the clipboard.
@@ -69,9 +69,6 @@
 
   raw_ptr<VirtualCardManualFallbackBubbleController> controller_;
 
-  PaymentsBubbleClosedReason closed_reason_ =
-      PaymentsBubbleClosedReason::kUnknown;
-
   // The map keeping the references to each button with card information text in
   // the bubble.
   std::map<VirtualCardManualFallbackBubbleField, views::MdTextButton*>
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
index 384fb42..ae62eaa 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.h"
 #include <algorithm>
 
+#include "base/bind.h"
 #include "chrome/browser/ui/bookmarks/bookmark_utils_desktop.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/layout_constants.h"
@@ -102,7 +103,9 @@
   // for the button.
   AddChildViewAt(
       std::make_unique<SavedTabGroupButton>(
-          group, page_navigator(),
+          group,
+          base::BindRepeating(&SavedTabGroupBar::page_navigator,
+                              base::Unretained(this)),
           base::BindRepeating(&SavedTabGroupBar::OnTabGroupButtonPressed,
                               base::Unretained(this), group.group_id),
           /*is_group_in_tabstrip*/ false, animations_enabled_),
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
index 7efe588..d92034d2 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
@@ -44,11 +44,12 @@
 constexpr float kCircleRadius = 14.0f;
 }  // namespace
 
-SavedTabGroupButton::SavedTabGroupButton(const SavedTabGroup& group,
-                                         content::PageNavigator* page_navigator,
-                                         PressedCallback callback,
-                                         bool is_group_in_tabstrip,
-                                         bool animations_enabled)
+SavedTabGroupButton::SavedTabGroupButton(
+    const SavedTabGroup& group,
+    base::RepeatingCallback<content::PageNavigator*()> page_navigator,
+    PressedCallback callback,
+    bool is_group_in_tabstrip,
+    bool animations_enabled)
     : MenuButton(std::move(callback), group.title),
       tab_group_color_id_(group.color),
       is_group_in_tabstrip_(is_group_in_tabstrip),
@@ -191,7 +192,7 @@
 
 SavedTabGroupButton::ContextMenuController::ContextMenuController(
     const std::vector<SavedTabGroupTab>& tabs,
-    content::PageNavigator* page_navigator)
+    base::RepeatingCallback<content::PageNavigator*()> page_navigator)
     : tabs_(tabs), page_navigator_(page_navigator) {}
 SavedTabGroupButton::ContextMenuController::~ContextMenuController() = default;
 
@@ -205,7 +206,9 @@
     dialog_model.AddMenuItem(
         ui::ImageModel::FromImage(tab.favicon), tab.tab_title,
         base::BindRepeating(
-            [](GURL url, content::PageNavigator* page_navigator,
+            [](GURL url,
+               base::RepeatingCallback<content::PageNavigator*()>
+                   page_navigator,
                int event_flags) {
               content::OpenURLParams params(
                   url, content::Referrer(),
@@ -213,7 +216,7 @@
                   ui::PAGE_TRANSITION_AUTO_BOOKMARK,
                   /*is_renderer_initiated=*/false,
                   /*started_from_context_menu=*/true);
-              page_navigator->OpenURL(params);
+              page_navigator.Run()->OpenURL(params);
             },
             tab.url, page_navigator_));
   }
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h
index e0ba3cc3..d932458 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h
@@ -29,11 +29,12 @@
 class SavedTabGroupButton : public views::MenuButton {
  public:
   METADATA_HEADER(SavedTabGroupButton);
-  SavedTabGroupButton(const SavedTabGroup& group,
-                      content::PageNavigator* page_navigator,
-                      PressedCallback callback,
-                      bool is_group_in_tabstrip,
-                      bool animations_enabled = true);
+  SavedTabGroupButton(
+      const SavedTabGroup& group,
+      base::RepeatingCallback<content::PageNavigator*()> page_navigator,
+      PressedCallback callback,
+      bool is_group_in_tabstrip,
+      bool animations_enabled = true);
 
   SavedTabGroupButton(const SavedTabGroupButton&) = delete;
   SavedTabGroupButton& operator=(const SavedTabGroupButton&) = delete;
@@ -59,8 +60,9 @@
   // when a tab group is updated.
   class ContextMenuController : public views::ContextMenuController {
    public:
-    ContextMenuController(const std::vector<SavedTabGroupTab>& tabs,
-                          content::PageNavigator* page_navigator);
+    ContextMenuController(
+        const std::vector<SavedTabGroupTab>& tabs,
+        base::RepeatingCallback<content::PageNavigator*()> page_navigator);
     ~ContextMenuController() override;
 
    private:
@@ -78,8 +80,8 @@
     // title, url, and favicon.
     const std::vector<SavedTabGroupTab> tabs_;
 
-    // The page navigator used to open the context menu items into a new tab.
-    content::PageNavigator* page_navigator_;
+    // A callback used to fetch the current PageNavigator used to open URLs.
+    base::RepeatingCallback<content::PageNavigator*()> page_navigator_;
   };
 
   // The animations for button movement.
diff --git a/chrome/browser/ui/views/crostini/crostini_expired_container_warning_view.cc b/chrome/browser/ui/views/crostini/crostini_expired_container_warning_view.cc
index cb574728..9cc0a521 100644
--- a/chrome/browser/ui/views/crostini/crostini_expired_container_warning_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_expired_container_warning_view.cc
@@ -20,17 +20,14 @@
 
 }  // namespace
 
-void CrostiniExpiredContainerWarningView::Show(
-    Profile* profile,
-    std::vector<base::OnceClosure> callbacks) {
+void CrostiniExpiredContainerWarningView::Show(Profile* profile,
+                                               base::OnceClosure callback) {
   if (g_crostini_expired_container_warning_view) {
-    for (auto&& callback : callbacks) {
-      g_crostini_expired_container_warning_view->callbacks_.push_back(
-          std::move(callback));
-    }
+    g_crostini_expired_container_warning_view->callbacks_.push_back(
+        std::move(callback));
   } else {
     g_crostini_expired_container_warning_view =
-        new CrostiniExpiredContainerWarningView(profile, std::move(callbacks));
+        new CrostiniExpiredContainerWarningView(profile, std::move(callback));
     CreateDialogWidget(g_crostini_expired_container_warning_view, nullptr,
                        nullptr);
   }
@@ -41,10 +38,10 @@
 
 CrostiniExpiredContainerWarningView::CrostiniExpiredContainerWarningView(
     Profile* profile,
-    std::vector<base::OnceClosure> callbacks)
-    : profile_(profile),
-      callbacks_(std::move(callbacks)),
-      weak_ptr_factory_(this) {
+    base::OnceClosure callback)
+    : profile_(profile), weak_ptr_factory_(this) {
+  callbacks_.push_back(std::move(callback));
+
   // Make the dialog modal to force the user to make a decision.
   SetModalType(ui::MODAL_TYPE_SYSTEM);
 
diff --git a/chrome/browser/ui/views/crostini/crostini_expired_container_warning_view.h b/chrome/browser/ui/views/crostini/crostini_expired_container_warning_view.h
index a728ae8..573c53d 100644
--- a/chrome/browser/ui/views/crostini/crostini_expired_container_warning_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_expired_container_warning_view.h
@@ -21,11 +21,11 @@
  public:
   METADATA_HEADER(CrostiniExpiredContainerWarningView);
 
-  static void Show(Profile* profile, std::vector<base::OnceClosure> callbacks);
+  static void Show(Profile* profile, base::OnceClosure callback);
 
  private:
   CrostiniExpiredContainerWarningView(Profile* profile,
-                                      std::vector<base::OnceClosure> callbacks);
+                                      base::OnceClosure callback);
   ~CrostiniExpiredContainerWarningView() override;
 
   Profile* const profile_;  // Not owned.
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view.cc b/chrome/browser/ui/views/intent_picker_bubble_view.cc
index 1ecdcac..3b45ab91 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view.cc
+++ b/chrome/browser/ui/views/intent_picker_bubble_view.cc
@@ -12,8 +12,10 @@
 #include "base/i18n/rtl.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/intent_helper/intent_picker_constants.h"
+#include "chrome/browser/apps/intent_helper/intent_picker_features.h"
 #include "chrome/browser/apps/intent_helper/intent_picker_helpers.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/sharing/click_to_call/click_to_call_ui_controller.h"
@@ -46,6 +48,10 @@
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/grid_layout.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "ui/chromeos/devicetype_utils.h"
+#endif
+
 namespace {
 
 constexpr char kInvalidLaunchName[] = "";
@@ -189,7 +195,9 @@
         IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TITLE_LABEL);
   }
 
-  return l10n_util::GetStringUTF16(IDS_INTENT_PICKER_BUBBLE_VIEW_OPEN_WITH);
+  return l10n_util::GetStringUTF16(
+      use_grid_view_ ? IDS_INTENT_CHIP_OPEN_IN_APP
+                     : IDS_INTENT_PICKER_BUBBLE_VIEW_OPEN_WITH);
 }
 
 IntentPickerBubbleView::IntentPickerBubbleView(
@@ -204,7 +212,9 @@
     : LocationBarBubbleDelegateView(anchor_view, web_contents),
       intent_picker_cb_(std::move(intent_picker_cb)),
       app_info_(std::move(app_info)),
-      show_stay_in_chrome_(show_stay_in_chrome),
+      use_grid_view_(apps::features::LinkCapturingUiUpdateEnabled() &&
+                     bubble_type == BubbleType::kLinkCapturing),
+      show_stay_in_chrome_(show_stay_in_chrome && !use_grid_view_),
       show_remember_selection_(show_remember_selection),
       bubble_type_(bubble_type),
       initiating_origin_(initiating_origin) {
@@ -321,18 +331,42 @@
       !initiating_origin_->IsSameOriginWith(
           web_contents()->GetMainFrame()->GetLastCommittedOrigin());
 
+  auto leading_content_type = use_grid_view_
+                                  ? views::DialogContentType::kText
+                                  : views::DialogContentType::kControl;
+  auto trailing_content_type = (show_origin && !show_remember_selection_)
+                                   ? views::DialogContentType::kText
+                                   : views::DialogContentType::kControl;
   const auto* provider = ChromeLayoutProvider::Get();
-  auto insets = provider->GetDialogInsetsForContentType(
-      views::DialogContentType::kControl,
-      (show_origin && !show_remember_selection_)
-          ? views::DialogContentType::kText
-          : views::DialogContentType::kControl);
+  auto insets = provider->GetDialogInsetsForContentType(leading_content_type,
+                                                        trailing_content_type);
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical,
       gfx::Insets::TLBR(insets.top(), 0, insets.bottom(), 0),
       provider->GetDistanceMetric(views::DISTANCE_UNRELATED_CONTROL_VERTICAL)));
   insets = gfx::Insets::TLBR(0, insets.left(), 0, insets.right());
 
+  const int kMaxDialogWidth =
+      provider->GetDistanceMetric(views::DISTANCE_BUBBLE_PREFERRED_WIDTH);
+
+  if (use_grid_view_) {
+#if BUILDFLAG(IS_CHROMEOS)
+    auto subtitle_string = ui::SubstituteChromeOSDeviceType(
+        IDS_INTENT_PICKER_SELECT_AN_APP_SUBTITLE);
+#else
+    auto subtitle_string = l10n_util::GetStringUTF16(
+        IDS_INTENT_PICKER_SELECT_AN_APP_GENERIC_SUBTITLE);
+#endif
+    auto* subtitle = AddChildView(std::make_unique<views::Label>(
+        subtitle_string, views::style::TextContext::CONTEXT_DIALOG_BODY_TEXT,
+        views::style::STYLE_PRIMARY));
+    subtitle->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    subtitle->SetAllowCharacterBreak(true);
+    subtitle->SetMultiLine(true);
+    subtitle->SetProperty(views::kMarginsKey, insets);
+    subtitle->SetMaximumWidth(kMaxDialogWidth - insets.width());
+  }
+
   scroll_view_ = AddChildView(std::move(scroll_view));
 
   if (show_origin) {
@@ -347,13 +381,14 @@
     label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     label->SetAllowCharacterBreak(true);
     label->SetMultiLine(true);
-    constexpr int kMaxDialogWidth = 320;
     label->SetMaximumWidth(kMaxDialogWidth - insets.width());
     label->SetProperty(views::kMarginsKey, insets);
   }
 
   if (show_remember_selection_) {
-    AddChildView(std::make_unique<views::Separator>());
+    if (!use_grid_view_) {
+      AddChildView(std::make_unique<views::Separator>());
+    }
 
     remember_selection_checkbox_ = AddChildView(
         std::make_unique<views::Checkbox>(l10n_util::GetStringUTF16(
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view.h b/chrome/browser/ui/views/intent_picker_bubble_view.h
index ee21a595..d990dc9e 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view.h
+++ b/chrome/browser/ui/views/intent_picker_bubble_view.h
@@ -225,6 +225,10 @@
 
   raw_ptr<views::Checkbox> remember_selection_checkbox_ = nullptr;
 
+  // When true, enables an alternate layout which presents apps as a grid
+  // instead of a list.
+  const bool use_grid_view_;
+
   // Tells whether 'Stay in Chrome' button should be shown or hidden.
   const bool show_stay_in_chrome_;
 
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc b/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc
index 9a46def..469458d 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc
@@ -5,6 +5,7 @@
 #include "base/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
+#include "chrome/browser/apps/intent_helper/intent_picker_features.h"
 #include "chrome/browser/apps/intent_helper/intent_picker_helpers.h"
 #include "chrome/browser/favicon/favicon_utils.h"
 #include "chrome/browser/ui/browser.h"
@@ -14,6 +15,7 @@
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
 #include "chrome/browser/ui/views/intent_picker_bubble_view.h"
+#include "chrome/browser/ui/views/location_bar/intent_chip_button.h"
 #include "chrome/browser/ui/views/location_bar/intent_picker_view.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
@@ -417,10 +419,6 @@
 
   // DialogBrowserTest:
   void ShowUi(const std::string& name) override {
-    PageActionIconView* anchor =
-        BrowserView::GetBrowserViewForBrowser(browser())
-            ->toolbar_button_provider()
-            ->GetPageActionIconView(PageActionIconType::kIntentPicker);
     std::vector<apps::IntentPickerAppInfo> app_info;
     const auto add_entry = [&app_info](const std::string& str) {
       app_info.emplace_back(
@@ -436,13 +434,41 @@
     add_entry("c");
     add_entry("d");
     IntentPickerBubbleView::ShowBubble(
-        anchor, anchor, IntentPickerBubbleView::BubbleType::kLinkCapturing,
+        BrowserView::GetBrowserViewForBrowser(browser())->GetLocationBarView(),
+        GetAnchorButton(), IntentPickerBubbleView::BubbleType::kLinkCapturing,
         browser()->tab_strip_model()->GetActiveWebContents(),
         std::move(app_info), true, true,
         url::Origin::Create(GURL("https://c.com")), base::DoNothing());
   }
+
+ private:
+  virtual views::Button* GetAnchorButton() {
+    return BrowserView::GetBrowserViewForBrowser(browser())
+        ->toolbar_button_provider()
+        ->GetPageActionIconView(PageActionIconType::kIntentPicker);
+  }
 };
 
 IN_PROC_BROWSER_TEST_F(IntentPickerDialogTest, InvokeUi_default) {
   ShowAndVerifyUi();
 }
+
+class IntentPickerDialogGridViewTest : public IntentPickerDialogTest {
+ public:
+  IntentPickerDialogGridViewTest() {
+    feature_list_.InitAndEnableFeature(apps::features::kLinkCapturingUiUpdate);
+  }
+
+ private:
+  views::Button* GetAnchorButton() override {
+    return BrowserView::GetBrowserViewForBrowser(browser())
+        ->toolbar_button_provider()
+        ->GetIntentChipButton();
+  }
+
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(IntentPickerDialogGridViewTest, InvokeUi_default) {
+  ShowAndVerifyUi();
+}
diff --git a/chrome/browser/ui/views/location_bar/intent_chip_button.cc b/chrome/browser/ui/views/location_bar/intent_chip_button.cc
index e0bea8e..62137ff 100644
--- a/chrome/browser/ui/views/location_bar/intent_chip_button.cc
+++ b/chrome/browser/ui/views/location_bar/intent_chip_button.cc
@@ -28,12 +28,12 @@
                                             base::Unretained(this)),
                         vector_icons::kOpenInNewIcon,
                         vector_icons::kOpenInNewIcon,
-                        l10n_util::GetStringUTF16(IDS_INTENT_CHIP_LABEL),
+                        l10n_util::GetStringUTF16(IDS_INTENT_CHIP_OPEN_IN_APP),
                         true),
       browser_(browser),
       delegate_(delegate) {
   SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
-  SetTooltipText(l10n_util::GetStringUTF16(IDS_INTENT_CHIP_LABEL));
+  SetTooltipText(l10n_util::GetStringUTF16(IDS_INTENT_CHIP_OPEN_IN_APP));
   SetProperty(views::kElementIdentifierKey, kIntentChipElementId);
 }
 
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view.cc b/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
index 783ca2d..664a406 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
@@ -22,6 +22,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/image/canvas_image_source.h"
+#include "ui/gfx/image/image_skia_operations.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/button/md_text_button.h"
 #include "ui/views/controls/image_view.h"
@@ -149,19 +150,22 @@
   std::u16string title = l10n_util::GetStringFUTF16(
       IDS_ACCOUNT_SELECTION_SHEET_TITLE_EXPLICIT,
       base::UTF8ToUTF16(rp_etld_plus_one), idp_etld_plus_one_);
-  gfx::ImageSkia icon =
-      gfx::ImageSkia::CreateFrom1xBitmap(skia::ImageOperations::Resize(
-          idp_metadata.brand_icon, skia::ImageOperations::RESIZE_LANCZOS3,
-          kDesiredIconSize, kDesiredIconSize));
-  header_view_ = AddChildView(CreateHeaderView(icon, title));
+  header_view_ = AddChildView(CreateHeaderView(title));
   AddChildView(std::make_unique<views::Separator>());
   AddChildView(CreateAccountChooser(accounts));
+
+  image_fetcher::ImageFetcherParams params(kTrafficAnnotation,
+                                           kImageFetcherUmaClient);
+  image_fetcher_->FetchImage(
+      idp_metadata.brand_icon_url,
+      base::BindOnce(&AccountSelectionBubbleView::OnBrandImageFetched,
+                     weak_ptr_factory_.GetWeakPtr()),
+      std::move(params));
 }
 
 AccountSelectionBubbleView::~AccountSelectionBubbleView() = default;
 
 std::unique_ptr<views::View> AccountSelectionBubbleView::CreateHeaderView(
-    gfx::ImageSkia icon,
     const std::u16string& title) {
   auto header = std::make_unique<views::View>();
   // Do not use a top margin as it has already been set in the bubble.
@@ -171,10 +175,9 @@
 
   // Add the icon.
   auto image_view = std::make_unique<views::ImageView>();
-  image_view->SetImage(icon);
   image_view->SetProperty(views::kMarginsKey,
                           gfx::Insets().set_right(kLeftRightPadding));
-  header->AddChildView(image_view.release());
+  bubble_icon_view_ = header->AddChildView(image_view.release());
 
   // Add the title.
   title_label_ = header->AddChildView(std::make_unique<views::Label>(
@@ -420,6 +423,18 @@
   image_view->SetImage(avatar);
 }
 
+void AccountSelectionBubbleView::OnBrandImageFetched(
+    const gfx::Image& image,
+    const image_fetcher::RequestMetadata& metadata) {
+  if (image.Width() == image.Height() &&
+      image.Width() >= AccountSelectionView::GetBrandIconMinimumSize()) {
+    gfx::ImageSkia resized_image = gfx::ImageSkiaOperations::CreateResizedImage(
+        image.AsImageSkia(), skia::ImageOperations::RESIZE_LANCZOS3,
+        gfx::Size(kDesiredIconSize, kDesiredIconSize));
+    bubble_icon_view_->SetImage(resized_image);
+  }
+}
+
 void AccountSelectionBubbleView::OnLinkClicked(const GURL& gurl) {
   DCHECK(tab_strip_model_);
   // Add a tab for the URL at the end of the tab strip, in the foreground.
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view.h b/chrome/browser/ui/views/webid/account_selection_bubble_view.h
index 2f28f24..96ea1571 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view.h
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view.h
@@ -15,6 +15,11 @@
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/view.h"
 
+namespace views {
+class ImageView;
+class Label;
+}  // namespace views
+
 // Bubble dialog that is used in the FedCM flow. It creates a dialog with an
 // account chooser for the user, and it changes the content of that dialog as
 // user moves through the FedCM flow steps.
@@ -37,8 +42,7 @@
  private:
   // Returns a View containing the logo of the identity provider and the title
   // of the bubble, properly formatted.
-  std::unique_ptr<views::View> CreateHeaderView(gfx::ImageSkia icon,
-                                                const std::u16string& title);
+  std::unique_ptr<views::View> CreateHeaderView(const std::u16string& title);
 
   void CloseBubble();
 
@@ -71,6 +75,10 @@
                              const gfx::Image& image,
                              const image_fetcher::RequestMetadata& metadata);
 
+  // Called when the brand icon image has beend downloaded.
+  void OnBrandImageFetched(const gfx::Image& image,
+                           const image_fetcher::RequestMetadata& metadata);
+
   // Called when the user clicks on the privacy policy or terms of service URL.
   // Opens the URL in a new tab.
   void OnLinkClicked(const GURL& gurl);
@@ -115,6 +123,9 @@
   // View containing the logo of the identity provider and the title.
   views::View* header_view_{nullptr};
 
+  // View containing the bubble icon.
+  views::ImageView* bubble_icon_view_{nullptr};
+
   // View containing the bubble title.
   views::Label* title_label_{nullptr};
 
diff --git a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.cc b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.cc
index b3f0f15..5f179e1 100644
--- a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.cc
@@ -87,7 +87,9 @@
 }
 
 void CrostiniUpgraderPageHandler::Launch() {
-  std::move(launch_callback_).Run(restart_required_);
+  if (launch_callback_) {
+    std::move(launch_callback_).Run(restart_required_);
+  }
 }
 
 void CrostiniUpgraderPageHandler::CancelBeforeStart() {
@@ -95,16 +97,13 @@
       crostini::UpgradeDialogEvent::kNotStarted);
   restart_required_ = false;
   upgrader_ui_delegate_->CancelBeforeStart();
-  if (launch_callback_) {
-    // Running launch closure - no upgrade wanted, no need to restart crostini.
-    Launch();
-  }
+
+  // Running launch closure - no upgrade wanted, no need to restart crostini.
+  Launch();
 }
 
 void CrostiniUpgraderPageHandler::OnPageClosed() {
-  if (launch_callback_) {
-    Launch();
-  }
+  Launch();
   if (on_page_closed_) {
     std::move(on_page_closed_).Run();
   }
diff --git a/chrome/browser/ui/webui/chromeos/login/update_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/update_screen_handler.cc
index e9cb505..6581df1 100644
--- a/chrome/browser/ui/webui/chromeos/login/update_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/update_screen_handler.cc
@@ -9,7 +9,6 @@
 #include "ash/constants/ash_features.h"
 #include "base/values.h"
 #include "chrome/browser/ash/login/oobe_screen.h"
-#include "chrome/browser/ash/login/screens/update_screen.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/login/localized_values_builder.h"
@@ -17,8 +16,6 @@
 
 namespace chromeos {
 
-constexpr StaticOobeScreenId UpdateView::kScreenId;
-
 namespace {
 
 constexpr bool strings_equal(char const* a, char const* b) {
@@ -42,9 +39,7 @@
 
 }  // namespace
 
-UpdateScreenHandler::UpdateScreenHandler() : BaseScreenHandler(kScreenId) {
-  set_user_acted_method_path_deprecated("login.UpdateScreen.userActed");
-}
+UpdateScreenHandler::UpdateScreenHandler() : BaseScreenHandler(kScreenId) {}
 
 UpdateScreenHandler::~UpdateScreenHandler() = default;
 
@@ -54,35 +49,25 @@
   ShowInWebUI(std::move(data));
 }
 
-void UpdateScreenHandler::Hide() {}
-
-void UpdateScreenHandler::Bind(UpdateScreen* screen) {
-  BaseScreenHandler::SetBaseScreenDeprecated(screen);
-}
-
-void UpdateScreenHandler::Unbind() {
-  BaseScreenHandler::SetBaseScreenDeprecated(nullptr);
-}
-
 void UpdateScreenHandler::SetUpdateState(UpdateView::UIState value) {
   switch (value) {
     case UpdateView::UIState::kCheckingForUpdate:
-      CallJS("login.UpdateScreen.setUpdateState", kCheckingForUpdate);
+      CallExternalAPI("setUpdateState", kCheckingForUpdate);
       break;
     case UpdateView::UIState::kUpdateInProgress:
-      CallJS("login.UpdateScreen.setUpdateState", kUpdateInProgress);
+      CallExternalAPI("setUpdateState", kUpdateInProgress);
       break;
     case UpdateView::UIState::kRestartInProgress:
-      CallJS("login.UpdateScreen.setUpdateState", kRestartInProgress);
+      CallExternalAPI("setUpdateState", kRestartInProgress);
       break;
     case UpdateView::UIState::kManualReboot:
-      CallJS("login.UpdateScreen.setUpdateState", kManualReboot);
+      CallExternalAPI("setUpdateState", kManualReboot);
       break;
     case UpdateView::UIState::kCellularPermission:
-      CallJS("login.UpdateScreen.setUpdateState", kCellularPermission);
+      CallExternalAPI("setUpdateState", kCellularPermission);
       break;
     case UpdateView::UIState::kOptOutInfo:
-      CallJS("login.UpdateScreen.setUpdateState", kOptOutInfo);
+      CallExternalAPI("setUpdateState", kOptOutInfo);
       break;
   }
 }
@@ -91,20 +76,20 @@
     int percent,
     const std::u16string& percent_message,
     const std::u16string& timeleft_message) {
-  CallJS("login.UpdateScreen.setUpdateStatus", percent, percent_message,
-         timeleft_message);
+  CallExternalAPI("setUpdateStatus", percent, percent_message,
+                  timeleft_message);
 }
 
 void UpdateScreenHandler::ShowLowBatteryWarningMessage(bool value) {
-  CallJS("login.UpdateScreen.showLowBatteryWarningMessage", value);
+  CallExternalAPI("showLowBatteryWarningMessage", value);
 }
 
 void UpdateScreenHandler::SetAutoTransition(bool value) {
-  CallJS("login.UpdateScreen.setAutoTransition", value);
+  CallExternalAPI("setAutoTransition", value);
 }
 
 void UpdateScreenHandler::SetCancelUpdateShortcutEnabled(bool value) {
-  CallJS("login.UpdateScreen.setCancelUpdateShortcutEnabled", value);
+  CallExternalAPI("setCancelUpdateShortcutEnabled", value);
 }
 
 void UpdateScreenHandler::DeclareLocalizedValues(
diff --git a/chrome/browser/ui/webui/chromeos/login/update_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/update_screen_handler.h
index d6e60bc9..bb84b52 100644
--- a/chrome/browser/ui/webui/chromeos/login/update_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/update_screen_handler.h
@@ -25,7 +25,8 @@
   // The screen name must never change. It's stored into local state as a
   // pending screen during OOBE update. So the value should be the same between
   // versions.
-  constexpr static StaticOobeScreenId kScreenId{"oobe-update"};
+  inline constexpr static StaticOobeScreenId kScreenId{"oobe-update",
+                                                       "UpdateScreen"};
 
   enum class UIState {
     kCheckingForUpdate = 0,
@@ -41,15 +42,6 @@
   // Shows the contents of the screen.
   virtual void Show(bool is_opt_out_enabled) = 0;
 
-  // Hides the contents of the screen.
-  virtual void Hide() = 0;
-
-  // Binds `screen` to the view.
-  virtual void Bind(ash::UpdateScreen* screen) = 0;
-
-  // Unbinds the screen from the view.
-  virtual void Unbind() = 0;
-
   virtual void SetUpdateState(UIState value) = 0;
   virtual void SetUpdateStatus(int percent,
                                const std::u16string& percent_message,
@@ -73,9 +65,6 @@
  private:
   // UpdateView:
   void Show(bool is_opt_out_enabled) override;
-  void Hide() override;
-  void Bind(ash::UpdateScreen* screen) override;
-  void Unbind() override;
 
   void SetUpdateState(UpdateView::UIState value) override;
   void SetUpdateStatus(int percent,
diff --git a/chrome/browser/ui/webui/flags/flags_ui_handler.cc b/chrome/browser/ui/webui/flags/flags_ui_handler.cc
index f19cb7a..cf6ec1c 100644
--- a/chrome/browser/ui/webui/flags/flags_ui_handler.cc
+++ b/chrome/browser/ui/webui/flags/flags_ui_handler.cc
@@ -30,29 +30,29 @@
 FlagsUIHandler::~FlagsUIHandler() {}
 
 void FlagsUIHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       flags_ui::kRequestExperimentalFeatures,
       base::BindRepeating(&FlagsUIHandler::HandleRequestExperimentalFeatures,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       flags_ui::kEnableExperimentalFeature,
       base::BindRepeating(
           &FlagsUIHandler::HandleEnableExperimentalFeatureMessage,
           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       flags_ui::kSetOriginListFlag,
       base::BindRepeating(&FlagsUIHandler::HandleSetOriginListFlagMessage,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       flags_ui::kRestartBrowser,
       base::BindRepeating(&FlagsUIHandler::HandleRestartBrowser,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       flags_ui::kResetAllFlags,
       base::BindRepeating(&FlagsUIHandler::HandleResetAllFlags,
                           base::Unretained(this)));
 #if BUILDFLAG(IS_CHROMEOS)
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       flags_ui::kCrosUrlFlagsRedirect,
       base::BindRepeating(&FlagsUIHandler::HandleCrosUrlFlagsRedirect,
                           base::Unretained(this)));
@@ -69,9 +69,9 @@
 }
 
 void FlagsUIHandler::HandleRequestExperimentalFeatures(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   AllowJavascript();
-  const base::Value& callback_id = args->GetListDeprecated()[0];
+  const base::Value& callback_id = args[0];
 
   experimental_features_callback_id_ = callback_id.GetString();
   // Bail out if the handler hasn't been initialized yet. The request will be
@@ -132,20 +132,18 @@
 }
 
 void FlagsUIHandler::HandleEnableExperimentalFeatureMessage(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   DCHECK(flags_storage_);
-  DCHECK_EQ(2u, args->GetListDeprecated().size());
-  if (args->GetListDeprecated().size() != 2)
+  DCHECK_EQ(2u, args.size());
+  if (args.size() != 2)
     return;
 
-  if (!args->GetListDeprecated()[0].is_string() ||
-      !args->GetListDeprecated()[1].is_string()) {
+  if (!args[0].is_string() || !args[1].is_string()) {
     NOTREACHED();
     return;
   }
-  const std::string& entry_internal_name =
-      args->GetListDeprecated()[0].GetString();
-  const std::string& enable_str = args->GetListDeprecated()[1].GetString();
+  const std::string& entry_internal_name = args[0].GetString();
+  const std::string& enable_str = args[1].GetString();
   if (entry_internal_name.empty()) {
     NOTREACHED();
     return;
@@ -156,21 +154,19 @@
 }
 
 void FlagsUIHandler::HandleSetOriginListFlagMessage(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   DCHECK(flags_storage_);
-  if (args->GetListDeprecated().size() != 2) {
+  if (args.size() != 2) {
     NOTREACHED();
     return;
   }
 
-  if (!args->GetListDeprecated()[0].is_string() ||
-      !args->GetListDeprecated()[1].is_string()) {
+  if (!args[0].is_string() || !args[1].is_string()) {
     NOTREACHED();
     return;
   }
-  const std::string& entry_internal_name =
-      args->GetListDeprecated()[0].GetString();
-  const std::string& value_str = args->GetListDeprecated()[1].GetString();
+  const std::string& entry_internal_name = args[0].GetString();
+  const std::string& value_str = args[1].GetString();
   if (entry_internal_name.empty()) {
     NOTREACHED();
     return;
@@ -180,7 +176,7 @@
                                  flags_storage_.get());
 }
 
-void FlagsUIHandler::HandleRestartBrowser(const base::ListValue* args) {
+void FlagsUIHandler::HandleRestartBrowser(const base::Value::List& args) {
   DCHECK(flags_storage_);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   // On Chrome OS be less intrusive and restart inside the user session after
@@ -201,13 +197,13 @@
   chrome::AttemptRestart();
 }
 
-void FlagsUIHandler::HandleResetAllFlags(const base::ListValue* args) {
+void FlagsUIHandler::HandleResetAllFlags(const base::Value::List& args) {
   DCHECK(flags_storage_);
   about_flags::ResetAllFlags(flags_storage_.get());
 }
 
 #if BUILDFLAG(IS_CHROMEOS)
-void FlagsUIHandler::HandleCrosUrlFlagsRedirect(const base::ListValue* args) {
+void FlagsUIHandler::HandleCrosUrlFlagsRedirect(const base::Value::List& args) {
   about_flags::CrosUrlFlagsRedirect();
 }
 #endif
diff --git a/chrome/browser/ui/webui/flags/flags_ui_handler.h b/chrome/browser/ui/webui/flags/flags_ui_handler.h
index 88ccc45..7466c79 100644
--- a/chrome/browser/ui/webui/flags/flags_ui_handler.h
+++ b/chrome/browser/ui/webui/flags/flags_ui_handler.h
@@ -43,23 +43,23 @@
   void RegisterMessages() override;
 
   // Callback for the "requestExperimentFeatures" message.
-  void HandleRequestExperimentalFeatures(const base::ListValue* args);
+  void HandleRequestExperimentalFeatures(const base::Value::List& args);
 
   // Callback for the "enableExperimentalFeature" message.
-  void HandleEnableExperimentalFeatureMessage(const base::ListValue* args);
+  void HandleEnableExperimentalFeatureMessage(const base::Value::List& args);
 
   // Callback for the "setOriginListFlag" message.
-  void HandleSetOriginListFlagMessage(const base::ListValue* args);
+  void HandleSetOriginListFlagMessage(const base::Value::List& args);
 
   // Callback for the "restartBrowser" message. Restores all tabs on restart.
-  void HandleRestartBrowser(const base::ListValue* args);
+  void HandleRestartBrowser(const base::Value::List& args);
 
   // Callback for the "resetAllFlags" message.
-  void HandleResetAllFlags(const base::ListValue* args);
+  void HandleResetAllFlags(const base::Value::List& args);
 
 #if BUILDFLAG(IS_CHROMEOS)
   // Callback for the "CrosUrlFlagsRedirect" message.
-  void HandleCrosUrlFlagsRedirect(const base::ListValue* args);
+  void HandleCrosUrlFlagsRedirect(const base::Value::List& args);
 #endif
 
  private:
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 44570a5..88af088 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1652378338-a18056b387cca3a638946b0ba7a2b0dd37455517.profdata
+chrome-linux-main-1652421211-461d5e9353eae435bc30731d562e970c9cf9fce0.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 8370fdad..53cec6c 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1652378338-a08fb678b5cbbf3150b78f70ef88b312adbc4d11.profdata
+chrome-mac-arm-main-1652421211-aa4d657dd25f207e5570c14695bada9db27c159f.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 3c3384c0..522d631b 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1652378338-eb7adac901d0ba707d1a4bfe147abeec38f6fa05.profdata
+chrome-mac-main-1652421211-24d2c875e799cb199befb21567e6345b216d350f.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 60a7036b1..1715f28 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1652378338-ad1f26558bf965da6d11e6e28f76e1abe2e167e6.profdata
+chrome-win32-main-1652421211-9625d35061f64c69d96dd5f7fbd242157bfc9a32.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 4534cd7..34b5373c 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1652378338-a8016e3743c83e5912aabafca6ce7011e87a376f.profdata
+chrome-win64-main-1652421211-3a513d2ec426175c141901038e799c71c9137b3d.profdata
diff --git a/chrome/common/extensions/api/file_manager_private.idl b/chrome/common/extensions/api/file_manager_private.idl
index 8f6e711c4..e026cd5 100644
--- a/chrome/common/extensions/api/file_manager_private.idl
+++ b/chrome/common/extensions/api/file_manager_private.idl
@@ -1301,7 +1301,7 @@
 
   // Unmounts a mounted resource.
   // |volumeId| An ID of the volume.
-  static void removeMount(DOMString volumeId);
+  static void removeMount(DOMString volumeId, SimpleCallback callback);
 
   // Get the list of mounted volumes.
   // |callback|
diff --git a/chrome/services/sharing/nearby/platform/scheduled_executor.cc b/chrome/services/sharing/nearby/platform/scheduled_executor.cc
index 6c10ccb4..828e125 100644
--- a/chrome/services/sharing/nearby/platform/scheduled_executor.cc
+++ b/chrome/services/sharing/nearby/platform/scheduled_executor.cc
@@ -107,11 +107,11 @@
   timer_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&ScheduledExecutor::StartTimerWithId,
-                     base::Unretained(this), id,
+                     timer_task_runner_weak_factory_.GetWeakPtr(), id,
                      base::Microseconds(absl::ToInt64Microseconds(duration))));
 
-  return std::make_shared<CancelableTask>(
-      base::BindOnce(&TryCancelTask, weak_factory_.GetWeakPtr(), id));
+  return std::make_shared<CancelableTask>(base::BindOnce(
+      &TryCancelTask, cancelable_task_weak_factory_.GetWeakPtr(), id));
 }
 
 void ScheduledExecutor::StartTimerWithId(const base::UnguessableToken& id,
@@ -125,9 +125,10 @@
     return;
 
   it->second->timer.SetTaskRunner(timer_task_runner_);
-  it->second->timer.Start(FROM_HERE, delay,
-                          base::BindOnce(&ScheduledExecutor::RunTaskWithId,
-                                         base::Unretained(this), id));
+  it->second->timer.Start(
+      FROM_HERE, delay,
+      base::BindOnce(&ScheduledExecutor::RunTaskWithId,
+                     timer_task_runner_weak_factory_.GetWeakPtr(), id));
 }
 
 void ScheduledExecutor::StopTimerWithIdAndDeleteTaskEntry(
@@ -177,7 +178,7 @@
   timer_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&ScheduledExecutor::StopTimerWithIdAndDeleteTaskEntry,
-                     base::Unretained(this), id));
+                     timer_task_runner_weak_factory_.GetWeakPtr(), id));
   return true;
 }
 
diff --git a/chrome/services/sharing/nearby/platform/scheduled_executor.h b/chrome/services/sharing/nearby/platform/scheduled_executor.h
index 9ef9d50..979d7924 100644
--- a/chrome/services/sharing/nearby/platform/scheduled_executor.h
+++ b/chrome/services/sharing/nearby/platform/scheduled_executor.h
@@ -77,7 +77,10 @@
   std::map<base::UnguessableToken, std::unique_ptr<PendingTaskWithTimer>>
       id_to_task_map_ GUARDED_BY(lock_);
   SEQUENCE_CHECKER(timer_sequence_checker_);
-  base::WeakPtrFactory<ScheduledExecutor> weak_factory_{this};
+  // WeakPtrFactory bound to |timer_task_runer_| to prevent use-after-free.
+  base::WeakPtrFactory<ScheduledExecutor> timer_task_runner_weak_factory_{this};
+  // WeakPtrFactory bound to Cancelable task
+  base::WeakPtrFactory<ScheduledExecutor> cancelable_task_weak_factory_{this};
 };
 
 }  // namespace chrome
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 72cec19a..34d1c76c 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5200,7 +5200,7 @@
     "../browser/predictors/prefetch_manager_unittest.cc",
     "../browser/predictors/resource_prefetch_predictor_tables_unittest.cc",
     "../browser/predictors/resource_prefetch_predictor_unittest.cc",
-    "../browser/prefetch/no_state_prefetch/prerender_unittest.cc",
+    "../browser/prefetch/no_state_prefetch/no_state_prefetch_unittest.cc",
     "../browser/prefetch/prefetch_prefs_unittest.cc",
     "../browser/prefetch/prefetch_proxy/prefetch_container_unittest.cc",
     "../browser/prefetch/prefetch_proxy/prefetch_proxy_canary_checker_unittest.cc",
diff --git a/chrome/test/data/extensions/api_test/file_browser/mount_test/test.js b/chrome/test/data/extensions/api_test/file_browser/mount_test/test.js
index 9a8167f..bba9d87 100644
--- a/chrome/test/data/extensions/api_test/file_browser/mount_test/test.js
+++ b/chrome/test/data/extensions/api_test/file_browser/mount_test/test.js
@@ -192,19 +192,32 @@
 
 chrome.test.runTests([
   function removeMount() {
-    chrome.fileManagerPrivate.removeMount('removable:mount_path1');
-
-    // We actually check this one on C++ side. If MountLibrary.RemoveMount
-    // doesn't get called, test will fail.
-    chrome.test.succeed();
+    chrome.fileManagerPrivate.removeMount('removable:mount_path1', () => {
+      chrome.test.assertNoLastError();
+      chrome.test.succeed();
+    });
   },
 
   function removeMountArchive() {
-    chrome.fileManagerPrivate.removeMount('archive:archive_mount_path');
+    chrome.fileManagerPrivate.removeMount('archive:archive_mount_path', () => {
+      chrome.test.assertNoLastError();
+      chrome.test.succeed();
+    });
+  },
 
-    // We actually check this one on C++ side. If MountLibrary.RemoveMount
-    // doesn't get called, test will fail.
-    chrome.test.succeed();
+  function removeMount() {
+    chrome.fileManagerPrivate.removeMount('removable:mount_path1', () => {
+      chrome.test.assertEq(chrome.runtime.lastError.message, 'error_cancelled');
+      chrome.test.succeed();
+    });
+  },
+
+  function removeMountArchive() {
+    chrome.fileManagerPrivate.removeMount('archive:archive_mount_path', () => {
+      chrome.test.assertEq(
+          chrome.runtime.lastError.message, 'error_need_password');
+      chrome.test.succeed();
+    });
   },
 
   function getVolumeMetadataList() {
diff --git a/chrome/test/data/extensions/api_test/file_system_provider/unmount/test.js b/chrome/test/data/extensions/api_test/file_system_provider/unmount/test.js
index 6684e3f..f7ecd231 100644
--- a/chrome/test/data/extensions/api_test/file_system_provider/unmount/test.js
+++ b/chrome/test/data/extensions/api_test/file_system_provider/unmount/test.js
@@ -93,7 +93,9 @@
 
       test_util.getVolumeInfo(SECOND_FILE_SYSTEM_ID, function(volumeInfo) {
         chrome.test.assertTrue(!!volumeInfo);
-        chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId);
+        chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId, () => {
+          chrome.test.assertNoLastError();
+        });
       });
     },
 
@@ -125,7 +127,7 @@
         chrome.test.assertTrue(unmountRequested);
 
         // Remove the handlers and mark the test as succeeded.
-        chrome.fileManagerPrivate.removeMount(SECOND_FILE_SYSTEM_ID);
+        chrome.fileManagerPrivate.removeMount(SECOND_FILE_SYSTEM_ID, () => {});
         chrome.fileManagerPrivate.onMountCompleted.removeListener(
             onMountCompleted);
       });
@@ -136,7 +138,9 @@
 
       test_util.getVolumeInfo(SECOND_FILE_SYSTEM_ID, function(volumeInfo) {
         chrome.test.assertTrue(!!volumeInfo);
-        chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId);
+        chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId, () => {
+          chrome.test.assertNoLastError();
+        });
       });
     }
   ]);
diff --git a/chromeos/services/machine_learning/public/mojom/README.md b/chromeos/services/machine_learning/public/mojom/README.md
new file mode 100644
index 0000000..3e9578c4
--- /dev/null
+++ b/chromeos/services/machine_learning/public/mojom/README.md
@@ -0,0 +1 @@
+This directory contains the mojom API for ML Service. It is mirrored in ChromeOS src/platform2/ml/mojom/, see the header in .mojom for details. Use the roll_mojoms.sh script.
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index e7a4119b..0592134 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -29,14 +29,6 @@
   "apps.SystemWebAppsReinstall.default_enabled_apps_stable",
   "camera.EncodeAccelJPEG",
 
-  # crbug.com/1247485
-  "inputs.PhysicalKeyboardInputFields.us_en",
-  "inputs.VirtualKeyboardAccent",
-
-  # crbug.com/1247177
-  "inputs.PhysicalKeyboardEnglishTyping",
-  "inputs.PhysicalKeyboardEmoji",
-
   # https://crbug.com/1252352
   "quicksettings.SignInScreen.battery",
   "quicksettings.SignInScreen.noaudio_nobattery",
@@ -93,17 +85,6 @@
   # https://crbug.com/1282369
   "apps.LaunchHelpAppOffline",
 
-  # https://crbug.com/1282366 and https://crbug.com/1311184
-  "inputs.VirtualKeyboardChangeInput.a11y",
-  "inputs.VirtualKeyboardDeadKeys.french",
-  "inputs.VirtualKeyboardFloat.clamshell",
-  "inputs.VirtualKeyboardGlideTyping.clamshell_a11y_floating",
-  "inputs.VirtualKeyboardHandwriting.docked",
-  "inputs.VirtualKeyboardHandwriting.floating",
-  "inputs.VirtualKeyboardMultipaste",
-  "inputs.VirtualKeyboardOOBE",
-  "inputs.VirtualKeyboardSystemLanguages.es",
-
   # https://crbug.com/1282370
   "arc.Boot.vm_with_per_vcpu_core_scheduling",
 
@@ -113,12 +94,6 @@
   # http://b/212644512
   "security.SELinuxFilesDataDir",
 
-  # https://crbug.com/1283300 (http://b/212637568)
-  "inputs.PhysicalKeyboardCapsLock",
-  "inputs.PhysicalKeyboardChangeInput",
-  "inputs.PhysicalKeyboardAutocorrect.en_us_1",
-  "inputs.PhysicalKeyboardDeadKeys.us_intl_acute",
-
   # https://crbug.com/1260046
   "wmp.WindowCycle",
 
diff --git a/components/feed/core/v2/proto_util.cc b/components/feed/core/v2/proto_util.cc
index 6cc9e13..9489f23 100644
--- a/components/feed/core/v2/proto_util.cc
+++ b/components/feed/core/v2/proto_util.cc
@@ -162,6 +162,13 @@
     feed_request.add_client_capability(Capability::DOWNLOAD_LINK);
   }
 
+#if BUILDFLAG(IS_ANDROID)
+  // Note that the Crow feature is referenced as THANK_CREATOR within the feed.
+  if (base::FeatureList::IsEnabled(kShareCrowButton)) {
+    feed_request.add_client_capability(Capability::THANK_CREATOR);
+  }
+#endif
+
   if (base::FeatureList::IsEnabled(kPersonalizeFeedUnsignedUsers)) {
     feed_request.add_client_capability(Capability::ON_DEVICE_USER_PROFILE);
   }
diff --git a/components/feed/core/v2/proto_util_unittest.cc b/components/feed/core/v2/proto_util_unittest.cc
index ee252d52..06eb7953 100644
--- a/components/feed/core/v2/proto_util_unittest.cc
+++ b/components/feed/core/v2/proto_util_unittest.cc
@@ -283,6 +283,22 @@
               Not(Contains((feedwire::Capability::READ_LATER))));
 }
 
+#if BUILDFLAG(IS_ANDROID)
+TEST(ProtoUtilTest, CrowButtonEnabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures({kShareCrowButton}, {});
+  feedwire::FeedRequest request =
+      CreateFeedQueryRefreshRequest(kForYouStream,
+                                    feedwire::FeedQuery::MANUAL_REFRESH,
+                                    /*request_metadata=*/{},
+                                    /*consistency_token=*/std::string())
+          .feed_request();
+
+  ASSERT_THAT(request.client_capability(),
+              Contains(feedwire::Capability::THANK_CREATOR));
+}
+#endif  // BUILDFLAG(IS_ANDROID)
+
 TEST(ProtoUtilTest, InfoCardAcknowledgementTrackingEnabled) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures({kInfoCardAcknowledgementTracking}, {});
diff --git a/components/feed/feed_feature_list.cc b/components/feed/feed_feature_list.cc
index e062650..f7b35cb 100644
--- a/components/feed/feed_feature_list.cc
+++ b/components/feed/feed_feature_list.cc
@@ -113,4 +113,7 @@
 const base::Feature kInfoCardAcknowledgementTracking{
     "InfoCardAcknowledgementTracking", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kShareCrowButton{"ShareCrowButton",
+                                     base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace feed
diff --git a/components/feed/feed_feature_list.h b/components/feed/feed_feature_list.h
index f7c0312e..39de26f3 100644
--- a/components/feed/feed_feature_list.h
+++ b/components/feed/feed_feature_list.h
@@ -111,6 +111,11 @@
 // Feature that enables tracking the acknowledgement state for the info cards.
 extern const base::Feature kInfoCardAcknowledgementTracking;
 
+// Feature that enables the Crow feature.
+// Owned by the CwF team but located here until it makes sense to create a crow
+// component, since it is being used in the feed component.
+extern const base::Feature kShareCrowButton;
+
 }  // namespace feed
 
 #endif  // COMPONENTS_FEED_FEED_FEATURE_LIST_H_
diff --git a/components/history_clusters/core/BUILD.gn b/components/history_clusters/core/BUILD.gn
index 6174bc44..02c2aa7 100644
--- a/components/history_clusters/core/BUILD.gn
+++ b/components/history_clusters/core/BUILD.gn
@@ -55,8 +55,6 @@
     "similar_visit_deduper_cluster_finalizer.h",
     "single_visit_cluster_finalizer.cc",
     "single_visit_cluster_finalizer.h",
-    "url_deduper_cluster_finalizer.cc",
-    "url_deduper_cluster_finalizer.h",
   ]
   deps = [
     "//base",
@@ -97,7 +95,6 @@
     "ranking_cluster_finalizer_unittest.cc",
     "similar_visit_deduper_cluster_finalizer_unittest.cc",
     "single_visit_cluster_finalizer_unittest.cc",
-    "url_deduper_cluster_finalizer_unittest.cc",
   ]
   deps = [
     ":core",
diff --git a/components/history_clusters/core/clustering_test_utils.cc b/components/history_clusters/core/clustering_test_utils.cc
index 371b820b..0e7da82 100644
--- a/components/history_clusters/core/clustering_test_utils.cc
+++ b/components/history_clusters/core/clustering_test_utils.cc
@@ -88,6 +88,8 @@
       normalized_url ? *normalized_url : annotated_visit.url_row.url();
   cluster_visit.url_for_deduping =
       ComputeURLForDeduping(cluster_visit.normalized_url);
+  cluster_visit.url_for_display =
+      ComputeURLForDisplay(cluster_visit.normalized_url);
   return cluster_visit;
 }
 
diff --git a/components/history_clusters/core/config.cc b/components/history_clusters/core/config.cc
index 56807a3c5..a1e2d5b 100644
--- a/components/history_clusters/core/config.cc
+++ b/components/history_clusters/core/config.cc
@@ -143,10 +143,6 @@
           "hide_single_visit_clusters_on_prominent_ui_surfaces",
           should_hide_single_visit_clusters_on_prominent_ui_surfaces);
 
-  should_dedupe_similar_visits = GetFieldTrialParamByFeatureAsBool(
-      features::kOnDeviceClustering, "dedupe_similar_visits",
-      should_dedupe_similar_visits);
-
   should_filter_noisy_clusters = GetFieldTrialParamByFeatureAsBool(
       features::kOnDeviceClustering, "filter_noisy_clusters",
       should_filter_noisy_clusters);
diff --git a/components/history_clusters/core/config.h b/components/history_clusters/core/config.h
index 5b06e059..e95f323c 100644
--- a/components/history_clusters/core/config.h
+++ b/components/history_clusters/core/config.h
@@ -143,10 +143,6 @@
   // Whether to hide single-visit clusters on prominent UI surfaces.
   bool should_hide_single_visit_clusters_on_prominent_ui_surfaces = true;
 
-  // Whether to collapse visits within a cluster that will show on the UI in the
-  // same way.
-  bool should_dedupe_similar_visits = true;
-
   // Whether to filter clusters that are noisy from the UI. This will
   // heuristically remove clusters that are unlikely to be "interesting".
   bool should_filter_noisy_clusters = true;
diff --git a/components/history_clusters/core/on_device_clustering_backend.cc b/components/history_clusters/core/on_device_clustering_backend.cc
index bf4a4be..ea0a49c 100644
--- a/components/history_clusters/core/on_device_clustering_backend.cc
+++ b/components/history_clusters/core/on_device_clustering_backend.cc
@@ -31,7 +31,6 @@
 #include "components/history_clusters/core/ranking_cluster_finalizer.h"
 #include "components/history_clusters/core/similar_visit_deduper_cluster_finalizer.h"
 #include "components/history_clusters/core/single_visit_cluster_finalizer.h"
-#include "components/history_clusters/core/url_deduper_cluster_finalizer.h"
 #include "components/optimization_guide/core/batch_entity_metadata_task.h"
 #include "components/optimization_guide/core/entity_metadata_provider.h"
 #include "components/optimization_guide/core/new_optimization_guide_decider.h"
@@ -421,14 +420,8 @@
 
   cluster_finalizers.push_back(
       std::make_unique<ContentVisibilityClusterFinalizer>());
-  if (GetConfig().should_dedupe_similar_visits) {
-    cluster_finalizers.push_back(
-        std::make_unique<SimilarVisitDeduperClusterFinalizer>());
-  } else {
-    // Otherwise, only dedupe based on normalized URL.
-    cluster_finalizers.push_back(
-        std::make_unique<UrlDeduperClusterFinalizer>());
-  }
+  cluster_finalizers.push_back(
+      std::make_unique<SimilarVisitDeduperClusterFinalizer>());
   cluster_finalizers.push_back(std::make_unique<RankingClusterFinalizer>());
   if (GetConfig().should_hide_single_visit_clusters_on_prominent_ui_surfaces) {
     cluster_finalizers.push_back(
diff --git a/components/history_clusters/core/on_device_clustering_backend_unittest.cc b/components/history_clusters/core/on_device_clustering_backend_unittest.cc
index 9e18087..e0a2200 100644
--- a/components/history_clusters/core/on_device_clustering_backend_unittest.cc
+++ b/components/history_clusters/core/on_device_clustering_backend_unittest.cc
@@ -120,7 +120,6 @@
  public:
   OnDeviceClusteringWithoutContentBackendTest() {
     config_.content_clustering_enabled = false;
-    config_.should_dedupe_similar_visits = false;
     config_.should_include_categories_in_keywords = true;
     config_.should_exclude_keywords_from_noisy_visits = false;
     config_.split_clusters_at_search_visits = false;
@@ -431,7 +430,6 @@
  public:
   OnDeviceClusteringWithContentBackendTest() {
     config_.content_clustering_enabled = true;
-    config_.should_dedupe_similar_visits = false;
     config_.should_include_categories_in_keywords = true;
     config_.should_exclude_keywords_from_noisy_visits = false;
     config_.should_check_hosts_to_skip_clustering_for = false;
@@ -689,7 +687,6 @@
  public:
   EngagementCacheOnDeviceClusteringWithoutContentBackendTest() {
     config_.content_clustering_enabled = false;
-    config_.should_dedupe_similar_visits = false;
     config_.should_include_categories_in_keywords = true;
     config_.should_exclude_keywords_from_noisy_visits = false;
     SetConfigForTesting(config_);
@@ -760,7 +757,6 @@
  public:
   BatchedClusteringTaskOnDeviceClusteringWithoutContentBackendTest() {
     config_.content_clustering_enabled = false;
-    config_.should_dedupe_similar_visits = false;
     config_.should_include_categories_in_keywords = true;
     config_.should_exclude_keywords_from_noisy_visits = false;
     config_.clustering_tasks_batch_size = 1;
diff --git a/components/history_clusters/core/ranking_cluster_finalizer.cc b/components/history_clusters/core/ranking_cluster_finalizer.cc
index b040a05..782f438 100644
--- a/components/history_clusters/core/ranking_cluster_finalizer.cc
+++ b/components/history_clusters/core/ranking_cluster_finalizer.cc
@@ -132,7 +132,6 @@
     }
 
     // Determine the max score to use for normalizing all the scores.
-    auto visit_url = visit.normalized_url;
     auto visit_scores_it =
         url_visit_scores.find(visit.annotated_visit.visit_row.visit_id);
     if (visit_scores_it != url_visit_scores.end()) {
diff --git a/components/history_clusters/core/similar_visit_deduper_cluster_finalizer.cc b/components/history_clusters/core/similar_visit_deduper_cluster_finalizer.cc
index 2349022d..af88d37 100644
--- a/components/history_clusters/core/similar_visit_deduper_cluster_finalizer.cc
+++ b/components/history_clusters/core/similar_visit_deduper_cluster_finalizer.cc
@@ -16,18 +16,18 @@
   SimilarVisit() = default;
   explicit SimilarVisit(const history::ClusterVisit& visit)
       : title(visit.annotated_visit.url_row.title()),
-        url_for_deduping(visit.url_for_deduping) {}
+        url_for_display(visit.url_for_display) {}
   SimilarVisit(const SimilarVisit&) = default;
   ~SimilarVisit() = default;
 
   std::u16string title;
-  GURL url_for_deduping;
+  std::u16string url_for_display;
 
   struct Comp {
     bool operator()(const SimilarVisit& lhs, const SimilarVisit& rhs) const {
       if (lhs.title != rhs.title)
         return lhs.title < rhs.title;
-      return lhs.url_for_deduping < rhs.url_for_deduping;
+      return lhs.url_for_display < rhs.url_for_display;
     }
   };
 };
diff --git a/components/history_clusters/core/similar_visit_deduper_cluster_finalizer_unittest.cc b/components/history_clusters/core/similar_visit_deduper_cluster_finalizer_unittest.cc
index 1fd06ac..9b4dd074 100644
--- a/components/history_clusters/core/similar_visit_deduper_cluster_finalizer_unittest.cc
+++ b/components/history_clusters/core/similar_visit_deduper_cluster_finalizer_unittest.cc
@@ -40,10 +40,12 @@
   visit.annotated_visit.url_row.set_title(u"sametitle");
   visit.annotated_visit.context_annotations.total_foreground_duration =
       base::Seconds(20);
+  visit.url_for_display = u"someurl";
 
   history::ClusterVisit canonical_visit = testing::CreateClusterVisit(
       testing::CreateDefaultAnnotatedVisit(2, GURL("https://google.com/#abc")));
   canonical_visit.annotated_visit.url_row.set_title(u"sametitle");
+  canonical_visit.url_for_display = u"someurl";
 
   history::Cluster cluster;
   cluster.visits = {visit, canonical_visit};
diff --git a/components/history_clusters/core/url_deduper_cluster_finalizer.cc b/components/history_clusters/core/url_deduper_cluster_finalizer.cc
deleted file mode 100644
index bf47f02..0000000
--- a/components/history_clusters/core/url_deduper_cluster_finalizer.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/history_clusters/core/url_deduper_cluster_finalizer.h"
-
-#include "base/ranges/algorithm.h"
-#include "components/history_clusters/core/on_device_clustering_util.h"
-
-namespace history_clusters {
-
-UrlDeduperClusterFinalizer::UrlDeduperClusterFinalizer() = default;
-UrlDeduperClusterFinalizer::~UrlDeduperClusterFinalizer() = default;
-
-void UrlDeduperClusterFinalizer::FinalizeCluster(history::Cluster& cluster) {
-  base::flat_map<std::string, history::ClusterVisit*> url_to_canonical_visit;
-  // First do a prepass to find the canonical visit for each URL. This simply
-  // marks the last visit in `cluster` with any given URL as the canonical one.
-  for (auto& visit : cluster.visits) {
-    url_to_canonical_visit[visit.url_for_deduping.possibly_invalid_spec()] =
-        &visit;
-  }
-
-  cluster.visits.erase(
-      base::ranges::remove_if(
-          cluster.visits,
-          [&](auto& visit) {
-            // We are guaranteed to find a matching canonical visit, due to our
-            // prepass above.
-            auto it = url_to_canonical_visit.find(
-                visit.url_for_deduping.possibly_invalid_spec());
-            DCHECK(it != url_to_canonical_visit.end());
-            history::ClusterVisit* canonical_visit = it->second;
-
-            // If a DIFFERENT visit is the canonical visit for this key, merge
-            // this visit in, and mark this visit as to be removed.
-            if (&visit != canonical_visit) {
-              MergeDuplicateVisitIntoCanonicalVisit(std::move(visit),
-                                                    *canonical_visit);
-              return true;
-            }
-
-            return false;
-          }),
-      cluster.visits.end());
-}
-
-}  // namespace history_clusters
diff --git a/components/history_clusters/core/url_deduper_cluster_finalizer.h b/components/history_clusters/core/url_deduper_cluster_finalizer.h
deleted file mode 100644
index 47bfd68d..0000000
--- a/components/history_clusters/core/url_deduper_cluster_finalizer.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_HISTORY_CLUSTERS_CORE_URL_DEDUPER_CLUSTER_FINALIZER_H_
-#define COMPONENTS_HISTORY_CLUSTERS_CORE_URL_DEDUPER_CLUSTER_FINALIZER_H_
-
-#include "components/history_clusters/core/cluster_finalizer.h"
-
-namespace history_clusters {
-
-// A cluster finalizer that dedupes visits based on URL.
-class UrlDeduperClusterFinalizer : public ClusterFinalizer {
- public:
-  UrlDeduperClusterFinalizer();
-  ~UrlDeduperClusterFinalizer() override;
-
-  // ClusterFinalizer:
-  void FinalizeCluster(history::Cluster& cluster) override;
-};
-
-}  // namespace history_clusters
-
-#endif  // COMPONENTS_HISTORY_CLUSTERS_CORE_URL_DEDUPER_CLUSTER_FINALIZER_H_
diff --git a/components/history_clusters/core/url_deduper_cluster_finalizer_unittest.cc b/components/history_clusters/core/url_deduper_cluster_finalizer_unittest.cc
deleted file mode 100644
index 51414d22..0000000
--- a/components/history_clusters/core/url_deduper_cluster_finalizer_unittest.cc
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/history_clusters/core/url_deduper_cluster_finalizer.h"
-
-#include "base/test/task_environment.h"
-#include "components/history_clusters/core/clustering_test_utils.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace history_clusters {
-namespace {
-
-using ::testing::ElementsAre;
-using ::testing::UnorderedElementsAre;
-
-class UrlDeduperClusterFinalizerTest : public ::testing::Test {
- public:
-  void SetUp() override {
-    cluster_finalizer_ = std::make_unique<UrlDeduperClusterFinalizer>();
-  }
-
-  void TearDown() override { cluster_finalizer_.reset(); }
-
-  void FinalizeCluster(history::Cluster& cluster) {
-    cluster_finalizer_->FinalizeCluster(cluster);
-  }
-
- private:
-  std::unique_ptr<UrlDeduperClusterFinalizer> cluster_finalizer_;
-  base::test::TaskEnvironment task_environment_;
-};
-
-TEST_F(UrlDeduperClusterFinalizerTest, DedupeExactURL) {
-  // canonical_visit has the same URL as Visit1.
-  history::ClusterVisit visit = testing::CreateClusterVisit(
-      testing::CreateDefaultAnnotatedVisit(1, GURL("https://google.com/")));
-  visit.annotated_visit.context_annotations.total_foreground_duration =
-      base::Seconds(20);
-
-  history::ClusterVisit canonical_visit = testing::CreateClusterVisit(
-      testing::CreateDefaultAnnotatedVisit(2, GURL("https://google.com/")));
-
-  history::Cluster cluster;
-  cluster.visits = {visit, canonical_visit};
-  FinalizeCluster(cluster);
-  EXPECT_THAT(testing::ToVisitResults({cluster}),
-              ElementsAre(ElementsAre(testing::VisitResult(
-                  2, 1.0, {testing::VisitResult(1, 1.0)}))));
-  const auto& actual_canonical_visit = cluster.visits.at(0);
-  // Make sure total foreground duration is updated correctly even if some don't
-  // have the field populated.
-  EXPECT_EQ(actual_canonical_visit.annotated_visit.context_annotations
-                .total_foreground_duration,
-            base::Seconds(20));
-}
-
-TEST_F(UrlDeduperClusterFinalizerTest, DedupeRespectsDifferentURLs) {
-  history::ClusterVisit visit = testing::CreateClusterVisit(
-      testing::CreateDefaultAnnotatedVisit(1, GURL("https://google.com/")));
-
-  history::ClusterVisit canonical_visit = testing::CreateClusterVisit(
-      testing::CreateDefaultAnnotatedVisit(2, GURL("https://foo.com/")));
-
-  history::Cluster cluster;
-  cluster.visits = {visit, canonical_visit};
-  FinalizeCluster(cluster);
-  EXPECT_THAT(testing::ToVisitResults({cluster}),
-              ElementsAre(ElementsAre(testing::VisitResult(1, 1.0),
-                                      testing::VisitResult(2, 1.0))));
-}
-
-TEST_F(UrlDeduperClusterFinalizerTest, DedupeNormalizedUrl) {
-  // canonical_visit has the same normalized URL as Visit1.
-  history::ClusterVisit visit = testing::CreateClusterVisit(
-      testing::CreateDefaultAnnotatedVisit(
-          1, GURL("https://example.com/normalized?q=whatever")),
-      GURL("https://example.com/normalized"));
-
-  history::ClusterVisit canonical_visit =
-      testing::CreateClusterVisit(testing::CreateDefaultAnnotatedVisit(
-          2, GURL("https://example.com/normalized")));
-
-  history::Cluster cluster;
-  cluster.visits = {visit, canonical_visit};
-  FinalizeCluster(cluster);
-  EXPECT_THAT(testing::ToVisitResults({cluster}),
-              ElementsAre(ElementsAre(testing::VisitResult(
-                  2, 1.0, {testing::VisitResult(1, 1.0)}))));
-  const auto& actual_canonical_visit = cluster.visits.at(0);
-  // Make sure total foreground duration not updated if none of the visits have
-  // it populated.
-  EXPECT_EQ(actual_canonical_visit.annotated_visit.context_annotations
-                .total_foreground_duration,
-            base::Seconds(-1));
-}
-
-TEST_F(UrlDeduperClusterFinalizerTest, MergesAnnotations) {
-  // canonical_visit has the same normalized URL as duplicated_visit.
-  history::ClusterVisit duplicate_visit = testing::CreateClusterVisit(
-      testing::CreateDefaultAnnotatedVisit(
-          1, GURL("https://example.com/normalized?q=whatever")),
-      GURL("https://example.com/normalized"));
-  duplicate_visit.annotated_visit.content_annotations.related_searches = {
-      "xyz"};
-  duplicate_visit.annotated_visit.context_annotations.omnibox_url_copied = true;
-  duplicate_visit.annotated_visit.context_annotations.is_existing_bookmark =
-      true;
-  duplicate_visit.annotated_visit.context_annotations
-      .is_existing_part_of_tab_group = true;
-  duplicate_visit.annotated_visit.context_annotations.is_new_bookmark = true;
-  duplicate_visit.annotated_visit.context_annotations.is_placed_in_tab_group =
-      true;
-  duplicate_visit.annotated_visit.context_annotations.is_ntp_custom_link = true;
-  duplicate_visit.annotated_visit.context_annotations
-      .total_foreground_duration = base::Seconds(20);
-
-  history::ClusterVisit canonical_visit =
-      testing::CreateClusterVisit(testing::CreateDefaultAnnotatedVisit(
-          2, GURL("https://example.com/normalized")));
-  canonical_visit.annotated_visit.content_annotations.related_searches = {
-      "abc", "xyz"};
-  canonical_visit.annotated_visit.context_annotations.omnibox_url_copied =
-      false;
-  canonical_visit.annotated_visit.context_annotations.is_existing_bookmark =
-      false;
-  canonical_visit.annotated_visit.context_annotations
-      .is_existing_part_of_tab_group = false;
-  canonical_visit.annotated_visit.context_annotations.is_new_bookmark = false;
-  canonical_visit.annotated_visit.context_annotations.is_placed_in_tab_group =
-      false;
-  canonical_visit.annotated_visit.context_annotations.is_ntp_custom_link =
-      false;
-  canonical_visit.annotated_visit.context_annotations
-      .total_foreground_duration = base::Seconds(20);
-
-  history::Cluster cluster;
-  cluster.visits = {duplicate_visit, canonical_visit};
-  FinalizeCluster(cluster);
-  EXPECT_THAT(testing::ToVisitResults({cluster}),
-              ElementsAre(ElementsAre(testing::VisitResult(
-                  2, 1.0, {testing::VisitResult(1, 1.0)}))));
-  const auto& actual_canonical_visit = cluster.visits.at(0);
-  EXPECT_TRUE(actual_canonical_visit.annotated_visit.context_annotations
-                  .omnibox_url_copied);
-  EXPECT_TRUE(actual_canonical_visit.annotated_visit.context_annotations
-                  .is_existing_bookmark);
-  EXPECT_TRUE(actual_canonical_visit.annotated_visit.context_annotations
-                  .is_existing_part_of_tab_group);
-  EXPECT_TRUE(actual_canonical_visit.annotated_visit.context_annotations
-                  .is_new_bookmark);
-  EXPECT_TRUE(actual_canonical_visit.annotated_visit.context_annotations
-                  .is_placed_in_tab_group);
-  EXPECT_TRUE(actual_canonical_visit.annotated_visit.context_annotations
-                  .is_ntp_custom_link);
-  EXPECT_THAT(actual_canonical_visit.annotated_visit.content_annotations
-                  .related_searches,
-              UnorderedElementsAre("abc", "xyz"));
-  EXPECT_EQ(actual_canonical_visit.annotated_visit.visit_row.visit_duration,
-            base::Seconds(10 * 2));
-  EXPECT_EQ(actual_canonical_visit.annotated_visit.context_annotations
-                .total_foreground_duration,
-            base::Seconds(20 * 2));
-}
-
-}  // namespace
-}  // namespace history_clusters
diff --git a/components/no_state_prefetch/browser/no_state_prefetch_link_manager.h b/components/no_state_prefetch/browser/no_state_prefetch_link_manager.h
index d656aeb..37d2720f 100644
--- a/components/no_state_prefetch/browser/no_state_prefetch_link_manager.h
+++ b/components/no_state_prefetch/browser/no_state_prefetch_link_manager.h
@@ -63,7 +63,7 @@
 
  private:
   friend class PrerenderBrowserTest;
-  friend class PrerenderTest;
+  friend class NoStatePrefetchTest;
   // WebViewTest.NoPrerenderer needs to access the private IsEmpty() method.
   FRIEND_TEST_ALL_PREFIXES(::WebViewTest, NoPrerenderer);
 
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
index 69f0fa0..089633a4 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
@@ -6,10 +6,12 @@
 
 #include "base/base64.h"
 #include "base/containers/contains.h"
+#include "base/no_destructor.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
+#include "net/base/backoff_entry.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -19,10 +21,120 @@
 
 // EncryptedReportingJobConfiguration strings
 constexpr char kEncryptedRecordListKey[] = "encryptedRecord";
+constexpr char kSequenceInformationKey[] = "sequenceInformation";
+constexpr char kSequenceId[] = "sequencingId";
+constexpr char kGenerationId[] = "generationId";
+constexpr char kPriority[] = "priority";
 constexpr char kAttachEncryptionSettingsKey[] = "attachEncryptionSettings";
 constexpr char kDeviceKey[] = "device";
 constexpr char kBrowserKey[] = "browser";
 
+// Generate new backoff entry.
+std::unique_ptr<::net::BackoffEntry> GetBackoffEntry(
+    ::reporting::Priority priority) {
+  // Retry policy for SECURITY queue.
+  static const ::net::BackoffEntry::Policy kSecurityUploadBackoffPolicy = {
+      // Number of initial errors to ignore before applying
+      // exponential back-off rules.
+      /*num_errors_to_ignore=*/0,
+
+      // Initial delay is 10 seconds.
+      /*initial_delay_ms=*/10 * 1000,
+
+      // Factor by which the waiting time will be multiplied.
+      /*multiply_factor=*/2,
+
+      // Fuzzing percentage.
+      /*jitter_factor=*/0.1,
+
+      // Maximum delay is 1 minute.
+      /*maximum_backoff_ms=*/1 * 60 * 1000,
+
+      // It's up to the caller to reset the backoff time.
+      /*entry_lifetime_ms=*/-1,
+
+      /*always_use_initial_delay=*/true,
+  };
+  // Retry policy for all other queues, including initial key delivery.
+  static const ::net::BackoffEntry::Policy kDefaultUploadBackoffPolicy = {
+      // Number of initial errors to ignore before applying
+      // exponential back-off rules.
+      /*num_errors_to_ignore=*/0,
+
+      // Initial delay is 10 seconds.
+      /*initial_delay_ms=*/10 * 1000,
+
+      // Factor by which the waiting time will be multiplied.
+      /*multiply_factor=*/2,
+
+      // Fuzzing percentage.
+      /*jitter_factor=*/0.1,
+
+      // Maximum delay is 24 hours.
+      /*maximum_backoff_ms=*/24 * 60 * 60 * 1000,
+
+      // It's up to the caller to reset the backoff time.
+      /*entry_lifetime_ms=*/-1,
+
+      /*always_use_initial_delay=*/true,
+  };
+  // Maximum backoff is set per priority. Current proposal is to set SECURITY
+  // events to be backed off only slightly: max delay is set to 1 minute.
+  // For all other priorities max delay is set to 24 hours.
+  auto backoff_entry = std::make_unique<::net::BackoffEntry>(
+      priority == ::reporting::SECURITY ? &kSecurityUploadBackoffPolicy
+                                        : &kDefaultUploadBackoffPolicy);
+  return backoff_entry;
+}
+
+// State of single priority queue uploads.
+// It is a singleton, protected implicitly by the fact that all relevant
+// EncryptedReportingJobConfiguration actions are called on the sequenced task
+// runner.
+struct UploadState {
+  // Highest sequence id that has been posted for upload.
+  int64_t last_sequence_id;
+  // Generation id that has been posted for upload.
+  int64_t last_generation_id;
+
+  // Time when the next request will be allowed.
+  // This is essentially the cache value of the backoff->GetReleaseTime().
+  // When the time is reached, one request is allowed, backoff is updated as if
+  // the request failed, and the new release time is cached.
+  base::TimeTicks earliest_retry_timestamp;
+
+  // Current backoff entry for this prioririty.
+  std::unique_ptr<::net::BackoffEntry> backoff_entry;
+};
+// Map of all the queues states.
+using UploadStateMap = base::flat_map<::reporting::Priority, UploadState>;
+
+UploadStateMap* state_map() {
+  static base::NoDestructor<UploadStateMap> map;
+  return map.get();
+}
+
+UploadState* AccessState(::reporting::Priority priority,
+                         int64_t generation_id,
+                         int64_t sequence_id) {
+  auto state_it = state_map()->find(priority);
+  if (state_it == state_map()->end() ||
+      state_it->second.last_generation_id != generation_id) {
+    // This priority pops up for the first time or (rare case) generation has
+    // changed. Record new state and allow upload.
+    state_it = state_map()
+                   ->insert_or_assign(
+                       priority,
+                       UploadState{.last_sequence_id = sequence_id,
+                                   .last_generation_id = generation_id,
+                                   .backoff_entry = GetBackoffEntry(priority)})
+                   .first;
+    state_it->second.earliest_retry_timestamp =
+        state_it->second.backoff_entry->GetReleaseTime();
+  }
+  return &state_it->second;
+}
+
 }  // namespace
 
 EncryptedReportingJobConfiguration::EncryptedReportingJobConfiguration(
@@ -38,6 +150,42 @@
                                     std::move(complete_cb)) {
   // Merge it into the base class payload.
   payload_.Merge(merging_payload);
+  // Retrieve priorities and figure out maximum sequence id for each.
+  // Payload is expected to be correctly formed, any malformed piece is ignored.
+  // TODO(b/214040103): if batching is enabled, multiple priorities may be
+  // found. Before that, each payload can only have no more than one, and the
+  // highest sequence id comes from the last record.
+  // TODO(b/232455728): if test_request_payload is moved to components/
+  // we would be able to use it here.
+  const auto* const encrypted_record_list =
+      payload_.FindList(kEncryptedRecordListKey);
+  // If there are no records, assume UNDEFINED priority and seq_id = -1.
+  priority_ = ::reporting::UNDEFINED_PRIORITY;
+  generation_id_ = -1;
+  sequence_id_ = -1;
+  if (encrypted_record_list != nullptr && !encrypted_record_list->empty()) {
+    const auto sequence_information_it =
+        std::prev(encrypted_record_list->cend());
+    const auto* const sequence_information =
+        sequence_information_it->GetDict().FindDict(kSequenceInformationKey);
+    if (sequence_information != nullptr) {
+      const auto maybe_priority = sequence_information->FindInt(kPriority);
+      auto* const generation_id_ptr =
+          sequence_information->FindString(kGenerationId);
+      auto* const sequence_id_ptr =
+          sequence_information->FindString(kSequenceId);
+      if (maybe_priority.has_value() &&
+          ::reporting::Priority_IsValid(maybe_priority.value())) {
+        priority_ = static_cast<::reporting::Priority>(maybe_priority.value());
+      }
+      if (generation_id_ptr != nullptr) {
+        base::StringToInt64(*generation_id_ptr, &generation_id_);
+      }
+      if (sequence_id_ptr != nullptr) {
+        base::StringToInt64(*sequence_id_ptr, &sequence_id_);
+      }
+    }
+  }
 }
 
 EncryptedReportingJobConfiguration::~EncryptedReportingJobConfiguration() {
@@ -69,8 +217,32 @@
 
 base::TimeDelta EncryptedReportingJobConfiguration::WhenIsAllowedToProceed()
     const {
-  return base::TimeDelta();  // 0 - allowed right away. TODO(b/214044545):
-                             // implement.
+  // Now pick up the state.
+  const auto* const state =
+      AccessState(priority_, generation_id_, sequence_id_);
+  // Use and update previously recorded state, base upload decision on it.
+  if (state->last_sequence_id > sequence_id_) {
+    // Sequence id decreased, the upload is outdated, reject it forever.
+    return base::TimeDelta::Max();
+  }
+  if (state->last_sequence_id < sequence_id_) {
+    // Sequence id increased, keep validating.
+    switch (priority_) {
+      case ::reporting::SECURITY:
+        // For SECURITY events the request is allowed.
+        return base::TimeDelta();  // 0 - allowed right away.
+      default: {
+        // For all other priorities we will act like in case of request’s
+        // last_sequence_id is == last_sequence_id above - observing the
+        // backoff time expiration.
+      }
+    }
+  }
+  // Allow upload only if earliest retry time has passed.
+  // Return delta till the allowed time - if positive, upload is going to be
+  // rejected.
+  return state->earliest_retry_timestamp -
+         state->backoff_entry->GetTimeTicksNow();
 }
 
 void EncryptedReportingJobConfiguration::CancelNotAllowedJob() {
@@ -81,7 +253,15 @@
 }
 
 void EncryptedReportingJobConfiguration::AccountForAllowedJob() {
-  // TODO(b/214044545): implement.
+  auto* const state = AccessState(priority_, generation_id_, sequence_id_);
+  // Update state to reflect highest sequence_id_ (we never allow upload with
+  // lower sequence_id_).
+  state->last_sequence_id = sequence_id_;
+  // Calculate delay as exponential backoff (based on the retry_count).
+  // Update backoff under assumption that this request fails.
+  // If it is responded successfully, we will reset it.
+  state->backoff_entry->InformOfRequest(/*succeeded=*/false);
+  state->earliest_retry_timestamp = state->backoff_entry->GetReleaseTime();
 }
 
 DeviceManagementService::Job::RetryMethod
@@ -92,6 +272,34 @@
   return DeviceManagementService::Job::NO_RETRY;
 }
 
+void EncryptedReportingJobConfiguration::OnURLLoadComplete(
+    DeviceManagementService::Job* job,
+    int net_error,
+    int response_code,
+    const std::string& response_body) {
+  // Analyze the net error and update upload state for possible future retries.
+  auto* const state = AccessState(priority_, generation_id_, sequence_id_);
+  if (net_error != ::net::OK) {
+    // Network error
+  } else if (response_code >= 400 && response_code <= 499) {
+    // Permanent error code returned by server, impose artificial 24h backoff.
+    state->backoff_entry->SetCustomReleaseTime(
+        state->backoff_entry->GetTimeTicksNow() + base::Days(1));
+  }
+  // For all other cases keep the currently set retry time.
+  // In case of success, inform backoff entry about that.
+  if (net_error == ::net::OK &&
+      response_code == DeviceManagementService::kSuccess) {
+    state->backoff_entry->InformOfRequest(/*succeeded=*/true);
+  }
+  // Cache earliest retry time based on the current backoff entry.
+  state->earliest_retry_timestamp = state->backoff_entry->GetReleaseTime();
+
+  // Then deliver response and status by making a call to the base class.
+  ReportingJobConfigurationBase::OnURLLoadComplete(
+      job, net_error, response_code, response_body);
+}
+
 std::string EncryptedReportingJobConfiguration::GetUmaString() const {
   return "Enterprise.EncryptedReportingSuccess";
 }
@@ -104,4 +312,9 @@
   return kTopLevelKeyAllowList;
 }
 
+// static
+void EncryptedReportingJobConfiguration::ResetUploadsStateForTest() {
+  state_map()->clear();
+}
+
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
index a62f529e..cb90ac6 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
@@ -105,6 +105,15 @@
   // Cancels the job, that was not allowed to proceed.
   void CancelNotAllowedJob();
 
+  // Callback to process error codes and, in case of success, response body.
+  void OnURLLoadComplete(DeviceManagementService::Job* job,
+                         int net_error,
+                         int response_code,
+                         const std::string& response_body) override;
+
+  // Test-only method that resets collected uploads state.
+  static void ResetUploadsStateForTest();
+
  protected:
   void UpdatePayloadBeforeGetInternal() override;
 
@@ -117,6 +126,11 @@
 
  private:
   std::set<std::string> GetTopLevelKeyAllowList();
+
+  // Parameters populated from the payload_.
+  ::reporting::Priority priority_;
+  int64_t generation_id_{-1};
+  int64_t sequence_id_{-1};
 };
 
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc b/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
index 4fc47b5..f9add06 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/test/task_environment.h"
 #include "base/values.h"
 #include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/policy/core/common/cloud/cloud_policy_util.h"
 #include "components/policy/core/common/cloud/dm_auth.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
@@ -28,6 +29,7 @@
 using ::testing::_;
 using ::testing::ByRef;
 using ::testing::Eq;
+using ::testing::Ge;
 using ::testing::IsNull;
 using ::testing::MockFunction;
 using ::testing::NotNull;
@@ -82,29 +84,6 @@
   return kSequencingId++;
 }
 
-base::Value GenerateSingleRecord(base::StringPiece encrypted_wrapped_record) {
-  base::Value record_dictionary{base::Value::Type::DICTIONARY};
-  std::string base64_encode;
-  base::Base64Encode(encrypted_wrapped_record, &base64_encode);
-  record_dictionary.SetStringKey(kEncryptedWrappedRecordKey, base64_encode);
-
-  base::Value* const sequencing_dictionary = record_dictionary.SetKey(
-      kSequenceInformationKey, base::Value{base::Value::Type::DICTIONARY});
-  sequencing_dictionary->SetStringKey(
-      kSequencingIdKey, base::NumberToString(GetNextSequenceId()));
-  sequencing_dictionary->SetStringKey(kGenerationIdKey,
-                                      base::NumberToString(kGenerationId));
-  sequencing_dictionary->SetIntKey(kPriorityKey, kPriority);
-
-  base::Value* const encryption_info_dictionary = record_dictionary.SetKey(
-      kEncryptionInfoKey, base::Value{base::Value::Type::DICTIONARY});
-  encryption_info_dictionary->SetStringKey(kEncryptionKey, kEncryptionKeyValue);
-  encryption_info_dictionary->SetStringKey(
-      kPublicKeyIdKey, base::NumberToString(kPublicKeyIdValue));
-
-  return record_dictionary;
-}
-
 class RequestPayloadBuilder {
  public:
   explicit RequestPayloadBuilder(bool attach_encryption_settings = false) {
@@ -238,10 +217,61 @@
   }
 
  protected:
-  using MockCompleteCb = MockFunction<void(DeviceManagementService::Job* job,
+  using MockCompleteCb = MockFunction<void(DeviceManagementService::Job* upload,
                                            DeviceManagementStatus code,
                                            int response_code,
                                            absl::optional<base::Value::Dict>)>;
+  struct TestUpload {
+    std::unique_ptr<EncryptedReportingJobConfiguration> configuration;
+    std::unique_ptr<StrictMock<MockCompleteCb>> completion_cb;
+    base::Value::Dict response;
+    DeviceManagementService::Job job;
+  };
+
+  void SetUp() override {
+    EncryptedReportingJobConfiguration::ResetUploadsStateForTest();
+  }
+
+  TestUpload CreateTestUpload(const base::Value& record_value) {
+    TestUpload test_upload;
+    test_upload.response = ResponseValueBuilder::CreateResponse(
+        *record_value.FindDictKey(kSequenceInformationKey), absl::nullopt);
+    test_upload.completion_cb = std::make_unique<StrictMock<MockCompleteCb>>();
+    test_upload.configuration =
+        std::make_unique<EncryptedReportingJobConfiguration>(
+            &client_,
+            service_.configuration()->GetEncryptedReportingServerUrl(),
+            RequestPayloadBuilder().AddRecord(record_value).Build(),
+            base::BindOnce(&MockCompleteCb::Call,
+                           base::Unretained(test_upload.completion_cb.get())));
+    return test_upload;
+  }
+
+  base::Value GenerateSingleRecord(base::StringPiece encrypted_wrapped_record,
+                                   ::reporting::Priority priority = kPriority) {
+    base::Value record_dictionary{base::Value::Type::DICTIONARY};
+    std::string base64_encode;
+    base::Base64Encode(encrypted_wrapped_record, &base64_encode);
+    record_dictionary.SetStringKey(kEncryptedWrappedRecordKey, base64_encode);
+
+    base::Value* const sequencing_dictionary = record_dictionary.SetKey(
+        kSequenceInformationKey, base::Value{base::Value::Type::DICTIONARY});
+    sequencing_dictionary->SetStringKey(
+        kSequencingIdKey, base::NumberToString(GetNextSequenceId()));
+    sequencing_dictionary->SetStringKey(kGenerationIdKey,
+                                        base::NumberToString(kGenerationId));
+    sequencing_dictionary->SetIntKey(kPriorityKey, priority);
+
+    base::Value* const encryption_info_dictionary = record_dictionary.SetKey(
+        kEncryptionInfoKey, base::Value{base::Value::Type::DICTIONARY});
+    encryption_info_dictionary->SetStringKey(kEncryptionKey,
+                                             kEncryptionKeyValue);
+    encryption_info_dictionary->SetStringKey(
+        kPublicKeyIdKey, base::NumberToString(kPublicKeyIdValue));
+
+    return record_dictionary;
+  }
+
   static base::Value::Dict GenerateContext(base::StringPiece key,
                                            base::StringPiece value) {
     base::Value::Dict context;
@@ -274,7 +304,8 @@
     return &payload_;
   }
 
-  base::test::SingleThreadTaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
@@ -295,9 +326,6 @@
   StrictMock<MockJobCreationHandler> job_creation_handler_;
   FakeDeviceManagementService service_{&job_creation_handler_};
   MockCloudPolicyClient client_;
-  StrictMock<MockCompleteCb> complete_cb_;
-
-  DeviceManagementService::Job job_;
 
  private:
   base::Value payload_;
@@ -306,11 +334,12 @@
 // Validates that the non-Record portions of the payload are generated
 // correctly.
 TEST_F(EncryptedReportingJobConfigurationTest, ValidatePayload) {
-  EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+  StrictMock<MockCompleteCb> completion_cb;
+  EXPECT_CALL(completion_cb, Call(_, _, _, _)).Times(1);
   EncryptedReportingJobConfiguration configuration(
       &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
       RequestPayloadBuilder().Build(),
-      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
   auto* payload = GetPayload(&configuration);
   EXPECT_FALSE(GetDeviceName().empty());
   EXPECT_EQ(
@@ -345,12 +374,13 @@
 TEST_F(EncryptedReportingJobConfigurationTest, CorrectlyAddEncryptedRecord) {
   const std::string kEncryptedWrappedRecord = "TEST_INFO";
   base::Value record_value = GenerateSingleRecord(kEncryptedWrappedRecord);
+  StrictMock<MockCompleteCb> completion_cb;
 
-  EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+  EXPECT_CALL(completion_cb, Call(_, _, _, _)).Times(1);
   EncryptedReportingJobConfiguration configuration(
       &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
       RequestPayloadBuilder().AddRecord(record_value).Build(),
-      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
   base::Value* record_list = nullptr;
   GetRecordList(&configuration, &record_list);
@@ -378,11 +408,12 @@
     builder.AddRecord(records.back());
   }
 
-  EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+  StrictMock<MockCompleteCb> completion_cb;
+  EXPECT_CALL(completion_cb, Call(_, _, _, _)).Times(1);
   EncryptedReportingJobConfiguration configuration(
       &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
       builder.Build(),
-      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
   base::Value* record_list = nullptr;
   GetRecordList(&configuration, &record_list);
@@ -402,11 +433,12 @@
 TEST_F(EncryptedReportingJobConfigurationTest,
        AllowsAttachEncryptionSettingsAlone) {
   RequestPayloadBuilder builder{/*attach_encryption_settings=*/true};
-  EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+  StrictMock<MockCompleteCb> completion_cb;
+  EXPECT_CALL(completion_cb, Call(_, _, _, _)).Times(1);
   EncryptedReportingJobConfiguration configuration(
       &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
       builder.Build(),
-      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
   base::Value* record_list = nullptr;
   GetRecordList(&configuration, &record_list);
@@ -427,11 +459,12 @@
     builder.AddRecord(records.back());
   }
 
-  EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+  StrictMock<MockCompleteCb> completion_cb;
+  EXPECT_CALL(completion_cb, Call(_, _, _, _)).Times(1);
   EncryptedReportingJobConfiguration configuration(
       &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
       builder.Build(),
-      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
   base::Value* record_list = nullptr;
   GetRecordList(&configuration, &record_list);
@@ -448,11 +481,12 @@
 
 // Ensures that the context can be updated.
 TEST_F(EncryptedReportingJobConfigurationTest, CorrectlyAddsAndUpdatesContext) {
-  EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+  StrictMock<MockCompleteCb> completion_cb;
+  EXPECT_CALL(completion_cb, Call(_, _, _, _)).Times(1);
   EncryptedReportingJobConfiguration configuration(
       &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
       RequestPayloadBuilder().Build(),
-      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
   const std::string kTestKey = "device.name";
   const std::string kTestValue = "1701-A";
@@ -463,7 +497,7 @@
   base::Value* payload = GetPayload(&configuration);
   std::string* good_result = payload->FindStringPath(kTestKey);
   ASSERT_THAT(good_result, NotNull());
-  EXPECT_EQ(*good_result, kTestValue);
+  EXPECT_THAT(*good_result, StrEq(kTestValue));
 
   // Add a path that isn't in the allow list.
   const std::string kBadTestKey = "profile.string";
@@ -494,40 +528,187 @@
 TEST_F(EncryptedReportingJobConfigurationTest, OnURLLoadComplete_Success) {
   const std::string kEncryptedWrappedRecord = "TEST_INFO";
   base::Value record_value = GenerateSingleRecord(kEncryptedWrappedRecord);
+  auto upload = CreateTestUpload(record_value);
 
-  base::Value::Dict response = ResponseValueBuilder::CreateResponse(
-      *record_value.FindDictKey(kSequenceInformationKey), absl::nullopt);
-
-  EXPECT_CALL(complete_cb_,
-              Call(&job_, DM_STATUS_SUCCESS, DeviceManagementService::kSuccess,
-                   Eq(ByRef(response))))
+  EXPECT_CALL(*upload.completion_cb, Call(&upload.job, DM_STATUS_SUCCESS,
+                                          DeviceManagementService::kSuccess,
+                                          Eq(ByRef(upload.response))))
       .Times(1);
-  EncryptedReportingJobConfiguration configuration(
-      &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
-      RequestPayloadBuilder().AddRecord(record_value).Build(),
-      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
 
   const std::string kTestString = "device.clientId";
   const std::string kTestInt = "1701-A";
   base::Value::Dict context = GenerateContext(kTestString, kTestInt);
-  configuration.UpdateContext(std::move(context));
+  upload.configuration->UpdateContext(std::move(context));
 
-  configuration.OnURLLoadComplete(
-      &job_, net::OK, DeviceManagementService::kSuccess,
-      ResponseValueBuilder::CreateResponseString(response));
+  upload.configuration->OnURLLoadComplete(
+      &upload.job, net::OK, DeviceManagementService::kSuccess,
+      ResponseValueBuilder::CreateResponseString(upload.response));
 }
 
 // Ensures that upload failure is handled correctly.
 TEST_F(EncryptedReportingJobConfigurationTest, OnURLLoadComplete_NetError) {
-  EXPECT_CALL(complete_cb_, Call(&job_, DM_STATUS_REQUEST_FAILED, _,
-                                 testing::Eq(absl::nullopt)))
+  StrictMock<MockCompleteCb> completion_cb;
+  DeviceManagementService::Job job;
+  EXPECT_CALL(completion_cb, Call(&job, DM_STATUS_REQUEST_FAILED, _,
+                                  testing::Eq(absl::nullopt)))
       .Times(1);
   EncryptedReportingJobConfiguration configuration(
       &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
       RequestPayloadBuilder().Build(),
-      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
-  configuration.OnURLLoadComplete(&job_, net::ERR_CONNECTION_RESET,
+      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
+  configuration.OnURLLoadComplete(&job, net::ERR_CONNECTION_RESET,
                                   0 /* ignored */, "");
 }
 
+TEST_F(EncryptedReportingJobConfigurationTest,
+       IdenticalUploadRetriesThrottled) {
+  const size_t kTotalRetries = 10;
+  const std::string kEncryptedWrappedRecord = "TEST_INFO";
+  base::Value record_value =
+      GenerateSingleRecord(kEncryptedWrappedRecord, kPriority);
+
+  base::TimeDelta expected_delay_after = base::Seconds(10);
+  for (size_t i = 0; i < kTotalRetries; ++i) {
+    auto upload = CreateTestUpload(record_value);
+    // Expect upload to fail with a temporary error, to justify a retry.
+    EXPECT_CALL(*upload.completion_cb,
+                Call(&upload.job, DM_STATUS_TEMPORARY_UNAVAILABLE,
+                     DeviceManagementService::kServiceUnavailable,
+                     Eq(ByRef(upload.response))))
+        .Times(1);
+
+    auto allowed_delay = upload.configuration->WhenIsAllowedToProceed();
+    if (i == 0) {
+      // First upload allowed immediately.
+      EXPECT_FALSE(allowed_delay.is_positive());
+    } else {
+      // Further uploads allowed with delay.
+      EXPECT_THAT(allowed_delay, Ge(expected_delay_after));
+      // Double the expectation for the next retry.
+      expected_delay_after *= 2;
+      // Move forward to allow.
+      task_environment_.FastForwardBy(allowed_delay - base::Seconds(1));
+      EXPECT_TRUE(upload.configuration->WhenIsAllowedToProceed().is_positive());
+      task_environment_.FastForwardBy(base::Seconds(1));
+    }
+
+    EXPECT_FALSE(upload.configuration->WhenIsAllowedToProceed().is_positive());
+    upload.configuration->AccountForAllowedJob();
+    // Process temporary error response code.
+    upload.configuration->OnURLLoadComplete(
+        &upload.job, net::OK, DeviceManagementService::kServiceUnavailable,
+        ResponseValueBuilder::CreateResponseString(upload.response));
+  }
+}
+
+TEST_F(EncryptedReportingJobConfigurationTest, UploadsSequenceThrottled) {
+  const size_t kTotalRetries = 10;
+  const std::string kEncryptedWrappedRecord = "TEST_INFO";
+
+  std::vector<TestUpload> uploads;
+  base::TimeDelta expected_delay_after = base::Seconds(10);
+  for (size_t i = 0; i < kTotalRetries; ++i) {
+    // Create new record with next seq id.
+    base::Value record_value =
+        GenerateSingleRecord(kEncryptedWrappedRecord, kPriority);
+
+    uploads.emplace_back(CreateTestUpload(record_value));
+    auto allowed_delay = uploads.back().configuration->WhenIsAllowedToProceed();
+    if (i == 0) {
+      EXPECT_FALSE(allowed_delay.is_positive());
+      // Next retry not before 10 sec.
+    } else {
+      EXPECT_THAT(allowed_delay, Ge(expected_delay_after));
+      // Double the expectation for the next upload.
+      expected_delay_after *= 2;
+      // Move forward to allow.
+      task_environment_.FastForwardBy(allowed_delay - base::Seconds(1));
+      EXPECT_TRUE(
+          uploads.back().configuration->WhenIsAllowedToProceed().is_positive());
+      task_environment_.FastForwardBy(base::Seconds(1));
+    }
+
+    EXPECT_FALSE(
+        uploads.back().configuration->WhenIsAllowedToProceed().is_positive());
+    uploads.back().configuration->AccountForAllowedJob();
+  }
+
+  // Now complete all created uploads.
+  for (auto& upload : uploads) {
+    EXPECT_CALL(*upload.completion_cb, Call(&upload.job, DM_STATUS_SUCCESS,
+                                            DeviceManagementService::kSuccess,
+                                            Eq(ByRef(upload.response))))
+        .Times(1);
+    upload.configuration->OnURLLoadComplete(
+        &upload.job, net::OK, DeviceManagementService::kSuccess,
+        ResponseValueBuilder::CreateResponseString(upload.response));
+  }
+}
+
+TEST_F(EncryptedReportingJobConfigurationTest,
+       SecurityUploadsSequenceNotThrottled) {
+  const size_t kTotalRetries = 10;
+  const std::string kEncryptedWrappedRecord = "TEST_INFO";
+
+  std::vector<TestUpload> uploads;
+  for (size_t i = 0; i < kTotalRetries; ++i) {
+    // Create new record with next seq id.
+    base::Value record_value = GenerateSingleRecord(
+        kEncryptedWrappedRecord, ::reporting::Priority::SECURITY);
+
+    uploads.emplace_back(CreateTestUpload(record_value));
+    auto allowed_delay = uploads.back().configuration->WhenIsAllowedToProceed();
+    EXPECT_FALSE(allowed_delay.is_positive());
+    uploads.back().configuration->AccountForAllowedJob();
+  }
+
+  // Now complete all created uploads.
+  for (auto& upload : uploads) {
+    EXPECT_CALL(*upload.completion_cb, Call(&upload.job, DM_STATUS_SUCCESS,
+                                            DeviceManagementService::kSuccess,
+                                            Eq(ByRef(upload.response))))
+        .Times(1);
+    upload.configuration->OnURLLoadComplete(
+        &upload.job, net::OK, DeviceManagementService::kSuccess,
+        ResponseValueBuilder::CreateResponseString(upload.response));
+  }
+}
+
+TEST_F(EncryptedReportingJobConfigurationTest, FailedUploadsSequenceThrottled) {
+  const size_t kTotalRetries = 10;
+  const std::string kEncryptedWrappedRecord = "TEST_INFO";
+
+  for (size_t i = 0; i < kTotalRetries; ++i) {
+    // Create new record with next seq id.
+    base::Value record_value =
+        GenerateSingleRecord(kEncryptedWrappedRecord, kPriority);
+
+    auto upload = CreateTestUpload(record_value);
+
+    EXPECT_CALL(*upload.completion_cb,
+                Call(&upload.job, DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID,
+                     DeviceManagementService::kInvalidAuthCookieOrDMToken,
+                     Eq(ByRef(upload.response))))
+        .Times(1);
+
+    auto allowed_delay = upload.configuration->WhenIsAllowedToProceed();
+    if (i == 0) {
+      // The very first upload is allowed.
+      EXPECT_FALSE(allowed_delay.is_positive());
+    } else {
+      EXPECT_THAT(allowed_delay, Ge(base::Days(1)));
+      // Move forward to allow.
+      task_environment_.FastForwardBy(allowed_delay - base::Seconds(1));
+      EXPECT_TRUE(upload.configuration->WhenIsAllowedToProceed().is_positive());
+      task_environment_.FastForwardBy(base::Seconds(1));
+    }
+
+    EXPECT_FALSE(upload.configuration->WhenIsAllowedToProceed().is_positive());
+    upload.configuration->AccountForAllowedJob();
+    upload.configuration->OnURLLoadComplete(
+        &upload.job, net::OK,
+        DeviceManagementService::kInvalidAuthCookieOrDMToken,
+        ResponseValueBuilder::CreateResponseString(upload.response));
+  }
+}
 }  // namespace policy
diff --git a/components/segmentation_platform/public/features.cc b/components/segmentation_platform/public/features.cc
index 0513d83..ba41ab3f 100644
--- a/components/segmentation_platform/public/features.cc
+++ b/components/segmentation_platform/public/features.cc
@@ -4,10 +4,19 @@
 
 #include "components/segmentation_platform/public/features.h"
 
-namespace segmentation_platform::features {
+#include "build/build_config.h"
 
-const base::Feature kSegmentationPlatformFeature{
-    "SegmentationPlatform", base::FEATURE_ENABLED_BY_DEFAULT};
+namespace segmentation_platform {
+namespace features {
+
+const base::Feature kSegmentationPlatformFeature {
+  "SegmentationPlatform",
+#if BUILDFLAG(IS_ANDROID)
+      base::FEATURE_ENABLED_BY_DEFAULT
+#else
+      base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
 
 const base::Feature kSegmentationPlatformDummyFeature{
     "SegmentationPlatformDummyFeature", base::FEATURE_DISABLED_BY_DEFAULT};
@@ -20,6 +29,7 @@
 
 const base::Feature kSegmentationPlatformLowEngagementFeature{
     "SegmentationPlatformLowEngagementFeature",
-    base::FEATURE_ENABLED_BY_DEFAULT};
+    base::FEATURE_DISABLED_BY_DEFAULT};
 
-}  // namespace segmentation_platform::features
+}  // namespace features
+}  // namespace segmentation_platform
diff --git a/components/services/unzip/BUILD.gn b/components/services/unzip/BUILD.gn
index a21e47ca..579d80d 100644
--- a/components/services/unzip/BUILD.gn
+++ b/components/services/unzip/BUILD.gn
@@ -62,6 +62,7 @@
     "//components/test/data/unzip_service/SJIS 13.zip",
     "//components/test/data/unzip_service/UTF8 (Bug 903664).zip",
     "//components/test/data/unzip_service/bad_archive.zip",
+    "//components/test/data/unzip_service/bug953599.zip",
     "//components/test/data/unzip_service/good_archive.zip",
   ]
   outputs = [ "{{bundle_resources_dir}}/" +
diff --git a/components/services/unzip/public/cpp/unzip_unittest.cc b/components/services/unzip/public/cpp/unzip_unittest.cc
index d1ae016..5289b2f 100644
--- a/components/services/unzip/public/cpp/unzip_unittest.cc
+++ b/components/services/unzip/public/cpp/unzip_unittest.cc
@@ -53,6 +53,12 @@
   return file_count;
 }
 
+base::FilePath GetFirstFilePath(const base::FilePath& dir) {
+  base::FileEnumerator file_enumerator(dir, /*recursive=*/true,
+                                       base::FileEnumerator::FILES);
+  return file_enumerator.Next();
+}
+
 class UnzipTest : public testing::Test {
  public:
   UnzipTest() = default;
@@ -245,6 +251,13 @@
   }
 }
 
+TEST_F(UnzipTest, DecodeExtendedHeader) {
+  EXPECT_TRUE(DoUnzip(GetArchivePath("bug953599.zip"), unzip_dir_));
+
+  base::FilePath fileName = GetFirstFilePath(unzip_dir_);
+  EXPECT_EQ(FILE_PATH_LITERAL("새 문서.txt"), fileName.BaseName().value());
+}
+
 TEST_F(UnzipTest, GetExtractedSize) {
   mojom::Size result = DoGetExtractedSize(GetArchivePath("good_archive.zip"));
   EXPECT_TRUE(result.is_valid);
diff --git a/components/signin/core/browser/mirror_landing_account_reconcilor_delegate.cc b/components/signin/core/browser/mirror_landing_account_reconcilor_delegate.cc
index 0fdffe5..6a5bfae 100644
--- a/components/signin/core/browser/mirror_landing_account_reconcilor_delegate.cc
+++ b/components/signin/core/browser/mirror_landing_account_reconcilor_delegate.cc
@@ -6,14 +6,19 @@
 
 #include "base/containers/contains.h"
 #include "base/logging.h"
+#include "components/signin/public/base/signin_metrics.h"
+#include "components/signin/public/identity_manager/accounts_mutator.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
 #include "google_apis/gaia/core_account_id.h"
 #include "google_apis/gaia/gaia_auth_fetcher.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 
 namespace signin {
 
-MirrorLandingAccountReconcilorDelegate::
-    MirrorLandingAccountReconcilorDelegate() = default;
+MirrorLandingAccountReconcilorDelegate::MirrorLandingAccountReconcilorDelegate(
+    IdentityManager* identity_manager,
+    bool is_main_profile)
+    : identity_manager_(identity_manager), is_main_profile_(is_main_profile) {}
 
 MirrorLandingAccountReconcilorDelegate::
     ~MirrorLandingAccountReconcilorDelegate() = default;
@@ -27,6 +32,22 @@
   return gaia::GaiaSource::kAccountReconcilorMirror;
 }
 
+bool MirrorLandingAccountReconcilorDelegate::ShouldRevokeTokensOnCookieDeleted()
+    const {
+  return !is_main_profile_ &&
+         !identity_manager_->HasPrimaryAccount(ConsentLevel::kSync);
+}
+
+void MirrorLandingAccountReconcilorDelegate::
+    OnAccountsCookieDeletedByUserAction(bool synced_data_deletion_in_progress) {
+  if (!ShouldRevokeTokensOnCookieDeleted())
+    return;
+
+  identity_manager_->GetAccountsMutator()->RemoveAllAccounts(
+      signin_metrics::SourceForRefreshTokenOperation::
+          kAccountReconcilor_GaiaCookiesDeletedByUser);
+}
+
 bool MirrorLandingAccountReconcilorDelegate::
     ShouldAbortReconcileIfPrimaryHasError() const {
   return false;
diff --git a/components/signin/core/browser/mirror_landing_account_reconcilor_delegate.h b/components/signin/core/browser/mirror_landing_account_reconcilor_delegate.h
index aa4a112..b020696 100644
--- a/components/signin/core/browser/mirror_landing_account_reconcilor_delegate.h
+++ b/components/signin/core/browser/mirror_landing_account_reconcilor_delegate.h
@@ -9,12 +9,15 @@
 
 namespace signin {
 
+class IdentityManager;
+
 // AccountReconcilorDelegate specialized for Mirror, using the "Mirror landing"
 // variant. Mirror is always enabled, even when there is no primary account.
 class MirrorLandingAccountReconcilorDelegate
     : public AccountReconcilorDelegate {
  public:
-  MirrorLandingAccountReconcilorDelegate();
+  MirrorLandingAccountReconcilorDelegate(IdentityManager* identity_manager,
+                                         bool is_main_profile);
   ~MirrorLandingAccountReconcilorDelegate() override;
 
   MirrorLandingAccountReconcilorDelegate(
@@ -27,6 +30,8 @@
   gaia::GaiaSource GetGaiaApiSource() const override;
   bool ShouldAbortReconcileIfPrimaryHasError() const override;
   ConsentLevel GetConsentLevelForPrimaryAccount() const override;
+  void OnAccountsCookieDeletedByUserAction(
+      bool synced_data_deletion_in_progress) override;
   std::vector<CoreAccountId> GetChromeAccountsForReconcile(
       const std::vector<CoreAccountId>& chrome_accounts,
       const CoreAccountId& primary_account,
@@ -34,6 +39,11 @@
       bool first_execution,
       bool primary_has_error,
       const gaia::MultiloginMode mode) const override;
+
+ private:
+  bool ShouldRevokeTokensOnCookieDeleted() const;
+  const raw_ptr<IdentityManager> identity_manager_;
+  const bool is_main_profile_;
 };
 
 }  // namespace signin
diff --git a/components/signin/core/browser/mirror_landing_account_reconcilor_delegate_unittest.cc b/components/signin/core/browser/mirror_landing_account_reconcilor_delegate_unittest.cc
index 31cff3b..c37a004 100644
--- a/components/signin/core/browser/mirror_landing_account_reconcilor_delegate_unittest.cc
+++ b/components/signin/core/browser/mirror_landing_account_reconcilor_delegate_unittest.cc
@@ -6,6 +6,8 @@
 
 #include <string>
 
+#include "base/test/task_environment.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "google_apis/gaia/core_account_id.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -28,6 +30,7 @@
 
 TEST(MirrorLandingAccountReconcilorDelegateTest,
      GetChromeAccountsForReconcile) {
+  base::test::TaskEnvironment task_environment;
   CoreAccountId kPrimaryAccountId = CoreAccountId::FromGaiaId("primary");
   CoreAccountId kOtherAccountId1 = CoreAccountId::FromGaiaId("1");
   CoreAccountId kOtherAccountId2 = CoreAccountId::FromGaiaId("2");
@@ -35,7 +38,8 @@
   gaia::ListedAccount gaia_account_1 = BuildListedAccount("1");
   gaia::ListedAccount gaia_account_2 = BuildListedAccount("2");
   gaia::ListedAccount gaia_account_3 = BuildListedAccount("3");
-  MirrorLandingAccountReconcilorDelegate delegate;
+  MirrorLandingAccountReconcilorDelegate delegate(
+      IdentityTestEnvironment().identity_manager(), /*is_main_profile=*/false);
   // No primary account. Gaia accounts are removed.
   EXPECT_TRUE(
       delegate
diff --git a/components/signin/internal/identity_manager/account_tracker_service_unittest.cc b/components/signin/internal/identity_manager/account_tracker_service_unittest.cc
index f3e41ad2..d7058bc 100644
--- a/components/signin/internal/identity_manager/account_tracker_service_unittest.cc
+++ b/components/signin/internal/identity_manager/account_tracker_service_unittest.cc
@@ -211,7 +211,8 @@
   void SetUp() override {
     testing::Test::SetUp();
     CreateAccountTracker(base::FilePath(), /*network_enabled=*/true);
-    fake_oauth2_token_service_.LoadCredentials(CoreAccountId());
+    fake_oauth2_token_service_.LoadCredentials(CoreAccountId(),
+                                               /*is_syncing=*/false);
   }
 
   void TearDown() override {
diff --git a/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc b/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc
index 0d9458f..c54f007b 100644
--- a/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc
+++ b/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc
@@ -103,7 +103,8 @@
 }
 
 void FakeProfileOAuth2TokenServiceDelegate::LoadCredentials(
-    const CoreAccountId& primary_account_id) {
+    const CoreAccountId& primary_account_id,
+    bool is_syncing) {
   set_load_credentials_state(
       signin::LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS);
   FireRefreshTokensLoaded();
diff --git a/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.h b/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.h
index 8cc9a88..8aa89396 100644
--- a/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.h
+++ b/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.h
@@ -45,7 +45,8 @@
                        const GoogleServiceAuthError& error) override;
   std::vector<CoreAccountId> GetAccounts() const override;
   void RevokeAllCredentials() override;
-  void LoadCredentials(const CoreAccountId& primary_account_id) override;
+  void LoadCredentials(const CoreAccountId& primary_account_id,
+                       bool is_syncing) override;
   void UpdateCredentials(const CoreAccountId& account_id,
                          const std::string& refresh_token) override;
   void RevokeCredentials(const CoreAccountId& account_id) override;
diff --git a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc
index 791a211..4d5dc82 100644
--- a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc
+++ b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc
@@ -367,7 +367,8 @@
 }
 
 void MutableProfileOAuth2TokenServiceDelegate::LoadCredentials(
-    const CoreAccountId& primary_account_id) {
+    const CoreAccountId& primary_account_id,
+    bool is_syncing) {
   if (load_credentials_state() ==
       signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS) {
     VLOG(1) << "Load credentials operation already in progress";
diff --git a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h
index ef9951b7..822e4e689 100644
--- a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h
+++ b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h
@@ -71,7 +71,8 @@
   std::vector<CoreAccountId> GetAccounts() const override;
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory()
       const override;
-  void LoadCredentials(const CoreAccountId& primary_account_id) override;
+  void LoadCredentials(const CoreAccountId& primary_account_id,
+                       bool is_syncing) override;
   void UpdateCredentials(const CoreAccountId& account_id,
                          const std::string& refresh_token) override;
   void RevokeAllCredentials() override;
diff --git a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc
index 794ebcc..8d4ae409 100644
--- a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc
+++ b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc
@@ -280,7 +280,8 @@
                        main_refresh_token);
 
   // Force LoadCredentials.
-  oauth2_service_delegate_->LoadCredentials(main_account_id);
+  oauth2_service_delegate_->LoadCredentials(main_account_id,
+                                            /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   // Legacy tokens get discarded, but the old refresh token is kept.
@@ -309,7 +310,8 @@
   ResetObserverCounts();
 
   // Force LoadCredentials.
-  oauth2_service_delegate_->LoadCredentials(main_account_id);
+  oauth2_service_delegate_->LoadCredentials(main_account_id,
+                                            /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   // Again legacy tokens get discarded, but since the main porfile account
@@ -376,7 +378,8 @@
 
   EXPECT_EQ(signin::LoadCredentialsState::LOAD_CREDENTIALS_NOT_STARTED,
             oauth2_service_delegate_->load_credentials_state());
-  oauth2_service_delegate_->LoadCredentials(CoreAccountId());
+  oauth2_service_delegate_->LoadCredentials(CoreAccountId(),
+                                            /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(
       signin::LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS,
@@ -417,7 +420,8 @@
 
   AddAuthTokenManually("AccountId-" + account1.ToString(), "refresh_token");
   AddAuthTokenManually("AccountId-" + account2.ToString(), "refresh_token");
-  oauth2_service_delegate_->LoadCredentials(CoreAccountId());
+  oauth2_service_delegate_->LoadCredentials(CoreAccountId(),
+                                            /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(1, tokens_loaded_count_);
@@ -444,7 +448,7 @@
   // Perform a load from an empty DB.
   EXPECT_EQ(signin::LoadCredentialsState::LOAD_CREDENTIALS_NOT_STARTED,
             oauth2_service_delegate_->load_credentials_state());
-  oauth2_service_delegate_->LoadCredentials(account_id);
+  oauth2_service_delegate_->LoadCredentials(account_id, /*is_syncing=*/false);
   EXPECT_EQ(signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS,
             oauth2_service_delegate_->load_credentials_state());
   base::RunLoop().RunUntilIdle();
@@ -482,7 +486,7 @@
   EXPECT_EQ(2, auth_error_changed_count_);
   ResetObserverCounts();
 
-  oauth2_service_delegate_->LoadCredentials(account_id);
+  oauth2_service_delegate_->LoadCredentials(account_id, /*is_syncing=*/false);
   EXPECT_EQ(signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS,
             oauth2_service_delegate_->load_credentials_state());
   base::RunLoop().RunUntilIdle();
@@ -522,7 +526,8 @@
   // Perform a load from an empty DB.
   EXPECT_EQ(signin::LoadCredentialsState::LOAD_CREDENTIALS_NOT_STARTED,
             oauth2_service_delegate_->load_credentials_state());
-  oauth2_service_delegate_->LoadCredentials(CoreAccountId());
+  oauth2_service_delegate_->LoadCredentials(CoreAccountId(),
+                                            /*is_syncing=*/false);
   EXPECT_EQ(signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS,
             oauth2_service_delegate_->load_credentials_state());
   base::RunLoop().RunUntilIdle();
@@ -545,7 +550,8 @@
   EXPECT_EQ(2, auth_error_changed_count_);
   ResetObserverCounts();
 
-  oauth2_service_delegate_->LoadCredentials(CoreAccountId());
+  oauth2_service_delegate_->LoadCredentials(CoreAccountId(),
+                                            /*is_syncing=*/false);
   EXPECT_EQ(signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS,
             oauth2_service_delegate_->load_credentials_state());
   base::RunLoop().RunUntilIdle();
@@ -584,7 +590,8 @@
   AddAuthTokenManually("AccountId-" + primary_account.account_id.ToString(),
                        "refresh_token");
 
-  oauth2_service_delegate_->LoadCredentials(CoreAccountId());
+  oauth2_service_delegate_->LoadCredentials(CoreAccountId(),
+                                            /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable(
@@ -606,7 +613,8 @@
   // Shutdown the database to trigger a database read error.
   token_web_data_->ShutdownDatabase();
 
-  oauth2_service_delegate_->LoadCredentials(CoreAccountId());
+  oauth2_service_delegate_->LoadCredentials(CoreAccountId(),
+                                            /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(0u, oauth2_service_delegate_->GetAccounts().size());
@@ -625,7 +633,7 @@
 
   InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled);
   oauth2_service_delegate_->LoadCredentials(
-      /*primary_account_id=*/CoreAccountId());
+      /*primary_account_id=*/CoreAccountId(), /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   // No tokens were loaded.
@@ -1080,7 +1088,7 @@
   EXPECT_EQ(2u, accounts.size());
   EXPECT_EQ(1, count(accounts.begin(), accounts.end(), account_id1));
   EXPECT_EQ(1, count(accounts.begin(), accounts.end(), account_id2));
-  oauth2_service_delegate_->LoadCredentials(account_id1);
+  oauth2_service_delegate_->LoadCredentials(account_id1, /*is_syncing=*/false);
   oauth2_service_delegate_->UpdateCredentials(account_id1, "refresh_token3");
   oauth2_service_delegate_->Shutdown();
   EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty());
@@ -1110,7 +1118,8 @@
     account_tracker_service_.ResetForTesting();
 
     AddAuthTokenManually("AccountId-" + email, "refresh_token");
-    oauth2_service_delegate_->LoadCredentials(acc_id_gaia_id);
+    oauth2_service_delegate_->LoadCredentials(acc_id_gaia_id,
+                                              /*is_syncing=*/false);
     base::RunLoop().RunUntilIdle();
 
     EXPECT_EQ(1, tokens_loaded_count_);
@@ -1130,7 +1139,8 @@
     oauth2_service_delegate_->Shutdown();
     ResetObserverCounts();
 
-    oauth2_service_delegate_->LoadCredentials(acc_id_gaia_id);
+    oauth2_service_delegate_->LoadCredentials(acc_id_gaia_id,
+                                              /*is_syncing=*/false);
     base::RunLoop().RunUntilIdle();
 
     EXPECT_EQ(1, tokens_loaded_count_);
@@ -1180,7 +1190,7 @@
     AddAuthTokenManually("AccountId-" + email1, "refresh_token");
     AddAuthTokenManually("AccountId-" + email2, "refresh_token");
     AddAuthTokenManually("AccountId-" + gaia_id1, "refresh_token");
-    oauth2_service_delegate_->LoadCredentials(acc_gaia1);
+    oauth2_service_delegate_->LoadCredentials(acc_gaia1, /*is_syncing=*/false);
     base::RunLoop().RunUntilIdle();
 
     EXPECT_EQ(1, tokens_loaded_count_);
@@ -1200,7 +1210,7 @@
     oauth2_service_delegate_->Shutdown();
     ResetObserverCounts();
 
-    oauth2_service_delegate_->LoadCredentials(acc_gaia1);
+    oauth2_service_delegate_->LoadCredentials(acc_gaia1, /*is_syncing=*/false);
     base::RunLoop().RunUntilIdle();
 
     EXPECT_EQ(1, tokens_loaded_count_);
@@ -1228,7 +1238,8 @@
                        "refresh_token");
   AddAuthTokenManually("AccountId-" + secondary_account.ToString(),
                        "refresh_token");
-  oauth2_service_delegate_->LoadCredentials(primary_account);
+  oauth2_service_delegate_->LoadCredentials(primary_account,
+                                            /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(1, tokens_loaded_count_);
@@ -1387,7 +1398,8 @@
                        "refresh_token");
   AddAuthTokenManually("AccountId-" + secondary_account.ToString(),
                        "refresh_token");
-  oauth2_service_delegate_->LoadCredentials(primary_account);
+  oauth2_service_delegate_->LoadCredentials(primary_account,
+                                            /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(1, tokens_loaded_count_);
@@ -1415,7 +1427,8 @@
   // Check that the changes have been persisted in the database: tokens are not
   // revoked again on the server.
   client_->SetNetworkCallsDelayed(true);
-  oauth2_service_delegate_->LoadCredentials(primary_account);
+  oauth2_service_delegate_->LoadCredentials(primary_account,
+                                            /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(
       oauth2_service_delegate_->RefreshTokenIsAvailable(primary_account));
@@ -1451,7 +1464,7 @@
   {
     base::HistogramTester h_tester;
     AddAuthTokenManually("account_id", "refresh_token");
-    token_service.LoadCredentials(account_id);
+    token_service.LoadCredentials(account_id, /*is_syncing=*/false);
     base::RunLoop().RunUntilIdle();
 
     EXPECT_EQ("TokenService::LoadCredentials",
@@ -1512,7 +1525,8 @@
 
 TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, ExtractCredentials) {
   InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDice);
-  oauth2_service_delegate_->LoadCredentials(CoreAccountId());
+  oauth2_service_delegate_->LoadCredentials(CoreAccountId(),
+                                            /*is_syncing=*/false);
   const CoreAccountId account_id("account_id");
 
   // Create another token service
@@ -1522,7 +1536,7 @@
       std::make_unique<FakeProfileOAuth2TokenServiceDelegate>();
   FakeProfileOAuth2TokenServiceDelegate* other_delegate = delegate.get();
   ProfileOAuth2TokenService other_token_service(&prefs, std::move(delegate));
-  other_token_service.LoadCredentials(CoreAccountId());
+  other_token_service.LoadCredentials(CoreAccountId(), /*is_syncing=*/false);
 
   // Add credentials to the first token service delegate.
   oauth2_service_delegate_->UpdateCredentials(account_id, "token");
diff --git a/components/signin/internal/identity_manager/primary_account_manager.cc b/components/signin/internal/identity_manager/primary_account_manager.cc
index 9d0a7c7..428394b 100644
--- a/components/signin/internal/identity_manager/primary_account_manager.cc
+++ b/components/signin/internal/identity_manager/primary_account_manager.cc
@@ -120,7 +120,8 @@
   // token service.
   token_service_->AddObserver(this);
   token_service_->LoadCredentials(
-      GetPrimaryAccountId(signin::ConsentLevel::kSignin));
+      GetPrimaryAccountId(signin::ConsentLevel::kSignin),
+      HasPrimaryAccount(signin::ConsentLevel::kSync));
 }
 
 bool PrimaryAccountManager::IsInitialized() const {
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service.cc
index a98fecb0..44dea91 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service.cc
@@ -255,12 +255,13 @@
 }
 
 void ProfileOAuth2TokenService::LoadCredentials(
-    const CoreAccountId& primary_account_id) {
+    const CoreAccountId& primary_account_id,
+    bool is_syncing) {
   DCHECK_EQ(SourceForRefreshTokenOperation::kUnknown,
             update_refresh_token_source_);
   update_refresh_token_source_ =
       SourceForRefreshTokenOperation::kTokenService_LoadCredentials;
-  GetDelegate()->LoadCredentials(primary_account_id);
+  GetDelegate()->LoadCredentials(primary_account_id, is_syncing);
 }
 
 void ProfileOAuth2TokenService::UpdateCredentials(
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service.h b/components/signin/internal/identity_manager/profile_oauth2_token_service.h
index 2177cc9..a9bbef2 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service.h
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service.h
@@ -187,7 +187,9 @@
   // For a regular profile, the primary account id comes from
   // PrimaryAccountManager.
   // For a supervised user, the id comes from SupervisedUserService.
-  void LoadCredentials(const CoreAccountId& primary_account_id);
+  // |is_syncing| whether the primary account has sync consent.
+  void LoadCredentials(const CoreAccountId& primary_account_id,
+                       bool is_syncing);
 
   // Returns true if LoadCredentials finished with no errors.
   bool HasLoadCredentialsFinishedWithNoErrors();
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
index de96128..79f98e6 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
@@ -59,10 +59,17 @@
     AccountTrackerService* account_tracker_service,
     network::NetworkConnectionTracker* network_connection_tracker,
     account_manager::AccountManagerFacade* account_manager_facade,
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    bool delete_signin_cookies_on_exit,
+#endif
     bool is_regular_profile) {
   return std::make_unique<signin::ProfileOAuth2TokenServiceDelegateChromeOS>(
       signin_client, account_tracker_service, network_connection_tracker,
-      account_manager_facade, is_regular_profile);
+      account_manager_facade,
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+      delete_signin_cookies_on_exit,
+#endif
+      is_regular_profile);
 }
 #elif BUILDFLAG(ENABLE_DICE_SUPPORT)
 
@@ -105,8 +112,10 @@
     account_manager::AccountManagerFacade* account_manager_facade,
     bool is_regular_profile,
 #endif  // BUILDFLAG(IS_CHROMEOS)
-#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
     bool delete_signin_cookies_on_exit,
+#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
     scoped_refptr<TokenWebData> token_web_data,
 #endif
 #if BUILDFLAG(IS_IOS)
@@ -126,7 +135,11 @@
 #elif BUILDFLAG(IS_CHROMEOS)
   return CreateCrOsOAuthDelegate(signin_client, account_tracker_service,
                                  network_connection_tracker,
-                                 account_manager_facade, is_regular_profile);
+                                 account_manager_facade,
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+                                 delete_signin_cookies_on_exit,
+#endif
+                                 is_regular_profile);
 #elif BUILDFLAG(ENABLE_DICE_SUPPORT)
   // Fall back to |MutableProfileOAuth2TokenServiceDelegate| on all platforms
   // other than Android, iOS, and Chrome OS (Ash and Lacros).
@@ -154,10 +167,12 @@
     account_manager::AccountManagerFacade* account_manager_facade,
     bool is_regular_profile,
 #endif  // BUILDFLAG(IS_CHROMEOS)
-#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
     bool delete_signin_cookies_on_exit,
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
     scoped_refptr<TokenWebData> token_web_data,
-#endif
+#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 #if BUILDFLAG(IS_IOS)
     std::unique_ptr<DeviceAccountsProvider> device_accounts_provider,
 #endif
@@ -182,8 +197,11 @@
 #if BUILDFLAG(IS_CHROMEOS)
           account_manager_facade, is_regular_profile,
 #endif  // BUILDFLAG(IS_CHROMEOS)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
+          delete_signin_cookies_on_exit,
+#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
-          delete_signin_cookies_on_exit, token_web_data,
+          token_web_data,
 #endif
 #if BUILDFLAG(IS_IOS)
           std::move(device_accounts_provider),
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h
index 0031a7c..85978f99 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h
@@ -9,6 +9,7 @@
 
 #include "build/build_config.h"
 #include "build/buildflag.h"
+#include "build/chromeos_buildflags.h"
 #include "components/signin/public/base/signin_buildflags.h"
 
 #if !BUILDFLAG(IS_ANDROID)
@@ -55,10 +56,12 @@
     account_manager::AccountManagerFacade* account_manager_facade,
     bool is_regular_profile,
 #endif  // BUILDFLAG(IS_CHROMEOS)
-#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
     bool delete_signin_cookies_on_exit,
+#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
     scoped_refptr<TokenWebData> token_web_data,
-#endif
+#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 #if BUILDFLAG(IS_IOS)
     std::unique_ptr<DeviceAccountsProvider> device_accounts_provider,
 #endif
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.cc
index d73c7e1..925866b9 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.cc
@@ -123,7 +123,8 @@
 }
 
 void ProfileOAuth2TokenServiceDelegate::LoadCredentials(
-    const CoreAccountId& primary_account_id) {
+    const CoreAccountId& primary_account_id,
+    bool is_syncing) {
   NOTREACHED()
       << "ProfileOAuth2TokenServiceDelegate does not load credentials. "
          "Subclasses that need to load credentials must provide "
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h
index 7cbe170..fb6cfe1 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h
@@ -116,7 +116,8 @@
   // is initialized. Default implementation is NOTREACHED - subsclasses that
   // are used by the ProfileOAuth2TokenService must provide an implementation
   // for this method.
-  virtual void LoadCredentials(const CoreAccountId& primary_account_id);
+  virtual void LoadCredentials(const CoreAccountId& primary_account_id,
+                               bool is_syncing);
 
   // Returns the state of the load credentials operation.
   signin::LoadCredentialsState load_credentials_state() const {
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc
index ef5e58dc..31283be 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc
@@ -451,7 +451,8 @@
 }
 
 void ProfileOAuth2TokenServiceDelegateAndroid::LoadCredentials(
-    const CoreAccountId& primary_account_id) {
+    const CoreAccountId& primary_account_id,
+    bool is_syncing) {
   DCHECK_EQ(signin::LoadCredentialsState::LOAD_CREDENTIALS_NOT_STARTED,
             load_credentials_state());
   set_load_credentials_state(
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.h b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.h
index 4fddcb3c..e452292 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.h
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.h
@@ -54,7 +54,8 @@
   // POA2TService aware accounts.
   void RevokeAllCredentials() override;
 
-  void LoadCredentials(const CoreAccountId& primary_account_id) override;
+  void LoadCredentials(const CoreAccountId& primary_account_id,
+                       bool is_syncing) override;
 
   void ReloadAllAccountsFromSystemWithPrimaryAccount(
       const absl::optional<CoreAccountId>& primary_account_id) override;
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.cc
index 21c577a..9b5045e 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.cc
@@ -143,6 +143,9 @@
         AccountTrackerService* account_tracker_service,
         network::NetworkConnectionTracker* network_connection_tracker,
         account_manager::AccountManagerFacade* account_manager_facade,
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+        bool delete_signin_cookies_on_exit,
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
         bool is_regular_profile)
     : signin_client_(signin_client),
       account_tracker_service_(account_tracker_service),
@@ -150,6 +153,9 @@
       account_manager_facade_(account_manager_facade),
       backoff_entry_(&kBackoffPolicy),
       backoff_error_(GoogleServiceAuthError::NONE),
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+      delete_signin_cookies_on_exit_(delete_signin_cookies_on_exit),
+#endif
       is_regular_profile_(is_regular_profile),
       weak_factory_(this) {
   network_connection_tracker_->AddNetworkConnectionObserver(this);
@@ -285,7 +291,8 @@
 }
 
 void ProfileOAuth2TokenServiceDelegateChromeOS::LoadCredentials(
-    const CoreAccountId& primary_account_id) {
+    const CoreAccountId& primary_account_id,
+    bool is_syncing) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (load_credentials_state() !=
@@ -311,6 +318,20 @@
 
   DCHECK(account_manager_facade_);
   account_manager_facade_->AddObserver(this);
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // On Lacros, signin cookies can only be cleared for non-syncing
+  // secondary profiles, because:
+  // - the main profile cannot be signed out,
+  // - clearing cookie does not turn sync off
+  // Additionally, there is no way for Chrome to "invalidate" a token. In
+  // particular, the "sync paused" state does not exist.
+  bool revoke_all_tokens =
+      delete_signin_cookies_on_exit_ && !is_syncing &&
+      !signin_client_->GetInitialPrimaryAccount().has_value();
+  if (revoke_all_tokens)
+    RevokeAllCredentials();
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
   account_manager_facade_->GetAccounts(
       base::BindOnce(&ProfileOAuth2TokenServiceDelegateChromeOS::OnGetAccounts,
                      weak_factory_.GetWeakPtr()));
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.h b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.h
index 76d3e6b..eb6487e 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.h
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.h
@@ -36,6 +36,11 @@
       AccountTrackerService* account_tracker_service,
       network::NetworkConnectionTracker* network_connection_tracker,
       account_manager::AccountManagerFacade* account_manager_facade,
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+      // |delete_signin_cookies_on_exit|  is used on startup, in case the
+      // cookies were not properly cleared on last exit.
+      bool delete_signin_cookies_on_exit,
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
       bool is_regular_profile);
 
   ProfileOAuth2TokenServiceDelegateChromeOS(
@@ -59,7 +64,8 @@
   GoogleServiceAuthError GetAuthError(
       const CoreAccountId& account_id) const override;
   std::vector<CoreAccountId> GetAccounts() const override;
-  void LoadCredentials(const CoreAccountId& primary_account_id) override;
+  void LoadCredentials(const CoreAccountId& primary_account_id,
+                       bool is_syncing) override;
   void UpdateCredentials(const CoreAccountId& account_id,
                          const std::string& refresh_token) override;
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory()
@@ -123,6 +129,10 @@
   net::BackoffEntry backoff_entry_;
   GoogleServiceAuthError backoff_error_;
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  const bool delete_signin_cookies_on_exit_;
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
   // Is |this| attached to a regular (non-Signin && non-LockScreen) Profile.
   const bool is_regular_profile_;
 
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos_unittest.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos_unittest.cc
index 66bfecf..bb58f28 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos_unittest.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos_unittest.cc
@@ -223,7 +223,11 @@
     delegate_ = std::make_unique<ProfileOAuth2TokenServiceDelegateChromeOS>(
         client_.get(), &account_tracker_service_,
         network::TestNetworkConnectionTracker::GetInstance(),
-        account_manager_facade_.get(), true /* is_regular_profile */);
+        account_manager_facade_.get(),
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+        /*delete_signin_cookies_on_exit=*/false,
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+        /*is_regular_profile=*/true);
 
     LoadCredentialsAndWaitForCompletion(
         account_info_.account_id /* primary_account_id */);
@@ -270,7 +274,7 @@
     base::RunLoop run_loop;
     EXPECT_CALL(observer, OnRefreshTokensLoaded())
         .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
-    delegate_->LoadCredentials(primary_account_id);
+    delegate_->LoadCredentials(primary_account_id, /*is_syncing=*/false);
     run_loop.Run();
   }
 
@@ -378,12 +382,17 @@
   auto delegate = std::make_unique<ProfileOAuth2TokenServiceDelegateChromeOS>(
       client_.get(), &account_tracker_service_,
       network::TestNetworkConnectionTracker::GetInstance(),
-      account_manager_facade_.get(), false /* is_regular_profile */);
+      account_manager_facade_.get(),
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+      /*delete_signin_cookies_on_exit=*/false,
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+      /*is_regular_profile=*/false);
   TestOAuth2TokenServiceObserver observer(delegate.get());
 
   // Test that LoadCredentials works as expected.
   EXPECT_FALSE(observer.refresh_tokens_loaded_);
-  delegate->LoadCredentials(CoreAccountId() /* primary_account_id */);
+  delegate->LoadCredentials(CoreAccountId() /* primary_account_id */,
+                            /*is_syncing=*/false);
   EXPECT_TRUE(observer.refresh_tokens_loaded_);
   EXPECT_EQ(LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS,
             delegate->load_credentials_state());
@@ -601,8 +610,13 @@
   auto delegate = std::make_unique<ProfileOAuth2TokenServiceDelegateChromeOS>(
       client_.get(), &account_tracker_service_,
       network::TestNetworkConnectionTracker::GetInstance(),
-      account_manager_facade.get(), true /* is_regular_profile */);
-  delegate->LoadCredentials(account1.account_id /* primary_account_id */);
+      account_manager_facade.get(),
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+      /*delete_signin_cookies_on_exit=*/false,
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS
+      /*is_regular_profile=*/true);
+  delegate->LoadCredentials(account1.account_id /* primary_account_id */,
+                            /*is_syncing=*/false);
   TestOAuth2TokenServiceObserver observer(delegate.get());
   // Wait until AccountManager is fully initialized.
   task_environment_.RunUntilIdle();
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.h b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.h
index f33ec8a..1750fde 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.h
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.h
@@ -46,7 +46,8 @@
   void UpdateAuthError(const CoreAccountId& account_id,
                        const GoogleServiceAuthError& error) override;
 
-  void LoadCredentials(const CoreAccountId& primary_account_id) override;
+  void LoadCredentials(const CoreAccountId& primary_account_id,
+                       bool is_syncing) override;
   std::vector<CoreAccountId> GetAccounts() const override;
 
   // This method should not be called when using shared authentication.
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.mm b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.mm
index 83622a0a..3b4d9bf 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.mm
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.mm
@@ -178,7 +178,8 @@
 }
 
 void ProfileOAuth2TokenServiceIOSDelegate::LoadCredentials(
-    const CoreAccountId& primary_account_id) {
+    const CoreAccountId& primary_account_id,
+    bool is_syncing) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   DCHECK_EQ(signin::LoadCredentialsState::LOAD_CREDENTIALS_NOT_STARTED,
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios_unittest.mm b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios_unittest.mm
index 016d758..dc5428c3 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios_unittest.mm
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios_unittest.mm
@@ -117,7 +117,8 @@
 TEST_F(ProfileOAuth2TokenServiceIOSDelegateTest,
        LoadRevokeCredentialsOneAccount) {
   ProviderAccount account = fake_provider_->AddAccount("gaia_1", "email_1@x");
-  oauth2_delegate_->LoadCredentials(GetAccountId(account));
+  oauth2_delegate_->LoadCredentials(GetAccountId(account),
+                                    /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, token_available_count_);
   EXPECT_EQ(1, tokens_loaded_count_);
@@ -140,7 +141,8 @@
   ProviderAccount account1 = fake_provider_->AddAccount("gaia_1", "email_1@x");
   ProviderAccount account2 = fake_provider_->AddAccount("gaia_2", "email_2@x");
   ProviderAccount account3 = fake_provider_->AddAccount("gaia_3", "email_3@x");
-  oauth2_delegate_->LoadCredentials(GetAccountId(account1));
+  oauth2_delegate_->LoadCredentials(GetAccountId(account1),
+                                    /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(3, token_available_count_);
   EXPECT_EQ(1, tokens_loaded_count_);
@@ -171,7 +173,7 @@
        LoadCredentialsPrimaryAccountMissing) {
   CoreAccountId primary_account =
       account_tracker_.SeedAccountInfo("gaia_1", "email_1@x");
-  oauth2_delegate_->LoadCredentials(primary_account);
+  oauth2_delegate_->LoadCredentials(primary_account, /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, token_available_count_);
   EXPECT_EQ(1, tokens_loaded_count_);
@@ -202,7 +204,8 @@
   ProviderAccount account1 = fake_provider_->AddAccount("gaia_1", "email_1@x");
   ProviderAccount account2 = fake_provider_->AddAccount("gaia_2", "email_2@x");
   ProviderAccount account3 = fake_provider_->AddAccount("gaia_3", "email_3@x");
-  oauth2_delegate_->LoadCredentials(GetAccountId(account1));
+  oauth2_delegate_->LoadCredentials(GetAccountId(account1),
+                                    /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   // Change the accounts.
@@ -246,7 +249,8 @@
 
 TEST_F(ProfileOAuth2TokenServiceIOSDelegateTest, StartRequestSuccess) {
   ProviderAccount account1 = fake_provider_->AddAccount("gaia_1", "email_1@x");
-  oauth2_delegate_->LoadCredentials(GetAccountId(account1));
+  oauth2_delegate_->LoadCredentials(GetAccountId(account1),
+                                    /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   // Fetch access tokens.
@@ -270,7 +274,8 @@
 
 TEST_F(ProfileOAuth2TokenServiceIOSDelegateTest, StartRequestFailure) {
   ProviderAccount account1 = fake_provider_->AddAccount("gaia_1", "email_1@x");
-  oauth2_delegate_->LoadCredentials(GetAccountId(account1));
+  oauth2_delegate_->LoadCredentials(GetAccountId(account1),
+                                    /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   // Fetch access tokens.
@@ -297,7 +302,8 @@
 TEST_F(ProfileOAuth2TokenServiceIOSDelegateTest,
        UpdateAuthErrorAfterRevokeCredentials) {
   ProviderAccount account1 = fake_provider_->AddAccount("gaia_1", "email_1@x");
-  oauth2_delegate_->LoadCredentials(GetAccountId(account1));
+  oauth2_delegate_->LoadCredentials(GetAccountId(account1),
+                                    /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
 
   ResetObserverCounts();
@@ -315,7 +321,8 @@
 TEST_F(ProfileOAuth2TokenServiceIOSDelegateTest, GetAuthError) {
   // Accounts have no error by default.
   ProviderAccount account1 = fake_provider_->AddAccount("gaia_1", "email_1@x");
-  oauth2_delegate_->LoadCredentials(GetAccountId(account1));
+  oauth2_delegate_->LoadCredentials(GetAccountId(account1),
+                                    /*is_syncing=*/false);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(),
             oauth2_delegate_->GetAuthError(GetAccountId(account1)));
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_unittest.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_unittest.cc
index 91cb7d1..4bb13098 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_unittest.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_unittest.cc
@@ -155,7 +155,8 @@
   EXPECT_TRUE(accounts.empty());
 
   // Load tokens from disk.
-  oauth2_service_->GetDelegate()->LoadCredentials(CoreAccountId());
+  oauth2_service_->GetDelegate()->LoadCredentials(CoreAccountId(),
+                                                  /*is_syncing=*/false);
 
   // |account_id_| should now be visible in the accounts.
   accounts = oauth2_service_->GetAccounts();
diff --git a/components/signin/internal/identity_manager/test_profile_oauth2_token_service_delegate_chromeos.cc b/components/signin/internal/identity_manager/test_profile_oauth2_token_service_delegate_chromeos.cc
index 74dd41c..2742c8f 100644
--- a/components/signin/internal/identity_manager/test_profile_oauth2_token_service_delegate_chromeos.cc
+++ b/components/signin/internal/identity_manager/test_profile_oauth2_token_service_delegate_chromeos.cc
@@ -76,7 +76,8 @@
 }
 
 void TestProfileOAuth2TokenServiceDelegateChromeOS::LoadCredentials(
-    const CoreAccountId& primary_account_id) {
+    const CoreAccountId& primary_account_id,
+    bool is_syncing) {
   // In tests |LoadCredentials| may be called twice, in this case we call
   // |FireRefreshTokensLoaded| again to notify that credentials are loaded.
   if (load_credentials_state() ==
@@ -92,7 +93,7 @@
 
   set_load_credentials_state(
       signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS);
-  delegate_->LoadCredentials(primary_account_id);
+  delegate_->LoadCredentials(primary_account_id, is_syncing);
 }
 
 void TestProfileOAuth2TokenServiceDelegateChromeOS::UpdateCredentials(
diff --git a/components/signin/internal/identity_manager/test_profile_oauth2_token_service_delegate_chromeos.h b/components/signin/internal/identity_manager/test_profile_oauth2_token_service_delegate_chromeos.h
index 6a990db..a37f65f6 100644
--- a/components/signin/internal/identity_manager/test_profile_oauth2_token_service_delegate_chromeos.h
+++ b/components/signin/internal/identity_manager/test_profile_oauth2_token_service_delegate_chromeos.h
@@ -50,7 +50,8 @@
   GoogleServiceAuthError GetAuthError(
       const CoreAccountId& account_id) const override;
   std::vector<CoreAccountId> GetAccounts() const override;
-  void LoadCredentials(const CoreAccountId& primary_account_id) override;
+  void LoadCredentials(const CoreAccountId& primary_account_id,
+                       bool is_syncing) override;
   void UpdateCredentials(const CoreAccountId& account_id,
                          const std::string& refresh_token) override;
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory()
diff --git a/components/signin/public/identity_manager/access_token_constants.cc b/components/signin/public/identity_manager/access_token_constants.cc
index c7bb097e..b8ea2e1 100644
--- a/components/signin/public/identity_manager/access_token_constants.cc
+++ b/components/signin/public/identity_manager/access_token_constants.cc
@@ -46,6 +46,7 @@
 
       // Required by cloud policy.
       GaiaConstants::kDeviceManagementServiceOAuth,
+      GaiaConstants::kSecureConnectOAuth2Scope,
 
       // Required by Permission Request Creator.
       GaiaConstants::kClassifyUrlKidPermissionOAuth2Scope,
diff --git a/components/signin/public/identity_manager/identity_manager_builder.cc b/components/signin/public/identity_manager/identity_manager_builder.cc
index ccf4942..ad42d047 100644
--- a/components/signin/public/identity_manager/identity_manager_builder.cc
+++ b/components/signin/public/identity_manager/identity_manager_builder.cc
@@ -115,9 +115,12 @@
 #if BUILDFLAG(IS_CHROMEOS)
           params->account_manager_facade, params->is_regular_profile,
 #endif  // BUILDFLAG(IS_CHROMEOS)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
+          params->delete_signin_cookies_on_exit,
+#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT) ||  BUILDFLAG(IS_CHROMEOS_LACROS)
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
-          params->delete_signin_cookies_on_exit, params->token_web_data,
-#endif
+          params->token_web_data,
+#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 #if BUILDFLAG(IS_IOS)
           std::move(params->device_accounts_provider),
 #endif
diff --git a/components/signin/public/identity_manager/identity_manager_builder.h b/components/signin/public/identity_manager/identity_manager_builder.h
index ab28b316..0cdab6f 100644
--- a/components/signin/public/identity_manager/identity_manager_builder.h
+++ b/components/signin/public/identity_manager/identity_manager_builder.h
@@ -62,8 +62,11 @@
   base::FilePath profile_path;
   raw_ptr<SigninClient> signin_client = nullptr;
 
-#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+#if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
   bool delete_signin_cookies_on_exit = false;
+#endif
+
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
   scoped_refptr<TokenWebData> token_web_data;
 #endif
 
diff --git a/components/signin/public/identity_manager/identity_manager_unittest.cc b/components/signin/public/identity_manager/identity_manager_unittest.cc
index 03e336d6..d9f0f91 100644
--- a/components/signin/public/identity_manager/identity_manager_unittest.cc
+++ b/components/signin/public/identity_manager/identity_manager_unittest.cc
@@ -1725,7 +1725,7 @@
   // we fake the credentials loaded state and force another load in
   // order to be able to capture the TokensLoaded event.
   token_service()->set_all_credentials_loaded_for_testing(false);
-  token_service()->LoadCredentials(CoreAccountId());
+  token_service()->LoadCredentials(CoreAccountId(), /*is_syncing=*/false);
   run_loop.Run();
 }
 
@@ -2352,7 +2352,7 @@
   // order to test AreRefreshTokensLoaded.
   token_service()->set_all_credentials_loaded_for_testing(false);
   EXPECT_FALSE(identity_manager()->AreRefreshTokensLoaded());
-  token_service()->LoadCredentials(CoreAccountId());
+  token_service()->LoadCredentials(CoreAccountId(), /*is_syncing=*/false);
   run_loop.Run();
   EXPECT_TRUE(identity_manager()->AreRefreshTokensLoaded());
 }
diff --git a/components/signin/public/identity_manager/identity_test_environment.cc b/components/signin/public/identity_manager/identity_test_environment.cc
index 314ac25e..abb77b6 100644
--- a/components/signin/public/identity_manager/identity_test_environment.cc
+++ b/components/signin/public/identity_manager/identity_test_environment.cc
@@ -672,7 +672,7 @@
 }
 
 void IdentityTestEnvironment::ReloadAccountsFromDisk() {
-  fake_token_service()->LoadCredentials(CoreAccountId());
+  fake_token_service()->LoadCredentials(CoreAccountId(), /*is_syncing=*/false);
 }
 
 bool IdentityTestEnvironment::IsAccessTokenRequestPending() {
diff --git a/components/test/data/unzip_service/bug953599.zip b/components/test/data/unzip_service/bug953599.zip
new file mode 100644
index 0000000..bb2b1c7
--- /dev/null
+++ b/components/test/data/unzip_service/bug953599.zip
Binary files differ
diff --git a/content/browser/indexed_db/indexed_db_context_impl.cc b/content/browser/indexed_db/indexed_db_context_impl.cc
index 3d0b3e4..4402995 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.cc
+++ b/content/browser/indexed_db/indexed_db_context_impl.cc
@@ -147,7 +147,7 @@
 // static
 void IndexedDBContextImpl::ReleaseOnIDBSequence(
     scoped_refptr<IndexedDBContextImpl>&& context) {
-  if (!context->idb_task_runner_->RunsTasksInCurrentSequence()) {
+  if (!context->IDBTaskRunner()->RunsTasksInCurrentSequence()) {
     IndexedDBContextImpl* context_ptr = context.get();
     context_ptr->IDBTaskRunner()->ReleaseSoon(FROM_HERE, std::move(context));
   }
@@ -194,38 +194,32 @@
       std::move(quota_client_remote),
       storage::QuotaClientType::kIndexedDatabase,
       {blink::mojom::StorageType::kTemporary});
+  IDBTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&IndexedDBContextImpl::BindPipesOnIDBSequence,
+                                weak_factory_.GetWeakPtr(),
+                                std::move(quota_client_receiver),
+                                std::move(blob_storage_context),
+                                std::move(file_system_access_context)));
+}
 
-  // This is safe because the IndexedDBContextImpl must be destructed on the
-  // IDBTaskRunner, and this task will always happen before that.
-  idb_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](mojo::Remote<storage::mojom::BlobStorageContext>*
-                 blob_storage_context,
-             mojo::Remote<storage::mojom::FileSystemAccessContext>*
-                 file_system_access_context,
-             mojo::Receiver<storage::mojom::QuotaClient>* quota_client_receiver,
-             mojo::PendingRemote<storage::mojom::BlobStorageContext>
-                 pending_blob_storage_context,
-             mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
-                 pending_file_system_access_context,
-             mojo::PendingReceiver<storage::mojom::QuotaClient>
-                 quota_client_pending_receiver) {
-            quota_client_receiver->Bind(
-                std::move(quota_client_pending_receiver));
-            if (pending_blob_storage_context) {
-              blob_storage_context->Bind(
-                  std::move(pending_blob_storage_context));
-            }
-            if (pending_file_system_access_context) {
-              file_system_access_context->Bind(
-                  std::move(pending_file_system_access_context));
-            }
-          },
-          &blob_storage_context_, &file_system_access_context_,
-          &quota_client_receiver_, std::move(blob_storage_context),
-          std::move(file_system_access_context),
-          std::move(quota_client_receiver)));
+void IndexedDBContextImpl::BindPipesOnIDBSequence(
+    mojo::PendingReceiver<storage::mojom::QuotaClient>
+        pending_quota_client_receiver,
+    mojo::PendingRemote<storage::mojom::BlobStorageContext>
+        pending_blob_storage_context,
+    mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
+        pending_file_system_access_context) {
+  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  if (pending_quota_client_receiver) {
+    quota_client_receiver_.Bind(std::move(pending_quota_client_receiver));
+  }
+  if (pending_blob_storage_context) {
+    blob_storage_context_.Bind(std::move(pending_blob_storage_context));
+  }
+  if (pending_file_system_access_context) {
+    file_system_access_context_.Bind(
+        std::move(pending_file_system_access_context));
+  }
 }
 
 void IndexedDBContextImpl::Bind(
@@ -604,7 +598,7 @@
 }
 
 void IndexedDBContextImpl::SetForceKeepSessionState() {
-  idb_task_runner_->PostTask(
+  IDBTaskRunner()->PostTask(
       FROM_HERE, base::BindOnce(
                      [](base::WeakPtr<IndexedDBContextImpl> context) {
                        if (context)
@@ -1024,7 +1018,7 @@
 }
 
 void IndexedDBContextImpl::ShutdownOnIDBSequence() {
-  DCHECK(idb_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
 
   if (force_keep_session_state_)
     return;
@@ -1076,7 +1070,7 @@
   if (is_incognito())
     return;
 
-  idb_task_runner_->PostTask(
+  IDBTaskRunner()->PostTask(
       FROM_HERE,
       base::BindOnce(
           &IndexedDBContextImpl::InitializeFromFilesIfNeeded,
diff --git a/content/browser/indexed_db/indexed_db_context_impl.h b/content/browser/indexed_db/indexed_db_context_impl.h
index 731532d7..00b053ae 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.h
+++ b/content/browser/indexed_db/indexed_db_context_impl.h
@@ -229,6 +229,14 @@
 
   ~IndexedDBContextImpl() override;
 
+  void BindPipesOnIDBSequence(
+      mojo::PendingReceiver<storage::mojom::QuotaClient>
+          pending_quota_client_receiver,
+      mojo::PendingRemote<storage::mojom::BlobStorageContext>
+          pending_blob_storage_context,
+      mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
+          pending_file_system_access_context);
+
   // mojom::IndexedDBControl internal implementation:
   void BindIndexedDBImpl(
       mojo::PendingReceiver<blink::mojom::IDBFactory> receiver,
@@ -332,6 +340,8 @@
   mojo::Receiver<storage::mojom::QuotaClient> quota_client_receiver_;
   const std::unique_ptr<storage::FilesystemProxy> filesystem_proxy_;
 
+  // weak_factory_->GetWeakPtr() may be used on any thread, but the resulting
+  // pointer must only be checked/used on idb_task_runner_.
   base::WeakPtrFactory<IndexedDBContextImpl> weak_factory_{this};
 };
 
diff --git a/content/browser/prerender/prerender_host.cc b/content/browser/prerender/prerender_host.cc
index e0813423..f266429 100644
--- a/content/browser/prerender/prerender_host.cc
+++ b/content/browser/prerender/prerender_host.cc
@@ -331,6 +331,30 @@
                              WebContents& web_contents)
     : attributes_(attributes) {
   DCHECK(blink::features::IsPrerender2Enabled());
+  // If the prerendering is browser-initiated, it is expected to have no
+  // initiator. All initiator related information should be null or invalid. On
+  // the other hand, renderer-initiated prerendering should have valid initiator
+  // information.
+  if (attributes.IsBrowserInitiated()) {
+    DCHECK(!attributes.initiator_origin.has_value());
+    DCHECK(!attributes.initiator_frame_token.has_value());
+    DCHECK_EQ(attributes.initiator_process_id,
+              ChildProcessHost::kInvalidUniqueID);
+    DCHECK_EQ(attributes.initiator_ukm_id, ukm::kInvalidSourceId);
+    DCHECK_EQ(attributes.initiator_frame_tree_node_id,
+              RenderFrameHost::kNoFrameTreeNodeId);
+  } else {
+    DCHECK(attributes.initiator_origin.has_value());
+    DCHECK(attributes.initiator_frame_token.has_value());
+    // TODO(https://crbug.com/1325211): Add back the following DCHECKs after
+    // fixing prerendering activation for embedder-triggered prerendering in
+    // unittests.
+    // DCHECK_NE(attributes.initiator_process_id,
+    // ChildProcessHost::kInvalidUniqueID);
+    // DCHECK_NE(attributes.initiator_ukm_id, ukm::kInvalidSourceId);
+    // DCHECK_NE(attributes.initiator_frame_tree_node_id,
+    //           RenderFrameHost::kNoFrameTreeNodeId);
+  }
   CreatePageHolder(*static_cast<WebContentsImpl*>(&web_contents));
 }
 
diff --git a/content/browser/prerender/prerender_host_registry_unittest.cc b/content/browser/prerender/prerender_host_registry_unittest.cc
index 4dc8c21..772d2335 100644
--- a/content/browser/prerender/prerender_host_registry_unittest.cc
+++ b/content/browser/prerender/prerender_host_registry_unittest.cc
@@ -47,16 +47,28 @@
     PrerenderTriggerType trigger_type,
     const std::string& embedder_histogram_suffix,
     RenderFrameHostImpl* rfh) {
-  return PrerenderAttributes(
-      url, trigger_type, embedder_histogram_suffix, Referrer(),
-      rfh->GetLastCommittedOrigin(), rfh->GetLastCommittedURL(),
-      rfh->GetProcess()->GetID(), rfh->GetFrameToken(),
-      rfh->GetFrameTreeNodeId(),
-      trigger_type == PrerenderTriggerType::kSpeculationRule
-          ? rfh->GetPageUkmSourceId()
-          : ukm::kInvalidSourceId,
-      ui::PAGE_TRANSITION_LINK,
-      /*url_match_predicate=*/absl::nullopt);
+  if (trigger_type == PrerenderTriggerType::kSpeculationRule) {
+    return PrerenderAttributes(
+        url, trigger_type, embedder_histogram_suffix, Referrer(),
+        rfh->GetLastCommittedOrigin(), rfh->GetLastCommittedURL(),
+        rfh->GetProcess()->GetID(), rfh->GetFrameToken(),
+        rfh->GetFrameTreeNodeId(), rfh->GetPageUkmSourceId(),
+        ui::PAGE_TRANSITION_LINK,
+        /*url_match_predicate=*/absl::nullopt);
+  } else {
+    // TODO(https://crbug.com/1325211): remove initiator_origin and
+    // initiator_frame_token after fixing prerendering activation for
+    // embedder-triggered prerendering in unittests.
+    return PrerenderAttributes(
+        url, trigger_type, embedder_histogram_suffix, Referrer(),
+        /*initiator_origin=*/rfh->GetLastCommittedOrigin(),
+        rfh->GetLastCommittedURL(),
+        /*initiator_process_id=*/ChildProcessHost::kInvalidUniqueID,
+        /*initiator_frame_token=*/rfh->GetFrameToken(),
+        /*initiator_frame_tree_node_id=*/RenderFrameHost::kNoFrameTreeNodeId,
+        /*initiator_ukm_id=*/ukm::kInvalidSourceId, ui::PAGE_TRANSITION_LINK,
+        /*url_match_predicate=*/absl::nullopt);
+  }
 }
 
 // This definition is needed because this constant is odr-used in gtest macros.
diff --git a/content/browser/renderer_host/agent_scheduling_group_host.cc b/content/browser/renderer_host/agent_scheduling_group_host.cc
index ea839d5..4a1ad43 100644
--- a/content/browser/renderer_host/agent_scheduling_group_host.cc
+++ b/content/browser/renderer_host/agent_scheduling_group_host.cc
@@ -265,6 +265,11 @@
   return process_.Init();
 }
 
+base::SafeRef<AgentSchedulingGroupHost> AgentSchedulingGroupHost::GetSafeRef()
+    const {
+  return weak_ptr_factory_.GetSafeRef();
+}
+
 ChannelProxy* AgentSchedulingGroupHost::GetChannel() {
   DCHECK_EQ(state_, LifecycleState::kBound);
 
diff --git a/content/browser/renderer_host/agent_scheduling_group_host.h b/content/browser/renderer_host/agent_scheduling_group_host.h
index 66ccbb7..191ca0a 100644
--- a/content/browser/renderer_host/agent_scheduling_group_host.h
+++ b/content/browser/renderer_host/agent_scheduling_group_host.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include "base/containers/id_map.h"
+#include "base/memory/safe_ref.h"
 #include "base/supports_user_data.h"
 #include "content/browser/browser_interface_broker_impl.h"
 #include "content/common/agent_scheduling_group.mojom.h"
@@ -84,6 +85,9 @@
   // Returns |false| if any part of the initialization failed.
   bool Init();
 
+  // Returns a SafeRef to `this`.
+  base::SafeRef<AgentSchedulingGroupHost> GetSafeRef() const;
+
   int32_t id_for_debugging() const { return id_for_debugging_; }
 
   // IPC and mojo messages to be forwarded to the RenderProcessHost, for now. In
@@ -229,6 +233,9 @@
       associated_interface_provider_receivers_;
 
   LifecycleState state_{LifecycleState::kNewborn};
+
+  // This is used to create SafeRefs, and as a result, cannot be reset.
+  base::WeakPtrFactory<AgentSchedulingGroupHost> weak_ptr_factory_{this};
 };
 
 std::ostream& operator<<(std::ostream& os,
diff --git a/content/browser/renderer_host/cross_process_frame_connector.cc b/content/browser/renderer_host/cross_process_frame_connector.cc
index 3990b12..0948a76 100644
--- a/content/browser/renderer_host/cross_process_frame_connector.cc
+++ b/content/browser/renderer_host/cross_process_frame_connector.cc
@@ -616,8 +616,8 @@
   // of a hidden tab with a sad frame being auto-reloaded when it becomes
   // shown.
   bool has_pending_navigation = false;
-  for (auto* parent = current_child_frame_host()->GetParent(); parent;
-       parent = parent->GetParent()) {
+  for (auto* parent = current_child_frame_host()->GetParentOrOuterDocument();
+       parent; parent = parent->GetParentOrOuterDocument()) {
     if (parent->frame_tree_node()->HasPendingCrossDocumentNavigation()) {
       has_pending_navigation = true;
       break;
diff --git a/content/browser/renderer_host/frame_tree.h b/content/browser/renderer_host/frame_tree.h
index 9fb1c2f..7fec3a6 100644
--- a/content/browser/renderer_host/frame_tree.h
+++ b/content/browser/renderer_host/frame_tree.h
@@ -499,6 +499,8 @@
   FRIEND_TEST_ALL_PREFIXES(RenderFrameHostImplBrowserTest, RemoveFocusedFrame);
   FRIEND_TEST_ALL_PREFIXES(PortalBrowserTest, NodesForIsLoading);
   FRIEND_TEST_ALL_PREFIXES(FencedFrameBrowserTest, NodesForIsLoading);
+  FRIEND_TEST_ALL_PREFIXES(RenderFrameHostManagerTest,
+                           CreateRenderViewAfterProcessKillAndClosedProxy);
 
   // Returns a range to iterate over all FrameTreeNodes in the frame tree in
   // breadth-first traversal order, skipping the subtree rooted at
diff --git a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
index 8561a67..cf41edc 100644
--- a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
@@ -3517,7 +3517,7 @@
   // load stop.
   RenderFrameHostImpl* rfh_a = root->current_frame_host();
   rfh_a->DisableUnloadTimerForTesting();
-  SiteInstanceGroup* site_instance_group_a = rfh_a->GetSiteInstance()->group();
+  scoped_refptr<SiteInstanceImpl> site_instance_a = rfh_a->GetSiteInstance();
   TestFrameNavigationObserver commit_observer(root);
   shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title2.html"));
   commit_observer.WaitForCommit();
@@ -3525,7 +3525,7 @@
             new_shell->web_contents()->GetSiteInstance());
   EXPECT_TRUE(root->current_frame_host()
                   ->browsing_context_state()
-                  ->GetRenderFrameProxyHost(site_instance_group_a));
+                  ->GetRenderFrameProxyHost(site_instance_a->group()));
 
   // The previous RFH should still be pending deletion, as we wait for either
   // the mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame or a timeout.
@@ -3551,20 +3551,30 @@
   EXPECT_TRUE(rvh_a->HasOneRef());
   EXPECT_TRUE(root->current_frame_host()
                   ->browsing_context_state()
-                  ->GetRenderFrameProxyHost(site_instance_group_a));
+                  ->GetRenderFrameProxyHost(site_instance_a->group()));
   EXPECT_FALSE(root->current_frame_host()
                    ->browsing_context_state()
-                   ->GetRenderFrameProxyHost(site_instance_group_a)
+                   ->GetRenderFrameProxyHost(site_instance_a->group())
                    ->is_render_frame_proxy_live());
 
   // Close the popup so there is no proxy for a.com in the original tab.
   new_shell->Close();
-  EXPECT_FALSE(root->current_frame_host()
-                   ->browsing_context_state()
-                   ->GetRenderFrameProxyHost(site_instance_group_a));
 
-  // This should delete the RVH as well.
-  EXPECT_FALSE(root->frame_tree()->GetRenderViewHost(site_instance_group_a));
+  // Verify that there are no proxies, meaning there's no proxy for a.com. At
+  // this point, |site_instance_group_a| has been freed, so searching the proxy
+  // host map using it isn't an option.
+  EXPECT_EQ(nullptr, site_instance_a->group());
+  EXPECT_EQ(0u, root->current_frame_host()
+                    ->browsing_context_state()
+                    ->proxy_hosts()
+                    .size());
+
+  // This should delete the RVH as well. Check this by verifying that there's
+  // only one RVH in the frame tree, and it's for the current SiteInstanceGroup,
+  // not |site_instance_group_a|.
+  EXPECT_TRUE(root->frame_tree()->GetRenderViewHost(
+      root->current_frame_host()->GetSiteInstance()->group()));
+  EXPECT_EQ(1u, root->frame_tree()->render_view_host_map_.size());
 
   // Go back in the main frame from b.com to a.com. In https://crbug.com/581912,
   // the browser process would crash here because there was no main frame
diff --git a/content/browser/renderer_host/render_frame_host_manager_unittest.cc b/content/browser/renderer_host/render_frame_host_manager_unittest.cc
index 37c31ad..bd369ed 100644
--- a/content/browser/renderer_host/render_frame_host_manager_unittest.cc
+++ b/content/browser/renderer_host/render_frame_host_manager_unittest.cc
@@ -1982,11 +1982,9 @@
   EXPECT_TRUE(delete_watcher1.deleted());
   EXPECT_TRUE(delete_watcher2.deleted());
 
-  EXPECT_EQ(0U, site_instance->group()->active_frame_count());
-  EXPECT_EQ(nullptr, root_manager->current_frame_host()
-                         ->browsing_context_state()
-                         ->GetRenderFrameProxyHost(site_instance->group()))
-      << "Proxies should have been cleaned up";
+  // |site_instance| should no longer have a group, which means there are no
+  // active frames left, or any proxies for its group.
+  EXPECT_FALSE(site_instance->group());
   EXPECT_TRUE(site_instance->HasOneRef())
       << "This SiteInstance should be destroyable now.";
 }
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 43f0560..4d36500 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -3653,6 +3653,10 @@
   return id_;
 }
 
+base::SafeRef<RenderProcessHost> RenderProcessHostImpl::GetSafeRef() const {
+  return weak_ptr_factory_.GetSafeRef();
+}
+
 bool RenderProcessHostImpl::IsInitializedAndNotDead() {
   return is_initialized_ && !is_dead_;
 }
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index c680411..3f1b0f5 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -18,6 +18,7 @@
 #include "base/containers/flat_set.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/safe_ref.h"
 #include "base/observer_list.h"
 #include "base/threading/sequence_bound.h"
 #include "base/time/time.h"
@@ -214,6 +215,7 @@
   BrowserContext* GetBrowserContext() override;
   bool InSameStoragePartition(StoragePartition* partition) override;
   int GetID() const override;
+  base::SafeRef<RenderProcessHost> GetSafeRef() const override;
   bool IsInitializedAndNotDead() override;
   void SetBlocked(bool blocked) override;
   bool IsBlocked() override;
@@ -1197,6 +1199,10 @@
   // Used to vend WeakPtrs which are invalidated any time the RenderProcessHost
   // is recycled.
   base::WeakPtrFactory<RenderProcessHostImpl> instance_weak_factory_{this};
+
+  // A WeakPtrFactory that doesn't get reset, unlike |instance_weak_factory_|
+  // above. This is used to create SafeRefs.
+  base::WeakPtrFactory<RenderProcessHostImpl> weak_ptr_factory_{this};
 };
 
 }  // namespace content
diff --git a/content/browser/site_instance_group.cc b/content/browser/site_instance_group.cc
index 972a992..780904899 100644
--- a/content/browser/site_instance_group.cc
+++ b/content/browser/site_instance_group.cc
@@ -5,6 +5,8 @@
 #include "content/browser/site_instance_group.h"
 
 #include "base/observer_list.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/site_instance_impl.h"
 #include "content/public/browser/render_process_host.h"
 
 namespace content {
@@ -16,19 +18,16 @@
 SiteInstanceGroup::SiteInstanceGroup(BrowsingInstanceId browsing_instance_id,
                                      RenderProcessHost* process)
     : id_(site_instance_group_id_generator.GenerateNextId()),
-      browsing_instance_id_(browsing_instance_id) {
-  SetProcessAndAgentSchedulingGroup(process);
+      browsing_instance_id_(browsing_instance_id),
+      process_(process->GetSafeRef()),
+      agent_scheduling_group_(
+          AgentSchedulingGroupHost::GetOrCreate(*this, *process)
+              ->GetSafeRef()) {
+  process->AddObserver(this);
 }
 
 SiteInstanceGroup::~SiteInstanceGroup() {
-  if (!process_)
-    return;
-
   process_->RemoveObserver(this);
-
-  // Ensure the RenderProcessHost gets deleted if this SiteInstanceGroup
-  // created a process which was never used by any listeners.
-  process_->Cleanup();
 }
 
 SiteInstanceGroupId SiteInstanceGroup::GetId() const {
@@ -47,6 +46,16 @@
   observers_.RemoveObserver(observer);
 }
 
+void SiteInstanceGroup::AddSiteInstance(SiteInstanceImpl* site_instance) {
+  site_instances_.insert(site_instance);
+}
+
+void SiteInstanceGroup::RemoveSiteInstance(SiteInstanceImpl* site_instance) {
+  site_instances_.erase(site_instance);
+  if (site_instances_.empty())
+    process_->Cleanup();
+}
+
 void SiteInstanceGroup::IncrementActiveFrameCount() {
   active_frame_count_++;
 }
@@ -58,23 +67,15 @@
   }
 }
 
-void SiteInstanceGroup::SetProcessAndAgentSchedulingGroup(
-    RenderProcessHost* process) {
-  // It is never safe to change |process_| without going through
-  // RenderProcessHostDestroyed first to set it to null. Otherwise, same-site
-  // frames will end up in different processes and everything will get confused.
-  CHECK(!process_);
-  process->AddObserver(this);
-  process_ = process;
-  agent_scheduling_group_ =
-      AgentSchedulingGroupHost::GetOrCreate(*this, *process);
-}
-
 void SiteInstanceGroup::RenderProcessHostDestroyed(RenderProcessHost* host) {
-  DCHECK_EQ(process_, host);
+  DCHECK_EQ(process_->GetID(), host->GetID());
   process_->RemoveObserver(this);
-  process_ = nullptr;
-  agent_scheduling_group_ = nullptr;
+
+  // Remove references to `this` from all SiteInstances in this group. That will
+  // cause `this` to be destructed, to enforce the invariant that a
+  // SiteInstanceGroup must have a RenderProcessHost.
+  for (auto* instance : site_instances_)
+    instance->ResetSiteInstanceGroup();
 }
 
 void SiteInstanceGroup::RenderProcessExited(
diff --git a/content/browser/site_instance_group.h b/content/browser/site_instance_group.h
index 90d4f4d..9e3cb74 100644
--- a/content/browser/site_instance_group.h
+++ b/content/browser/site_instance_group.h
@@ -11,6 +11,7 @@
 #include "base/observer_list.h"
 #include "base/types/id_type.h"
 #include "content/browser/renderer_host/agent_scheduling_group_host.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/browsing_instance_id.h"
 #include "content/public/browser/render_process_host_observer.h"
@@ -23,6 +24,7 @@
 namespace content {
 
 class SiteInstance;
+class SiteInstanceImpl;
 struct ChildProcessTerminationInfo;
 
 using SiteInstanceGroupId = base::IdType32<class SiteInstanceGroupIdTag>;
@@ -51,9 +53,15 @@
 // The browser process coordinates activities across groups to produce a full
 // web page.
 //
+// A SiteInstanceGroup always has a RenderProcessHost. If the RenderProcessHost
+// itself (and not just the renderer process) goes away, then all
+// RenderFrameHosts, RenderFrameProxyHosts, and workers using it are gone, and
+// the SiteInstanceGroup itself goes away as well. SiteInstances in the group
+// may outlive this (e.g., when kept alive by NavigationEntry), in which case
+// they will get a new SiteInstanceGroup the next time one is needed.
 // SiteInstanceGroups are refcounted by the SiteInstances using them, allowing
-// for flexible policies.  Currently, each SiteInstanceGroup has exactly one
-// SiteInstance.  See crbug.com/1195535.
+// for flexible policies. Currently, each SiteInstanceGroup has exactly one
+// SiteInstance. See crbug.com/1195535.
 class CONTENT_EXPORT SiteInstanceGroup
     : public base::RefCounted<SiteInstanceGroup>,
       public RenderProcessHostObserver {
@@ -62,12 +70,13 @@
    public:
     // Called when this SiteInstanceGroup transitions to having no active
     // frames, as measured by active_frame_count().
-    virtual void ActiveFrameCountIsZero(SiteInstanceGroup* site_instance) {}
+    virtual void ActiveFrameCountIsZero(
+        SiteInstanceGroup* site_instance_group) {}
 
     // Called when the renderer process of this SiteInstanceGroup has exited.
     // Note that GetProcess() still returns the same RenderProcessHost instance.
     // You can reinitialize it by a call to SiteInstance::GetProcess()->Init().
-    virtual void RenderProcessGone(SiteInstanceGroup* site_instance,
+    virtual void RenderProcessGone(SiteInstanceGroup* site_instance_group,
                                    const ChildProcessTerminationInfo& info) {}
   };
 
@@ -84,6 +93,12 @@
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
+  // Used to keep track of the SiteInstances that belong in this group, so they
+  // can be notified to clear their references to `this` when it gets
+  // destructed.
+  void AddSiteInstance(SiteInstanceImpl* site_instance);
+  void RemoveSiteInstance(SiteInstanceImpl* site_instance);
+
   // Increase the number of active frames in this SiteInstanceGroup. This is
   // increased when a frame is created.
   void IncrementActiveFrameCount();
@@ -98,27 +113,16 @@
   // be safely discarded.
   size_t active_frame_count() const { return active_frame_count_; }
 
-  // `process_` and `agent_scheduling_group_` have to be set together. See
-  // `process_` for more details.
-  // TODO(crbug.com/1294045): Remove once `this` has the same lifetime as
-  // `process`.
-  void SetProcessAndAgentSchedulingGroup(RenderProcessHost* process);
-
-  RenderProcessHost* process() const { return process_; }
-  bool has_process() const { return process_ != nullptr; }
+  RenderProcessHost* process() const { return &*process_; }
 
   BrowsingInstanceId browsing_instance_id() const {
     return browsing_instance_id_;
   }
 
   AgentSchedulingGroupHost& agent_scheduling_group() {
-    DCHECK(agent_scheduling_group_);
-    DCHECK_EQ(agent_scheduling_group_->GetProcess(), process_);
+    DCHECK_EQ(agent_scheduling_group_->GetProcess(), &*process_);
     return *agent_scheduling_group_;
   }
-  bool has_agent_scheduling_group() {
-    return agent_scheduling_group_ != nullptr;
-  }
 
   using TraceProto = perfetto::protos::pbzero::SiteInstanceGroup;
   // Write a representation of this object into a trace.
@@ -144,15 +148,18 @@
 
   // Current RenderProcessHost that is rendering pages for this
   // SiteInstanceGroup, and AgentSchedulingGroupHost (within the process) this
-  // SiteInstanceGroup belongs to. Since AgentSchedulingGroupHost is associated
-  // with a specific RenderProcessHost, these *must be* changed together to
-  // avoid UAF!
-  // The |process_| pointer (and hence the |agent_scheduling_group_| pointer as
-  // well) will only change once the RenderProcessHost is destructed. They will
-  // still remain the same even if the process crashes, since in that scenario
-  // the RenderProcessHost remains the same.
-  raw_ptr<RenderProcessHost> process_ = nullptr;
-  raw_ptr<AgentSchedulingGroupHost> agent_scheduling_group_ = nullptr;
+  // SiteInstanceGroup belongs to.
+  // If the RenderProcessHost gets destroyed, `this` will also be destructed.
+  // Any SiteInstances in the group will get a new process and group the next
+  // time they need a process. If the process crashes, `this` will not be
+  // destructed as long as the RenderProcessHost is still alive.
+  const base::SafeRef<RenderProcessHost> process_;
+  const base::SafeRef<AgentSchedulingGroupHost> agent_scheduling_group_;
+
+  // List of SiteInstanceImpls that belong in this group. When any SiteInstance
+  // in the set goes away, it must also be removed from `site_instances_` to
+  // prevent UaF.
+  base::flat_set<SiteInstanceImpl*> site_instances_;
 
   base::ObserverList<Observer, true>::Unchecked observers_;
 
diff --git a/content/browser/site_instance_group_manager.cc b/content/browser/site_instance_group_manager.cc
index cb646162..d8a53f1 100644
--- a/content/browser/site_instance_group_manager.cc
+++ b/content/browser/site_instance_group_manager.cc
@@ -45,8 +45,10 @@
   // own SiteInstanceGroup, and we can always create a new group for each new
   // SiteInstance here. When grouping policies are introduced, this function may
   // return an existing SiteInstanceGroup for a new SiteInstance.
-  return base::WrapRefCounted(
+  scoped_refptr<SiteInstanceGroup> site_instance_group = base::WrapRefCounted(
       new SiteInstanceGroup(site_instance->GetBrowsingInstanceId(), process));
+  site_instance_group->AddSiteInstance(site_instance);
+  return site_instance_group;
 }
 
 void SiteInstanceGroupManager::OnSiteInfoSet(SiteInstanceImpl* site_instance,
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index ee5c04f..9329648 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -111,6 +111,11 @@
   // (within the same BrowsingInstance) can safely create a new SiteInstance.
   if (has_site_)
     browsing_instance_->UnregisterSiteInstance(this);
+
+  if (has_group()) {
+    group()->RemoveSiteInstance(this);
+    ResetSiteInstanceGroup();
+  }
 }
 
 // static
@@ -288,7 +293,7 @@
 }
 
 bool SiteInstanceImpl::HasProcess() {
-  if (site_instance_group_ && site_instance_group_->has_process())
+  if (has_group())
     return true;
 
   // If we would use process-per-site for this site, also check if there is an
@@ -303,15 +308,12 @@
 }
 
 RenderProcessHost* SiteInstanceImpl::GetProcess() {
-  // Create a new process if our group's went away or was reused.
+  // Create a new SiteInstanceGroup and RenderProcessHost is there isn't one.
   // All SiteInstances within a SiteInstanceGroup share a process and
-  // AgentSchedulingGroupHost. The process and AgentSchedulingGroupHost may go
-  // away if the SiteInstanceGroup outlives all of its documents and workers. If
-  // needed, make sure the SiteInstanceGroup, process, and
-  // AgentSchedulingGroupHost exist.
-  // TODO(crbug.com/1294045): Update checks and comments when a
-  // SiteInstanceGroup must have a RenderProcessHost and AgentSchedulingGroup.
-  if (!site_instance_group_ || !site_instance_group_->has_process()) {
+  // AgentSchedulingGroupHost. A group must have a process. If the process gets
+  // destructed, `site_instance_group_` will get cleared, and another one with a
+  // new process will be assigned the next time GetProcess() gets called.
+  if (!has_group()) {
     // Check if the ProcessReusePolicy should be updated.
     if (ShouldUseProcessPerSite()) {
       process_reuse_policy_ = ProcessReusePolicy::PROCESS_PER_SITE;
@@ -322,8 +324,6 @@
         RenderProcessHostImpl::GetProcessHostForSiteInstance(this));
   }
   DCHECK(site_instance_group_);
-  DCHECK(site_instance_group_->has_process());
-  DCHECK(site_instance_group_->has_agent_scheduling_group());
 
   return site_instance_group_->process();
 }
@@ -364,15 +364,8 @@
     site_instance_group_ =
         browsing_instance_->site_instance_group_manager()
             .GetOrCreateGroupForNewSiteInstance(this, process);
-  } else if (!site_instance_group_->process()) {
-    // TODO(crbug.com/1261963): Remove this clause once the lifetime of
-    // SiteInstanceGroup  is tied to that of `process`.
-    site_instance_group_->SetProcessAndAgentSchedulingGroup(process);
   }
 
-  DCHECK(site_instance_group_->has_process());
-  DCHECK(site_instance_group_->has_agent_scheduling_group());
-
   LockProcessIfNeeded();
 
   // If we are using process-per-site, we need to register this process
@@ -511,9 +504,7 @@
   if (should_use_process_per_site)
     process_reuse_policy_ = ProcessReusePolicy::PROCESS_PER_SITE;
 
-  bool has_process =
-      site_instance_group_ && site_instance_group_->has_process();
-  if (has_process) {
+  if (has_group()) {
     LockProcessIfNeeded();
 
     // Ensure the process is registered for this site if necessary.
@@ -527,7 +518,7 @@
   // SiteInstance. This must be called after LockProcessIfNeeded() because
   // the SiteInstanceGroupManager does suitability checks that use the lock.
   browsing_instance_->site_instance_group_manager().OnSiteInfoSet(this,
-                                                                  has_process);
+                                                                  has_group());
 }
 
 void SiteInstanceImpl::ConvertToDefaultOrSetSite(const UrlInfo& url_info) {
@@ -639,14 +630,16 @@
 }
 
 AgentSchedulingGroupHost& SiteInstanceImpl::GetOrCreateAgentSchedulingGroup() {
-  if (!site_instance_group_ ||
-      !site_instance_group_->has_agent_scheduling_group()) {
+  if (!site_instance_group_)
     GetProcess();
-  }
 
   return site_instance_group_->agent_scheduling_group();
 }
 
+void SiteInstanceImpl::ResetSiteInstanceGroup() {
+  site_instance_group_.reset();
+}
+
 bool SiteInstanceImpl::IsRelatedSiteInstance(const SiteInstance* instance) {
   return browsing_instance_.get() ==
          static_cast<const SiteInstanceImpl*>(instance)
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h
index df44307..04b8857 100644
--- a/content/browser/site_instance_impl.h
+++ b/content/browser/site_instance_impl.h
@@ -119,6 +119,11 @@
   // `site_instance_group_` doesn't have one.
   AgentSchedulingGroupHost& GetOrCreateAgentSchedulingGroup();
 
+  // Resets the `site_instance_group_` refptr, and must be called when its
+  // RenderProcessHost goes away. `site_instance_group_` can be reassigned later
+  // as needed.
+  void ResetSiteInstanceGroup();
+
   // SiteInstance implementation.
   SiteInstanceId GetId() override;
   BrowsingInstanceId GetBrowsingInstanceId() override;
@@ -414,6 +419,9 @@
 
   ~SiteInstanceImpl() override;
 
+  // Returns true when |this| has a SiteInstanceGroup.
+  bool has_group() { return group() != nullptr; }
+
   // Used to restrict a process' origin access rights. This method gets called
   // when a process gets assigned to this SiteInstance and when the
   // SiteInfo is explicitly set. If the SiteInfo hasn't been set yet and
@@ -502,6 +510,8 @@
   // `site_instance_group_` is set when a RenderProcessHost is set for this
   // SiteInstance, and will be how `this` gets its RenderProcessHost and
   // AgentSchedulingGroup.
+  // If the RenderProcessHost goes away, `site_instance_group_` will get reset.
+  // It can be set to another group later on as needed.
   // See the class-level comment of SiteInstanceGroup for more details.
   scoped_refptr<SiteInstanceGroup> site_instance_group_;
 
diff --git a/content/browser/site_per_process_sad_frame_browsertest.cc b/content/browser/site_per_process_sad_frame_browsertest.cc
index 35606fc..d3d750f 100644
--- a/content/browser/site_per_process_sad_frame_browsertest.cc
+++ b/content/browser/site_per_process_sad_frame_browsertest.cc
@@ -11,6 +11,7 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/fenced_frame_test_util.h"
 #include "content/shell/browser/shell.h"
 #include "content/test/render_document_feature.h"
 #include "content/test/render_widget_host_visibility_observer.h"
@@ -34,6 +35,13 @@
             run_loop_.QuitClosure());
   }
 
+  explicit SadFrameShownObserver(RenderFrameHostImpl* rfhi) {
+    RenderFrameProxyHost* proxy_to_parent = rfhi->GetProxyToOuterDelegate();
+    proxy_to_parent->cross_process_frame_connector()
+        ->set_child_frame_crash_shown_closure_for_testing(
+            run_loop_.QuitClosure());
+  }
+
   SadFrameShownObserver(const SadFrameShownObserver&) = delete;
   SadFrameShownObserver& operator=(const SadFrameShownObserver&) = delete;
 
@@ -233,7 +241,29 @@
     EXPECT_FALSE(ftn->current_frame_host()->IsRenderFrameLive());
   }
 
+  void CrashRendererProcess(RenderFrameHostImpl* rfhi) {
+    RenderProcessHost* process = rfhi->GetProcess();
+    RenderProcessHostWatcher crash_observer(
+        process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+    process->Shutdown(0);
+    crash_observer.Wait();
+    EXPECT_FALSE(rfhi->IsRenderFrameLive());
+  }
+
+  WebContentsImpl* web_contents() {
+    return static_cast<WebContentsImpl*>(shell()->web_contents());
+  }
+
+  RenderFrameHostImpl* primary_main_frame_host() {
+    return web_contents()->GetMainFrame();
+  }
+
+  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
+    return fenced_frame_helper_;
+  }
+
  private:
+  content::test::FencedFrameTestHelper fenced_frame_helper_;
   base::test::ScopedFeatureList feature_list_;
 };
 
@@ -388,6 +418,82 @@
   }
 }
 
+// Verify that a sad frame shown when its parent frame is loading is logged
+// with appropriate metrics, namely as kShownWhileAncestorIsLoading rather than
+// kShownAfterCrashing. See https://crbug.com/1132938.
+IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
+                       CrashedFencedframeVisibilityMetricsDuringParentLoad) {
+  // Since Fenced Frames should create a renderer per fenced frame, we
+  // do not need to explicitly change the site.
+  GURL main_url(
+      embedded_test_server()->GetURL("a.com", "/fenced_frames/title1.html"));
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
+  RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host());
+  RenderFrameHostImplWrapper child_rfh(
+      fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(),
+                                                   main_url));
+  // Note that height and width follows the layout function in
+  // content/test/data/cross_site_iframe_factory.html.
+  EXPECT_TRUE(ExecJs(primary_rfh.get(), R"(
+       var ff = document.querySelector('fencedframe');
+       // layoutX = gridSizeX * largestChildX + extraXPerLevel
+       ff.width = 1 * (110 + 30) + 50;
+       // layoutY = gridSizeY * largestChildY + extraYPerLevel
+       ff.height = 1 * (110 + 30) + 50
+       )"));
+  RenderFrameHostImplWrapper grandchild_rfh(
+      fenced_frame_test_helper().CreateFencedFrame(child_rfh.get(), main_url));
+  // Note that height and width follows the layout function in
+  // content/test/data/cross_site_iframe_factory.html.
+  EXPECT_TRUE(ExecJs(child_rfh.get(), R"(
+       var ff = document.querySelector('fencedframe');
+       ff.width = 110;
+       ff.height = 110;
+       )"));
+
+  // Hide the grandchild frame.
+  RenderWidgetHostVisibilityObserver hide_observer(
+      grandchild_rfh->GetRenderWidgetHost(), false /* became_visible */);
+  EXPECT_TRUE(
+      ExecJs(child_rfh.get(),
+             "document.querySelector('fencedframe').style.display = 'none'"));
+  hide_observer.WaitUntilSatisfied();
+
+  // Kill the grandchild process.
+  CrashRendererProcess(grandchild_rfh.get());
+
+  // Start a navigation in the child frame, but don't commit.
+  GURL url_d(
+      embedded_test_server()->GetURL("d.com", "/fenced_frames/title1.html"));
+  TestNavigationManager manager(web_contents(), url_d);
+  EXPECT_TRUE(ExecJs(child_rfh.get(), JsReplace("location.href = $1", url_d)));
+  EXPECT_TRUE(manager.WaitForRequestStart());
+
+  // Make the grandchild fencedframe with the sad frame visible again.
+  // This should get logged as kShownWhileAncestorIsLoading, because its parent
+  // is currently loading.
+  {
+    base::HistogramTester histograms;
+    SadFrameShownObserver sad_frame_observer(grandchild_rfh.get());
+    EXPECT_TRUE(ExecJs(
+        child_rfh.get(),
+        "document.querySelector('fencedframe').style.display = 'block'"));
+    sad_frame_observer.Wait();
+
+    histograms.ExpectUniqueSample("Stability.ChildFrameCrash.Visibility",
+                                  CrashVisibility::kShownWhileAncestorIsLoading,
+                                  1);
+
+    // Ensure no new metrics are logged after the navigation completes.
+    manager.WaitForNavigationFinished();
+    EXPECT_TRUE(manager.was_successful());
+    histograms.ExpectUniqueSample("Stability.ChildFrameCrash.Visibility",
+                                  CrashVisibility::kShownWhileAncestorIsLoading,
+                                  1);
+  }
+}
+
 // Verify the feature where hidden tabs with crashed subframes are marked for
 // reload. This avoids showing crashed subframes if a hidden tab is eventually
 // shown. Similar to the test above, except that the crashed subframe is
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 02f2e9ab..eed471b 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -776,13 +776,18 @@
         /*should_call_callback=*/false);
     return;
   }
-  GURL brand_icon_url = idp_metadata.brand_icon_url;
-  DownloadBitmap(
-      brand_icon_url, request_dialog_controller_->GetBrandIconIdealSize(),
-      base::BindOnce(&FederatedAuthRequestImpl::OnBrandIconDownloaded,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     request_dialog_controller_->GetBrandIconMinimumSize(),
-                     std::move(idp_metadata)));
+  if (IsEndpointUrlValid(endpoints_.client_metadata)) {
+    network_manager_->FetchClientMetadata(
+        endpoints_.client_metadata, client_id_,
+        base::BindOnce(
+            &FederatedAuthRequestImpl::OnClientMetadataResponseReceived,
+            weak_ptr_factory_.GetWeakPtr(), std::move(idp_metadata)));
+  } else {
+    network_manager_->SendAccountsRequest(
+        endpoints_.accounts, client_id_,
+        base::BindOnce(&FederatedAuthRequestImpl::OnAccountsResponseReceived,
+                       weak_ptr_factory_.GetWeakPtr(), idp_metadata));
+  }
 }
 
 void FederatedAuthRequestImpl::OnManifestFetchedForRevoke(
@@ -890,35 +895,6 @@
     std::move(revoke_callback_).Run(status);
 }
 
-void FederatedAuthRequestImpl::OnBrandIconDownloaded(
-    int icon_minimum_size,
-    IdentityProviderMetadata idp_metadata,
-    int id,
-    int http_status_code,
-    const GURL& image_url,
-    const std::vector<SkBitmap>& bitmaps,
-    const std::vector<gfx::Size>& sizes) {
-  // For the sake of FedCM spec simplicity do not support multi-resolution .ico
-  // files.
-  if (bitmaps.size() == 1 && bitmaps[0].width() == bitmaps[0].height() &&
-      bitmaps[0].width() >= icon_minimum_size) {
-    idp_metadata.brand_icon = bitmaps[0];
-  }
-
-  if (IsEndpointUrlValid(endpoints_.client_metadata)) {
-    network_manager_->FetchClientMetadata(
-        endpoints_.client_metadata, client_id_,
-        base::BindOnce(
-            &FederatedAuthRequestImpl::OnClientMetadataResponseReceived,
-            weak_ptr_factory_.GetWeakPtr(), std::move(idp_metadata)));
-  } else {
-    network_manager_->SendAccountsRequest(
-        endpoints_.accounts, client_id_,
-        base::BindOnce(&FederatedAuthRequestImpl::OnAccountsResponseReceived,
-                       weak_ptr_factory_.GetWeakPtr(), idp_metadata));
-  }
-}
-
 void FederatedAuthRequestImpl::OnClientMetadataResponseReceived(
     IdentityProviderMetadata idp_metadata,
     IdpNetworkRequestManager::FetchStatus status,
@@ -932,23 +908,6 @@
                      weak_ptr_factory_.GetWeakPtr(), idp_metadata));
 }
 
-void FederatedAuthRequestImpl::DownloadBitmap(
-    const GURL& icon_url,
-    int ideal_icon_size,
-    WebContents::ImageDownloadCallback callback) {
-  if (!icon_url.is_valid()) {
-    std::move(callback).Run(0 /* id */, 404 /* http_status_code */, icon_url,
-                            std::vector<SkBitmap>(), std::vector<gfx::Size>());
-    return;
-  }
-
-  WebContents::FromRenderFrameHost(render_frame_host_)
-      ->DownloadImage(icon_url, /*is_favicon*/ false,
-                      gfx::Size(ideal_icon_size, ideal_icon_size),
-                      0 /* max_bitmap_size */, false /* bypass_cache */,
-                      std::move(callback));
-}
-
 void FederatedAuthRequestImpl::OnAccountsResponseReceived(
     IdentityProviderMetadata idp_metadata,
     IdpNetworkRequestManager::FetchStatus status,
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index 618131a..61bf2760 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -94,21 +94,11 @@
                          IdpNetworkRequestManager::Endpoints,
                          IdentityProviderMetadata idp_metadata);
   void OnManifestReady(IdentityProviderMetadata idp_metadata);
-  void OnBrandIconDownloaded(int icon_minimum_size,
-                             IdentityProviderMetadata idp_metadata,
-                             int id,
-                             int http_status_code,
-                             const GURL& image_url,
-                             const std::vector<SkBitmap>& bitmaps,
-                             const std::vector<gfx::Size>& sizes);
   void OnClientMetadataResponseReceived(
       IdentityProviderMetadata idp_metadata,
       IdpNetworkRequestManager::FetchStatus status,
       IdpNetworkRequestManager::ClientMetadata data);
 
-  void DownloadBitmap(const GURL& icon_url,
-                      int ideal_icon_size,
-                      WebContents::ImageDownloadCallback callback);
   void OnAccountsResponseReceived(
       IdentityProviderMetadata idp_metadata,
       IdpNetworkRequestManager::FetchStatus status,
diff --git a/content/public/browser/identity_request_dialog_controller.h b/content/public/browser/identity_request_dialog_controller.h
index 3745507..3bb1dc75 100644
--- a/content/public/browser/identity_request_dialog_controller.h
+++ b/content/public/browser/identity_request_dialog_controller.h
@@ -12,7 +12,6 @@
 #include "base/containers/span.h"
 #include "content/common/content_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "url/gurl.h"
 
@@ -82,7 +81,6 @@
 
   absl::optional<SkColor> brand_text_color;
   absl::optional<SkColor> brand_background_color;
-  SkBitmap brand_icon;
   GURL brand_icon_url;
 };
 
diff --git a/content/public/browser/render_process_host.h b/content/public/browser/render_process_host.h
index fc33c59..63d78348 100644
--- a/content/public/browser/render_process_host.h
+++ b/content/public/browser/render_process_host.h
@@ -275,6 +275,13 @@
   // This will never return ChildProcessHost::kInvalidUniqueID.
   virtual int GetID() const = 0;
 
+  // Returns a SafeRef to `this`. It should only be used in non-owning cases,
+  // where the caller is not expected to outlive `this`.
+  // This method is public so that it can be called from within //content, and
+  // used by MockRenderProcessHost. It isn't meant to be called outside of
+  // //content.
+  virtual base::SafeRef<RenderProcessHost> GetSafeRef() const = 0;
+
   // Returns true iff the Init() was called and the process hasn't died yet.
   //
   // Note that even if IsInitializedAndNotDead() returns true, then (for a short
diff --git a/content/public/test/mock_render_process_host.cc b/content/public/test/mock_render_process_host.cc
index 1ec037f7..6e80463 100644
--- a/content/public/test/mock_render_process_host.cc
+++ b/content/public/test/mock_render_process_host.cc
@@ -276,6 +276,10 @@
   return id_;
 }
 
+base::SafeRef<RenderProcessHost> MockRenderProcessHost::GetSafeRef() const {
+  return weak_ptr_factory_.GetSafeRef();
+}
+
 bool MockRenderProcessHost::IsInitializedAndNotDead() {
   return has_connection_;
 }
diff --git a/content/public/test/mock_render_process_host.h b/content/public/test/mock_render_process_host.h
index ffcd438..11e7e868 100644
--- a/content/public/test/mock_render_process_host.h
+++ b/content/public/test/mock_render_process_host.h
@@ -122,6 +122,7 @@
   const base::Process& GetProcess() override;
   bool IsReady() override;
   int GetID() const override;
+  base::SafeRef<RenderProcessHost> GetSafeRef() const override;
   bool IsInitializedAndNotDead() override;
   void SetBlocked(bool blocked) override;
   bool IsBlocked() override;
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 7fc96c9..3fb7b5e1 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -380,6 +380,11 @@
 crbug.com/1321305 [ mac release apple-apple-m1 ] Pixel_WebGPUImportVideoFrame [ Failure ]
 crbug.com/1321305 [ mac release apple-apple-m1 ] Pixel_WebGPUImportVideoFrameOffscreenCanvas [ Failure ]
 
+# Hangs with mac 0-copy path of WebGPU ImportExternalTexture
+crbug.com/1213170 [ mac no-asan amd-0x6821 ] Pixel_WebGPUImport* [ Failure ]
+crbug.com/1213170 [ mac no-asan intel-0xd26 ] Pixel_WebGPUImport* [ Failure ]
+crbug.com/1213170 [ mac no-asan intel-0x3e9b ] Pixel_WebGPUImport* [ Failure ]
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/device/BUILD.gn b/device/BUILD.gn
index 23c5580..e41a0a9 100644
--- a/device/BUILD.gn
+++ b/device/BUILD.gn
@@ -244,6 +244,9 @@
       "bluetooth/chromeos/bluetooth_utils_unittest.cc",
     ]
     deps += [ "//chromeos/constants" ]
+    if (is_chromeos_ash) {
+      deps += [ "//ash/constants" ]
+    }
     if (is_chromeos_lacros) {
       deps += [ "//chromeos/lacros:test_support" ]
     }
diff --git a/device/bluetooth/bluetooth_device.cc b/device/bluetooth/bluetooth_device.cc
index fffec04b..e9f8535b9 100644
--- a/device/bluetooth/bluetooth_device.cc
+++ b/device/bluetooth/bluetooth_device.cc
@@ -310,7 +310,7 @@
   BluetoothDeviceType type = GetDeviceType();
 
   // Get the vendor part of the address: "00:11:22" for "00:11:22:33:44:55"
-  std::string vendor = GetAddress().substr(0, 8);
+  std::string vendor = GetOuiPortionOfBluetoothAddress();
 
   // Verbatim "Bluetooth Mouse", model 96674
   if (type == BluetoothDeviceType::MOUSE && vendor == "00:12:A1")
@@ -459,6 +459,11 @@
   return GetAddress();
 }
 
+std::string BluetoothDevice::GetOuiPortionOfBluetoothAddress() const {
+  // Get the vendor part of the address: "00:11:22" for "00:11:22:33:44:55".
+  return GetAddress().substr(0, 8);
+}
+
 void BluetoothDevice::UpdateAdvertisementData(
     int8_t rssi,
     absl::optional<uint8_t> flags,
diff --git a/device/bluetooth/bluetooth_device.h b/device/bluetooth/bluetooth_device.h
index 3f91bbf..57b05f7 100644
--- a/device/bluetooth/bluetooth_device.h
+++ b/device/bluetooth/bluetooth_device.h
@@ -267,6 +267,10 @@
   // a unique key to identify the device and copied where needed.
   virtual std::string GetAddress() const = 0;
 
+  // Returns the OUI portion of the Bluetooth address, which refers to the
+  // device's vendor.
+  std::string GetOuiPortionOfBluetoothAddress() const;
+
   // Returns the Bluetooth address type of the device. Currently available on
   // Linux and Chrome OS.
   virtual AddressType GetAddressType() const = 0;
diff --git a/device/bluetooth/chromeos/bluetooth_utils.cc b/device/bluetooth/chromeos/bluetooth_utils.cc
index de338fd..5473c928 100644
--- a/device/bluetooth/chromeos/bluetooth_utils.cc
+++ b/device/bluetooth/chromeos/bluetooth_utils.cc
@@ -4,8 +4,8 @@
 
 #include "device/bluetooth/chromeos/bluetooth_utils.h"
 
-#include "ash/constants/ash_features.h"
 #include "base/containers/contains.h"
+#include "base/containers/fixed_flat_set.h"
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_functions.h"
@@ -21,6 +21,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/constants/ash_features.h"
 #include "ash/constants/ash_switches.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
@@ -150,6 +151,23 @@
   }
 }
 
+bool IsPolyDevicePairingAllowed() {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  return ash::features::IsPolyDevicePairingAllowed();
+#else
+  return false;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+}
+
+bool IsPolyDevice(const device::BluetoothDevice* device) {
+  // OUI portions of Bluetooth addresses for devices manufactured by Poly. See
+  // https://standards-oui.ieee.org/.
+  constexpr auto kPolyOuis = base::MakeFixedFlatSet<base::StringPiece>(
+      {"64:16:7F", "48:25:67", "00:04:F2"});
+
+  return base::Contains(kPolyOuis, device->GetOuiPortionOfBluetoothAddress());
+}
+
 }  // namespace
 
 device::BluetoothAdapter::DeviceList FilterBluetoothDeviceList(
@@ -182,6 +200,12 @@
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
+  // Never filter out Poly devices; this requires a special case since these
+  // devices often identify themselves as phones, which are disallowed below.
+  // See b/228118615.
+  if (IsPolyDevicePairingAllowed() && IsPolyDevice(device))
+    return false;
+
   // Always filter out laptops, etc. There is no intended use case or
   // Bluetooth profile in this context.
   if (device->GetDeviceType() == BluetoothDeviceType::COMPUTER)
diff --git a/device/bluetooth/chromeos/bluetooth_utils_unittest.cc b/device/bluetooth/chromeos/bluetooth_utils_unittest.cc
index 62402bcf..af879d51 100644
--- a/device/bluetooth/chromeos/bluetooth_utils_unittest.cc
+++ b/device/bluetooth/chromeos/bluetooth_utils_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/command_line.h"
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "build/chromeos_buildflags.h"
 #include "device/bluetooth/bluetooth_adapter_factory.h"
@@ -17,6 +18,10 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/constants/ash_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chromeos/lacros/lacros_test_helper.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -30,6 +35,14 @@
 constexpr char kHIDServiceUUID[] = "1812";
 constexpr char kSecurityKeyServiceUUID[] = "FFFD";
 constexpr char kUnexpectedServiceUUID[] = "1234";
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Note: The first 3 hex bytes represent the OUI portion of the address, which
+// indicates the device vendor. In this case, "64:16:7F:**:**:**" represents a
+// device manufactured by Poly.
+constexpr char kFakePolyDeviceAddress[] = "64:16:7F:12:34:56";
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 const size_t kMaxDevicesForFilter = 5;
 
 }  // namespace
@@ -63,6 +76,19 @@
     return mock_bluetooth_device_ptr;
   }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  void AddMockPolyDeviceToAdapter() {
+    MockBluetoothDevice* mock_bluetooth_device =
+        AddMockBluetoothDeviceToAdapter(BLUETOOTH_TRANSPORT_CLASSIC);
+    ON_CALL(*mock_bluetooth_device, GetName)
+        .WillByDefault(testing::Return(absl::nullopt));
+    ON_CALL(*mock_bluetooth_device, GetDeviceType)
+        .WillByDefault(testing::Return(BluetoothDeviceType::UNKNOWN));
+    ON_CALL(*mock_bluetooth_device, GetAddress)
+        .WillByDefault(testing::Return(kFakePolyDeviceAddress));
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   MockBluetoothAdapter* adapter() { return adapter_.get(); }
 
   MockBluetoothDevice* GetMockBluetoothDevice(size_t index) {
@@ -147,6 +173,29 @@
                                   1u /* num_expected_remaining_devices */);
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(BluetoothUtilsTest, ShowPolyDevice_PolyFlagDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      ash::features::kAllowPolyDevicePairing);
+
+  AddMockPolyDeviceToAdapter();
+  VerifyFilterBluetoothDeviceList(BluetoothFilterType::KNOWN,
+                                  0u /* num_expected_remaining_devices */);
+}
+
+// Regression test for b/228118615.
+TEST_F(BluetoothUtilsTest, ShowPolyDevice_PolyFlagEnabled) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      ash::features::kAllowPolyDevicePairing};
+
+  // Poly devices should not be filtered out, regardless of device type.
+  AddMockPolyDeviceToAdapter();
+  VerifyFilterBluetoothDeviceList(BluetoothFilterType::KNOWN,
+                                  1u /* num_expected_remaining_devices */);
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 TEST_F(
     BluetoothUtilsTest,
     TestFilterBluetoothDeviceList_FilterKnown_RemoveClassicDevicesWithoutNames) {
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm b/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
index 5cfcad9..30a9215 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
+++ b/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
@@ -10,6 +10,7 @@
 #include "components/viz/common/gpu/metal_context_provider.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "components/viz/common/resources/resource_sizes.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/command_buffer/service/mailbox_manager.h"
 #include "gpu/command_buffer/service/shared_context_state.h"
 #include "gpu/command_buffer/service/shared_image_backing.h"
@@ -120,12 +121,15 @@
     texture_descriptor.mipLevelCount = 1;
     texture_descriptor.sampleCount = 1;
 
-    // We need to have internal usages of CopySrc for copies and
-    // RenderAttachment for clears.
+    // We need to have internal usages of CopySrc for copies. If texture is not
+    // for video frame import, which has bi-planar format, we also need
+    // RenderAttachment usage for clears.
     WGPUDawnTextureInternalUsageDescriptor internalDesc = {};
     internalDesc.chain.sType = WGPUSType_DawnTextureInternalUsageDescriptor;
-    internalDesc.internalUsage =
-        WGPUTextureUsage_CopySrc | WGPUTextureUsage_RenderAttachment;
+    internalDesc.internalUsage = WGPUTextureUsage_CopySrc;
+    if (this->usage() & gpu::SHARED_IMAGE_USAGE_WEBGPU_SWAP_CHAIN_TEXTURE)
+      internalDesc.internalUsage |= WGPUTextureUsage_RenderAttachment;
+
     texture_descriptor.nextInChain =
         reinterpret_cast<WGPUChainedStruct*>(&internalDesc);
 
@@ -222,6 +226,12 @@
   if (!io_surface)
     return nullptr;
 
+  // TODO(crbug.com/1293514): Remove this if condition after using single
+  // multiplanar mailbox and actual_format could report multiplanar format
+  // correctly.
+  if (IOSurfaceGetPixelFormat(io_surface) == '420v')
+    actual_format = viz::YUV_420_BIPLANAR;
+
   absl::optional<WGPUTextureFormat> wgpu_format =
       viz::ToWGPUFormat(actual_format);
   if (wgpu_format.value() == WGPUTextureFormat_Undefined)
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index a157778b..249fc0a 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -218,9 +218,7 @@
     NOTIMPLEMENTED();
     return false;
   }
-  bool WasContextLostByRobustnessExtension() const override {
-    return false;
-  }
+  bool WasContextLostByRobustnessExtension() const override { return false; }
   void MarkContextLost(error::ContextLostReason reason) override {
     NOTIMPLEMENTED();
   }
diff --git a/ios/chrome/browser/ui/main/layout_guide_scene_agent.h b/ios/chrome/browser/ui/main/layout_guide_scene_agent.h
index fc044fc..09ee52d7 100644
--- a/ios/chrome/browser/ui/main/layout_guide_scene_agent.h
+++ b/ios/chrome/browser/ui/main/layout_guide_scene_agent.h
@@ -12,9 +12,12 @@
 // A scene agent that scopes a LayoutGuideCenter to a scene.
 @interface LayoutGuideSceneAgent : ObservingSceneAgent
 
-// The layout guide center for the current scene.
+// The layout guide center for the current scene and regular browser state.
 @property(nonatomic, readonly) LayoutGuideCenter* layoutGuideCenter;
 
+// The layout guide center for the current scene and Incognito browser state.
+@property(nonatomic, readonly) LayoutGuideCenter* incognitoLayoutGuideCenter;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_MAIN_LAYOUT_GUIDE_SCENE_AGENT_H_
diff --git a/ios/chrome/browser/ui/main/layout_guide_scene_agent.mm b/ios/chrome/browser/ui/main/layout_guide_scene_agent.mm
index 06eed799..c8a0a9f 100644
--- a/ios/chrome/browser/ui/main/layout_guide_scene_agent.mm
+++ b/ios/chrome/browser/ui/main/layout_guide_scene_agent.mm
@@ -16,6 +16,7 @@
   self = [super init];
   if (self) {
     _layoutGuideCenter = [[LayoutGuideCenter alloc] init];
+    _incognitoLayoutGuideCenter = [[LayoutGuideCenter alloc] init];
   }
   return self;
 }
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 2709f325..8db3277 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 @@
-2e0bdabc4a95a59d342a5a841f32d1d9408b1214
\ No newline at end of file
+d1fc6aa3308eb5bfd5d93a8486e44269d5c2c9d4
\ 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 e1ecb55a..6b426d9 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 @@
-5c4c9f051d5650920be3b16af2c2c70b69bff6af
\ No newline at end of file
+0b7695d1f19a2dfa7a12b0c5d511fafbdf51cc62
\ 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 c863ecd..697f8be9 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 @@
-70338a9d71ba0c266ad09d067ec3123d601363c1
\ No newline at end of file
+1f78b24fd2d2e6c88f7666468070445bb7519f07
\ 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 603d498e..378d51e 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 @@
-85add884205d35b32b30baab7ce447312ef082d3
\ No newline at end of file
+eae37b18f0cf8ab6f5725671c422094b6d161a6b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index 1c8fb9b6..0d3aa79 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-ae994e087e8d40cb99538da694681e6b992012c4
\ No newline at end of file
+21847acfbd519316d26667c7572b4496d6b61b6b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index 8c2f48da8..1a32f13 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-97d4071d9aca24e06dd752f2ccf24f2ea4772694
\ No newline at end of file
+b78e2c0f1a1bb7fbb8da4d0d08104a5c124fe854
\ 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 5ec805d..d0dd5531 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 @@
-185dc0bad2da8a532a1f4b2b758d63b31d9704a4
\ No newline at end of file
+7a9e50bbb45adccdf1f77f4ad3775d09bf078111
\ 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 a954c1bb..b76608e 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 @@
-8959092d8d5c2b7b998d62406e3a0f91280d56f4
\ No newline at end of file
+8a4997952bccf4a9372e45f32cec5feaf889b847
\ 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 381d9e9..e4b19b5 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 @@
-aed1eb425df1ed3f5ba6cff0c9de63430db95678
\ No newline at end of file
+93054abedc3d3b47a16b529bf6705042712397e6
\ 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 88fea63..75078ac 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 @@
-d4e29e7b2cf4f12ea110a091108440a717c52ff1
\ No newline at end of file
+72477b801958607ecd7e695bf5ff4861f69addc2
\ No newline at end of file
diff --git a/media/capture/video/win/video_capture_device_mf_win.cc b/media/capture/video/win/video_capture_device_mf_win.cc
index 3fbcc3b..d0fea45 100644
--- a/media/capture/video/win/video_capture_device_mf_win.cc
+++ b/media/capture/video/win/video_capture_device_mf_win.cc
@@ -999,12 +999,22 @@
     const VideoCaptureParams& params,
     std::unique_ptr<VideoCaptureDevice::Client> client) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  base::AutoLock lock(lock_);
-  return AllocateAndStartLocked(params, std::move(client));
+  bool success = false;
+  {
+    base::AutoLock lock(lock_);
+    success = AllocateAndStartLocked(params, std::move(client));
+  }
+  // Do not hold the lock while waiting.
+  if (success) {
+    HRESULT hr = WaitOnCaptureEvent(MF_CAPTURE_ENGINE_PREVIEW_STARTED);
+    if (SUCCEEDED(hr)) {
+      base::AutoLock lock(lock_);
+      is_started_ = true;
+    }
+  }
 }
 
-void VideoCaptureDeviceMFWin::AllocateAndStartLocked(
+bool VideoCaptureDeviceMFWin::AllocateAndStartLocked(
     const VideoCaptureParams& params,
     std::unique_ptr<VideoCaptureDevice::Client> client) {
   params_ = params;
@@ -1014,7 +1024,7 @@
   if (!engine_) {
     OnError(VideoCaptureError::kWinMediaFoundationEngineIsNull, FROM_HERE,
             E_FAIL);
-    return;
+    return false;
   }
 
   ComPtr<IMFCaptureSource> source;
@@ -1022,14 +1032,14 @@
   if (FAILED(hr)) {
     OnError(VideoCaptureError::kWinMediaFoundationEngineGetSourceFailed,
             FROM_HERE, hr);
-    return;
+    return false;
   }
 
   hr = FillCapabilities(source.Get(), true, &photo_capabilities_);
   if (FAILED(hr)) {
     OnError(VideoCaptureError::kWinMediaFoundationFillPhotoCapabilitiesFailed,
             FROM_HERE, hr);
-    return;
+    return false;
   }
 
   if (!photo_capabilities_.empty()) {
@@ -1042,13 +1052,13 @@
   if (FAILED(hr)) {
     OnError(VideoCaptureError::kWinMediaFoundationFillVideoCapabilitiesFailed,
             FROM_HERE, hr);
-    return;
+    return false;
   }
 
   if (video_capabilities.empty()) {
     OnError(VideoCaptureError::kWinMediaFoundationNoVideoCapabilityFound,
             FROM_HERE, "No video capability found");
-    return;
+    return false;
   }
 
   const CapabilityWin best_match_video_capability =
@@ -1061,7 +1071,7 @@
     OnError(
         VideoCaptureError::kWinMediaFoundationGetAvailableDeviceMediaTypeFailed,
         FROM_HERE, hr);
-    return;
+    return false;
   }
 
   hr = source->SetCurrentDeviceMediaType(
@@ -1070,7 +1080,7 @@
     OnError(
         VideoCaptureError::kWinMediaFoundationSetCurrentDeviceMediaTypeFailed,
         FROM_HERE, hr);
-    return;
+    return false;
   }
 
   ComPtr<IMFCaptureSink> sink;
@@ -1078,7 +1088,7 @@
   if (FAILED(hr)) {
     OnError(VideoCaptureError::kWinMediaFoundationEngineGetSinkFailed,
             FROM_HERE, hr);
-    return;
+    return false;
   }
 
   ComPtr<IMFCapturePreviewSink> preview_sink;
@@ -1087,14 +1097,14 @@
     OnError(VideoCaptureError::
                 kWinMediaFoundationSinkQueryCapturePreviewInterfaceFailed,
             FROM_HERE, hr);
-    return;
+    return false;
   }
 
   hr = preview_sink->RemoveAllStreams();
   if (FAILED(hr)) {
     OnError(VideoCaptureError::kWinMediaFoundationSinkRemoveAllStreamsFailed,
             FROM_HERE, hr);
-    return;
+    return false;
   }
 
   ComPtr<IMFMediaType> sink_video_media_type;
@@ -1103,7 +1113,7 @@
     OnError(
         VideoCaptureError::kWinMediaFoundationCreateSinkVideoMediaTypeFailed,
         FROM_HERE, hr);
-    return;
+    return false;
   }
 
   hr = ConvertToVideoSinkMediaType(
@@ -1114,7 +1124,7 @@
     OnError(
         VideoCaptureError::kWinMediaFoundationConvertToVideoSinkMediaTypeFailed,
         FROM_HERE, hr);
-    return;
+    return false;
   }
 
   DWORD dw_sink_stream_index = 0;
@@ -1124,7 +1134,7 @@
   if (FAILED(hr)) {
     OnError(VideoCaptureError::kWinMediaFoundationSinkAddStreamFailed,
             FROM_HERE, hr);
-    return;
+    return false;
   }
 
   hr = preview_sink->SetSampleCallback(dw_sink_stream_index,
@@ -1132,7 +1142,7 @@
   if (FAILED(hr)) {
     OnError(VideoCaptureError::kWinMediaFoundationSinkSetSampleCallbackFailed,
             FROM_HERE, hr);
-    return;
+    return false;
   }
 
   // Note, that it is not sufficient to wait for
@@ -1143,19 +1153,19 @@
   // event. For the lack of any other events indicating success, we have to wait
   // for the first video frame to arrive before sending our |OnStarted| event to
   // |client_|.
+  // We still need to wait for MF_CAPTURE_ENGINE_PREVIEW_STARTED event to ensure
+  // that we won't call StopPreview before the preview is started.
   has_sent_on_started_to_client_ = false;
   hr = engine_->StartPreview();
   if (FAILED(hr)) {
     OnError(VideoCaptureError::kWinMediaFoundationEngineStartPreviewFailed,
             FROM_HERE, hr);
-    return;
+    return false;
   }
 
   selected_video_capability_ =
       std::make_unique<CapabilityWin>(best_match_video_capability);
 
-  is_started_ = true;
-
   base::UmaHistogramEnumeration(
       "Media.VideoCapture.Win.Device.InternalPixelFormat",
       best_match_video_capability.source_pixel_format,
@@ -1168,17 +1178,31 @@
       "Media.VideoCapture.Win.Device.RequestedPixelFormat",
       params.requested_format.pixel_format,
       media::VideoPixelFormat::PIXEL_FORMAT_MAX);
+
+  return true;
 }
 
 void VideoCaptureDeviceMFWin::StopAndDeAllocate() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  base::AutoLock lock(lock_);
+  HRESULT hr = E_FAIL;
+  {
+    base::AutoLock lock(lock_);
 
-  if (is_started_ && engine_)
-    engine_->StopPreview();
-  is_started_ = false;
+    if (is_started_ && engine_) {
+      hr = engine_->StopPreview();
+    }
+  }
 
-  client_.reset();
+  // Do not hold the lock while waiting.
+  if (SUCCEEDED(hr)) {
+    WaitOnCaptureEvent(MF_CAPTURE_ENGINE_PREVIEW_STOPPED);
+  }
+
+  {
+    base::AutoLock lock(lock_);
+    is_started_ = false;
+    client_.reset();
+  }
 }
 
 void VideoCaptureDeviceMFWin::TakePhoto(TakePhotoCallback callback) {
@@ -1759,21 +1783,24 @@
   media_event->GetStatus(&hr);
   media_event->GetExtendedType(&capture_event_guid);
 
-  // TODO(http://crbug.com/1093521): Add cases for Start
-  // MF_CAPTURE_ENGINE_PREVIEW_STARTED and MF_CAPTURE_ENGINE_PREVIEW_STOPPED
   // When MF_CAPTURE_ENGINE_ERROR is returned the captureengine object is no
   // longer valid.
   if (capture_event_guid == MF_CAPTURE_ENGINE_ERROR || FAILED(hr)) {
+    last_error_hr_ = hr;
     capture_error_.Signal();
     // There should always be a valid error
     hr = SUCCEEDED(hr) ? E_UNEXPECTED : hr;
-  } else if (capture_event_guid == MF_CAPTURE_ENGINE_INITIALIZED) {
-    capture_initialize_.Signal();
+  } else {
+    if (capture_event_guid == MF_CAPTURE_ENGINE_INITIALIZED) {
+      capture_initialize_.Signal();
+    } else if (capture_event_guid == MF_CAPTURE_ENGINE_PREVIEW_STOPPED) {
+      capture_stopped_.Signal();
+    } else if (capture_event_guid == MF_CAPTURE_ENGINE_PREVIEW_STARTED) {
+      capture_started_.Signal();
+    }
+    return;
   }
 
-  // Lock is taken after events are signalled, because if the capture
-  // is being restarted, lock is currently owned by another thread running
-  // OnEvent().
   base::AutoLock lock(lock_);
 
   if (hr == DXGI_ERROR_DEVICE_REMOVED && dxgi_device_manager_ != nullptr) {
@@ -1804,6 +1831,12 @@
       // If AllocateAndStart fails somehow, OnError() will be called
       // internally. Therefore, it's safe to always override |hr| here.
       hr = S_OK;
+      // Ideally we should wait for MF_CAPTURE_ENGINE_PREVIEW_STARTED.
+      // However introducing that wait here could deadlocks in case if
+      // the same thread is used by MFCaptureEngine to signal events to
+      // the client.
+      // So we mark |is_started_| speculatevly here.
+      is_started_ = true;
       ++num_restarts_;
     } else {
       LOG(ERROR) << "Failed to re-initialize.";
@@ -1845,10 +1878,12 @@
   HRESULT hr = S_OK;
   HANDLE events[] = {nullptr, capture_error_.handle()};
 
-  // TODO(http://crbug.com/1093521): Add cases for Start
-  // MF_CAPTURE_ENGINE_PREVIEW_STARTED and MF_CAPTURE_ENGINE_PREVIEW_STOPPED
   if (capture_event_guid == MF_CAPTURE_ENGINE_INITIALIZED) {
     events[0] = capture_initialize_.handle();
+  } else if (capture_event_guid == MF_CAPTURE_ENGINE_PREVIEW_STOPPED) {
+    events[0] = capture_stopped_.handle();
+  } else if (capture_event_guid == MF_CAPTURE_ENGINE_PREVIEW_STARTED) {
+    events[0] = capture_started_.handle();
   } else {
     // no registered event handle for the event requested
     hr = E_NOTIMPL;
@@ -1866,7 +1901,10 @@
       LogError(FROM_HERE, hr);
       break;
     default:
-      hr = E_UNEXPECTED;
+      hr = last_error_hr_;
+      if (SUCCEEDED(hr)) {
+        hr = MF_E_UNEXPECTED;
+      }
       LogError(FROM_HERE, hr);
       break;
   }
diff --git a/media/capture/video/win/video_capture_device_mf_win.h b/media/capture/video/win/video_capture_device_mf_win.h
index 0e7cfb4a..207cae25 100644
--- a/media/capture/video/win/video_capture_device_mf_win.h
+++ b/media/capture/video/win/video_capture_device_mf_win.h
@@ -148,7 +148,7 @@
       base::TimeDelta timestamp,
       VideoCaptureFrameDropReason& frame_drop_reason);
   bool RecreateMFSource();
-  void AllocateAndStartLocked(
+  bool AllocateAndStartLocked(
       const VideoCaptureParams& params,
       std::unique_ptr<VideoCaptureDevice::Client> client);
 
@@ -181,6 +181,9 @@
   base::queue<TakePhotoCallback> video_stream_take_photo_callbacks_;
   base::WaitableEvent capture_initialize_;
   base::WaitableEvent capture_error_;
+  base::WaitableEvent capture_stopped_;
+  base::WaitableEvent capture_started_;
+  HRESULT last_error_hr_ = S_OK;
   scoped_refptr<DXGIDeviceManager> dxgi_device_manager_;
   absl::optional<int> camera_rotation_;
   VideoCaptureParams params_;
diff --git a/media/capture/video/win/video_capture_device_mf_win_unittest.cc b/media/capture/video/win/video_capture_device_mf_win_unittest.cc
index 15812ed..bf4eb40 100644
--- a/media/capture/video/win/video_capture_device_mf_win_unittest.cc
+++ b/media/capture/video/win/video_capture_device_mf_win_unittest.cc
@@ -575,6 +575,7 @@
 
   IFACEMETHODIMP StartPreview(void) override {
     OnStartPreview();
+    FireCaptureEvent(MF_CAPTURE_ENGINE_PREVIEW_STARTED, S_OK);
     return S_OK;
   }
 
@@ -582,6 +583,7 @@
 
   IFACEMETHODIMP StopPreview(void) override {
     OnStopPreview();
+    FireCaptureEvent(MF_CAPTURE_ENGINE_PREVIEW_STOPPED, S_OK);
     return S_OK;
   }
 
diff --git a/media/gpu/ipc/service/picture_buffer_manager.cc b/media/gpu/ipc/service/picture_buffer_manager.cc
index 7c02ce15..47229a7 100644
--- a/media/gpu/ipc/service/picture_buffer_manager.cc
+++ b/media/gpu/ipc/service/picture_buffer_manager.cc
@@ -252,6 +252,7 @@
     frame->metadata().allow_overlay = picture.allow_overlay();
     frame->metadata().read_lock_fences_enabled =
         picture.read_lock_fences_enabled();
+    frame->metadata().is_webgpu_compatible = picture.is_webgpu_compatible();
 
     // TODO(sandersd): Provide an API for VDAs to control this.
     frame->metadata().power_efficient = true;
diff --git a/media/gpu/mac/vt_video_decode_accelerator_mac.cc b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
index a2abeca8..9b34236 100644
--- a/media/gpu/mac/vt_video_decode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
@@ -2225,6 +2225,9 @@
     picture.set_scoped_shared_image(picture_info->scoped_shared_images[plane],
                                     plane);
   }
+  if (picture_format_ == PIXEL_FORMAT_NV12)
+    picture.set_is_webgpu_compatible(true);
+
   client_->PictureReady(std::move(picture));
   return true;
 }
diff --git a/media/video/picture.cc b/media/video/picture.cc
index 046aeb1..216db308 100644
--- a/media/video/picture.cc
+++ b/media/video/picture.cc
@@ -98,7 +98,8 @@
       read_lock_fences_enabled_(false),
       size_changed_(false),
       texture_owner_(false),
-      wants_promotion_hint_(false) {}
+      wants_promotion_hint_(false),
+      is_webgpu_compatible_(false) {}
 
 Picture::Picture(const Picture& other) = default;
 
diff --git a/media/video/picture.h b/media/video/picture.h
index 0b2173d..088a1c9 100644
--- a/media/video/picture.h
+++ b/media/video/picture.h
@@ -173,6 +173,12 @@
     return scoped_shared_images_[plane];
   }
 
+  void set_is_webgpu_compatible(bool is_webgpu_compatible) {
+    is_webgpu_compatible_ = is_webgpu_compatible;
+  }
+
+  bool is_webgpu_compatible() { return is_webgpu_compatible_; }
+
  private:
   int32_t picture_buffer_id_;
   int32_t bitstream_buffer_id_;
@@ -183,6 +189,7 @@
   bool size_changed_;
   bool texture_owner_;
   bool wants_promotion_hint_;
+  bool is_webgpu_compatible_;
   std::array<scoped_refptr<ScopedSharedImage>, VideoFrame::kMaxPlanes>
       scoped_shared_images_;
 };
diff --git a/net/third_party/quiche/BUILD.gn b/net/third_party/quiche/BUILD.gn
index b78cd4db..91fd774 100644
--- a/net/third_party/quiche/BUILD.gn
+++ b/net/third_party/quiche/BUILD.gn
@@ -69,15 +69,18 @@
     "overrides/quiche_platform_impl/quiche_url_utils_impl.cc",
     "overrides/quiche_platform_impl/quiche_url_utils_impl.h",
     "src/quiche/common/platform/api/quiche_bug_tracker.h",
+    "src/quiche/common/platform/api/quiche_client_stats.h",
     "src/quiche/common/platform/api/quiche_containers.h",
     "src/quiche/common/platform/api/quiche_export.h",
     "src/quiche/common/platform/api/quiche_export.h",
     "src/quiche/common/platform/api/quiche_flag_utils.h",
     "src/quiche/common/platform/api/quiche_flags.h",
+    "src/quiche/common/platform/api/quiche_header_policy.h",
     "src/quiche/common/platform/api/quiche_hostname_utils.cc",
     "src/quiche/common/platform/api/quiche_hostname_utils.h",
     "src/quiche/common/platform/api/quiche_iovec.h",
     "src/quiche/common/platform/api/quiche_logging.h",
+    "src/quiche/common/platform/api/quiche_lower_case_string.h",
     "src/quiche/common/platform/api/quiche_mem_slice.h",
     "src/quiche/common/platform/api/quiche_mutex.cc",
     "src/quiche/common/platform/api/quiche_mutex.h",
@@ -281,6 +284,8 @@
     "src/quiche/quic/core/crypto/boring_utils.h",
     "src/quiche/quic/core/crypto/cert_compressor.cc",
     "src/quiche/quic/core/crypto/cert_compressor.h",
+    "src/quiche/quic/core/crypto/certificate_util.cc",
+    "src/quiche/quic/core/crypto/certificate_util.h",
     "src/quiche/quic/core/crypto/certificate_view.cc",
     "src/quiche/quic/core/crypto/certificate_view.h",
     "src/quiche/quic/core/crypto/chacha20_poly1305_decrypter.cc",
@@ -631,6 +636,7 @@
     "src/quiche/quic/core/uber_quic_stream_id_manager.h",
     "src/quiche/quic/core/uber_received_packet_manager.cc",
     "src/quiche/quic/core/uber_received_packet_manager.h",
+    "src/quiche/quic/core/web_transport_interface.h",
     "src/quiche/quic/platform/api/quic_bug_tracker.h",
     "src/quiche/quic/platform/api/quic_client_stats.h",
     "src/quiche/quic/platform/api/quic_export.h",
@@ -647,6 +653,7 @@
     "src/quiche/quic/platform/api/quic_socket_address.cc",
     "src/quiche/quic/platform/api/quic_socket_address.h",
     "src/quiche/quic/platform/api/quic_stack_trace.h",
+    "src/quiche/quic/platform/api/quic_testvalue.h",
     "src/quiche/quic/platform/api/quic_thread.h",
     "src/quiche/spdy/core/hpack/hpack_constants.cc",
     "src/quiche/spdy/core/hpack/hpack_constants.h",
@@ -920,14 +927,6 @@
       "src/quiche/epoll_server/platform/api/epoll_address_test_utils.h",
       "src/quiche/epoll_server/platform/api/epoll_expect_bug.h",
       "src/quiche/epoll_server/platform/api/epoll_test.h",
-      "src/quiche/quic/test_tools/bad_packet_writer.cc",
-      "src/quiche/quic/test_tools/bad_packet_writer.h",
-      "src/quiche/quic/test_tools/limited_mtu_test_writer.cc",
-      "src/quiche/quic/test_tools/limited_mtu_test_writer.h",
-      "src/quiche/quic/test_tools/packet_dropping_test_writer.cc",
-      "src/quiche/quic/test_tools/packet_dropping_test_writer.h",
-      "src/quiche/quic/test_tools/packet_reordering_writer.cc",
-      "src/quiche/quic/test_tools/packet_reordering_writer.h",
       "src/quiche/quic/test_tools/quic_client_peer.cc",
       "src/quiche/quic/test_tools/quic_client_peer.h",
       "src/quiche/quic/test_tools/quic_server_peer.cc",
@@ -977,12 +976,54 @@
     "src/quiche/common/platform/default/quiche_platform_impl/quiche_test_loopback_impl.h",
     "src/quiche/common/test_tools/quiche_test_utils.cc",
     "src/quiche/common/test_tools/quiche_test_utils.h",
+    "src/quiche/http2/adapter/test_frame_sequence.cc",
+    "src/quiche/http2/adapter/test_frame_sequence.h",
+    "src/quiche/http2/adapter/test_utils.cc",
+    "src/quiche/http2/adapter/test_utils.h",
+    "src/quiche/http2/test_tools/frame_decoder_state_test_util.cc",
+    "src/quiche/http2/test_tools/frame_decoder_state_test_util.h",
+    "src/quiche/http2/test_tools/frame_parts.cc",
+    "src/quiche/http2/test_tools/frame_parts.h",
+    "src/quiche/http2/test_tools/frame_parts_collector.cc",
+    "src/quiche/http2/test_tools/frame_parts_collector.h",
+    "src/quiche/http2/test_tools/frame_parts_collector_listener.cc",
+    "src/quiche/http2/test_tools/frame_parts_collector_listener.h",
+    "src/quiche/http2/test_tools/hpack_block_builder.cc",
+    "src/quiche/http2/test_tools/hpack_block_builder.h",
+    "src/quiche/http2/test_tools/hpack_block_collector.cc",
+    "src/quiche/http2/test_tools/hpack_block_collector.h",
+    "src/quiche/http2/test_tools/hpack_entry_collector.cc",
+    "src/quiche/http2/test_tools/hpack_entry_collector.h",
+    "src/quiche/http2/test_tools/hpack_example.cc",
+    "src/quiche/http2/test_tools/hpack_example.h",
+    "src/quiche/http2/test_tools/hpack_string_collector.cc",
+    "src/quiche/http2/test_tools/hpack_string_collector.h",
+    "src/quiche/http2/test_tools/http2_constants_test_util.cc",
+    "src/quiche/http2/test_tools/http2_constants_test_util.h",
+    "src/quiche/http2/test_tools/http2_frame_builder.cc",
+    "src/quiche/http2/test_tools/http2_frame_builder.h",
+    "src/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.cc",
+    "src/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h",
+    "src/quiche/http2/test_tools/http2_random.cc",
+    "src/quiche/http2/test_tools/http2_random.h",
+    "src/quiche/http2/test_tools/http2_structure_decoder_test_util.cc",
+    "src/quiche/http2/test_tools/http2_structure_decoder_test_util.h",
+    "src/quiche/http2/test_tools/http2_structures_test_util.cc",
+    "src/quiche/http2/test_tools/http2_structures_test_util.h",
+    "src/quiche/http2/test_tools/payload_decoder_base_test_util.cc",
+    "src/quiche/http2/test_tools/payload_decoder_base_test_util.h",
+    "src/quiche/http2/test_tools/random_decoder_test_base.cc",
+    "src/quiche/http2/test_tools/random_decoder_test_base.h",
+    "src/quiche/http2/test_tools/random_util.cc",
+    "src/quiche/http2/test_tools/random_util.h",
     "src/quiche/quic/core/quic_trace_visitor.cc",
     "src/quiche/quic/core/quic_trace_visitor.h",
     "src/quiche/quic/platform/api/quic_expect_bug.h",
     "src/quiche/quic/platform/api/quic_test.h",
     "src/quiche/quic/platform/api/quic_test_loopback.h",
     "src/quiche/quic/platform/api/quic_test_output.h",
+    "src/quiche/quic/test_tools/bad_packet_writer.cc",
+    "src/quiche/quic/test_tools/bad_packet_writer.h",
     "src/quiche/quic/test_tools/crypto_test_utils.cc",
     "src/quiche/quic/test_tools/crypto_test_utils.h",
     "src/quiche/quic/test_tools/failing_proof_source.cc",
@@ -991,6 +1032,8 @@
     "src/quiche/quic/test_tools/fake_proof_source.h",
     "src/quiche/quic/test_tools/first_flight.cc",
     "src/quiche/quic/test_tools/first_flight.h",
+    "src/quiche/quic/test_tools/limited_mtu_test_writer.cc",
+    "src/quiche/quic/test_tools/limited_mtu_test_writer.h",
     "src/quiche/quic/test_tools/mock_clock.cc",
     "src/quiche/quic/test_tools/mock_clock.h",
     "src/quiche/quic/test_tools/mock_quic_client_promised_info.cc",
@@ -1001,8 +1044,14 @@
     "src/quiche/quic/test_tools/mock_quic_session_visitor.h",
     "src/quiche/quic/test_tools/mock_quic_spdy_client_stream.cc",
     "src/quiche/quic/test_tools/mock_quic_spdy_client_stream.h",
+    "src/quiche/quic/test_tools/mock_quic_time_wait_list_manager.cc",
+    "src/quiche/quic/test_tools/mock_quic_time_wait_list_manager.h",
     "src/quiche/quic/test_tools/mock_random.cc",
     "src/quiche/quic/test_tools/mock_random.h",
+    "src/quiche/quic/test_tools/packet_dropping_test_writer.cc",
+    "src/quiche/quic/test_tools/packet_dropping_test_writer.h",
+    "src/quiche/quic/test_tools/packet_reordering_writer.cc",
+    "src/quiche/quic/test_tools/packet_reordering_writer.h",
     "src/quiche/quic/test_tools/qpack/qpack_decoder_test_utils.cc",
     "src/quiche/quic/test_tools/qpack/qpack_decoder_test_utils.h",
     "src/quiche/quic/test_tools/qpack/qpack_encoder_peer.cc",
@@ -1017,6 +1066,7 @@
     "src/quiche/quic/test_tools/quic_buffered_packet_store_peer.h",
     "src/quiche/quic/test_tools/quic_client_promised_info_peer.cc",
     "src/quiche/quic/test_tools/quic_client_promised_info_peer.h",
+    "src/quiche/quic/test_tools/quic_client_session_cache_peer.h",
     "src/quiche/quic/test_tools/quic_coalesced_packet_peer.cc",
     "src/quiche/quic/test_tools/quic_coalesced_packet_peer.h",
     "src/quiche/quic/test_tools/quic_config_peer.cc",
@@ -1105,8 +1155,11 @@
     "src/quiche/quic/test_tools/test_ticket_crypter.h",
     "src/quiche/quic/test_tools/web_transport_resets_backend.cc",
     "src/quiche/quic/test_tools/web_transport_resets_backend.h",
-    "src/quiche/quic/tools/quic_tcp_like_trace_converter.cc",
-    "src/quiche/quic/tools/quic_tcp_like_trace_converter.h",
+    "src/quiche/quic/test_tools/web_transport_test_tools.h",
+    "src/quiche/spdy/test_tools/mock_spdy_framer_visitor.cc",
+    "src/quiche/spdy/test_tools/mock_spdy_framer_visitor.h",
+    "src/quiche/spdy/test_tools/spdy_test_utils.cc",
+    "src/quiche/spdy/test_tools/spdy_test_utils.h",
   ]
 
   configs += [ ":quiche_internal_config" ]
@@ -1134,6 +1187,7 @@
     "overrides/quiche_platform_impl/quiche_default_proof_providers_impl.cc",
     "overrides/quiche_platform_impl/quiche_default_proof_providers_impl.h",
     "overrides/quiche_platform_impl/quiche_system_event_loop_impl.h",
+    "src/quiche/common/platform/api/quiche_command_line_flags.h",
     "src/quiche/common/platform/api/quiche_default_proof_providers.h",
     "src/quiche/common/platform/api/quiche_file_utils.cc",
     "src/quiche/common/platform/api/quiche_file_utils.h",
@@ -1155,6 +1209,7 @@
     "src/quiche/quic/core/quic_time_wait_list_manager.h",
     "src/quiche/quic/core/tls_chlo_extractor.cc",
     "src/quiche/quic/core/tls_chlo_extractor.h",
+    "src/quiche/quic/platform/api/quic_default_proof_providers.h",
     "src/quiche/quic/tools/fake_proof_verifier.h",
     "src/quiche/quic/tools/quic_backend_response.cc",
     "src/quiche/quic/tools/quic_backend_response.h",
@@ -1178,10 +1233,15 @@
     "src/quiche/quic/tools/quic_spdy_client_base.cc",
     "src/quiche/quic/tools/quic_spdy_client_base.h",
     "src/quiche/quic/tools/quic_spdy_server_base.h",
+    "src/quiche/quic/tools/quic_tcp_like_trace_converter.cc",
+    "src/quiche/quic/tools/quic_tcp_like_trace_converter.h",
     "src/quiche/quic/tools/quic_url.cc",
     "src/quiche/quic/tools/quic_url.h",
     "src/quiche/quic/tools/simple_ticket_crypter.cc",
     "src/quiche/quic/tools/simple_ticket_crypter.h",
+    "src/quiche/quic/tools/web_transport_test_visitors.h",
+    "src/quiche/spdy/core/array_output_buffer.cc",
+    "src/quiche/spdy/core/array_output_buffer.h",
   ]
 
   configs += [ ":quiche_internal_config" ]
@@ -1298,16 +1358,31 @@
   }
 }
 
+bundle_data("test_data") {
+  visibility = [ ":quiche_tests" ]
+  testonly = true
+  sources = [
+    "src/quiche/common/platform/api/testdir/README.md",
+    "src/quiche/common/platform/api/testdir/a/b/c/d/e",
+    "src/quiche/common/platform/api/testdir/a/subdir/testfile",
+    "src/quiche/common/platform/api/testdir/a/z",
+    "src/quiche/common/platform/api/testdir/testfile",
+  ]
+  outputs = [ "{{bundle_resources_dir}}/" +
+              "{{source_root_relative_dir}}/{{source_file_part}}" ]
+}
+
 source_set("quiche_tests") {
   testonly = true
   sources = [
-    # TODO(bnc): Include in tests after test data files are added to QUICHE.
-    # "src/quiche/common/platform/api/quiche_file_utils_test.cc",
+    "src/quiche/common/platform/api/quiche_file_utils_test.cc",
     "src/quiche/common/platform/api/quiche_hostname_utils_test.cc",
     "src/quiche/common/platform/api/quiche_mem_slice_test.cc",
     "src/quiche/common/platform/api/quiche_reference_counted_test.cc",
+    "src/quiche/common/platform/api/quiche_stack_trace_test.cc",
     "src/quiche/common/platform/api/quiche_time_utils_test.cc",
     "src/quiche/common/platform/api/quiche_url_utils_test.cc",
+    "src/quiche/common/quiche_buffer_allocator_test.cc",
     "src/quiche/common/quiche_circular_deque_test.cc",
     "src/quiche/common/quiche_data_reader_test.cc",
     "src/quiche/common/quiche_data_writer_test.cc",
@@ -1323,10 +1398,6 @@
     "src/quiche/http2/adapter/noop_header_validator_test.cc",
     "src/quiche/http2/adapter/oghttp2_adapter_test.cc",
     "src/quiche/http2/adapter/oghttp2_session_test.cc",
-    "src/quiche/http2/adapter/test_frame_sequence.cc",
-    "src/quiche/http2/adapter/test_frame_sequence.h",
-    "src/quiche/http2/adapter/test_utils.cc",
-    "src/quiche/http2/adapter/test_utils.h",
     "src/quiche/http2/adapter/window_manager_test.cc",
     "src/quiche/http2/core/priority_write_scheduler_test.cc",
     "src/quiche/http2/decoder/decode_buffer_test.cc",
@@ -1366,44 +1437,8 @@
     "src/quiche/http2/hpack/varint/hpack_varint_round_trip_test.cc",
     "src/quiche/http2/http2_constants_test.cc",
     "src/quiche/http2/http2_structures_test.cc",
-    "src/quiche/http2/test_tools/frame_decoder_state_test_util.cc",
-    "src/quiche/http2/test_tools/frame_decoder_state_test_util.h",
-    "src/quiche/http2/test_tools/frame_parts.cc",
-    "src/quiche/http2/test_tools/frame_parts.h",
-    "src/quiche/http2/test_tools/frame_parts_collector.cc",
-    "src/quiche/http2/test_tools/frame_parts_collector.h",
-    "src/quiche/http2/test_tools/frame_parts_collector_listener.cc",
-    "src/quiche/http2/test_tools/frame_parts_collector_listener.h",
-    "src/quiche/http2/test_tools/hpack_block_builder.cc",
-    "src/quiche/http2/test_tools/hpack_block_builder.h",
     "src/quiche/http2/test_tools/hpack_block_builder_test.cc",
-    "src/quiche/http2/test_tools/hpack_block_collector.cc",
-    "src/quiche/http2/test_tools/hpack_block_collector.h",
-    "src/quiche/http2/test_tools/hpack_entry_collector.cc",
-    "src/quiche/http2/test_tools/hpack_entry_collector.h",
-    "src/quiche/http2/test_tools/hpack_example.cc",
-    "src/quiche/http2/test_tools/hpack_example.h",
-    "src/quiche/http2/test_tools/hpack_string_collector.cc",
-    "src/quiche/http2/test_tools/hpack_string_collector.h",
-    "src/quiche/http2/test_tools/http2_constants_test_util.cc",
-    "src/quiche/http2/test_tools/http2_constants_test_util.h",
-    "src/quiche/http2/test_tools/http2_frame_builder.cc",
-    "src/quiche/http2/test_tools/http2_frame_builder.h",
-    "src/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.cc",
-    "src/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h",
-    "src/quiche/http2/test_tools/http2_random.cc",
-    "src/quiche/http2/test_tools/http2_random.h",
     "src/quiche/http2/test_tools/http2_random_test.cc",
-    "src/quiche/http2/test_tools/http2_structure_decoder_test_util.cc",
-    "src/quiche/http2/test_tools/http2_structure_decoder_test_util.h",
-    "src/quiche/http2/test_tools/http2_structures_test_util.cc",
-    "src/quiche/http2/test_tools/http2_structures_test_util.h",
-    "src/quiche/http2/test_tools/payload_decoder_base_test_util.cc",
-    "src/quiche/http2/test_tools/payload_decoder_base_test_util.h",
-    "src/quiche/http2/test_tools/random_decoder_test_base.cc",
-    "src/quiche/http2/test_tools/random_decoder_test_base.h",
-    "src/quiche/http2/test_tools/random_util.cc",
-    "src/quiche/http2/test_tools/random_util.h",
     "src/quiche/quic/core/congestion_control/bandwidth_sampler_test.cc",
     "src/quiche/quic/core/congestion_control/bbr2_simulator_test.cc",
     "src/quiche/quic/core/congestion_control/bbr_sender_test.cc",
@@ -1424,12 +1459,14 @@
     "src/quiche/quic/core/crypto/aes_256_gcm_decrypter_test.cc",
     "src/quiche/quic/core/crypto/aes_256_gcm_encrypter_test.cc",
     "src/quiche/quic/core/crypto/cert_compressor_test.cc",
+    "src/quiche/quic/core/crypto/certificate_util_test.cc",
     "src/quiche/quic/core/crypto/certificate_view_test.cc",
     "src/quiche/quic/core/crypto/chacha20_poly1305_decrypter_test.cc",
     "src/quiche/quic/core/crypto/chacha20_poly1305_encrypter_test.cc",
     "src/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter_test.cc",
     "src/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter_test.cc",
     "src/quiche/quic/core/crypto/channel_id_test.cc",
+    "src/quiche/quic/core/crypto/client_proof_source_test.cc",
     "src/quiche/quic/core/crypto/crypto_framer_test.cc",
     "src/quiche/quic/core/crypto/crypto_handshake_message_test.cc",
     "src/quiche/quic/core/crypto/crypto_secret_boxer_test.cc",
@@ -1465,6 +1502,7 @@
     "src/quiche/quic/core/http/quic_spdy_stream_test.cc",
     "src/quiche/quic/core/http/spdy_server_push_utils_test.cc",
     "src/quiche/quic/core/http/spdy_utils_test.cc",
+    "src/quiche/quic/core/http/web_transport_http3_test.cc",
     "src/quiche/quic/core/legacy_quic_stream_id_manager_test.cc",
     "src/quiche/quic/core/packet_number_indexed_queue_test.cc",
     "src/quiche/quic/core/qpack/qpack_blocking_manager_test.cc",
@@ -1547,8 +1585,6 @@
     "src/quiche/quic/platform/api/quic_ip_address_test.cc",
     "src/quiche/quic/platform/api/quic_socket_address_test.cc",
     "src/quiche/quic/test_tools/crypto_test_utils_test.cc",
-    "src/quiche/quic/test_tools/mock_quic_time_wait_list_manager.cc",
-    "src/quiche/quic/test_tools/mock_quic_time_wait_list_manager.h",
     "src/quiche/quic/test_tools/quic_test_utils_test.cc",
     "src/quiche/quic/test_tools/simple_session_notifier_test.cc",
     "src/quiche/quic/test_tools/simulator/quic_endpoint_test.cc",
@@ -1556,8 +1592,6 @@
     "src/quiche/quic/tools/quic_memory_cache_backend_test.cc",
     "src/quiche/quic/tools/quic_tcp_like_trace_converter_test.cc",
     "src/quiche/quic/tools/simple_ticket_crypter_test.cc",
-    "src/quiche/spdy/core/array_output_buffer.cc",
-    "src/quiche/spdy/core/array_output_buffer.h",
     "src/quiche/spdy/core/array_output_buffer_test.cc",
     "src/quiche/spdy/core/hpack/hpack_decoder_adapter_test.cc",
     "src/quiche/spdy/core/hpack/hpack_encoder_test.cc",
@@ -1576,12 +1610,10 @@
     "src/quiche/spdy/core/spdy_prefixed_buffer_reader_test.cc",
     "src/quiche/spdy/core/spdy_protocol_test.cc",
     "src/quiche/spdy/core/spdy_simple_arena_test.cc",
-    "src/quiche/spdy/test_tools/mock_spdy_framer_visitor.cc",
-    "src/quiche/spdy/test_tools/mock_spdy_framer_visitor.h",
-    "src/quiche/spdy/test_tools/spdy_test_utils.cc",
-    "src/quiche/spdy/test_tools/spdy_test_utils.h",
   ]
 
+  data = [ "src/quiche/common/platform/api/testdir/" ]
+
   configs += [ ":quiche_internal_config" ]
   public_configs = [ ":quiche_config" ]
 
@@ -1593,6 +1625,9 @@
     "//testing/gmock",
     "//testing/gtest",
   ]
+  if (is_ios) {
+    deps += [ ":test_data" ]
+  }
   public_deps = []
 
   if (build_epoll_based_tools) {
diff --git a/net/third_party/quiche/overrides/quiche_platform_impl/quiche_test_impl.cc b/net/third_party/quiche/overrides/quiche_platform_impl/quiche_test_impl.cc
index 91318b6..fe818b3 100644
--- a/net/third_party/quiche/overrides/quiche_platform_impl/quiche_test_impl.cc
+++ b/net/third_party/quiche/overrides/quiche_platform_impl/quiche_test_impl.cc
@@ -15,7 +15,8 @@
 
 std::string QuicheGetCommonSourcePathImpl() {
   base::FilePath net_path = net::GetTestNetDirectory();
-  return net_path.AppendASCII("third_party/quiche/common").MaybeAsASCII();
+  return net_path.AppendASCII("third_party/quiche/src/quiche/common")
+      .MaybeAsASCII();
 }
 
 }  // namespace test
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 397a93f..38785e0 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -8154,15 +8154,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8188,7 +8188,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.68"
+              "revision": "version:101.0.4951.69"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8239,15 +8239,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M102/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M102/out/Release",
           "--client-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8273,7 +8273,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.51"
+              "revision": "version:102.0.5005.52"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8664,15 +8664,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8698,7 +8698,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.68"
+              "revision": "version:101.0.4951.69"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8749,15 +8749,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M102/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8783,7 +8783,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.51"
+              "revision": "version:102.0.5005.52"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index b45618c..ecbad5c3 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -46128,15 +46128,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46162,7 +46162,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.68"
+              "revision": "version:101.0.4951.69"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46213,15 +46213,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M102/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M102/out/Release",
           "--client-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46247,7 +46247,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.51"
+              "revision": "version:102.0.5005.52"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46638,15 +46638,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46672,7 +46672,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.68"
+              "revision": "version:101.0.4951.69"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46723,15 +46723,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M102/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46757,7 +46757,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.51"
+              "revision": "version:102.0.5005.52"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47152,15 +47152,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47186,7 +47186,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.68"
+              "revision": "version:101.0.4951.69"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47237,15 +47237,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M102/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M102/out/Release",
           "--client-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47271,7 +47271,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.51"
+              "revision": "version:102.0.5005.52"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47662,15 +47662,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47696,7 +47696,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.68"
+              "revision": "version:101.0.4951.69"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47747,15 +47747,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M102/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47781,7 +47781,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.51"
+              "revision": "version:102.0.5005.52"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48244,15 +48244,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48278,7 +48278,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.68"
+              "revision": "version:101.0.4951.69"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48329,15 +48329,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M102/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M102/out/Release",
           "--client-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48363,7 +48363,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.51"
+              "revision": "version:102.0.5005.52"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48754,15 +48754,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48788,7 +48788,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.68"
+              "revision": "version:101.0.4951.69"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48839,15 +48839,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M102/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48873,7 +48873,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.51"
+              "revision": "version:102.0.5005.52"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49336,15 +49336,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49370,7 +49370,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.68"
+              "revision": "version:101.0.4951.69"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49421,15 +49421,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M102/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M102/out/Release",
           "--client-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49455,7 +49455,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.51"
+              "revision": "version:102.0.5005.52"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49846,15 +49846,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49880,7 +49880,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.68"
+              "revision": "version:101.0.4951.69"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49931,15 +49931,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M102/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49965,7 +49965,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.51"
+              "revision": "version:102.0.5005.52"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/filters/pixel_tests.filter b/testing/buildbot/filters/pixel_tests.filter
index 660035a..1700a39 100644
--- a/testing/buildbot/filters/pixel_tests.filter
+++ b/testing/buildbot/filters/pixel_tests.filter
@@ -37,6 +37,7 @@
 ImportLockDialogViewBrowserTest.*
 InlineLoginHelperBrowserTest.InvokeUi_*
 IntentPickerDialogTest.*
+IntentPickerDialogGridViewTest.*
 InteractionSequenceBrowserUtilTest.CompareScreenshot_*
 LocalCardMigrationBrowserUiTest.*
 NewTabPageTest.*
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index bd93cbf0..33644610 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -462,16 +462,16 @@
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M102/out/Release',
-      '--impl-version=102'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=102',
     ],
     'identifier': 'with_impl_from_102',
     'swarming': {
@@ -479,23 +479,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M102',
-          'revision': 'version:102.0.5005.51'
+          'revision': 'version:102.0.5005.52',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M101/out/Release',
-      '--impl-version=101'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=101',
     ],
     'identifier': 'with_impl_from_101',
     'swarming': {
@@ -503,10 +503,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M101',
-          'revision': 'version:101.0.4951.68'
+          'revision': 'version:101.0.4951.69',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -606,16 +606,16 @@
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M102/out/Release',
-      '--impl-version=102'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=102',
     ],
     'identifier': 'with_impl_from_102',
     'swarming': {
@@ -623,23 +623,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M102',
-          'revision': 'version:102.0.5005.51'
+          'revision': 'version:102.0.5005.52',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M101/out/Release',
-      '--impl-version=101'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=101',
     ],
     'identifier': 'with_impl_from_101',
     'swarming': {
@@ -647,10 +647,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M101',
-          'revision': 'version:101.0.4951.68'
+          'revision': 'version:101.0.4951.69',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -750,16 +750,16 @@
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
+      '--client-outdir',
+      '../../weblayer_instrumentation_test_M102/out/Release',
       '--implementation-outdir',
       '.',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
-      '--client-outdir',
-      '../../weblayer_instrumentation_test_M102/out/Release',
-      '--client-version=102'
+      '--client-version=102',
     ],
     'identifier': 'with_client_from_102',
     'swarming': {
@@ -767,23 +767,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M102',
-          'revision': 'version:102.0.5005.51'
+          'revision': 'version:102.0.5005.52',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
+      '--client-outdir',
+      '../../weblayer_instrumentation_test_M101/out/Release',
       '--implementation-outdir',
       '.',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
-      '--client-outdir',
-      '../../weblayer_instrumentation_test_M101/out/Release',
-      '--client-version=101'
+      '--client-version=101',
     ],
     'identifier': 'with_client_from_101',
     'swarming': {
@@ -791,10 +791,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M101',
-          'revision': 'version:101.0.4951.68'
+          'revision': 'version:101.0.4951.69',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index f48cb663..6264adc1 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -8734,6 +8734,21 @@
             ]
         }
     ],
+    "WebViewNewInvalidateHeuristic": [
+        {
+            "platforms": [
+                "android_webview"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "WebViewNewInvalidateHeuristic"
+                    ]
+                }
+            ]
+        }
+    ],
     "WebViewOriginTrials": [
         {
             "platforms": [
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index 1346279c3..26deff5 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -647,6 +647,12 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_context.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_context_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_context_options.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_graph_builder.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_graph_builder.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_operand_descriptor.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_operand_descriptor.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_operand.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_operand.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_multi_cache_query_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_multi_cache_query_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_native_io_read_result.cc",
@@ -1248,6 +1254,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_model_format.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_model_loader.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_model_loader.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_operand_type.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_operand_type.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_power_preference.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_power_preference.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ml_tensor.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index 29c5f9968..5229ada 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -447,6 +447,9 @@
           "//third_party/blink/renderer/modules/ml/ml_tensor.idl",
           "//third_party/blink/renderer/modules/ml/ml_tensor_info.idl",
           "//third_party/blink/renderer/modules/ml/navigator_ml.idl",
+          "//third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.idl",
+          "//third_party/blink/renderer/modules/ml/webnn/ml_operand_descriptor.idl",
+          "//third_party/blink/renderer/modules/ml/webnn/ml_operand.idl",
           "//third_party/blink/renderer/modules/mojo/mojo_file_system_access.idl",
           "//third_party/blink/renderer/modules/native_io/native_io_file.idl",
           "//third_party/blink/renderer/modules/native_io/native_io_file_manager.idl",
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 86cdee74..07b04f3 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -306,6 +306,7 @@
   }
 
   deps = [
+    ":generate_eventhandler_names",
     ":generated_settings_macros",
     "//build:chromeos_buildflags",
     "//components/paint_preview/common",
@@ -1214,6 +1215,28 @@
   }
 }
 
+# Generate a list of event handler attributes, for use by Trusted Types.
+action("generate_eventhandler_names") {
+  script = "//third_party/blink/renderer/build/scripts/run_with_pythonpath.py"
+  real_script = "trustedtypes/generate_eventhandler_names.py"
+  inputs = [
+    web_idl_database_filepath,
+    real_script,
+  ]
+  outputs = [ "$target_gen_dir/trustedtypes/event_handler_names.h" ]
+  deps = [ "//third_party/blink/renderer/bindings:web_idl_database" ]
+  args = [
+    "-I",
+    rebase_path("//third_party/blink/renderer/bindings/scripts",
+                root_build_dir),
+    rebase_path(real_script, root_build_dir),
+    "--webidl",
+    rebase_path(inputs[0], root_build_dir),
+    "--out",
+    rebase_path(outputs[0], root_build_dir),
+  ]
+}
+
 # Fuzzer for blink::TextResourceDecoder.
 fuzzer_test("text_resource_decoder_fuzzer") {
   sources = [
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 67bca07..8208fc3 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -2292,13 +2292,18 @@
   if (iter != attribute_types->end())
     return iter->value;
 
-  if (q_name.LocalName().StartsWith("on")) {
-    // TODO(jakubvrana): This requires TrustedScript in all attributes
-    // starting with "on", including e.g. "one". We use this pattern elsewhere
-    // (e.g. in IsEventHandlerAttribute) but it's not ideal. Consider using
-    // the event attribute of the resulting AttributeTriggers.
+  // Since event handlers can be defined on nearly all elements, we will
+  // consider them independently of the specific element they're attached to.
+  //
+  // Note: Element::IsEventHandlerAttribute is different and over-approximates
+  // event-handler-ness, since it is expected to work only for builtin
+  // attributes (like "onclick"), while Trusted Types needs to deal with
+  // whatever users pass into setAttribute (for example "one"). Also, it
+  // requires the actual Attribute rather than the QName, which means
+  // Element::IsEventHandlerAttribute can only be called after an attribute has
+  // been constructed.
+  if (IsTrustedTypesEventHandlerAttribute(q_name))
     return SpecificTrustedType::kScript;
-  }
 
   return SpecificTrustedType::kNone;
 }
@@ -4296,6 +4301,7 @@
             element.AncestorsOrSiblingsAffectedByActiveInHas();
         break;
 
+      case CSSSelector::kPseudoAnyLink:
       case CSSSelector::kPseudoChecked:
       case CSSSelector::kPseudoDefault:
       case CSSSelector::kPseudoDisabled:
@@ -4303,12 +4309,14 @@
       case CSSSelector::kPseudoIndeterminate:
       case CSSSelector::kPseudoInRange:
       case CSSSelector::kPseudoInvalid:
+      case CSSSelector::kPseudoLink:
       case CSSSelector::kPseudoOutOfRange:
       case CSSSelector::kPseudoOptional:
       case CSSSelector::kPseudoPlaceholderShown:
       case CSSSelector::kPseudoReadOnly:
       case CSSSelector::kPseudoReadWrite:
       case CSSSelector::kPseudoRequired:
+      case CSSSelector::kPseudoTarget:
       case CSSSelector::kPseudoValid:
         ancestors_or_siblings = true;
         break;
diff --git a/third_party/blink/renderer/core/fileapi/public_url_manager.cc b/third_party/blink/renderer/core/fileapi/public_url_manager.cc
index 17c4f34..a079dfcfa 100644
--- a/third_party/blink/renderer/core/fileapi/public_url_manager.cc
+++ b/third_party/blink/renderer/core/fileapi/public_url_manager.cc
@@ -26,6 +26,7 @@
 
 #include "third_party/blink/renderer/core/fileapi/public_url_manager.h"
 
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/unguessable_token.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -35,6 +36,7 @@
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/fileapi/url_registry.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
 #include "third_party/blink/renderer/platform/blob/blob_data.h"
 #include "third_party/blink/renderer/platform/blob/blob_url.h"
 #include "third_party/blink/renderer/platform/blob/blob_url_null_origin_map.h"
@@ -76,6 +78,19 @@
   DCHECK(!url.IsEmpty());
   const String& url_string = url.GetString();
 
+  // Collect metrics on how frequently a worker context that makes use of the
+  // Blob URL API was created from a data URL. Note that we ignore service
+  // workers for this since they can't be created from data URLs.
+  if (GetExecutionContext()->IsWorkerGlobalScope()) {
+    WorkerGlobalScope* worker_global_scope =
+        DynamicTo<WorkerGlobalScope>(GetExecutionContext());
+    if (worker_global_scope->IsDedicatedWorkerGlobalScope() ||
+        worker_global_scope->IsSharedWorkerGlobalScope()) {
+      base::UmaHistogramBoolean("Storage.Blob.DataURLWorkerRegister",
+                                worker_global_scope->Url().ProtocolIsData());
+    }
+  }
+
   if (registrable->IsMojoBlob()) {
     // Measure how much jank the following synchronous IPC introduces.
     SCOPED_UMA_HISTOGRAM_TIMER("Storage.Blob.RegisterPublicURLTime");
@@ -143,6 +158,25 @@
     return;
 
   DCHECK(url.ProtocolIs("blob"));
+
+  // Collect metrics on how frequently a worker context that makes use of the
+  // Blob URL API was created from a data URL. Note that we ignore service
+  // workers for this since they can't be created from data URLs.
+  if (GetExecutionContext()->IsWorkerGlobalScope()) {
+    WorkerGlobalScope* worker_global_scope =
+        DynamicTo<WorkerGlobalScope>(GetExecutionContext());
+    // Note that for module workers created from blob URLs, this gets called
+    // before the worker global scope has been initialized. Thus, no valid URL
+    // is available.
+    if (worker_global_scope->IsUrlValid() &&
+        (worker_global_scope->IsDedicatedWorkerGlobalScope() ||
+         worker_global_scope->IsSharedWorkerGlobalScope())) {
+      base::UmaHistogramBoolean(
+          "Storage.Blob.DataURLWorkerResolveAsURLLoaderFactory",
+          worker_global_scope->Url().ProtocolIsData());
+    }
+  }
+
   url_store_->ResolveAsURLLoaderFactory(
       url, std::move(factory_receiver),
       WTF::Bind(
@@ -198,6 +232,24 @@
     return;
 
   DCHECK(url.ProtocolIs("blob"));
+
+  // Collect metrics on how frequently a worker context that makes use of the
+  // Blob URL API was created from a data URL. Note that we ignore service
+  // workers for this since they can't be created from data URLs.
+  if (GetExecutionContext()->IsWorkerGlobalScope()) {
+    WorkerGlobalScope* worker_global_scope =
+        DynamicTo<WorkerGlobalScope>(GetExecutionContext());
+    // Note that the URL validity check here is not known to be needed but
+    // adding it just in case!
+    if (worker_global_scope->IsUrlValid() &&
+        (worker_global_scope->IsDedicatedWorkerGlobalScope() ||
+         worker_global_scope->IsSharedWorkerGlobalScope())) {
+      base::UmaHistogramBoolean(
+          "Storage.Blob.DataURLWorkerResolveForNavigation",
+          worker_global_scope->Url().ProtocolIsData());
+    }
+  }
+
   url_store_->ResolveForNavigation(
       url, std::move(token_receiver),
       WTF::Bind(
diff --git a/third_party/blink/renderer/core/trustedtypes/generate_eventhandler_names.py b/third_party/blink/renderer/core/trustedtypes/generate_eventhandler_names.py
new file mode 100755
index 0000000..2258554
--- /dev/null
+++ b/third_party/blink/renderer/core/trustedtypes/generate_eventhandler_names.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import optparse
+import sys
+
+import web_idl
+
+
+# Read the WebIDL database and write a list of all event handler attributes.
+#
+# Reads the WebIDL database (--webidl) and writes a C++ .h file with a macro
+# containing all event handler names (to --out). All attributes declared as
+# EventHandler or On(BeforeUnload|Error)EventHandler types are considered
+# event handlers.
+#
+# The macro is called EVENT_HANDLER_LIST and follows the "X macro" model of
+# macro lists [1], as its used elsewhere [2] in the Chromium code base.
+#
+# [1] https://en.wikipedia.org/wiki/X_Macro
+# [2] https://source.chromium.org/search?q=%5E%23define%5C%20%5BA-Z_%5D*LIST%5C(%20file:v8
+def main(argv):
+    parser = optparse.OptionParser()
+    parser.add_option("--out")
+    parser.add_option("--webidl")
+    options, args = parser.parse_args(argv[1:])
+
+    for option in ("out", "webidl"):
+        if not getattr(options, option):
+            parser.error(f"--{option} is required.")
+    if args:
+        parser.error("No positional arguments supported.")
+
+    event_handlers = set()
+    event_handler_types = ("EventHandler", "OnBeforeUnloadEventHandler",
+                           "OnErrorEventHandler")
+
+    web_idl_database = web_idl.Database.read_from_file(options.webidl)
+    for interface in web_idl_database.interfaces:
+        for attribute in interface.attributes:
+            idl_type = attribute.idl_type
+            if (idl_type.is_typedef
+                    and idl_type.identifier in event_handler_types):
+                event_handlers.add(attribute.identifier)
+
+    license_and_header = """\
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+"""
+
+    with open(options.out, "w") as out:
+        print(license_and_header, file=out)
+        print("// Generated from WebIDL database. Don't edit, just generate.",
+              file=out)
+        print("//", file=out)
+        print(f"// Generator: {argv[0]}", file=out)
+        print("", file=out)
+        print("#define EVENT_HANDLER_LIST(EH) \\", file=out)
+        for event in event_handlers:
+            print(f"  EH({event}) \\", file=out)
+        print("\n", file=out)
+
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
diff --git a/third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.cc b/third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.cc
index 04138a1..29988be 100644
--- a/third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.cc
+++ b/third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.cc
@@ -19,6 +19,7 @@
 #include "third_party/blink/renderer/core/inspector/exception_metadata.h"
 #include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/core/trustedtypes/event_handler_names.h"
 #include "third_party/blink/renderer/core/trustedtypes/trusted_html.h"
 #include "third_party/blink/renderer/core/trustedtypes/trusted_script.h"
 #include "third_party/blink/renderer/core/trustedtypes/trusted_type_policy.h"
@@ -193,7 +194,10 @@
      true},
     {"*", "innerHTML", nullptr, SpecificTrustedType::kHTML, false, true},
     {"*", "outerHTML", nullptr, SpecificTrustedType::kHTML, false, true},
-    {"*", "on*", nullptr, SpecificTrustedType::kScript, true, false},
+#define FOREACH_EVENT_HANDLER(name) \
+  {"*", #name, nullptr, SpecificTrustedType::kScript, true, false},
+    EVENT_HANDLER_LIST(FOREACH_EVENT_HANDLER)
+#undef FOREACH_EVENT_HANDLER
 };
 
 // Does a type table entry match a property?
@@ -204,22 +208,20 @@
                     const String& ns) {
   DCHECK_EQ(tag.LowerASCII(), tag);
   return (left.element == tag || !strcmp(left.element, "*")) &&
-         (left.property == attr ||
-          (!strcmp(left.property, "on*") && attr.StartsWith("on"))) &&
-         left.element_namespace == ns && !left.is_not_property;
+         left.property == attr && left.element_namespace == ns &&
+         !left.is_not_property;
 }
 
 // Does a type table entry match an attribute?
 // (Attributes get queried by calling acecssor methods on the DOM. These are
-//  case-insensitivem, because DOM.)
+//  case-insensitive, because DOM.)
 bool EqualsAttribute(decltype(*kTypeTable)& left,
                      const String& tag,
                      const String& attr,
                      const String& ns) {
   DCHECK_EQ(tag.LowerASCII(), tag);
   return (left.element == tag || !strcmp(left.element, "*")) &&
-         (String(left.property).LowerASCII() == attr.LowerASCII() ||
-          (!strcmp(left.property, "on*") && attr.StartsWith("on"))) &&
+         CodeUnitCompareIgnoringASCIICase(attr, left.property) == 0 &&
          left.element_namespace == ns && !left.is_not_attribute;
 }
 
@@ -241,10 +243,10 @@
                                  const String&,
                                  const String&);
 
-String FindTypeInTypeTable(const String& tagName,
-                           const String& propertyName,
-                           const String& elementNS,
-                           PropertyEqualsFn equals) {
+SpecificTrustedType FindTypeInTypeTable(const String& tagName,
+                                        const String& propertyName,
+                                        const String& elementNS,
+                                        PropertyEqualsFn equals) {
   SpecificTrustedType type = SpecificTrustedType::kNone;
   for (auto* it = std::cbegin(kTypeTable); it != std::cend(kTypeTable); it++) {
     if ((*equals)(*it, tagName, propertyName, elementNS)) {
@@ -252,15 +254,23 @@
       break;
     }
   }
-  return getTrustedTypeName(type);
+  return type;
+}
+
+String FindTypeNameInTypeTable(const String& tagName,
+                               const String& propertyName,
+                               const String& elementNS,
+                               PropertyEqualsFn equals) {
+  return getTrustedTypeName(
+      FindTypeInTypeTable(tagName, propertyName, elementNS, equals));
 }
 
 String TrustedTypePolicyFactory::getPropertyType(
     const String& tagName,
     const String& propertyName,
     const String& elementNS) const {
-  return FindTypeInTypeTable(tagName.LowerASCII(), propertyName, elementNS,
-                             &EqualsProperty);
+  return FindTypeNameInTypeTable(tagName.LowerASCII(), propertyName, elementNS,
+                                 &EqualsProperty);
 }
 
 String TrustedTypePolicyFactory::getAttributeType(
@@ -268,8 +278,8 @@
     const String& attributeName,
     const String& tagNS,
     const String& attributeNS) const {
-  return FindTypeInTypeTable(tagName.LowerASCII(), attributeName, tagNS,
-                             &EqualsAttribute);
+  return FindTypeNameInTypeTable(tagName.LowerASCII(), attributeName, tagNS,
+                                 &EqualsAttribute);
 }
 
 String TrustedTypePolicyFactory::getPropertyType(
@@ -372,4 +382,20 @@
   visitor->Trace(policy_map_);
 }
 
+inline bool FindEventHandlerAttributeInTable(
+    const AtomicString& attributeName) {
+  return SpecificTrustedType::kScript ==
+         FindTypeInTypeTable("*", attributeName, String(), &EqualsAttribute);
+}
+
+bool TrustedTypePolicyFactory::IsEventHandlerAttributeName(
+    const AtomicString& attributeName) {
+  // Check that the "on" prefix indeed filters out only non-event handlers.
+  DCHECK(!FindEventHandlerAttributeInTable(attributeName) ||
+         attributeName.StartsWithIgnoringASCIICase("on"));
+
+  return attributeName.StartsWithIgnoringASCIICase("on") &&
+         FindEventHandlerAttributeInTable(attributeName);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.h b/third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.h
index e44fe58..1288407 100644
--- a/third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.h
+++ b/third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.h
@@ -75,6 +75,13 @@
   ExecutionContext* GetExecutionContext() const override;
   void Trace(Visitor*) const override;
 
+  // Check whether a given attribute is considered an event handler.
+  //
+  // This function is largely unrelated to the TrustedTypePolicyFactory, but
+  // it reuses the data from getTypeMapping, which is why we have defined it
+  // here.
+  static bool IsEventHandlerAttributeName(const AtomicString& attributeName);
+
  private:
   const WrapperTypeInfo* GetWrapperTypeInfoFromScriptValue(ScriptState*,
                                                            const ScriptValue&);
diff --git a/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc b/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc
index 17ca747..12500449 100644
--- a/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc
+++ b/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc
@@ -599,4 +599,10 @@
   return TrustedTypesCheckForHTML(html, execution_context, exception_state);
 }
 
+bool IsTrustedTypesEventHandlerAttribute(const QualifiedName& q_name) {
+  return q_name.NamespaceURI().IsNull() &&
+         TrustedTypePolicyFactory::IsEventHandlerAttributeName(
+             q_name.LocalName());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/trustedtypes/trusted_types_util.h b/third_party/blink/renderer/core/trustedtypes/trusted_types_util.h
index c0e40d41..12733890 100644
--- a/third_party/blink/renderer/core/trustedtypes/trusted_types_util.h
+++ b/third_party/blink/renderer/core/trustedtypes/trusted_types_util.h
@@ -14,6 +14,7 @@
 
 class ExceptionState;
 class ExecutionContext;
+class QualifiedName;
 class V8UnionStringOrTrustedScript;
 class V8UnionStringTreatNullAsEmptyStringOrTrustedScript;
 
@@ -87,6 +88,16 @@
 // into account.
 CORE_EXPORT bool RequireTrustedTypesCheck(const ExecutionContext*);
 
+// Determine whether an attribute is considered an event handler by Trusted
+// Types.
+//
+// Note: This is different from Element::IsEventHandlerAttribute, because
+// Element only needs this distinction for built-in attributes, but not for
+// user-defined property names. But Trusted Types needs this for any built-in or
+// user-defined attribute/property, and thus must check against a list of known
+// event handlers.
+bool IsTrustedTypesEventHandlerAttribute(const QualifiedName&);
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_TRUSTEDTYPES_TRUSTED_TYPES_UTIL_H_
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index 8a726511..b479f44c 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -117,6 +117,7 @@
     "//third_party/blink/renderer/modules/mediastream",
     "//third_party/blink/renderer/modules/media",
     "//third_party/blink/renderer/modules/ml",
+    "//third_party/blink/renderer/modules/ml/webnn",
     "//third_party/blink/renderer/modules/native_io",
     "//third_party/blink/renderer/modules/navigatorcontentutils",
     "//third_party/blink/renderer/modules/netinfo",
diff --git a/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.cc b/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.cc
index 7a5d4ff..a16285b 100644
--- a/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.cc
+++ b/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.cc
@@ -8,6 +8,7 @@
 
 #include "base/location.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/trace_event/trace_event.h"
 #include "third_party/blink/public/mojom/mediastream/media_devices.mojom-blink.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h"
 #include "third_party/blink/public/platform/web_string.h"
@@ -38,6 +39,43 @@
 
 }  // namespace
 
+constexpr const char kTraceCategory[] =
+    TRACE_DISABLED_BY_DEFAULT("mediastream");
+constexpr const char kVideoDeviceTraceName[] = "VideoDeviceRequest";
+
+class ScopedAsyncTrace {
+ public:
+  static std::unique_ptr<ScopedAsyncTrace> CreateIfEnabled(const char* name) {
+    bool enabled = false;
+    TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTraceCategory, &enabled);
+    return enabled ? base::WrapUnique(new ScopedAsyncTrace(name)) : nullptr;
+  }
+
+  ScopedAsyncTrace(const ScopedAsyncTrace&) = delete;
+  ScopedAsyncTrace& operator=(const ScopedAsyncTrace&) = delete;
+
+  ~ScopedAsyncTrace() {
+    TRACE_EVENT_NESTABLE_ASYNC_END0(kTraceCategory, name_, TRACE_ID_LOCAL(id_));
+  }
+
+  void AddStep(const char* step_name) {
+    step_.reset();  // Ensure previous trace step closes first.
+    step_.reset(new ScopedAsyncTrace(step_name, this));
+  }
+
+ private:
+  ScopedAsyncTrace(const char* name) : ScopedAsyncTrace(name, this) {}
+  ScopedAsyncTrace(const char* name, const void* id) : name_(name), id_(id) {
+    TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(kTraceCategory, name_,
+                                      TRACE_ID_LOCAL(id_));
+  }
+
+  const char* name_;
+  const void* id_;
+
+  std::unique_ptr<ScopedAsyncTrace> step_;
+};
+
 ApplyConstraintsProcessor::ApplyConstraintsProcessor(
     MediaDevicesDispatcherCallback media_devices_dispatcher_cb,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
@@ -115,6 +153,9 @@
 
 void ApplyConstraintsProcessor::ProcessVideoDeviceRequest() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  video_device_request_trace_ =
+      ScopedAsyncTrace::CreateIfEnabled(kVideoDeviceTraceName);
+
   if (AbortIfVideoRequestStateInvalid())
     return;
 
@@ -139,6 +180,9 @@
 void ApplyConstraintsProcessor::MaybeStopSourceForRestart(
     const Vector<media::VideoCaptureFormat>& formats) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (video_device_request_trace_)
+    video_device_request_trace_->AddStep("MaybeStopSourceForRestart");
+
   if (AbortIfVideoRequestStateInvalid())
     return;
 
@@ -163,6 +207,9 @@
 void ApplyConstraintsProcessor::MaybeSourceStoppedForRestart(
     blink::MediaStreamVideoSource::RestartResult result) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (video_device_request_trace_)
+    video_device_request_trace_->AddStep("MaybeSourceStoppedForRestart");
+
   if (AbortIfVideoRequestStateInvalid())
     return;
 
@@ -181,6 +228,9 @@
 void ApplyConstraintsProcessor::FindNewFormatAndRestart(
     const Vector<media::VideoCaptureFormat>& formats) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (video_device_request_trace_)
+    video_device_request_trace_->AddStep("FindNewFormatAndRestart");
+
   if (AbortIfVideoRequestStateInvalid())
     return;
 
@@ -198,6 +248,8 @@
 
 void ApplyConstraintsProcessor::MaybeSourceRestarted(
     blink::MediaStreamVideoSource::RestartResult result) {
+  if (video_device_request_trace_)
+    video_device_request_trace_->AddStep("MaybeSourceRestarted");
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (AbortIfVideoRequestStateInvalid())
     return;
@@ -213,6 +265,9 @@
 
 void ApplyConstraintsProcessor::FinalizeVideoRequest() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (video_device_request_trace_)
+    video_device_request_trace_->AddStep("FinalizeVideoRequest");
+
   if (AbortIfVideoRequestStateInvalid())
     return;
 
@@ -308,7 +363,10 @@
   DCHECK_EQ(current_request_->Track()->Source()->GetType(),
             MediaStreamSource::kTypeVideo);
   DCHECK(request_completed_cb_);
+
   if (GetCurrentVideoSource() != video_source_) {
+    if (video_device_request_trace_)
+      video_device_request_trace_->AddStep("Aborted");
     CannotApplyConstraints(
         "Track stopped or source changed. ApplyConstraints not possible.");
     return true;
@@ -356,6 +414,7 @@
   std::move(user_media_request_callback).Run();
   current_request_ = nullptr;
   video_source_ = nullptr;
+  video_device_request_trace_.reset();
 }
 
 blink::mojom::blink::MediaDevicesDispatcherHost*
diff --git a/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.h b/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.h
index 6840f56c..5999f8d 100644
--- a/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.h
+++ b/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.h
@@ -21,6 +21,7 @@
 namespace blink {
 class MediaStreamAudioSource;
 class MediaStreamVideoTrack;
+class ScopedAsyncTrace;
 
 // ApplyConstraintsProcessor is responsible for processing applyConstraints()
 // requests. Only one applyConstraints() request can be processed at a time.
@@ -87,6 +88,7 @@
   // |video_source_| and |request_completed_cb_| are the video source and
   // reply callback for the current request.
   Member<blink::ApplyConstraintsRequest> current_request_;
+  std::unique_ptr<ScopedAsyncTrace> video_device_request_trace_;
 
   // TODO(crbug.com/704136): Change to use Member.
   blink::MediaStreamVideoSource* video_source_ = nullptr;
diff --git a/third_party/blink/renderer/modules/ml/webnn/BUILD.gn b/third_party/blink/renderer/modules/ml/webnn/BUILD.gn
new file mode 100644
index 0000000..658d642
--- /dev/null
+++ b/third_party/blink/renderer/modules/ml/webnn/BUILD.gn
@@ -0,0 +1,15 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/blink/renderer/modules/modules.gni")
+
+blink_modules_sources("webnn") {
+  sources = [
+    "ml_graph_builder.cc",
+    "ml_graph_builder.h",
+    "ml_operand.cc",
+    "ml_operand.h",
+  ]
+  deps = [ "//third_party/blink/renderer/modules/ml" ]
+}
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
new file mode 100644
index 0000000..1449ef35f
--- /dev/null
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
@@ -0,0 +1,44 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.h"
+
+#include "third_party/blink/renderer/bindings/modules/v8/v8_ml_operand_descriptor.h"
+#include "third_party/blink/renderer/modules/ml/ml_context.h"
+#include "third_party/blink/renderer/modules/ml/webnn/ml_operand.h"
+
+namespace blink {
+
+// static
+MLGraphBuilder* MLGraphBuilder::Create(MLContext* context) {
+  return MakeGarbageCollected<MLGraphBuilder>(context);
+}
+
+MLGraphBuilder::MLGraphBuilder(MLContext* context) : ml_context_(context) {}
+
+MLGraphBuilder::~MLGraphBuilder() = default;
+
+void MLGraphBuilder::Trace(Visitor* visitor) const {
+  visitor->Trace(ml_context_);
+  ScriptWrappable::Trace(visitor);
+}
+
+MLOperand* MLGraphBuilder::input(String name, const MLOperandDescriptor* desc) {
+  return MakeGarbageCollected<MLOperand>(GetContext());
+}
+
+MLOperand* MLGraphBuilder::constant(const MLOperandDescriptor* desc,
+                                    NotShared<DOMArrayBufferView> buffer_view) {
+  return MakeGarbageCollected<MLOperand>(GetContext());
+}
+
+MLOperand* MLGraphBuilder::add(const MLOperand* a, const MLOperand* b) {
+  return MakeGarbageCollected<MLOperand>(GetContext());
+}
+
+MLContext* MLGraphBuilder::GetContext() const {
+  return ml_context_.Get();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.h b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.h
new file mode 100644
index 0000000..e541ec58
--- /dev/null
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.h
@@ -0,0 +1,52 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_ML_WEBNN_ML_GRAPH_BUILDER_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_ML_WEBNN_ML_GRAPH_BUILDER_H_
+
+#include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h"
+#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h"
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/heap/visitor.h"
+
+namespace blink {
+
+class MLContext;
+class MLOperand;
+class MLOperandDescriptor;
+
+typedef HeapVector<std::pair<String, Member<MLOperand>>> MLNamedOperands;
+
+class MLGraphBuilder : public ScriptWrappable {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  static MLGraphBuilder* Create(MLContext* context);
+
+  explicit MLGraphBuilder(MLContext* context);
+
+  MLGraphBuilder(const MLGraphBuilder&) = delete;
+  MLGraphBuilder& operator=(const MLGraphBuilder&) = delete;
+
+  ~MLGraphBuilder() override;
+
+  void Trace(Visitor* visitor) const override;
+
+  // ml_graph_builder.idl
+  MLOperand* input(String name, const MLOperandDescriptor* desc);
+  MLOperand* constant(const MLOperandDescriptor* desc,
+                      NotShared<DOMArrayBufferView> buffer_view);
+
+  MLOperand* add(const MLOperand* a, const MLOperand* b);
+
+ private:
+  MLContext* GetContext() const;
+  Member<MLContext> ml_context_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_ML_WEBNN_ML_GRAPH_BUILDER_H_
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.idl b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.idl
new file mode 100644
index 0000000..bd9aa02
--- /dev/null
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.idl
@@ -0,0 +1,20 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// https://www.w3.org/TR/webnn/
+
+typedef record<DOMString, MLOperand> MLNamedOperands;
+
+[
+  RuntimeEnabled=MachineLearningNeuralNetwork,
+  Exposed=Window
+] interface MLGraphBuilder {
+  constructor(MLContext context);
+
+  MLOperand input(DOMString name, MLOperandDescriptor desc);
+
+  MLOperand constant(MLOperandDescriptor desc, MLBufferView bufferView);
+
+  MLOperand add(MLOperand a, MLOperand b);
+};
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_operand.cc b/third_party/blink/renderer/modules/ml/webnn/ml_operand.cc
new file mode 100644
index 0000000..73a7831
--- /dev/null
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_operand.cc
@@ -0,0 +1,20 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/ml/webnn/ml_operand.h"
+
+#include "third_party/blink/renderer/modules/ml/ml_context.h"
+
+namespace blink {
+
+MLOperand::MLOperand(MLContext* context) : ml_context_(context) {}
+
+MLOperand::~MLOperand() = default;
+
+void MLOperand::Trace(Visitor* visitor) const {
+  visitor->Trace(ml_context_);
+  ScriptWrappable::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_operand.h b/third_party/blink/renderer/modules/ml/webnn/ml_operand.h
new file mode 100644
index 0000000..044ac48e
--- /dev/null
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_operand.h
@@ -0,0 +1,35 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_ML_WEBNN_ML_OPERAND_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_ML_WEBNN_ML_OPERAND_H_
+
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/heap/visitor.h"
+
+namespace blink {
+
+class MLContext;
+
+class MLOperand : public ScriptWrappable {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  explicit MLOperand(MLContext* context);
+
+  MLOperand(const MLOperand&) = delete;
+  MLOperand& operator=(const MLOperand&) = delete;
+
+  ~MLOperand() override;
+
+  void Trace(Visitor* visitor) const override;
+
+ private:
+  Member<MLContext> ml_context_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_ML_WEBNN_ML_OPERAND_H_
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_operand.idl b/third_party/blink/renderer/modules/ml/webnn/ml_operand.idl
new file mode 100644
index 0000000..ae556f58
--- /dev/null
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_operand.idl
@@ -0,0 +1,10 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// https://www.w3.org/TR/webnn/
+
+[
+  RuntimeEnabled=MachineLearningNeuralNetwork,
+  Exposed=Window
+] interface MLOperand {};
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_operand_descriptor.idl b/third_party/blink/renderer/modules/ml/webnn/ml_operand_descriptor.idl
new file mode 100644
index 0000000..fffc3f3
--- /dev/null
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_operand_descriptor.idl
@@ -0,0 +1,19 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// https://www.w3.org/TR/webnn/
+
+enum MLOperandType {
+  "float32",
+  "float16",
+  "int32",
+  "uint32",
+  "int8",
+  "uint8"
+};
+
+dictionary MLOperandDescriptor {
+  required MLOperandType type;
+  sequence<long> dimensions;
+};
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index e285aa5e..dfd21029 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -1342,6 +1342,13 @@
 }
 
 WebGLRenderingContextBase::~WebGLRenderingContextBase() {
+  // This resolver is non-null during a makeXRCompatible call, while waiting
+  // for a response from the browser and XR process. If the WebGL context is
+  // destroyed before we get a response, the resolver has to be rejected to
+  // be properly disposed of.
+  xr_compatible_ = false;
+  CompleteXrCompatiblePromiseIfPending(DOMExceptionCode::kInvalidStateError);
+
   // It's forbidden to refer to other GC'd objects in a GC'd object's
   // destructor. It's useful for DrawingBuffer to guarantee that it
   // calls its DrawingBufferClient during its own destruction, but if
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc
index adbdf242f..aab867e 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc
@@ -12,7 +12,9 @@
 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
 #include "third_party/blink/renderer/modules/webcodecs/video_frame.h"
 #include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
+#include "third_party/blink/renderer/modules/webgpu/gpu_adapter.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
+#include "third_party/blink/renderer/modules/webgpu/gpu_supported_features.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_texture.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_texture_view.h"
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
@@ -88,8 +90,13 @@
 
   // TODO(crbug.com/1306753): Use SharedImageProducer and CompositeSharedImage
   // rather than check 'is_webgpu_compatible'.
+  bool device_support_zero_copy =
+      device->adapter()->features()->FeatureNameSet().Contains(
+          "multi-planar-formats");
+
   if (media_video_frame->HasTextures() &&
       (media_video_frame->format() == media::PIXEL_FORMAT_NV12) &&
+      device_support_zero_copy &&
       media_video_frame->metadata().is_webgpu_compatible) {
     scoped_refptr<WebGPUMailboxTexture> mailbox_texture =
         WebGPUMailboxTexture::FromVideoFrame(
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index f05f8f5..243635c6 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1374,6 +1374,10 @@
       status: "experimental",
     },
     {
+      name: "MachineLearningNeuralNetwork",
+      status: "test",
+    },
+    {
       name: "ManagedConfiguration",
       status: "stable",
     },
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index d5f955e4..e024014c 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -33,12 +33,14 @@
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/common/input/web_input_event_attribution.h"
 #include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
 #include "third_party/blink/public/common/input/web_touch_event.h"
 #include "third_party/blink/public/common/page/launching_process_state.h"
 #include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h"
 #include "third_party/blink/public/platform/scheduler/web_renderer_process_type.h"
+#include "third_party/blink/public/platform/web_input_event_result.h"
 #include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.h"
 #include "third_party/blink/renderer/platform/scheduler/common/features.h"
 #include "third_party/blink/renderer/platform/scheduler/common/process_state.h"
@@ -48,6 +50,7 @@
 #include "third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.h"
 #include "third_party/blink/renderer/platform/scheduler/main_thread/main_thread.h"
 #include "third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl.h"
+#include "third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.h"
 #include "third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.h"
 #include "third_party/blink/renderer/platform/scheduler/main_thread/widget_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/event_loop.h"
@@ -870,9 +873,6 @@
 }
 
 void MainThreadSchedulerImpl::WillBeginFrame(const viz::BeginFrameArgs& args) {
-  // TODO(crbug/1068426): Figure out when and if |UpdatePolicy| should be called
-  //  and, if needed, remove the call to it from
-  //  |SetPrioritizeCompositingAfterInput|.
   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
                "MainThreadSchedulerImpl::WillBeginFrame", "args",
                args.AsValue());
@@ -888,7 +888,6 @@
     base::AutoLock lock(any_thread_lock_);
     any_thread().begin_main_frame_on_critical_path = args.on_critical_path;
   }
-  SetPrioritizeCompositingAfterInput(false);
   main_thread_only().have_seen_a_frame = true;
 }
 
@@ -1338,6 +1337,11 @@
       UpdatePolicyLocked(UpdateType::kMayEarlyOutIfPolicyUnchanged);
     }
   }
+  if (result != WebInputEventResult::kNotHandled &&
+      result != WebInputEventResult::kHandledSuppressed &&
+      !PendingUserInput::IsContinuousEventType(web_input_event.GetType())) {
+    main_thread_only().did_handle_discrete_input_event = true;
+  }
 }
 
 bool MainThreadSchedulerImpl::IsHighPriorityWorkAnticipated() {
@@ -2527,14 +2531,6 @@
 
   RecordTaskUkm(queue.get(), task, *task_timing);
 
-  // Assume this input will result in a frame, which we want to show ASAP.
-  if (queue &&
-      queue->GetPrioritisationType() ==
-          MainThreadTaskQueue::QueueTraits::PrioritisationType::kInput) {
-    SetPrioritizeCompositingAfterInput(
-        scheduling_settings().prioritize_compositing_after_input);
-  }
-
   MaybeUpdateCompositorTaskQueuePriorityOnTaskCompleted(queue.get(),
                                                         *task_timing);
 
@@ -2729,17 +2725,6 @@
   return scheduling_settings_;
 }
 
-void MainThreadSchedulerImpl::SetPrioritizeCompositingAfterInput(
-    bool prioritize_compositing_after_input) {
-  if (main_thread_only().prioritize_compositing_after_input ==
-      prioritize_compositing_after_input) {
-    return;
-  }
-  main_thread_only().prioritize_compositing_after_input =
-      prioritize_compositing_after_input;
-  UpdateCompositorTaskQueuePriority();
-}
-
 TaskQueue::QueuePriority MainThreadSchedulerImpl::ComputeCompositorPriority()
     const {
   if (main_thread_only().prioritize_compositing_after_input) {
@@ -2783,7 +2768,9 @@
     MaybeUpdateCompositorTaskQueuePriorityOnTaskCompleted(
         MainThreadTaskQueue* queue,
         const base::sequence_manager::TaskQueue::TaskTiming& task_timing) {
-  bool current_should_prioritize_compositor_task_queue =
+  bool current_prioritize_compositer_after_input =
+      main_thread_only().prioritize_compositing_after_input;
+  bool current_prioritize_compositor_after_delay =
       main_thread_only().should_prioritize_compositor_task_queue_after_delay;
   if (queue &&
       queue->queue_type() == MainThreadTaskQueue::QueueType::kCompositor &&
@@ -2792,14 +2779,25 @@
     main_thread_only().have_seen_a_frame = false;
     main_thread_only().should_prioritize_compositor_task_queue_after_delay =
         false;
+    main_thread_only().prioritize_compositing_after_input = false;
+  } else if (scheduling_settings().prioritize_compositing_after_input &&
+             queue &&
+             queue->queue_type() == MainThreadTaskQueue::QueueType::kInput &&
+             main_thread_only().did_handle_discrete_input_event) {
+    // Assume this input will result in a frame, which we want to show ASAP.
+    main_thread_only().prioritize_compositing_after_input = true;
   } else if (task_timing.end_time() - main_thread_only().last_frame_time >=
              kPrioritizeCompositingAfterDelay) {
     main_thread_only().should_prioritize_compositor_task_queue_after_delay =
         true;
   }
 
+  main_thread_only().did_handle_discrete_input_event = false;
+
   if (main_thread_only().should_prioritize_compositor_task_queue_after_delay !=
-      current_should_prioritize_compositor_task_queue) {
+          current_prioritize_compositor_after_delay ||
+      main_thread_only().prioritize_compositing_after_input !=
+          current_prioritize_compositer_after_input) {
     UpdateCompositorTaskQueuePriority();
   }
 }
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
index b68afd0..1f8a1719 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
@@ -397,9 +397,6 @@
 
   const SchedulingSettings& scheduling_settings() const;
 
-  void SetPrioritizeCompositingAfterInput(
-      bool prioritize_compositing_after_input);
-
   base::WeakPtr<MainThreadSchedulerImpl> GetWeakPtr();
 
   base::sequence_manager::TaskQueue::QueuePriority compositor_priority() const {
@@ -916,6 +913,11 @@
     bool should_prioritize_compositor_task_queue_after_delay;
     bool have_seen_a_frame;
 
+    // Set when a discrete input event is handled on the main thread. This is
+    // used by the kPrioritizeCompositingAfterInput experiment to determine if
+    // the next frame should be prioritized.
+    bool did_handle_discrete_input_event = false;
+
     WTF::Vector<AgentGroupSchedulerScope> agent_group_scheduler_scope_stack;
 
     std::unique_ptr<power_scheduler::PowerModeVoter> audible_power_mode_voter;
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
index 639af48..318dbf8 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/run_loop.h"
+#include "base/strings/string_util.h"
 #include "base/task/common/task_annotator.h"
 #include "base/task/sequence_manager/test/fake_task.h"
 #include "base/task/sequence_manager/test/sequence_manager_for_test.h"
@@ -35,6 +36,7 @@
 #include "third_party/blink/public/common/input/web_touch_event.h"
 #include "third_party/blink/public/common/page/launching_process_state.h"
 #include "third_party/blink/public/platform/scheduler/web_widget_scheduler.h"
+#include "third_party/blink/public/platform/web_input_event_result.h"
 #include "third_party/blink/renderer/platform/scheduler/common/features.h"
 #include "third_party/blink/renderer/platform/scheduler/common/throttling/budget_pool.h"
 #include "third_party/blink/renderer/platform/scheduler/main_thread/auto_advancing_virtual_time_domain.h"
@@ -811,12 +813,40 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  void AppendToVectorBeginMainFrameTask(Vector<String>* vector, String value) {
+    DoMainFrame();
+    AppendToVectorTestTask(vector, value);
+  }
+
+  void AppendToVectorBeginMainFrameTaskWithInput(Vector<String>* vector,
+                                                 String value) {
+    scheduler_->DidHandleInputEventOnMainThread(
+        FakeInputEvent(WebInputEvent::Type::kMouseMove),
+        WebInputEventResult::kHandledApplication);
+    AppendToVectorBeginMainFrameTask(vector, value);
+  }
+
+  void AppendToVectorInputEventTask(WebInputEvent::Type event_type,
+                                    Vector<String>* vector,
+                                    String value) {
+    scheduler_->DidHandleInputEventOnMainThread(
+        FakeInputEvent(event_type), WebInputEventResult::kHandledApplication);
+    AppendToVectorTestTask(vector, value);
+  }
+
   // Helper for posting several tasks of specific types. |task_descriptor| is a
   // string with space delimited task identifiers. The first letter of each
-  // task identifier specifies the task type:
+  // task identifier specifies the task type. For 'C' and 'P' types, the second
+  // letter specifies that type of task to simulate.
   // - 'D': Default task
   // - 'C': Compositor task
+  //   - "CM": Compositor task that simulates running a main frame
+  //   - "CI": Compositor task that simulates running a main frame with
+  //            rAF-algined input
   // - 'P': Input task
+  //   - "PC": Input task that simulates dispatching a continuous input event
+  //   - "PD": Input task that simulates dispatching a discrete input event
+  // - 'E': Input task that dispatches input events
   // - 'L': Loading task
   // - 'M': Loading Control task
   // - 'I': Idle task
@@ -836,14 +866,46 @@
                                         String::FromUTF8(task)));
           break;
         case 'C':
-          compositor_task_runner_->PostTask(
-              FROM_HERE, base::BindOnce(&AppendToVectorTestTask, run_order,
-                                        String::FromUTF8(task)));
+          if (base::StartsWith(task, "CM")) {
+            compositor_task_runner_->PostTask(
+                FROM_HERE, base::BindOnce(&MainThreadSchedulerImplTest::
+                                              AppendToVectorBeginMainFrameTask,
+                                          base::Unretained(this), run_order,
+                                          String::FromUTF8(task)));
+          } else if (base::StartsWith(task, "CI")) {
+            compositor_task_runner_->PostTask(
+                FROM_HERE,
+                base::BindOnce(&MainThreadSchedulerImplTest::
+                                   AppendToVectorBeginMainFrameTaskWithInput,
+                               base::Unretained(this), run_order,
+                               String::FromUTF8(task)));
+          } else {
+            compositor_task_runner_->PostTask(
+                FROM_HERE, base::BindOnce(&AppendToVectorTestTask, run_order,
+                                          String::FromUTF8(task)));
+          }
           break;
         case 'P':
-          input_task_runner_->PostTask(
-              FROM_HERE, base::BindOnce(&AppendToVectorTestTask, run_order,
-                                        String::FromUTF8(task)));
+          if (base::StartsWith(task, "PC")) {
+            input_task_runner_->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    &MainThreadSchedulerImplTest::AppendToVectorInputEventTask,
+                    base::Unretained(this), WebInputEvent::Type::kMouseMove,
+                    run_order, String::FromUTF8(task)));
+
+          } else if (base::StartsWith(task, "PD")) {
+            input_task_runner_->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    &MainThreadSchedulerImplTest::AppendToVectorInputEventTask,
+                    base::Unretained(this), WebInputEvent::Type::kMouseUp,
+                    run_order, String::FromUTF8(task)));
+          } else {
+            input_task_runner_->PostTask(
+                FROM_HERE, base::BindOnce(&AppendToVectorTestTask, run_order,
+                                          String::FromUTF8(task)));
+          }
           break;
         case 'L':
           loading_task_queue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
@@ -3410,25 +3472,46 @@
 TEST_F(MainThreadSchedulerImplWithCompositingAfterInputPrioritizationTest,
        CompositingAfterInput) {
   Vector<String> run_order;
-  // Input tasks cause compositor tasks to be prioritized until a BeginMainFrame
-  // runs.
+
+  // Input tasks don't cause compositor tasks to be prioritized unless an input
+  // event was handled.
   PostTestTasks(&run_order, "P1 T1 C1 C2");
   base::RunLoop().RunUntilIdle();
-  EXPECT_THAT(run_order, testing::ElementsAre("P1", "C1", "C2", "T1"));
+  EXPECT_THAT(run_order, testing::ElementsAre("P1", "T1", "C1", "C2"));
   run_order.clear();
 
-  scheduler_->WillBeginFrame(viz::BeginFrameArgs());
-
-  PostTestTasks(&run_order, "T2 C3 C4");
+  // Tasks with input events cause compositor tasks to be prioritized until a
+  // BeginMainFrame runs.
+  PostTestTasks(&run_order, "T1 P1 PD1 C1 C2 CM1 C2 T2 CM2");
   base::RunLoop().RunUntilIdle();
-  EXPECT_THAT(run_order, testing::ElementsAre("T2", "C3", "C4"));
+  EXPECT_THAT(run_order, testing::ElementsAre("P1", "PD1", "C1", "C2", "CM1",
+                                              "T1", "C2", "T2", "CM2"));
   run_order.clear();
 
   // Input tasks and compositor tasks will be interleaved because they have the
   // same priority.
-  PostTestTasks(&run_order, "T3 P2 C5 P3 C6");
+  PostTestTasks(&run_order, "T1 PD1 C1 PD2 C2");
   base::RunLoop().RunUntilIdle();
-  EXPECT_THAT(run_order, testing::ElementsAre("P2", "C5", "P3", "C6", "T3"));
+  EXPECT_THAT(run_order, testing::ElementsAre("PD1", "C1", "PD2", "C2", "T1"));
+  run_order.clear();
+}
+
+TEST_F(MainThreadSchedulerImplWithCompositingAfterInputPrioritizationTest,
+       CompositorNotPrioritizedAfterContinuousInput) {
+  Vector<String> run_order;
+
+  // rAF-aligned input should not cause the next frame to be prioritized.
+  PostTestTasks(&run_order, "P1 T1 CI1 T2 CI2 T3 CM1");
+  base::RunLoop().RunUntilIdle();
+  EXPECT_THAT(run_order, testing::ElementsAre("P1", "T1", "CI1", "T2", "CI2",
+                                              "T3", "CM1"));
+  run_order.clear();
+
+  // Continuous input that runs outside of rAF should not cause the next frame
+  // to be prioritized.
+  PostTestTasks(&run_order, "PC1 T1 CM1 T2 CM2");
+  base::RunLoop().RunUntilIdle();
+  EXPECT_THAT(run_order, testing::ElementsAre("PC1", "T1", "CM1", "T2", "CM2"));
   run_order.clear();
 }
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 055e355..e995752 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3270,7 +3270,6 @@
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/navigation-api/ordering-and-transition/navigate-cross-document-double.html [ Timeout ]
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/websockets/Create-extensions-empty.any.html?wpt_flags=h2 [ Skip Timeout ]
 crbug.com/626703 external/wpt/scroll-to-text-fragment/scroll-to-text-fragment.html [ Failure Skip Timeout ]
-crbug.com/626703 [ Mac10.12 ] external/wpt/service-workers/service-worker/same-site-cookies.https.html [ Skip Timeout ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/signed-exchange/subresource/sxg-subresource-header-integrity-mismatch.tentative.html [ Timeout ]
 crbug.com/626703 virtual/prerender/wpt_internal/prerender/unload-on-prerender-cross-origin-subframe-navigation.html [ Skip ]
 crbug.com/626703 virtual/prerender/wpt_internal/prerender/unload-on-prerender-main-frame-navigation.html [ Skip ]
@@ -6692,7 +6691,7 @@
 crbug.com/1306770 http/tests/inspector-protocol/network/interception-download.js [ Failure Pass ]
 
 # Sheriff 2022-03-18
-crbug.com/1290040 [ Linux ] external/wpt/service-workers/service-worker/same-site-cookies.https.html [ Skip ]
+crbug.com/1290040 external/wpt/service-workers/service-worker/same-site-cookies.https.html [ Skip ]
 
 # WebAudio flaky timeout (crbug.com/1307741)
 crbug.com/1307741 [ Mac ] external/wpt/webaudio/the-audio-api/the-mediastreamaudiosourcenode-interface/mediastreamaudiosourcenode-routing.html [ Timeout ]
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index 4cdaceb..487748a8 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: eb25c93dec4bd85f69baae714bc10dc56897f7f3
+Version: 59a12b30a554c66c4e9f67d8954f716439b7aa77
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 20cfb9b..a8edc2d6 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
@@ -250593,7 +250593,7 @@
      []
     ],
     "fedcm.https.html.headers": [
-     "e907cdd15d72a612b8dc06ddc68b47f05e1a4c18",
+     "408f78253c6783379a7e9715bbd5f56d9732a939",
      []
     ],
     "support": {
@@ -381518,6 +381518,13 @@
        {}
       ]
      ],
+     "snap-intended-direction.html": [
+      "4a1b56d2512f4ebd345ce6fc8357d471957f71f2",
+      [
+       null,
+       {}
+      ]
+     ],
      "snap-to-transformed-target.html": [
       "b8604269b4b9a1d6dbf337cfe867714cbf40fabb",
       [
@@ -457776,6 +457783,13 @@
         {}
        ]
       ],
+      "non-render-blocking-scripts.optional.html": [
+       "a4c32ea037b7b47490c54ddd7616b88bfebcdc76",
+       [
+        null,
+        {}
+       ]
+      ],
       "parser-blocking-script.tentative.html": [
        "8d391144b26b04a2c74ba1fdaf1f6ce675a79a76",
        [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/snap-intended-direction.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/snap-intended-direction.html
new file mode 100644
index 0000000..4a1b56d2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/snap-intended-direction.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>`intended direction` scroll snaps only at points ahead of the scroll direction</title>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-type" />
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1766805">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+div {
+  position: absolute;
+  margin: 0px;
+}
+#scroller {
+  width: 200px;
+  height: 100px;
+  overflow: scroll;
+  scroll-snap-type: x mandatory;
+}
+.snap {
+  scroll-snap-align: start;
+  background: green;
+}
+</style>
+
+<div id="scroller">
+  <div style="width: 2000px; height: 100px;"></div>
+  <div class="snap" style="left:   0px; width: 20px; height: 100px;">1</div>
+  <div class="snap" style="left: 100px; width: 20px; height: 100px;">2</div>
+  <div class="snap" style="left: 120px; width: 20px; height: 100px;">3</div>
+  <div class="snap" style="left: 300px; width: 20px; height: 100px;">4</div>
+  <div class="snap" style="left: 400px; width: 20px; height: 100px;">5</div>
+</div>
+
+<script>
+test(() => {
+  scroller.scrollBy(10, 0);
+  assert_equals(scroller.scrollLeft, 100);
+
+  scroller.scrollBy(10, 0);
+  assert_equals(scroller.scrollLeft, 120);
+
+  scroller.scrollBy(10, 0);
+  // Snaps to the next snap point even if the previous snap point is closer to
+  // the current position.
+  assert_equals(scroller.scrollLeft, 300);
+}, "`intended direction` scroll snaps only at points ahead of the scroll " +
+   "direction");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/link-pseudo-in-has.html b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/link-pseudo-in-has.html
new file mode 100644
index 0000000..0ff87976
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/link-pseudo-in-has.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>CSS Selectors Invalidation: :link, :visited :any-link, pseudo-class in :has() argument</title>
+<link rel="author" title="Byungwoo Lee" href="blee@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #parent { color: blue; }
+  #grandparent { color: blue; }
+  #parent:has(> :not(:link)) { color: grey; }
+  #parent:has(> :link) { color: green; }
+  #parent:has(> :visited) { color: red; }
+  #grandparent:has(:not(:any-link)) { color: grey; }
+  #grandparent:has(:any-link) { color: green; }
+</style>
+<div id="grandparent"></div>
+<script>
+  const BLUE = "rgb(0, 0, 255)";
+  const GREY = "rgb(128, 128, 128)";
+  const GREEN = "rgb(0, 128, 0)";
+  const RED = "rgb(255, 0, 0)";
+
+  function checkColor(id, color, target_matches) {
+    let element = document.getElementById(id);
+    let message = ["location.hash ==", location.hash, ": #" + id, "should be",
+                   color, (target_matches ? "with" : "without"),
+                   ":target"].join(" ");
+    assert_equals(getComputedStyle(element).color, color, message);
+  }
+
+  promise_test(async () => {
+    assert_equals(getComputedStyle(grandparent).color, BLUE,
+                  "grandparent should be blue without any element");
+
+    let parent = document.createElement("div");
+    parent.id = "parent";
+    grandparent.appendChild(parent);
+
+    assert_equals(getComputedStyle(grandparent).color, GREY,
+                  "grandparent should be grey after parent added");
+    assert_equals(getComputedStyle(parent).color, BLUE,
+                  "parent should be blue without any link");
+
+    let div = document.createElement("div");
+    parent.appendChild(div);
+
+    assert_equals(getComputedStyle(grandparent).color, GREY,
+                  "grandparent should be grey after div added");
+    assert_equals(getComputedStyle(parent).color, GREY,
+                  "parent should be grey after div added");
+
+    let visited = document.createElement("a");
+    visited.href = "";
+    parent.appendChild(visited);
+
+    assert_equals(getComputedStyle(grandparent).color, GREEN,
+                  "grandparent should be green after visited link added");
+    assert_equals(getComputedStyle(parent).color, GREEN,
+                  "parent should be green after visited link added");
+
+    let unvisited = document.createElement("a");
+    unvisited.href = "unvisited";
+    parent.appendChild(unvisited);
+
+    assert_equals(getComputedStyle(grandparent).color, GREEN,
+                  "grandparent should be green after unvisited link added");
+    assert_equals(getComputedStyle(parent).color, GREEN,
+                  "parent should be green after unvisited link added");
+
+    unvisited.remove();
+
+    assert_equals(getComputedStyle(grandparent).color, GREEN,
+                  "grandparent should be green after unvisited link removed");
+    assert_equals(getComputedStyle(parent).color, GREEN,
+                  "parent should be blue after unvisited link removed");
+
+    visited.remove();
+
+    assert_equals(getComputedStyle(grandparent).color, GREY,
+                  "grandparent should be grey after visited link removed");
+    assert_equals(getComputedStyle(parent).color, GREY,
+                  "parent should be grey after visited link removed");
+
+    div.remove();
+
+    assert_equals(getComputedStyle(grandparent).color, GREY,
+                  "grandparent should be grey after div removed");
+    assert_equals(getComputedStyle(parent).color, BLUE,
+                  "parent should be blue after div removed");
+
+    parent.remove();
+
+    assert_equals(getComputedStyle(grandparent).color, BLUE,
+                  "grandparent should be blue after parent removed");
+  });
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/web-xr-immersive-vr-session.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/web-xr-immersive-vr-session.https.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/prerender/resources/web-xr-immersive-vr-session.https.html
rename to third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/web-xr-immersive-vr-session.https.html
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/web-xr-inline-session.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/web-xr-inline-session.https.html
similarity index 100%
rename from third_party/blink/web_tests/wpt_internal/prerender/resources/web-xr-inline-session.https.html
rename to third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/web-xr-inline-session.https.html
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-web-xr-immersive-vr-session.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-web-xr-immersive-vr-session.https.html
similarity index 96%
rename from third_party/blink/web_tests/wpt_internal/prerender/restriction-web-xr-immersive-vr-session.https.html
rename to third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-web-xr-immersive-vr-session.https.html
index 3c7c6f8..b864718c 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/restriction-web-xr-immersive-vr-session.https.html
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-web-xr-immersive-vr-session.https.html
@@ -8,6 +8,8 @@
 <body>
 <script>
 
+setup(() => assertSpeculationRulesIsSupported());
+
 promise_test(async t => {
   const uid = token();
   const bc = new PrerenderChannel('test-channel', uid);
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-web-xr-inline-session.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-web-xr-inline-session.https.html
similarity index 96%
rename from third_party/blink/web_tests/wpt_internal/prerender/restriction-web-xr-inline-session.https.html
rename to third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-web-xr-inline-session.https.html
index 656dce6..4f308a7 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/restriction-web-xr-inline-session.https.html
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-web-xr-inline-session.https.html
@@ -8,6 +8,8 @@
 <body>
 <script>
 
+setup(() => assertSpeculationRulesIsSupported());
+
 promise_test(async t => {
   const uid = token();
   const bc = new PrerenderChannel('test-channel', uid);
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-event-handlers.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-event-handlers.tentative.html
new file mode 100644
index 0000000..57f8d3d9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-event-handlers.tentative.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'">
+</head>
+<body>
+<script>
+const element = document.createElement("div");
+
+[
+  "onclick",
+  "onchange",
+  "onfocus",
+  "oNclick",
+  "OnClIcK"
+].forEach(name => {
+  test(t => {
+    assert_throws_js(TypeError,
+        _ => element.setAttribute(name, "2+2"));
+  }, `Event handler ${name} should be blocked.`);
+});
+
+[
+  "one",
+  "oNe",
+  "onIcon",
+  "offIcon",
+  "blubb"
+].forEach(name => {
+  test(t => {
+    element.setAttribute(name, "2+2");
+  }, `Non-event handler ${name} should not be blocked.`);
+});
+
+// We'd like to be sure we're not missing anything. Let's "query" an HTML
+// element about which attributes it knows.
+const div = document.createElement("div");
+for(name in div.__proto__) {
+  const should_be_event_handler = name.startsWith("on");
+  if (should_be_event_handler) {
+    test(t => {
+      assert_throws_js(TypeError,
+          _ => element.setAttribute(name, "2+2"));
+    }, `Event handler div.${name} should be blocked.`);
+  } else {
+    test(t => {
+      element.setAttribute(name, "2+2");
+    }, `Non-event handler div.${name} should not be blocked.`);
+  }
+}
+</script>
+</body>
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt b/third_party/blink/web_tests/flag-specific/highdpi/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt
deleted file mode 100644
index 9f19c5d..0000000
--- a/third_party/blink/web_tests/flag-specific/highdpi/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL CSS Selectors Invalidation: target pseudo in :has() argument assert_equals: parent should be yellowgreen on fragment click expected "rgb(154, 205, 50)" but got "rgb(0, 128, 0)"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt b/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt
deleted file mode 100644
index 574ddd3..0000000
--- a/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL CSS Selectors Invalidation: :target pseudo-class in :has() argument assert_equals: parent1 should be green on fragment click expected "rgb(0, 128, 0)" but got "rgb(128, 128, 128)"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/external/wpt/webnn/idlharness.https.any-expected.txt b/third_party/blink/web_tests/platform/generic/external/wpt/webnn/idlharness.https.any-expected.txt
index 5df3157..59275703 100644
--- a/third_party/blink/web_tests/platform/generic/external/wpt/webnn/idlharness.https.any-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/external/wpt/webnn/idlharness.https.any-expected.txt
@@ -1,6 +1,6 @@
 This is a testharness.js-based test.
-Found 386 tests; 120 PASS, 266 FAIL, 0 TIMEOUT, 0 NOTRUN.
-FAIL idl_test setup promise_test: Unhandled rejection with value: object "ReferenceError: MLGraphBuilder is not defined"
+Found 386 tests; 135 PASS, 251 FAIL, 0 TIMEOUT, 0 NOTRUN.
+FAIL idl_test setup promise_test: Unhandled rejection with value: object "TypeError: Failed to construct 'MLGraphBuilder': parameter 1 is not of type 'MLContext'."
 PASS idl_test validation
 PASS Partial interface MLContext: original interface defined
 PASS Partial interface MLContext: member names are unique
@@ -137,12 +137,12 @@
 FAIL MLContext interface: context must inherit property "computeAsync(MLGraph, MLNamedArrayInputs, MLNamedArrayOutputs)" with the proper type assert_inherits: property "computeAsync" not found in prototype chain
 FAIL MLContext interface: calling computeAsync(MLGraph, MLNamedArrayInputs, MLNamedArrayOutputs) on context with too few arguments must throw TypeError assert_inherits: property "computeAsync" not found in prototype chain
 FAIL MLContext interface: context must inherit property "createCommandEncoder()" with the proper type assert_inherits: property "createCommandEncoder" not found in prototype chain
-FAIL MLOperand interface: existence and properties of interface object assert_own_property: self does not have own property "MLOperand" expected property "MLOperand" missing
-FAIL MLOperand interface object length assert_own_property: self does not have own property "MLOperand" expected property "MLOperand" missing
-FAIL MLOperand interface object name assert_own_property: self does not have own property "MLOperand" expected property "MLOperand" missing
-FAIL MLOperand interface: existence and properties of interface prototype object assert_own_property: self does not have own property "MLOperand" expected property "MLOperand" missing
-FAIL MLOperand interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "MLOperand" expected property "MLOperand" missing
-FAIL MLOperand interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "MLOperand" expected property "MLOperand" missing
+PASS MLOperand interface: existence and properties of interface object
+PASS MLOperand interface object length
+PASS MLOperand interface object name
+PASS MLOperand interface: existence and properties of interface prototype object
+PASS MLOperand interface: existence and properties of interface prototype object's "constructor" property
+PASS MLOperand interface: existence and properties of interface prototype object's @@unscopables property
 FAIL MLOperand must be primary interface of input assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: input is not defined"
 FAIL Stringification of input assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: input is not defined"
 FAIL MLOperand must be primary interface of filter assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: filter is not defined"
@@ -155,84 +155,84 @@
 FAIL MLOperator interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "MLOperator" expected property "MLOperator" missing
 FAIL MLOperator must be primary interface of relu assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: relu is not defined"
 FAIL Stringification of relu assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: relu is not defined"
-FAIL MLGraphBuilder interface: existence and properties of interface object assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface object length assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface object name assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: existence and properties of interface prototype object assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation input(DOMString, MLOperandDescriptor) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation constant(MLOperandDescriptor, MLBufferView) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation constant(double, optional MLOperandType) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: member build Cannot use 'in' operator to search for 'build' in undefined
-FAIL MLGraphBuilder interface: operation batchNormalization(MLOperand, MLOperand, MLOperand, optional MLBatchNormalizationOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation clamp(MLOperand, optional MLClampOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation clamp(optional MLClampOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation concat(sequence<MLOperand>, long) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation conv2d(MLOperand, MLOperand, optional MLConv2dOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation convTranspose2d(MLOperand, MLOperand, optional MLConvTranspose2dOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation add(MLOperand, MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation sub(MLOperand, MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation mul(MLOperand, MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation div(MLOperand, MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation max(MLOperand, MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation min(MLOperand, MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation pow(MLOperand, MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation abs(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation ceil(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation cos(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation exp(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation floor(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation log(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation neg(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation sin(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation tan(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation elu(MLOperand, optional MLEluOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation elu(optional MLEluOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation gemm(MLOperand, MLOperand, optional MLGemmOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation gru(MLOperand, MLOperand, MLOperand, long, long, optional MLGruOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation gruCell(MLOperand, MLOperand, MLOperand, MLOperand, long, optional MLGruCellOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation hardSigmoid(MLOperand, optional MLHardSigmoidOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation hardSigmoid(optional MLHardSigmoidOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation hardSwish(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation hardSwish() assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation instanceNormalization(MLOperand, optional MLInstanceNormalizationOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation leakyRelu(MLOperand, optional MLLeakyReluOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation leakyRelu(optional MLLeakyReluOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation matmul(MLOperand, MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation linear(MLOperand, optional MLLinearOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation linear(optional MLLinearOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation pad(MLOperand, MLOperand, optional MLPadOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation averagePool2d(MLOperand, optional MLPool2dOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation l2Pool2d(MLOperand, optional MLPool2dOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation maxPool2d(MLOperand, optional MLPool2dOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reduceL1(MLOperand, optional MLReduceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reduceL2(MLOperand, optional MLReduceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reduceLogSum(MLOperand, optional MLReduceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reduceLogSumExp(MLOperand, optional MLReduceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reduceMax(MLOperand, optional MLReduceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reduceMean(MLOperand, optional MLReduceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reduceMin(MLOperand, optional MLReduceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reduceProduct(MLOperand, optional MLReduceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reduceSum(MLOperand, optional MLReduceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reduceSumSquare(MLOperand, optional MLReduceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation relu(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation relu() assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation resample2d(MLOperand, optional MLResample2dOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation reshape(MLOperand, sequence<long>) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation sigmoid(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation sigmoid() assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation slice(MLOperand, sequence<long>, sequence<long>, optional MLSliceOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation softmax(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation softplus(MLOperand, optional MLSoftplusOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation softplus(optional MLSoftplusOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation softsign(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation softsign() assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation split(MLOperand, (unsigned long or sequence<unsigned long>), optional MLSplitOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation squeeze(MLOperand, optional MLSqueezeOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation tanh(MLOperand) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation tanh() assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
-FAIL MLGraphBuilder interface: operation transpose(MLOperand, optional MLTransposeOptions) assert_own_property: self does not have own property "MLGraphBuilder" expected property "MLGraphBuilder" missing
+PASS MLGraphBuilder interface: existence and properties of interface object
+PASS MLGraphBuilder interface object length
+PASS MLGraphBuilder interface object name
+PASS MLGraphBuilder interface: existence and properties of interface prototype object
+PASS MLGraphBuilder interface: existence and properties of interface prototype object's "constructor" property
+PASS MLGraphBuilder interface: existence and properties of interface prototype object's @@unscopables property
+PASS MLGraphBuilder interface: operation input(DOMString, MLOperandDescriptor)
+FAIL MLGraphBuilder interface: operation constant(MLOperandDescriptor, MLBufferView) assert_equals: property has wrong .length expected 1 but got 2
+FAIL MLGraphBuilder interface: operation constant(double, optional MLOperandType) assert_equals: property has wrong .length expected 1 but got 2
+PASS MLGraphBuilder interface: member build
+FAIL MLGraphBuilder interface: operation batchNormalization(MLOperand, MLOperand, MLOperand, optional MLBatchNormalizationOptions) assert_own_property: interface prototype object missing non-static operation expected property "batchNormalization" missing
+FAIL MLGraphBuilder interface: operation clamp(MLOperand, optional MLClampOptions) assert_own_property: interface prototype object missing non-static operation expected property "clamp" missing
+FAIL MLGraphBuilder interface: operation clamp(optional MLClampOptions) assert_own_property: interface prototype object missing non-static operation expected property "clamp" missing
+FAIL MLGraphBuilder interface: operation concat(sequence<MLOperand>, long) assert_own_property: interface prototype object missing non-static operation expected property "concat" missing
+FAIL MLGraphBuilder interface: operation conv2d(MLOperand, MLOperand, optional MLConv2dOptions) assert_own_property: interface prototype object missing non-static operation expected property "conv2d" missing
+FAIL MLGraphBuilder interface: operation convTranspose2d(MLOperand, MLOperand, optional MLConvTranspose2dOptions) assert_own_property: interface prototype object missing non-static operation expected property "convTranspose2d" missing
+PASS MLGraphBuilder interface: operation add(MLOperand, MLOperand)
+FAIL MLGraphBuilder interface: operation sub(MLOperand, MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "sub" missing
+FAIL MLGraphBuilder interface: operation mul(MLOperand, MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "mul" missing
+FAIL MLGraphBuilder interface: operation div(MLOperand, MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "div" missing
+FAIL MLGraphBuilder interface: operation max(MLOperand, MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "max" missing
+FAIL MLGraphBuilder interface: operation min(MLOperand, MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "min" missing
+FAIL MLGraphBuilder interface: operation pow(MLOperand, MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "pow" missing
+FAIL MLGraphBuilder interface: operation abs(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "abs" missing
+FAIL MLGraphBuilder interface: operation ceil(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "ceil" missing
+FAIL MLGraphBuilder interface: operation cos(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "cos" missing
+FAIL MLGraphBuilder interface: operation exp(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "exp" missing
+FAIL MLGraphBuilder interface: operation floor(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "floor" missing
+FAIL MLGraphBuilder interface: operation log(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "log" missing
+FAIL MLGraphBuilder interface: operation neg(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "neg" missing
+FAIL MLGraphBuilder interface: operation sin(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "sin" missing
+FAIL MLGraphBuilder interface: operation tan(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "tan" missing
+FAIL MLGraphBuilder interface: operation elu(MLOperand, optional MLEluOptions) assert_own_property: interface prototype object missing non-static operation expected property "elu" missing
+FAIL MLGraphBuilder interface: operation elu(optional MLEluOptions) assert_own_property: interface prototype object missing non-static operation expected property "elu" missing
+FAIL MLGraphBuilder interface: operation gemm(MLOperand, MLOperand, optional MLGemmOptions) assert_own_property: interface prototype object missing non-static operation expected property "gemm" missing
+FAIL MLGraphBuilder interface: operation gru(MLOperand, MLOperand, MLOperand, long, long, optional MLGruOptions) assert_own_property: interface prototype object missing non-static operation expected property "gru" missing
+FAIL MLGraphBuilder interface: operation gruCell(MLOperand, MLOperand, MLOperand, MLOperand, long, optional MLGruCellOptions) assert_own_property: interface prototype object missing non-static operation expected property "gruCell" missing
+FAIL MLGraphBuilder interface: operation hardSigmoid(MLOperand, optional MLHardSigmoidOptions) assert_own_property: interface prototype object missing non-static operation expected property "hardSigmoid" missing
+FAIL MLGraphBuilder interface: operation hardSigmoid(optional MLHardSigmoidOptions) assert_own_property: interface prototype object missing non-static operation expected property "hardSigmoid" missing
+FAIL MLGraphBuilder interface: operation hardSwish(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "hardSwish" missing
+FAIL MLGraphBuilder interface: operation hardSwish() assert_own_property: interface prototype object missing non-static operation expected property "hardSwish" missing
+FAIL MLGraphBuilder interface: operation instanceNormalization(MLOperand, optional MLInstanceNormalizationOptions) assert_own_property: interface prototype object missing non-static operation expected property "instanceNormalization" missing
+FAIL MLGraphBuilder interface: operation leakyRelu(MLOperand, optional MLLeakyReluOptions) assert_own_property: interface prototype object missing non-static operation expected property "leakyRelu" missing
+FAIL MLGraphBuilder interface: operation leakyRelu(optional MLLeakyReluOptions) assert_own_property: interface prototype object missing non-static operation expected property "leakyRelu" missing
+FAIL MLGraphBuilder interface: operation matmul(MLOperand, MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "matmul" missing
+FAIL MLGraphBuilder interface: operation linear(MLOperand, optional MLLinearOptions) assert_own_property: interface prototype object missing non-static operation expected property "linear" missing
+FAIL MLGraphBuilder interface: operation linear(optional MLLinearOptions) assert_own_property: interface prototype object missing non-static operation expected property "linear" missing
+FAIL MLGraphBuilder interface: operation pad(MLOperand, MLOperand, optional MLPadOptions) assert_own_property: interface prototype object missing non-static operation expected property "pad" missing
+FAIL MLGraphBuilder interface: operation averagePool2d(MLOperand, optional MLPool2dOptions) assert_own_property: interface prototype object missing non-static operation expected property "averagePool2d" missing
+FAIL MLGraphBuilder interface: operation l2Pool2d(MLOperand, optional MLPool2dOptions) assert_own_property: interface prototype object missing non-static operation expected property "l2Pool2d" missing
+FAIL MLGraphBuilder interface: operation maxPool2d(MLOperand, optional MLPool2dOptions) assert_own_property: interface prototype object missing non-static operation expected property "maxPool2d" missing
+FAIL MLGraphBuilder interface: operation reduceL1(MLOperand, optional MLReduceOptions) assert_own_property: interface prototype object missing non-static operation expected property "reduceL1" missing
+FAIL MLGraphBuilder interface: operation reduceL2(MLOperand, optional MLReduceOptions) assert_own_property: interface prototype object missing non-static operation expected property "reduceL2" missing
+FAIL MLGraphBuilder interface: operation reduceLogSum(MLOperand, optional MLReduceOptions) assert_own_property: interface prototype object missing non-static operation expected property "reduceLogSum" missing
+FAIL MLGraphBuilder interface: operation reduceLogSumExp(MLOperand, optional MLReduceOptions) assert_own_property: interface prototype object missing non-static operation expected property "reduceLogSumExp" missing
+FAIL MLGraphBuilder interface: operation reduceMax(MLOperand, optional MLReduceOptions) assert_own_property: interface prototype object missing non-static operation expected property "reduceMax" missing
+FAIL MLGraphBuilder interface: operation reduceMean(MLOperand, optional MLReduceOptions) assert_own_property: interface prototype object missing non-static operation expected property "reduceMean" missing
+FAIL MLGraphBuilder interface: operation reduceMin(MLOperand, optional MLReduceOptions) assert_own_property: interface prototype object missing non-static operation expected property "reduceMin" missing
+FAIL MLGraphBuilder interface: operation reduceProduct(MLOperand, optional MLReduceOptions) assert_own_property: interface prototype object missing non-static operation expected property "reduceProduct" missing
+FAIL MLGraphBuilder interface: operation reduceSum(MLOperand, optional MLReduceOptions) assert_own_property: interface prototype object missing non-static operation expected property "reduceSum" missing
+FAIL MLGraphBuilder interface: operation reduceSumSquare(MLOperand, optional MLReduceOptions) assert_own_property: interface prototype object missing non-static operation expected property "reduceSumSquare" missing
+FAIL MLGraphBuilder interface: operation relu(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "relu" missing
+FAIL MLGraphBuilder interface: operation relu() assert_own_property: interface prototype object missing non-static operation expected property "relu" missing
+FAIL MLGraphBuilder interface: operation resample2d(MLOperand, optional MLResample2dOptions) assert_own_property: interface prototype object missing non-static operation expected property "resample2d" missing
+FAIL MLGraphBuilder interface: operation reshape(MLOperand, sequence<long>) assert_own_property: interface prototype object missing non-static operation expected property "reshape" missing
+FAIL MLGraphBuilder interface: operation sigmoid(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "sigmoid" missing
+FAIL MLGraphBuilder interface: operation sigmoid() assert_own_property: interface prototype object missing non-static operation expected property "sigmoid" missing
+FAIL MLGraphBuilder interface: operation slice(MLOperand, sequence<long>, sequence<long>, optional MLSliceOptions) assert_own_property: interface prototype object missing non-static operation expected property "slice" missing
+FAIL MLGraphBuilder interface: operation softmax(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "softmax" missing
+FAIL MLGraphBuilder interface: operation softplus(MLOperand, optional MLSoftplusOptions) assert_own_property: interface prototype object missing non-static operation expected property "softplus" missing
+FAIL MLGraphBuilder interface: operation softplus(optional MLSoftplusOptions) assert_own_property: interface prototype object missing non-static operation expected property "softplus" missing
+FAIL MLGraphBuilder interface: operation softsign(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "softsign" missing
+FAIL MLGraphBuilder interface: operation softsign() assert_own_property: interface prototype object missing non-static operation expected property "softsign" missing
+FAIL MLGraphBuilder interface: operation split(MLOperand, (unsigned long or sequence<unsigned long>), optional MLSplitOptions) assert_own_property: interface prototype object missing non-static operation expected property "split" missing
+FAIL MLGraphBuilder interface: operation squeeze(MLOperand, optional MLSqueezeOptions) assert_own_property: interface prototype object missing non-static operation expected property "squeeze" missing
+FAIL MLGraphBuilder interface: operation tanh(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "tanh" missing
+FAIL MLGraphBuilder interface: operation tanh() assert_own_property: interface prototype object missing non-static operation expected property "tanh" missing
+FAIL MLGraphBuilder interface: operation transpose(MLOperand, optional MLTransposeOptions) assert_own_property: interface prototype object missing non-static operation expected property "transpose" missing
 FAIL MLGraphBuilder must be primary interface of builder assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: builder is not defined"
 FAIL Stringification of builder assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: builder is not defined"
 FAIL MLGraphBuilder interface: builder must inherit property "input(DOMString, MLOperandDescriptor)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: builder is not defined"
diff --git a/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
index 454f386..b2807042 100644
--- a/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
@@ -5062,10 +5062,19 @@
 interface MLContext
     attribute @@toStringTag
     method constructor
+interface MLGraphBuilder
+    attribute @@toStringTag
+    method add
+    method constant
+    method constructor
+    method input
 interface MLModelLoader
     attribute @@toStringTag
     method constructor
     method load
+interface MLOperand
+    attribute @@toStringTag
+    method constructor
 interface Magnetometer : Sensor
     attribute @@toStringTag
     getter x
diff --git a/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt
deleted file mode 100644
index 9f19c5d..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL CSS Selectors Invalidation: target pseudo in :has() argument assert_equals: parent should be yellowgreen on fragment click expected "rgb(154, 205, 50)" but got "rgb(0, 128, 0)"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html b/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html
index 245e0b3..bfb2b7b1e 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html
@@ -2998,6 +2998,20 @@
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGather:depth_array:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGatherCompare:array:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGatherCompare:sampled_array:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:sampled_1d:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:sampled_2d:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:sampled_3d:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:multisampled:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:depth:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:external:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:arrayed:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumLayers:sampled:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumLayers:arrayed:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumLayers:storage:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumLevels:sampled:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumLevels:depth:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumSamples:sampled:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumSamples:depth:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:stage:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:control_flow:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:sampled:*'>
diff --git a/third_party/closure_compiler/externs/file_manager_private.js b/third_party/closure_compiler/externs/file_manager_private.js
index b3d4fd1..05c4695 100644
--- a/third_party/closure_compiler/externs/file_manager_private.js
+++ b/third_party/closure_compiler/externs/file_manager_private.js
@@ -894,8 +894,9 @@
 /**
  * Unmounts a mounted resource. |volumeId| An ID of the volume.
  * @param {string} volumeId
+ * @param {function()} callback
  */
-chrome.fileManagerPrivate.removeMount = function(volumeId) {};
+chrome.fileManagerPrivate.removeMount = function(volumeId, callback) {};
 
 /**
  * Get the list of mounted volumes. |callback|
diff --git a/third_party/webgpu-cts/ts_sources.txt b/third_party/webgpu-cts/ts_sources.txt
index c1f4bde..4501201 100644
--- a/third_party/webgpu-cts/ts_sources.txt
+++ b/third_party/webgpu-cts/ts_sources.txt
@@ -340,6 +340,10 @@
 src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/textureGather.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts
diff --git a/tools/browserbench-webdriver/README.md b/tools/browserbench-webdriver/README.md
index 6313c717..962a3ace 100644
--- a/tools/browserbench-webdriver/README.md
+++ b/tools/browserbench-webdriver/README.md
@@ -3,7 +3,7 @@
 The following command line flags are supported for all benchmarks:
 
 - `-b`': The browser to run the benchmark in. The valid options currently are
-         'chrome' and 'safari'.
+         'chrome', 'safari', and 'stp'.
 
 - `-e`: Path to the executable for the driver binary.
 
@@ -27,6 +27,7 @@
 
 1. Enable the developer menu in the Advanced tab of Preferences.
 2. Enable 'Remote Automation' via the 'Developer' menu.
+3. Run `safaridriver --enable`.
 
 ## MotionMark
 
diff --git a/tools/browserbench-webdriver/browserbench.py b/tools/browserbench-webdriver/browserbench.py
index bbcefc3e..a616c6b 100644
--- a/tools/browserbench-webdriver/browserbench.py
+++ b/tools/browserbench-webdriver/browserbench.py
@@ -10,6 +10,7 @@
 import sys
 import time
 
+DEFAULT_STP_DRIVER_PATH = '/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver'
 
 class BrowserBench(object):
   def __init__(self, name, version):
@@ -34,15 +35,30 @@
     return chrome
 
   @staticmethod
+  def _CreateSafariDriver(optargs):
+    params = {}
+    if optargs.executable:
+      params['exexutable_path'] = optargs.executable
+    if optargs.browser == 'stp':
+      safari_options = webdriver.safari.options.Options()
+      safari_options.use_technology_preview = 1
+      params['desired_capabilities'] = {
+          'browserName': safari_options.capabilities['browserName']
+      }
+      # Stp requires executable_path. If the path is not supplied use the
+      # typical location.
+      if not optargs.executable:
+        params['executable_path'] = DEFAULT_STP_DRIVER_PATH
+    return webdriver.Safari(**params)
+
+  @staticmethod
   def _CreateDriver(optargs):
     if optargs.browser == 'chrome':
       return BrowserBench._CreateChromeDriver(optargs)
-    elif optargs.browser == 'safari':
+    elif optargs.browser == 'safari' or optargs.browser == 'stp':
       for i in range(0, 10):
         try:
-          return webdriver.Safari(
-              executable_path=optargs.executable
-          ) if optargs.executable else webdriver.Safari()
+          return BrowserBench._CreateSafariDriver(optargs)
         except selenium.common.exceptions.SessionNotCreatedException as e:
           print('Connecting to Safari failed, will try again ', e)
           time.sleep(5)
@@ -114,11 +130,13 @@
     parser.add_option('-b',
                       '--browser',
                       dest='browser',
-                      help='The browser to use to run MotionMark in.')
+                      help="""The browser to use. One of chrome, safari, or stp
+                              (Safari Technology Preview).""")
     parser.add_option('-e',
                       '--executable-path',
                       dest='executable',
-                      help='Path to the executable to the driver binary.')
+                      help="""Path to the executable to the driver binary. For
+                              safari this is the path to safaridriver.""")
     parser.add_option('-a',
                       '--arguments',
                       dest='arguments',
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 6086a1b..2de475c 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -55191,6 +55191,7 @@
   <int value="-1625881240" label="FullscreenExitUI:disabled"/>
   <int value="-1625777277"
       label="AutofillUpstreamEditableCardholderName:disabled"/>
+  <int value="-1625235210" label="VirtualKeyboardRoundCorners:enabled"/>
   <int value="-1624992248" label="LacrosTtsSupport:disabled"/>
   <int value="-1624854957" label="enable-es3-apis"/>
   <int value="-1624593106" label="NewTabPageBackgrounds:enabled"/>
@@ -55527,6 +55528,7 @@
   <int value="-1420949910" label="ChromeWideEchoCancellation:enabled"/>
   <int value="-1420848224" label="WebViewForceDarkModeMatchTheme:enabled"/>
   <int value="-1420542268" label="AutofillEnableAccountWalletStorage:disabled"/>
+  <int value="-1420478723" label="EapGtcWifiAuthentication:enabled"/>
   <int value="-1419788257" label="enable-experimental-hotwording"/>
   <int value="-1419449681" label="PermissionChipRequestTypeSensitive:enabled"/>
   <int value="-1418200801" label="LauncherLacrosIntegration:enabled"/>
@@ -59917,6 +59919,7 @@
   <int value="1514158607" label="NearbySharingDeviceContacts:enabled"/>
   <int value="1515196403" label="fast-user-switching"/>
   <int value="1515334367" label="ToolbarMicIphAndroid:disabled"/>
+  <int value="1515472077" label="VirtualKeyboardRoundCorners:disabled"/>
   <int value="1516258676" label="DesktopPWAsDefaultOfflinePage:disabled"/>
   <int value="1517863401" label="history-entry-requires-user-gesture"/>
   <int value="1517988103" label="FilesBannerFramework:disabled"/>
@@ -60657,6 +60660,7 @@
   <int value="2006413281"
       label="ContextualSuggestionsAlternateCardLayout:enabled"/>
   <int value="2006856618" label="BiometricTouchToFill:enabled"/>
+  <int value="2007421572" label="EapGtcWifiAuthentication:disabled"/>
   <int value="2007638959" label="CrOSLabsFloatWindow:disabled"/>
   <int value="2008599705" label="EnableFeedbackPanel:enabled"/>
   <int value="2008878342" label="TouchToFillAndroid:disabled"/>
diff --git a/tools/metrics/histograms/metadata/local/histograms.xml b/tools/metrics/histograms/metadata/local/histograms.xml
index f7bd701c..3630088 100644
--- a/tools/metrics/histograms/metadata/local/histograms.xml
+++ b/tools/metrics/histograms/metadata/local/histograms.xml
@@ -380,15 +380,6 @@
   </summary>
 </histogram>
 
-<histogram name="LocalStorageContext.OnConnectionDestroyed" enum="Boolean"
-    expires_after="M87">
-  <owner>mek@chromium.org</owner>
-  <owner>dullweber@chromium.org</owner>
-  <summary>
-    Recorded when the database connection is closed unexpectedly.
-  </summary>
-</histogram>
-
 <histogram name="LocalStorageContext.OpenError" enum="LocalStorageOpenError"
     expires_after="M87">
   <owner>mek@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/storage/histograms.xml b/tools/metrics/histograms/metadata/storage/histograms.xml
index da6147f..23c8e04 100644
--- a/tools/metrics/histograms/metadata/storage/histograms.xml
+++ b/tools/metrics/histograms/metadata/storage/histograms.xml
@@ -256,6 +256,39 @@
   </summary>
 </histogram>
 
+<histogram name="Storage.Blob.DataURLWorkerRegister" enum="Boolean"
+    expires_after="M106">
+  <owner>awillia@chromium.org</owner>
+  <owner>ayui@chromium.org</owner>
+  <summary>
+    Recorded when the PublicURLManager::Register method is called from a worker
+    execution context (excluding service worker contexts). The value indicates
+    if the worker was created from a data URL.
+  </summary>
+</histogram>
+
+<histogram name="Storage.Blob.DataURLWorkerResolveAsURLLoaderFactory"
+    enum="Boolean" expires_after="M106">
+  <owner>awillia@chromium.org</owner>
+  <owner>ayui@chromium.org</owner>
+  <summary>
+    Recorded when the PublicURLManager::Resolve URLLoaderFactory method is
+    called from a worker execution context (excluding service worker contexts).
+    The value indicates if the worker was created from a data URL.
+  </summary>
+</histogram>
+
+<histogram name="Storage.Blob.DataURLWorkerResolveForNavigation" enum="Boolean"
+    expires_after="M106">
+  <owner>awillia@chromium.org</owner>
+  <owner>ayui@chromium.org</owner>
+  <summary>
+    Recorded when the PublicURLManager::Resolve method for navigations is called
+    from a worker execution context (excluding service worker contexts). The
+    value indicates if the worker was created from a data URL.
+  </summary>
+</histogram>
+
 <histogram name="Storage.Blob.FileReaderLoader.FailureType"
     enum="FileReaderLoaderFailureType" expires_after="M95">
   <owner>mek@chromium.org</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 229142d..0fdc7d1 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -13,16 +13,16 @@
             "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "mac": {
-            "hash": "2c87c1764f0d0379d6ff599a4611d0c9f004ab26",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/078d86518ab973104a8066b22a94a4f281cb8553/trace_processor_shell"
+            "hash": "1468549bfc3e6ac56ca211d6de78a4fcf9d1227e",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/f7b904a467cb55e4ff3a2ded74354e8e8d8ffabf/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "e1ad4861384b06d911a65f035317914b8cc975c6",
             "full_remote_path": "perfetto-luci-artifacts/v25.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "b6c4202bfd5ae74bed44096185acfd4c358d58d4",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/57be0e79252a66d9723bcedccd509faaf37ee95f/trace_processor_shell"
+            "hash": "2a8de7ddf93e7ee096e6e62656e910314e743bd3",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/f7b904a467cb55e4ff3a2ded74354e8e8d8ffabf/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/chromeos/strings/network_element_localized_strings_provider.cc b/ui/chromeos/strings/network_element_localized_strings_provider.cc
index 21506be..bcdb0b0 100644
--- a/ui/chromeos/strings/network_element_localized_strings_provider.cc
+++ b/ui/chromeos/strings/network_element_localized_strings_provider.cc
@@ -453,6 +453,9 @@
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
+  html_source->AddBoolean("eapGtcWifiAuthentication",
+                          ash::features::IsEapGtcWifiAuthenticationEnabled());
+
   html_source->AddBoolean(
       "showHiddenNetworkWarning",
       base::FeatureList::IsEnabled(ash::features::kHiddenNetworkWarning));
diff --git a/ui/chromeos/styles/cros_typography.json5 b/ui/chromeos/styles/cros_typography.json5
index 4dcf0ad..4310bed 100644
--- a/ui/chromeos/styles/cros_typography.json5
+++ b/ui/chromeos/styles/cros_typography.json5
@@ -41,6 +41,12 @@
         font_weight: 500,
         line_height: 28,
       },
+      display_7: {
+        font_family: '$font_family_google_sans',
+        font_size: 18,
+        font_weight: 500,
+        line_height: 24,
+      },
       annotation_2: {
         font_family: '$font_family_google_sans',
         font_size: 11,
diff --git a/ui/file_manager/file_manager/background/js/volume_manager_impl.js b/ui/file_manager/file_manager/background/js/volume_manager_impl.js
index fdb81aa..b6ff594 100644
--- a/ui/file_manager/file_manager/background/js/volume_manager_impl.js
+++ b/ui/file_manager/file_manager/background/js/volume_manager_impl.js
@@ -160,8 +160,8 @@
 
     try {
       console.warn('Getting volumes');
-      let volumeMetadataList = await new Promise(
-          resolve => chrome.fileManagerPrivate.getVolumeMetadataList(resolve));
+      let volumeMetadataList =
+          await promisify(chrome.fileManagerPrivate.getVolumeMetadataList);
       if (!volumeMetadataList) {
         console.warn('Cannot get volumes');
         finishInitialization();
@@ -323,9 +323,8 @@
 
   /** @override */
   async mountArchive(fileUrl, password) {
-    const path = await new Promise(resolve => {
-      chrome.fileManagerPrivate.addMount(fileUrl, password, resolve);
-    });
+    const path =
+        await promisify(chrome.fileManagerPrivate.addMount, fileUrl, password);
     console.debug(`Mounting '${path}'`);
     const key = this.makeRequestKey_('mount', path);
     return this.startRequest_(key);
@@ -340,22 +339,16 @@
   /** @override */
   async unmount({volumeId}) {
     console.warn(`Unmounting '${volumeId}'`);
-    chrome.fileManagerPrivate.removeMount(volumeId);
     const key = this.makeRequestKey_('unmount', volumeId);
-    await this.startRequest_(key);
+    const request = this.startRequest_(key);
+    await promisify(chrome.fileManagerPrivate.removeMount, volumeId);
+    await request;
   }
 
   /** @override */
   configure(volumeInfo) {
-    return new Promise((fulfill, reject) => {
-      chrome.fileManagerPrivate.configureVolume(volumeInfo.volumeId, () => {
-        if (chrome.runtime.lastError) {
-          reject(chrome.runtime.lastError.message);
-        } else {
-          fulfill();
-        }
-      });
-    });
+    return promisify(
+        chrome.fileManagerPrivate.configureVolume, volumeInfo.volumeId);
   }
 
   /** @override */
diff --git a/ui/file_manager/file_manager/background/js/volume_manager_unittest.m.js b/ui/file_manager/file_manager/background/js/volume_manager_unittest.m.js
index 051c0675..2853f3f8 100644
--- a/ui/file_manager/file_manager/background/js/volume_manager_unittest.m.js
+++ b/ui/file_manager/file_manager/background/js/volume_manager_unittest.m.js
@@ -53,13 +53,14 @@
         callback(
             chrome.fileManagerPrivate.fileSystemMap_[options.volumeId].root);
       },
-      removeMount: function(volumeId) {
+      removeMount: function(volumeId, callback) {
         const event = {
           eventType: 'unmount',
           status: 'success',
           volumeMetadata: {volumeId: volumeId}
         };
         mockChrome.fileManagerPrivate.onMountCompleted.dispatchEvent(event);
+        callback();
       },
       onDriveConnectionStatusChanged: {
         addListener: function(listener) {
diff --git a/ui/webui/resources/cr_components/certificate_manager/BUILD.gn b/ui/webui/resources/cr_components/certificate_manager/BUILD.gn
index d49d464..f1995f9 100644
--- a/ui/webui/resources/cr_components/certificate_manager/BUILD.gn
+++ b/ui/webui/resources/cr_components/certificate_manager/BUILD.gn
@@ -4,7 +4,7 @@
 
 import("//crypto/features.gni")
 import("//tools/grit/preprocess_if_expr.gni")
-import("//tools/polymer/html_to_js.gni")
+import("//tools/polymer/css_to_wrapper.gni")
 import("//tools/polymer/html_to_wrapper.gni")
 import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
@@ -33,9 +33,9 @@
   ]
 }
 
-html_to_js("css_wrapper_files") {
+css_to_wrapper("css_wrapper_files") {
   visibility = [ ":preprocess_generated" ]
-  js_files = css_wrapper_files
+  in_files = css_files
 }
 
 html_to_wrapper("html_wrapper_files") {
diff --git a/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.ts b/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.ts
index 8180f7a..ace147ed 100644
--- a/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.ts
@@ -12,7 +12,7 @@
 import '../../cr_elements/cr_checkbox/cr_checkbox.m.js';
 import '../../cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
-import './certificate_shared_css.js';
+import './certificate_shared.css.js';
 
 import {PaperSpinnerLiteElement} from 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_delete_confirmation_dialog.ts b/ui/webui/resources/cr_components/certificate_manager/certificate_delete_confirmation_dialog.ts
index a187d4d8..0c1868b 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_delete_confirmation_dialog.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_delete_confirmation_dialog.ts
@@ -8,7 +8,7 @@
  */
 import '../../cr_elements/cr_button/cr_button.m.js';
 import '../../cr_elements/cr_dialog/cr_dialog.m.js';
-import './certificate_shared_css.js';
+import './certificate_shared.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_entry.ts b/ui/webui/resources/cr_components/certificate_manager/certificate_entry.ts
index 75c4088e..ac619cc8 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_entry.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_entry.ts
@@ -8,7 +8,7 @@
 import '../../cr_elements/cr_expand_button/cr_expand_button.m.js';
 import '../../cr_elements/policy/cr_policy_indicator.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
-import './certificate_shared_css.js';
+import './certificate_shared.css.js';
 import './certificate_subentry.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_list.ts b/ui/webui/resources/cr_components/certificate_manager/certificate_list.ts
index f78935e..5ce5118 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_list.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_list.ts
@@ -9,7 +9,7 @@
 import '../../cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import './certificate_entry.js';
-import './certificate_shared_css.js';
+import './certificate_shared.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_manager.gni b/ui/webui/resources/cr_components/certificate_manager/certificate_manager.gni
index 93aecc8..3da67376 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_manager.gni
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_manager.gni
@@ -48,4 +48,11 @@
 
 ts_files = _web_component_files + _non_web_component_files
 
-css_wrapper_files = [ "certificate_shared_css.ts" ]
+# Files that are passed as input to css_to_wrapper().
+css_files = [ "certificate_shared.css" ]
+
+# Files that are generated by css_to_wrapper().
+css_wrapper_files = []
+foreach(f, css_files) {
+  css_wrapper_files += [ f + ".ts" ]
+}
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_password_decryption_dialog.ts b/ui/webui/resources/cr_components/certificate_manager/certificate_password_decryption_dialog.ts
index 4549a1b..0ed9234 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_password_decryption_dialog.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_password_decryption_dialog.ts
@@ -9,7 +9,7 @@
 import '../../cr_elements/cr_button/cr_button.m.js';
 import '../../cr_elements/cr_dialog/cr_dialog.m.js';
 import '../../cr_elements/cr_input/cr_input.m.js';
-import './certificate_shared_css.js';
+import './certificate_shared.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.ts b/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.ts
index 38d6bd9..b1d44b3a 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.ts
@@ -10,7 +10,7 @@
 import '../../cr_elements/cr_dialog/cr_dialog.m.js';
 import '../../cr_elements/cr_input/cr_input.m.js';
 import '../../cr_elements/shared_vars_css.m.js';
-import './certificate_shared_css.js';
+import './certificate_shared.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_provisioning_entry.ts b/ui/webui/resources/cr_components/certificate_manager/certificate_provisioning_entry.ts
index 91a3e279..6319f4d 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_provisioning_entry.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_provisioning_entry.ts
@@ -10,7 +10,7 @@
 import '../../cr_elements/cr_icon_button/cr_icon_button.m.js';
 import '../../cr_elements/cr_lazy_render/cr_lazy_render.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
-import './certificate_shared_css.js';
+import './certificate_shared.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_shared.css b/ui/webui/resources/cr_components/certificate_manager/certificate_shared.css
new file mode 100644
index 0000000..ac8c65d
--- /dev/null
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_shared.css
@@ -0,0 +1,35 @@
+/* Copyright 2022 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * #import=../../cr_elements/shared_style_css.m.js
+ * #include=cr-shared-style
+ * #css_wrapper_metadata_end */
+
+/* .list-frame and .list-item match the styling in settings_shared_css. */
+.list-frame {
+  align-items: center;
+  display: block;
+  padding-inline-end: 20px;
+  padding-inline-start: 60px;
+}
+
+.list-item {
+  align-items: center;
+  display: flex;
+  min-height: 48px;
+}
+
+.list-item.underbar {
+  border-bottom: var(--cr-separator-line);
+}
+
+.list-item.selected {
+  font-weight: 500;
+}
+
+.list-item > .start {
+  flex: 1;
+}
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_shared_css.html b/ui/webui/resources/cr_components/certificate_manager/certificate_shared_css.html
deleted file mode 100644
index 015350f..0000000
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_shared_css.html
+++ /dev/null
@@ -1,30 +0,0 @@
-
-  <template>
-    <style include="cr-shared-style">
-      /* .list-frame and .list-item match the styling in settings_shared_css. */
-      .list-frame {
-        align-items: center;
-        display: block;
-        padding-inline-end: 20px;
-        padding-inline-start: 60px;
-      }
-
-      .list-item {
-        align-items: center;
-        display: flex;
-        min-height: 48px;
-      }
-
-      .list-item.underbar {
-        border-bottom: var(--cr-separator-line);
-      }
-
-      .list-item.selected {
-        font-weight: 500;
-      }
-
-      .list-item > .start {
-        flex: 1;
-      }
-    </style>
-  </template>
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_shared_css.ts b/ui/webui/resources/cr_components/certificate_manager/certificate_shared_css.ts
deleted file mode 100644
index 277b0dc..0000000
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_shared_css.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import '../../cr_elements/shared_style_css.m.js';
-
-const styleMod = document.createElement('dom-module');
-styleMod.innerHTML = `{__html_template__}`;
-styleMod.register('certificate-shared');
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.ts b/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.ts
index 0af6f210..95a7ed07 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.ts
@@ -11,7 +11,7 @@
 import '../../cr_elements/cr_lazy_render/cr_lazy_render.m.js';
 import '../../cr_elements/policy/cr_policy_indicator.m.js';
 import '../../cr_elements/icons.m.js';
-import './certificate_shared_css.js';
+import './certificate_shared.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificates_error_dialog.ts b/ui/webui/resources/cr_components/certificate_manager/certificates_error_dialog.ts
index fbbe882f..ae6d7d5 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificates_error_dialog.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/certificates_error_dialog.ts
@@ -8,7 +8,7 @@
  */
 import '../../cr_elements/cr_button/cr_button.m.js';
 import '../../cr_elements/cr_dialog/cr_dialog.m.js';
-import './certificate_shared_css.js';
+import './certificate_shared.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_config.js b/ui/webui/resources/cr_components/chromeos/network/network_config.js
index 35afad4..0579b27 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_config.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_config.js
@@ -329,7 +329,13 @@
     eapInnerItemsPeap_: {
       type: Array,
       readOnly: true,
-      value: ['Automatic', 'MD5', 'MSCHAPv2'],
+      value: () => {
+        const values = ['Automatic', 'MD5', 'MSCHAPv2'];
+        if (loadTimeData.getBoolean('eapGtcWifiAuthentication')) {
+          values.push('GTC');
+        }
+        return values;
+      },
     },
 
     /**
diff --git a/ui/webui/resources/js/cr/ui/BUILD.gn b/ui/webui/resources/js/cr/ui/BUILD.gn
index 10a7fae..f3245894 100644
--- a/ui/webui/resources/js/cr/ui/BUILD.gn
+++ b/ui/webui/resources/js/cr/ui/BUILD.gn
@@ -172,7 +172,10 @@
 }
 
 js_library("dialogs") {
-  deps = [ "../..:cr" ]
+  deps = [
+    "../..:cr",
+    "../..:util",
+  ]
 }
 
 js_library("focus_manager") {
diff --git a/ui/webui/resources/js/cr/ui/dialogs.js b/ui/webui/resources/js/cr/ui/dialogs.js
index e1c76cb..4ae3b36 100644
--- a/ui/webui/resources/js/cr/ui/dialogs.js
+++ b/ui/webui/resources/js/cr/ui/dialogs.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {isRTL} from 'chrome://resources/js/util.m.js'
+
 cr.define('cr.ui.dialogs', function() {
   /**
    * @constructor
@@ -135,14 +137,21 @@
 
   /** @protected */
   BaseDialog.prototype.onContainerKeyDown = function(event) {
-    // Handle Escape.
-    if (event.keyCode === 27 && !this.cancelButton.disabled) {
+    // 0=cancel, 1=ok.
+    const focus = i => [this.cancelButton, this.okButton][i].focus();
+
+    if (event.key === 'Escape' && !this.cancelButton.disabled) {
       this.onCancelClick_(event);
-      event.stopPropagation();
-      // Prevent the event from being handled by the container of the dialog.
-      // e.g. Prevent the parent container from closing at the same time.
-      event.preventDefault();
+    } else if (event.key === 'ArrowLeft') {
+      focus(isRTL() ? 1 : 0);
+    } else if (event.key === 'ArrowRight') {
+      focus(isRTL() ? 0 : 1);
+    } else {
+      // Not handled, so return and allow event to propagate.
+      return;
     }
+    event.stopPropagation();
+    event.preventDefault();
   };
 
   /** @private */