diff --git a/DEPS b/DEPS
index 41d7690..58831d9 100644
--- a/DEPS
+++ b/DEPS
@@ -253,7 +253,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': 'ff138c94d60087da0527d86f02565315d86c22ed',
+  'skia_revision': 'e1880aed8f81fd9f7cdea2439e7d6d724738647c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -320,7 +320,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': 'd55bdb952f5c4c0a0abe36df3af65071a3801e0c',
+  'catapult_revision': '63a615bbdb57ad0a2e794ed800b826dddcef2713',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -368,7 +368,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': '3b6b4a1051403863b4940d8801bb43a9d84c9070',
+  'dawn_revision': '5f881cbca0e92057ad86dc2a93e8b48fafeda17a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -412,7 +412,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.
-  'libcxxabi_revision':    '289d52ce75d6091a79e90ae6aead38c5c701d786',
+  'libcxxabi_revision':    'a897d0f3f8e8c28ac2abf848f3b695b724409298',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -435,7 +435,7 @@
   'libcxx_revision':       '79a2e924d96e2fc1e4b937c42efd08898fa472d7',
 
   # GN CIPD package version.
-  'gn_version': 'git_revision:f27bae882b2178ccc3c24f314c88db9a34118992',
+  'gn_version': 'git_revision:bd99dbf98cbdefe18a4128189665c5761263bcfb',
 }
 
 # Only these hosts are allowed for dependencies in this DEPS file.
@@ -708,7 +708,7 @@
   },
 
   'src/ios/third_party/earl_grey2/src': {
-      'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + '9753a68d7453aaea0c11320827b21a3bd05512fa',
+      'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + 'd772a0e8eb889fb184e3bd74a1d0cd868d94605c',
       'condition': 'checkout_ios',
   },
 
@@ -991,7 +991,7 @@
     Var('chromium_git') + '/angle/angle.git' + '@' +  Var('angle_revision'),
 
   'src/third_party/dav1d/libdav1d':
-    Var('chromium_git') + '/external/github.com/videolan/dav1d.git' + '@' + '493ffb12f77df791f7dcde991b92d64bf873fefd',
+    Var('chromium_git') + '/external/github.com/videolan/dav1d.git' + '@' + 'b1a5189c9d37c837099ce50852b6ce9597b89b0c',
 
   'src/third_party/dawn':
     Var('dawn_git') + '/dawn.git' + '@' +  Var('dawn_revision'),
@@ -1120,7 +1120,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'c848a4ed332e79a654cb4ee7ef29acd796f7147d',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '34816458276bf9a528f306f0bc26f33dfdb428c8',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1347,7 +1347,7 @@
 
   # Userspace interface to kernel DRM services.
   'src/third_party/libdrm/src': {
-      'url': Var('chromium_git') + '/chromiumos/third_party/libdrm.git' + '@' + '0190f49a139e7069d7cad6a6890832831da1aa8b',
+      'url': Var('chromium_git') + '/chromiumos/third_party/libdrm.git' + '@' + '56f81e6776c1c100c3f627b2c1feb9dcae2aad3c',
       'condition': 'checkout_linux',
   },
 
@@ -1581,7 +1581,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/android/aemu/release/linux-amd64',
-              'version': 'culX6ICjvTS7jMkEu0225UNiGeKAEjTAx20d6zfz58IC'
+              'version': 'p_ehnYla4xhPaxDnGkBMOVRzCWuby9Pbm7OknbRYhrwC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1724,7 +1724,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'd4ab434e91c1a578f6a43c7418eae2b92856ee05',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'fa2c945f73cc68a2a36c31475b2b89421f0aa933',
+    Var('webrtc_git') + '/src.git' + '@' + '041c08377a0dd4afbd928ef40e3789b08118313c',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1794,7 +1794,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@646427a38792ed15b897a714afd4585659eaee86',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@276ba242765462c7ee95b522886e7ac305caee04',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index 08a9a79b..b2b5c0e 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2712,7 +2712,6 @@
                          'vasilii+watchlistpasswordmanager@chromium.org'],
     'payments': ['rouslan+payments@chromium.org',
                  'gogerald+paymentswatch@chromium.org',
-                 'maxlg+paymentswatch@chromium.org',
                  'nburris+paymentswatch@chromium.org'],
     'pdf': ['pdf-reviews@chromium.org'],
     'pepper_api': ['binji+watch@chromium.org',
diff --git a/android_webview/java/src/org/chromium/android_webview/js/browser/AwJsContext.java b/android_webview/java/src/org/chromium/android_webview/js/browser/AwJsContext.java
index 0bb958e..07b9664 100644
--- a/android_webview/java/src/org/chromium/android_webview/js/browser/AwJsContext.java
+++ b/android_webview/java/src/org/chromium/android_webview/js/browser/AwJsContext.java
@@ -20,6 +20,8 @@
     /** Used to report the results of the JS evaluation. */
     public interface ExecutionCallback {
         void reportResult(String result);
+
+        void reportError(String error);
     }
 
     AwJsContext(IJsSandboxContext jsContextStub) {
@@ -42,6 +44,11 @@
             public void reportResult(String result) {
                 callback.reportResult(result);
             }
+
+            @Override
+            public void reportError(String error) {
+                callback.reportError(error);
+            }
         };
         try {
             mJsContextStub.evaluateJavascript(code, callbackStub);
diff --git a/android_webview/java/src/org/chromium/android_webview/js/common/IJsSandboxContextCallback.aidl b/android_webview/java/src/org/chromium/android_webview/js/common/IJsSandboxContextCallback.aidl
index 56a4a11..bb3613d 100644
--- a/android_webview/java/src/org/chromium/android_webview/js/common/IJsSandboxContextCallback.aidl
+++ b/android_webview/java/src/org/chromium/android_webview/js/common/IJsSandboxContextCallback.aidl
@@ -10,4 +10,5 @@
  */
 oneway interface IJsSandboxContextCallback {
     void reportResult(in String result);
+    void reportError(in String error);
 }
diff --git a/android_webview/java/src/org/chromium/android_webview/js/renderer/JsSandboxContext.java b/android_webview/java/src/org/chromium/android_webview/js/renderer/JsSandboxContext.java
index c14126e..2005aa59 100644
--- a/android_webview/java/src/org/chromium/android_webview/js/renderer/JsSandboxContext.java
+++ b/android_webview/java/src/org/chromium/android_webview/js/renderer/JsSandboxContext.java
@@ -21,18 +21,27 @@
     private boolean mIsClosed = false;
 
     @Override
-    public void evaluateJavascript(String serialisedJS, IJsSandboxContextCallback callback) {
+    public void evaluateJavascript(String code, IJsSandboxContextCallback callback) {
         if (mIsClosed) {
             throw new IllegalStateException("evaluateJavascript() called after close()");
         }
         // Just posting to the mainLooper for now.
         Handler handler = new Handler(Looper.getMainLooper());
         handler.postDelayed(() -> {
-            String result = serialisedJS.toUpperCase();
-            try {
-                callback.reportResult(result);
-            } catch (RemoteException e) {
-                Log.e(TAG, "reporting result failed", e);
+            // Currently just hardcoded to return error string when it encounters "ERROR" as input.
+            if (code.equals("ERROR")) {
+                try {
+                    callback.reportError("There has been an error.");
+                } catch (RemoteException e) {
+                    Log.e(TAG, "reporting result failed", e);
+                }
+            } else {
+                String result = code.toUpperCase();
+                try {
+                    callback.reportResult(result);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "reporting result failed", e);
+                }
             }
         }, 100);
     }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
index 24dca42..9478ecc 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
@@ -21,12 +21,19 @@
     private class TestExecutionCallback implements AwJsContext.ExecutionCallback {
         public CallbackHelper helper = new CallbackHelper();
         public String result;
+        public String error;
 
         @Override
         public void reportResult(String result) {
             this.result = result;
             helper.notifyCalled();
         }
+
+        @Override
+        public void reportError(String error) {
+            this.error = error;
+            helper.notifyCalled();
+        }
     }
 
     // Test sending data to the service and retrieving it back.
@@ -67,4 +74,20 @@
         callback.helper.waitForCallback("Timed out waiting for reportResult() to be called", 0);
         Assert.assertEquals(expected, callback.result);
     }
+
+    @Test
+    @MediumTest
+    public void testJsEvaluationError() throws Throwable {
+        final String smallCase = "ERROR";
+        final String expected = "There has been an error.";
+        TestExecutionCallback callback = new TestExecutionCallback();
+
+        AwJsSandbox.newConnectedInstance(jsSandbox -> {
+            AwJsContext jsContext = jsSandbox.createContext();
+            jsContext.evaluateJavascript(smallCase, callback);
+        });
+
+        callback.helper.waitForCallback("Timed out waiting for reportResult() to be called", 0);
+        Assert.assertEquals(expected, callback.error);
+    }
 }
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 11f5c53..0cbf25f4 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -170,6 +170,8 @@
     "ambient/model/ambient_slideshow_photo_config.h",
     "ambient/resources/ambient_animation_resource_constants.h",
     "ambient/resources/ambient_animation_static_resources.h",
+    "ambient/ui/ambient_animation_player.cc",
+    "ambient/ui/ambient_animation_player.h",
     "ambient/ui/ambient_animation_resizer.cc",
     "ambient/ui/ambient_animation_resizer.h",
     "ambient/ui/ambient_animation_shield_controller.cc",
diff --git a/ash/ambient/ui/ambient_animation_player.cc b/ash/ambient/ui/ambient_animation_player.cc
new file mode 100644
index 0000000..ce0affa8
--- /dev/null
+++ b/ash/ambient/ui/ambient_animation_player.cc
@@ -0,0 +1,100 @@
+// 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 "ash/ambient/ui/ambient_animation_player.h"
+
+#include <string>
+
+#include "ash/utility/lottie_util.h"
+#include "base/check.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+#include "cc/paint/skottie_marker.h"
+#include "cc/paint/skottie_wrapper.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/views/controls/animated_image_view.h"
+
+namespace ash {
+
+namespace {
+
+absl::optional<base::TimeDelta> FindCycleRestartTimestamp(
+    const cc::SkottieWrapper& skottie) {
+  static const base::NoDestructor<std::string> kRestartMarkerName(
+      base::StrCat({kLottieCustomizableIdPrefix, "_Marker_CycleRestart"}));
+  DCHECK(skottie.is_valid());
+  absl::optional<base::TimeDelta> restart_timestamp;
+  for (const cc::SkottieMarker& marker : skottie.GetAllMarkers()) {
+    if (marker.name != *kRestartMarkerName) {
+      continue;
+    } else if (restart_timestamp.has_value()) {
+      LOG(DFATAL) << "Multiple markers with name " << *kRestartMarkerName
+                  << " found in animation file. Defaulting to first one.";
+    } else {
+      // |marker.begin_time| is a normalized timestamp in range [0, 1), where 1
+      // is the animation's cycle duration.
+      restart_timestamp.emplace(base::Seconds(skottie.duration()) *
+                                marker.begin_time);
+      DVLOG(1) << "Found restart marker at timestamp " << *restart_timestamp;
+    }
+  }
+  return restart_timestamp;
+}
+
+}  // namespace
+
+AmbientAnimationPlayer::AmbientAnimationPlayer(
+    views::AnimatedImageView* animated_image_view)
+    : animated_image_view_(animated_image_view) {
+  DCHECK(animated_image_view_);
+  lottie::Animation* animation = animated_image_view_->animated_image();
+  DCHECK(animation);
+  absl::optional<base::TimeDelta> cycle_restart_timestamp_found =
+      FindCycleRestartTimestamp(*animation->skottie());
+  if (cycle_restart_timestamp_found.has_value()) {
+    cycle_restart_timestamp_ = *cycle_restart_timestamp_found;
+    if (cycle_restart_timestamp_ >= animation->GetAnimationDuration()) {
+      LOG(DFATAL) << "Animation has invalid cycle restart timestamp "
+                  << cycle_restart_timestamp_ << ". Total cycle duration "
+                  << animation->GetAnimationDuration();
+    }
+  } else {
+    DVLOG(1) << "Restart marker not found in animation. Defaulting to cycle "
+                "restart at timestamp 0";
+    DCHECK(cycle_restart_timestamp_.is_zero());
+  }
+  animation_observation_.Observe(animation);
+  animated_image_view_->Play(lottie::Animation::Style::kLinear);
+}
+
+AmbientAnimationPlayer::~AmbientAnimationPlayer() {
+  animated_image_view_->Stop();
+}
+
+void AmbientAnimationPlayer::AnimationCycleEnded(
+    const lottie::Animation* animation) {
+  DVLOG(1) << "First animation cycle ended. Restarting at "
+           << cycle_restart_timestamp_;
+  // No need to keep observing after the first animation cycle ends because all
+  // future animation cycles will automatically loop below.
+  animation_observation_.Reset();
+  // Stop()/Play() are actually very light-weight operations. They do not cause
+  // the animation to be re-loaded and only modify internal book-keeping state.
+  // The latency between the last frame of the first animation cycle and the
+  // first frame of the second cycle was compared against the same
+  // frame-to-frame latency at the end of other animation cycles, and there was
+  // no observable difference.
+  animated_image_view_->Stop();
+  animated_image_view_->Play(
+      /*start_offset=*/cycle_restart_timestamp_,
+      /*duration=*/
+      animated_image_view_->animated_image()->GetAnimationDuration() -
+          cycle_restart_timestamp_,
+      lottie::Animation::Style::kLoop);
+}
+
+}  // namespace ash
diff --git a/ash/ambient/ui/ambient_animation_player.h b/ash/ambient/ui/ambient_animation_player.h
new file mode 100644
index 0000000..8f5f29d
--- /dev/null
+++ b/ash/ambient/ui/ambient_animation_player.h
@@ -0,0 +1,64 @@
+// 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 ASH_AMBIENT_UI_AMBIENT_ANIMATION_PLAYER_H_
+#define ASH_AMBIENT_UI_AMBIENT_ANIMATION_PLAYER_H_
+
+#include "ash/ash_export.h"
+#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
+#include "base/time/time.h"
+#include "ui/lottie/animation.h"
+#include "ui/lottie/animation_observer.h"
+
+namespace views {
+class AnimatedImageView;
+}  // namespace views
+
+namespace ash {
+
+// Plays an AnimatedImageView in a loop until destruction. The "looping" logic
+// meets ambient mode's custom requirements: The lottie animation may optionally
+// have a "marker" embedded in it. The "marker" is a timestamp set by the motion
+// designer, which in this case, indicates where the animation should restart
+// after each cycle ends. If the marker timestamp is M and the total animation
+// cycle duration is D (where 0 < M < D), then the animation cycles look
+// like this:
+// [0, D]
+// [M, D]
+// [M, D]
+// ...
+//
+// Note the very first animation cycle is always played starting at time 0 (the
+// very first frame in the animation).
+//
+// If the animation does not have a marker embedded in it, the default behavior
+// is to restart at the beginning of the animation each time (M is effectively
+// 0):
+// [0, D]
+// [0, D]
+// [0, D]
+// ...
+class ASH_EXPORT AmbientAnimationPlayer : public lottie::AnimationObserver {
+ public:
+  // Starts playing the |animated_image_view| immediately upon construction.
+  explicit AmbientAnimationPlayer(
+      views::AnimatedImageView* animated_image_view);
+  AmbientAnimationPlayer(const AmbientAnimationPlayer&) = delete;
+  AmbientAnimationPlayer& operator=(const AmbientAnimationPlayer&) = delete;
+  ~AmbientAnimationPlayer() override;
+
+  // lottie::AnimationObserver implementation:
+  void AnimationCycleEnded(const lottie::Animation* animation) override;
+
+ private:
+  const base::raw_ptr<views::AnimatedImageView> animated_image_view_;
+  base::ScopedObservation<lottie::Animation, lottie::AnimationObserver>
+      animation_observation_{this};
+  base::TimeDelta cycle_restart_timestamp_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_AMBIENT_UI_AMBIENT_ANIMATION_PLAYER_H_
diff --git a/ash/ambient/ui/ambient_animation_view.cc b/ash/ambient/ui/ambient_animation_view.cc
index a2405a8..0a70a34 100644
--- a/ash/ambient/ui/ambient_animation_view.cc
+++ b/ash/ambient/ui/ambient_animation_view.cc
@@ -14,6 +14,7 @@
 #include "ash/ambient/model/ambient_backend_model.h"
 #include "ash/ambient/model/ambient_photo_config.h"
 #include "ash/ambient/resources/ambient_animation_static_resources.h"
+#include "ash/ambient/ui/ambient_animation_player.h"
 #include "ash/ambient/ui/ambient_animation_resizer.h"
 #include "ash/ambient/ui/ambient_animation_shield_controller.h"
 #include "ash/ambient/ui/ambient_view_delegate.h"
@@ -260,12 +261,6 @@
   media_string_view->SetVisible(false);
 }
 
-void AmbientAnimationView::AnimationWillStartPlaying(
-    const lottie::Animation* animation) {
-  event_handler_->OnMarkerHit(AmbientPhotoConfig::Marker::kUiStartRendering);
-  last_jitter_timestamp_ = base::TimeTicks::Now();
-}
-
 void AmbientAnimationView::AnimationCycleEnded(
     const lottie::Animation* animation) {
   event_handler_->OnMarkerHit(AmbientPhotoConfig::Marker::kUiCycleEnded);
@@ -302,7 +297,7 @@
       << animated_image_view_->animated_image()->GetOriginalSize().ToString()
       << " from " << previous_animation_bounds.ToString() << " to "
       << animated_image_view_->GetImageBounds().ToString();
-  animated_image_view_->Play();
+  StartPlayingAnimation();
   if (!throughput_tracker_restart_timer_.IsRunning()) {
     RestartThroughputTracking();
     throughput_tracker_restart_timer_.Start(
@@ -311,6 +306,19 @@
   }
 }
 
+void AmbientAnimationView::StartPlayingAnimation() {
+  // There should only be one active AmbientAnimationPlayer at any given time,
+  // otherwise multiple active players can lead to confusing simultaneous state
+  // changes. So destroy the existing player first before creating a new one.
+  animation_player_.reset();
+  // |animated_image_view_| is owned by the base |View| class and outlives the
+  // |animation_player_|, so it's safe to pass a raw ptr here.
+  animation_player_ =
+      std::make_unique<AmbientAnimationPlayer>(animated_image_view_);
+  event_handler_->OnMarkerHit(AmbientPhotoConfig::Marker::kUiStartRendering);
+  last_jitter_timestamp_ = base::TimeTicks::Now();
+}
+
 void AmbientAnimationView::RestartThroughputTracking() {
   // Stop() must be called to trigger throughput reporting.
   if (throughput_tracker_ && !throughput_tracker_->Stop()) {
diff --git a/ash/ambient/ui/ambient_animation_view.h b/ash/ambient/ui/ambient_animation_view.h
index e1c0d67..873ef26 100644
--- a/ash/ambient/ui/ambient_animation_view.h
+++ b/ash/ambient/ui/ambient_animation_view.h
@@ -29,6 +29,7 @@
 namespace ash {
 
 class AmbientAnimationAttributionProvider;
+class AmbientAnimationPlayer;
 class AmbientAnimationStaticResources;
 class AmbientAnimationShieldController;
 class AmbientViewDelegate;
@@ -50,11 +51,11 @@
  private:
   void Init(AmbientViewDelegate* view_delegate);
 
-  void AnimationWillStartPlaying(const lottie::Animation* animation) override;
   void AnimationCycleEnded(const lottie::Animation* animation) override;
 
   void OnViewBoundsChanged(View* observed_view) override;
 
+  void StartPlayingAnimation();
   void StartThroughputTracking();
   void RestartThroughputTracking();
   void ApplyJitter();
@@ -71,6 +72,7 @@
   views::BoxLayoutView* glanceable_info_container_ = nullptr;
   views::BoxLayoutView* media_string_container_ = nullptr;
   std::unique_ptr<AmbientAnimationShieldController> shield_view_controller_;
+  std::unique_ptr<AmbientAnimationPlayer> animation_player_;
   base::ScopedObservation<View, ViewObserver> animated_image_view_observer_{
       this};
   base::ScopedObservation<lottie::Animation, lottie::AnimationObserver>
diff --git a/ash/app_list/app_list_presenter_unittest.cc b/ash/app_list/app_list_presenter_unittest.cc
index ece320a7c..d72f9a0 100644
--- a/ash/app_list/app_list_presenter_unittest.cc
+++ b/ash/app_list/app_list_presenter_unittest.cc
@@ -4,6 +4,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <string>
 
 #include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/app_list/app_list_bubble_presenter.h"
@@ -354,15 +355,16 @@
 INSTANTIATE_TEST_SUITE_P(All, AppListPresenterNonBubbleTest, testing::Bool());
 
 // Tests all tablet/clamshell classic/bubble launcher combinations.
-class AppListBubbleAndTabletTest
-    : public AshTestBase,
-      public testing::WithParamInterface<std::tuple<bool, bool>> {
+class AppListBubbleAndTabletTestBase : public AshTestBase {
  public:
-  AppListBubbleAndTabletTest() = default;
-  AppListBubbleAndTabletTest(const AppListBubbleAndTabletTest&) = delete;
-  AppListBubbleAndTabletTest& operator=(const AppListBubbleAndTabletTest&) =
+  AppListBubbleAndTabletTestBase(bool productivity_launcher, bool tablet_mode)
+      : productivity_launcher_(productivity_launcher),
+        tablet_mode_(tablet_mode) {}
+  AppListBubbleAndTabletTestBase(const AppListBubbleAndTabletTestBase&) =
       delete;
-  ~AppListBubbleAndTabletTest() override = default;
+  AppListBubbleAndTabletTestBase& operator=(
+      const AppListBubbleAndTabletTestBase&) = delete;
+  ~AppListBubbleAndTabletTestBase() override = default;
 
   // testing::Test:
   void SetUp() override {
@@ -412,17 +414,17 @@
               /*animate=*/true, /*update_position_closure=*/base::DoNothing());
 
     base::RunLoop run_loop;
-    GetAppsGridView()->AddReorderCallbackForTest(
-        base::BindRepeating(&AppListBubbleAndTabletTest::OnReorderAnimationDone,
-                            base::Unretained(this), run_loop.QuitClosure()));
+    GetAppsGridView()->AddReorderCallbackForTest(base::BindRepeating(
+        &AppListBubbleAndTabletTestBase::OnReorderAnimationDone,
+        base::Unretained(this), run_loop.QuitClosure()));
     run_loop.Run();
   }
 
   // Whether we should use the ProductivityLauncher flag.
-  bool productivity_launcher_param() { return std::get<0>(GetParam()); }
+  bool productivity_launcher_param() { return productivity_launcher_; }
 
   // Whether we should run the test in tablet mode.
-  bool tablet_mode_param() { return std::get<1>(GetParam()); }
+  bool tablet_mode_param() { return tablet_mode_; }
 
   // Bubble launcher is visible in clamshell mode with kProductivityLauncher
   // enabled.
@@ -532,6 +534,21 @@
                      ->GetFullscreenLauncherAppsSeparatorView();
   }
 
+  AppListFolderView* GetFolderView() {
+    return should_show_bubble_launcher()
+               ? GetAppListTestHelper()->GetBubbleFolderView()
+               : GetAppListTestHelper()->GetFullscreenFolderView();
+  }
+
+  void DeleteFolderItemChildren(AppListFolderItem* item) {
+    std::vector<std::string> items_to_delete;
+    for (size_t i = 0; i < item->ChildItemCount(); ++i) {
+      items_to_delete.push_back(item->GetChildItemAt(i)->id());
+    }
+    for (auto& item_to_delete : items_to_delete)
+      app_list_test_model_->DeleteItem(item_to_delete);
+  }
+
   void LongPressAt(const gfx::Point& point) {
     ui::TouchEvent long_press(ui::ET_GESTURE_LONG_PRESS, point,
                               base::TimeTicks::Now(),
@@ -578,6 +595,9 @@
   }
 
  protected:
+  const bool productivity_launcher_;
+  const bool tablet_mode_;
+
   std::unique_ptr<test::AppsGridViewTestApi> grid_test_api_;
   base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<test::AppListTestModel> app_list_test_model_;
@@ -585,6 +605,45 @@
   AppsGridView* apps_grid_view_ = nullptr;
 };
 
+// Parameterized by productivity launcher flag, and tablet mode.
+class AppListBubbleAndTabletTest
+    : public AppListBubbleAndTabletTestBase,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+  AppListBubbleAndTabletTest()
+      : AppListBubbleAndTabletTestBase(
+            /*productivity_launcher=*/std::get<0>(GetParam()),
+            /*tablet_mode=*/std::get<1>(GetParam())) {}
+  AppListBubbleAndTabletTest(const AppListBubbleAndTabletTest&) = delete;
+  AppListBubbleAndTabletTest& operator=(const AppListBubbleAndTabletTest&) =
+      delete;
+  ~AppListBubbleAndTabletTest() override = default;
+};
+
+// Instantiate the values in the parameterized tests. First boolean is used to
+// determine whether to use the kProductivityLauncher feature flag. The second
+// boolean is to determine whether to run the test in tablet mode.
+INSTANTIATE_TEST_SUITE_P(All,
+                         AppListBubbleAndTabletTest,
+                         testing::Combine(testing::Bool(), testing::Bool()));
+
+// Parameterized by tablet mode.
+class ProductivityLauncherTest : public AppListBubbleAndTabletTestBase,
+                                 public testing::WithParamInterface<bool> {
+ public:
+  ProductivityLauncherTest()
+      : AppListBubbleAndTabletTestBase(
+            /*productivity_launcher=*/true,
+            /*tablet_mode=*/GetParam()) {}
+  ProductivityLauncherTest(const ProductivityLauncherTest&) = delete;
+  ProductivityLauncherTest& operator=(const ProductivityLauncherTest&) = delete;
+  ~ProductivityLauncherTest() override = default;
+};
+
+// Instantiate the values in the parameterized tests. The boolean
+// determines whether to run the test in tablet mode.
+INSTANTIATE_TEST_SUITE_P(TabletMode, ProductivityLauncherTest, testing::Bool());
+
 // Used to test app_list behavior with a populated apps_grid.
 class PopulatedAppListTestBase : public AshTestBase {
  public:
@@ -727,18 +786,8 @@
   }
 };
 
-// Instantiate the values in the parameterized tests. First boolean is used to
-// determine whether to use the kProductivityLauncher feature flag. The second
-// boolean is to determine whether to run the test in tablet mode.
-INSTANTIATE_TEST_SUITE_P(All,
-                         AppListBubbleAndTabletTest,
-                         testing::Combine(testing::Bool(), testing::Bool()));
-
-// Verify that open folders are closed after sorting apps grid. Only run for
-// Productivity launcher.
-TEST_P(AppListBubbleAndTabletTest, SortingClosesOpenFolderView) {
-  if (!productivity_launcher_param())
-    return;
+// Verify that open folders are closed after sorting apps grid.
+TEST_P(ProductivityLauncherTest, SortingClosesOpenFolderView) {
   ui::ScopedAnimationDurationScaleMode scope_duration(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
 
@@ -757,6 +806,536 @@
   EXPECT_FALSE(AppListIsInFolderView());
 }
 
+// Tests that folder view bounds do not change if an item gets added to app list
+// model while the folder view is visible (even if it changes the folder item
+// view position in the root apps grid).
+TEST_P(ProductivityLauncherTest,
+       FolderViewRemainsInPlaceWhenAddingItemToModel) {
+  app_list_test_model_->PopulateApps(2);
+  AppListFolderItem* const folder_item =
+      app_list_test_model_->CreateAndPopulateFolderWithApps(3);
+  const std::string folder_id = folder_item->id();
+  app_list_test_model_->PopulateApps(3);
+
+  // Setup tablet/clamshell mode and show launcher.
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+  SetupGridTestApi();
+
+  ui::ScopedAnimationDurationScaleMode scope_duration(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  grid_test_api_->PressItemAt(2);
+  EXPECT_TRUE(AppListIsInFolderView());
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  AppListFolderView* const folder_view = GetFolderView();
+
+  // Cache the initial folder bounds.
+  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
+  const gfx::Rect original_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
+  const gfx::Rect final_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
+
+  // Add a new item.
+  test::AppListTestModel::AppListTestItem* new_item =
+      app_list_test_model_->CreateItem("new_test_item");
+  new_item->SetPosition(app_list_test_model_->top_level_item_list()
+                            ->item_at(0)
+                            ->position()
+                            .CreateBefore());
+  app_list_test_model_->AddItem(new_item);
+  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
+  grid_test_api_->WaitForItemMoveAnimationDone();
+
+  // Verify that the folder view location did not change.
+  EXPECT_EQ(folder_bounds, GetFolderView()->GetBoundsInScreen());
+
+  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(3);
+  ASSERT_TRUE(folder_item_view);
+  ASSERT_TRUE(folder_item_view->is_folder());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // The item at slot 2 should be laid out right of the folder while the folder
+  // is shown.
+  EXPECT_LT(original_folder_item_bounds.right(),
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen().x());
+
+  // The item at slot 1 should be laid out left of the folder.
+  EXPECT_GT(original_folder_item_bounds.x(),
+            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen().right());
+
+  // Close the folder view.
+  ui::test::EventGenerator* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(
+      GetFolderView()->GetBoundsInScreen().right_center() +
+      gfx::Vector2d(10, 0));
+  event_generator->ClickLeftButton();
+
+  EXPECT_TRUE(folder_view->IsAnimationRunning());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // Once folder completes hiding, the folder item view should be moved to
+  // target location.
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
+  grid_test_api_->WaitForItemMoveAnimationDone();
+  EXPECT_FALSE(AppListIsInFolderView());
+
+  EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+  EXPECT_EQ(original_folder_item_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+}
+
+// Tests that folder view bounds do not change if position of the original
+// folder item view changes in the model (as long as the folder is open).
+TEST_P(ProductivityLauncherTest,
+       FolderViewRemainsInPlaceWhenItemMovedToEndInModel) {
+  app_list_test_model_->PopulateApps(2);
+  AppListFolderItem* const folder_item =
+      app_list_test_model_->CreateAndPopulateFolderWithApps(3);
+  const std::string folder_id = folder_item->id();
+  app_list_test_model_->PopulateApps(3);
+
+  // Setup tablet/clamshell mode and show launcher.
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+  SetupGridTestApi();
+
+  ui::ScopedAnimationDurationScaleMode scope_duration(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  grid_test_api_->PressItemAt(2);
+  EXPECT_TRUE(AppListIsInFolderView());
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  AppListFolderView* const folder_view = GetFolderView();
+
+  // Cache the initial folder bounds.
+  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
+  const gfx::Rect original_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
+  const gfx::Rect original_item_1_bounds =
+      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
+  const gfx::Rect original_item_3_bounds =
+      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
+  const gfx::Rect final_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(5)->GetBoundsInScreen();
+
+  // Move the folder item to the last position in the model.
+  app_list_test_model_->RequestPositionUpdate(
+      folder_id,
+      app_list_test_model_->top_level_item_list()
+          ->item_at(5)
+          ->position()
+          .CreateAfter(),
+      RequestPositionUpdateReason::kMoveItem);
+
+  // Verify that the folder view location did not actually change.
+  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());
+
+  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(5);
+  ASSERT_TRUE(folder_item_view);
+  ASSERT_TRUE(folder_item_view->is_folder());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // The item at slot 2 in the model should remain at slot 3 (where it was
+  // before folder item moved in the model).
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+
+  // The item at slot 1 should be remain in place.
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
+
+  // Close the folder view.
+  ui::test::EventGenerator* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(folder_view->GetBoundsInScreen().right_center() +
+                               gfx::Vector2d(10, 0));
+  event_generator->ClickLeftButton();
+
+  EXPECT_TRUE(folder_view->IsAnimationRunning());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // Once folder completes hiding, the folder item view should be moved to
+  // target location.
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
+  grid_test_api_->WaitForItemMoveAnimationDone();
+  EXPECT_FALSE(AppListIsInFolderView());
+
+  EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  EXPECT_EQ(original_folder_item_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+
+  // The item at slot 1 should be remain in place.
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
+  // The item at slot 2 in the model should move into original folder item slot.
+  EXPECT_EQ(original_folder_item_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+}
+
+// Tests that folder view bounds do not change if position of the original
+// folder item view changes in the model (as long as the folder is open).
+TEST_P(ProductivityLauncherTest,
+       FolderViewRemainsInPlaceWhenItemMovedToStartInModel) {
+  app_list_test_model_->PopulateApps(2);
+  AppListFolderItem* const folder_item =
+      app_list_test_model_->CreateAndPopulateFolderWithApps(3);
+  const std::string folder_id = folder_item->id();
+  app_list_test_model_->PopulateApps(3);
+
+  // Setup tablet/clamshell mode and show launcher.
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+  SetupGridTestApi();
+
+  ui::ScopedAnimationDurationScaleMode scope_duration(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  grid_test_api_->PressItemAt(2);
+  EXPECT_TRUE(AppListIsInFolderView());
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  AppListFolderView* const folder_view = GetFolderView();
+
+  // Cache the initial folder bounds.
+  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
+  const gfx::Rect original_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
+  const gfx::Rect original_item_1_bounds =
+      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
+  const gfx::Rect original_item_3_bounds =
+      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
+  const gfx::Rect final_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(0)->GetBoundsInScreen();
+
+  // Move the folder item to the last position in the model.
+  app_list_test_model_->RequestPositionUpdate(
+      folder_id,
+      app_list_test_model_->top_level_item_list()
+          ->item_at(0)
+          ->position()
+          .CreateBefore(),
+      RequestPositionUpdateReason::kMoveItem);
+
+  // Verify that the folder view location did not actually change.
+  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());
+
+  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(0);
+  ASSERT_TRUE(folder_item_view);
+  ASSERT_TRUE(folder_item_view->is_folder());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // The item at slot 3 in the model did not change, so it should remain in
+  // place.
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+
+  // The item at slot 2 in the model should remain in the old position (slot 1).
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+
+  // Close the folder view.
+  ui::test::EventGenerator* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(folder_view->GetBoundsInScreen().right_center() +
+                               gfx::Vector2d(10, 0));
+  event_generator->ClickLeftButton();
+
+  EXPECT_TRUE(folder_view->IsAnimationRunning());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // Once folder completes hiding, the folder item view should be moved to
+  // target location.
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
+  grid_test_api_->WaitForItemMoveAnimationDone();
+  EXPECT_FALSE(AppListIsInFolderView());
+
+  EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  EXPECT_EQ(original_folder_item_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+
+  // The item at slot 2 in the model should move into original folder item slot.
+  EXPECT_EQ(original_folder_item_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+  // The item at slot 3 in the model should move into new position.
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+}
+
+// Tests that folder item deletion during folder view hide animation is handled
+// well.
+TEST_P(ProductivityLauncherTest, ReorderedFolderItemDeletionDuringFolderClose) {
+  app_list_test_model_->PopulateApps(2);
+  AppListFolderItem* const folder_item =
+      app_list_test_model_->CreateAndPopulateFolderWithApps(3);
+  const std::string folder_id = folder_item->id();
+  app_list_test_model_->PopulateApps(3);
+
+  // Setup tablet/clamshell mode and show launcher.
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+  SetupGridTestApi();
+
+  ui::ScopedAnimationDurationScaleMode scope_duration(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  grid_test_api_->PressItemAt(2);
+  EXPECT_TRUE(AppListIsInFolderView());
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  AppListFolderView* const folder_view = GetFolderView();
+
+  // Cache the initial folder bounds.
+  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
+  const gfx::Rect original_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
+  const gfx::Rect original_item_1_bounds =
+      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
+  const gfx::Rect original_item_3_bounds =
+      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
+
+  // Move the folder item to the last position in the model.
+  app_list_test_model_->RequestPositionUpdate(
+      folder_id,
+      app_list_test_model_->top_level_item_list()
+          ->item_at(0)
+          ->position()
+          .CreateBefore(),
+      RequestPositionUpdateReason::kMoveItem);
+
+  // Verify that the folder view location did not actually change.
+  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());
+
+  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(0);
+  ASSERT_TRUE(folder_item_view);
+  ASSERT_TRUE(folder_item_view->is_folder());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // The item at slot 3 in the model did not change, so it should remain in
+  // place.
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+  // The item at slot 2 in the model should remain in the old position (slot 1).
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+
+  // Close the folder view.
+  ui::test::EventGenerator* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(folder_view->GetBoundsInScreen().right_center() +
+                               gfx::Vector2d(10, 0));
+  event_generator->ClickLeftButton();
+
+  // Delete the folder item while the folder is animating out.
+  DeleteFolderItemChildren(folder_item);
+  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
+  grid_test_api_->WaitForItemMoveAnimationDone();
+  EXPECT_FALSE(AppListIsInFolderView());
+
+  // Verify remaining items are moved into correct slots.
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
+  EXPECT_EQ(original_folder_item_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+}
+
+// Tests that folder item deletion just after folder gets hidden (while item
+// bounds are still animating to final positions) gets handled well.
+TEST_P(ProductivityLauncherTest, ReorderedFolderItemDeletionAfterFolderClose) {
+  app_list_test_model_->PopulateApps(2);
+  AppListFolderItem* const folder_item =
+      app_list_test_model_->CreateAndPopulateFolderWithApps(3);
+  const std::string folder_id = folder_item->id();
+  app_list_test_model_->PopulateApps(3);
+
+  // Setup tablet/clamshell mode and show launcher.
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+  SetupGridTestApi();
+
+  ui::ScopedAnimationDurationScaleMode scope_duration(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  grid_test_api_->PressItemAt(2);
+  EXPECT_TRUE(AppListIsInFolderView());
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  AppListFolderView* folder_view = GetFolderView();
+
+  // Cache the initial folder bounds.
+  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
+  const gfx::Rect original_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
+  const gfx::Rect original_item_1_bounds =
+      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
+  const gfx::Rect original_item_3_bounds =
+      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
+
+  // Move the folder item to the last position in the model.
+  app_list_test_model_->RequestPositionUpdate(
+      folder_id,
+      app_list_test_model_->top_level_item_list()
+          ->item_at(0)
+          ->position()
+          .CreateBefore(),
+      RequestPositionUpdateReason::kMoveItem);
+
+  // Verify that the folder view location did not actually change.
+  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());
+
+  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(0);
+  ASSERT_TRUE(folder_item_view);
+  ASSERT_TRUE(folder_item_view->is_folder());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // The item at slot 3 in the model did not change, so it should remain in
+  // place.
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+  // The item at slot 2 in the model should remain in the old position (slot 1).
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+
+  // Close the folder view.
+  ui::test::EventGenerator* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(folder_view->GetBoundsInScreen().right_center() +
+                               gfx::Vector2d(10, 0));
+  event_generator->ClickLeftButton();
+
+  GetAppListTestHelper()->WaitForFolderAnimation();
+
+  // Delete the folder item while items are animating into their final
+  // positions.
+  DeleteFolderItemChildren(folder_item);
+  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
+  grid_test_api_->WaitForItemMoveAnimationDone();
+  EXPECT_FALSE(AppListIsInFolderView());
+
+  // Verify remaining items are moved into correct slots.
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
+  EXPECT_EQ(original_folder_item_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+}
+
+// Tests that folder item deletion while the folder is shown gets handled well.
+TEST_P(ProductivityLauncherTest, ReorderedFolderItemDeletionWhileFolderShown) {
+  app_list_test_model_->PopulateApps(2);
+  AppListFolderItem* const folder_item =
+      app_list_test_model_->CreateAndPopulateFolderWithApps(3);
+  const std::string folder_id = folder_item->id();
+  app_list_test_model_->PopulateApps(3);
+
+  // Setup tablet/clamshell mode and show launcher.
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+  SetupGridTestApi();
+
+  ui::ScopedAnimationDurationScaleMode scope_duration(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  grid_test_api_->PressItemAt(2);
+  EXPECT_TRUE(AppListIsInFolderView());
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  AppListFolderView* folder_view = GetFolderView();
+
+  // Cache the initial folder bounds.
+  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
+  const gfx::Rect original_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
+  const gfx::Rect original_item_1_bounds =
+      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
+  const gfx::Rect original_item_3_bounds =
+      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
+
+  // Move the folder item to the last position in the model.
+  app_list_test_model_->RequestPositionUpdate(
+      folder_id,
+      app_list_test_model_->top_level_item_list()
+          ->item_at(0)
+          ->position()
+          .CreateBefore(),
+      RequestPositionUpdateReason::kMoveItem);
+
+  // Verify that the folder view location did not actually change.
+  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());
+
+  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(0);
+  ASSERT_TRUE(folder_item_view);
+  ASSERT_TRUE(folder_item_view->is_folder());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // The item at slot 3 in the model did not change, so it should remain in
+  // place.
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+  // The item at slot 2 in the model should remain in the old position (slot 1).
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+
+  // Delete the folder item while it's still shown.
+  DeleteFolderItemChildren(folder_item);
+
+  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
+  grid_test_api_->WaitForItemMoveAnimationDone();
+  EXPECT_FALSE(AppListIsInFolderView());
+
+  // Verify remaining items are moved into correct slots.
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
+  EXPECT_EQ(original_folder_item_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+}
+
+// Tests that folder item deletion while the folder view is still animating into
+// shown state gets handled well.
+TEST_P(ProductivityLauncherTest, ReorderedFolderItemDeletionDuringShow) {
+  app_list_test_model_->PopulateApps(2);
+  AppListFolderItem* const folder_item =
+      app_list_test_model_->CreateAndPopulateFolderWithApps(3);
+  const std::string folder_id = folder_item->id();
+  app_list_test_model_->PopulateApps(3);
+
+  // Setup tablet/clamshell mode and show launcher.
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+  SetupGridTestApi();
+
+  ui::ScopedAnimationDurationScaleMode scope_duration(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  grid_test_api_->PressItemAt(2);
+  EXPECT_TRUE(AppListIsInFolderView());
+
+  // Cache the initial folder bounds.
+  const gfx::Rect original_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
+  const gfx::Rect original_item_1_bounds =
+      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
+  const gfx::Rect original_item_3_bounds =
+      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
+
+  // Delete the folder item while the folder is still showing.
+  DeleteFolderItemChildren(folder_item);
+  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
+  grid_test_api_->WaitForItemMoveAnimationDone();
+  EXPECT_FALSE(AppListIsInFolderView());
+
+  // Verify remaining items are moved into correct slots.
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
+  EXPECT_EQ(original_folder_item_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+}
+
 // Tests that Zero State Search is only shown when needed.
 TEST_P(AppListBubbleAndTabletTest, LauncherSearchZeroState) {
   EnableTabletMode(tablet_mode_param());
@@ -1980,11 +2559,14 @@
   const int kItemCount = 6;
   PopulateApps(kItemCount);
 
-  // Dragging the item with index 4.
-  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(4);
+  // Dragging the item with index 2.
+  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(2);
   AppListItem* const dragged_item = dragged_view->item();
   AppListItem* const merged_item = apps_grid_view_->GetItemViewAt(3)->item();
 
+  const gfx::Rect expected_folder_item_view_bounds =
+      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
+
   // Drag the item on top of the item with index 3.
   ui::test::EventGenerator* event_generator = GetEventGenerator();
   event_generator->MoveMouseTo(dragged_view->GetBoundsInScreen().CenterPoint());
@@ -1999,9 +2581,11 @@
   event_generator->ReleaseLeftButton();
   EXPECT_FALSE(apps_grid_view_->IsDragging());
 
-  EXPECT_TRUE(apps_grid_view_->GetItemViewAt(3)->item()->is_folder());
-  EXPECT_EQ(dragged_item->folder_id(),
-            apps_grid_view_->GetItemViewAt(3)->item()->id());
+  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(2);
+  EXPECT_TRUE(folder_item_view->is_folder());
+  EXPECT_EQ(expected_folder_item_view_bounds,
+            folder_item_view->GetBoundsInScreen());
+  EXPECT_EQ(dragged_item->folder_id(), folder_item_view->item()->id());
 
   // Verify that item layers have been destroyed after the drag operation ended.
   apps_grid_test_api_->WaitForItemMoveAnimationDone();
@@ -2014,10 +2598,12 @@
   // Open the newly created folder - when productivity launcher is enabled this
   // happens automatically.
   if (!IsProductivityLauncherEnabled())
-    LeftClickOn(apps_grid_view_->GetItemViewAt(3));
+    LeftClickOn(folder_item_view);
 
   // Verify that item views have no layers after the folder has been opened.
   apps_grid_test_api_->WaitForItemMoveAnimationDone();
+  EXPECT_EQ(expected_folder_item_view_bounds,
+            folder_item_view->GetBoundsInScreen());
   EXPECT_TRUE(AppListIsInFolderView());
   for (int i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
     views::View* item_view = apps_grid_view_->view_model()->view_at(i);
@@ -2038,7 +2624,7 @@
   apps_grid_test_api_->WaitForItemMoveAnimationDone();
 
   EXPECT_FALSE(AppListIsInFolderView());
-  EXPECT_FALSE(apps_grid_view_->GetItemViewAt(3)->item()->is_folder());
+  EXPECT_FALSE(apps_grid_view_->GetItemViewAt(2)->item()->is_folder());
 
   // Verify that a pending layout, if any, does not cause a crash.
   apps_grid_view_->InvalidateLayout();
@@ -2054,6 +2640,8 @@
   AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(4);
   AppListItem* const dragged_item = dragged_view->item();
   AppListItem* const merged_item = apps_grid_view_->GetItemViewAt(3)->item();
+  const gfx::Rect expected_folder_item_view_bounds =
+      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
 
   // Drag the item on top of the item with index 3.
   ui::test::EventGenerator* event_generator = GetEventGenerator();
@@ -2081,11 +2669,16 @@
     EXPECT_FALSE(item_view->layer()) << "at " << i;
   }
 
+  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(3);
+  EXPECT_TRUE(folder_item_view->is_folder());
+  EXPECT_EQ(expected_folder_item_view_bounds,
+            folder_item_view->GetBoundsInScreen());
+
   // Open the newly created folder - with productivity launcher, the folder
   // should already be open.
   if (!IsProductivityLauncherEnabled()) {
     event_generator->MoveMouseTo(
-        apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen().CenterPoint());
+        folder_item_view->GetBoundsInScreen().CenterPoint());
     event_generator->ClickLeftButton();
     event_generator->ReleaseLeftButton();
   }
diff --git a/ash/app_list/test/app_list_test_helper.cc b/ash/app_list/test/app_list_test_helper.cc
index c160cdc..adf6baa 100644
--- a/ash/app_list/test/app_list_test_helper.cc
+++ b/ash/app_list/test/app_list_test_helper.cc
@@ -16,6 +16,7 @@
 #include "ash/app_list/views/app_list_bubble_apps_page.h"
 #include "ash/app_list/views/app_list_bubble_search_page.h"
 #include "ash/app_list/views/app_list_bubble_view.h"
+#include "ash/app_list/views/app_list_folder_view.h"
 #include "ash/app_list/views/app_list_main_view.h"
 #include "ash/app_list/views/app_list_toast_container_view.h"
 #include "ash/app_list/views/app_list_view.h"
@@ -57,6 +58,22 @@
   base::RunLoop().RunUntilIdle();
 }
 
+void AppListTestHelper::WaitForFolderAnimation() {
+  AppListFolderView* folder_view = nullptr;
+  if (!Shell::Get()->IsInTabletMode() &&
+      features::IsProductivityLauncherEnabled()) {
+    folder_view = GetBubbleFolderView();
+  } else {
+    folder_view = GetFullscreenFolderView();
+  }
+  if (!folder_view || !folder_view->IsAnimationRunning())
+    return;
+
+  base::RunLoop run_loop;
+  folder_view->SetAnimationDoneTestCallback(run_loop.QuitClosure());
+  run_loop.Run();
+}
+
 void AppListTestHelper::ShowAppList() {
   app_list_controller_->ShowAppList();
 }
diff --git a/ash/app_list/test/app_list_test_helper.h b/ash/app_list/test/app_list_test_helper.h
index f816936..a3338cf 100644
--- a/ash/app_list/test/app_list_test_helper.h
+++ b/ash/app_list/test/app_list_test_helper.h
@@ -96,6 +96,9 @@
   // Run all pending in message loop to wait for animation to finish.
   void WaitUntilIdle();
 
+  // If a folder view is shown, waits until the folder animations complete.
+  void WaitForFolderAnimation();
+
   // Adds `num_apps` to the app list model.
   void AddAppItems(int num_apps);
 
diff --git a/ash/app_list/views/app_list_bubble_view.cc b/ash/app_list/views/app_list_bubble_view.cc
index c978314c..6679775 100644
--- a/ash/app_list/views/app_list_bubble_view.cc
+++ b/ash/app_list/views/app_list_bubble_view.cc
@@ -592,7 +592,8 @@
 }
 
 void AppListBubbleView::ShowFolderForItemView(AppListItemView* folder_item_view,
-                                              bool focus_name_input) {
+                                              bool focus_name_input,
+                                              base::OnceClosure hide_callback) {
   DVLOG(1) << __FUNCTION__;
   if (folder_view_->IsAnimationRunning())
     return;
@@ -601,7 +602,8 @@
   // Apps.AppListFolderOpened or introduce a new metric.
 
   DCHECK(folder_item_view->is_folder());
-  folder_view_->ConfigureForFolderItemView(folder_item_view);
+  folder_view_->ConfigureForFolderItemView(folder_item_view,
+                                           std::move(hide_callback));
   showing_folder_ = true;
   Layout();
   folder_background_view_->SetVisible(true);
diff --git a/ash/app_list/views/app_list_bubble_view.h b/ash/app_list/views/app_list_bubble_view.h
index 8fce414..f3d2595 100644
--- a/ash/app_list/views/app_list_bubble_view.h
+++ b/ash/app_list/views/app_list_bubble_view.h
@@ -106,7 +106,8 @@
 
   // AppListFolderController:
   void ShowFolderForItemView(AppListItemView* folder_item_view,
-                             bool focus_name_input) override;
+                             bool focus_name_input,
+                             base::OnceClosure hide_callback) override;
   void ShowApps(AppListItemView* folder_item_view, bool select_folder) override;
   void ReparentFolderItemTransit(AppListFolderItem* folder_item) override;
   void ReparentDragEnded() override;
diff --git a/ash/app_list/views/app_list_folder_controller.h b/ash/app_list/views/app_list_folder_controller.h
index e0e90d0..4975c05 100644
--- a/ash/app_list/views/app_list_folder_controller.h
+++ b/ash/app_list/views/app_list_folder_controller.h
@@ -5,6 +5,8 @@
 #ifndef ASH_APP_LIST_VIEWS_APP_LIST_FOLDER_CONTROLLER_H_
 #define ASH_APP_LIST_VIEWS_APP_LIST_FOLDER_CONTROLLER_H_
 
+#include "base/callback_forward.h"
+
 namespace ash {
 
 class AppListFolderItem;
@@ -25,8 +27,10 @@
   // the associated folder item (`folder_item_view->item()`).
   // `focus_name_input` indicates whether the folder name textfield should
   // receive focus by default.
+  // `hide_callback` is a callback run when the folder view gets hidden.
   virtual void ShowFolderForItemView(AppListItemView* folder_item_view,
-                                     bool focus_name_input) = 0;
+                                     bool focus_name_input,
+                                     base::OnceClosure hide_callback) = 0;
 
   // Shows the root level apps list. Called when the UI navigates back from the
   // folder for `folder_item_view`. If `folder_item_view` is nullptr skips
diff --git a/ash/app_list/views/app_list_folder_view.cc b/ash/app_list/views/app_list_folder_view.cc
index a2abfad..bfd7a0e1 100644
--- a/ash/app_list/views/app_list_folder_view.cc
+++ b/ash/app_list/views/app_list_folder_view.cc
@@ -742,7 +742,8 @@
 }
 
 void AppListFolderView::ConfigureForFolderItemView(
-    AppListItemView* folder_item_view) {
+    AppListItemView* folder_item_view,
+    base::OnceClosure hide_callback) {
   DCHECK(folder_item_view->is_folder());
   DCHECK(folder_item_view->item());
   DCHECK(items_grid_view_->app_list_config());
@@ -751,6 +752,7 @@
   // cancel any pending hide animations.
   ResetState(/*restore_folder_item_view_state=*/true);
 
+  hide_callback_ = std::move(hide_callback);
   folder_item_view_ = folder_item_view;
   folder_item_view_observer_.Observe(folder_item_view);
 
@@ -900,6 +902,10 @@
 
 void AppListFolderView::ResetState(bool restore_folder_item_view_state) {
   DVLOG(1) << __FUNCTION__;
+
+  if (hide_callback_)
+    std::move(hide_callback_).Run();
+
   if (folder_item_) {
     items_grid_view_->ClearSelectedView();
     items_grid_view_->SetItemList(nullptr);
@@ -947,10 +953,14 @@
   // view's liveness (so it can reset animations if the folder item
   // view gets deleted).
   // If the view is hidden for reparent, the state will be cleared
-  // when the reparent drag ends.
+  // when the reparent drag ends - close callback still needs to be called so
+  // the root apps grid knows to update its state.
   if (!hide_for_reparent) {
     ResetState(
         /*reset_folder_item_view_state=*/true);
+  } else {
+    if (hide_callback_)
+      std::move(hide_callback_).Run();
   }
 
   if (animation_done_test_callback_)
@@ -1065,6 +1075,10 @@
 
 bool AppListFolderView::IsDragPointOutsideOfFolder(
     const gfx::Point& drag_point) {
+  // Wait for the folder bound to stabilize before starting reparent drag.
+  if (IsAnimationRunning())
+    return false;
+
   gfx::Point drag_point_in_folder = drag_point;
   views::View::ConvertPointToTarget(items_grid_view_, this,
                                     &drag_point_in_folder);
@@ -1083,6 +1097,7 @@
 // drag_view_ in hidden grid view will dispatch the drag and drop event to
 // the top level grid view, until the drag ends.
 void AppListFolderView::ReparentItem(
+    AppsGridView::Pointer pointer,
     AppListItemView* original_drag_view,
     const gfx::Point& drag_point_in_folder_grid) {
   // Convert the drag point relative to the root level AppsGridView.
@@ -1093,7 +1108,7 @@
   // the drag
   folder_item_->NotifyOfDraggedItem(original_drag_view->item());
   root_apps_grid_view_->InitiateDragFromReparentItemInRootLevelGridView(
-      original_drag_view, to_root_level_grid,
+      pointer, original_drag_view, to_root_level_grid,
       base::BindOnce(&AppListFolderView::CancelReparentDragFromRootGrid,
                      weak_ptr_factory_.GetWeakPtr()));
   folder_controller_->ReparentFolderItemTransit(folder_item_);
@@ -1176,6 +1191,15 @@
 void AppListFolderView::HandleKeyboardReparent(AppListItemView* reparented_view,
                                                ui::KeyboardCode key_code) {
   folder_controller_->ReparentFolderItemTransit(folder_item_);
+
+  // Notify the root apps grid that folder is closing before handling keyboard
+  // reparent, to match general flow during drag reparent (where close callback
+  // gets called before reparenting the dragged view). This ensures that items
+  // are in their ideal locations when the item gets reparented (i.e. that the
+  // folder item's slot is not locked to the folder item's initial location).
+  if (hide_callback_)
+    std::move(hide_callback_).Run();
+
   root_apps_grid_view_->HandleKeyboardReparent(reparented_view,
                                                folder_item_view_, key_code);
   folder_controller_->ReparentDragEnded();
diff --git a/ash/app_list/views/app_list_folder_view.h b/ash/app_list/views/app_list_folder_view.h
index e36a4e5..5261de2 100644
--- a/ash/app_list/views/app_list_folder_view.h
+++ b/ash/app_list/views/app_list_folder_view.h
@@ -85,8 +85,10 @@
 
   // Configures AppListFolderView to show the contents for the folder item
   // associated with `folder_item_view`. The folder view will be anchored at
-  // `folder_item_view`.
-  void ConfigureForFolderItemView(AppListItemView* folder_item_view);
+  // `folder_item_view`. `hide_callback` gets called when the folder gets
+  // hidden (after all hide animations complete).
+  void ConfigureForFolderItemView(AppListItemView* folder_item_view,
+                                  base::OnceClosure hide_callback);
 
   // Schedules an animation to show or hide the view.
   // If |show| is false, the view should be set to invisible after the
@@ -177,7 +179,8 @@
   void SetItemName(AppListFolderItem* item, const std::string& name) override;
 
   // Overridden from AppsGridViewFolderDelegate:
-  void ReparentItem(AppListItemView* original_drag_view,
+  void ReparentItem(AppsGridView::Pointer pointer,
+                    AppListItemView* original_drag_view,
                     const gfx::Point& drag_point_in_folder_grid) override;
   void DispatchDragEventForReparent(
       AppsGridView::Pointer pointer,
@@ -278,6 +281,10 @@
   // Whether the folder view is currently shown, or showing.
   bool shown_ = false;
 
+  // If set, the callback that will be called when the folder hides (after hide
+  // animations complete).
+  base::OnceClosure hide_callback_;
+
   // The folder item in the root apps grid associated with this folder.
   AppListItemView* folder_item_view_ = nullptr;
 
diff --git a/ash/app_list/views/apps_container_view.cc b/ash/app_list/views/apps_container_view.cc
index 1414f01..62b6634 100644
--- a/ash/app_list/views/apps_container_view.cc
+++ b/ash/app_list/views/apps_container_view.cc
@@ -414,7 +414,8 @@
 }
 
 void AppsContainerView::ShowFolderForItemView(AppListItemView* folder_item_view,
-                                              bool focus_name_input) {
+                                              bool focus_name_input,
+                                              base::OnceClosure hide_callback) {
   // Prevent new animations from starting if there are currently animations
   // pending. This fixes crbug.com/357099.
   if (app_list_folder_view_->IsAnimationRunning())
@@ -425,7 +426,8 @@
   UMA_HISTOGRAM_ENUMERATION("Apps.AppListFolderOpened",
                             kFullscreenAppListFolders, kMaxFolderOpened);
 
-  app_list_folder_view_->ConfigureForFolderItemView(folder_item_view);
+  app_list_folder_view_->ConfigureForFolderItemView(folder_item_view,
+                                                    std::move(hide_callback));
   SetShowState(SHOW_ACTIVE_FOLDER, false);
 
   // If there is no selected view in the root grid when a folder is opened,
diff --git a/ash/app_list/views/apps_container_view.h b/ash/app_list/views/apps_container_view.h
index d20228ff..3f416cad 100644
--- a/ash/app_list/views/apps_container_view.h
+++ b/ash/app_list/views/apps_container_view.h
@@ -146,7 +146,8 @@
 
   // AppListFolderController:
   void ShowFolderForItemView(AppListItemView* folder_item_view,
-                             bool focus_name_input) override;
+                             bool focus_name_input,
+                             base::OnceClosure hide_callback) override;
   void ShowApps(AppListItemView* folder_item_view, bool select_folder) override;
   void ReparentFolderItemTransit(AppListFolderItem* folder_item) override;
   void ReparentDragEnded() override;
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc
index 3663bf3..58bc456 100644
--- a/ash/app_list/views/apps_grid_view.cc
+++ b/ash/app_list/views/apps_grid_view.cc
@@ -178,6 +178,10 @@
 
 }  // namespace
 
+bool GridIndex::IsValid() const {
+  return page >= 0 && slot >= 0;
+}
+
 std::string GridIndex::ToString() const {
   std::stringstream ss;
   ss << "Page: " << page << ", Slot: " << slot;
@@ -597,6 +601,16 @@
   if (!drag_item_)
     return;  // Drag canceled.
 
+  // If folder is currently open from the grid, delay drag updates until the
+  // folder finishes closing.
+  if (open_folder_info_) {
+    // Only handle pointers that initiated the drag - e.g. ignore drag events
+    // that come from touch if a mouse drag is currently in progress.
+    if (drag_pointer_ == pointer)
+      last_drag_point_ = point;
+    return;
+  }
+
   gfx::Vector2d drag_vector(point - drag_start_grid_view_);
 
   if (ExceededDragThreshold(drag_vector)) {
@@ -717,11 +731,17 @@
         if (MoveItemToFolder(drag_item_, drop_target_, kMoveByDragIntoFolder,
                              &target_folder_id, &is_new_folder)) {
           MaybeCreateFolderDroppingAccessibilityEvent();
-          // If move to folder created a folder, layout the grid to ensure the
-          // created folder's bounds are correct.
-          Layout();
-          if (is_new_folder && features::IsProductivityLauncherEnabled())
+          if (is_new_folder && features::IsProductivityLauncherEnabled()) {
             folder_to_open_after_drag_icon_animation_ = target_folder_id;
+            SetOpenFolderInfo(target_folder_id, drop_target_,
+                              reorder_placeholder_);
+          }
+
+          // If item drag created a folder, layout the grid to ensure the
+          // created folder's bounds are correct. Note that `open_folder_info_`
+          // affects ideal item bounds, so `Layout()` needs to be callsed after
+          // `SetOpenFolderInfo()`.
+          Layout();
         }
       } else if (IsValidReorderTargetIndex(drop_target_)) {
         // Ensure reorder event has already been announced by the end of drag.
@@ -796,6 +816,7 @@
 }
 
 void AppsGridView::InitiateDragFromReparentItemInRootLevelGridView(
+    Pointer pointer,
     AppListItemView* original_drag_view,
     const gfx::Point& drag_point,
     base::OnceClosure cancellation_callback) {
@@ -810,13 +831,12 @@
   for (const auto& entry : view_model_.entries())
     static_cast<AppListItemView*>(entry.view)->EnsureLayer();
 
+  drag_pointer_ = pointer;
   drag_item_ = original_drag_view->item();
   drag_start_grid_view_ = drag_point;
   // Set the flag in root level grid view.
   dragging_for_reparent_item_ = true;
   reparent_drag_cancellation_ = std::move(cancellation_callback);
-
-  MaybeStartCardifiedView();
 }
 
 void AppsGridView::UpdateDragFromReparentItem(Pointer pointer,
@@ -830,6 +850,53 @@
   UpdateDrag(pointer, drag_point);
 }
 
+void AppsGridView::SetOpenFolderInfo(const std::string& folder_id,
+                                     const GridIndex& target_folder_position,
+                                     const GridIndex& position_to_skip) {
+  GridIndex expected_folder_position = target_folder_position;
+  // If the target view is positioned after `position_to_skip`, move the
+  // target one slot earlier, as `position_to_skip` is assumed about to be
+  // emptied.
+  if (position_to_skip.IsValid() &&
+      position_to_skip < expected_folder_position &&
+      expected_folder_position.slot > 0) {
+    --expected_folder_position.slot;
+  }
+
+  open_folder_info_ = {.item_id = folder_id,
+                       .grid_index = expected_folder_position};
+}
+
+void AppsGridView::ShowFolderForView(AppListItemView* folder_view,
+                                     bool new_folder) {
+  DCHECK(open_folder_info_);
+
+  // Guard against invalid folder view.
+  if (!folder_view || !folder_view->is_folder()) {
+    open_folder_info_.reset();
+    return;
+  }
+
+  folder_controller_->ShowFolderForItemView(
+      folder_view,
+      /*focus_name_input=*/new_folder,
+      base::BindOnce(&AppsGridView::FolderHidden, weak_factory_.GetWeakPtr(),
+                     folder_view->item()->id()));
+}
+
+void AppsGridView::FolderHidden(const std::string& item_id) {
+  if (open_folder_info_ && open_folder_info_->item_id == item_id) {
+    open_folder_info_.reset();
+    AnimateToIdealBounds();
+    // Drag updates get throttled while folder is closing during reparent drag -
+    // handle cached drag update if reparent drag is in progress.
+    if (IsDraggingForReparentInRootLevelGridView()) {
+      MaybeStartCardifiedView();
+      UpdateDrag(drag_pointer_, last_drag_point_);
+    }
+  }
+}
+
 bool AppsGridView::IsDragging() const {
   return drag_pointer_ != NONE;
 }
@@ -1110,36 +1177,58 @@
   }
 }
 
+void AppsGridView::SetIdealBoundsForViewToGridIndex(
+    int view_index_in_model,
+    const GridIndex& view_grid_index) {
+  gfx::Rect tile_bounds = GetExpectedTileBounds(view_grid_index);
+  tile_bounds.Offset(CalculateTransitionOffset(view_grid_index.page));
+  if (view_index_in_model < view_model_.view_size()) {
+    view_model_.set_ideal_bounds(view_index_in_model, tile_bounds);
+  } else {
+    pulsing_blocks_model_.set_ideal_bounds(
+        view_index_in_model - view_model_.view_size(), tile_bounds);
+  }
+}
+
 void AppsGridView::CalculateIdealBounds() {
   if (view_structure_.mode() == PagedViewStructure::Mode::kPartialPages) {
     CalculateIdealBoundsForPageStructureWithPartialPages();
     return;
   }
 
+  AppListItemView* view_with_locked_position = nullptr;
+  if (open_folder_info_)
+    view_with_locked_position = GetItemViewForItem(open_folder_info_->item_id);
+
+  std::set<GridIndex> reserved_slots;
+  reserved_slots.insert(reorder_placeholder_);
+  if (open_folder_info_) {
+    reserved_slots.insert(open_folder_info_->grid_index);
+  }
+
   const int total_views =
       view_model_.view_size() + pulsing_blocks_model_.view_size();
   int slot_index = 0;
   for (int i = 0; i < total_views; ++i) {
-    if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_)
+    if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_) {
       continue;
+    }
+
+    if (i < view_model_.view_size() &&
+        view_model_.view_at(i) == view_with_locked_position) {
+      SetIdealBoundsForViewToGridIndex(i, open_folder_info_->grid_index);
+      continue;
+    }
 
     GridIndex view_index = view_structure_.GetIndexFromModelIndex(slot_index);
 
     // Leaves a blank space in the grid for the current reorder placeholder.
-    if (reorder_placeholder_ == view_index) {
+    while (reserved_slots.count(view_index)) {
       ++slot_index;
       view_index = view_structure_.GetIndexFromModelIndex(slot_index);
     }
 
-    gfx::Rect tile_slot = GetExpectedTileBounds(view_index);
-    tile_slot.Offset(CalculateTransitionOffset(view_index.page));
-    if (i < view_model_.view_size()) {
-      view_model_.set_ideal_bounds(i, tile_slot);
-    } else {
-      pulsing_blocks_model_.set_ideal_bounds(i - view_model_.view_size(),
-                                             tile_slot);
-    }
-
+    SetIdealBoundsForViewToGridIndex(i, view_index);
     ++slot_index;
   }
 }
@@ -1446,10 +1535,7 @@
     AppListItemView* folder_view =
         GetItemViewForItem(folder_to_open_after_drag_icon_animation_);
     folder_to_open_after_drag_icon_animation_.clear();
-    if (folder_view && folder_view->is_folder()) {
-      folder_controller_->ShowFolderForItemView(folder_view,
-                                                /*focus_name_input=*/true);
-    }
+    ShowFolderForView(folder_view, /*new_folder=*/true);
   }
 }
 
@@ -1539,10 +1625,10 @@
   CreateGhostImageView();
 }
 
-void AppsGridView::OnFolderItemReparentTimer() {
+void AppsGridView::OnFolderItemReparentTimer(Pointer pointer) {
   DCHECK(folder_delegate_);
   if (drag_out_of_folder_container_ && drag_view_) {
-    folder_delegate_->ReparentItem(drag_view_, last_drag_point_);
+    folder_delegate_->ReparentItem(pointer, drag_view_, last_drag_point_);
 
     // Set the flag in the folder's grid view.
     dragging_for_reparent_item_ = true;
@@ -1572,8 +1658,9 @@
   if (is_item_dragged_out_of_folder) {
     if (!drag_out_of_folder_container_) {
       folder_item_reparent_timer_.Start(
-          FROM_HERE, base::Milliseconds(kFolderItemReparentDelay), this,
-          &AppsGridView::OnFolderItemReparentTimer);
+          FROM_HERE, base::Milliseconds(kFolderItemReparentDelay),
+          base::BindOnce(&AppsGridView::OnFolderItemReparentTimer,
+                         base::Unretained(this), pointer));
       drag_out_of_folder_container_ = true;
     }
   } else {
@@ -1627,6 +1714,7 @@
 }
 
 void AppsGridView::HandleKeyboardFoldering(ui::KeyboardCode key_code) {
+  const GridIndex source_index = GetIndexOfView(selected_view_);
   const GridIndex target_index = GetTargetGridIndexForKeyboardMove(key_code);
   if (!CanMoveSelectedToTargetForKeyboardFoldering(target_index))
     return;
@@ -1643,16 +1731,20 @@
                        kMoveByKeyboardIntoFolder, &folder_id, &is_new_folder)) {
     a11y_announcer_->AnnounceKeyboardFoldering(
         moving_view_title, target_view_title, target_view_is_folder);
-    Layout();
     AppListItemView* folder_view = GetItemViewForItem(folder_id);
     if (folder_view) {
       if (is_new_folder && features::IsProductivityLauncherEnabled()) {
-        folder_controller_->ShowFolderForItemView(folder_view,
-                                                  /*focus_name_input=*/true);
+        SetOpenFolderInfo(folder_id, target_index, source_index);
+        ShowFolderForView(folder_view, /*new_folder=*/true);
       } else {
         folder_view->RequestFocus();
       }
     }
+
+    // Layout the grid to ensure the created folder's bounds are correct.
+    // Note that `open_folder_info_` affects ideal item bounds, so `Layout()`
+    // needs to be callsed after `SetOpenFolderInfo()`.
+    Layout();
   }
 }
 
@@ -1800,8 +1892,11 @@
         // If move to folder created a folder, layout the grid to ensure the
         // created folder's bounds are correct.
         Layout();
-        if (is_new_folder && features::IsProductivityLauncherEnabled())
+        if (is_new_folder && features::IsProductivityLauncherEnabled()) {
           folder_to_open_after_drag_icon_animation_ = target_folder_id;
+          SetOpenFolderInfo(target_folder_id, drop_target_,
+                            reorder_placeholder_);
+        }
       } else {
         cancel_reparent = true;
       }
@@ -2295,6 +2390,10 @@
   view_structure_.Remove(item_view);
   if (item_view == drag_view_)
     drag_view_ = nullptr;
+  if (open_folder_info_ &&
+      open_folder_info_->item_id == item_view->item()->id()) {
+    open_folder_info_.reset();
+  }
   delete item_view;
 }
 
@@ -2803,7 +2902,7 @@
 }
 
 void AppsGridView::MaybeCreateDragReorderAccessibilityEvent() {
-  if (drop_target_region_ == ON_ITEM && !IsFolderItem(drag_view_->item()))
+  if (drop_target_region_ == ON_ITEM && !IsFolderItem(drag_item_))
     return;
 
   // If app was dragged out of folder, no need to announce location for the
@@ -2904,8 +3003,9 @@
     // Note that `folder_controller_` will be null inside a folder apps grid,
     // but those grid are not expected to contain folder items.
     DCHECK(folder_controller_);
-    folder_controller_->ShowFolderForItemView(pressed_item_view,
-                                              /*focus_name_input=*/false);
+    SetOpenFolderInfo(pressed_item_view->item()->id(),
+                      GetIndexOfView(pressed_item_view), GridIndex());
+    ShowFolderForView(pressed_item_view, /*new_folder=*/false);
     return;
   }
 
diff --git a/ash/app_list/views/apps_grid_view.h b/ash/app_list/views/apps_grid_view.h
index 88988284..96287e5 100644
--- a/ash/app_list/views/apps_grid_view.h
+++ b/ash/app_list/views/apps_grid_view.h
@@ -75,6 +75,12 @@
   bool operator<(const GridIndex& other) const {
     return std::tie(page, slot) < std::tie(other.page, other.slot);
   }
+
+  // Whether the grid index is a valid index, i.e. whether page and slot are
+  // non-negative. This method does *not* check whether the index exists in an
+  // apps grid.
+  bool IsValid() const;
+
   std::string ToString() const;
 
   int page = -1;  // Which page an item view is on.
@@ -222,20 +228,24 @@
 
   // Called to initiate drag for reparenting a folder item in root level grid
   // view.
+  // `pointer` - The pointer that's used for dragging (mouse or touch).
   // `drag_point` is in the coordinates of root level grid view.
   // `cancellation_callback` - the callback that can be invoked from the root
   // level grid to cancel drag operation in the originating folder grid.
   void InitiateDragFromReparentItemInRootLevelGridView(
+      Pointer pointer,
       AppListItemView* original_drag_view,
       const gfx::Point& drag_point,
       base::OnceClosure cancellation_callback);
 
   // Updates drag in the root level grid view when receiving the drag event
   // dispatched from the hidden grid view for reparenting a folder item.
+  // `pointer` - The pointer that's used for dragging (mouse or touch).
   void UpdateDragFromReparentItem(Pointer pointer,
                                   const gfx::Point& drag_point);
 
   // Dispatches the drag event from hidden grid view to the top level grid view.
+  // `pointer` - The pointer that's used for dragging (mouse or touch).
   void DispatchDragEventForReparent(Pointer pointer,
                                     const gfx::Point& drag_point);
 
@@ -468,6 +478,12 @@
   // the grid, so number of actual columns may be smaller than `max_columns`.
   void SetMaxColumnsInternal(int max_columns);
 
+  // Sets the ideal bounds for view at index `view_inde_in_model` in
+  // `view_model_`. The bounds are set to match the expected tile bounds at
+  // `view_grid_index` in the apps grid.
+  void SetIdealBoundsForViewToGridIndex(int view_index_in_model,
+                                        const GridIndex& view_grid_index);
+
   // Calculates the item views' bounds for both folder and non-folder.
   void CalculateIdealBounds();
   void CalculateIdealBoundsForPageStructureWithPartialPages();
@@ -650,6 +666,7 @@
 
   // Called when the user is dragging an app. |point| is in grid view
   // coordinates.
+  // `pointer` - The pointer that's used for dragging (mouse or touch).
   void UpdateDrag(Pointer pointer, const gfx::Point& point);
 
   // Returns true if the current drag is occurring within a certain range of the
@@ -762,9 +779,10 @@
   void OnReorderTimer();
 
   // Invoked when |folder_item_reparent_timer_| fires.
-  void OnFolderItemReparentTimer();
+  void OnFolderItemReparentTimer(Pointer pointer);
 
   // Updates drag state for dragging inside a folder's grid view.
+  // `pointer` - The pointer that's used for dragging (mouse or touch).
   void UpdateDragStateInsideFolder(Pointer pointer,
                                    const gfx::Point& drag_point);
 
@@ -866,6 +884,28 @@
       bool aborted,
       AppListReorderAnimationStatus animation_source);
 
+  // Sets `open_folder_info_` for  a folder that is about to be shown.
+  // `folder_id` is the folder's item ID, `target_folder_position` is the grid
+  // index at which the folder is located (or being created).
+  // `position_to_skip`, if valid, is the grid index of an app list item that is
+  // expected to be removed (for example, the reorder placeholder gets removed
+  // after an app list item drag ends, and should thus be ignored when
+  // calculating the final folder position during drag end). The target folder
+  // position should be adjusted as if the item at this position is gone.
+  void SetOpenFolderInfo(const std::string& folder_id,
+                         const GridIndex& target_folder_position,
+                         const GridIndex& position_to_skip);
+
+  // Requsets a folder view for the provided app list folder item to be shown.
+  // `new_folder` indicates whether the folder is a newly created folder.
+  void ShowFolderForView(AppListItemView* folder_view, bool new_folder);
+
+  // Called when a folder view that was opened from this apps grid hides (and
+  // completes hide animation), either when user closes the folder, or when the
+  // folder gets hidden during reparent drag.
+  // `item_id` is the folder items app list model ID.
+  void FolderHidden(const std::string& item_id);
+
   class ScopedModelUpdate;
 
   AppListModel* model_ = nullptr;         // Owned by AppListView.
@@ -999,6 +1039,18 @@
   // The location when |current_ghost_view_| was shown.
   GridIndex current_ghost_location_;
 
+  struct OpenFolderInfo {
+    std::string item_id;
+    GridIndex grid_index;
+  };
+  // Set when a folder view was opened from the apps grid - it contains the
+  // opened folder ID and original location in the grid. While the folder
+  // remains open, the folder item view position will be forced to the original
+  // grid slot, to prevent folder UI from jumping, or empty slots from appearing
+  // behind a folder when the gird item list changes (e.g. if another item gets
+  // added by sync, or the folder item moves as a result of folder rename).
+  absl::optional<OpenFolderInfo> open_folder_info_;
+
   std::unique_ptr<AppsGridContextMenu> context_menu_;
 
   // Indicates the current reorder animation.
diff --git a/ash/app_list/views/apps_grid_view_folder_delegate.h b/ash/app_list/views/apps_grid_view_folder_delegate.h
index 1fc2251..1c8e686a 100644
--- a/ash/app_list/views/apps_grid_view_folder_delegate.h
+++ b/ash/app_list/views/apps_grid_view_folder_delegate.h
@@ -23,10 +23,12 @@
 class ASH_EXPORT AppsGridViewFolderDelegate {
  public:
   // Called when a folder item is dragged out of the folder to be re-parented.
-  // |original_drag_view| is the |drag_view_| inside the folder's grid view.
-  // |drag_point_in_folder_grid| is the last drag point in coordinate of the
-  // AppsGridView inside the folder.
-  virtual void ReparentItem(AppListItemView* original_drag_view,
+  // `original_drag_view` is the `drag_view_` inside the folder's grid view.
+  // `drag_point_in_folder_grid` is the last drag point in coordinate of the
+  // AppsGridView inside the folder. `pointer` describes the type of pointer
+  // used for the drag action (mouse or touch).
+  virtual void ReparentItem(AppsGridView::Pointer pointer,
+                            AppListItemView* original_drag_view,
                             const gfx::Point& drag_point_in_folder_grid) = 0;
 
   // Dispatches drag event from the hidden grid view to the root level grid view
diff --git a/ash/app_list/views/apps_grid_view_unittest.cc b/ash/app_list/views/apps_grid_view_unittest.cc
index 6e83664..e9caf56 100644
--- a/ash/app_list/views/apps_grid_view_unittest.cc
+++ b/ash/app_list/views/apps_grid_view_unittest.cc
@@ -2827,6 +2827,7 @@
   AppListItemView* first_item = GetItemViewInTopLevelGrid(0);
   const std::string first_item_id = first_item->item()->id();
   const std::string second_item_id = GetItemViewInTopLevelGrid(1)->item()->id();
+  const gfx::Rect expected_folder_view_bounds = first_item->GetBoundsInScreen();
 
   ui::test::EventGenerator* const event_generator = GetEventGenerator();
   // Press an arrow key to engage keyboard traversal in fullscreen launcher.
@@ -2840,6 +2841,7 @@
   // Test that the first item in the grid is now a folder with the first and
   // second items.
   AppListItemView* new_folder = GetItemViewInTopLevelGrid(0);
+  EXPECT_EQ(expected_folder_view_bounds, new_folder->GetBoundsInScreen());
   AppListFolderItem* folder_item =
       static_cast<AppListFolderItem*>(new_folder->item());
   EXPECT_TRUE(folder_item->is_folder());
@@ -2865,6 +2867,7 @@
     event_generator->PressAndReleaseKey(ui::VKEY_UP);
     event_generator->PressAndReleaseKey(ui::VKEY_ESCAPE);
     EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
+    EXPECT_EQ(expected_folder_view_bounds, new_folder->GetBoundsInScreen());
   }
   ASSERT_TRUE(new_folder->HasFocus());
   ASSERT_TRUE(apps_grid_view_->IsSelectedView(new_folder));
@@ -2920,6 +2923,64 @@
                                      kMoveByKeyboardIntoFolder, 4);
 }
 
+// Tests that control + shift + left arrow puts |selected_item_| creates a
+// folder if one does not exist.
+TEST_P(AppsGridViewClamshellAndTabletTest,
+       ControlShiftLeftArrowFoldersItemBasic) {
+  base::HistogramTester histogram_tester;
+  model_->PopulateApps(3 * apps_grid_view_->cols());
+  UpdateLayout();
+  // Select the first item in the grid, folder it with the item to the right.
+  AppListItemView* first_item = GetItemViewInTopLevelGrid(0);
+  const std::string first_item_id = first_item->item()->id();
+  AppListItemView* second_item = GetItemViewInTopLevelGrid(1);
+  const std::string second_item_id = second_item->item()->id();
+  gfx::Rect expected_folder_view_bounds = first_item->GetBoundsInScreen();
+
+  ui::test::EventGenerator* const event_generator = GetEventGenerator();
+  // Press an arrow key to engage keyboard traversal in fullscreen launcher.
+  event_generator->PressAndReleaseKey(ui::VKEY_DOWN);
+
+  // Focus second item, and folder it.
+  apps_grid_view_->GetFocusManager()->SetFocusedView(second_item);
+  event_generator->PressAndReleaseKey(ui::VKEY_LEFT,
+                                      ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
+
+  // Test that the first item in the grid is now a folder with the first and
+  // second items.
+  AppListItemView* new_folder = GetItemViewInTopLevelGrid(0);
+  EXPECT_EQ(expected_folder_view_bounds, new_folder->GetBoundsInScreen());
+  AppListFolderItem* folder_item =
+      static_cast<AppListFolderItem*>(new_folder->item());
+  EXPECT_TRUE(folder_item->is_folder());
+  EXPECT_EQ(2u, folder_item->ChildItemCount());
+  EXPECT_TRUE(folder_item->FindChildItem(first_item_id));
+  EXPECT_TRUE(folder_item->FindChildItem(second_item_id));
+  histogram_tester.ExpectBucketCount(GetItemMoveTypeHistogramName(),
+                                     kMoveByKeyboardIntoFolder, 1);
+
+  // With productivity launcher enabled, the folder is expected to get opened
+  // after creation.
+  EXPECT_EQ(!is_productivity_launcher_enabled_,
+            apps_grid_view_->IsSelectedView(new_folder));
+  EXPECT_EQ(is_productivity_launcher_enabled_,
+            GetAppListTestHelper()->IsInFolderView());
+  if (is_productivity_launcher_enabled_) {
+    EXPECT_EQ(folder_item, app_list_folder_view_->folder_item());
+    EXPECT_TRUE(app_list_folder_view_->folder_header_view()
+                    ->GetFolderNameViewForTest()
+                    ->HasFocus());
+
+    // Close the folder.
+    event_generator->PressAndReleaseKey(ui::VKEY_UP);
+    event_generator->PressAndReleaseKey(ui::VKEY_ESCAPE);
+    EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
+    EXPECT_EQ(expected_folder_view_bounds, new_folder->GetBoundsInScreen());
+  }
+  ASSERT_TRUE(new_folder->HasFocus());
+  ASSERT_TRUE(apps_grid_view_->IsSelectedView(new_folder));
+}
+
 // Tests that foldering an item that is on a different page fails.
 TEST_P(AppsGridViewTabletTest, ControlShiftArrowFailsToFolderAcrossPages) {
   model_->PopulateApps(GetTilesPerPage(0) + GetTilesPerPage(1));
@@ -3004,11 +3065,14 @@
   folder_view->RequestFocus();
   EXPECT_TRUE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
       folder_view->GetBoundsInScreen()));
+  const gfx::Rect original_folder_view_bounds =
+      folder_view->GetBoundsInScreen();
 
   // Open the folder.
   ui::test::EventGenerator* const event_generator = GetEventGenerator();
   event_generator->PressAndReleaseKey(ui::VKEY_RETURN);
   ASSERT_TRUE(GetAppListTestHelper()->IsInFolderView());
+  EXPECT_EQ(original_folder_view_bounds, folder_view->GetBoundsInScreen());
 
   const AppListItemView* reparented_item_view =
       folder_apps_grid_view()->view_model()->view_at(0);
@@ -3069,11 +3133,14 @@
   folder_view->RequestFocus();
   EXPECT_TRUE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
       folder_view->GetBoundsInScreen()));
+  const gfx::Rect original_folder_view_bounds =
+      folder_view->GetBoundsInScreen();
 
   // Open the folder.
   ui::test::EventGenerator* const event_generator = GetEventGenerator();
   event_generator->PressAndReleaseKey(ui::VKEY_RETURN);
   ASSERT_TRUE(GetAppListTestHelper()->IsInFolderView());
+  EXPECT_EQ(original_folder_view_bounds, folder_view->GetBoundsInScreen());
 
   const AppListItemView* reparented_item_view =
       folder_apps_grid_view()->view_model()->view_at(0);
@@ -3123,11 +3190,14 @@
       test_api_->GetViewAtIndex(GridIndex(0, GetTilesPerPage(0) - 2));
   ASSERT_TRUE(folder_view->is_folder());
   folder_view->RequestFocus();
+  const gfx::Rect original_folder_view_bounds =
+      folder_view->GetBoundsInScreen();
 
   // Open the folder.
   ui::test::EventGenerator* const event_generator = GetEventGenerator();
   event_generator->PressAndReleaseKey(ui::VKEY_RETURN);
   ASSERT_TRUE(GetAppListTestHelper()->IsInFolderView());
+  EXPECT_EQ(original_folder_view_bounds, folder_view->GetBoundsInScreen());
 
   const AppListItemView* reparented_item_view =
       folder_apps_grid_view()->view_model()->view_at(0);
@@ -3171,6 +3241,8 @@
   const std::string first_item_id = moving_item->item()->id();
   const std::string second_item_id =
       GetItemViewInTopLevelGrid(kNumberOfApps - 1)->item()->id();
+  const gfx::Rect expected_folder_view_bounds =
+      moving_item->GetBoundsInScreen();
 
   ui::test::EventGenerator* const event_generator = GetEventGenerator();
   // Press an arrow key to engage keyboard traversal in fullscreen launcher.
@@ -3183,6 +3255,7 @@
   // Test that the first item in the grid is now a folder with the first and
   // second items, and that the folder is the selected view.
   AppListItemView* new_folder = GetItemViewInTopLevelGrid(kNumberOfApps - 2);
+  EXPECT_EQ(expected_folder_view_bounds, new_folder->GetBoundsInScreen());
   AppListFolderItem* folder_item =
       static_cast<AppListFolderItem*>(new_folder->item());
   EXPECT_TRUE(folder_item->is_folder());
diff --git a/ash/components/arc/test/fake_file_system_instance.cc b/ash/components/arc/test/fake_file_system_instance.cc
index c840342f..7ea647ca 100644
--- a/ash/components/arc/test/fake_file_system_instance.cc
+++ b/ash/components/arc/test/fake_file_system_instance.cc
@@ -50,7 +50,7 @@
   return root;
 }
 
-// Generates unique document ID on each calls.
+// Generates unique document ID on each call.
 std::string GenerateDocumentId() {
   static int count = 0;
   std::ostringstream ss;
@@ -58,12 +58,17 @@
   return ss.str();
 }
 
+// Generates unique URL ID on each call.
+std::string GenerateUrlId() {
+  static int count = 0;
+  std::ostringstream ss;
+  ss << "url_" << count++;
+  return ss.str();
+}
+
 // Maximum size in bytes to read FD from PIPE.
 constexpr size_t kMaxBytesToReadFromPipe = 8 * 1024;  // 8KB;
 
-// URL ID used by OpenFileSessionToWrite() and OpenFileSessionToRead().
-constexpr char kUrlId[] = "url_id";
-
 }  // namespace
 
 constexpr base::FilePath::CharType FakeFileSystemInstance::kFakeAndroidPath[];
@@ -444,12 +449,13 @@
                                                   base::File::Flags::FLAG_WRITE)
           : CreateStreamFileDescriptorToWrite(file.url);
   DCHECK(fd.is_valid());
-  AddOpenSession(kUrlId, fd.get());
+  std::string url_id = GenerateUrlId();
+  AddOpenSession(url_id, fd.get());
   mojo::ScopedHandle wrapped_handle =
       mojo::WrapPlatformHandle(mojo::PlatformHandle(std::move(fd)));
   DCHECK(wrapped_handle.is_valid());
   mojom::FileSessionPtr file_session = mojom::FileSession::New();
-  file_session->url_id = kUrlId;
+  file_session->url_id = std::move(url_id);
   file_session->fd = std::move(wrapped_handle);
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(std::move(callback), std::move(file_session)));
@@ -473,12 +479,13 @@
                                                   base::File::Flags::FLAG_READ)
           : CreateStreamFileDescriptorToRead(file.content);
   DCHECK(fd.is_valid());
-  AddOpenSession(kUrlId, fd.get());
+  std::string url_id = GenerateUrlId();
+  AddOpenSession(url_id, fd.get());
   mojo::ScopedHandle wrapped_handle =
       mojo::WrapPlatformHandle(mojo::PlatformHandle(std::move(fd)));
   DCHECK(wrapped_handle.is_valid());
   mojom::FileSessionPtr file_session = mojom::FileSession::New();
-  file_session->url_id = kUrlId;
+  file_session->url_id = std::move(url_id);
   file_session->fd = std::move(wrapped_handle);
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(std::move(callback), std::move(file_session)));
diff --git a/ash/components/device_activity/BUILD.gn b/ash/components/device_activity/BUILD.gn
index d1a3502a..389c253 100644
--- a/ash/components/device_activity/BUILD.gn
+++ b/ash/components/device_activity/BUILD.gn
@@ -60,6 +60,7 @@
   deps = [
     ":device_activity",
     ":fresnel_service_proto",
+    "//ash/constants:constants",
     "//base",
     "//base/test:test_support",
     "//chromeos/network",
diff --git a/ash/components/device_activity/device_activity_client.cc b/ash/components/device_activity/device_activity_client.cc
index 8fbdf86d..831c539 100644
--- a/ash/components/device_activity/device_activity_client.cc
+++ b/ash/components/device_activity/device_activity_client.cc
@@ -328,15 +328,20 @@
         }
       case psm_rlwe::RlweUseCase::CROS_FRESNEL_MONTHLY:
         if (base::FeatureList::IsEnabled(
-                features::kDeviceActiveClientMonthlyCheckMembership)) {
-          TransitionToCheckMembershipOprf(current_use_case);
-          return;
-        } else {
+                features::kDeviceActiveClientMonthlyCheckIn)) {
           // During rollout, we perform CheckIn without CheckMembership for
           // powerwash, recovery, or RMA devices.
           TransitionToCheckIn(current_use_case);
           return;
         }
+
+        if (base::FeatureList::IsEnabled(
+                features::kDeviceActiveClientMonthlyCheckMembership)) {
+          TransitionToCheckMembershipOprf(current_use_case);
+          return;
+        }
+
+        break;
       default:
         VLOG(1) << "Use case is not supported yet. "
                 << psm_rlwe::RlweUseCase_Name(
diff --git a/ash/components/device_activity/device_activity_client_unittest.cc b/ash/components/device_activity/device_activity_client_unittest.cc
index 5f6c1674..9914e5f 100644
--- a/ash/components/device_activity/device_activity_client_unittest.cc
+++ b/ash/components/device_activity/device_activity_client_unittest.cc
@@ -9,11 +9,13 @@
 #include "ash/components/device_activity/fresnel_pref_names.h"
 #include "ash/components/device_activity/fresnel_service.pb.h"
 #include "ash/components/device_activity/monthly_use_case_impl.h"
+#include "ash/constants/ash_features.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/timer/mock_timer.h"
 #include "chromeos/network/network_state_handler_observer.h"
@@ -236,6 +238,12 @@
 
   // testing::Test:
   void SetUp() override {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kDeviceActiveClientMonthlyCheckIn},
+        /*disabled_features*/ {
+            features::kDeviceActiveClientDailyCheckMembership,
+            features::kDeviceActiveClientMonthlyCheckMembership});
+
     // Initialize pointer to our fake |PsmTestData| object.
     psm_test_data_ = GetPsmTestData();
 
@@ -320,6 +328,7 @@
   // The underlying |psm_test_data_| object will outlive this testing class.
   PsmTestData* psm_test_data_ = nullptr;
 
+  base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<NetworkStateTestHelper> network_state_test_helper_;
   TestingPrefServiceSimple local_state_;
   scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index bc2b563c..43f3753 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -574,8 +574,7 @@
 
 // Enables or disables enterprise policy control for eSIM cellular networks.
 // See https://crbug.com/1231305.
-const base::Feature kESimPolicy{"ESimPolicy",
-                                base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kESimPolicy{"ESimPolicy", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enable or disable support for touchpad with haptic feedback.
 const base::Feature kExoHapticFeedbackSupport("ExoHapticFeedbackSupport",
@@ -1429,6 +1428,11 @@
     "DeviceActiveClientDailyCheckMembership",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables or disables PSM CheckIn for the monthly device active pings
+// on Chrome OS.
+const base::Feature kDeviceActiveClientMonthlyCheckIn{
+    "DeviceActiveClientMonthlyCheckIn", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables or disables PSM CheckMembership for monthly device active pings
 // on Chrome OS.
 const base::Feature kDeviceActiveClientMonthlyCheckMembership{
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 8c27d9e..0790e1b4 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -163,6 +163,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kDeviceActiveClientDailyCheckMembership;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kDeviceActiveClientMonthlyCheckIn;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kDeviceActiveClientMonthlyCheckMembership;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kDeviceForceScheduledReboot;
diff --git a/ash/webui/eche_app_ui/eche_app_manager.cc b/ash/webui/eche_app_ui/eche_app_manager.cc
index d4307d7..b183c67 100644
--- a/ash/webui/eche_app_ui/eche_app_manager.cc
+++ b/ash/webui/eche_app_ui/eche_app_manager.cc
@@ -40,7 +40,8 @@
         presence_monitor_client,
     LaunchAppHelper::LaunchEcheAppFunction launch_eche_app_function,
     LaunchAppHelper::CloseEcheAppFunction close_eche_app_function,
-    LaunchAppHelper::LaunchNotificationFunction launch_notification_function)
+    LaunchAppHelper::LaunchNotificationFunction launch_notification_function,
+    StreamStatusChangedFunction stream_status_changed_function)
     : connection_manager_(
           std::make_unique<secure_channel::ConnectionManagerImpl>(
               multidevice_setup_client,
@@ -65,8 +66,7 @@
           std::make_unique<EcheNotificationClickHandler>(
               phone_hub_manager,
               feature_status_provider_.get(),
-              launch_app_helper_.get(),
-              display_stream_handler_.get())),
+              launch_app_helper_.get())),
       eche_connector_(
           std::make_unique<EcheConnectorImpl>(feature_status_provider_.get(),
                                               connection_manager_.get())),
@@ -86,8 +86,7 @@
           std::make_unique<EcheRecentAppClickHandler>(
               phone_hub_manager,
               feature_status_provider_.get(),
-              launch_app_helper_.get(),
-              display_stream_handler_.get())),
+              launch_app_helper_.get())),
       notification_generator_(std::make_unique<EcheNotificationGenerator>(
           launch_app_helper_.get())),
       apps_access_manager_(std::make_unique<AppsAccessManagerImpl>(
@@ -95,11 +94,14 @@
           message_receiver_.get(),
           feature_status_provider_.get(),
           pref_service,
-          multidevice_setup_client)) {
+          multidevice_setup_client)),
+      stream_status_changed_function_(
+          std::move(stream_status_changed_function)) {
   ash::GetNetworkConfigService(
       remote_cros_network_config_.BindNewPipeAndPassReceiver());
   system_info_provider_ = std::make_unique<SystemInfoProvider>(
       std::move(system_info), remote_cros_network_config_.get());
+  display_stream_handler_->AddObserver(this);
 }
 
 EcheAppManager::~EcheAppManager() = default;
@@ -129,6 +131,15 @@
   display_stream_handler_->Bind(std::move(receiver));
 }
 
+void EcheAppManager::OnStartStreaming() {
+  stream_status_changed_function_.Run(
+      mojom::StreamStatus::kStreamStatusStarted);
+}
+
+void EcheAppManager::OnStreamStatusChanged(mojom::StreamStatus status) {
+  stream_status_changed_function_.Run(status);
+}
+
 AppsAccessManager* EcheAppManager::GetAppsAccessManager() {
   return apps_access_manager_.get();
 }
@@ -146,6 +157,7 @@
   signaler_.reset();
   eche_connector_.reset();
   eche_notification_click_handler_.reset();
+  display_stream_handler_->RemoveObserver(this);
   display_stream_handler_.reset();
   launch_app_helper_.reset();
   feature_status_provider_.reset();
diff --git a/ash/webui/eche_app_ui/eche_app_manager.h b/ash/webui/eche_app_ui/eche_app_manager.h
index b84d0da..b767a99 100644
--- a/ash/webui/eche_app_ui/eche_app_manager.h
+++ b/ash/webui/eche_app_ui/eche_app_manager.h
@@ -16,6 +16,7 @@
 #include "ash/services/secure_channel/public/cpp/client/presence_monitor_client_impl.h"
 // TODO(https://crbug.com/1164001): move to forward declaration.
 #include "ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
+#include "ash/webui/eche_app_ui/eche_display_stream_handler.h"
 #include "ash/webui/eche_app_ui/eche_feature_status_provider.h"
 #include "ash/webui/eche_app_ui/eche_notification_click_handler.h"
 #include "ash/webui/eche_app_ui/eche_recent_app_click_handler.h"
@@ -49,8 +50,14 @@
 // Implements the core logic of the EcheApp and exposes interfaces via its
 // public API. Implemented as a KeyedService since it depends on other
 // KeyedService instances.
-class EcheAppManager : public KeyedService {
+class EcheAppManager : public KeyedService,
+                       public EcheDisplayStreamHandler::Observer {
  public:
+  using StreamStatusChangedFunction =
+      base::RepeatingCallback<void(const mojom::StreamStatus status)>;
+
+  // TODO(b/223321926): clean up callback functions from constructor to a
+  // specific class
   EcheAppManager(PrefService* pref_service,
                  std::unique_ptr<SystemInfo> system_info,
                  phonehub::PhoneHubManager*,
@@ -61,7 +68,8 @@
                      presence_monitor_client,
                  LaunchAppHelper::LaunchEcheAppFunction,
                  LaunchAppHelper::CloseEcheAppFunction,
-                 LaunchAppHelper::LaunchNotificationFunction);
+                 LaunchAppHelper::LaunchNotificationFunction,
+                 StreamStatusChangedFunction);
   ~EcheAppManager() override;
 
   EcheAppManager(const EcheAppManager&) = delete;
@@ -87,6 +95,10 @@
   // KeyedService:
   void Shutdown() override;
 
+  // EcheDisplayStreamHandler::Observer:
+  void OnStartStreaming() override;
+  void OnStreamStatusChanged(mojom::StreamStatus status) override;
+
  private:
   std::unique_ptr<secure_channel::ConnectionManager> connection_manager_;
   std::unique_ptr<EcheFeatureStatusProvider> feature_status_provider_;
@@ -105,6 +117,7 @@
       remote_cros_network_config_;
   std::unique_ptr<SystemInfoProvider> system_info_provider_;
   std::unique_ptr<AppsAccessManager> apps_access_manager_;
+  StreamStatusChangedFunction stream_status_changed_function_;
 };
 
 }  // namespace eche_app
diff --git a/ash/webui/eche_app_ui/eche_app_manager_unittest.cc b/ash/webui/eche_app_ui/eche_app_manager_unittest.cc
index 90944bff..7525f8c 100644
--- a/ash/webui/eche_app_ui/eche_app_manager_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_app_manager_unittest.cc
@@ -46,6 +46,8 @@
     const absl::optional<std::u16string>& message,
     std::unique_ptr<LaunchAppHelper::NotificationInfo> info) {}
 
+void StreamStatusChangedFunction(const mojom::StreamStatus status) {}
+
 class FakePresenceMonitorClient : public secure_channel::PresenceMonitorClient {
  public:
   FakePresenceMonitorClient() = default;
@@ -117,7 +119,8 @@
         std::move(fake_presence_monitor_client),
         base::BindRepeating(&LaunchEcheAppFunction),
         base::BindRepeating(&CloseEcheAppFunction),
-        base::BindRepeating(&LaunchNotificationFunction));
+        base::BindRepeating(&LaunchNotificationFunction),
+        base::BindRepeating(&StreamStatusChangedFunction));
   }
 
   mojo::Remote<mojom::SignalingMessageExchanger>&
diff --git a/ash/webui/eche_app_ui/eche_display_stream_handler.cc b/ash/webui/eche_app_ui/eche_display_stream_handler.cc
index d1b9f86a9..7d2f1ee 100644
--- a/ash/webui/eche_app_ui/eche_display_stream_handler.cc
+++ b/ash/webui/eche_app_ui/eche_display_stream_handler.cc
@@ -19,6 +19,13 @@
   NotifyStartStreaming();
 }
 
+void EcheDisplayStreamHandler::OnStreamStatusChanged(
+    mojom::StreamStatus status) {
+  PA_LOG(INFO) << "echeapi EcheDisplayStreamHandler OnStreamStatusChanged "
+               << status;
+  NotifyStreamStatusChanged(status);
+}
+
 void EcheDisplayStreamHandler::Bind(
     mojo::PendingReceiver<mojom::DisplayStreamHandler> receiver) {
   display_stream_receiver_.reset();
@@ -38,5 +45,11 @@
     observer.OnStartStreaming();
 }
 
+void EcheDisplayStreamHandler::NotifyStreamStatusChanged(
+    mojom::StreamStatus status) {
+  for (auto& observer : observer_list_)
+    observer.OnStreamStatusChanged(status);
+}
+
 }  // namespace eche_app
 }  // namespace ash
diff --git a/ash/webui/eche_app_ui/eche_display_stream_handler.h b/ash/webui/eche_app_ui/eche_display_stream_handler.h
index 339b1b2..0696119 100644
--- a/ash/webui/eche_app_ui/eche_display_stream_handler.h
+++ b/ash/webui/eche_app_ui/eche_display_stream_handler.h
@@ -25,11 +25,8 @@
    public:
     ~Observer() override = default;
 
-    //  Called when the streaming is ready. About another status:
-    //  OnStopStreaming, we prefer to listen to the stop signal when the bubble
-    //  is really closed.
-    // TODO(paulzchen): Using generic method `OnStreamStatusChanged`.
     virtual void OnStartStreaming() = 0;
+    virtual void OnStreamStatusChanged(mojom::StreamStatus status) = 0;
   };
 
   EcheDisplayStreamHandler();
@@ -40,6 +37,7 @@
 
   // mojom::DisplayStreamHandler:
   void StartStreaming() override;
+  void OnStreamStatusChanged(mojom::StreamStatus status) override;
 
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
@@ -48,6 +46,7 @@
 
  protected:
   void NotifyStartStreaming();
+  void NotifyStreamStatusChanged(mojom::StreamStatus status);
 
  private:
   mojo::Receiver<mojom::DisplayStreamHandler> display_stream_receiver_{this};
diff --git a/ash/webui/eche_app_ui/eche_display_stream_handler_unittest.cc b/ash/webui/eche_app_ui/eche_display_stream_handler_unittest.cc
index fec38a0..4db83d6 100644
--- a/ash/webui/eche_app_ui/eche_display_stream_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_display_stream_handler_unittest.cc
@@ -15,13 +15,23 @@
   FakeObserver() = default;
   ~FakeObserver() override = default;
 
-  size_t num_calls() const { return num_calls_; }
+  size_t num_start_streaming_calls() const {
+    return num_start_streaming_calls_;
+  }
+  mojom::StreamStatus last_notified_stream_status() const {
+    return last_notified_stream_status_;
+  }
 
   // EcheDisplayStreamHandler::Observer:
-  void OnStartStreaming() override { ++num_calls_; }
+  void OnStartStreaming() override { ++num_start_streaming_calls_; }
+  void OnStreamStatusChanged(mojom::StreamStatus status) override {
+    last_notified_stream_status_ = status;
+  }
 
  private:
-  size_t num_calls_ = 0;
+  size_t num_start_streaming_calls_ = 0;
+  mojom::StreamStatus last_notified_stream_status_ =
+      mojom::StreamStatus::kStreamStatusUnknown;
 };
 
 }  // namespace
@@ -46,8 +56,16 @@
   }
 
   void StartStreaming() { handler_->StartStreaming(); }
+  void NotifyStreamStatus(mojom::StreamStatus status) {
+    handler_->OnStreamStatusChanged(status);
+  }
 
-  size_t GetNumObserverCalls() const { return fake_observer_.num_calls(); }
+  size_t GetNumObserverStartStreamingCalls() const {
+    return fake_observer_.num_start_streaming_calls();
+  }
+  mojom::StreamStatus GetObservedStreamStatus() const {
+    return fake_observer_.last_notified_stream_status();
+  }
 
  private:
   FakeObserver fake_observer_;
@@ -56,8 +74,22 @@
 
 TEST_F(EcheDisplayStreamHandlerTest, StartStreaming) {
   StartStreaming();
-  EXPECT_EQ(1u, GetNumObserverCalls());
+  EXPECT_EQ(1u, GetNumObserverStartStreamingCalls());
+}
+
+TEST_F(EcheDisplayStreamHandlerTest, OnStreamStatusChanged) {
+  NotifyStreamStatus(mojom::StreamStatus::kStreamStatusInitializing);
+  EXPECT_EQ(mojom::StreamStatus::kStreamStatusInitializing,
+            GetObservedStreamStatus());
+
+  NotifyStreamStatus(mojom::StreamStatus::kStreamStatusStarted);
+  EXPECT_EQ(mojom::StreamStatus::kStreamStatusStarted,
+            GetObservedStreamStatus());
+
+  NotifyStreamStatus(mojom::StreamStatus::kStreamStatusStopped);
+  EXPECT_EQ(mojom::StreamStatus::kStreamStatusStopped,
+            GetObservedStreamStatus());
 }
 
 }  // namespace eche_app
-}  // namespace ash
\ No newline at end of file
+}  // namespace ash
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler.cc b/ash/webui/eche_app_ui/eche_notification_click_handler.cc
index 3f6324e..6b58bbe8 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler.cc
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler.cc
@@ -18,11 +18,9 @@
 EcheNotificationClickHandler::EcheNotificationClickHandler(
     phonehub::PhoneHubManager* phone_hub_manager,
     FeatureStatusProvider* feature_status_provider,
-    LaunchAppHelper* launch_app_helper,
-    EcheDisplayStreamHandler* display_stream_handler)
+    LaunchAppHelper* launch_app_helper)
     : feature_status_provider_(feature_status_provider),
-      launch_app_helper_(launch_app_helper),
-      display_stream_handler_(display_stream_handler) {
+      launch_app_helper_(launch_app_helper) {
   handler_ = phone_hub_manager->GetNotificationInteractionHandler();
   feature_status_provider_->AddObserver(this);
   if (handler_ && IsClickable(feature_status_provider_->GetStatus())) {
@@ -32,18 +30,12 @@
     PA_LOG(INFO)
         << "No Phone Hub interaction handler to set Eche click handler";
   }
-
-  if (features::IsEcheSWAInBackgroundEnabled())
-    display_stream_handler_->AddObserver(this);
 }
 
 EcheNotificationClickHandler::~EcheNotificationClickHandler() {
   feature_status_provider_->RemoveObserver(this);
   if (is_click_handler_set_ && handler_)
     handler_->RemoveNotificationClickHandler(this);
-
-  if (features::IsEcheSWAInBackgroundEnabled())
-    display_stream_handler_->RemoveObserver(this);
 }
 
 void EcheNotificationClickHandler::HandleNotificationClick(
@@ -58,7 +50,6 @@
           notification_id, app_metadata.package_name,
           app_metadata.visible_app_name, app_metadata.user_id,
           app_metadata.icon);
-      is_waiting_for_streaming_to_show_ = true;
       break;
     case LaunchAppHelper::AppLaunchProhibitedReason::kDisabledByScreenLock:
       launch_app_helper_->ShowNotification(
@@ -93,30 +84,15 @@
   } else if (is_click_handler_set_ && !clickable) {
     handler_->RemoveNotificationClickHandler(this);
     is_click_handler_set_ = false;
-    is_waiting_for_streaming_to_show_ = false;
   }
 
   if (NeedClose(feature_status_provider_->GetStatus()) &&
       !base::FeatureList::IsEnabled(features::kEcheSWADebugMode)) {
     PA_LOG(INFO) << "Close Eche app window";
-    is_waiting_for_streaming_to_show_ = false;
     launch_app_helper_->CloseEcheApp();
   }
 }
 
-void EcheNotificationClickHandler::OnStartStreaming() {
-  if (features::IsEcheCustomWidgetEnabled()) {
-    // TODO(paulzchen): Move the eche tray control to factory.
-    auto* eche_tray = Shell::GetPrimaryRootWindowController()
-                          ->GetStatusAreaWidget()
-                          ->eche_tray();
-    if (eche_tray && is_waiting_for_streaming_to_show_) {
-      eche_tray->ShowBubble();
-      is_waiting_for_streaming_to_show_ = false;
-    }
-  }
-}
-
 bool EcheNotificationClickHandler::IsClickable(FeatureStatus status) {
   return status == FeatureStatus::kDisconnected ||
          status == FeatureStatus::kConnecting ||
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler.h b/ash/webui/eche_app_ui/eche_notification_click_handler.h
index c46cc41..5fe0c234 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler.h
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler.h
@@ -10,7 +10,6 @@
 #include "ash/components/phonehub/notification_interaction_handler.h"
 // TODO(https://crbug.com/1164001): move to forward declaration.
 #include "ash/components/phonehub/phone_hub_manager.h"
-#include "ash/webui/eche_app_ui/eche_display_stream_handler.h"
 #include "ash/webui/eche_app_ui/feature_status_provider.h"
 #include "base/callback.h"
 
@@ -21,14 +20,11 @@
 
 // Handles notification clicks originating from Phone Hub notifications.
 class EcheNotificationClickHandler : public phonehub::NotificationClickHandler,
-                                     public FeatureStatusProvider::Observer,
-                                     public EcheDisplayStreamHandler::Observer {
+                                     public FeatureStatusProvider::Observer {
  public:
-  EcheNotificationClickHandler(
-      phonehub::PhoneHubManager* phone_hub_manager,
-      FeatureStatusProvider* feature_status_provider,
-      LaunchAppHelper* launch_app_helper,
-      EcheDisplayStreamHandler* display_stream_handler);
+  EcheNotificationClickHandler(phonehub::PhoneHubManager* phone_hub_manager,
+                               FeatureStatusProvider* feature_status_provider,
+                               LaunchAppHelper* launch_app_helper);
   ~EcheNotificationClickHandler() override;
 
   EcheNotificationClickHandler(const EcheNotificationClickHandler&) = delete;
@@ -43,14 +39,6 @@
   // FeatureStatusProvider::Observer:
   void OnFeatureStatusChanged() override;
 
-  // EcheDisplayStreamHandler::Observer:
-  void OnStartStreaming() override;
-
-  // Test helpers, we need this to confirm the streaming will work as expected.
-  bool waiting_for_streaming_to_show() {
-    return is_waiting_for_streaming_to_show_;
-  }
-
  private:
   bool IsClickable(FeatureStatus status);
 
@@ -59,9 +47,7 @@
   phonehub::NotificationInteractionHandler* handler_;
   FeatureStatusProvider* feature_status_provider_;
   LaunchAppHelper* launch_app_helper_;
-  EcheDisplayStreamHandler* display_stream_handler_;
   bool is_click_handler_set_ = false;
-  bool is_waiting_for_streaming_to_show_ = false;
 };
 
 }  // namespace eche_app
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc b/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
index 64fb496..04df5a3 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
@@ -8,12 +8,6 @@
 
 #include "ash/components/phonehub/fake_phone_hub_manager.h"
 #include "ash/constants/ash_features.h"
-#include "ash/system/eche/eche_tray.h"
-#include "ash/system/status_area_widget_test_helper.h"
-#include "ash/system/tray/tray_bubble_wrapper.h"
-#include "ash/test/ash_test_base.h"
-#include "ash/test/ash_test_suite.h"
-#include "ash/test/test_ash_web_view_factory.h"
 #include "ash/webui/eche_app_ui/fake_feature_status_provider.h"
 #include "ash/webui/eche_app_ui/fake_launch_app_helper.h"
 #include "ash/webui/eche_app_ui/launch_app_helper.h"
@@ -27,7 +21,7 @@
 namespace ash {
 namespace eche_app {
 
-class EcheNotificationClickHandlerTest : public AshTestBase {
+class EcheNotificationClickHandlerTest : public testing::Test {
  protected:
   EcheNotificationClickHandlerTest() = default;
   EcheNotificationClickHandlerTest(const EcheNotificationClickHandlerTest&) =
@@ -36,23 +30,14 @@
       const EcheNotificationClickHandlerTest&) = delete;
   ~EcheNotificationClickHandlerTest() override = default;
 
-  // AshTestBase::Test:
+  // testing::Test:
   void SetUp() override {
-    scoped_feature_list_.InitWithFeatures(
-        /*enabled_features=*/{features::kEcheSWA, features::kEcheCustomWidget},
-        /*disabled_features=*/{});
-
-    DCHECK(test_web_view_factory_.get());
-
-    ui::ResourceBundle::CleanupSharedInstance();
-    AshTestSuite::LoadTestResources();
-    AshTestBase::SetUp();
-    eche_tray_ =
-        ash::StatusAreaWidgetTestHelper::GetStatusAreaWidget()->eche_tray();
-
     fake_phone_hub_manager_.fake_feature_status_provider()->SetStatus(
         phonehub::FeatureStatus::kEnabledAndConnected);
     fake_feature_status_provider_.SetStatus(FeatureStatus::kIneligible);
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kEcheSWA},
+        /*disabled_features=*/{});
     launch_app_helper_ = std::make_unique<FakeLaunchAppHelper>(
         &fake_phone_hub_manager_,
         base::BindRepeating(
@@ -64,16 +49,13 @@
         base::BindRepeating(
             &EcheNotificationClickHandlerTest::FakeLaunchNotificationFunction,
             base::Unretained(this)));
-    display_stream_handler_ = std::make_unique<EcheDisplayStreamHandler>();
     handler_ = std::make_unique<EcheNotificationClickHandler>(
         &fake_phone_hub_manager_, &fake_feature_status_provider_,
-        launch_app_helper_.get(), display_stream_handler_.get());
+        launch_app_helper_.get());
   }
 
   void TearDown() override {
-    AshTestBase::TearDown();
     launch_app_helper_.reset();
-    display_stream_handler_.reset();
     handler_.reset();
   }
 
@@ -114,20 +96,12 @@
         ->notification_click_handler_count();
   }
 
-  void StartStreaming() { handler_->OnStartStreaming(); }
-
   bool close_eche_is_called() { return close_eche_is_called_; }
 
   size_t num_notifications_shown() { return num_notifications_shown_; }
 
   size_t num_app_launch() { return num_app_launch_; }
 
-  bool waiting_for_streaming_to_show() {
-    return handler_->waiting_for_streaming_to_show();
-  }
-
-  EcheTray* eche_tray() { return eche_tray_; }
-
   void reset() {
     close_eche_is_called_ = false;
     num_notifications_shown_ = 0;
@@ -141,15 +115,9 @@
   base::test::ScopedFeatureList scoped_feature_list_;
   FakeFeatureStatusProvider fake_feature_status_provider_;
   std::unique_ptr<FakeLaunchAppHelper> launch_app_helper_;
-  std::unique_ptr<EcheDisplayStreamHandler> display_stream_handler_;
   bool close_eche_is_called_;
   size_t num_notifications_shown_ = 0;
   size_t num_app_launch_ = 0;
-  EcheTray* eche_tray_ = nullptr;  // Not owned
-
-  // Calling the factory constructor is enough to set it up.
-  std::unique_ptr<TestAshWebViewFactory> test_web_view_factory_ =
-      std::make_unique<TestAshWebViewFactory>();
 };
 
 TEST_F(EcheNotificationClickHandlerTest, StatusChangeTransitions) {
@@ -236,28 +204,5 @@
   EXPECT_EQ(num_notifications_shown(), 1u);
 }
 
-TEST_F(EcheNotificationClickHandlerTest, StartStreaming) {
-  EXPECT_FALSE(waiting_for_streaming_to_show());
-
-  const int64_t notification_id = 0;
-  const char16_t app_name[] = u"Test App";
-  const char package_name[] = "com.google.testapp";
-  const int64_t user_id = 0;
-  phonehub::Notification::AppMetadata app_meta_data =
-      phonehub::Notification::AppMetadata(app_name, package_name,
-                                          /*icon=*/gfx::Image(),
-                                          /*icon_color=*/absl::nullopt,
-                                          /*icon_is_monochrome=*/true, user_id);
-  HandleNotificationClick(notification_id, app_meta_data);
-
-  EXPECT_TRUE(waiting_for_streaming_to_show());
-
-  StartStreaming();
-
-  EXPECT_TRUE(
-      eche_tray()->get_bubble_wrapper_for_test()->bubble_view()->GetVisible());
-  EXPECT_FALSE(waiting_for_streaming_to_show());
-}
-
 }  // namespace eche_app
 }  // namespace ash
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc b/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
index 6c770649..ae5fe2c 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
@@ -17,11 +17,9 @@
 EcheRecentAppClickHandler::EcheRecentAppClickHandler(
     phonehub::PhoneHubManager* phone_hub_manager,
     FeatureStatusProvider* feature_status_provider,
-    LaunchAppHelper* launch_app_helper,
-    EcheDisplayStreamHandler* display_stream_handler)
+    LaunchAppHelper* launch_app_helper)
     : feature_status_provider_(feature_status_provider),
-      launch_app_helper_(launch_app_helper),
-      display_stream_handler_(display_stream_handler) {
+      launch_app_helper_(launch_app_helper) {
   notification_handler_ =
       phone_hub_manager->GetNotificationInteractionHandler();
   recent_apps_handler_ = phone_hub_manager->GetRecentAppsInteractionHandler();
@@ -33,9 +31,6 @@
     recent_apps_handler_->AddRecentAppClickObserver(this);
     is_click_handler_set_ = true;
   }
-
-  if (features::IsEcheSWAInBackgroundEnabled())
-    display_stream_handler_->AddObserver(this);
 }
 
 EcheRecentAppClickHandler::~EcheRecentAppClickHandler() {
@@ -44,9 +39,6 @@
     notification_handler_->RemoveNotificationClickHandler(this);
   if (recent_apps_handler_)
     recent_apps_handler_->RemoveRecentAppClickObserver(this);
-
-  if (features::IsEcheSWAInBackgroundEnabled())
-    display_stream_handler_->RemoveObserver(this);
 }
 
 void EcheRecentAppClickHandler::HandleNotificationClick(
@@ -74,7 +66,6 @@
           /*notification_id=*/absl::nullopt, app_metadata.package_name,
           app_metadata.visible_app_name, app_metadata.user_id,
           app_metadata.icon);
-      is_waiting_for_streaming_to_show_ = true;
       break;
     case LaunchAppHelper::AppLaunchProhibitedReason::kDisabledByScreenLock:
       launch_app_helper_->ShowNotification(
@@ -110,20 +101,6 @@
     notification_handler_->RemoveNotificationClickHandler(this);
     recent_apps_handler_->RemoveRecentAppClickObserver(this);
     is_click_handler_set_ = false;
-    is_waiting_for_streaming_to_show_ = false;
-  }
-}
-
-void EcheRecentAppClickHandler::OnStartStreaming() {
-  if (features::IsEcheCustomWidgetEnabled()) {
-    // TODO(paulzchen): Move the eche tray control to factory.
-    auto* eche_tray = Shell::GetPrimaryRootWindowController()
-                          ->GetStatusAreaWidget()
-                          ->eche_tray();
-    if (eche_tray && is_waiting_for_streaming_to_show_) {
-      eche_tray->ShowBubble();
-      is_waiting_for_streaming_to_show_ = false;
-    }
   }
 }
 
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler.h b/ash/webui/eche_app_ui/eche_recent_app_click_handler.h
index efdb6aae..f5297a1 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler.h
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler.h
@@ -12,7 +12,6 @@
 #include "ash/components/phonehub/phone_hub_manager.h"
 #include "ash/components/phonehub/recent_app_click_observer.h"
 #include "ash/components/phonehub/recent_apps_interaction_handler.h"
-#include "ash/webui/eche_app_ui/eche_display_stream_handler.h"
 #include "ash/webui/eche_app_ui/feature_status_provider.h"
 #include "base/callback.h"
 
@@ -24,13 +23,11 @@
 // Handles recent app clicks originating from Phone Hub recent apps.
 class EcheRecentAppClickHandler : public phonehub::NotificationClickHandler,
                                   public FeatureStatusProvider::Observer,
-                                  public phonehub::RecentAppClickObserver,
-                                  public EcheDisplayStreamHandler::Observer {
+                                  public phonehub::RecentAppClickObserver {
  public:
   EcheRecentAppClickHandler(phonehub::PhoneHubManager* phone_hub_manager,
                             FeatureStatusProvider* feature_status_provider,
-                            LaunchAppHelper* launch_app_helper,
-                            EcheDisplayStreamHandler* display_stream_handler);
+                            LaunchAppHelper* launch_app_helper);
   ~EcheRecentAppClickHandler() override;
 
   EcheRecentAppClickHandler(const EcheRecentAppClickHandler&) = delete;
@@ -49,14 +46,6 @@
   // FeatureStatusProvider::Observer:
   void OnFeatureStatusChanged() override;
 
-  // EcheDisplayStreamHandler::Observer:
-  void OnStartStreaming() override;
-
-  // Test helpers
-  bool waiting_for_streaming_to_show() {
-    return is_waiting_for_streaming_to_show_;
-  }
-
  private:
   bool IsClickable(FeatureStatus status);
 
@@ -64,9 +53,7 @@
   phonehub::RecentAppsInteractionHandler* recent_apps_handler_;
   FeatureStatusProvider* feature_status_provider_;
   LaunchAppHelper* launch_app_helper_;
-  EcheDisplayStreamHandler* display_stream_handler_;
   bool is_click_handler_set_ = false;
-  bool is_waiting_for_streaming_to_show_ = false;
 };
 
 }  // namespace eche_app
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc b/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
index e13cb35..6da1e7d5 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
@@ -8,12 +8,6 @@
 
 #include "ash/components/phonehub/fake_phone_hub_manager.h"
 #include "ash/constants/ash_features.h"
-#include "ash/system/eche/eche_tray.h"
-#include "ash/system/status_area_widget_test_helper.h"
-#include "ash/system/tray/tray_bubble_wrapper.h"
-#include "ash/test/ash_test_base.h"
-#include "ash/test/ash_test_suite.h"
-#include "ash/test/test_ash_web_view_factory.h"
 #include "ash/webui/eche_app_ui/fake_feature_status_provider.h"
 #include "ash/webui/eche_app_ui/fake_launch_app_helper.h"
 #include "ash/webui/eche_app_ui/launch_app_helper.h"
@@ -27,7 +21,7 @@
 namespace ash {
 namespace eche_app {
 
-class EcheRecentAppClickHandlerTest : public AshTestBase {
+class EcheRecentAppClickHandlerTest : public testing::Test {
  protected:
   EcheRecentAppClickHandlerTest() = default;
   EcheRecentAppClickHandlerTest(const EcheRecentAppClickHandlerTest&) = delete;
@@ -35,24 +29,14 @@
       const EcheRecentAppClickHandlerTest&) = delete;
   ~EcheRecentAppClickHandlerTest() override = default;
 
-  // AshTestBase::Test:
+  // testing::Test:
   void SetUp() override {
-    scoped_feature_list_.InitWithFeatures(
-        /*enabled_features=*/{features::kEcheSWA, features::kEcheCustomWidget},
-        /*disabled_features=*/{});
-
-    DCHECK(test_web_view_factory_.get());
-
-    ui::ResourceBundle::CleanupSharedInstance();
-    AshTestSuite::LoadTestResources();
-    AshTestBase::SetUp();
-    eche_tray_ =
-        ash::StatusAreaWidgetTestHelper::GetStatusAreaWidget()->eche_tray();
-
     fake_phone_hub_manager_.fake_feature_status_provider()->SetStatus(
         phonehub::FeatureStatus::kEnabledAndConnected);
     fake_feature_status_provider_.SetStatus(FeatureStatus::kIneligible);
-
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kEcheSWA},
+        /*disabled_features=*/{});
     launch_app_helper_ = std::make_unique<FakeLaunchAppHelper>(
         &fake_phone_hub_manager_,
         base::BindRepeating(
@@ -64,16 +48,13 @@
         base::BindRepeating(
             &EcheRecentAppClickHandlerTest::FakeLaunchNotificationFunction,
             base::Unretained(this)));
-    display_stream_handler_ = std::make_unique<EcheDisplayStreamHandler>();
     handler_ = std::make_unique<EcheRecentAppClickHandler>(
         &fake_phone_hub_manager_, &fake_feature_status_provider_,
-        launch_app_helper_.get(), display_stream_handler_.get());
+        launch_app_helper_.get());
   }
 
   void TearDown() override {
-    AshTestBase::TearDown();
     launch_app_helper_.reset();
-    display_stream_handler_.reset();
     handler_.reset();
   }
 
@@ -124,35 +105,21 @@
         ->FetchRecentAppMetadataList();
   }
 
-  void StartStreaming() { handler_->OnStartStreaming(); }
-
   const std::string& get_package_name() { return package_name_; }
 
   const std::u16string& get_visible_name() { return visible_name_; }
 
   int64_t get_user_id() { return user_id_; }
 
-  bool waiting_for_streaming_to_show() {
-    return handler_->waiting_for_streaming_to_show();
-  }
-
-  EcheTray* eche_tray() { return eche_tray_; }
-
  private:
   phonehub::FakePhoneHubManager fake_phone_hub_manager_;
   base::test::ScopedFeatureList scoped_feature_list_;
   FakeFeatureStatusProvider fake_feature_status_provider_;
   std::unique_ptr<LaunchAppHelper> launch_app_helper_;
   std::unique_ptr<EcheRecentAppClickHandler> handler_;
-  std::unique_ptr<EcheDisplayStreamHandler> display_stream_handler_;
   std::string package_name_;
   std::u16string visible_name_;
   int64_t user_id_;
-  EcheTray* eche_tray_ = nullptr;  // Not owned
-
-  // Calling the factory constructor is enough to set it up.
-  std::unique_ptr<TestAshWebViewFactory> test_web_view_factory_ =
-      std::make_unique<TestAshWebViewFactory>();
 };
 
 TEST_F(EcheRecentAppClickHandlerTest, StatusChangeTransitions) {
@@ -217,25 +184,5 @@
   EXPECT_EQ(fake_app_metadata.user_id, app_metadata[0].user_id);
 }
 
-TEST_F(EcheRecentAppClickHandlerTest, StartStreaming) {
-  EXPECT_FALSE(waiting_for_streaming_to_show());
-
-  const int64_t user_id = 1;
-  const char16_t app_visible_name[] = u"Fake App";
-  const char package_name[] = "com.fakeapp";
-  auto fake_app_metadata = phonehub::Notification::AppMetadata(
-      app_visible_name, package_name, gfx::Image(),
-      /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id);
-  RecentAppClicked(fake_app_metadata);
-
-  EXPECT_TRUE(waiting_for_streaming_to_show());
-
-  StartStreaming();
-
-  EXPECT_TRUE(
-      eche_tray()->get_bubble_wrapper_for_test()->bubble_view()->GetVisible());
-  EXPECT_FALSE(waiting_for_streaming_to_show());
-}
-
 }  // namespace eche_app
 }  // namespace ash
diff --git a/ash/webui/eche_app_ui/mojom/eche_app.mojom b/ash/webui/eche_app_ui/mojom/eche_app.mojom
index 3bfbaa1..312b34b 100644
--- a/ash/webui/eche_app_ui/mojom/eche_app.mojom
+++ b/ash/webui/eche_app_ui/mojom/eche_app.mojom
@@ -89,9 +89,21 @@
           mojo_base.mojom.String16 message, WebNotificationType type);
 };
 
-// Interface for streaming a display video with which the connection is
-// established. TODO(paulzchen): Using generic method `OnStreamStatusChanged`.
+// Enum representing the video streaming status from browser. Numerical
+// values should not be changed because they must stay in sync with value on
+// Eche web app code.
+enum StreamStatus {
+    kStreamStatusUnknown,      // Initial state, not trigger anything yet
+    kStreamStatusInitializing, // Eche browser is setting up video streaming
+    kStreamStatusStarted,      // Video streaming is set up and started
+    kStreamStatusStopped,      // Video streaming is stopped
+};
+
+// Interface to notify the video streaming status from Eche browser to Eche
+// SWA native code.
 interface DisplayStreamHandler {
   // Stream a display video for Eche.
   StartStreaming();
+  // Notifies the stream status change for Eche.
+  OnStreamStatusChanged(StreamStatus status);
 };
diff --git a/ash/webui/eche_app_ui/resources/browser_proxy.js b/ash/webui/eche_app_ui/resources/browser_proxy.js
index f1ede4e..e0e3c4a 100644
--- a/ash/webui/eche_app_ui/resources/browser_proxy.js
+++ b/ash/webui/eche_app_ui/resources/browser_proxy.js
@@ -86,7 +86,8 @@
      console.log('echeapi debug on, browser_proxy.js window.close block');
    } else {
      console.log('echeapi browser_proxy.js window.close');
-     window.close();
+     displayStreamHandler.onStreamStatusChanged(
+         ash.echeApp.mojom.StreamStatus.kStreamStatusStopped);
    }
  });
 
@@ -153,7 +154,8 @@
  // Register START_STREAMING pipes.
  guestMessagePipe.registerHandler(Message.START_STREAMING, async () => {
    console.log('echeapi browser_proxy.js startStreaming');
-   displayStreamHandler.startStreaming();
+   displayStreamHandler.onStreamStatusChanged(
+       ash.echeApp.mojom.StreamStatus.kStreamStatusStarted);
  });
 
  // We can't access hash change event inside iframe so parse the notification
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
index 81222234..8df577a2 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
@@ -102,7 +102,7 @@
         return;
       }
     }
-    setAlbumSelected(albumChanged, getAmbientProvider());
+    setAlbumSelected(albumChanged, getAmbientProvider(), this.getStore());
   }
 
   private onArtAlbumDialogClose_() {
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts
index 9299805..d8bd9ef 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts
@@ -12,19 +12,24 @@
 
 export enum AmbientActionName {
   SET_ALBUMS = 'set_albums',
+  SET_ALBUM_SELECTED = 'set_album_selected',
   SET_AMBIENT_MODE_ENABLED = 'set_ambient_mode_enabled',
   SET_TEMPERATURE_UNIT = 'set_temperature_unit',
   SET_TOPIC_SOURCE = 'set_topic_source',
 }
 
-export type AmbientActions = SetAlbumsAction|SetAmbientModeEnabledAction|
-    SetTopicSourceAction|SetTemperatureUnitAction;
+export type AmbientActions = SetAlbumsAction|SetAlbumSelectedAction|
+    SetAmbientModeEnabledAction|SetTopicSourceAction|SetTemperatureUnitAction;
 
 export type SetAlbumsAction = Action&{
   name: AmbientActionName.SET_ALBUMS;
   albums: AmbientModeAlbum[];
 };
 
+export type SetAlbumSelectedAction = Action&{
+  name: AmbientActionName.SET_ALBUM_SELECTED;
+};
+
 export type SetAmbientModeEnabledAction = Action&{
   name: AmbientActionName.SET_AMBIENT_MODE_ENABLED;
   enabled: boolean;
@@ -47,6 +52,10 @@
   return {name: AmbientActionName.SET_ALBUMS, albums};
 }
 
+export function setAlbumSelectedAction(): SetAlbumSelectedAction {
+  return {name: AmbientActionName.SET_ALBUM_SELECTED};
+}
+
 /**
  * Sets the current value of the ambient mode pref.
  */
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts
index 8d45b4e..82134d7 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts
@@ -5,7 +5,7 @@
 import {AmbientModeAlbum, AmbientProviderInterface, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
 import {PersonalizationStore} from '../personalization_store.js';
 
-import {setAmbientModeEnabledAction, setTemperatureUnitAction, setTopicSourceAction} from './ambient_actions.js';
+import {setAlbumSelectedAction, setAmbientModeEnabledAction, setTemperatureUnitAction, setTopicSourceAction} from './ambient_actions.js';
 
 /**
  * @fileoverview contains all of the functions to interact with ambient mode
@@ -46,6 +46,10 @@
 
 // Set one album as selected or not.
 export function setAlbumSelected(
-    album: AmbientModeAlbum, provider: AmbientProviderInterface): void {
+    album: AmbientModeAlbum, provider: AmbientProviderInterface,
+    store: PersonalizationStore): void {
+  // Dispatch action to update albums info with the changed album.
+  store.dispatch(setAlbumSelectedAction());
+
   provider.setAlbumSelected(album.id, album.topicSource, album.checked);
 }
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts
index 8798c336..4e22619 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts
@@ -15,6 +15,14 @@
   switch (action.name) {
     case AmbientActionName.SET_ALBUMS:
       return action.albums;
+    case AmbientActionName.SET_ALBUM_SELECTED:
+      if (!state) {
+        return state;
+      }
+      // An albums in AmbientState.albums is mutated by setting checked
+      // to True/False, have to return a copy of albums state so that
+      // Polymer knows there is an update.
+      return [...state];
     default:
       return state;
   }
diff --git a/ash/wm/desks/templates/desks_templates_unittest.cc b/ash/wm/desks/templates/desks_templates_unittest.cc
index f6204f6..c3406fd 100644
--- a/ash/wm/desks/templates/desks_templates_unittest.cc
+++ b/ash/wm/desks/templates/desks_templates_unittest.cc
@@ -43,6 +43,7 @@
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/overview/overview_test_base.h"
 #include "ash/wm/overview/overview_test_util.h"
+#include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
 #include "base/callback_helpers.h"
@@ -471,6 +472,48 @@
   EXPECT_FALSE(grid_list[1]->no_windows_widget());
 }
 
+// Tests that the "App does not support split-screen" label is hidden when the
+// desk templates grid is shown.
+TEST_F(DesksTemplatesTest, NoAppSplitScreenLabelOnTemplateGridShow) {
+  std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow();
+  auto test_window = CreateAppWindow();
+
+  // At least one entry is required for the templates grid to be shown.
+  AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now());
+
+  // Start overview mode.
+  ToggleOverview();
+  WaitForDesksTemplatesUI();
+  ASSERT_TRUE(GetOverviewSession());
+
+  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
+
+  OverviewItem* snappable_overview_item =
+      GetOverviewItemForWindow(test_window.get());
+  OverviewItem* unsnappable_overview_item =
+      GetOverviewItemForWindow(unsnappable_window.get());
+
+  // Note: `cannot_snap_widget_` will be created on demand.
+  EXPECT_FALSE(snappable_overview_item->cannot_snap_widget_for_testing());
+  ASSERT_FALSE(unsnappable_overview_item->cannot_snap_widget_for_testing());
+
+  // Snap the extra snappable window to enter split view mode.
+  SplitViewController* split_view_controller =
+      SplitViewController::Get(Shell::GetPrimaryRootWindow());
+
+  split_view_controller->SnapWindow(test_window.get(),
+                                    SplitViewController::LEFT);
+  ASSERT_TRUE(split_view_controller->InSplitViewMode());
+  ASSERT_TRUE(unsnappable_overview_item->cannot_snap_widget_for_testing());
+  ui::Layer* unsnappable_layer =
+      unsnappable_overview_item->cannot_snap_widget_for_testing()->GetLayer();
+  EXPECT_EQ(1.f, unsnappable_layer->opacity());
+
+  // Entering the templates grid will hide the unsnappable label.
+  ShowDesksTemplatesGrids();
+  EXPECT_EQ(0.f, unsnappable_layer->opacity());
+}
+
 // Tests when user enter desk templates, a11y alert being sent.
 TEST_F(DesksTemplatesTest, InvokeAccessibilityAlertOnEnterDeskTemplates) {
   TestAccessibilityControllerClient client;
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 71627a4..8bffaff 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -1054,7 +1054,7 @@
   if (split_view_drag_indicators_)
     split_view_drag_indicators_->OnDisplayBoundsChanged();
 
-  UpdateCannotSnapWarningVisibility();
+  UpdateCannotSnapWarningVisibility(/*animate=*/true);
 
   // In case of split view mode, the grid bounds and item positions will be
   // updated in |OnSplitViewDividerPositionChanged|.
@@ -2002,7 +2002,7 @@
   }
 
   // Update the cannot snap warnings and adjust the grid bounds.
-  UpdateCannotSnapWarningVisibility();
+  UpdateCannotSnapWarningVisibility(/*animate=*/true);
   SetBoundsAndUpdatePositions(GetGridBoundsInScreen(root_window_),
                               /*ignored_items=*/{}, /*animate=*/false);
 
@@ -2399,9 +2399,9 @@
                                               root_window_);
 }
 
-void OverviewGrid::UpdateCannotSnapWarningVisibility() {
+void OverviewGrid::UpdateCannotSnapWarningVisibility(bool animate) {
   for (auto& overview_mode_item : window_list_)
-    overview_mode_item->UpdateCannotSnapWarningVisibility();
+    overview_mode_item->UpdateCannotSnapWarningVisibility(animate);
 }
 
 void OverviewGrid::OnSaveDeskAsTemplateButtonPressed() {
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index 5c86e2e..c161a0187 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -510,7 +510,7 @@
   // Returns the the bounds of the desks widget in screen coordinates.
   gfx::Rect GetDesksWidgetBounds() const;
 
-  void UpdateCannotSnapWarningVisibility();
+  void UpdateCannotSnapWarningVisibility(bool animate);
 
   // Called back when the button to save a desk template is pressed.
   void OnSaveDeskAsTemplateButtonPressed();
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index e1123b4..8a8cc5e 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -231,6 +231,8 @@
   item_widget_event_blocker_ =
       std::make_unique<aura::ScopedWindowEventTargetingBlocker>(
           item_widget_->GetNativeWindow());
+
+  HideCannotSnapWarning(animate);
 }
 
 void OverviewItem::RevertHideForDesksTemplatesGrid(bool animate) {
@@ -244,6 +246,8 @@
   }
 
   item_widget_event_blocker_.reset();
+
+  UpdateCannotSnapWarningVisibility(animate);
 }
 
 void OverviewItem::OnMovingWindowToAnotherDesk() {
@@ -509,7 +513,7 @@
   transform_window_.Close();
 }
 
-void OverviewItem::UpdateCannotSnapWarningVisibility() {
+void OverviewItem::UpdateCannotSnapWarningVisibility(bool animate) {
   // Windows which can snap will never show this warning. Or if the window is
   // the drop target window, also do not show this warning.
   bool visible = true;
@@ -546,21 +550,28 @@
     GetWindow()->parent()->StackChildAbove(
         cannot_snap_widget_->GetNativeWindow(), GetWindow());
   }
-
-  DoSplitviewOpacityAnimation(cannot_snap_widget_->GetNativeWindow()->layer(),
-                              visible
-                                  ? SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_IN
-                                  : SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_OUT);
+  if (animate) {
+    DoSplitviewOpacityAnimation(
+        cannot_snap_widget_->GetLayer(),
+        visible ? SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_IN
+                : SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_OUT);
+  } else {
+    cannot_snap_widget_->GetLayer()->SetOpacity(visible ? 1.f : 0.f);
+  }
   const gfx::Rect bounds =
       ToStableSizeRoundedRect(GetWindowTargetBoundsWithInsets());
   cannot_snap_widget_->SetBoundsCenteredIn(bounds, /*animate=*/false);
 }
 
-void OverviewItem::HideCannotSnapWarning() {
+void OverviewItem::HideCannotSnapWarning(bool animate) {
   if (!cannot_snap_widget_)
     return;
-  DoSplitviewOpacityAnimation(cannot_snap_widget_->GetNativeWindow()->layer(),
-                              SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_OUT);
+  if (animate) {
+    DoSplitviewOpacityAnimation(cannot_snap_widget_->GetLayer(),
+                                SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_OUT);
+  } else {
+    cannot_snap_widget_->GetLayer()->SetOpacity(0.f);
+  }
 }
 
 void OverviewItem::OnSelectorItemDragStarted(OverviewItem* item) {
@@ -787,7 +798,7 @@
   const bool show_backdrop =
       GetWindowDimensionsType() != OverviewGridWindowFillMode::kNormal;
   overview_item_view_->SetBackdropVisibility(show_backdrop);
-  UpdateCannotSnapWarningVisibility();
+  UpdateCannotSnapWarningVisibility(/*animate=*/true);
 }
 
 void OverviewItem::StopWidgetAnimation() {
diff --git a/ash/wm/overview/overview_item.h b/ash/wm/overview/overview_item.h
index ec1e2b6..7e7eec2 100644
--- a/ash/wm/overview/overview_item.h
+++ b/ash/wm/overview/overview_item.h
@@ -123,11 +123,11 @@
 
   // Shows the cannot snap warning if currently in splitview, and the associated
   // window cannot be snapped.
-  void UpdateCannotSnapWarningVisibility();
+  void UpdateCannotSnapWarningVisibility(bool animate);
 
   // Hides the cannot snap warning (if it was showing) until the next call to
   // |UpdateCannotSnapWarningVisibility|.
-  void HideCannotSnapWarning();
+  void HideCannotSnapWarning(bool animate);
 
   // Called when a OverviewItem on any grid is dragged. Hides the close button
   // when a drag is started, and reshows it when a drag is finished.
diff --git a/ash/wm/overview/overview_window_drag_controller.cc b/ash/wm/overview/overview_window_drag_controller.cc
index c2b3bec4..ed31c5c 100644
--- a/ash/wm/overview/overview_window_drag_controller.cc
+++ b/ash/wm/overview/overview_window_drag_controller.cc
@@ -320,7 +320,7 @@
             /*is_dragging=*/true,
             SplitViewDragIndicators::WindowDraggingState::kFromOverview,
             SplitViewController::NONE));
-    item_->HideCannotSnapWarning();
+    item_->HideCannotSnapWarning(/*animate=*/true);
 
     // Update the split view divider bar status if necessary. If splitview is
     // active when dragging the overview window, the split divider bar should be
@@ -428,7 +428,7 @@
       SplitViewController::Get(Shell::GetPrimaryRootWindow())
           ->OnWindowDragCanceled();
       overview_session_->ResetSplitViewDragIndicatorsWindowDraggingStates();
-      item_->UpdateCannotSnapWarningVisibility();
+      item_->UpdateCannotSnapWarningVisibility(/*animate=*/true);
     }
   }
   overview_session_->PositionWindows(/*animate=*/true);
@@ -649,7 +649,7 @@
     // orientation was changed.
     UpdateDragIndicatorsAndOverviewGrid(location_in_screen);
     overview_session_->ResetSplitViewDragIndicatorsWindowDraggingStates();
-    item_->UpdateCannotSnapWarningVisibility();
+    item_->UpdateCannotSnapWarningVisibility(/*animate=*/true);
   }
 
   // This function has multiple exit positions, at each we must update the desks
diff --git a/base/files/file_util.h b/base/files/file_util.h
index 19c9f1d..2d6122ae 100644
--- a/base/files/file_util.h
+++ b/base/files/file_util.h
@@ -578,7 +578,9 @@
 // Returns true if it was able to set it in the close-on-exec mode, otherwise
 // false.
 BASE_EXPORT bool SetCloseOnExec(int fd);
+#endif  // BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 
+#if BUILDFLAG(IS_MAC)
 // Test that |path| can only be changed by a given user and members of
 // a given set of groups.
 // Specifically, test that all parts of |path| under (and including) |base|:
@@ -594,9 +596,7 @@
                                             const base::FilePath& path,
                                             uid_t owner_uid,
                                             const std::set<gid_t>& group_gids);
-#endif  // BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 
-#if BUILDFLAG(IS_MAC)
 // Is |path| writable only by a user with administrator privileges?
 // This function uses Mac OS conventions.  The super user is assumed to have
 // uid 0, and the administrator group is assumed to be named "admin".
diff --git a/base/files/file_util_posix.cc b/base/files/file_util_posix.cc
index ee25c71..7577b45 100644
--- a/base/files/file_util_posix.cc
+++ b/base/files/file_util_posix.cc
@@ -76,6 +76,7 @@
 
 namespace {
 
+#if BUILDFLAG(IS_MAC)
 // Helper for VerifyPathControlledByUser.
 bool VerifySpecificPathControlledByUser(const FilePath& path,
                                         uid_t owner_uid,
@@ -111,6 +112,7 @@
 
   return true;
 }
+#endif
 
 base::FilePath GetTempTemplate() {
   return FormatTemporaryFileName("XXXXXX");
@@ -1012,6 +1014,7 @@
   return chdir(path.value().c_str()) == 0;
 }
 
+#if BUILDFLAG(IS_MAC)
 bool VerifyPathControlledByUser(const FilePath& base,
                                 const FilePath& path,
                                 uid_t owner_uid,
@@ -1047,7 +1050,6 @@
   return true;
 }
 
-#if BUILDFLAG(IS_MAC)
 bool VerifyPathControlledByAdmin(const FilePath& path) {
   const unsigned kRootUid = 0;
   const FilePath kFileSystemRoot("/");
diff --git a/base/files/file_util_unittest.cc b/base/files/file_util_unittest.cc
index a57f38d..c66e348e 100644
--- a/base/files/file_util_unittest.cc
+++ b/base/files/file_util_unittest.cc
@@ -214,9 +214,7 @@
 
 #endif
 
-// Fuchsia doesn't support file permissions.
-#if !BUILDFLAG(IS_FUCHSIA)
-#if BUILDFLAG(IS_POSIX)
+#if BUILDFLAG(IS_MAC)
 // Provide a simple way to change the permissions bits on |path| in tests.
 // ASSERT failures will return, but not stop the test.  Caller should wrap
 // calls to this function in ASSERT_NO_FATAL_FAILURE().
@@ -232,8 +230,10 @@
   mode &= ~mode_bits_to_clear;
   ASSERT_TRUE(SetPosixFilePermissions(path, mode));
 }
-#endif  // BUILDFLAG(IS_POSIX)
+#endif  // BUILDFLAG(IS_MAC)
 
+// Fuchsia doesn't support file permissions.
+#if !BUILDFLAG(IS_FUCHSIA)
 // Sets the source file to read-only.
 void SetReadOnly(const FilePath& path, bool read_only) {
 #if BUILDFLAG(IS_WIN)
@@ -3760,7 +3760,7 @@
 
 #endif
 
-#if BUILDFLAG(IS_POSIX)
+#if BUILDFLAG(IS_MAC)
 
 // Testing VerifyPathControlledByAdmin() is hard, because there is no
 // way a test can make a file owned by root, or change file paths
@@ -4044,7 +4044,7 @@
   EXPECT_TRUE(VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
 }
 
-#endif  // BUILDFLAG(IS_POSIX)
+#endif  // BUILDFLAG(IS_MAC)
 
 // Flaky test: crbug/1054637
 #if BUILDFLAG(IS_ANDROID)
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index ca9ed58..10e41d6 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-7.20220314.2.1
+7.20220314.3.3
diff --git a/build/toolchain/apple/toolchain.gni b/build/toolchain/apple/toolchain.gni
index e837b981..7221c80 100644
--- a/build/toolchain/apple/toolchain.gni
+++ b/build/toolchain/apple/toolchain.gni
@@ -230,7 +230,7 @@
       # However, it seems -fuse-ld=lld causes difficulties.
 
       tool("rust_staticlib") {
-        rust_outfile = "{{target_out_dir}}/{{crate_name}}.a"
+        rust_outfile = "{{output_dir}}/{{target_output_name}}.a"
         depfile = "{{output}}.d"
         rspfile = "$rust_outfile.rsp"
         rspfile_content = "{{rustdeps}} {{externs}}"
@@ -241,7 +241,7 @@
       }
 
       tool("rust_rlib") {
-        rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.rlib"
+        rust_outfile = "{{output_dir}}/lib{{target_output_name}}.rlib"
         depfile = "{{output}}.d"
 
         # Do not use rsp files in this (common) case because they occupy the
@@ -255,7 +255,7 @@
 
       if (toolchain_args.rustc_can_link) {
         tool("rust_bin") {
-          rust_outfile = "{{root_out_dir}}/{{crate_name}}"
+          rust_outfile = "{{root_out_dir}}/{{target_output_name}}"
           depfile = "{{output}}.d"
           rspfile = "$rust_outfile.rsp"
           rspfile_content = "{{rustdeps}} {{externs}}"
@@ -266,7 +266,7 @@
         }
 
         tool("rust_cdylib") {
-          rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.dylib"
+          rust_outfile = "{{output_dir}}/lib{{target_output_name}}.dylib"
           depfile = "{{output}}.d"
           rspfile = "$rust_outfile.rsp"
           rspfile_content = "{{rustdeps}} {{externs}}"
@@ -277,7 +277,7 @@
         }
 
         tool("rust_macro") {
-          rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.dylib"
+          rust_outfile = "{{output_dir}}/lib{{target_output_name}}.dylib"
           depfile = "{{output}}.d"
           rspfile = "$rust_outfile.rsp"
           rspfile_content = "{{rustdeps}} {{externs}}"
diff --git a/build/toolchain/win/BUILD.gn b/build/toolchain/win/BUILD.gn
index 96a914bb..254855d 100644
--- a/build/toolchain/win/BUILD.gn
+++ b/build/toolchain/win/BUILD.gn
@@ -307,7 +307,7 @@
       rustc = rebase_path("${rust_sysroot}/bin/rustc", root_build_dir)
       rust_sysroot_relative_to_out = rebase_path(rust_sysroot, root_out_dir)
       tool("rust_staticlib") {
-        rust_outfile = "{{target_out_dir}}/{{crate_name}}.lib"
+        rust_outfile = "{{output_dir}}/{{target_output_name}}.lib"
         depfile = "{{output}}.d"
         rspfile = "$rust_outfile.rsp"
         rspfile_content = "{{rustdeps}} {{externs}}"
@@ -318,8 +318,9 @@
       }
 
       tool("rust_rlib") {
-        rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.rlib"
+        rust_outfile = "{{output_dir}}/lib{{target_output_name}}.rlib"
         depfile = "{{output}}.d"
+
         # Do not use rsp files in this (common) case because they occupy the
         # ninja main thread, and {{rlibs}} have shorter command lines than
         # fully linked targets.
@@ -331,7 +332,7 @@
 
       if (toolchain_args.rustc_can_link) {
         tool("rust_bin") {
-          rust_outfile = "{{root_out_dir}}/{{crate_name}}.exe"
+          rust_outfile = "{{root_out_dir}}/{{target_output_name}}.exe"
           depfile = "{{output}}.d"
           rspfile = "$rust_outfile.rsp"
           rspfile_content = "{{rustdeps}} {{externs}}"
@@ -342,7 +343,7 @@
         }
 
         tool("rust_cdylib") {
-          rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.dll"
+          rust_outfile = "{{output_dir}}/lib{{target_output_name}}.dll"
           depfile = "{{output}}.d"
           rspfile = "$rust_outfile.rsp"
           rspfile_content = "{{rustdeps}} {{externs}}"
@@ -353,7 +354,7 @@
         }
 
         tool("rust_macro") {
-          rust_outfile = "{{target_out_dir}}/{{crate_name}}.dll"
+          rust_outfile = "{{output_dir}}/{{target_output_name}}.dll"
           depfile = "{{output}}.d"
           rspfile = "$rust_outfile.rsp"
           rspfile_content = "{{rustdeps}} {{externs}}"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
index d2e065e..2eb809f0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -103,7 +103,6 @@
                 add(ChromeFeatureList.PAINT_PREVIEW_DEMO);
                 add(ChromeFeatureList.PAINT_PREVIEW_SHOW_ON_STARTUP);
                 add(ChromeFeatureList.READ_LATER);
-                add(ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP);
                 add(ChromeFeatureList.START_SURFACE_ANDROID);
                 add(ChromeFeatureList.STORE_HOURS);
                 add(ChromeFeatureList.SWAP_PIXEL_FORMAT_TO_FIX_CONVERT_FROM_TRANSLUCENT);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index 460057954..f3635215 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -248,7 +248,7 @@
         }
         mNoSearchLogoSpacer = findViewById(R.id.no_search_logo_spacer);
 
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP)) {
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP_ANDROID)) {
             // If SHOW_SCROLLABLE_MV_ON_NTP is true, TileGroup and other logic will be handled by
             // MostVisitedListCoordinator.
             initializeMostVisitedListCoordinator(
@@ -392,7 +392,7 @@
     private void initializeMostVisitedListCoordinator(
             ActivityLifecycleDispatcher activityLifecycleDispatcher,
             TileGroup.Delegate tileGroupDelegate, TouchEnabledDelegate touchEnabledDelegate) {
-        assert ChromeFeatureList.isEnabled(ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP);
+        assert ChromeFeatureList.isEnabled(ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP_ANDROID);
         assert mMvTilesLayout != null;
         mMostVisitedListCoordinator = new MostVisitedListCoordinator(mActivity,
                 activityLifecycleDispatcher, mMvTilesLayout.findViewById(R.id.mv_tiles_layout),
@@ -470,7 +470,7 @@
     private void insertSiteSectionView() {
         int insertionPoint = indexOfChild(mMiddleSpacer) + 1;
 
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP)) {
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP_ANDROID)) {
             setClipToPadding(false);
             mMvTilesLayout = (ViewGroup) LayoutInflater.from(this.getContext())
                                      .inflate(R.layout.mv_tiles_layout, this, false);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
index a6b1a8e..4c8a51ef 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
@@ -365,7 +365,7 @@
     @Test
     @SmallTest
     @Feature({"NewTabPage", "FeedNewTabPage"})
-    @Features.EnableFeatures({ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP})
+    @Features.EnableFeatures({ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP_ANDROID})
     public void testClickMostVisitedItemOnMVTCarousel() {
         Assert.assertNotNull(mMVTCarouselLayout);
         ChromeTabUtils.waitForTabPageLoaded(
@@ -385,7 +385,7 @@
     @Test
     @SmallTest
     @Feature({"NewTabPage", "FeedNewTabPage"})
-    @Features.EnableFeatures({ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP})
+    @Features.EnableFeatures({ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP_ANDROID})
     public void testOpenMostVisitedItemInNewTabOnMVTCarousel() throws ExecutionException {
         Assert.assertNotNull(mMVTCarouselLayout);
         ChromeTabUtils.invokeContextMenuAndOpenInANewTab(mActivityTestRule,
@@ -400,7 +400,7 @@
     @Test
     @SmallTest
     @Feature({"NewTabPage", "FeedNewTabPage"})
-    @Features.EnableFeatures({ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP})
+    @Features.EnableFeatures({ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP_ANDROID})
     public void testOpenMostVisitedItemInIncognitoTabOnMVTCarousel() throws ExecutionException {
         Assert.assertNotNull(mMVTCarouselLayout);
         ChromeTabUtils.invokeContextMenuAndOpenInANewTab(mActivityTestRule,
@@ -416,7 +416,7 @@
     @SmallTest
     @Feature({"NewTabPage", "FeedNewTabPage"})
     @FlakyTest(message = "crbug.com/1075804")
-    @Features.EnableFeatures({ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP})
+    @Features.EnableFeatures({ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP_ANDROID})
     public void testRemoveMostVisitedItemOnMVTCarousel() throws ExecutionException {
         Assert.assertNotNull(mMVTCarouselLayout);
         SiteSuggestion testSite = mSiteSuggestions.get(0);
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index b9e070d..f28e0102 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -5556,6 +5556,11 @@
      flag_descriptions::kPrintWithReducedRasterizationDescription, kOsWin,
      FEATURE_VALUE_TYPE(printing::features::kPrintWithReducedRasterization)},
 
+    {"read-printer-capabilities-with-xps",
+     flag_descriptions::kReadPrinterCapabilitiesWithXpsName,
+     flag_descriptions::kReadPrinterCapabilitiesWithXpsDescription, kOsWin,
+     FEATURE_VALUE_TYPE(printing::features::kReadPrinterCapabilitiesWithXps)},
+
     {"use-xps-for-printing", flag_descriptions::kUseXpsForPrintingName,
      flag_descriptions::kUseXpsForPrintingDescription, kOsWin,
      FEATURE_VALUE_TYPE(printing::features::kUseXpsForPrinting)},
@@ -5585,6 +5590,11 @@
      flag_descriptions::kInstantStartDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kInstantStart)},
 
+    {"enable-show-scrollable-mvt-on-ntp",
+     flag_descriptions::kShowScrollableMVTOnNTPAndroidName,
+     flag_descriptions::kShowScrollableMVTOnNTPAndroidDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kShowScrollableMVTOnNTPAndroid)},
+
     {"enable-close-tab-suggestions",
      flag_descriptions::kCloseTabSuggestionsName,
      flag_descriptions::kCloseTabSuggestionsDescription, kOsAndroid,
@@ -8462,11 +8472,12 @@
     return !ash::features::IsWallpaperWebUIEnabled();
 
   // Only show Google Photos wallpaper integration flag if:
-  // * channel is one of Dev/Canary/Unknown, and
+  // * channel is one of Beta/Dev/Canary/Unknown, and
   // * wallpaper Web UI flag is enabled.
   if (!strcmp(kWallpaperGooglePhotosIntegrationInternalName,
               entry.internal_name)) {
-    return (channel != version_info::Channel::DEV &&
+    return (channel != version_info::Channel::BETA &&
+            channel != version_info::Channel::DEV &&
             channel != version_info::Channel::CANARY &&
             channel != version_info::Channel::UNKNOWN) ||
            !ash::features::IsWallpaperWebUIEnabled();
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
index 3e8df92..1691ac6c 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.cc
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
@@ -204,6 +204,20 @@
   autocomplete_controller_->Start(input_);
 }
 
+void AutocompleteControllerAndroid::StartPrefetch(JNIEnv* env) {
+  AutocompleteInput autocomplete_input(
+      u"", metrics::OmniboxEventProto::NTP_ZPS_PREFETCH,
+      ChromeAutocompleteSchemeClassifier(profile_));
+  autocomplete_input.set_focus_type(OmniboxFocusType::ON_FOCUS);
+
+  if (base::FeatureList::IsEnabled(omnibox::kZeroSuggestPrefetching)) {
+    autocomplete_controller_->StartPrefetch(autocomplete_input);
+  } else {
+    // ZeroSuggestPrefetcher deletes itself after it's done prefetching.
+    new ZeroSuggestPrefetcher(profile_);
+  }
+}
+
 ScopedJavaLocalRef<jobject> AutocompleteControllerAndroid::Classify(
     JNIEnv* env,
     const JavaParamRef<jstring>& j_text,
@@ -535,12 +549,3 @@
     return {};
   return native_bridge->GetJavaObject();
 }
-
-static void JNI_AutocompleteController_PrefetchZeroSuggestResults(JNIEnv* env) {
-  Profile* profile = ProfileManager::GetActiveUserProfile();
-  if (!profile)
-    return;
-
-  // ZeroSuggestPrefetcher deletes itself after it's done prefetching.
-  new ZeroSuggestPrefetcher(profile);
-}
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.h b/chrome/browser/android/omnibox/autocomplete_controller_android.h
index e150fb3..2e8f6b2 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.h
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.h
@@ -48,6 +48,7 @@
              bool prefer_keyword,
              bool allow_exact_keyword_match,
              bool want_asynchronous_matches);
+  void StartPrefetch(JNIEnv* env);
   base::android::ScopedJavaLocalRef<jobject> Classify(
       JNIEnv* env,
       const base::android::JavaParamRef<jstring>& j_text,
diff --git a/chrome/browser/apps/intent_helper/chromeos_intent_picker_helpers.cc b/chrome/browser/apps/intent_helper/chromeos_intent_picker_helpers.cc
index bfdf4725..785a8d6 100644
--- a/chrome/browser/apps/intent_helper/chromeos_intent_picker_helpers.cc
+++ b/chrome/browser/apps/intent_helper/chromeos_intent_picker_helpers.cc
@@ -182,7 +182,7 @@
   if (entry_type == PickerEntryType::kUnknown &&
       close_reason == IntentPickerCloseReason::DIALOG_DEACTIVATED &&
       ui_auto_display_service) {
-    ui_auto_display_service->IncrementCounter(url);
+    ui_auto_display_service->IncrementPickerUICounter(url);
   }
 
   if (should_persist) {
@@ -207,6 +207,10 @@
   Profile* profile =
       Profile::FromBrowserContext(web_contents->GetBrowserContext());
 
+  if (base::FeatureList::IsEnabled(features::kLinkCapturingUiUpdate)) {
+    IntentPickerAutoDisplayService::Get(profile)->ResetIntentChipCounter(url);
+  }
+
   auto* proxy = AppServiceProxyFactory::GetForProfile(profile);
 
   if (app_type == PickerEntryType::kWeb) {
diff --git a/chrome/browser/apps/intent_helper/intent_picker_auto_display_service.cc b/chrome/browser/apps/intent_helper/intent_picker_auto_display_service.cc
index da89a47..4a6111c 100644
--- a/chrome/browser/apps/intent_helper/intent_picker_auto_display_service.cc
+++ b/chrome/browser/apps/intent_helper/intent_picker_auto_display_service.cc
@@ -59,7 +59,7 @@
   return pref_dict.FindIntKey(kAutoDisplayKey).value_or(0) < kDismissThreshold;
 }
 
-void IntentPickerAutoDisplayService::IncrementCounter(const GURL& url) {
+void IntentPickerAutoDisplayService::IncrementPickerUICounter(const GURL& url) {
   auto* settings_map = HostContentSettingsMapFactory::GetForProfile(profile_);
   base::Value pref_dict = GetAutoDisplayDictForSettings(settings_map, url);
 
@@ -91,6 +91,17 @@
   return ChipState::kExpanded;
 }
 
+void IntentPickerAutoDisplayService::ResetIntentChipCounter(const GURL& url) {
+  auto* settings_map = HostContentSettingsMapFactory::GetForProfile(profile_);
+  base::Value pref_dict = GetAutoDisplayDictForSettings(settings_map, url);
+
+  pref_dict.SetIntKey(kIntentChipCountKey, 0);
+
+  settings_map->SetWebsiteSettingDefaultScope(
+      url, url, ContentSettingsType::INTENT_PICKER_DISPLAY,
+      std::move(pref_dict));
+}
+
 IntentPickerAutoDisplayService::Platform
 IntentPickerAutoDisplayService::GetLastUsedPlatformForTablets(const GURL& url) {
   base::Value pref_dict = GetAutoDisplayDictForSettings(
diff --git a/chrome/browser/apps/intent_helper/intent_picker_auto_display_service.h b/chrome/browser/apps/intent_helper/intent_picker_auto_display_service.h
index 9f5bcbf..59f227bd 100644
--- a/chrome/browser/apps/intent_helper/intent_picker_auto_display_service.h
+++ b/chrome/browser/apps/intent_helper/intent_picker_auto_display_service.h
@@ -36,13 +36,18 @@
   bool ShouldAutoDisplayUi(const GURL& url);
 
   // Keep track of the |url| repetitions.
-  void IncrementCounter(const GURL& url);
+  void IncrementPickerUICounter(const GURL& url);
 
   // Returns a ChipState indicating whether the Intent Chip should be shown as
   // expanded or collapsed for a given URL. Increments an internal counter to
   // track the number of times the chip has been shown for that URL.
   ChipState GetChipStateAndIncrementCounter(const GURL& url);
 
+  // Reset the intent chip counter to 0. When this is called, it allows the
+  // GetChipStateAndIncrementCounter function will return an Expanded ChipState
+  // another 3 times for that |url|.
+  void ResetIntentChipCounter(const GURL& url);
+
   // Returns the last platform selected by the user to handle |url|.
   // If it has not been checked then it will return |Platform::kNone|
   // for devices of tablet form factor.
diff --git a/chrome/browser/apps/intent_helper/intent_picker_auto_display_service_unittest.cc b/chrome/browser/apps/intent_helper/intent_picker_auto_display_service_unittest.cc
index c04f3e0..16f7b5d 100644
--- a/chrome/browser/apps/intent_helper/intent_picker_auto_display_service_unittest.cc
+++ b/chrome/browser/apps/intent_helper/intent_picker_auto_display_service_unittest.cc
@@ -98,9 +98,9 @@
       IntentPickerAutoDisplayService::Get(&profile);
 
   EXPECT_TRUE(service->ShouldAutoDisplayUi(url1));
-  service->IncrementCounter(url1);
+  service->IncrementPickerUICounter(url1);
   EXPECT_TRUE(service->ShouldAutoDisplayUi(url1));
-  service->IncrementCounter(url1);
+  service->IncrementPickerUICounter(url1);
   EXPECT_FALSE(service->ShouldAutoDisplayUi(url1));
 
   // Should return false for a different URL on the same host.
@@ -109,6 +109,34 @@
   EXPECT_TRUE(service->ShouldAutoDisplayUi(url3));
 }
 
+TEST_F(IntentPickerAutoDisplayServiceTest, ResetIntentChipCounter) {
+  GURL url("https://www.google.com/abcde");
+
+  TestingProfile profile;
+  IntentPickerAutoDisplayService* service =
+      IntentPickerAutoDisplayService::Get(&profile);
+
+  // Increment counter a few times.
+  EXPECT_EQ(service->GetChipStateAndIncrementCounter(url),
+            IntentPickerAutoDisplayService::ChipState::kExpanded);
+  EXPECT_EQ(service->GetChipStateAndIncrementCounter(url),
+            IntentPickerAutoDisplayService::ChipState::kExpanded);
+
+  // Reset the count back to 0.
+  service->ResetIntentChipCounter(url);
+
+  // Since the counter is reset, the chip is expanded another 3 times
+  // before collapsing.
+  EXPECT_EQ(service->GetChipStateAndIncrementCounter(url),
+            IntentPickerAutoDisplayService::ChipState::kExpanded);
+  EXPECT_EQ(service->GetChipStateAndIncrementCounter(url),
+            IntentPickerAutoDisplayService::ChipState::kExpanded);
+  EXPECT_EQ(service->GetChipStateAndIncrementCounter(url),
+            IntentPickerAutoDisplayService::ChipState::kExpanded);
+  EXPECT_EQ(service->GetChipStateAndIncrementCounter(url),
+            IntentPickerAutoDisplayService::ChipState::kCollapsed);
+}
+
 // Checks that calling GetChipStateAndIncrementCounter tracks views per-URL
 // and collapses the chip after a fixed number of views.
 TEST_F(IntentPickerAutoDisplayServiceTest, GetChipStateAndIncrementCounter) {
diff --git a/chrome/browser/apps/intent_helper/intent_picker_helpers.cc b/chrome/browser/apps/intent_helper/intent_picker_helpers.cc
index 155bfbbb..e1f8af2 100644
--- a/chrome/browser/apps/intent_helper/intent_picker_helpers.cc
+++ b/chrome/browser/apps/intent_helper/intent_picker_helpers.cc
@@ -67,6 +67,12 @@
 #if BUILDFLAG(IS_CHROMEOS)
   LaunchAppFromIntentPickerChromeOs(web_contents, url, launch_name, app_type);
 #else
+  if (base::FeatureList::IsEnabled(features::kLinkCapturingUiUpdate)) {
+    Profile* profile =
+        Profile::FromBrowserContext(web_contents->GetBrowserContext());
+    IntentPickerAutoDisplayService::Get(profile)->ResetIntentChipCounter(url);
+  }
+
   switch (app_type) {
     case PickerEntryType::kWeb:
       web_app::ReparentWebContentsIntoAppBrowser(web_contents, launch_name);
@@ -109,7 +115,7 @@
     // We reach here if the picker was closed without an app being chosen, e.g.
     // due to the tab being closed. Keep count of this scenario so we can stop
     // the UI from showing after 2+ dismissals.
-    ui_auto_display_service->IncrementCounter(url);
+    ui_auto_display_service->IncrementPickerUICounter(url);
   }
 #endif  // BUILDFLAG(IS_CHROMEOS)
 }
diff --git a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader_unittest.cc b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader_unittest.cc
index d0f87c4..3649ac1 100644
--- a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader_unittest.cc
+++ b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader_unittest.cc
@@ -51,6 +51,11 @@
 
 constexpr char kMimeType[] = "application/octet-stream";
 
+// Non-zero read offsets used in unit tests.
+constexpr size_t kOffset5 = 5;
+constexpr size_t kOffset10 = 10;
+constexpr size_t kOffset15 = 15;
+
 // Reads data from the reader to fill the buffer.
 bool ReadData(ArcContentFileSystemFileStreamReader* reader,
               net::IOBufferWithSize* buffer) {
@@ -139,18 +144,59 @@
 }
 
 TEST_F(ArcContentFileSystemFileStreamReaderTest, ReadRegularFileWithOffset) {
-  constexpr size_t kOffset = 10;
   auto buffer =
-      base::MakeRefCounted<net::IOBufferWithSize>(strlen(kData) - kOffset);
+      base::MakeRefCounted<net::IOBufferWithSize>(strlen(kData) - kOffset10);
   {
-    ArcContentFileSystemFileStreamReader reader(GURL(kArcUrlFile), kOffset);
+    ArcContentFileSystemFileStreamReader reader(GURL(kArcUrlFile), kOffset10);
     EXPECT_TRUE(ReadData(&reader, buffer.get()));
   }
   base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(base::StringPiece(kData + kOffset, strlen(kData) - kOffset),
+  EXPECT_EQ(base::StringPiece(kData + kOffset10, strlen(kData) - kOffset10),
             base::StringPiece(buffer->data(), buffer->size()));
 }
 
+TEST_F(ArcContentFileSystemFileStreamReaderTest, ReadRegularFileWithOffsets) {
+  auto buffer1 =
+      base::MakeRefCounted<net::IOBufferWithSize>(kOffset15 - kOffset5);
+  auto buffer2 = base::MakeRefCounted<net::IOBufferWithSize>(
+      strlen(kData) - kOffset5 - kOffset15);
+  {
+    ArcContentFileSystemFileStreamReader reader1(GURL(kArcUrlFile), kOffset5);
+    EXPECT_TRUE(ReadData(&reader1, buffer1.get()));
+    ArcContentFileSystemFileStreamReader reader2(GURL(kArcUrlFile), kOffset15);
+    EXPECT_TRUE(ReadData(&reader2, buffer2.get()));
+  }
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(base::StringPiece(kData + kOffset5, kOffset15 - kOffset5),
+            base::StringPiece(buffer1->data(), buffer1->size()));
+  EXPECT_EQ(base::StringPiece(kData + kOffset15,
+                              strlen(kData) - kOffset5 - kOffset15),
+            base::StringPiece(buffer2->data(), buffer2->size()));
+}
+
+TEST_F(ArcContentFileSystemFileStreamReaderTest,
+       ReadRegularFileWithOffsets_CloseWithWait) {
+  auto buffer1 =
+      base::MakeRefCounted<net::IOBufferWithSize>(kOffset15 - kOffset5);
+  auto buffer2 = base::MakeRefCounted<net::IOBufferWithSize>(
+      strlen(kData) - kOffset5 - kOffset15);
+  {
+    ArcContentFileSystemFileStreamReader reader1(GURL(kArcUrlFile), kOffset5);
+    EXPECT_TRUE(ReadData(&reader1, buffer1.get()));
+  }
+  base::RunLoop().RunUntilIdle();
+  {
+    ArcContentFileSystemFileStreamReader reader2(GURL(kArcUrlFile), kOffset15);
+    EXPECT_TRUE(ReadData(&reader2, buffer2.get()));
+  }
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(base::StringPiece(kData + kOffset5, kOffset15 - kOffset5),
+            base::StringPiece(buffer1->data(), buffer1->size()));
+  EXPECT_EQ(base::StringPiece(kData + kOffset15,
+                              strlen(kData) - kOffset5 - kOffset15),
+            base::StringPiece(buffer2->data(), buffer2->size()));
+}
+
 TEST_F(ArcContentFileSystemFileStreamReaderTest, ReadPipe) {
   auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(strlen(kData));
   {
@@ -164,18 +210,58 @@
 }
 
 TEST_F(ArcContentFileSystemFileStreamReaderTest, ReadPipeWithOffset) {
-  constexpr size_t kOffset = 10;
   auto buffer =
-      base::MakeRefCounted<net::IOBufferWithSize>(strlen(kData) - kOffset);
+      base::MakeRefCounted<net::IOBufferWithSize>(strlen(kData) - kOffset10);
   {
-    ArcContentFileSystemFileStreamReader reader(GURL(kArcUrlPipe), kOffset);
+    ArcContentFileSystemFileStreamReader reader(GURL(kArcUrlPipe), kOffset10);
     EXPECT_TRUE(ReadData(&reader, buffer.get()));
   }
   base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(base::StringPiece(kData + kOffset, strlen(kData) - kOffset),
+  EXPECT_EQ(base::StringPiece(kData + kOffset10, strlen(kData) - kOffset10),
             base::StringPiece(buffer->data(), buffer->size()));
 }
 
+TEST_F(ArcContentFileSystemFileStreamReaderTest, ReadPipeWithOffsets) {
+  auto buffer1 =
+      base::MakeRefCounted<net::IOBufferWithSize>(kOffset15 - kOffset5);
+  auto buffer2 = base::MakeRefCounted<net::IOBufferWithSize>(
+      strlen(kData) - kOffset5 - kOffset15);
+  {
+    ArcContentFileSystemFileStreamReader reader1(GURL(kArcUrlPipe), kOffset5);
+    EXPECT_TRUE(ReadData(&reader1, buffer1.get()));
+    ArcContentFileSystemFileStreamReader reader2(GURL(kArcUrlPipe), kOffset15);
+    EXPECT_TRUE(ReadData(&reader2, buffer2.get()));
+  }
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(base::StringPiece(kData + kOffset5, kOffset15 - kOffset5),
+            base::StringPiece(buffer1->data(), buffer1->size()));
+  EXPECT_EQ(base::StringPiece(kData + kOffset15,
+                              strlen(kData) - kOffset5 - kOffset15),
+            base::StringPiece(buffer2->data(), buffer2->size()));
+}
+
+TEST_F(ArcContentFileSystemFileStreamReaderTest,
+       ReadPipeWithOffsets_CloseWithoutWait) {
+  auto buffer1 =
+      base::MakeRefCounted<net::IOBufferWithSize>(kOffset15 - kOffset5);
+  auto buffer2 = base::MakeRefCounted<net::IOBufferWithSize>(
+      strlen(kData) - kOffset5 - kOffset15);
+  {
+    ArcContentFileSystemFileStreamReader reader1(GURL(kArcUrlPipe), kOffset5);
+    EXPECT_TRUE(ReadData(&reader1, buffer1.get()));
+  }
+  {
+    ArcContentFileSystemFileStreamReader reader2(GURL(kArcUrlPipe), kOffset15);
+    EXPECT_TRUE(ReadData(&reader2, buffer2.get()));
+  }
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(base::StringPiece(kData + kOffset5, kOffset15 - kOffset5),
+            base::StringPiece(buffer1->data(), buffer1->size()));
+  EXPECT_EQ(base::StringPiece(kData + kOffset15,
+                              strlen(kData) - kOffset5 - kOffset15),
+            base::StringPiece(buffer2->data(), buffer2->size()));
+}
+
 TEST_F(ArcContentFileSystemFileStreamReaderTest, GetLength) {
   {
     ArcContentFileSystemFileStreamReader reader(GURL(kArcUrlFile),
diff --git a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_writer.h b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_writer.h
index 18b85e3..e79aa585 100644
--- a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_writer.h
+++ b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_writer.h
@@ -44,7 +44,7 @@
 
   // storage::FileStreamWriter override:
   int Write(net::IOBuffer* buffer,
-            int bufffer_length,
+            int buffer_length,
             net::CompletionOnceCallback callback) override;
   int Cancel(net::CompletionOnceCallback callback) override;
   int Flush(net::CompletionOnceCallback callback) override;
diff --git a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_writer_unittest.cc b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_writer_unittest.cc
index 805d2e4..ab71a78 100644
--- a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_writer_unittest.cc
+++ b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_writer_unittest.cc
@@ -126,9 +126,9 @@
 
 TEST_F(ArcContentFileSystemFileStreamWriterTest, Write) {
   std::string url =
-      CreateFileWithContent("file_a", std::string(), true /* seekable */);
+      CreateFileWithContent("file_a", std::string(), /* seekable */ true);
   {
-    ArcContentFileSystemFileStreamWriter writer(GURL(url), 0);
+    ArcContentFileSystemFileStreamWriter writer(GURL(url), /* offset */ 0);
     EXPECT_EQ(net::OK, WriteStringToWriter(&writer, "foo"));
     EXPECT_EQ(net::OK, WriteStringToWriter(&writer, "bar"));
   }
@@ -138,9 +138,9 @@
 
 TEST_F(ArcContentFileSystemFileStreamWriterTest, WriteMiddle) {
   std::string url =
-      CreateFileWithContent("file_a", "foobar", true /* seekable */);
+      CreateFileWithContent("file_a", "foobar", /* seekable */ true);
   {
-    ArcContentFileSystemFileStreamWriter writer(GURL(url), 2);
+    ArcContentFileSystemFileStreamWriter writer(GURL(url), /* offset */ 2);
     EXPECT_EQ(net::OK, WriteStringToWriter(&writer, "xxx"));
   }
   base::RunLoop().RunUntilIdle();
@@ -149,19 +149,95 @@
 
 TEST_F(ArcContentFileSystemFileStreamWriterTest, WriteEnd) {
   std::string url =
-      CreateFileWithContent("file_a", "foobar", true /* seekable */);
+      CreateFileWithContent("file_a", "foobar", /* seekable */ true);
   {
-    ArcContentFileSystemFileStreamWriter writer(GURL(url), 6);
+    ArcContentFileSystemFileStreamWriter writer(GURL(url), /* offset */ 6);
     EXPECT_EQ(net::OK, WriteStringToWriter(&writer, "xxx"));
   }
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ("foobarxxx", GetFileContent(url));
 }
 
+TEST_F(ArcContentFileSystemFileStreamWriterTest, WriteBeginningAndMiddle) {
+  std::string url =
+      CreateFileWithContent("file_ab", "foobar123456789", /* seekable */ true);
+  {
+    ArcContentFileSystemFileStreamWriter writer1(GURL(url), /* offset */ 0);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer1, "xx "));
+    ArcContentFileSystemFileStreamWriter writer2(GURL(url), /* offset */ 6);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer2, " xx "));
+  }
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ("xx bar xx 56789", GetFileContent(url));
+}
+
+TEST_F(ArcContentFileSystemFileStreamWriterTest, WriteMiddleAndEnd) {
+  std::string url =
+      CreateFileWithContent("file_bc", "foobar123456789", /* seekable */ true);
+  {
+    ArcContentFileSystemFileStreamWriter writer1(GURL(url), /* offset */ 6);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer1, " x "));
+    ArcContentFileSystemFileStreamWriter writer2(GURL(url), /* offset */ 12);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer2, " xxx"));
+  }
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ("foobar x 456 xxx", GetFileContent(url));
+}
+
+TEST_F(ArcContentFileSystemFileStreamWriterTest, WriteBeginningAndPastEnd) {
+  std::string url =
+      CreateFileWithContent("file_ac", "foobar123456789", /* seekable */ true);
+  {
+    ArcContentFileSystemFileStreamWriter writer1(GURL(url), /* offset */ 0);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer1, " xx"));
+    ArcContentFileSystemFileStreamWriter writer2(GURL(url), /* offset */ 12);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer2, "xxx "));
+  }
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(" xxbar123456xxx ", GetFileContent(url));
+}
+
+TEST_F(ArcContentFileSystemFileStreamWriterTest,
+       WriteBeginningAndEnd_CloseWithWait) {
+  std::string url =
+      CreateFileWithContent("file_ac", "foobar123456789", /* seekable */ true);
+  {
+    ArcContentFileSystemFileStreamWriter writer1(GURL(url), /* offset */ 0);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer1, "xxx"));
+  }
+  base::RunLoop().RunUntilIdle();
+  {
+    ArcContentFileSystemFileStreamWriter writer2(GURL(url), /* offset */ 12);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer2, "xxx"));
+  }
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ("xxxbar123456xxx", GetFileContent(url));
+}
+
+TEST_F(ArcContentFileSystemFileStreamWriterTest,
+       WriteBeginningMiddleAndEnd_CloseWithoutWait) {
+  std::string url =
+      CreateFileWithContent("file_abc", "foobar123456789", /* seekable */ true);
+  {
+    ArcContentFileSystemFileStreamWriter writer1(GURL(url), /* offset */ 0);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer1, "xx"));
+  }
+  {
+    ArcContentFileSystemFileStreamWriter writer2(GURL(url), /* offset */ 7);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer2, " obar"));
+  }
+  {
+    ArcContentFileSystemFileStreamWriter writer3(GURL(url), /* offset */ 13);
+    EXPECT_EQ(net::OK, WriteStringToWriter(&writer3, "xx"));
+  }
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ("xxobar1 obar7xx", GetFileContent(url));
+}
+
 TEST_F(ArcContentFileSystemFileStreamWriterTest, WriteFailForNonexistingFile) {
   std::string url = ArcUrl("file_a");
   {
-    ArcContentFileSystemFileStreamWriter writer(GURL(url), 0);
+    ArcContentFileSystemFileStreamWriter writer(GURL(url), /* offset */ 0);
     EXPECT_EQ(net::ERR_INVALID_ARGUMENT, WriteStringToWriter(&writer, "foo"));
   }
   base::RunLoop().RunUntilIdle();
@@ -169,21 +245,21 @@
 
 TEST_F(ArcContentFileSystemFileStreamWriterTest, WriteNonSeekable) {
   std::string url =
-      CreateFileWithContent("file_a", std::string(), false /* not seekable */);
+      CreateFileWithContent("file_a", std::string(), /* seekable */ false);
   {
-    ArcContentFileSystemFileStreamWriter writer(GURL(url), 0);
+    ArcContentFileSystemFileStreamWriter writer(GURL(url), /* offset */ 0);
     EXPECT_EQ(net::OK, WriteStringToWriter(&writer, "foo"));
     EXPECT_EQ(net::OK, WriteStringToWriter(&writer, "bar"));
   }
   base::RunLoop().RunUntilIdle();
-  EXPECT_EQ("foobar", GetFileContent(url, 6));
+  EXPECT_EQ("foobar", GetFileContent(url, /* bytes */ 6));
 }
 
 TEST_F(ArcContentFileSystemFileStreamWriterTest, WriteNonSeekableFailForSeek) {
   std::string url =
-      CreateFileWithContent("file_a", "foobar", false /* not seekable */);
+      CreateFileWithContent("file_a", "foobar", /* seekable */ false);
   {
-    ArcContentFileSystemFileStreamWriter writer(GURL(url), 2);
+    ArcContentFileSystemFileStreamWriter writer(GURL(url), /* offset */ 2);
     EXPECT_EQ(net::ERR_FAILED, WriteStringToWriter(&writer, "xxx"));
   }
   base::RunLoop().RunUntilIdle();
@@ -191,9 +267,9 @@
 
 TEST_F(ArcContentFileSystemFileStreamWriterTest, CancelBeforeOperation) {
   std::string url =
-      CreateFileWithContent("file_a", std::string(), true /* seekable */);
+      CreateFileWithContent("file_a", std::string(), /* seekable */ true);
   {
-    ArcContentFileSystemFileStreamWriter writer(GURL(url), 0);
+    ArcContentFileSystemFileStreamWriter writer(GURL(url), /* offset */ 0);
     // Cancel immediately fails when there's no in-flight operation.
     int cancel_result = writer.Cancel(base::BindOnce(&NeverCalled));
     EXPECT_EQ(net::ERR_UNEXPECTED, cancel_result);
@@ -203,9 +279,9 @@
 
 TEST_F(ArcContentFileSystemFileStreamWriterTest, CancelAfterFinishedOperation) {
   std::string url =
-      CreateFileWithContent("file_a", std::string(), true /* seekable */);
+      CreateFileWithContent("file_a", std::string(), /* seekable */ true);
   {
-    ArcContentFileSystemFileStreamWriter writer(GURL(url), 0);
+    ArcContentFileSystemFileStreamWriter writer(GURL(url), /* offset */ 0);
     EXPECT_EQ(net::OK, WriteStringToWriter(&writer, "foo"));
 
     // Cancel immediately fails when there's no in-flight operation.
@@ -213,14 +289,14 @@
     EXPECT_EQ(net::ERR_UNEXPECTED, cancel_result);
   }
   base::RunLoop().RunUntilIdle();
-  EXPECT_EQ("foo", GetFileContent(url, 6));
+  EXPECT_EQ("foo", GetFileContent(url, /* bytes */ 6));
 }
 
 TEST_F(ArcContentFileSystemFileStreamWriterTest, CancelWrite) {
   std::string url =
-      CreateFileWithContent("file_a", "foobar", true /* seekable */);
+      CreateFileWithContent("file_a", "foobar", /* seekable */ true);
   {
-    ArcContentFileSystemFileStreamWriter writer(GURL(url), 0);
+    ArcContentFileSystemFileStreamWriter writer(GURL(url), /* offset */ 0);
 
     scoped_refptr<net::StringIOBuffer> buffer(
         base::MakeRefCounted<net::StringIOBuffer>("xxx"));
diff --git a/chrome/browser/ash/borealis/borealis_context_manager_impl.cc b/chrome/browser/ash/borealis/borealis_context_manager_impl.cc
index e95e82e..a059fb0 100644
--- a/chrome/browser/ash/borealis/borealis_context_manager_impl.cc
+++ b/chrome/browser/ash/borealis/borealis_context_manager_impl.cc
@@ -85,19 +85,19 @@
 
 BorealisContextManagerImpl::BorealisContextManagerImpl(Profile* profile)
     : profile_(profile), weak_factory_(this) {
-  // DBusThreadManager may not be initialized in tests.
-  if (chromeos::DBusThreadManager::IsInitialized()) {
+  // ConciergeClient may not be initialized in tests.
+  if (chromeos::ConciergeClient::Get()) {
     ShutDownBorealisIfRunning();
     chromeos::ConciergeClient::Get()->AddVmObserver(this);
   }
 }
 
 BorealisContextManagerImpl::~BorealisContextManagerImpl() {
-  // Even if initialized, DBusThreadManager may be destroyed prior to
-  // BorealisService/BorealisContextManagerImpl in tests. Therefore we must not
-  // keep a pointer to the observed ConciergeClient, either directly or via
-  // ScopedObservation or similar.
-  if (chromeos::DBusThreadManager::IsInitialized()) {
+  // Even if initialized, DBusThreadManager or ConciergeClient may be destroyed
+  // prior to BorealisService/BorealisContextManagerImpl in tests. Therefore we
+  // must not keep a pointer to the observed ConciergeClient, either directly or
+  // via ScopedObservation or similar.
+  if (chromeos::ConciergeClient::Get()) {
     chromeos::ConciergeClient::Get()->RemoveVmObserver(this);
   }
 }
diff --git a/chrome/browser/ash/eche_app/eche_app_manager_factory.cc b/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
index 54c064c..e4dd75e 100644
--- a/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
+++ b/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
@@ -62,27 +62,6 @@
       ->eche_tray();
 }
 
-void CloseEche(Profile* profile) {
-  if (features::IsEcheCustomWidgetEnabled()) {
-    auto* eche_tray = GetEcheTray();
-    if (eche_tray) {
-      eche_tray->PurgeAndClose();
-    }
-    return;
-  }
-  for (auto* browser : *(BrowserList::GetInstance())) {
-    if (browser->profile() != profile)
-      continue;
-    if (!browser->app_controller() ||
-        !browser->app_controller()->system_app() ||
-        browser->app_controller()->system_app()->GetType() !=
-            web_app::SystemAppType::ECHE) {
-      continue;
-    }
-    browser->window()->Close();
-    return;
-  }
-}
 // Enumeration of possible interactions with a PhoneHub notification. Keep in
 // sync with corresponding enum in tools/metrics/histograms/enums.xml. These
 // values are persisted to logs. Entries should not be renumbered and numeric
@@ -102,7 +81,6 @@
     eche_tray->SetVisiblePreferred(true);
     if (!features::IsEcheSWAInBackgroundEnabled()) {
       eche_tray->ShowBubble();
-
     } else {
       eche_tray->InitBubble();
 
@@ -160,27 +138,13 @@
                                    params);
 }
 
-void LaunchEcheApp(Profile* profile,
-                   const absl::optional<int64_t>& notification_id,
-                   const std::string& package_name,
-                   const std::u16string& visible_name,
-                   const absl::optional<int64_t>& user_id,
-                   const gfx::Image& icon) {
-  LaunchWebApp(package_name, notification_id, visible_name, user_id, icon,
-               profile);
-  base::UmaHistogramEnumeration("Eche.NotificationClicked",
-                                NotificationInteraction::kOpenAppStreaming);
-  EcheAppManagerFactory::GetInstance()
-      ->CloseConnectionOrLaunchErrorNotifications();
-}
-
 void RelaunchLast(Profile* profile) {
   std::unique_ptr<LaunchedAppInfo> last_launched_app_info =
       EcheAppManagerFactory::GetInstance()->GetLastLaunchedAppInfo();
-  LaunchEcheApp(profile, absl::nullopt, last_launched_app_info->package_name(),
-                last_launched_app_info->visible_name(),
-                last_launched_app_info->user_id(),
-                last_launched_app_info->icon());
+  EcheAppManagerFactory::LaunchEcheApp(
+      profile, absl::nullopt, last_launched_app_info->package_name(),
+      last_launched_app_info->visible_name(), last_launched_app_info->user_id(),
+      last_launched_app_info->icon());
 }
 
 }  // namespace
@@ -244,6 +208,55 @@
   }
 }
 
+// static
+void EcheAppManagerFactory::CloseEche(Profile* profile) {
+  if (features::IsEcheCustomWidgetEnabled()) {
+    GetEcheTray()->PurgeAndClose();
+    return;
+  }
+  for (auto* browser : *(BrowserList::GetInstance())) {
+    if (browser->profile() != profile)
+      continue;
+    if (!browser->app_controller() ||
+        !browser->app_controller()->system_app() ||
+        browser->app_controller()->system_app()->GetType() !=
+            web_app::SystemAppType::ECHE) {
+      continue;
+    }
+    browser->window()->Close();
+    return;
+  }
+}
+
+// static
+void EcheAppManagerFactory::LaunchEcheApp(
+    Profile* profile,
+    const absl::optional<int64_t>& notification_id,
+    const std::string& package_name,
+    const std::u16string& visible_name,
+    const absl::optional<int64_t>& user_id,
+    const gfx::Image& icon) {
+  LaunchWebApp(package_name, notification_id, visible_name, user_id, icon,
+               profile);
+  base::UmaHistogramEnumeration("Eche.NotificationClicked",
+                                NotificationInteraction::kOpenAppStreaming);
+  EcheAppManagerFactory::GetInstance()
+      ->CloseConnectionOrLaunchErrorNotifications();
+}
+
+// static
+void EcheAppManagerFactory::OnStreamStateChanged(
+    Profile* profile,
+    const mojom::StreamStatus status) {
+  if (status == mojom::StreamStatus::kStreamStatusStarted &&
+      features::IsEcheCustomWidgetEnabled() &&
+      features::IsEcheSWAInBackgroundEnabled()) {
+    GetEcheTray()->ShowBubble();
+  } else if (status == mojom::StreamStatus::kStreamStatusStopped) {
+    CloseEche(profile);
+  }
+}
+
 EcheAppManagerFactory::EcheAppManagerFactory()
     : BrowserContextKeyedServiceFactory(
           "EcheAppManager",
@@ -299,10 +312,12 @@
       profile->GetPrefs(), GetSystemInfo(profile), phone_hub_manager,
       device_sync_client, multidevice_setup_client, secure_channel_client,
       std::move(presence_monitor_client),
-      base::BindRepeating(&LaunchEcheApp, profile),
-      base::BindRepeating(&CloseEche, profile),
+      base::BindRepeating(&EcheAppManagerFactory::LaunchEcheApp, profile),
+      base::BindRepeating(&EcheAppManagerFactory::CloseEche, profile),
       base::BindRepeating(&EcheAppManagerFactory::ShowNotification,
-                          weak_ptr_factory_.GetWeakPtr(), profile));
+                          weak_ptr_factory_.GetWeakPtr(), profile),
+      base::BindRepeating(&EcheAppManagerFactory::OnStreamStateChanged,
+                          profile));
 }
 
 std::unique_ptr<SystemInfo> EcheAppManagerFactory::GetSystemInfo(
diff --git a/chrome/browser/ash/eche_app/eche_app_manager_factory.h b/chrome/browser/ash/eche_app/eche_app_manager_factory.h
index f505952..8005689 100644
--- a/chrome/browser/ash/eche_app/eche_app_manager_factory.h
+++ b/chrome/browser/ash/eche_app/eche_app_manager_factory.h
@@ -93,6 +93,15 @@
       const absl::optional<std::u16string>& title,
       const absl::optional<std::u16string>& message,
       std::unique_ptr<LaunchAppHelper::NotificationInfo> info);
+  static void CloseEche(Profile* profile);
+  static void LaunchEcheApp(Profile* profile,
+                            const absl::optional<int64_t>& notification_id,
+                            const std::string& package_name,
+                            const std::u16string& visible_name,
+                            const absl::optional<int64_t>& user_id,
+                            const gfx::Image& icon);
+  static void OnStreamStateChanged(Profile* profile,
+                                   const mojom::StreamStatus status);
 
   void SetLastLaunchedAppInfo(
       std::unique_ptr<LaunchedAppInfo> last_launched_app_info);
diff --git a/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc b/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc
new file mode 100644
index 0000000..d1a2031c
--- /dev/null
+++ b/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc
@@ -0,0 +1,184 @@
+// 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 "chrome/browser/ash/eche_app/eche_app_manager_factory.h"
+
+#include "ash/constants/ash_features.h"
+#include "ash/system/eche/eche_tray.h"
+#include "ash/system/status_area_widget_test_helper.h"
+#include "ash/system/tray/tray_bubble_wrapper.h"
+#include "ash/test/test_ash_web_view_factory.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/test/base/chrome_ash_test_base.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+
+namespace ash {
+namespace eche_app {
+
+class EcheAppManagerFactoryTest : public ChromeAshTestBase {
+ protected:
+  EcheAppManagerFactoryTest() {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kEcheSWA, features::kEcheCustomWidget},
+        /*disabled_features=*/{features::kEcheSWAInBackground});
+    profile_manager_ = std::make_unique<TestingProfileManager>(
+        TestingBrowserProcess::GetGlobal());
+    if (profile_manager_->SetUp()) {
+      profile_ = profile_manager_->CreateTestingProfile("testing_profile");
+    }
+  }
+  ~EcheAppManagerFactoryTest() override = default;
+  EcheAppManagerFactoryTest(const EcheAppManagerFactoryTest&) = delete;
+  EcheAppManagerFactoryTest& operator=(const EcheAppManagerFactoryTest&) =
+      delete;
+
+  // AshTestBase::Test:
+  void SetUp() override {
+    DCHECK(profile_);
+    DCHECK(test_web_view_factory_.get());
+    ChromeAshTestBase::SetUp();
+    eche_tray_ =
+        ash::StatusAreaWidgetTestHelper::GetStatusAreaWidget()->eche_tray();
+  }
+
+  TestingProfile* GetProfile() { return profile_; }
+
+  EcheTray* eche_tray() { return eche_tray_; }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  std::unique_ptr<TestingProfileManager> profile_manager_;
+  TestingProfile* profile_;
+  EcheTray* eche_tray_ = nullptr;
+  // Calling the factory constructor is enough to set it up.
+  std::unique_ptr<TestAshWebViewFactory> test_web_view_factory_ =
+      std::make_unique<TestAshWebViewFactory>();
+};
+
+class EcheAppManagerFactoryWithBackgroundTest : public ChromeAshTestBase {
+ protected:
+  EcheAppManagerFactoryWithBackgroundTest() {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kEcheSWA, features::kEcheCustomWidget,
+                              features::kEcheSWAInBackground},
+        /*disabled_features=*/{});
+    profile_manager_ = std::make_unique<TestingProfileManager>(
+        TestingBrowserProcess::GetGlobal());
+    if (profile_manager_->SetUp()) {
+      profile_ = profile_manager_->CreateTestingProfile("testing_profile");
+    }
+  }
+  ~EcheAppManagerFactoryWithBackgroundTest() override = default;
+  EcheAppManagerFactoryWithBackgroundTest(
+      const EcheAppManagerFactoryWithBackgroundTest&) = delete;
+  EcheAppManagerFactoryWithBackgroundTest& operator=(
+      const EcheAppManagerFactoryWithBackgroundTest&) = delete;
+
+  // AshTestBase::Test:
+  void SetUp() override {
+    DCHECK(profile_);
+    DCHECK(test_web_view_factory_.get());
+    ChromeAshTestBase::SetUp();
+    eche_tray_ =
+        ash::StatusAreaWidgetTestHelper::GetStatusAreaWidget()->eche_tray();
+  }
+
+  TestingProfile* GetProfile() { return profile_; }
+
+  EcheTray* eche_tray() { return eche_tray_; }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  std::unique_ptr<TestingProfileManager> profile_manager_;
+  TestingProfile* profile_;
+  EcheTray* eche_tray_ = nullptr;
+  // Calling the factory constructor is enough to set it up.
+  std::unique_ptr<TestAshWebViewFactory> test_web_view_factory_ =
+      std::make_unique<TestAshWebViewFactory>();
+};
+
+TEST_F(EcheAppManagerFactoryTest, LaunchEcheApp) {
+  const int64_t user_id = 1;
+  const char16_t visible_name[] = u"Fake App";
+  const char package_name[] = "com.fakeapp";
+  EcheAppManagerFactory::LaunchEcheApp(
+      GetProfile(), /*notification_id=*/absl::nullopt, package_name,
+      visible_name, user_id, gfx::Image());
+  base::RunLoop().RunUntilIdle();
+  // Eche tray should be visible after launch.
+  EXPECT_TRUE(eche_tray()->is_active());
+}
+
+TEST_F(EcheAppManagerFactoryTest, CloseEche) {
+  const int64_t user_id = 1;
+  const char16_t visible_name[] = u"Fake App";
+  const char package_name[] = "com.fakeapp";
+  EcheAppManagerFactory::LaunchEcheApp(
+      GetProfile(), /*notification_id=*/absl::nullopt, package_name,
+      visible_name, user_id, gfx::Image());
+  EcheAppManagerFactory::CloseEche(GetProfile());
+  base::RunLoop().RunUntilIdle();
+  // Eche tray should be visible after close.
+  EXPECT_FALSE(eche_tray()->is_active());
+}
+
+TEST_F(EcheAppManagerFactoryTest, OnStreamStateChanged) {
+  const int64_t user_id = 1;
+  const char16_t visible_name[] = u"Fake App";
+  const char package_name[] = "com.fakeapp";
+  EcheAppManagerFactory::LaunchEcheApp(
+      GetProfile(), /*notification_id=*/absl::nullopt, package_name,
+      visible_name, user_id, gfx::Image());
+
+  // Eche tray should be visible when streaming is active
+  EcheAppManagerFactory::OnStreamStateChanged(
+      GetProfile(), mojom::StreamStatus::kStreamStatusStarted);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(eche_tray()->is_active());
+
+  // Eche tray should not be visible when streaming is finished
+  EcheAppManagerFactory::OnStreamStateChanged(
+      GetProfile(), mojom::StreamStatus::kStreamStatusStopped);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(eche_tray()->is_active());
+}
+
+TEST_F(EcheAppManagerFactoryWithBackgroundTest, LaunchEcheApp) {
+  const int64_t user_id = 1;
+  const char16_t visible_name[] = u"Fake App";
+  const char package_name[] = "com.fakeapp";
+  EcheAppManagerFactory::LaunchEcheApp(
+      GetProfile(), /*notification_id=*/absl::nullopt, package_name,
+      visible_name, user_id, gfx::Image());
+  base::RunLoop().RunUntilIdle();
+  // Eche tray should be visible when streaming is active, not ative when
+  // launch.
+  EXPECT_FALSE(eche_tray()->is_active());
+}
+
+TEST_F(EcheAppManagerFactoryWithBackgroundTest, OnStreamStateChanged) {
+  const int64_t user_id = 1;
+  const char16_t visible_name[] = u"Fake App";
+  const char package_name[] = "com.fakeapp";
+  EcheAppManagerFactory::LaunchEcheApp(
+      GetProfile(), /*notification_id=*/absl::nullopt, package_name,
+      visible_name, user_id, gfx::Image());
+
+  // Eche tray should be visible when streaming is active
+  EcheAppManagerFactory::OnStreamStateChanged(
+      GetProfile(), mojom::StreamStatus::kStreamStatusStarted);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(eche_tray()->is_active());
+
+  // Eche tray should not be visible when streaming is finished
+  EcheAppManagerFactory::OnStreamStateChanged(
+      GetProfile(), mojom::StreamStatus::kStreamStatusStopped);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(eche_tray()->is_active());
+}
+
+}  // namespace eche_app
+}  // namespace ash
diff --git a/chrome/browser/ash/file_manager/file_manager_string_util.cc b/chrome/browser/ash/file_manager/file_manager_string_util.cc
index ab2acf6d..2cca21a3 100644
--- a/chrome/browser/ash/file_manager/file_manager_string_util.cc
+++ b/chrome/browser/ash/file_manager/file_manager_string_util.cc
@@ -97,6 +97,9 @@
              IDS_FILE_BROWSER_POWERPOINT_PRESENTATION_FILE_TYPE);
   SET_STRING("RAR_ARCHIVE_FILE_TYPE", IDS_FILE_BROWSER_RAR_ARCHIVE_FILE_TYPE);
   SET_STRING("TAR_ARCHIVE_FILE_TYPE", IDS_FILE_BROWSER_TAR_ARCHIVE_FILE_TYPE);
+  SET_STRING("ISO_ARCHIVE_FILE_TYPE", IDS_FILE_BROWSER_ISO_ARCHIVE_FILE_TYPE);
+  SET_STRING("7Z_ARCHIVE_FILE_TYPE", IDS_FILE_BROWSER_7Z_ARCHIVE_FILE_TYPE);
+  SET_STRING("CRX_ARCHIVE_FILE_TYPE", IDS_FILE_BROWSER_CRX_ARCHIVE_FILE_TYPE);
   SET_STRING("TAR_BZIP2_ARCHIVE_FILE_TYPE",
              IDS_FILE_BROWSER_TAR_BZIP2_ARCHIVE_FILE_TYPE);
   SET_STRING("BZIP2_ARCHIVE_FILE_TYPE",
diff --git a/chrome/browser/ash/file_manager/file_tasks.cc b/chrome/browser/ash/file_manager/file_tasks.cc
index b4d6a203..3ed93c27 100644
--- a/chrome/browser/ash/file_manager/file_tasks.cc
+++ b/chrome/browser/ash/file_manager/file_tasks.cc
@@ -264,11 +264,20 @@
     web_drive_office_action_id_ =
         parseFilesAppActionId(web_drive_office_task->task_descriptor.action_id);
 
-    // Remove Web Drive Office action if Web Drive Office is disabled or if
-    // Drive is Offline.
-    if (!base::FeatureList::IsEnabled(ash::features::kFilesWebDriveOffice) ||
-        drive::util::GetDriveConnectionStatus(profile) !=
-            drive::util::DRIVE_CONNECTED) {
+    // Remove Web Drive Office action if Web Drive Office is disabled.
+    if (!base::FeatureList::IsEnabled(ash::features::kFilesWebDriveOffice)) {
+      UMA_HISTOGRAM_ENUMERATION(kWebDriveOfficeMetricName,
+                                WebDriveOfficeTaskResult::FLAG_DISABLED);
+      disabled_actions_.emplace(web_drive_office_action_id_);
+      EndAdjustTasks();
+      return;
+    }
+
+    // Remove Web Drive Office action if Drive is Offline.
+    if (drive::util::GetDriveConnectionStatus(profile) !=
+        drive::util::DRIVE_CONNECTED) {
+      UMA_HISTOGRAM_ENUMERATION(kWebDriveOfficeMetricName,
+                                WebDriveOfficeTaskResult::OFFLINE);
       disabled_actions_.emplace(web_drive_office_action_id_);
       EndAdjustTasks();
       return;
@@ -282,12 +291,17 @@
   void ProcessNextEntryForWebDriveOffice(size_t entry_index) {
     // Web Drive Office is available for all the selected entries.
     if (entry_index == entries.size()) {
+      UMA_HISTOGRAM_ENUMERATION(kWebDriveOfficeMetricName,
+                                WebDriveOfficeTaskResult::AVAILABLE);
       EndAdjustTasks();
       return;
     }
+
     // Check whether the entry is on Drive.
     if (!::file_manager::util::IsDriveLocalPath(profile,
                                                 entries[entry_index].path)) {
+      UMA_HISTOGRAM_ENUMERATION(kWebDriveOfficeMetricName,
+                                WebDriveOfficeTaskResult::NOT_ON_DRIVE);
       disabled_actions_.emplace(web_drive_office_action_id_);
       EndAdjustTasks();
       return;
@@ -301,6 +315,8 @@
           integration_service->GetDriveFsInterface() &&
           integration_service->GetRelativeDrivePath(entries[entry_index].path,
                                                     &relative_drive_path))) {
+      UMA_HISTOGRAM_ENUMERATION(kWebDriveOfficeMetricName,
+                                WebDriveOfficeTaskResult::DRIVE_ERROR);
       disabled_actions_.emplace(web_drive_office_action_id_);
       EndAdjustTasks();
       return;
@@ -321,6 +337,8 @@
       drive::FileError error,
       drivefs::mojom::FileMetadataPtr metadata) {
     if (error != drive::FILE_ERROR_OK) {
+      UMA_HISTOGRAM_ENUMERATION(kWebDriveOfficeMetricName,
+                                WebDriveOfficeTaskResult::DRIVE_METADATA_ERROR);
       disabled_actions_.emplace(web_drive_office_action_id_);
       EndAdjustTasks();
       return;
@@ -328,8 +346,24 @@
 
     GURL hosted_url(metadata->alternate_url);
     // URLs for editing Office files in Web Drive all have a "docs.google.com"
-    // host.
-    if (!hosted_url.is_valid() || hosted_url.host() != "docs.google.com") {
+    // host: Disable the task if the entry doesn't have such alternate URL.
+    if (!hosted_url.is_valid()) {
+      UMA_HISTOGRAM_ENUMERATION(
+          kWebDriveOfficeMetricName,
+          WebDriveOfficeTaskResult::INVALID_ALTERNATE_URL);
+      disabled_actions_.emplace(web_drive_office_action_id_);
+      EndAdjustTasks();
+      return;
+    } else if (hosted_url.host() == "drive.google.com") {
+      UMA_HISTOGRAM_ENUMERATION(kWebDriveOfficeMetricName,
+                                WebDriveOfficeTaskResult::DRIVE_ALTERNATE_URL);
+      disabled_actions_.emplace(web_drive_office_action_id_);
+      EndAdjustTasks();
+      return;
+    } else if (hosted_url.host() != "docs.google.com") {
+      UMA_HISTOGRAM_ENUMERATION(
+          kWebDriveOfficeMetricName,
+          WebDriveOfficeTaskResult::UNEXPECTED_ALTERNATE_URL);
       disabled_actions_.emplace(web_drive_office_action_id_);
       EndAdjustTasks();
       return;
diff --git a/chrome/browser/ash/file_manager/file_tasks.h b/chrome/browser/ash/file_manager/file_tasks.h
index 2e437a0d..d0aa969 100644
--- a/chrome/browser/ash/file_manager/file_tasks.h
+++ b/chrome/browser/ash/file_manager/file_tasks.h
@@ -144,6 +144,27 @@
 TaskType StringToTaskType(const std::string& str);
 std::string TaskTypeToString(TaskType task_type);
 
+// UMA metric name that tracks the result of trying to enable the Web Drive
+// Office task.
+constexpr char kWebDriveOfficeMetricName[] =
+    "FileBrowser.OfficeFiles.WebDriveOffice";
+
+// List of UMA enum value for Web Drive Office task results. The enum values
+// must be kept in sync with WebDriveOfficeTaskResult in
+// tools/metrics/histograms/enums.xml.
+enum class WebDriveOfficeTaskResult {
+  AVAILABLE = 0,
+  FLAG_DISABLED = 1,
+  OFFLINE = 2,
+  NOT_ON_DRIVE = 3,
+  DRIVE_ERROR = 4,
+  DRIVE_METADATA_ERROR = 5,
+  INVALID_ALTERNATE_URL = 6,
+  DRIVE_ALTERNATE_URL = 7,
+  UNEXPECTED_ALTERNATE_URL = 8,
+  kMaxValue = UNEXPECTED_ALTERNATE_URL,
+};
+
 // Describes a task.
 // See the comment above for <app-id>, <task-type>, and <action-id>.
 struct TaskDescriptor {
diff --git a/chrome/browser/ash/input_method/assistive_suggester.cc b/chrome/browser/ash/input_method/assistive_suggester.cc
index cb31d7d9..e2a324f4 100644
--- a/chrome/browser/ash/input_method/assistive_suggester.cc
+++ b/chrome/browser/ash/input_method/assistive_suggester.cc
@@ -453,11 +453,11 @@
       (cursor_pos == len || base::IsAsciiWhitespace(text[cursor_pos])) &&
       (base::IsAsciiWhitespace(text[cursor_pos - 1]) || IsSuggestionShown())) {
     if (IsSuggestionShown()) {
-      return current_suggester_->Suggest(text, cursor_pos, anchor_pos);
+      return current_suggester_->Suggest(text, cursor_pos);
     }
     if (IsAssistPersonalInfoEnabled() &&
         suggester_switch_->IsPersonalInfoSuggestionAllowed() &&
-        personal_info_suggester_.Suggest(text, cursor_pos, anchor_pos)) {
+        personal_info_suggester_.Suggest(text, cursor_pos)) {
       current_suggester_ = &personal_info_suggester_;
       if (personal_info_suggester_.IsFirstShown()) {
         RecordAssistiveCoverage(current_suggester_->GetProposeActionType());
@@ -466,7 +466,7 @@
     } else if (IsEmojiSuggestAdditionEnabled() &&
                !IsEnhancedEmojiSuggestEnabled() &&
                suggester_switch_->IsEmojiSuggestionAllowed() &&
-               emoji_suggester_.Suggest(text, cursor_pos, anchor_pos)) {
+               emoji_suggester_.Suggest(text, cursor_pos)) {
       current_suggester_ = &emoji_suggester_;
       RecordAssistiveCoverage(current_suggester_->GetProposeActionType());
       return true;
diff --git a/chrome/browser/ash/input_method/emoji_suggester.cc b/chrome/browser/ash/input_method/emoji_suggester.cc
index 0d5e4af..3b80cc1 100644
--- a/chrome/browser/ash/input_method/emoji_suggester.cc
+++ b/chrome/browser/ash/input_method/emoji_suggester.cc
@@ -221,9 +221,7 @@
   return false;
 }
 
-bool EmojiSuggester::Suggest(const std::u16string& text,
-                             size_t cursor_pos,
-                             size_t anchor_pos) {
+bool EmojiSuggester::Suggest(const std::u16string& text, size_t cursor_pos) {
   if (emoji_map_.empty() || text[text.length() - 1] != kSpaceChar)
     return false;
   std::string last_word =
diff --git a/chrome/browser/ash/input_method/emoji_suggester.h b/chrome/browser/ash/input_method/emoji_suggester.h
index ad504a0..238ba7a4 100644
--- a/chrome/browser/ash/input_method/emoji_suggester.h
+++ b/chrome/browser/ash/input_method/emoji_suggester.h
@@ -35,9 +35,7 @@
   void OnExternalSuggestionsUpdated(
       const std::vector<ime::TextSuggestion>& suggestions) override;
   SuggestionStatus HandleKeyEvent(const ui::KeyEvent& event) override;
-  bool Suggest(const std::u16string& text,
-               size_t cursor_pos,
-               size_t anchor_pos) override;
+  bool Suggest(const std::u16string& text, size_t cursor_pos) override;
   bool AcceptSuggestion(size_t index) override;
   void DismissSuggestion() override;
   AssistiveType GetProposeActionType() override;
diff --git a/chrome/browser/ash/input_method/emoji_suggester_unittest.cc b/chrome/browser/ash/input_method/emoji_suggester_unittest.cc
index 54d91602..0cd381e9 100644
--- a/chrome/browser/ash/input_method/emoji_suggester_unittest.cc
+++ b/chrome/browser/ash/input_method/emoji_suggester_unittest.cc
@@ -145,58 +145,58 @@
 };
 
 TEST_F(EmojiSuggesterTest, SuggestWhenStringEndsWithSpace) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
 }
 
 TEST_F(EmojiSuggesterTest, SuggestWhenStringStartsWithOpenBracket) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"(happy ", 7, 7));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"(happy ", 7));
 }
 
 TEST_F(EmojiSuggesterTest, SuggestWhenStringEndsWithSpaceAndIsUppercase) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"HAPPY ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"HAPPY ", 6));
 }
 
 TEST_F(EmojiSuggesterTest, DoNotSuggestWhenStringEndsWithNewLine) {
-  EXPECT_FALSE(emoji_suggester_->Suggest(u"happy\n", 6, 6));
+  EXPECT_FALSE(emoji_suggester_->Suggest(u"happy\n", 6));
 }
 
 TEST_F(EmojiSuggesterTest, DoNotSuggestWhenStringDoesNotEndWithSpace) {
-  EXPECT_FALSE(emoji_suggester_->Suggest(u"happy", 5, 5));
+  EXPECT_FALSE(emoji_suggester_->Suggest(u"happy", 5));
 }
 
 TEST_F(EmojiSuggesterTest, DoNotSuggestWhenWordNotInMap) {
-  EXPECT_FALSE(emoji_suggester_->Suggest(u"hapy ", 5, 5));
+  EXPECT_FALSE(emoji_suggester_->Suggest(u"hapy ", 5));
 }
 
 TEST_F(EmojiSuggesterTest, DoNotShowSuggestionWhenVirtualKeyboardEnabled) {
   chrome_keyboard_controller_client_->set_keyboard_visible_for_test(true);
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   EXPECT_FALSE(emoji_suggester_->HasSuggestions());
 }
 
 TEST_F(EmojiSuggesterTest, ReturnkBrowsingWhenPressingDown) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   ui::KeyEvent event = CreateKeyEventFromCode(ui::DomCode::ARROW_DOWN);
   EXPECT_EQ(SuggestionStatus::kBrowsing,
             emoji_suggester_->HandleKeyEvent(event));
 }
 
 TEST_F(EmojiSuggesterTest, ReturnkBrowsingWhenPressingUp) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   ui::KeyEvent event = CreateKeyEventFromCode(ui::DomCode::ARROW_UP);
   EXPECT_EQ(SuggestionStatus::kBrowsing,
             emoji_suggester_->HandleKeyEvent(event));
 }
 
 TEST_F(EmojiSuggesterTest, ReturnkDismissWhenPressingEsc) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   ui::KeyEvent event = CreateKeyEventFromCode(ui::DomCode::ESCAPE);
   EXPECT_EQ(SuggestionStatus::kDismiss,
             emoji_suggester_->HandleKeyEvent(event));
 }
 
 TEST_F(EmojiSuggesterTest, ReturnkNotHandledWhenPressDownThenValidNumber) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   ui::KeyEvent event1 = CreateKeyEventFromCode(ui::DomCode::ARROW_DOWN);
   emoji_suggester_->HandleKeyEvent(event1);
   ui::KeyEvent event2 = CreateKeyEventFromCode(ui::DomCode::DIGIT1);
@@ -205,7 +205,7 @@
 }
 
 TEST_F(EmojiSuggesterTest, ReturnkNotHandledWhenPressDownThenNotANumber) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   ui::KeyEvent event1 = CreateKeyEventFromCode(ui::DomCode::ARROW_DOWN);
   emoji_suggester_->HandleKeyEvent(event1);
   ui::KeyEvent event2 = CreateKeyEventFromCode(ui::DomCode::US_A);
@@ -215,7 +215,7 @@
 
 TEST_F(EmojiSuggesterTest,
        ReturnkNotHandledWhenPressingEnterAndACandidateHasNotBeenChosen) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   ui::KeyEvent event = CreateKeyEventFromCode(ui::DomCode::ENTER);
   EXPECT_EQ(SuggestionStatus::kNotHandled,
             emoji_suggester_->HandleKeyEvent(event));
@@ -223,7 +223,7 @@
 
 TEST_F(EmojiSuggesterTest,
        ReturnkAcceptWhenPressingEnterAndACandidateHasBeenChosenByPressingDown) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   // Press ui::DomCode::ARROW_DOWN to choose a candidate.
   ui::KeyEvent event1 = CreateKeyEventFromCode(ui::DomCode::ARROW_DOWN);
   emoji_suggester_->HandleKeyEvent(event1);
@@ -233,13 +233,13 @@
 }
 
 TEST_F(EmojiSuggesterTest, HighlightFirstCandidateWhenPressingDown) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   Press(ui::DomCode::ARROW_DOWN);
   engine_->VerifyCandidateHighlighted(0, true);
 }
 
 TEST_F(EmojiSuggesterTest, HighlightButtonCorrectlyWhenPressingUp) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
 
   // Go into the window.
   Press(ui::DomCode::ARROW_DOWN);
@@ -265,7 +265,7 @@
 }
 
 TEST_F(EmojiSuggesterTest, HighlightButtonCorrectlyWhenPressingDown) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
 
   // Press ui::DomCode::ARROW_DOWN to go through candidates.
   for (size_t i = 0; i < emoji_suggester_->GetCandidatesSizeForTesting(); i++) {
@@ -291,7 +291,7 @@
 
 TEST_F(EmojiSuggesterTest,
        OpenSettingWhenPressingEnterAndLearnMoreButtonIsChosen) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
 
   // Go into the window.
   Press(ui::DomCode::ARROW_DOWN);
@@ -303,57 +303,57 @@
 }
 
 TEST_F(EmojiSuggesterTest, DoesNotShowIndicesWhenFirstSuggesting) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
 
   engine_->VerifyShowIndices(false);
 }
 
 TEST_F(EmojiSuggesterTest, DoesNotShowIndexAfterPressingDown) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   Press(ui::DomCode::ARROW_DOWN);
 
   engine_->VerifyShowIndices(false);
 }
 
 TEST_F(EmojiSuggesterTest, DoesNotShowIndicesAfterGettingSuggestionsTwice) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
 
   engine_->VerifyShowIndices(false);
 }
 
 TEST_F(EmojiSuggesterTest,
        DoesNotShowIndicesAfterPressingDownThenGetNewSuggestions) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   Press(ui::DomCode::ARROW_DOWN);
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
 
   engine_->VerifyShowIndices(false);
 }
 
 TEST_F(EmojiSuggesterTest, ShowSettingLinkCorrectly) {
   for (int i = 0; i < kEmojiSuggesterShowSettingMaxCount; i++) {
-    emoji_suggester_->Suggest(u"happy ", 6, 6);
+    emoji_suggester_->Suggest(u"happy ", 6);
     // Dismiss suggestion.
     Press(ui::DomCode::ESCAPE);
     engine_->VerifyShowSettingLink(true);
   }
-  emoji_suggester_->Suggest(u"happy ", 6, 6);
+  emoji_suggester_->Suggest(u"happy ", 6);
   engine_->VerifyShowSettingLink(false);
 }
 
 TEST_F(EmojiSuggesterTest, IsShowingSuggestionTrueWhenCandidatesAvailable) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   EXPECT_TRUE(emoji_suggester_->HasSuggestions());
 }
 
 TEST_F(EmojiSuggesterTest, IsShowingSuggestionFalseWhenCandidatesUnavailable) {
-  EXPECT_FALSE(emoji_suggester_->Suggest(u"hapy", 4, 4));
+  EXPECT_FALSE(emoji_suggester_->Suggest(u"hapy", 4));
   EXPECT_FALSE(emoji_suggester_->HasSuggestions());
 }
 
 TEST_F(EmojiSuggesterTest, GetSuggestionReturnsCandidatesWhenAvailable) {
-  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6, 6));
+  EXPECT_TRUE(emoji_suggester_->Suggest(u"happy ", 6));
   EXPECT_EQ(emoji_suggester_->GetSuggestions(),
             (std::vector<TextSuggestion>{
                 TextSuggestion{.mode = TextSuggestionMode::kPrediction,
@@ -370,7 +370,7 @@
 
 TEST_F(EmojiSuggesterTest,
        GetSuggestionDoesNotReturnCandidatesWhenUnavailable) {
-  EXPECT_FALSE(emoji_suggester_->Suggest(u"hapy", 4, 4));
+  EXPECT_FALSE(emoji_suggester_->Suggest(u"hapy", 4));
   EXPECT_TRUE(emoji_suggester_->GetSuggestions().empty());
 }
 
diff --git a/chrome/browser/ash/input_method/multi_word_suggester.cc b/chrome/browser/ash/input_method/multi_word_suggester.cc
index 282ca6094..9593dab1 100644
--- a/chrome/browser/ash/input_method/multi_word_suggester.cc
+++ b/chrome/browser/ash/input_method/multi_word_suggester.cc
@@ -26,6 +26,8 @@
 using ime::TextSuggestionMode;
 using ime::TextSuggestionType;
 
+// Used for UmaHistogramExactLinear, should remain <= 101.
+constexpr size_t kMaxSuggestionLength = 101;
 constexpr char kMultiWordFirstAcceptTimeDays[] = "multi_word_first_accept";
 constexpr char16_t kSuggestionShownMessage[] =
     u"predictive writing candidate shown, press down to select or "
@@ -74,6 +76,12 @@
                           delta);
 }
 
+void RecordSuggestionLength(size_t suggestion_length) {
+  base::UmaHistogramExactLinear(
+      "InputMethod.Assistive.MultiWord.SuggestionLength", suggestion_length,
+      kMaxSuggestionLength);
+}
+
 absl::optional<int> GetTimeFirstAcceptedSuggestion(Profile* profile) {
   DictionaryPrefUpdate update(profile->GetPrefs(),
                               prefs::kAssistiveInputFeatureSettings);
@@ -153,6 +161,11 @@
     return;
   }
 
+  if (auto suggestion_length = multi_word_suggestion->text.size();
+      suggestion_length < kMaxSuggestionLength) {
+    RecordSuggestionLength(suggestion_length);
+  }
+
   auto suggestion = SuggestionState::Suggestion{
       .mode = multi_word_suggestion->mode,
       .text = base::UTF8ToUTF16(multi_word_suggestion->text),
@@ -192,8 +205,7 @@
 }
 
 bool MultiWordSuggester::Suggest(const std::u16string& text,
-                                 size_t cursor_pos,
-                                 size_t anchor_pos) {
+                                 size_t cursor_pos) {
   return state_.IsSuggestionShowing();
 }
 
diff --git a/chrome/browser/ash/input_method/multi_word_suggester.h b/chrome/browser/ash/input_method/multi_word_suggester.h
index 6701b0f..dfd3912 100644
--- a/chrome/browser/ash/input_method/multi_word_suggester.h
+++ b/chrome/browser/ash/input_method/multi_word_suggester.h
@@ -32,9 +32,7 @@
   void OnExternalSuggestionsUpdated(
       const std::vector<ime::TextSuggestion>& suggestions) override;
   SuggestionStatus HandleKeyEvent(const ui::KeyEvent& event) override;
-  bool Suggest(const std::u16string& text,
-               size_t cursor_pos,
-               size_t anchor_pos) override;
+  bool Suggest(const std::u16string& text, size_t cursor_pos) override;
   bool AcceptSuggestion(size_t index = 0) override;
   void DismissSuggestion() override;
   AssistiveType GetProposeActionType() override;
diff --git a/chrome/browser/ash/input_method/multi_word_suggester_unittest.cc b/chrome/browser/ash/input_method/multi_word_suggester_unittest.cc
index c9a48f3..9245c7e 100644
--- a/chrome/browser/ash/input_method/multi_word_suggester_unittest.cc
+++ b/chrome/browser/ash/input_method/multi_word_suggester_unittest.cc
@@ -450,12 +450,12 @@
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"h", /*cursor_pos=*/1,
                                        /*anchor_pos=*/1);
-  suggester_->Suggest(u"h", /*cursor_pos=*/1, /*anchor_pos=*/1);
+  suggester_->Suggest(u"h", /*cursor_pos=*/1);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"hh", /*cursor_pos=*/2,
                                        /*anchor_pos=*/2);
 
-  EXPECT_FALSE(suggester_->Suggest(u"hh", /*cursor_pos=*/2, /*anchor_pos=*/2));
+  EXPECT_FALSE(suggester_->Suggest(u"hh", /*cursor_pos=*/2));
 }
 
 TEST_F(MultiWordSuggesterTest, DoesNotDismissOnMultipleCursorMoveToEndOfText) {
@@ -468,16 +468,15 @@
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"hello h", /*cursor_pos=*/7,
                                        /*anchor_pos=*/7);
-  suggester_->Suggest(u"hello h", /*cursor_pos=*/7, /*anchor_pos=*/7);
+  suggester_->Suggest(u"hello h", /*cursor_pos=*/7);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"hello h", /*cursor_pos=*/7,
                                        /*anchor_pos=*/7);
-  suggester_->Suggest(u"hello h", /*cursor_pos=*/7, /*anchor_pos=*/7);
+  suggester_->Suggest(u"hello h", /*cursor_pos=*/7);
   suggester_->OnSurroundingTextChanged(u"hello h", /*cursor_pos=*/7,
                                        /*anchor_pos=*/7);
 
-  EXPECT_TRUE(suggester_->Suggest(u"hello h", /*cursor_pos=*/7,
-                                  /*anchor_pos=*/7));
+  EXPECT_TRUE(suggester_->Suggest(u"hello h", /*cursor_pos=*/7));
 }
 
 TEST_F(MultiWordSuggesterTest, TracksLastSuggestionOnSurroundingTextChange) {
@@ -491,17 +490,17 @@
   suggester_->OnSurroundingTextChanged(u"hey there sam whe", 17, 17);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"hey there sam wher", 18, 18);
-  suggester_->Suggest(u"hey there sam wher", 18, 18);
+  suggester_->Suggest(u"hey there sam wher", 18);
   suggester_->OnSurroundingTextChanged(u"hey there sam where", 19, 19);
-  suggester_->Suggest(u"hey there sam where", 19, 19);
+  suggester_->Suggest(u"hey there sam where", 19);
   suggester_->OnSurroundingTextChanged(u"hey there sam where ", 20, 20);
-  suggester_->Suggest(u"hey there sam where ", 20, 20);
+  suggester_->Suggest(u"hey there sam where ", 20);
   suggester_->OnSurroundingTextChanged(u"hey there sam where a", 21, 21);
-  suggester_->Suggest(u"hey there sam where a", 21, 21);
+  suggester_->Suggest(u"hey there sam where a", 21);
   suggester_->OnSurroundingTextChanged(u"hey there sam where ar", 22, 22);
-  suggester_->Suggest(u"hey there sam where ar", 22, 22);
+  suggester_->Suggest(u"hey there sam where ar", 22);
   suggester_->OnSurroundingTextChanged(u"hey there sam where are", 23, 23);
-  suggester_->Suggest(u"hey there sam where are", 23, 23);
+  suggester_->Suggest(u"hey there sam where are", 23);
 
   EXPECT_TRUE(suggestion_handler_.GetShowingSuggestion());
   EXPECT_EQ(suggestion_handler_.GetSuggestionText(), u"where are you going");
@@ -520,9 +519,9 @@
   suggester_->OnSurroundingTextChanged(u"h", 1, 1);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"ho", 2, 2);
-  suggester_->Suggest(u"ho", 2, 2);
+  suggester_->Suggest(u"ho", 2);
   suggester_->OnSurroundingTextChanged(u"how", 3, 3);
-  suggester_->Suggest(u"how", 3, 3);
+  suggester_->Suggest(u"how", 3);
 
   EXPECT_TRUE(suggestion_handler_.GetShowingSuggestion());
   EXPECT_EQ(suggestion_handler_.GetSuggestionText(), u"how are you");
@@ -541,9 +540,9 @@
   suggester_->OnSurroundingTextChanged(u"h", 1, 1);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"how ar", 6, 6);
-  suggester_->Suggest(u"how ar", 6, 6);
+  suggester_->Suggest(u"how ar", 6);
   suggester_->OnSurroundingTextChanged(u"how are yo", 10, 10);
-  suggester_->Suggest(u"how are yo", 10, 10);
+  suggester_->Suggest(u"how are yo", 10);
 
   EXPECT_TRUE(suggestion_handler_.GetShowingSuggestion());
   EXPECT_EQ(suggestion_handler_.GetSuggestionText(), u"how are you");
@@ -562,11 +561,11 @@
   suggester_->OnSurroundingTextChanged(u"h", 1, 1);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"how ar", 6, 6);
-  suggester_->Suggest(u"how ar", 6, 6);
+  suggester_->Suggest(u"how ar", 6);
   suggester_->OnSurroundingTextChanged(u"how yo", 6, 6);
 
   // The consumer will handle dismissing the suggestion
-  EXPECT_FALSE(suggester_->Suggest(u"how yo", 6, 6));
+  EXPECT_FALSE(suggester_->Suggest(u"how yo", 6));
 }
 
 TEST_F(MultiWordSuggesterTest,
@@ -581,18 +580,18 @@
   suggester_->OnSurroundingTextChanged(u"this is some text", 17, 17);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"this is some text ", 18, 18);
-  suggester_->Suggest(u"this is some text ", 18, 18);
+  suggester_->Suggest(u"this is some text ", 18);
   suggester_->OnSurroundingTextChanged(u"this is some text f", 19, 19);
-  suggester_->Suggest(u"this is some text f", 19, 19);
+  suggester_->Suggest(u"this is some text f", 19);
   suggester_->OnSurroundingTextChanged(u"this is some text fo", 20, 20);
-  suggester_->Suggest(u"this is some text fo", 20, 20);
+  suggester_->Suggest(u"this is some text fo", 20);
   suggester_->OnSurroundingTextChanged(u"this is some text f", 19, 19);
-  suggester_->Suggest(u"this is some text f", 19, 19);
+  suggester_->Suggest(u"this is some text f", 19);
   suggester_->OnSurroundingTextChanged(u"this is some text ", 18, 18);
-  suggester_->Suggest(u"this is some text ", 18, 18);
+  suggester_->Suggest(u"this is some text ", 18);
   suggester_->OnSurroundingTextChanged(u"this is some text", 17, 17);
 
-  EXPECT_FALSE(suggester_->Suggest(u"this is some text", 17, 17));
+  EXPECT_FALSE(suggester_->Suggest(u"this is some text", 17));
 }
 
 TEST_F(MultiWordSuggesterTest, DoesNotTrackSuggestionPastSuggestionPoint) {
@@ -606,13 +605,13 @@
   suggester_->OnSurroundingTextChanged(u"this is some text fo", 20, 20);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"this is some text for", 21, 21);
-  suggester_->Suggest(u"this is some text for", 21, 21);
+  suggester_->Suggest(u"this is some text for", 21);
   suggester_->OnSurroundingTextChanged(u"this is some text fo", 20, 20);
   bool at_suggestion_point =
-      suggester_->Suggest(u"this is some text fo", 20, 20);
+      suggester_->Suggest(u"this is some text fo", 20);
   suggester_->OnSurroundingTextChanged(u"this is some text f", 19, 19);
   bool before_suggestion_point =
-      suggester_->Suggest(u"this is some text f", 19, 19);
+      suggester_->Suggest(u"this is some text f", 19);
 
   EXPECT_TRUE(at_suggestion_point);
   EXPECT_FALSE(before_suggestion_point);
@@ -630,10 +629,10 @@
   suggester_->OnSurroundingTextChanged(u"this is some text fo", 20, 20);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"this is some text for", 21, 21);
-  suggester_->Suggest(u"this is some text for", 21, 21);
+  suggester_->Suggest(u"this is some text for", 21);
   suggester_->OnSurroundingTextChanged(u"this is some text for", 15, 15);
 
-  EXPECT_FALSE(suggester_->Suggest(u"this is some text for", 15, 15));
+  EXPECT_FALSE(suggester_->Suggest(u"this is some text for", 15));
 }
 
 TEST_F(MultiWordSuggesterTest, DismissesSuggestionOnUserTypingFullSuggestion) {
@@ -647,14 +646,14 @@
   suggester_->OnSurroundingTextChanged(u"how", 3, 3);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"how ", 4, 4);
-  suggester_->Suggest(u"how ", 4, 4);
+  suggester_->Suggest(u"how ", 4);
   suggester_->OnSurroundingTextChanged(u"how a", 5, 5);
-  suggester_->Suggest(u"how a", 5, 5);
+  suggester_->Suggest(u"how a", 5);
   suggester_->OnSurroundingTextChanged(u"how ar", 6, 6);
-  suggester_->Suggest(u"how ar", 6, 6);
+  suggester_->Suggest(u"how ar", 6);
   suggester_->OnSurroundingTextChanged(u"how are", 7, 7);
 
-  EXPECT_FALSE(suggester_->Suggest(u"how are", 7, 7));
+  EXPECT_FALSE(suggester_->Suggest(u"how are", 7));
 }
 
 TEST_F(MultiWordSuggesterTest, ReturnsGenericActionIfNoSuggestionHasBeenShown) {
@@ -706,7 +705,7 @@
 
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"why ar", 6, 6);
-  suggester_->Suggest(u"why", 6, 6);
+  suggester_->Suggest(u"why", 6);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   SendKeyEvent(suggester_.get(), ui::DomCode::TAB);
 
@@ -724,7 +723,7 @@
 
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"why", 3, 3);
-  suggester_->Suggest(u"why", 3, 3);
+  suggester_->Suggest(u"why", 3);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   SendKeyEvent(suggester_.get(), ui::DomCode::TAB);
 
@@ -773,17 +772,59 @@
       "InputMethod.Assistive.TimeToDismiss.MultiWord", 1);
 }
 
+TEST_F(MultiWordSuggesterTest, RecordsSuggestionLengthMetric) {
+  std::vector<TextSuggestion> suggestions = {
+      TextSuggestion{.mode = TextSuggestionMode::kPrediction,
+                     .type = TextSuggestionType::kMultiWord,
+                     .text = "how are you"},
+  };
+
+  base::HistogramTester histogram_tester;
+  histogram_tester.ExpectTotalCount(
+      "InputMethod.Assistive.MultiWord.SuggestionLength", 0);
+
+  suggester_->OnFocus(kFocusedContextId);
+  suggester_->OnSurroundingTextChanged(u"how", 3, 3);
+  suggester_->OnExternalSuggestionsUpdated(suggestions);
+
+  histogram_tester.ExpectTotalCount(
+      "InputMethod.Assistive.MultiWord.SuggestionLength", 1);
+  // "how are you" = 11 chars
+  histogram_tester.ExpectUniqueSample(
+      "InputMethod.Assistive.MultiWord.SuggestionLength", /*sample=*/11,
+      /*expected_bucket_count=*/1);
+}
+
+TEST_F(MultiWordSuggesterTest, DoesntRecordIfSuggestionLengthIsBig) {
+  std::vector<TextSuggestion> suggestions = {
+      TextSuggestion{.mode = TextSuggestionMode::kPrediction,
+                     .type = TextSuggestionType::kMultiWord,
+                     .text = std::string(101, 'h')},
+  };
+
+  base::HistogramTester histogram_tester;
+  histogram_tester.ExpectTotalCount(
+      "InputMethod.Assistive.MultiWord.SuggestionLength", 0);
+
+  suggester_->OnFocus(kFocusedContextId);
+  suggester_->OnSurroundingTextChanged(u"how", 3, 3);
+  suggester_->OnExternalSuggestionsUpdated(suggestions);
+
+  histogram_tester.ExpectTotalCount(
+      "InputMethod.Assistive.MultiWord.SuggestionLength", 0);
+}
+
 TEST_F(MultiWordSuggesterTest,
        SurroundingTextChangesDoNotTriggerAnnouncements) {
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"why are", 7, 7);
-  suggester_->Suggest(u"why are", 7, 7);
+  suggester_->Suggest(u"why are", 7);
   suggester_->OnSurroundingTextChanged(u"why aren", 8, 8);
-  suggester_->Suggest(u"why aren", 8, 8);
+  suggester_->Suggest(u"why aren", 8);
   suggester_->OnSurroundingTextChanged(u"why aren'", 9, 9);
-  suggester_->Suggest(u"why aren'", 9, 9);
+  suggester_->Suggest(u"why aren'", 9);
   suggester_->OnSurroundingTextChanged(u"why aren't", 10, 10);
-  suggester_->Suggest(u"why aren't", 10, 10);
+  suggester_->Suggest(u"why aren't", 10);
 
   ASSERT_EQ(suggestion_handler_.GetAnnouncements().size(), 0u);
 }
@@ -797,7 +838,7 @@
 
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"why are", 7, 7);
-  suggester_->Suggest(u"why are", 7, 7);
+  suggester_->Suggest(u"why are", 7);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
 
   ASSERT_EQ(suggestion_handler_.GetAnnouncements().size(), 1u);
@@ -816,14 +857,14 @@
 
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"why are", 7, 7);
-  suggester_->Suggest(u"why are", 7, 7);
+  suggester_->Suggest(u"why are", 7);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->OnSurroundingTextChanged(u"why aren", 8, 8);
-  suggester_->Suggest(u"why aren", 8, 8);
+  suggester_->Suggest(u"why aren", 8);
   suggester_->OnSurroundingTextChanged(u"why aren'", 9, 9);
-  suggester_->Suggest(u"why aren'", 9, 9);
+  suggester_->Suggest(u"why aren'", 9);
   suggester_->OnSurroundingTextChanged(u"why aren't", 10, 10);
-  suggester_->Suggest(u"why aren't", 10, 10);
+  suggester_->Suggest(u"why aren't", 10);
 
   ASSERT_EQ(suggestion_handler_.GetAnnouncements().size(), 1u);
   EXPECT_EQ(suggestion_handler_.GetAnnouncements().back(),
@@ -840,7 +881,7 @@
 
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"why are", 7, 7);
-  suggester_->Suggest(u"why are", 7, 7);
+  suggester_->Suggest(u"why are", 7);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   SendKeyEvent(suggester_.get(), ui::DomCode::TAB);
 
@@ -859,11 +900,11 @@
 
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"why are", 7, 7);
-  suggester_->Suggest(u"why are", 7, 7);
+  suggester_->Suggest(u"why are", 7);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   SendKeyEvent(suggester_.get(), ui::DomCode::TAB);
   suggester_->OnSurroundingTextChanged(u"why aren", 8, 8);
-  suggester_->Suggest(u"why aren", 8, 8);
+  suggester_->Suggest(u"why aren", 8);
 
   ASSERT_EQ(suggestion_handler_.GetAnnouncements().size(), 2u);
 }
@@ -877,7 +918,7 @@
 
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"why are", 7, 7);
-  suggester_->Suggest(u"why are", 7, 7);
+  suggester_->Suggest(u"why are", 7);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->DismissSuggestion();
 
@@ -896,11 +937,11 @@
 
   suggester_->OnFocus(kFocusedContextId);
   suggester_->OnSurroundingTextChanged(u"why are", 7, 7);
-  suggester_->Suggest(u"why are", 7, 7);
+  suggester_->Suggest(u"why are", 7);
   suggester_->OnExternalSuggestionsUpdated(suggestions);
   suggester_->DismissSuggestion();
   suggester_->OnSurroundingTextChanged(u"why aren", 8, 8);
-  suggester_->Suggest(u"why aren", 8, 8);
+  suggester_->Suggest(u"why aren", 8);
 
   ASSERT_EQ(suggestion_handler_.GetAnnouncements().size(), 2u);
 }
diff --git a/chrome/browser/ash/input_method/personal_info_suggester.cc b/chrome/browser/ash/input_method/personal_info_suggester.cc
index 4e6eb49..01bbd0b 100644
--- a/chrome/browser/ash/input_method/personal_info_suggester.cc
+++ b/chrome/browser/ash/input_method/personal_info_suggester.cc
@@ -239,8 +239,7 @@
 }
 
 bool PersonalInfoSuggester::Suggest(const std::u16string& text,
-                                    size_t cursor_pos,
-                                    size_t anchor_pos) {
+                                    size_t cursor_pos) {
   // |text| could be very long, we get at most |kMaxTextBeforeCursorLength|
   // characters before cursor.
   int start_pos = cursor_pos >= kMaxTextBeforeCursorLength
diff --git a/chrome/browser/ash/input_method/personal_info_suggester.h b/chrome/browser/ash/input_method/personal_info_suggester.h
index 095f6be0..b9c79c5 100644
--- a/chrome/browser/ash/input_method/personal_info_suggester.h
+++ b/chrome/browser/ash/input_method/personal_info_suggester.h
@@ -53,9 +53,7 @@
   void OnExternalSuggestionsUpdated(
       const std::vector<ime::TextSuggestion>& suggestions) override;
   SuggestionStatus HandleKeyEvent(const ui::KeyEvent& event) override;
-  bool Suggest(const std::u16string& text,
-               size_t cursor_pos,
-               size_t anchor_pos) override;
+  bool Suggest(const std::u16string& text, size_t cursor_pos) override;
   // index defaults to 0 as not required for this suggester.
   bool AcceptSuggestion(size_t index = 0) override;
   void DismissSuggestion() override;
diff --git a/chrome/browser/ash/input_method/personal_info_suggester_unittest.cc b/chrome/browser/ash/input_method/personal_info_suggester_unittest.cc
index ba8c46d..9fa8f32 100644
--- a/chrome/browser/ash/input_method/personal_info_suggester_unittest.cc
+++ b/chrome/browser/ash/input_method/personal_info_suggester_unittest.cc
@@ -191,15 +191,15 @@
 
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   suggestion_handler_->VerifySuggestion(email_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"My email is: ", 13, 13);
+  suggester_->Suggest(u"My email is: ", 13);
   suggestion_handler_->VerifySuggestion(email_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"hi, my email: ", 14, 14);
+  suggester_->Suggest(u"hi, my email: ", 14);
   suggestion_handler_->VerifySuggestion(email_, 0);
 }
 
@@ -211,11 +211,11 @@
 
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"\nmy email is ", 13, 13);
+  suggester_->Suggest(u"\nmy email is ", 13);
   suggestion_handler_->VerifySuggestion(email_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"Hey\nMan\nmy email is ", 20, 20);
+  suggester_->Suggest(u"Hey\nMan\nmy email is ", 20);
   suggestion_handler_->VerifySuggestion(email_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 }
@@ -228,13 +228,13 @@
 
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"\nmy email is \n", 14, 14);
+  suggester_->Suggest(u"\nmy email is \n", 14);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"\nmy email is \n ", 15, 15);
+  suggester_->Suggest(u"\nmy email is \n ", 15);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"Hey\nMan\nmy email is \nhey ", 25, 25);
+  suggester_->Suggest(u"Hey\nMan\nmy email is \nhey ", 25);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 }
 
@@ -246,7 +246,7 @@
 
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 }
 
@@ -258,10 +258,10 @@
 
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is John", 16, 16);
+  suggester_->Suggest(u"my email is John", 16);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"our email is: ", 14, 14);
+  suggester_->Suggest(u"our email is: ", 14);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 }
 
@@ -274,7 +274,7 @@
   chrome_keyboard_controller_client_->set_keyboard_visible_for_test(true);
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 }
 
@@ -288,7 +288,7 @@
   chrome_keyboard_controller_client_->set_keyboard_visible_for_test(true);
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   suggestion_handler_->VerifySuggestionDispatchedToExtension(
       std::vector<std::string>{base::UTF16ToUTF8(email_)});
 }
@@ -307,19 +307,19 @@
   autofill_profile.SetRawInfo(autofill::ServerFieldType::NAME_FULL, full_name_);
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"my first name is ", 17, 17);
+  suggester_->Suggest(u"my first name is ", 17);
   suggestion_handler_->VerifySuggestion(first_name_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"my last name is: ", 17, 17);
+  suggester_->Suggest(u"my last name is: ", 17);
   suggestion_handler_->VerifySuggestion(last_name_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"my name is ", 12, 12);
+  suggester_->Suggest(u"my name is ", 12);
   suggestion_handler_->VerifySuggestion(full_name_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"Hmm... my FULL name: ", 21, 21);
+  suggester_->Suggest(u"Hmm... my FULL name: ", 21);
   suggestion_handler_->VerifySuggestion(full_name_, 0);
 }
 
@@ -336,7 +336,7 @@
   histogram_tester.ExpectUniqueSample("InputMethod.Assistive.InsufficientData",
                                       AssistiveType::kPersonalName, 0);
 
-  suggester_->Suggest(u"my name is ", 12, 12);
+  suggester_->Suggest(u"my name is ", 12);
   histogram_tester.ExpectUniqueSample("InputMethod.Assistive.InsufficientData",
                                       AssistiveType::kPersonalName, 1);
 }
@@ -355,13 +355,13 @@
   autofill_profile.SetRawInfo(autofill::ServerFieldType::NAME_FULL, full_name_);
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"my first name is ", 17, 17);
+  suggester_->Suggest(u"my first name is ", 17);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"my last name is: ", 17, 17);
+  suggester_->Suggest(u"my last name is: ", 17);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"my name is ", 12, 12);
+  suggester_->Suggest(u"my name is ", 12);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 }
 
@@ -379,16 +379,16 @@
   autofill_profile.SetRawInfo(autofill::ServerFieldType::NAME_FULL, full_name_);
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"our first name is ", 18, 18);
+  suggester_->Suggest(u"our first name is ", 18);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"our last name is: ", 18, 18);
+  suggester_->Suggest(u"our last name is: ", 18);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"our name is ", 12, 12);
+  suggester_->Suggest(u"our name is ", 12);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"our full name: ", 15, 15);
+  suggester_->Suggest(u"our full name: ", 15);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 }
 
@@ -413,23 +413,23 @@
                               u"US");
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"my address is ", 14, 14);
+  suggester_->Suggest(u"my address is ", 14);
   suggestion_handler_->VerifySuggestion(address_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"our address is: ", 16, 16);
+  suggester_->Suggest(u"our address is: ", 16);
   suggestion_handler_->VerifySuggestion(address_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"my shipping address: ", 21, 21);
+  suggester_->Suggest(u"my shipping address: ", 21);
   suggestion_handler_->VerifySuggestion(address_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"our billing address is ", 23, 23);
+  suggester_->Suggest(u"our billing address is ", 23);
   suggestion_handler_->VerifySuggestion(address_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"my current address: ", 20, 20);
+  suggester_->Suggest(u"my current address: ", 20);
   suggestion_handler_->VerifySuggestion(address_, 0);
 }
 
@@ -454,7 +454,7 @@
                               u"US");
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"my address is ", 14, 14);
+  suggester_->Suggest(u"my address is ", 14);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 }
 
@@ -479,13 +479,13 @@
                               u"US");
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"my address ", 11, 11);
+  suggester_->Suggest(u"my address ", 11);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"my last address is: ", 20, 20);
+  suggester_->Suggest(u"my last address is: ", 20);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"our address number is ", 22, 22);
+  suggester_->Suggest(u"our address number is ", 22);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 }
 
@@ -501,23 +501,23 @@
       autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER, phone_number_);
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"my phone number is ", 19, 19);
+  suggester_->Suggest(u"my phone number is ", 19);
   suggestion_handler_->VerifySuggestion(phone_number_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"my number is ", 13, 13);
+  suggester_->Suggest(u"my number is ", 13);
   suggestion_handler_->VerifySuggestion(phone_number_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"my mobile number is: ", 21, 21);
+  suggester_->Suggest(u"my mobile number is: ", 21);
   suggestion_handler_->VerifySuggestion(phone_number_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"my number: ", 11, 11);
+  suggester_->Suggest(u"my number: ", 11);
   suggestion_handler_->VerifySuggestion(phone_number_, 0);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
 
-  suggester_->Suggest(u"my telephone number is ", 23, 23);
+  suggester_->Suggest(u"my telephone number is ", 23);
   suggestion_handler_->VerifySuggestion(phone_number_, 0);
 }
 
@@ -533,7 +533,7 @@
       autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER, phone_number_);
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"my phone number is ", 20, 20);
+  suggester_->Suggest(u"my phone number is ", 20);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 }
 
@@ -550,16 +550,16 @@
       autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER, phone_number_);
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"our phone number is ", 20, 20);
+  suggester_->Suggest(u"our phone number is ", 20);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"my number ", 10, 10);
+  suggester_->Suggest(u"my number ", 10);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"my number phone is: ", 20, 20);
+  suggester_->Suggest(u"my number phone is: ", 20);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 
-  suggester_->Suggest(u"my phone phone: ", 16, 16);
+  suggester_->Suggest(u"my phone phone: ", 16);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
 }
 
@@ -571,7 +571,7 @@
 
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   SendKeyboardEvent(ui::DomCode::ARROW_DOWN);
   SendKeyboardEvent(ui::DomCode::ENTER);
 
@@ -590,7 +590,7 @@
   update->SetIntKey(kPersonalInfoSuggesterAcceptanceCount, 1);
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   SendKeyboardEvent(ui::DomCode::ARROW_UP);
   SendKeyboardEvent(ui::DomCode::ENTER);
 
@@ -609,7 +609,7 @@
   autofill_profile.SetRawInfo(autofill::ServerFieldType::NAME_FULL, full_name_);
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"my name is ", 11, 11);
+  suggester_->Suggest(u"my name is ", 11);
   SendKeyboardEvent(ui::DomCode::ESCAPE);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
   EXPECT_FALSE(suggestion_handler_->IsSuggestionAccepted());
@@ -627,8 +627,8 @@
       autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER, phone_number_);
   personal_data_->AddProfile(autofill_profile);
 
-  suggester_->Suggest(u"my phone number is ", 19, 19);
-  suggester_->Suggest(u"my phone number is 16", 21, 21);
+  suggester_->Suggest(u"my phone number is ", 19);
+  suggester_->Suggest(u"my phone number is 16", 21);
   suggestion_handler_->VerifySuggestion(phone_number_, 2);
 }
 
@@ -642,7 +642,7 @@
   profile_->GetPrefs()->SetBoolean(
       ash::prefs::kAccessibilitySpokenFeedbackEnabled, true);
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   task_environment_.FastForwardBy(base::Milliseconds(200));
   suggestion_handler_->VerifyAnnouncement(
       u"Personal info suggested. Press down arrow to access; escape to "
@@ -653,7 +653,7 @@
   task_environment_.FastForwardBy(base::Milliseconds(200));
   suggestion_handler_->VerifyAnnouncement(u"Suggestion inserted.");
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   task_environment_.FastForwardBy(base::Milliseconds(1500));
   suggestion_handler_->VerifyAnnouncement(
       u"Personal info suggested. Press down arrow to access; escape to "
@@ -670,12 +670,12 @@
       /*disabled_features=*/{});
 
   for (int i = 0; i < kMaxAcceptanceCount; i++) {
-    suggester_->Suggest(u"my email is ", 12, 12);
+    suggester_->Suggest(u"my email is ", 12);
     SendKeyboardEvent(ui::DomCode::ARROW_DOWN);
     SendKeyboardEvent(ui::DomCode::ENTER);
     suggestion_handler_->VerifyShowAnnotation(true);
   }
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   suggestion_handler_->VerifyShowAnnotation(false);
 }
 
@@ -690,12 +690,12 @@
   update->RemoveKey(kPersonalInfoSuggesterShowSettingCount);
   update->RemoveKey(kPersonalInfoSuggesterAcceptanceCount);
   for (int i = 0; i < kMaxShowSettingCount; i++) {
-    suggester_->Suggest(u"my email is ", 12, 12);
+    suggester_->Suggest(u"my email is ", 12);
     // Dismiss suggestion.
     SendKeyboardEvent(ui::DomCode::ESCAPE);
     suggestion_handler_->VerifyShowSettingLink(true);
   }
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   suggestion_handler_->VerifyShowSettingLink(false);
 }
 
@@ -708,12 +708,12 @@
   DictionaryPrefUpdate update(profile_->GetPrefs(),
                               prefs::kAssistiveInputFeatureSettings);
   update->SetIntKey(kPersonalInfoSuggesterShowSettingCount, 0);
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   suggestion_handler_->VerifyShowSettingLink(true);
   // Accept suggestion.
   SendKeyboardEvent(ui::DomCode::ARROW_DOWN);
   SendKeyboardEvent(ui::DomCode::ENTER);
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   suggestion_handler_->VerifyShowSettingLink(false);
 }
 
@@ -729,7 +729,7 @@
   update->RemoveKey(kPersonalInfoSuggesterAcceptanceCount);
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   SendKeyboardEvent(ui::DomCode::ARROW_DOWN);
   SendKeyboardEvent(ui::DomCode::ARROW_DOWN);
   SendKeyboardEvent(ui::DomCode::ENTER);
@@ -750,7 +750,7 @@
   update->RemoveKey(kPersonalInfoSuggesterAcceptanceCount);
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   SendKeyboardEvent(ui::DomCode::ARROW_UP);
   SendKeyboardEvent(ui::DomCode::ENTER);
 
@@ -767,7 +767,7 @@
 
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   suggestion_handler_->VerifySuggestion(email_, 0);
   EXPECT_TRUE(suggester_->HasSuggestions());
 }
@@ -781,7 +781,7 @@
 
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"", 0, 0);
+  suggester_->Suggest(u"", 0);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
   EXPECT_FALSE(suggester_->HasSuggestions());
 }
@@ -795,7 +795,7 @@
 
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"my email is ", 12, 12);
+  suggester_->Suggest(u"my email is ", 12);
   suggestion_handler_->VerifySuggestion(email_, 0);
   EXPECT_EQ(suggester_->GetSuggestions(),
             (std::vector<TextSuggestion>{TextSuggestion{
@@ -813,7 +813,7 @@
 
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
-  suggester_->Suggest(u"", 0, 0);
+  suggester_->Suggest(u"", 0);
   suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
   EXPECT_TRUE(suggester_->GetSuggestions().empty());
 }
diff --git a/chrome/browser/ash/input_method/suggester.h b/chrome/browser/ash/input_method/suggester.h
index 35aa2a2..d202bf0 100644
--- a/chrome/browser/ash/input_method/suggester.h
+++ b/chrome/browser/ash/input_method/suggester.h
@@ -34,9 +34,7 @@
 
   // Check if suggestion should be displayed according to the surrounding text
   // information.
-  virtual bool Suggest(const std::u16string& text,
-                       size_t cursor_pos,
-                       size_t anchor_pos) = 0;
+  virtual bool Suggest(const std::u16string& text, size_t cursor_pos) = 0;
 
   // Accepts the suggestion at a given index, index can be made default if
   // unnecessary. Returns true if suggestion is accepted successfully.
diff --git a/chrome/browser/ash/policy/networking/euicc_status_uploader.cc b/chrome/browser/ash/policy/networking/euicc_status_uploader.cc
index 85197fa..bd5f73b 100644
--- a/chrome/browser/ash/policy/networking/euicc_status_uploader.cc
+++ b/chrome/browser/ash/policy/networking/euicc_status_uploader.cc
@@ -8,9 +8,9 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/ash/settings/device_settings_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chromeos/dbus/hermes/hermes_manager_client.h"
 #include "chromeos/network/managed_network_configuration_handler.h"
 #include "chromeos/network/network_event_log.h"
 #include "chromeos/network/network_handler.h"
@@ -52,6 +52,15 @@
     // Starts with initial delay.
     true,
 };
+
+// Returns whether the device's policy data is active and provisioned.
+bool IsDeviceManaged() {
+  return ::ash::DeviceSettingsService::IsInitialized() &&
+         ::ash::DeviceSettingsService::Get()->policy_data() &&
+         ::ash::DeviceSettingsService::Get()->policy_data()->state() ==
+             enterprise_management::PolicyData::ACTIVE;
+}
+
 }  // namespace
 
 // static
@@ -62,25 +71,33 @@
 
 EuiccStatusUploader::EuiccStatusUploader(CloudPolicyClient* client,
                                          PrefService* local_state)
+    : EuiccStatusUploader(client,
+                          local_state,
+                          base::BindRepeating(&IsDeviceManaged)) {}
+
+EuiccStatusUploader::EuiccStatusUploader(
+    CloudPolicyClient* client,
+    PrefService* local_state,
+    IsDeviceActiveCallback is_device_active_callback)
     : client_(client),
       local_state_(local_state),
+      is_device_managed_callback_(std::move(is_device_active_callback)),
       retry_entry_(&kBackOffPolicy) {
   if (!chromeos::NetworkHandler::IsInitialized()) {
     LOG(WARNING) << "NetworkHandler is not initialized.";
     return;
   }
+
+  hermes_manager_observation_.Observe(chromeos::HermesManagerClient::Get());
+  hermes_euicc_observation_.Observe(chromeos::HermesEuiccClient::Get());
+  cloud_policy_client_observation_.Observe(client_);
+
   network_handler_ = chromeos::NetworkHandler::Get();
   network_handler_->managed_network_configuration_handler()->AddObserver(this);
-  chromeos::HermesEuiccClient::Get()->AddObserver(this);
   network_handler_->network_state_handler()->AddObserver(this, FROM_HERE);
-
-  MaybeUploadStatus();
 }
 
 EuiccStatusUploader::~EuiccStatusUploader() {
-  if (chromeos::HermesEuiccClient::Get())
-    chromeos::HermesEuiccClient::Get()->RemoveObserver(this);
-
   if (network_handler_)
     OnShuttingDown();
 }
@@ -125,6 +142,19 @@
   network_handler_ = nullptr;
 }
 
+void EuiccStatusUploader::OnRegistrationStateChanged(
+    CloudPolicyClient* client) {
+  MaybeUploadStatus();
+}
+
+void EuiccStatusUploader::OnPolicyFetched(CloudPolicyClient* client) {
+  if (is_policy_fetched_) {
+    return;
+  }
+  is_policy_fetched_ = true;
+  MaybeUploadStatus();
+}
+
 void EuiccStatusUploader::PoliciesApplied(const std::string& userhash) {
   MaybeUploadStatus();
 }
@@ -133,6 +163,10 @@
   MaybeUploadStatus();
 }
 
+void EuiccStatusUploader::OnAvailableEuiccListChanged() {
+  MaybeUploadStatus();
+}
+
 void EuiccStatusUploader::OnEuiccReset(const dbus::ObjectPath& euicc_path) {
   // Remember that we should clear the profile list in the next upload. This
   // ensures that profile list will be eventually cleared even if the immediate
@@ -141,7 +175,7 @@
   MaybeUploadStatus();
 }
 
-base::Value EuiccStatusUploader::GetCurrentEuiccStatus() {
+base::Value EuiccStatusUploader::GetCurrentEuiccStatus() const {
   base::Value status(base::Value::Type::DICTIONARY);
 
   status.SetIntKey(
@@ -153,7 +187,7 @@
   chromeos::NetworkStateHandler::NetworkStateList networks;
   network_handler_->network_state_handler()->GetNetworkListByType(
       ash::NetworkTypePattern::Cellular(),
-      /*configure_only=*/false, /*visible_only=*/false,
+      /*configured_only=*/false, /*visible_only=*/false,
       /*limit=*/0, &networks);
 
   onc::ONCSource onc_source = onc::ONC_SOURCE_NONE;
@@ -205,10 +239,31 @@
 }
 
 void EuiccStatusUploader::MaybeUploadStatus() {
+  if (!client_->is_registered()) {
+    VLOG(1) << "Policy client is not registered.";
+    return;
+  }
+
+  if (!is_policy_fetched_) {
+    VLOG(1) << "Policy not fetched yet.";
+    return;
+  }
+
+  if (!is_device_managed_callback_.Run()) {
+    VLOG(1) << "Device is unmanaged or deprovisioned.";
+    return;
+  }
+
   if (!network_handler_) {
     LOG(WARNING) << "NetworkHandler is not initialized.";
     return;
   }
+
+  if (chromeos::HermesManagerClient::Get()->GetAvailableEuiccs().empty()) {
+    VLOG(1) << "No EUICC available on the device.";
+    return;
+  }
+
   const base::Value* last_uploaded_pref =
       local_state_->Get(kLastUploadedEuiccStatusPref);
   auto current_state = GetCurrentEuiccStatus();
diff --git a/chrome/browser/ash/policy/networking/euicc_status_uploader.h b/chrome/browser/ash/policy/networking/euicc_status_uploader.h
index c537fea..a76e4c19 100644
--- a/chrome/browser/ash/policy/networking/euicc_status_uploader.h
+++ b/chrome/browser/ash/policy/networking/euicc_status_uploader.h
@@ -8,9 +8,11 @@
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
 #include "chromeos/dbus/hermes/hermes_euicc_client.h"
+#include "chromeos/dbus/hermes/hermes_manager_client.h"
 #include "chromeos/network/network_policy_observer.h"
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_handler_observer.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "net/base/backoff_entry.h"
 
 class PrefService;
@@ -26,13 +28,13 @@
 
 namespace policy {
 
-class CloudPolicyClient;
-
 // Class responsible for uploading the information about the current ESim
 // profiles to DMServer.
 class EuiccStatusUploader : public chromeos::NetworkPolicyObserver,
                             public chromeos::NetworkStateHandlerObserver,
-                            public chromeos::HermesEuiccClient::Observer {
+                            public chromeos::HermesManagerClient::Observer,
+                            public chromeos::HermesEuiccClient::Observer,
+                            public CloudPolicyClient::Observer {
  public:
   EuiccStatusUploader(CloudPolicyClient* client, PrefService* local_state);
   ~EuiccStatusUploader() override;
@@ -42,6 +44,13 @@
  private:
   friend class EuiccStatusUploaderTest;
 
+  // Callback used in tests to mock out check for device provisioning state.
+  using IsDeviceActiveCallback = base::RepeatingCallback<bool()>;
+
+  EuiccStatusUploader(CloudPolicyClient* client,
+                      PrefService* local_state,
+                      IsDeviceActiveCallback is_device_managed_callback);
+
   // A local state preference that stores the last uploaded Euicc status in such
   // format:
   // {
@@ -70,10 +79,20 @@
   void NetworkListChanged() override;
   void OnShuttingDown() override;
 
+  // CloudPolicyClient::Observer:
+  void OnRegistrationStateChanged(CloudPolicyClient* client) override;
+  void OnPolicyFetched(CloudPolicyClient* client) override;
+  void OnClientError(CloudPolicyClient* client) override {}
+  void OnServiceAccountSet(CloudPolicyClient* client,
+                           const std::string& account_email) override {}
+
+  // chromeos::HermesManagerClient:
+  void OnAvailableEuiccListChanged() override;
+
   // chromeos::HermesEuiccClient:
   void OnEuiccReset(const dbus::ObjectPath& euicc_path) override;
 
-  base::Value GetCurrentEuiccStatus();
+  base::Value GetCurrentEuiccStatus() const;
   void MaybeUploadStatus();
   void MaybeUploadStatusWithDelay();
   void UploadStatus(base::Value status);
@@ -89,11 +108,22 @@
   bool currently_uploading_ = false;
   // The status that is being uploaded right now.
   base::Value attempted_upload_status_{base::Value::Type::DICTIONARY};
+  bool is_policy_fetched_ = false;
+  IsDeviceActiveCallback is_device_managed_callback_;
 
   // Timer which will try to reupload the status after a while.
   std::unique_ptr<base::OneShotTimer> retry_timer_;
   net::BackoffEntry retry_entry_;
 
+  base::ScopedObservation<chromeos::HermesManagerClient,
+                          chromeos::HermesManagerClient::Observer>
+      hermes_manager_observation_{this};
+  base::ScopedObservation<chromeos::HermesEuiccClient,
+                          chromeos::HermesEuiccClient::Observer>
+      hermes_euicc_observation_{this};
+  base::ScopedObservation<CloudPolicyClient, CloudPolicyClient::Observer>
+      cloud_policy_client_observation_{this};
+
   chromeos::NetworkHandler* network_handler_ = nullptr;
 
   base::WeakPtrFactory<EuiccStatusUploader> weak_ptr_factory_{this};
diff --git a/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc b/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc
index 2eb385b2..8a042159 100644
--- a/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc
+++ b/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "ash/constants/ash_pref_names.h"
 #include "base/json/json_string_value_serializer.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/values_test_util.h"
 #include "chrome/test/base/testing_browser_process.h"
@@ -64,6 +65,7 @@
          lhs.clear_profile_list() == rhs.clear_profile_list();
 }
 
+const char kDmToken[] = "token";
 const char kFakeObjectPath[] = "object-path";
 const char kFakeEid[] = "12";
 const char kEuiccStatusUploadResultHistogram[] =
@@ -138,11 +140,27 @@
     EuiccStatusUploader::RegisterLocalStatePrefs(local_state_.registry());
     helper_->RegisterPrefs(nullptr, local_state_.registry());
     helper_->InitializePrefs(nullptr, &local_state_);
+    SetPolicyClientIsRegistered(/*is_registered=*/true);
   }
 
-  std::unique_ptr<EuiccStatusUploader> CreateStatusUploader() {
-    return std::make_unique<EuiccStatusUploader>(&cloud_policy_client_,
-                                                 &local_state_);
+  std::unique_ptr<EuiccStatusUploader> CreateStatusUploader(
+      bool is_policy_fetched = true) {
+    auto status_uploader = base::WrapUnique(new EuiccStatusUploader(
+        &cloud_policy_client_, &local_state_,
+        base::BindRepeating(&EuiccStatusUploaderTest::is_device_active,
+                            base::Unretained(this))));
+    if (is_policy_fetched) {
+      SetPolicyFetched(status_uploader.get());
+    }
+    return status_uploader;
+  }
+
+  void SetPolicyClientIsRegistered(bool is_registered) {
+    cloud_policy_client_.dm_token_ = is_registered ? kDmToken : std::string();
+  }
+
+  void SetPolicyFetched(EuiccStatusUploader* status_uploader) {
+    status_uploader->OnPolicyFetched(&cloud_policy_client_);
   }
 
   void SetServerSuccessStatus(bool success) {
@@ -167,13 +185,18 @@
     status_uploader->FireRetryTimerIfExistsForTesting();
   }
 
+  void SetupEuicc(int euicc_id = 0) {
+    chromeos::HermesManagerClient::Get()->GetTestInterface()->AddEuicc(
+        dbus::ObjectPath(base::StringPrintf("%s%d", kFakeObjectPath, euicc_id)),
+        base::StringPrintf("%s%d", kFakeEid, euicc_id), /*is_active=*/true,
+        euicc_id);
+  }
+
   void SetUpDeviceProfiles(const EuiccTestData& data, bool add_to_onc = true) {
     // Create |data.euicc_count| fake EUICCs.
     chromeos::HermesManagerClient::Get()->GetTestInterface()->ClearEuiccs();
     for (int euicc_id = 0; euicc_id < data.euicc_count; euicc_id++) {
-      chromeos::HermesManagerClient::Get()->GetTestInterface()->AddEuicc(
-          dbus::ObjectPath(kFakeObjectPath), kFakeEid, /*is_active=*/true,
-          euicc_id);
+      SetupEuicc(euicc_id);
     }
 
     ash::ShillServiceClient::TestInterface* shill_service_client =
@@ -261,7 +284,12 @@
                                         /*expected_count=*/failed_count);
   }
 
+  void SetIsDeviceActive(bool value) { is_device_active_ = value; }
+
  private:
+  bool is_device_active() { return is_device_active_; }
+
+  bool is_device_active_ = true;
   content::BrowserTaskEnvironment task_environment_;
   FakeCloudPolicyClient cloud_policy_client_;
   TestingPrefServiceSimple local_state_;
@@ -271,29 +299,54 @@
 
 TEST_F(EuiccStatusUploaderTest, EmptySetup) {
   auto status_uploader = CreateStatusUploader();
-  // Initial upload request.
-  EXPECT_EQ(GetRequestCount(), 1);
+  EXPECT_EQ(GetRequestCount(), 0);
   // No value is uploaded yet.
   EXPECT_EQ("{}", GetStoredPrefString());
-  CheckHistogram(/*total_count=*/1, /*success_count=*/0, /*failed_count=*/1);
 
   // Make server accept requests.
   SetServerSuccessStatus(true);
   UpdateUploader(status_uploader.get());
-  EXPECT_EQ(GetRequestCount(), 2);
-  // Verify that last uploaded configuration is stored.
-  ValidateUploadedStatus(kEmptyEuiccStatus, /*clear_profile_list=*/false);
-  CheckHistogram(/*total_count=*/2, /*success_count=*/1, /*failed_count=*/1);
+  // Verify that no status is uploaded if there is no EUICC.
+  EXPECT_EQ(GetRequestCount(), 0);
+  CheckHistogram(/*total_count=*/0, /*success_count=*/0, /*failed_count=*/0);
+}
+
+TEST_F(EuiccStatusUploaderTest, InactiveDevice) {
+  SetIsDeviceActive(false);
+  auto status_uploader = CreateStatusUploader();
+  EXPECT_EQ(GetRequestCount(), 0);
+  // No value is uploaded yet.
+  EXPECT_EQ("{}", GetStoredPrefString());
+
+  // Make server accept requests.
+  SetServerSuccessStatus(true);
+  UpdateUploader(status_uploader.get());
+  // Verify that no status is uploaded if the device is inactive.
+  EXPECT_EQ(GetRequestCount(), 0);
+  CheckHistogram(/*total_count=*/0, /*success_count=*/0, /*failed_count=*/0);
+}
+
+TEST_F(EuiccStatusUploaderTest, ClientNotRegistered) {
+  SetupEuicc();
+  base::RunLoop().RunUntilIdle();
+  SetPolicyClientIsRegistered(/*is_registered=*/false);
+
+  auto status_uploader = CreateStatusUploader();
+  EXPECT_EQ(GetRequestCount(), 0);
+  // No value is uploaded yet.
+  EXPECT_EQ("{}", GetStoredPrefString());
+
+  UpdateUploader(status_uploader.get());
+  // Verify that no requests are made if client is not registered.
+  EXPECT_EQ(GetRequestCount(), 0);
+  EXPECT_EQ("{}", GetStoredPrefString());
+  CheckHistogram(/*total_count=*/0, /*success_count=*/0, /*failed_count=*/0);
 }
 
 TEST_F(EuiccStatusUploaderTest, ServerError) {
+  SetupEuicc();
+  base::RunLoop().RunUntilIdle();
   auto status_uploader = CreateStatusUploader();
-  // Initial upload request.
-  EXPECT_EQ(GetRequestCount(), 1);
-  // No value is uploaded yet.
-  EXPECT_EQ("{}", GetStoredPrefString());
-  CheckHistogram(/*total_count=*/1, /*success_count=*/0, /*failed_count=*/1);
-
   UpdateUploader(status_uploader.get());
   EXPECT_EQ(GetRequestCount(), 2);
   // Nothing is stored when requests fail.
@@ -301,6 +354,26 @@
   CheckHistogram(/*total_count=*/2, /*success_count=*/0, /*failed_count=*/2);
 }
 
+TEST_F(EuiccStatusUploaderTest, WaitForPolicyFetch) {
+  SetUpDeviceProfiles(kSetupOneEsimProfile);
+
+  auto status_uploader = CreateStatusUploader(/*is_policy_fetched=*/false);
+  EXPECT_EQ(GetRequestCount(), 0);
+  // No value is uploaded yet.
+  EXPECT_EQ("{}", GetStoredPrefString());
+
+  // Verify that no requests are made when policy has not been fetched.
+  SetServerSuccessStatus(true);
+  UpdateUploader(status_uploader.get());
+  EXPECT_EQ(GetRequestCount(), 0);
+
+  // Verify that status is uploaded correctly when policy is fetched.
+  SetPolicyFetched(status_uploader.get());
+  ValidateUploadedStatus(kEuiccStatusWithOneProfile,
+                         /*clear_profile_list=*/false);
+  CheckHistogram(/*total_count=*/1, /*success_count=*/1, /*failed_count=*/0);
+}
+
 TEST_F(EuiccStatusUploaderTest, Basic) {
   SetUpDeviceProfiles(kSetupOneEsimProfile);
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index c334140..1b9dc4e 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -4230,6 +4230,7 @@
     "../ash/drive/drive_integration_service_unittest.cc",
     "../ash/drive/drivefs_native_message_host_unittest.cc",
     "../ash/drive/file_system_util_unittest.cc",
+    "../ash/eche_app/eche_app_manager_factory_unittest.cc",
     "../ash/eche_app/eche_app_notification_controller_unittest.cc",
     "../ash/enhanced_network_tts/enhanced_network_tts_impl_unittest.cc",
     "../ash/enhanced_network_tts/enhanced_network_tts_test_utils.cc",
diff --git a/chrome/browser/chromeos/extensions/device_local_account_management_policy_provider_unittest.cc b/chrome/browser/chromeos/extensions/device_local_account_management_policy_provider_unittest.cc
index ce5f88d..32cdbfa7 100644
--- a/chrome/browser/chromeos/extensions/device_local_account_management_policy_provider_unittest.cc
+++ b/chrome/browser/chromeos/extensions/device_local_account_management_policy_provider_unittest.cc
@@ -456,15 +456,15 @@
   // Verify that a platform app with socket dictionary permission can be
   // installed.
   {
-    auto socket = std::make_unique<base::DictionaryValue>();
-    base::ListValue tcp_list;
+    base::Value::Dict socket;
+    base::Value::List tcp_list;
     tcp_list.Append("tcp-connect");
-    socket->SetKey("socket", std::move(tcp_list));
-    base::ListValue permissions;
+    socket.Set("socket", std::move(tcp_list));
+    base::Value::List permissions;
     permissions.Append(std::move(socket));
     base::DictionaryValue values;
     values.SetKey(extensions::manifest_keys::kPermissions,
-                  std::move(permissions));
+                  base::Value(std::move(permissions)));
 
     extension = CreatePlatformAppWithExtraValues(
         &values, ManifestLocation::kExternalPolicy,
@@ -479,15 +479,15 @@
   // Verify that a platform app with unknown dictionary permission cannot be
   // installed.
   {
-    auto socket = std::make_unique<base::DictionaryValue>();
-    base::ListValue tcp_list;
+    base::Value::Dict socket;
+    base::Value::List tcp_list;
     tcp_list.Append("unknown_value");
-    socket->SetKey("unknown_key", std::move(tcp_list));
-    base::ListValue permissions;
+    socket.Set("unknown_key", std::move(tcp_list));
+    base::Value::List permissions;
     permissions.Append(std::move(socket));
     base::DictionaryValue values;
     values.SetKey(extensions::manifest_keys::kPermissions,
-                  std::move(permissions));
+                  base::Value(std::move(permissions)));
 
     extension = CreatePlatformAppWithExtraValues(
         &values, ManifestLocation::kExternalPolicy,
diff --git a/chrome/browser/chromeos/extensions/dictionary_event_router.cc b/chrome/browser/chromeos/extensions/dictionary_event_router.cc
index ff39579c..28289cde 100644
--- a/chrome/browser/chromeos/extensions/dictionary_event_router.cc
+++ b/chrome/browser/chromeos/extensions/dictionary_event_router.cc
@@ -71,23 +71,25 @@
     return;
   }
 
-  std::unique_ptr<base::ListValue> added_words(new base::ListValue());
+  base::Value::List added_words;
+  added_words.reserve(dictionary_change.to_add().size());
   for (const std::string& word : dictionary_change.to_add())
-    added_words->Append(word);
+    added_words.Append(word);
 
-  std::unique_ptr<base::ListValue> removed_words(new base::ListValue());
+  base::Value::List removed_words;
+  removed_words.reserve(dictionary_change.to_remove().size());
   for (const std::string& word : dictionary_change.to_remove())
-    removed_words->Append(word);
+    removed_words.Append(word);
 
-  std::unique_ptr<base::ListValue> args(new base::ListValue());
-  args->Append(std::move(added_words));
-  args->Append(std::move(removed_words));
+  base::Value::List args;
+  args.Append(std::move(added_words));
+  args.Append(std::move(removed_words));
 
   // The router will only send the event to extensions that are listening.
   auto event = std::make_unique<extensions::Event>(
       extensions::events::INPUT_METHOD_PRIVATE_ON_DICTIONARY_CHANGED,
-      OnDictionaryChanged::kEventName, std::move(*args).TakeListDeprecated(),
-      context_);
+      OnDictionaryChanged::kEventName,
+      base::Value(std::move(args)).TakeListDeprecated(), context_);
   router->BroadcastEvent(std::move(event));
 }
 
diff --git a/chrome/browser/chromeos/extensions/input_method_api.cc b/chrome/browser/chromeos/extensions/input_method_api.cc
index b4bf6a9..08f37b1 100644
--- a/chrome/browser/chromeos/extensions/input_method_api.cc
+++ b/chrome/browser/chromeos/extensions/input_method_api.cc
@@ -180,7 +180,7 @@
 
 ExtensionFunction::ResponseAction
 InputMethodPrivateGetInputMethodsFunction::Run() {
-  std::unique_ptr<base::ListValue> output(new base::ListValue());
+  base::Value::List output;
   auto* manager = ash::input_method::InputMethodManager::Get();
   ash::input_method::InputMethodUtil* util = manager->GetInputMethodUtil();
   scoped_refptr<ash::input_method::InputMethodManager::State> ime_state =
@@ -190,14 +190,13 @@
   for (size_t i = 0; i < input_methods->size(); ++i) {
     const ash::input_method::InputMethodDescriptor& input_method =
         (*input_methods)[i];
-    auto val = std::make_unique<base::DictionaryValue>();
-    val->SetStringKey("id", input_method.id());
-    val->SetStringKey("name", util->GetInputMethodLongName(input_method));
-    val->SetStringKey("indicator", input_method.GetIndicator());
-    output->Append(std::move(val));
+    base::Value::Dict val;
+    val.Set("id", input_method.id());
+    val.Set("name", util->GetInputMethodLongName(input_method));
+    val.Set("indicator", input_method.GetIndicator());
+    output.Append(std::move(val));
   }
-  return RespondNow(
-      OneArgument(base::Value::FromUniquePtrValue(std::move(output))));
+  return RespondNow(OneArgument(base::Value(std::move(output))));
 }
 
 ExtensionFunction::ResponseAction
@@ -590,8 +589,6 @@
   if (!engine->InputMethodEngine::SetAutocorrectRange(
           params.context_id,
           gfx::Range(params.selection_start, params.selection_end), &error)) {
-    auto results = std::make_unique<base::ListValue>();
-    results->Append(std::make_unique<base::Value>(false));
     return RespondNow(Error(InformativeError(error, static_function_name())));
   }
   return RespondNow(NoArguments());
@@ -614,7 +611,7 @@
           params.context_id, *params.selection_start, *params.selection_end,
           &error)) {
     auto results = std::make_unique<base::ListValue>();
-    results->Append(std::make_unique<base::Value>(false));
+    results->Append(false);
     return RespondNow(ErrorWithArguments(
         std::move(results), InformativeError(error, static_function_name())));
   }
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h b/chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h
index 0d40e4b..ef3cf96 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h
+++ b/chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h
@@ -57,24 +57,11 @@
   };
 
   DlpWarnDialog() = delete;
+  DlpWarnDialog(OnDlpRestrictionCheckedCallback callback,
+                DlpWarnDialogOptions options);
   DlpWarnDialog(const DlpWarnDialog& other) = delete;
   DlpWarnDialog& operator=(const DlpWarnDialog& other) = delete;
   ~DlpWarnDialog() override = default;
-
- private:
-  // DlpWarnNotifier is used to create and show DlpWarnDialogs and should be the
-  // only way to do that. Therefore it needs access to some private members of
-  // the DlpWarnDialog class, such as the options and restriction types, as well
-  // as the constructor.
-  friend class DlpWarnNotifier;
-
-  // Helper method to create and show a warning dialog for a given
-  // |restriction|.
-  static void ShowDlpWarningDialog(OnDlpRestrictionCheckedCallback callback,
-                                   DlpWarnDialogOptions options);
-
-  DlpWarnDialog(OnDlpRestrictionCheckedCallback callback,
-                DlpWarnDialogOptions options);
 };
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.cc b/chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.cc
index 1b5bc68..6097cf14 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.cc
@@ -74,7 +74,7 @@
     OnDlpRestrictionCheckedCallback callback,
     DlpWarnDialog::DlpWarnDialogOptions options) {
   views::Widget* widget = views::DialogDelegate::CreateDialogWidget(
-      new DlpWarnDialog(std::move(callback), options),
+      std::make_unique<DlpWarnDialog>(std::move(callback), options),
       /*context=*/nullptr, /*parent=*/nullptr);
   widget->Show();
   // We disable the dialog's hide animations after showing it so that it doesn't
diff --git a/chrome/browser/devtools/devtools_file_system_indexer.cc b/chrome/browser/devtools/devtools_file_system_indexer.cc
index 82e5822..cd876e0e 100644
--- a/chrome/browser/devtools/devtools_file_system_indexer.cc
+++ b/chrome/browser/devtools/devtools_file_system_indexer.cc
@@ -8,6 +8,7 @@
 
 #include <iterator>
 #include <memory>
+#include <set>
 
 #include "base/bind.h"
 #include "base/callback.h"
diff --git a/chrome/browser/extensions/external_pref_loader.cc b/chrome/browser/extensions/external_pref_loader.cc
index 0c2204ff..0267a80 100644
--- a/chrome/browser/extensions/external_pref_loader.cc
+++ b/chrome/browser/extensions/external_pref_loader.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/extensions/external_pref_loader.h"
 
+#include <set>
 #include <utility>
 
 #include "base/bind.h"
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index fb2833c..e89fffc 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1878,7 +1878,7 @@
   },
   {
     "name": "enable-debug-for-store-billing",
-    "owners": [ "maxlg", "web-payments-team@google.com" ],
+    "owners": [ "web-payments-team@google.com" ],
     // This flag is to allow developers to develop their app-billing capable
     // TWA apps locally, without having to upload the apps to the Play Store.
     "expiry_milestone": -1
@@ -2586,7 +2586,7 @@
   },
   {
     "name": "enable-payment-request-basic-card",
-    "owners": [ "maxlg", "web-payments-team@google.com" ],
+    "owners": [ "web-payments-team@google.com" ],
     // This flag is used by developers to disable the "basic-card" method, to
     // make sure their web pages do not break after Chrome deprecates
     // "basic-card" in M100.
@@ -2760,7 +2760,7 @@
   },
   {
     "name": "enable-secure-payment-confirmation-android",
-    "owners": [ "maxlg", "web-payments-team@google.com" ],
+    "owners": [ "web-payments-team@google.com" ],
     "expiry_milestone": 102
   },
   {
@@ -2785,6 +2785,11 @@
     "expiry_milestone": -1
   },
   {
+    "name": "enable-show-scrollable-mvt-on-ntp",
+    "owners": ["clank-start","hanxi","spdonghao"],
+    "expiry_milestone": 110
+  },
+  {
     "name": "enable-site-isolation-for-password-sites",
     "owners": [ "site-isolation-dev", "alexmos", "lukasza" ],
     // Note: while password-triggered site isolation launched in M77, it only
@@ -5117,6 +5122,11 @@
     "expiry_milestone": 102
   },
   {
+    "name": "read-printer-capabilities-with-xps",
+    "owners": [ "awscreen", "//printing/OWNERS" ],
+    "expiry_milestone": 106
+  },
+  {
     "name": "reader-mode-heuristics",
     "owners": [ "mdjones", "wychen" ],
     // This flag is a utility for testing Reader Mode heuristics or force
@@ -5410,12 +5420,12 @@
   {
     "name": "sharing-desktop-screenshots",
     "owners": ["skare", "kmilka", "chrome-sharing-eng@google.com" ],
-    "expiry_milestone": 99
+    "expiry_milestone": 102
   },
   {
     "name": "sharing-desktop-screenshots-edit",
     "owners": ["skare", "jeffreycohen", "chrome-sharing-eng@google.com" ],
-    "expiry_milestone": 100
+    "expiry_milestone": 102
   },
   {
     "name": "sharing-hub-desktop-app-menu",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index f47a94a..6b59512 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3549,6 +3549,10 @@
     " On tablets with small screens a mobile site will be requested by "
     "default.";
 
+const char kShowScrollableMVTOnNTPAndroidName[] = "Show scrollable MVT on NTP";
+const char kShowScrollableMVTOnNTPAndroidDescription[] =
+    "Enable showing the scrollable most visited tiles on NTP.";
+
 const char kSecurePaymentConfirmationAndroidName[] =
     "Secure Payment Confirmation on Android";
 const char kSecurePaymentConfirmationAndroidDescription[] =
@@ -4009,6 +4013,11 @@
 const char kPrintWithReducedRasterizationDescription[] =
     "When using GDI printing, avoid rasterization if possible.";
 
+const char kReadPrinterCapabilitiesWithXpsName[] =
+    "Read printer capabilities with XPS";
+extern const char kReadPrinterCapabilitiesWithXpsDescription[] =
+    "When enabled, utilize XPS interface to read printer capabilities.";
+
 const char kUseXpsForPrintingName[] = "Use XPS for printing";
 const char kUseXpsForPrintingDescription[] =
     "When enabled, use XPS printing API instead of the GDI print API.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index cbddde18..3511e76c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2030,6 +2030,9 @@
 extern const char kSecurePaymentConfirmationAndroidName[];
 extern const char kSecurePaymentConfirmationAndroidDescription[];
 
+extern const char kShowScrollableMVTOnNTPAndroidName[];
+extern const char kShowScrollableMVTOnNTPAndroidDescription[];
+
 extern const char kSendTabToSelfV2Name[];
 extern const char kSendTabToSelfV2Description[];
 
@@ -2291,6 +2294,9 @@
 extern const char kPrintWithReducedRasterizationName[];
 extern const char kPrintWithReducedRasterizationDescription[];
 
+extern const char kReadPrinterCapabilitiesWithXpsName[];
+extern const char kReadPrinterCapabilitiesWithXpsDescription[];
+
 extern const char kUseXpsForPrintingName[];
 extern const char kUseXpsForPrintingDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 8f2bcae3c..53fc7b7 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -271,7 +271,7 @@
     &kServiceManagerForDownload,
     &kShareButtonInTopToolbar,
     &kSharedClipboardUI,
-    &kShowScrollableMVTOnNTP,
+    &kShowScrollableMVTOnNTPAndroid,
     &kSpannableInlineAutocomplete,
     &kSpecialLocaleWrapper,
     &kSpecialUserDecision,
@@ -749,8 +749,8 @@
 const base::Feature kShareButtonInTopToolbar{"ShareButtonInTopToolbar",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kShowScrollableMVTOnNTP{"ShowScrollableMVTOnNTP",
-                                            base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kShowScrollableMVTOnNTPAndroid{
+    "ShowScrollableMVTOnNTPAndroid", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kSpannableInlineAutocomplete{
     "SpannableInlineAutocomplete", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 4e28592..b00c9de 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -135,7 +135,7 @@
 extern const base::Feature kServiceManagerForDownload;
 extern const base::Feature kShareButtonInTopToolbar;
 extern const base::Feature kSharingHubLinkToggle;
-extern const base::Feature kShowScrollableMVTOnNTP;
+extern const base::Feature kShowScrollableMVTOnNTPAndroid;
 extern const base::Feature kSpannableInlineAutocomplete;
 extern const base::Feature kSpecialLocaleWrapper;
 extern const base::Feature kSpecialUserDecision;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
index f46e836..f0f9c65e 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
@@ -105,7 +105,6 @@
                     .put(ChromeFeatureList.GRID_TAB_SWITCHER_FOR_TABLETS, false)
                     .put(ChromeFeatureList.TAB_GROUPS_FOR_TABLETS, false)
                     .put(ChromeFeatureList.TAB_STRIP_IMPROVEMENTS, false)
-                    .put(ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP, false)
                     .build();
 
     /**
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 529e853..145a4e4b 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -483,7 +483,7 @@
     public static final String SHARED_HIGHLIGHTING_AMP = "SharedHighlightingAmp";
     public static final String SHOPPING_LIST = "ShoppingList";
     public static final String SHOW_EXTENDED_PRELOADING_SETTING = "ShowExtendedPreloadingSetting";
-    public static final String SHOW_SCROLLABLE_MVT_ON_NTP = "ShowScrollableMVTOnNTP";
+    public static final String SHOW_SCROLLABLE_MVT_ON_NTP_ANDROID = "ShowScrollableMVTOnNTPAndroid";
     public static final String SHOW_TRUSTED_PUBLISHER_URL = "ShowTrustedPublisherURL";
     public static final String SMART_SUGGESTION_FOR_LARGE_DOWNLOADS =
             "SmartSuggestionForLargeDownloads";
diff --git a/chrome/browser/permissions/notification_blocked_message_delegate_android.cc b/chrome/browser/permissions/notification_blocked_message_delegate_android.cc
index 84c34e8..c7040e9 100644
--- a/chrome/browser/permissions/notification_blocked_message_delegate_android.cc
+++ b/chrome/browser/permissions/notification_blocked_message_delegate_android.cc
@@ -177,7 +177,6 @@
   if (!permission_prompt_)
     return;
   permission_prompt_->Closing();
-  permission_prompt_.reset();
 }
 
 void NotificationBlockedMessageDelegate::Delegate::SetManageClicked() {
diff --git a/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc b/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
index 37b68c0..d685071 100644
--- a/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
@@ -22,6 +22,7 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/policy/messaging_layer/util/dm_token_retriever_provider.h"
+#include "chrome/browser/policy/messaging_layer/util/test.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "components/reporting/client/dm_token_retriever.h"
 #include "components/reporting/client/mock_dm_token_retriever.h"
@@ -406,7 +407,8 @@
 
   if (StorageSelector::is_uploader_required() &&
       !StorageSelector::is_use_missive()) {
-    EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+    EXPECT_CALL(*client_,
+                UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
         .WillOnce(WithArgs<0, 2>(Invoke(GetVerifyDataInvocation())));
   }
 
@@ -439,13 +441,16 @@
   if (StorageSelector::is_uploader_required() &&
       !StorageSelector::is_use_missive()) {
     // Note: there does not seem to be another way to define the expectations
-    // A+B for encrypted case and just B for non-encrypted.g
+    // A+B for encrypted case and just B for non-encrypted.
     if (is_encryption_enabled()) {
       EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
-          .WillOnce(WithArgs<0, 2>(Invoke(GetEncryptionKeyInvocation())))
+          .WillOnce(WithArgs<0, 2>(Invoke(GetEncryptionKeyInvocation())));
+      EXPECT_CALL(*client_,
+                  UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
           .WillOnce(WithArgs<0, 2>(Invoke(GetVerifyDataInvocation())));
     } else {
-      EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+      EXPECT_CALL(*client_,
+                  UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
           .WillOnce(WithArgs<0, 2>(Invoke(GetVerifyDataInvocation())));
     }
   }
diff --git a/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc b/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
index 6491d32..16c4f76 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
@@ -18,6 +18,7 @@
 #include "base/values.h"
 #include "chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.h"
 #include "chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h"
+#include "chrome/browser/policy/messaging_layer/util/test.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "components/policy/core/common/cloud/dm_token.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
@@ -258,7 +259,7 @@
       .sequence_information = test_records->back().sequence_information(),
       .force_confirm = force_confirm()};
 
-  EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+  EXPECT_CALL(*client_, UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
       .WillOnce(WithArgs<0, 2>(
           Invoke([&force_confirm_by_server](
                      base::Value::Dict request,
@@ -294,7 +295,7 @@
   auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId);
   const auto force_confirm_by_server = force_confirm();
 
-  EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+  EXPECT_CALL(*client_, UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
       .WillOnce(WithArgs<0, 2>(
           Invoke([&force_confirm_by_server](
                      base::Value::Dict request,
@@ -323,7 +324,7 @@
   auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId);
   const auto force_confirm_by_server = force_confirm();
 
-  EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+  EXPECT_CALL(*client_, UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
       .WillOnce(WithArgs<0, 2>(
           Invoke([&force_confirm_by_server](
                      base::Value::Dict request,
@@ -351,7 +352,7 @@
   static constexpr int64_t kGenerationId = 1234;
   auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId);
 
-  EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+  EXPECT_CALL(*client_, UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
       .WillOnce(WithArgs<2>(Invoke(
           [](base::OnceCallback<void(absl::optional<base::Value::Dict>)>
                  callback) { std::move(callback).Run(absl::nullopt); })));
@@ -389,7 +390,8 @@
   // Once for failure, and once for gap.
   {
     ::testing::InSequence seq;
-    EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+    EXPECT_CALL(*client_,
+                UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
         .WillOnce(WithArgs<0, 2>(
             Invoke([](base::Value::Dict request,
                       policy::CloudPolicyClient::ResponseCallback callback) {
@@ -397,7 +399,8 @@
               FailedResponseFromRequest(request, response);
               std::move(callback).Run(std::move(response));
             })));
-    EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+    EXPECT_CALL(*client_,
+                UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
         .WillOnce(WithArgs<0, 2>(
             Invoke([&force_confirm_by_server](
                        base::Value::Dict request,
@@ -439,7 +442,7 @@
   static constexpr int64_t kGenerationId = 1234;
   auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId);
 
-  EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+  EXPECT_CALL(*client_, UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
       .WillOnce(WithArgs<2>(
           Invoke([](policy::CloudPolicyClient::ResponseCallback callback) {
             std::move(callback).Run(base::Value::Dict());
@@ -474,7 +477,7 @@
       .sequence_information = test_records->back().sequence_information(),
       .force_confirm = force_confirm()};
 
-  EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+  EXPECT_CALL(*client_, UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
       .WillOnce(WithArgs<0, 2>(
           Invoke([&force_confirm_by_server](
                      base::Value::Dict request,
diff --git a/chrome/browser/policy/messaging_layer/upload/test_util.cc b/chrome/browser/policy/messaging_layer/upload/test_util.cc
deleted file mode 100644
index b380b7f..0000000
--- a/chrome/browser/policy/messaging_layer/upload/test_util.cc
+++ /dev/null
@@ -1,75 +0,0 @@
-// 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 "chrome/browser/policy/messaging_layer/upload/test_util.h"
-
-#include "base/json/json_reader.h"
-
-namespace reporting {
-
-bool RequestContainingRecordMatcher::IsSubDict(const base::Value::Dict& sub,
-                                               const base::Value::Dict& super) {
-  for (auto&& [key, sub_value] : sub) {
-    const auto* super_value = super.Find(key);
-    if (super_value == nullptr || *super_value != sub_value) {
-      return false;
-    }
-  }
-  return true;
-}
-
-RequestContainingRecordMatcher::RequestContainingRecordMatcher(
-    base::StringPiece matched_record_json)
-    : matched_record_json_(matched_record_json) {}
-
-bool RequestContainingRecordMatcher::MatchAndExplain(
-    const base::Value::Dict& arg,
-    std::ostream* os) const {
-  const auto* record_list = arg.FindList("encryptedRecord");
-  if (record_list == nullptr) {
-    *os << "No key named \"encryptedRecord\" in the argument.";
-    return false;
-  }
-
-  const auto matched_record = base::JSONReader::Read(matched_record_json_);
-  if (!matched_record.has_value()) {
-    *os << "The specified record cannot be parsed as a JSON object.";
-    return false;
-  }
-  const auto* matched_record_dict = matched_record->GetIfDict();
-  if (matched_record_dict == nullptr) {
-    *os << "The specified record must be a Dict itself because each record "
-           "is a Dict.";
-    return false;
-  }
-
-  for (const auto& record : *record_list) {
-    const auto* record_dict = record.GetIfDict();
-    if (!record_dict) {
-      continue;
-    }
-
-    // Match each key and value of matched_record with those of the iterated
-    // record_dict. In this way, users can specify part of a record instead of
-    // the full record.
-    if (IsSubDict(*matched_record_dict, *record_dict)) {
-      return true;
-    }
-  }
-
-  *os << "The specified record is not found in the argument.";
-  return false;
-}
-
-void RequestContainingRecordMatcher::DescribeTo(std::ostream* os) const {
-  *os << "contains the specified record.";
-}
-
-void RequestContainingRecordMatcher::DescribeNegationTo(
-    std::ostream* os) const {
-  *os << "does not contain the specified record or there are other failed "
-         "conditions.";
-}
-
-}  // namespace reporting
diff --git a/chrome/browser/policy/messaging_layer/upload/test_util.h b/chrome/browser/policy/messaging_layer/upload/test_util.h
deleted file mode 100644
index a1ef104..0000000
--- a/chrome/browser/policy/messaging_layer/upload/test_util.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// 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 CHROME_BROWSER_POLICY_MESSAGING_LAYER_UPLOAD_TEST_UTIL_H_
-#define CHROME_BROWSER_POLICY_MESSAGING_LAYER_UPLOAD_TEST_UTIL_H_
-
-#include <ostream>
-#include <string>
-
-#include "base/strings/string_piece.h"
-#include "base/values.h"
-
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace reporting {
-
-using ::testing::Matcher;
-
-class RequestContainingRecordMatcher {
- public:
-  using is_gtest_matcher = void;
-
-  explicit RequestContainingRecordMatcher(
-      base::StringPiece matched_record_json);
-  bool MatchAndExplain(const base::Value::Dict& arg, std::ostream* os) const;
-  void DescribeTo(std::ostream* os) const;
-  void DescribeNegationTo(std::ostream* os) const;
-
- private:
-  const std::string matched_record_json_;
-
-  // Determine if |sub| is a sub-dictionary of |super|. That means, whether
-  // |super| contains all keys of |sub| and the values corresponding to each of
-  // |sub|'s keys equal. This method does not call itself recursively on values
-  // that are dictionaries.
-  static bool IsSubDict(const base::Value::Dict& sub,
-                        const base::Value::Dict& super);
-};
-
-// Match a request Dict object that contains the given record
-// |matched_record_json|. The match will be successful as long as any record in
-// the request contains |matched_record_json| as a sub-dictionary -- they are
-// not required to equal. In this way, you can specify only part of the record
-// of interest (e.g., omit "encryptedWrappedRecord").
-//
-// This is templated because we expect the tested request comes in different
-// forms, including their referenceness (gtest need the matcher type to also
-// match references to some extent). As long as the type can be cast to a
-// |base::Value::Dict| object, this matcher should work.
-template <class T = base::Value::Dict>
-Matcher<T> DoesRequestContainRecord(base::StringPiece matched_record_json) {
-  return RequestContainingRecordMatcher(matched_record_json);
-}
-
-}  // namespace reporting
-
-#endif  // CHROME_BROWSER_POLICY_MESSAGING_LAYER_UPLOAD_TEST_UTIL_H_
diff --git a/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc b/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc
index 53fd697c..9f8885b7 100644
--- a/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/policy/messaging_layer/upload/upload_client.h"
-#include "chrome/browser/policy/messaging_layer/upload/test_util.h"
+#include "chrome/browser/policy/messaging_layer/util/test.h"
 
 #include <tuple>
 
@@ -210,7 +210,8 @@
 }
 )JSON";
   EXPECT_CALL(*client, UploadEncryptedReport(
-                           AllOf(DoesRequestContainRecord(base::StringPrintf(
+                           AllOf(IsDataUploadRequestValid(),
+                                 DoesRequestContainRecord(base::StringPrintf(
                                      matched_record_template, 0)),
                                  DoesRequestContainRecord(base::StringPrintf(
                                      matched_record_template, 1)),
diff --git a/chrome/browser/policy/messaging_layer/util/test.cc b/chrome/browser/policy/messaging_layer/util/test.cc
new file mode 100644
index 0000000..76d6d2ec
--- /dev/null
+++ b/chrome/browser/policy/messaging_layer/util/test.cc
@@ -0,0 +1,129 @@
+// 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 "chrome/browser/policy/messaging_layer/util/test.h"
+
+#include <string>
+#include "base/json/json_reader.h"
+
+namespace reporting {
+
+DataUploadRequestValidityMatcher::Settings&
+DataUploadRequestValidityMatcher::Settings::SetCheckEncryptedRecord(bool flag) {
+  check_encrypted_record_ = flag;
+  return *this;
+}
+
+DataUploadRequestValidityMatcher::DataUploadRequestValidityMatcher(
+    const Settings& settings)
+    : settings_(settings) {}
+
+bool DataUploadRequestValidityMatcher::MatchAndExplain(
+    const base::Value::Dict& arg,
+    MatchResultListener* listener) const {
+  if (settings_.check_encrypted_record_ &&
+      arg.FindList("encryptedRecord") == nullptr) {
+    *listener
+        << "No key named \"encryptedRecord\" in the argument or the value is "
+           "not a list.";
+    return false;
+  }
+  // TODO: Check the validity of each record based on whether they have required
+  // fields and types.
+
+  const auto* request_id = arg.FindString("requestId");
+  if (request_id == nullptr) {
+    *listener
+        << "No key named \"requestId\" in the argument or the value is not a "
+           "string.";
+    return false;
+  }
+  if (request_id->empty()) {
+    *listener << "Request ID is empty.";
+    return false;
+  }
+  if (request_id->find_first_not_of("0123456789abcdefABCDEF") !=
+      std::string::npos) {
+    *listener << "Request ID is not a hexadecimal number.";
+    return false;
+  }
+
+  return true;
+}
+
+void DataUploadRequestValidityMatcher::DescribeTo(std::ostream* os) const {
+  *os << "is valid.";
+}
+
+void DataUploadRequestValidityMatcher::DescribeNegationTo(
+    std::ostream* os) const {
+  *os << "is invalid.";
+}
+
+bool RequestContainingRecordMatcher::IsSubDict(const base::Value::Dict& sub,
+                                               const base::Value::Dict& super) {
+  for (auto&& [key, sub_value] : sub) {
+    const auto* super_value = super.Find(key);
+    if (super_value == nullptr || *super_value != sub_value) {
+      return false;
+    }
+  }
+  return true;
+}
+
+RequestContainingRecordMatcher::RequestContainingRecordMatcher(
+    base::StringPiece matched_record_json)
+    : matched_record_json_(matched_record_json) {}
+
+bool RequestContainingRecordMatcher::MatchAndExplain(
+    const base::Value::Dict& arg,
+    std::ostream* os) const {
+  const auto* record_list = arg.FindList("encryptedRecord");
+  if (record_list == nullptr) {
+    *os << "No key named \"encryptedRecord\" in the argument or the value is "
+           "not a list.";
+    return false;
+  }
+
+  const auto matched_record = base::JSONReader::Read(matched_record_json_);
+  if (!matched_record.has_value()) {
+    *os << "The specified record cannot be parsed as a JSON object.";
+    return false;
+  }
+  const auto* matched_record_dict = matched_record->GetIfDict();
+  if (matched_record_dict == nullptr) {
+    *os << "The specified record must be a Dict itself because each record "
+           "is a Dict.";
+    return false;
+  }
+
+  for (const auto& record : *record_list) {
+    const auto* record_dict = record.GetIfDict();
+    if (!record_dict) {
+      continue;
+    }
+
+    // Match each key and value of matched_record with those of the iterated
+    // record_dict. In this way, users can specify part of a record instead of
+    // the full record.
+    if (IsSubDict(*matched_record_dict, *record_dict)) {
+      return true;
+    }
+  }
+
+  *os << "The specified record is not found in the argument.";
+  return false;
+}
+
+void RequestContainingRecordMatcher::DescribeTo(std::ostream* os) const {
+  *os << "contains the specified record.";
+}
+
+void RequestContainingRecordMatcher::DescribeNegationTo(
+    std::ostream* os) const {
+  *os << "does not contain the specified record or there are other failed "
+         "conditions.";
+}
+
+}  // namespace reporting
diff --git a/chrome/browser/policy/messaging_layer/util/test.h b/chrome/browser/policy/messaging_layer/util/test.h
new file mode 100644
index 0000000..347727d
--- /dev/null
+++ b/chrome/browser/policy/messaging_layer/util/test.h
@@ -0,0 +1,104 @@
+// 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 CHROME_BROWSER_POLICY_MESSAGING_LAYER_UTIL_TEST_H_
+#define CHROME_BROWSER_POLICY_MESSAGING_LAYER_UTIL_TEST_H_
+
+#include <ostream>
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "base/values.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace reporting {
+
+using ::testing::Matcher;
+using ::testing::MatchResultListener;
+
+class DataUploadRequestValidityMatcher {
+ public:
+  using is_gtest_matcher = void;
+
+  class Settings {
+   public:
+    Settings() = default;
+    Settings(const Settings& other) = default;
+    Settings(Settings&& other) = default;
+    // Enable or disable checking the existence of key "encryptedRecord" and
+    // that the value is a list.
+    Settings& SetCheckEncryptedRecord(bool flag);
+
+   private:
+    friend DataUploadRequestValidityMatcher;
+    bool check_encrypted_record_ = true;
+  };
+
+  DataUploadRequestValidityMatcher() = default;
+  explicit DataUploadRequestValidityMatcher(const Settings& settings);
+  bool MatchAndExplain(const base::Value::Dict& arg,
+                       MatchResultListener* listener) const;
+  void DescribeTo(std::ostream* os) const;
+  void DescribeNegationTo(std::ostream* os) const;
+
+ private:
+  const Settings settings_{};
+};
+
+class RequestContainingRecordMatcher {
+ public:
+  using is_gtest_matcher = void;
+
+  explicit RequestContainingRecordMatcher(
+      base::StringPiece matched_record_json);
+  bool MatchAndExplain(const base::Value::Dict& arg, std::ostream* os) const;
+  void DescribeTo(std::ostream* os) const;
+  void DescribeNegationTo(std::ostream* os) const;
+
+ private:
+  const std::string matched_record_json_;
+
+  // Determine if |sub| is a sub-dictionary of |super|. That means, whether
+  // |super| contains all keys of |sub| and the values corresponding to each of
+  // |sub|'s keys equal. This method does not call itself recursively on values
+  // that are dictionaries.
+  static bool IsSubDict(const base::Value::Dict& sub,
+                        const base::Value::Dict& super);
+};
+
+// The following matcher functions templated because we expect the tested
+// request comes in different forms, including their referenceness (gtest need
+// the matcher type to also match references to some extent). As long as the
+// type can be cast to a |base::Value::Dict| object, this matcher should work.
+
+// Match a data upload request that is valid. This matcher is intended to be
+// called for most tested data upload requests to verify whether the request is
+// valid on some basic fronts, such as containing an "encryptedRecord" key, etc.
+//
+// You can use settings to enable or skip some part of the validity checks if
+// your test case intentionally creates a malformed request.
+template <class T = base::Value::Dict>
+Matcher<T> IsDataUploadRequestValid(
+    const DataUploadRequestValidityMatcher::Settings& settings) {
+  return DataUploadRequestValidityMatcher(settings);
+}
+template <class T = base::Value::Dict>
+Matcher<T> IsDataUploadRequestValid() {
+  return DataUploadRequestValidityMatcher();
+}
+
+// Match a request that contains the given record |matched_record_json|. The
+// match will be successful as long as any record in the request contains
+// |matched_record_json| as a sub-dictionary -- they are not required to equal.
+// In this way, you can specify only part of the record of interest (e.g., omit
+// "encryptedWrappedRecord").
+template <class T = base::Value::Dict>
+Matcher<T> DoesRequestContainRecord(base::StringPiece matched_record_json) {
+  return RequestContainingRecordMatcher(matched_record_json);
+}
+
+}  // namespace reporting
+
+#endif  // CHROME_BROWSER_POLICY_MESSAGING_LAYER_UTIL_TEST_H_
diff --git a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
index 02118ca..067a17c0 100644
--- a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
+++ b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
@@ -109,7 +109,7 @@
 
 BaseSearchPrefetchRequest::BaseSearchPrefetchRequest(
     const GURL& prefetch_url,
-    base::OnceClosure report_error_callback)
+    base::OnceCallback<void(bool)> report_error_callback)
     : prefetch_url_(prefetch_url),
       report_error_callback_(std::move(report_error_callback)) {}
 
diff --git a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
index 104a28ef3..82eb67d 100644
--- a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
+++ b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
@@ -48,8 +48,9 @@
 // updating |current_status_|.
 class BaseSearchPrefetchRequest {
  public:
-  BaseSearchPrefetchRequest(const GURL& prefetch_url,
-                            base::OnceClosure report_error_callback);
+  BaseSearchPrefetchRequest(
+      const GURL& prefetch_url,
+      base::OnceCallback<void(bool)> report_error_callback);
   virtual ~BaseSearchPrefetchRequest();
 
   BaseSearchPrefetchRequest(const BaseSearchPrefetchRequest&) = delete;
@@ -91,7 +92,7 @@
       Profile* profile,
       std::unique_ptr<network::ResourceRequest> resource_request,
       const net::NetworkTrafficAnnotationTag& traffic_annotation,
-      base::OnceClosure report_error_callback) = 0;
+      base::OnceCallback<void(bool)> report_error_callback) = 0;
 
   // Stops the on-going prefetch and should mark |current_status_|
   // appropriately.
@@ -112,7 +113,7 @@
   GURL prefetch_url_;
 
   // Called when there is a network/server error on the prefetch request.
-  base::OnceClosure report_error_callback_;
+  base::OnceCallback<void(bool)> report_error_callback_;
 };
 
 #endif  // CHROME_BROWSER_PREFETCH_SEARCH_PREFETCH_BASE_SEARCH_PREFETCH_REQUEST_H_
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
index 02a20f3e..c79bb542 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
@@ -183,7 +183,7 @@
 
   std::unique_ptr<BaseSearchPrefetchRequest> prefetch_request =
       std::make_unique<StreamingSearchPrefetchRequest>(
-          url, base::BindOnce(&SearchPrefetchService::ReportError,
+          url, base::BindOnce(&SearchPrefetchService::ReportFetchResult,
                               base::Unretained(this)));
 
   DCHECK(prefetch_request);
@@ -381,7 +381,10 @@
   prefetch_expiry_timers_.erase(search_terms);
 }
 
-void SearchPrefetchService::ReportError() {
+void SearchPrefetchService::ReportFetchResult(bool error) {
+  UMA_HISTOGRAM_BOOLEAN("Omnibox.SearchPrefetch.FetchResult", !error);
+  if (!error)
+    return;
   last_error_time_ticks_ = base::TimeTicks::Now();
 }
 
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
index a440fb5..9456dc6 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
@@ -137,8 +137,9 @@
   // Removes the prefetch and prefetch timers associated with |search_terms|.
   void DeletePrefetch(std::u16string search_terms);
 
-  // Records the current time to prevent prefetches for a set duration.
-  void ReportError();
+  // Records metrics around the error rate of prefetches. When |error| is true,
+  // records the current time to prevent prefetches for a set duration.
+  void ReportFetchResult(bool error);
 
   // If the navigation URL matches with a prefetch that can be served, this
   // function marks that prefetch as clicked to prevent deletion when omnibox
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
index 4abf0fd..8cfc7c2 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
@@ -729,6 +729,8 @@
 
   WaitUntilStatusChangesTo(base::ASCIIToUTF16(search_terms),
                            SearchPrefetchStatus::kComplete);
+  histogram_tester.ExpectUniqueSample("Omnibox.SearchPrefetch.FetchResult",
+                                      true, 1);
 
   EXPECT_EQ(1u, search_server_requests().size());
   EXPECT_NE(std::string::npos,
@@ -1048,6 +1050,7 @@
 
 IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
                        502PrefetchFunctionality) {
+  base::HistogramTester histogram_tester;
   auto* search_prefetch_service =
       SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
   EXPECT_NE(nullptr, search_prefetch_service);
@@ -1068,6 +1071,9 @@
   WaitUntilStatusChangesTo(base::ASCIIToUTF16(search_terms),
                            SearchPrefetchStatus::kRequestFailed);
 
+  histogram_tester.ExpectUniqueSample("Omnibox.SearchPrefetch.FetchResult",
+                                      false, 1);
+
   EXPECT_EQ(1u, search_server_requests().size());
   EXPECT_NE(std::string::npos,
             search_server_requests()[0].GetURL().spec().find(search_terms));
diff --git a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.cc b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.cc
index 22b7166bb..a076c204 100644
--- a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.cc
+++ b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.cc
@@ -9,7 +9,7 @@
 
 StreamingSearchPrefetchRequest::StreamingSearchPrefetchRequest(
     const GURL& prefetch_url,
-    base::OnceClosure report_error_callback)
+    base::OnceCallback<void(bool)> report_error_callback)
     : BaseSearchPrefetchRequest(prefetch_url,
                                 std::move(report_error_callback)) {}
 
@@ -19,7 +19,7 @@
     Profile* profile,
     std::unique_ptr<network::ResourceRequest> resource_request,
     const net::NetworkTrafficAnnotationTag& network_traffic_annotation,
-    base::OnceClosure report_error_callback) {
+    base::OnceCallback<void(bool)> report_error_callback) {
   streaming_url_loader_ = std::make_unique<StreamingSearchPrefetchURLLoader>(
       this, profile, std::move(resource_request), network_traffic_annotation,
       std::move(report_error_callback));
diff --git a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.h b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.h
index 3b291a0..8e29761 100644
--- a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.h
+++ b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.h
@@ -25,8 +25,9 @@
 // more easily.
 class StreamingSearchPrefetchRequest : public BaseSearchPrefetchRequest {
  public:
-  StreamingSearchPrefetchRequest(const GURL& prefetch_url,
-                                 base::OnceClosure report_error_callback);
+  StreamingSearchPrefetchRequest(
+      const GURL& prefetch_url,
+      base::OnceCallback<void(bool)> report_error_callback);
   ~StreamingSearchPrefetchRequest() override;
 
   StreamingSearchPrefetchRequest(const StreamingSearchPrefetchRequest&) =
@@ -39,7 +40,7 @@
       Profile* profile,
       std::unique_ptr<network::ResourceRequest> resource_request,
       const net::NetworkTrafficAnnotationTag& network_traffic_annotation,
-      base::OnceClosure report_error_callback) override;
+      base::OnceCallback<void(bool)> report_error_callback) override;
   void StopPrefetch() override;
   std::unique_ptr<SearchPrefetchURLLoader> TakeSearchPrefetchURLLoader()
       override;
diff --git a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
index b23d489..5262adc 100644
--- a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
+++ b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
@@ -54,7 +54,7 @@
     Profile* profile,
     std::unique_ptr<network::ResourceRequest> resource_request,
     const net::NetworkTrafficAnnotationTag& network_traffic_annotation,
-    base::OnceClosure report_error_callback)
+    base::OnceCallback<void(bool)> report_error_callback)
     : streaming_prefetch_request_(streaming_prefetch_request),
       report_error_callback_(std::move(report_error_callback)),
       profile_(profile),
@@ -181,10 +181,12 @@
     return;
   }
 
+  bool can_serve_response = CanServePrefetchRequest(head->headers);
+  std::move(report_error_callback_).Run(!can_serve_response);
+
   // If there is an error, either cancel the request or fallback depending on
   // whether we still have a parent pointer.
-  if (!CanServePrefetchRequest(head->headers)) {
-    std::move(report_error_callback_).Run();
+  if (!can_serve_response) {
     if (SearchPrefetchBlockBeforeHeadersIsEnabled() &&
         !streaming_prefetch_request_) {
       Fallback();
diff --git a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h
index e57152e..aa64916 100644
--- a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h
+++ b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h
@@ -41,7 +41,7 @@
       Profile* profile,
       std::unique_ptr<network::ResourceRequest> resource_request,
       const net::NetworkTrafficAnnotationTag& network_traffic_annotation,
-      base::OnceClosure report_error_callback);
+      base::OnceCallback<void(bool)> report_error_callback);
 
   ~StreamingSearchPrefetchURLLoader() override;
 
@@ -198,7 +198,7 @@
 
   // Whenever an error is reported, it needs to be reported to the service via
   // this callback.
-  base::OnceClosure report_error_callback_;
+  base::OnceCallback<void(bool)> report_error_callback_;
 
   // Track if the request has already been marked as servable, and if so, don't
   // report it again.
diff --git a/chrome/browser/resources/history/history_clusters/cluster.html b/chrome/browser/resources/history/history_clusters/cluster.html
index 00224173..dba56ca4 100644
--- a/chrome/browser/resources/history/history_clusters/cluster.html
+++ b/chrome/browser/resources/history/history_clusters/cluster.html
@@ -101,7 +101,8 @@
   }
 </style>
 <div id="container" on-visit-clicked="onVisitClicked_"
-    on-open-all-visits="onOpenAllVisits_">
+    on-open-all-visits="onOpenAllVisits_"
+    on-remove-visits="onRemoveVisits_">
   <div id="label-row" hidden="[[!cluster.label]]">
     <div id="label">[[cluster.label]]</div>
     <div class="timestamp-and-menu">
@@ -110,22 +111,17 @@
       </menu-container>
     </div>
   </div>
-  <url-visit visit="[[cluster.visit]]" index="0"
-      is-top-visit$="[[!cluster.label]]">
+  <url-visit visit="[[cluster.visit]]" is-top-visit$="[[!cluster.label]]">
   </url-visit>
   <template is="dom-repeat" items="[[visibleRelatedVisits_]]">
-    <url-visit visit="[[item]]"
-        index="[[getVisitIndex_(item, cluster.visit.relatedVisits.*)]]"
-        indented$="[[!cluster.label]]">
+    <url-visit visit="[[item]]" indented$="[[!cluster.label]]">
     </url-visit>
   </template>
   <!-- Disable animation on iron-collapse, as the parent iron-list can't
        easily handle it. -->
   <iron-collapse opened="[[expanded_]]" no-animation>
     <template is="dom-repeat" items="[[hiddenRelatedVisits_]]">
-      <url-visit visit="[[item]]"
-          index="[[getVisitIndex_(item, cluster.visit.relatedVisits.*)]]"
-          indented$="[[!cluster.label]]">
+      <url-visit visit="[[item]]" indented$="[[!cluster.label]]">
       </url-visit>
     </template>
   </iron-collapse>
diff --git a/chrome/browser/resources/history/history_clusters/cluster.ts b/chrome/browser/resources/history/history_clusters/cluster.ts
index ac8f479..469c3b8c 100644
--- a/chrome/browser/resources/history/history_clusters/cluster.ts
+++ b/chrome/browser/resources/history/history_clusters/cluster.ts
@@ -17,7 +17,7 @@
 import {BrowserProxyImpl} from './browser_proxy.js';
 import {getTemplate} from './cluster.html.js';
 import {Cluster, PageCallbackRouter, URLVisit} from './history_clusters.mojom-webui.js';
-import {ClusterAction, MetricsProxyImpl} from './metrics_proxy.js';
+import {ClusterAction, MetricsProxyImpl, VisitAction} from './metrics_proxy.js';
 
 /**
  * @fileoverview This file provides a custom element displaying a cluster.
@@ -138,9 +138,14 @@
         ClusterAction.RELATED_SEARCH_CLICKED, this.index);
   }
 
-  private onVisitClicked_() {
+  private onVisitClicked_(event: CustomEvent<URLVisit>) {
     MetricsProxyImpl.getInstance().recordClusterAction(
         ClusterAction.VISIT_CLICKED, this.index);
+
+    const visit = event.detail;
+    MetricsProxyImpl.getInstance().recordVisitAction(
+        VisitAction.CLICKED, this.getVisitIndex_(visit),
+        MetricsProxyImpl.getVisitType(visit));
   }
 
   private onOpenAllVisits_() {
@@ -158,6 +163,21 @@
         ClusterAction.OPENED_IN_TAB_GROUP, this.index);
   }
 
+  private onRemoveVisits_(event: CustomEvent<Array<URLVisit>>) {
+    // The actual removal is handled at in clusters.ts. This is just a good
+    // place to record the metric.
+    const visitsToBeRemoved = event.detail;
+
+    // To match the historic semantics, we only record this metric when a single
+    // visit is requested to be removed by the user.
+    if (visitsToBeRemoved.length === 1) {
+      const visit = visitsToBeRemoved[0];
+      MetricsProxyImpl.getInstance().recordVisitAction(
+          VisitAction.DELETED, this.getVisitIndex_(visit),
+          MetricsProxyImpl.getVisitType(visit));
+    }
+  }
+
   private onToggleButtonKeyDown_(e: KeyboardEvent) {
     if (e.key !== 'Enter' && e.key !== ' ') {
       return;
@@ -221,6 +241,9 @@
         composed: true,
         detail: this.index,
       }));
+
+      MetricsProxyImpl.getInstance().recordClusterAction(
+          ClusterAction.DELETED, this.index);
     } else {
       // Reconstitute the cluster by setting the top visit with the
       // `remainingVisits` as its related visits.
@@ -260,11 +283,20 @@
   }
 
   /**
-   * Returns the index of `relatedVisit` among the visits in the cluster.
+   * Returns the index of `visit` among the visits in the cluster. Returns -1
+   * if the visit is not found in the cluster at all.
    */
-  private getVisitIndex_(relatedVisit: URLVisit): number {
-    // Index 0 represents the top visit.
-    return this.cluster.visit.relatedVisits.indexOf(relatedVisit) + 1;
+  private getVisitIndex_(visit: URLVisit): number {
+    if (visit === this.cluster.visit) {
+      return 0;
+    }
+
+    const relatedVisitIndex = this.cluster.visit.relatedVisits.indexOf(visit);
+    if (relatedVisitIndex === -1) {
+      return -1;
+    }
+    // Add one, because the "top visit" is the 0th visit.
+    return relatedVisitIndex + 1;
   }
 }
 
diff --git a/chrome/browser/resources/history/history_clusters/clusters.ts b/chrome/browser/resources/history/history_clusters/clusters.ts
index 1f41a82..a599e8a 100644
--- a/chrome/browser/resources/history/history_clusters/clusters.ts
+++ b/chrome/browser/resources/history/history_clusters/clusters.ts
@@ -24,7 +24,6 @@
 import {BrowserProxyImpl} from './browser_proxy.js';
 import {getTemplate} from './clusters.html.js';
 import {Cluster, PageCallbackRouter, PageHandlerRemote, QueryResult, URLVisit} from './history_clusters.mojom-webui.js';
-import {ClusterAction, MetricsProxyImpl} from './metrics_proxy.js';
 
 /**
  * @fileoverview This file provides a custom element that requests and shows
@@ -204,8 +203,6 @@
   private onRemoveCluster_(event: CustomEvent<number>) {
     const index = event.detail;
     this.splice('result_.clusters', index, 1);
-    MetricsProxyImpl.getInstance().recordClusterAction(
-        ClusterAction.DELETED, index);
   }
 
   /**
diff --git a/chrome/browser/resources/history/history_clusters/menu_container.ts b/chrome/browser/resources/history/history_clusters/menu_container.ts
index 07b4082..6b2c2c4 100644
--- a/chrome/browser/resources/history/history_clusters/menu_container.ts
+++ b/chrome/browser/resources/history/history_clusters/menu_container.ts
@@ -10,9 +10,8 @@
 import {CrLazyRenderElement} from 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.m.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Annotation, URLVisit} from './history_clusters.mojom-webui.js';
+import {URLVisit} from './history_clusters.mojom-webui.js';
 import {getTemplate} from './menu_container.html.js';
-import {MetricsProxyImpl, VisitAction, VisitType} from './metrics_proxy.js';
 
 /**
  * @fileoverview This file provides a custom element displaying an action menu.
@@ -45,22 +44,6 @@
   static get properties() {
     return {
       /**
-       * The index of the cluster this menu belongs to.
-       */
-      clusterIndex: {
-        type: Number,
-        value: -1,  // Initialized to an invalid value.
-      },
-
-      /**
-       * The index of the visit in the cluster.
-       */
-      visitIndex: {
-        type: Number,
-        value: -1,  // Initialized to an invalid value.
-      },
-
-      /**
        * The visit associated with this menu.
        */
       visit: Object,
@@ -71,8 +54,6 @@
   // Properties
   //============================================================================
 
-  clusterIndex: number;
-  visitIndex: number;
   visit: URLVisit;
 
   //============================================================================
@@ -117,23 +98,6 @@
     }));
 
     this.$.actionMenu.get().close();
-
-    MetricsProxyImpl.getInstance().recordVisitAction(
-        VisitAction.DELETED, this.visitIndex, this.getVisitType_());
-  }
-
-  //============================================================================
-  // Helper methods
-  //============================================================================
-
-  /**
-   * Returns the VisitType based on whether this is a visit to the default
-   * search provider's results page.
-   */
-  private getVisitType_(): VisitType {
-    return this.visit.annotations.includes(Annotation.kSearchResultsPage) ?
-        VisitType.SRP :
-        VisitType.NON_SRP;
   }
 }
 
diff --git a/chrome/browser/resources/history/history_clusters/metrics_proxy.ts b/chrome/browser/resources/history/history_clusters/metrics_proxy.ts
index 08c182b0..bdc38fb 100644
--- a/chrome/browser/resources/history/history_clusters/metrics_proxy.ts
+++ b/chrome/browser/resources/history/history_clusters/metrics_proxy.ts
@@ -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 {Annotation, URLVisit} from './history_clusters.mojom-webui.js';
+
 /**
  * @fileoverview This file provides an abstraction layer for logging metrics for
  * mocking in tests.
@@ -70,6 +72,16 @@
   static setInstance(obj: MetricsProxy) {
     instance = obj;
   }
+
+  /**
+   * Returns the VisitType based on whether this is a visit to the default
+   * search provider's results page.
+   */
+  static getVisitType(visit: URLVisit): VisitType {
+    return visit.annotations.includes(Annotation.kSearchResultsPage) ?
+        VisitType.SRP :
+        VisitType.NON_SRP;
+  }
 }
 
 let instance: MetricsProxy|null = null;
diff --git a/chrome/browser/resources/history/history_clusters/url_visit.html b/chrome/browser/resources/history/history_clusters/url_visit.html
index 6f33f25..7ca534f 100644
--- a/chrome/browser/resources/history/history_clusters/url_visit.html
+++ b/chrome/browser/resources/history/history_clusters/url_visit.html
@@ -112,9 +112,8 @@
   </a>
   <div class="timestamp-and-menu">
     <div class="timestamp" hidden="[[!isTopVisit]]">[[visit.relativeDate]]</div>
-    <menu-container
-        is-top-menu$="[[isTopVisit]]" cluster-index="[[clusterIndex]]"
-        visit-index="[[index]]" visit="[[visit]]">
+    <menu-container is-top-menu$="[[isTopVisit]]"
+        cluster-index="[[clusterIndex]]" visit="[[visit]]">
     </menu-container>
   </div>
 </div>
diff --git a/chrome/browser/resources/history/history_clusters/url_visit.ts b/chrome/browser/resources/history/history_clusters/url_visit.ts
index 81a64af..4ea7c6c 100644
--- a/chrome/browser/resources/history/history_clusters/url_visit.ts
+++ b/chrome/browser/resources/history/history_clusters/url_visit.ts
@@ -12,7 +12,6 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Annotation, URLVisit} from './history_clusters.mojom-webui.js';
-import {MetricsProxyImpl, VisitAction, VisitType} from './metrics_proxy.js';
 import {OpenWindowProxyImpl} from './open_window_proxy.js';
 import {getTemplate} from './url_visit.html.js';
 
@@ -57,14 +56,6 @@
       },
 
       /**
-       * The index of the visit in the cluster.
-       */
-      index: {
-        type: Number,
-        value: -1,  // Initialized to an invalid value.
-      },
-
-      /**
        * The visit to display.
        */
       visit: Object,
@@ -91,7 +82,6 @@
   // Properties
   //============================================================================
 
-  index: number;
   visit: URLVisit;
 
   //============================================================================
@@ -99,13 +89,11 @@
   //============================================================================
 
   private onAuxClick_() {
-    MetricsProxyImpl.getInstance().recordVisitAction(
-        VisitAction.CLICKED, this.index, this.getVisitType_());
-
     // Notify the parent <history-cluster> element of this event.
     this.dispatchEvent(new CustomEvent('visit-clicked', {
       bubbles: true,
       composed: true,
+      detail: this.visit,
     }));
   }
 
@@ -173,16 +161,6 @@
       return '';
     }
   }
-
-  /**
-   * Returns the VisitType based on whether this is a visit to the default
-   * search provider's results page.
-   */
-  private getVisitType_(): VisitType {
-    return this.visit.annotations.includes(Annotation.kSearchResultsPage) ?
-        VisitType.SRP :
-        VisitType.NON_SRP;
-  }
 }
 
 customElements.define(VisitRowElement.is, VisitRowElement);
diff --git a/chrome/browser/resources/new_tab_page/voice_search_overlay.html b/chrome/browser/resources/new_tab_page/voice_search_overlay.html
index 130e0698..85755d3 100644
--- a/chrome/browser/resources/new_tab_page/voice_search_overlay.html
+++ b/chrome/browser/resources/new_tab_page/voice_search_overlay.html
@@ -177,11 +177,7 @@
 </style>
 <dialog id="dialog" on-close="onOverlayClose_" on-click="onOverlayClick_"
     on-keydown="onOverlayKeydown_">
-  <!-- Purely exists to capture focus upon opening the dialog. -->
-  <div tabindex="-1"></div>
-  <cr-icon-button id="closeButton" class="icon-clear" title="$i18n{close}">
-  </cr-icon-button>
-  <div id="content">
+  <div id="content" tabindex="-1">
     <iron-selector id="texts" selected="[[getText_(state_)]]"
         attr-for-selected="text" fallback-selection="none" aria-live="polite"
         selected-attribute="visible" class="display-stack">
@@ -236,4 +232,6 @@
       </cr-button>
     </div>
   </div>
+  <cr-icon-button id="closeButton" class="icon-clear" title="$i18n{close}">
+  </cr-icon-button>
 </dialog>
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index dff0949..6c3d02f 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -321,7 +321,6 @@
                                  "chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_page_browser_proxy.html",
                                  "chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_subpage.html",
                                  "chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb.html",
-                                 "chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb.html",
                                  "chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb_confirmation_dialog.html",
                                  "chrome/browser/resources/settings/chromeos/crostini_page/crostini_browser_proxy.html",
                                  "chrome/browser/resources/settings/chromeos/crostini_page/crostini_confirmation_dialog.html",
diff --git a/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.cc b/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.cc
index 04c037bb..b339b76 100644
--- a/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.cc
+++ b/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.cc
@@ -22,6 +22,11 @@
 const base::Feature kLocalWebApprovals{"LocalWebApprovals",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables child accounts (i.e. Unicorn accounts) to clear their browsing
+// history data from Settings.
+const base::Feature kAllowHistoryDeletionForChildAccounts{
+    "AllowHistoryDeletionForChildAccounts", base::FEATURE_DISABLED_BY_DEFAULT};
+
 bool IsWebFilterInterstitialRefreshEnabled() {
   DCHECK(base::FeatureList::IsEnabled(kWebFilterInterstitialRefresh) ||
          !base::FeatureList::IsEnabled(kLocalWebApprovals));
diff --git a/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h b/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h
index 77de78e..5e2f4851 100644
--- a/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h
+++ b/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h
@@ -13,6 +13,8 @@
 
 extern const base::Feature kLocalWebApprovals;
 
+extern const base::Feature kAllowHistoryDeletionForChildAccounts;
+
 // Returns whether refreshed version of the website filter interstitial is
 // enabled.
 bool IsWebFilterInterstitialRefreshEnabled();
diff --git a/chrome/browser/supervised_user/supervised_user_pref_store.cc b/chrome/browser/supervised_user/supervised_user_pref_store.cc
index 6b245a9..5ce51db 100644
--- a/chrome/browser/supervised_user/supervised_user_pref_store.cc
+++ b/chrome/browser/supervised_user/supervised_user_pref_store.cc
@@ -10,12 +10,14 @@
 
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/supervised_user/supervised_user_constants.h"
+#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
 #include "chrome/browser/supervised_user/supervised_user_settings_service.h"
 #include "chrome/browser/supervised_user/supervised_user_url_filter.h"
 #include "chrome/common/chrome_switches.h"
@@ -24,6 +26,7 @@
 #include "components/feed/core/shared_prefs/pref_names.h"
 #include "components/prefs/pref_value_map.h"
 #include "components/signin/public/base/signin_pref_names.h"
+#include "components/signin/public/base/signin_switches.h"
 #include "extensions/buildflags/buildflags.h"
 
 namespace {
@@ -139,7 +142,15 @@
       bool record_history =
           settings->FindBoolPath(supervised_users::kRecordHistory)
               .value_or(true);
-      prefs_->SetBoolean(prefs::kAllowDeletingBrowserHistory, !record_history);
+      // Allow history deletion for supervised accounts on supported platforms.
+      bool allow_history_deletion = base::FeatureList::IsEnabled(
+          supervised_users::kAllowHistoryDeletionForChildAccounts);
+      prefs_->SetBoolean(prefs::kAllowDeletingBrowserHistory,
+                         allow_history_deletion || !record_history);
+      // Incognito is disabled for supervised users across platforms.
+      // First-party sites use signed-in cookies to ensure that parental
+      // restrictions are applied for Unicorn accounts.
+      // TODO(crbug.com/1304177): Set to disabled by default.
       prefs_->SetInteger(
           prefs::kIncognitoModeAvailability,
           static_cast<int>(record_history
diff --git a/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc b/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc
index 68a0922c..9965818 100644
--- a/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc
+++ b/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc
@@ -7,8 +7,10 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/supervised_user/supervised_user_constants.h"
+#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
 #include "chrome/browser/supervised_user/supervised_user_pref_store.h"
 #include "chrome/browser/supervised_user/supervised_user_settings_service.h"
 #include "chrome/common/net/safe_search_util.h"
@@ -95,6 +97,28 @@
   service_.Shutdown();
 }
 
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+TEST_F(SupervisedUserPrefStoreTest,
+       ConfigureSettingsWithHistoryDeletionAllowed) {
+  SupervisedUserPrefStoreFixture fixture(&service_);
+  EXPECT_FALSE(fixture.initialization_completed());
+
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      supervised_users::kAllowHistoryDeletionForChildAccounts);
+
+  pref_store_->SetInitializationCompleted();
+  service_.SetActive(true);
+
+  // kAllowDeletingBrowserHistory is based on the state of the feature
+  // supervised_users::kAllowHistoryDeletionForChildAccounts.
+  // This is enabled in scope.
+  EXPECT_THAT(fixture.changed_prefs()->FindBoolPath(
+                  prefs::kAllowDeletingBrowserHistory),
+              Optional(true));
+}
+#endif
+
 TEST_F(SupervisedUserPrefStoreTest, ConfigureSettings) {
   SupervisedUserPrefStoreFixture fixture(&service_);
   EXPECT_FALSE(fixture.initialization_completed());
@@ -107,7 +131,9 @@
 
   service_.SetActive(true);
 
-  // kAllowDeletingBrowserHistory is hardcoded to false for supervised users.
+  // kAllowDeletingBrowserHistory is based on the state of the feature
+  // supervised_users::kAllowHistoryDeletionForChildAccounts.
+  // This is disabled in scope.
   EXPECT_THAT(fixture.changed_prefs()->FindBoolPath(
                   prefs::kAllowDeletingBrowserHistory),
               Optional(false));
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
index 7ca6ca7..1a40c23 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
@@ -93,6 +93,16 @@
     }
 
     /**
+     * Issue a prefetch request for zero prefix suggestions.
+     *
+     * Prefetch is a fire-and-forget operation that yields no results.
+     */
+    void startPrefetch() {
+        if (mNativeController == 0) return;
+        AutocompleteControllerJni.get().startPrefetch(mNativeController);
+    }
+
+    /**
      * Given some string |text| that the user wants to use for navigation, determines how it should
      * be interpreted. This is a fallback in case the user didn't select a visible suggestion (e.g.
      * the user pressed enter before omnibox suggestions had been shown).
@@ -355,7 +365,7 @@
         /**
          * Sends a zero suggest request to the server in order to pre-populate the result cache.
          */
-        void prefetchZeroSuggestResults();
+        void startPrefetch(long nativeAutocompleteControllerAndroid);
 
         /**
          * Acquire an instance of AutocompleteController associated with the supplied profile.
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
index 8820efb..e333139 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
@@ -398,7 +398,7 @@
      * Sends a zero suggest request to the server in order to pre-populate the result cache.
      */
     public void prefetchZeroSuggestResults() {
-        AutocompleteControllerJni.get().prefetchZeroSuggestResults();
+        mMediator.startPrefetch();
     }
 
     /** @return Suggestions Dropdown view, showing the list of suggestions. */
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index 7a1bfaf..d1b6fae 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -826,6 +826,14 @@
     }
 
     /**
+     * Sends a zero suggest request to the server in order to pre-populate the result cache.
+     */
+    /* package */ void startPrefetch() {
+        postAutocompleteRequest(
+                () -> mAutocomplete.startPrefetch(), SCHEDULE_FOR_IMMEDIATE_EXECUTION);
+    }
+
+    /**
      * Make a zero suggest request if:
      * - The URL bar has focus.
      * - The the tab/overview is not incognito.
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
index 4d31da1..c6c26c2 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
@@ -66,7 +66,6 @@
     /**
      * @return List of Action views.
      */
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     public List<ImageView> getActionButtons() {
         return mActionButtons;
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/pedal/PedalSuggestionView.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/pedal/PedalSuggestionView.java
index 85794f16..2861582 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/pedal/PedalSuggestionView.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/pedal/PedalSuggestionView.java
@@ -7,18 +7,24 @@
 import android.content.Context;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Px;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.view.MarginLayoutParamsCompat;
 
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionView;
 import org.chromium.chrome.browser.omnibox.suggestions.base.SimpleVerticalLayoutView;
 import org.chromium.components.browser_ui.widget.chips.ChipView;
 
+import java.util.List;
+
 /**
  * Base layout for pedals suggestion types. This is a {@link BaseSuggestionView} with a pedal under
  * it.
@@ -45,9 +51,7 @@
                 R.dimen.omnibox_pedal_suggestion_pedal_height);
         final @Px int pedalStartPaddingPx =
                 getResources().getDimensionPixelSize(R.dimen.omnibox_suggestion_icon_area_size);
-        final @Px int pedalEndPaddingPx =
-                getResources().getDimensionPixelSize(R.dimen.omnibox_suggestion_action_icon_width);
-        mPedal.setPaddingRelative(pedalStartPaddingPx, 0, pedalEndPaddingPx, 0);
+        mPedal.setPaddingRelative(pedalStartPaddingPx, 0, 0, 0);
         mPedal.getChipView().setMinimumHeight(pedalSuggestionSizePx);
         addView(mPedal);
 
@@ -61,6 +65,27 @@
         return mPedal.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        updatePedalLayoutMargin();
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    private void updatePedalLayoutMargin() {
+        List<ImageView> buttons = mBaseSuggestionView.getActionButtons();
+        int actionButtonsWidth = 0;
+        if (buttons != null) {
+            for (ImageView button : buttons) {
+                actionButtonsWidth += button.getMeasuredWidth();
+            }
+        }
+
+        MarginLayoutParams layoutParams =
+                new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+        MarginLayoutParamsCompat.setMarginEnd(layoutParams, actionButtonsWidth);
+        mPedal.setLayoutParams(layoutParams);
+    }
+
     /** @return base suggestion view. */
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
     public BaseSuggestionView<T> getBaseSuggestionView() {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/pedal/PedalView.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/pedal/PedalView.java
index 0a9581f4..fa8467790 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/pedal/PedalView.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/pedal/PedalView.java
@@ -6,10 +6,12 @@
 
 import android.content.Context;
 import android.view.KeyEvent;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.core.content.res.ResourcesCompat;
+import androidx.core.view.MarginLayoutParamsCompat;
 
 import com.google.android.material.color.MaterialColors;
 
@@ -73,7 +75,10 @@
 
     @Override
     protected void onMeasure(int widthSpec, int heightSpec) {
-        final int widthPx = MeasureSpec.getSize(widthSpec);
+        MarginLayoutParams marginLayoutParams = (MarginLayoutParams) getLayoutParams();
+
+        final int widthPx = MeasureSpec.getSize(widthSpec)
+                - MarginLayoutParamsCompat.getMarginEnd(marginLayoutParams);
         int chipViewWidth = widthPx - getPaddingLeft() - getPaddingRight();
 
         // Measure height of the content view given the width constraint.
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index f82f6c4d..7584668 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -164,8 +164,13 @@
     ThemeProperties::COLOR_OMNIBOX_SECURITY_CHIP_SECURE) \
   E(kColorOmniboxText, ThemeProperties::COLOR_OMNIBOX_TEXT) \
   E(kColorOmniboxTextDimmed, ThemeProperties::COLOR_OMNIBOX_TEXT_DIMMED) \
+  /* Overlay colors. */ \
+  E_CPONLY(kColorOverlayWindowBackgroundColor) \
   /* Payments colors. */ \
   E_CPONLY(kColorPaymentRequestRowBackgroundHighlighted) \
+  /* QR code colors. */ \
+  E_CPONLY(kColorQrCodeBackground) \
+  E_CPONLY(kColorQrCodeBorder) \
   /* Read Later button colors. */ \
   E(kColorReadLaterButtonHighlight, \
     ThemeProperties::COLOR_READ_LATER_BUTTON_HIGHLIGHT) \
diff --git a/chrome/browser/ui/color/chrome_color_mixer.cc b/chrome/browser/ui/color/chrome_color_mixer.cc
index e545e36..6405b97ed 100644
--- a/chrome/browser/ui/color/chrome_color_mixer.cc
+++ b/chrome/browser/ui/color/chrome_color_mixer.cc
@@ -295,8 +295,11 @@
       ui::kColorButtonForeground};
   mixer[kColorOmniboxText] =
       ui::GetColorWithMaxContrast(kColorOmniboxBackground);
+  mixer[kColorOverlayWindowBackgroundColor] = {SK_ColorBLACK};
   mixer[kColorPaymentRequestRowBackgroundHighlighted] = {
       SkColorSetA(SK_ColorBLACK, 0x0D)};
+  mixer[kColorQrCodeBackground] = {SK_ColorWHITE};
+  mixer[kColorQrCodeBorder] = {ui::kColorMidground};
   mixer[kColorReadLaterButtonHighlight] = {kColorAvatarButtonHighlightNormal};
   mixer[kColorScreenshotCapturedImageBackground] = {ui::kColorBubbleBackground};
   mixer[kColorScreenshotCapturedImageBorder] = {ui::kColorMidground};
diff --git a/chrome/browser/ui/user_education/feature_promo_controller.cc b/chrome/browser/ui/user_education/feature_promo_controller.cc
index aa0ddda..ee56daf6 100644
--- a/chrome/browser/ui/user_education/feature_promo_controller.cc
+++ b/chrome/browser/ui/user_education/feature_promo_controller.cc
@@ -235,8 +235,10 @@
   const bool was_open = promo_bubble_ && promo_bubble_->is_open();
   if (promo_bubble_)
     promo_bubble_->Close();
-  if (iph_feature_bypassing_tracker_ == &iph_feature)
+  if (!continuing_after_bubble_closed_ &&
+      iph_feature_bypassing_tracker_ == &iph_feature) {
     iph_feature_bypassing_tracker_ = nullptr;
+  }
   return was_open;
 }
 
diff --git a/chrome/browser/ui/views/location_bar/intent_chip_button_browsertest.cc b/chrome/browser/ui/views/location_bar/intent_chip_button_browsertest.cc
index 23df63b..86d7952 100644
--- a/chrome/browser/ui/views/location_bar/intent_chip_button_browsertest.cc
+++ b/chrome/browser/ui/views/location_bar/intent_chip_button_browsertest.cc
@@ -221,4 +221,16 @@
   ClickLinkAndWait(web_contents, in_scope_url, LinkTarget::SELF, "");
   EXPECT_TRUE(GetIntentChip()->GetVisible());
   EXPECT_TRUE(GetIntentChip()->is_fully_collapsed());
+
+  // Click to open app and reset the counter.
+  ClickIntentChip();
+
+  // Open another browser- we should be able to see the expanded chip again.
+  NavigateToLaunchingPage(browser());
+  web_contents = browser()->tab_strip_model()->GetActiveWebContents();
+
+  // 1st appearance since intent chip counter reset: Expanded.
+  ClickLinkAndWait(web_contents, in_scope_url, LinkTarget::SELF, "");
+  EXPECT_TRUE(GetIntentChip()->GetVisible());
+  EXPECT_FALSE(GetIntentChip()->is_fully_collapsed());
 }
diff --git a/chrome/browser/ui/views/overlay/document_overlay_window_views.cc b/chrome/browser/ui/views/overlay/document_overlay_window_views.cc
index d12f196..0202f96 100644
--- a/chrome/browser/ui/views/overlay/document_overlay_window_views.cc
+++ b/chrome/browser/ui/views/overlay/document_overlay_window_views.cc
@@ -4,11 +4,6 @@
 
 #include "chrome/browser/ui/views/overlay/document_overlay_window_views.h"
 
-#include "content/public/browser/document_picture_in_picture_window_controller.h"
-#include "content/public/browser/picture_in_picture_window_controller.h"
-#include "content/public/browser/web_contents.h"
-#include "ui/views/controls/webview/webview.h"
-
 #include <memory>
 #include <string>
 
@@ -26,6 +21,7 @@
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/toolbar/chrome_location_bar_model_delegate.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/browser/ui/views/overlay/back_to_tab_image_button.h"
@@ -35,17 +31,21 @@
 #include "components/omnibox/browser/location_bar_model_impl.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/document_picture_in_picture_window_controller.h"
+#include "content/public/browser/picture_in_picture_window_controller.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_constants.h"
 #include "media/base/media_switches.h"
 #include "media/base/video_util.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/layer.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/geometry/resize_utils.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/webview/webview.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/vector_icons.h"
 #include "ui/views/widget/widget_delegate.h"
@@ -88,6 +88,25 @@
   return static_cast<T*>(views->back().get());
 }
 
+class WindowBackgroundView : public views::View {
+ public:
+  METADATA_HEADER(WindowBackgroundView);
+
+  WindowBackgroundView() = default;
+  WindowBackgroundView(const WindowBackgroundView&) = delete;
+  WindowBackgroundView& operator=(const WindowBackgroundView&) = delete;
+  ~WindowBackgroundView() override = default;
+
+  void OnThemeChanged() override {
+    views::View::OnThemeChanged();
+    layer()->SetColor(
+        GetColorProvider()->GetColor(kColorOverlayWindowBackgroundColor));
+  }
+};
+
+BEGIN_METADATA(WindowBackgroundView, views::View)
+END_METADATA
+
 }  // namespace
 
 OverlayLocationBarViewProxy::~OverlayLocationBarViewProxy() = default;
@@ -296,10 +315,10 @@
   DVLOG(2) << __func__ << ": content WebView=" << web_view.get();
   web_view->SetWebContents(pip_contents);
 
-  // views::View that is displayed when WebView is hidden. ---------------------
+  // View that is displayed when WebView is hidden. ----------------------------
   // Adding an extra pixel to width/height makes sure controls background cover
   // entirely window when platform has fractional scale applied.
-  auto window_background_view = std::make_unique<views::View>();
+  auto window_background_view = std::make_unique<WindowBackgroundView>();
 
   auto controls_container_view = std::make_unique<views::View>();
 
@@ -334,7 +353,6 @@
 
   window_background_view->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
   window_background_view->layer()->SetName("WindowBackgroundView");
-  window_background_view->layer()->SetColor(SK_ColorBLACK);
 
   // view::View that holds the WebView. ---------------------------------------
   web_view->SetPaintToLayer(ui::LAYER_TEXTURED);
diff --git a/chrome/browser/ui/views/overlay/video_overlay_window_views.cc b/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
index d25bffe..be0115c 100644
--- a/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
+++ b/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/views/overlay/back_to_tab_image_button.h"
 #include "chrome/browser/ui/views/overlay/back_to_tab_label_button.h"
 #include "chrome/browser/ui/views/overlay/close_image_button.h"
@@ -36,6 +37,8 @@
 #include "media/base/video_util.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/compositor.h"
 #include "ui/compositor/layer.h"
 #include "ui/gfx/color_palette.h"
@@ -98,6 +101,25 @@
   return static_cast<T*>(views->back().get());
 }
 
+class WindowBackgroundView : public views::View {
+ public:
+  METADATA_HEADER(WindowBackgroundView);
+
+  WindowBackgroundView() = default;
+  WindowBackgroundView(const WindowBackgroundView&) = delete;
+  WindowBackgroundView& operator=(const WindowBackgroundView&) = delete;
+  ~WindowBackgroundView() override = default;
+
+  void OnThemeChanged() override {
+    views::View::OnThemeChanged();
+    layer()->SetColor(
+        GetColorProvider()->GetColor(kColorOverlayWindowBackgroundColor));
+  }
+};
+
+BEGIN_METADATA(WindowBackgroundView, views::View)
+END_METADATA
+
 }  // namespace
 
 // static
@@ -196,10 +218,10 @@
 }
 
 void VideoOverlayWindowViews::SetUpViews() {
-  // views::View that is displayed when video is hidden. ----------------------
+  // View that is displayed when video is hidden. ------------------------------
   // Adding an extra pixel to width/height makes sure controls background cover
   // entirely window when platform has fractional scale applied.
-  auto window_background_view = std::make_unique<views::View>();
+  auto window_background_view = std::make_unique<WindowBackgroundView>();
   auto video_view = std::make_unique<views::View>();
   auto controls_scrim_view = std::make_unique<views::View>();
   auto controls_container_view = std::make_unique<views::View>();
@@ -291,7 +313,6 @@
 
   window_background_view->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
   window_background_view->layer()->SetName("WindowBackgroundView");
-  window_background_view->layer()->SetColor(SK_ColorBLACK);
 
   // view::View that holds the video. -----------------------------------------
   video_view->SetPaintToLayer(ui::LAYER_TEXTURED);
diff --git a/chrome/browser/ui/views/passwords/password_bubble_view_base.cc b/chrome/browser/ui/views/passwords/password_bubble_view_base.cc
index 257da6a7..bbbd81b1 100644
--- a/chrome/browser/ui/views/passwords/password_bubble_view_base.cc
+++ b/chrome/browser/ui/views/passwords/password_bubble_view_base.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/passwords/password_bubble_view_base.h"
 
+#include "base/notreached.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
@@ -52,6 +53,11 @@
       CreateBubble(web_contents, anchor_view, reason);
   DCHECK(bubble);
   DCHECK_EQ(bubble, g_manage_passwords_bubble_);
+  // TODO(crbug.com/1305276): In non-DCHECK mode we could fall through here and
+  // hard-crash if we requested a bubble and were in the wrong state. In the
+  // meantime we will abort if we did not create a bubble.
+  if (!g_manage_passwords_bubble_)
+    return;
 
   g_manage_passwords_bubble_->SetHighlightedButton(
       button_provider->GetPageActionIconView(
diff --git a/chrome/browser/ui/views/qrcode_generator/qrcode_generator_bubble.cc b/chrome/browser/ui/views/qrcode_generator/qrcode_generator_bubble.cc
index 223e443..bfda8e41 100644
--- a/chrome/browser/ui/views/qrcode_generator/qrcode_generator_bubble.cc
+++ b/chrome/browser/ui/views/qrcode_generator/qrcode_generator_bubble.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/qrcode_generator/qrcode_generator_bubble_controller.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
@@ -55,12 +56,11 @@
 
 namespace {
 
-// Rendered QR Code size, pixels.
-constexpr int kQRImageSizePx = 240;
 constexpr int kPaddingTooltipDownloadButtonPx = 10;
 
 // Calculates the height of the QR Code with padding.
 constexpr gfx::Size GetQRCodeImageSize() {
+  constexpr int kQRImageSizePx = 240;
   return gfx::Size(kQRImageSizePx, kQRImageSizePx);
 }
 
@@ -68,15 +68,6 @@
   return size.width() == size.height();
 }
 
-// Renders a solid square of color {r, g, b} at 100% alpha.
-gfx::ImageSkia GetPlaceholderImageSkia(const SkColor color) {
-  SkBitmap bitmap;
-  bitmap.allocN32Pixels(kQRImageSizePx, kQRImageSizePx);
-  bitmap.eraseARGB(0xFF, 0xFF, 0xFF, 0xFF);
-  bitmap.eraseColor(color);
-  return gfx::ImageSkia::CreateFromBitmap(bitmap, 1.0f);
-}
-
 gfx::ImageSkia CreateBackgroundImageSkia(const gfx::Size& size) {
   SkBitmap bitmap;
   bitmap.allocN32Pixels(size.width(), size.height());
@@ -123,6 +114,19 @@
   CloseBubble();
 }
 
+void QRCodeGeneratorBubble::OnThemeChanged() {
+  LocationBarBubbleDelegateView::OnThemeChanged();
+
+  const int border_radius = views::LayoutProvider::Get()->GetCornerRadiusMetric(
+      views::Emphasis::kHigh);
+  const auto* color_provider = GetColorProvider();
+  qr_code_image_->SetBorder(views::CreateRoundedRectBorder(
+      /*thickness=*/2, border_radius,
+      color_provider->GetColor(kColorQrCodeBorder)));
+  qr_code_image_->SetBackground(views::CreateRoundedRectBackground(
+      color_provider->GetColor(kColorQrCodeBackground), border_radius, 2));
+}
+
 void QRCodeGeneratorBubble::UpdateQRContent() {
   if (textfield_url_->GetText().empty()) {
     DisplayPlaceholderImage();
@@ -169,7 +173,7 @@
 }
 
 void QRCodeGeneratorBubble::DisplayPlaceholderImage() {
-  UpdateQRImage(GetPlaceholderImageSkia(gfx::kGoogleGrey100));
+  UpdateQRImage(CreateBackgroundImageSkia(GetQRCodeImageSize()));
 }
 
 void QRCodeGeneratorBubble::DisplayError(mojom::QRCodeGeneratorError error) {
@@ -218,17 +222,13 @@
   // QR Code image, with padding and border.
   using Alignment = views::ImageView::Alignment;
   auto qr_code_image = std::make_unique<views::ImageView>();
-  const int border_radius = views::LayoutProvider::Get()->GetCornerRadiusMetric(
-      views::Emphasis::kHigh);
-  qr_code_image->SetBorder(views::CreateRoundedRectBorder(
-      /*thickness=*/2, border_radius, gfx::kGoogleGrey200));
   qr_code_image->SetHorizontalAlignment(Alignment::kCenter);
   qr_code_image->SetVerticalAlignment(Alignment::kCenter);
   qr_code_image->SetImageSize(GetQRCodeImageSize());
+  const int border_radius = views::LayoutProvider::Get()->GetCornerRadiusMetric(
+      views::Emphasis::kHigh);
   qr_code_image->SetPreferredSize(GetQRCodeImageSize() +
                                   gfx::Size(border_radius, border_radius));
-  qr_code_image->SetBackground(
-      views::CreateRoundedRectBackground(SK_ColorWHITE, border_radius, 2));
   qr_code_image->SetProperty(views::kCrossAxisAlignmentKey,
                              views::LayoutAlignment::kCenter);
 
diff --git a/chrome/browser/ui/views/qrcode_generator/qrcode_generator_bubble.h b/chrome/browser/ui/views/qrcode_generator/qrcode_generator_bubble.h
index 1e094ef..2955ca9 100644
--- a/chrome/browser/ui/views/qrcode_generator/qrcode_generator_bubble.h
+++ b/chrome/browser/ui/views/qrcode_generator/qrcode_generator_bubble.h
@@ -55,6 +55,7 @@
 
   // QRCodeGeneratorBubbleView:
   void Hide() override;
+  void OnThemeChanged() override;
 
   // Returns a suggested download filename for a given URL.
   // e.g.: www.foo.com may suggest qrcode_foo.png.
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index 0f4b16e..7475765 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -769,8 +769,7 @@
   // Cancel any pending tab transition.
   hover_tab_selector_.CancelTabTransition();
 
-  tabstrip_->AddTabAt(index, TabRendererData::FromTabInModel(model_, index),
-                      is_active);
+  tabstrip_->AddTabAt(index, TabRendererData::FromTabInModel(model_, index));
   // Try to show tab groups IPH if needed.
   if (tabstrip_->GetTabCount() >= 6) {
     browser_view_->NotifyFeatureEngagementEvent(
diff --git a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
index f100972..989dfaf 100644
--- a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
@@ -25,7 +25,7 @@
   num_tabs_++;
   tab_groups_.insert(tab_groups_.begin() + index, absl::nullopt);
 
-  tab_strip_->AddTabAt(index, TabRendererData(), is_active);
+  tab_strip_->AddTabAt(index, TabRendererData());
   if (is_active) {
     SelectTab(index,
               ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::PointF(), gfx::PointF(),
@@ -39,7 +39,7 @@
 
   TabRendererData data;
   data.pinned = true;
-  tab_strip_->AddTabAt(index, std::move(data), is_active);
+  tab_strip_->AddTabAt(index, std::move(data));
   if (is_active)
     active_index_ = index;
 }
diff --git a/chrome/browser/ui/views/tabs/tab_container.cc b/chrome/browser/ui/views/tabs/tab_container.cc
index bc4985f..9b59f4d 100644
--- a/chrome/browser/ui/views/tabs/tab_container.cc
+++ b/chrome/browser/ui/views/tabs/tab_container.cc
@@ -324,6 +324,17 @@
       std::move(tab), GetViewInsertionIndex(group, absl::nullopt, model_index));
   tabs_view_model_.Add(tab_ptr, model_index);
   layout_helper_->InsertTabAt(model_index, tab_ptr, pinned);
+
+  // Don't animate the first tab, it looks weird, and don't animate anything
+  // if the containing window isn't visible yet.
+  if (GetTabCount() > 1 && GetWidget() && GetWidget()->IsVisible()) {
+    StartInsertTabAnimation(model_index);
+  } else {
+    CompleteAnimationAndLayout();
+  }
+
+  UpdateAccessibleTabIndices();
+
   return tab_ptr;
 }
 
@@ -334,12 +345,23 @@
   layout_helper_->MoveTab(tab->group(), from_model_index, to_model_index);
 }
 
-void TabContainer::RemoveTabFromViewModel(int index) {
-  UpdateHoverCard(nullptr, TabController::HoverCardUpdateType::kTabRemoved);
+void TabContainer::RemoveTab(int model_index, bool was_active) {
+  UpdateClosingModeOnRemovedTab(model_index, was_active);
 
-  Tab* tab = GetTabAtModelIndex(index);
-  tabs_view_model_.Remove(index);
-  layout_helper_->RemoveTabAt(index, tab);
+  PrepareForAnimation();
+
+  Tab* tab = GetTabAtModelIndex(model_index);
+  tab->SetClosing(true);
+
+  RemoveTabFromViewModel(model_index);
+
+  UpdateIdealBounds();
+  AnimateToIdealBounds();
+
+  // Animate the tab closed.
+  AnimateTabClosed(tab, model_index);
+
+  UpdateAccessibleTabIndices();
 }
 
 void TabContainer::ScrollTabToVisible(int model_index) {
@@ -753,44 +775,6 @@
   }
 }
 
-void TabContainer::OnTabWillBeRemovedAt(int model_index, bool was_active) {
-  // The tab at |model_index| has already been removed from the model, but is
-  // still in |tabs_view_model_|.  Index math with care!
-  const int model_count = GetTabCount() - 1;
-  const int tab_overlap = TabStyle::GetTabOverlap();
-  if (in_tab_close() && model_count > 0 && model_index != model_count) {
-    // The user closed a tab other than the last tab. Set
-    // override_available_width_for_tabs_ so that as the user closes tabs with
-    // the mouse a tab continues to fall under the mouse.
-    int next_active_index = controller_->GetActiveIndex();
-    DCHECK(IsValidModelIndex(next_active_index));
-    if (model_index <= next_active_index) {
-      // At this point, model's internal state has already been updated.
-      // |contents| has been detached from model and the active index has been
-      // updated. But the tab for |contents| isn't removed yet. Thus, we need to
-      // fix up next_active_index based on it.
-      next_active_index++;
-    }
-    Tab* next_active_tab = GetTabAtModelIndex(next_active_index);
-    Tab* tab_being_removed = GetTabAtModelIndex(model_index);
-
-    int size_delta = tab_being_removed->width();
-    if (!tab_being_removed->data().pinned && was_active &&
-        layout_helper_->active_tab_width() >
-            layout_helper_->inactive_tab_width()) {
-      // When removing an active, non-pinned tab, an inactive tab will be made
-      // active and thus given the active width. Thus the width being removed
-      // from the container is really the current width of whichever inactive
-      // tab will be made active.
-      size_delta = next_active_tab->width();
-    }
-
-    override_available_width_for_tabs_ =
-        tabs_view_model_.ideal_bounds(model_count).right() - size_delta +
-        tab_overlap;
-  }
-}
-
 void TabContainer::Layout() {
   if (base::FeatureList::IsEnabled(features::kScrollableTabStrip)) {
     // With tab scrolling, the tab container is solely responsible for its own
@@ -926,6 +910,51 @@
   return this;
 }
 
+void TabContainer::StartInsertTabAnimation(int model_index) {
+  PrepareForAnimation();
+
+  ExitTabClosingMode();
+
+  gfx::Rect bounds = GetTabAtModelIndex(model_index)->bounds();
+  bounds.set_height(GetLayoutConstant(TAB_HEIGHT));
+
+  // Adjust the starting bounds of the new tab.
+  const int tab_overlap = TabStyle::GetTabOverlap();
+  if (model_index > 0) {
+    // If we have a tab to our left, start at its right edge.
+    bounds.set_x(GetTabAtModelIndex(model_index - 1)->bounds().right() -
+                 tab_overlap);
+  } else if (model_index + 1 < GetTabCount()) {
+    // Otherwise, if we have a tab to our right, start at its left edge.
+    bounds.set_x(GetTabAtModelIndex(model_index + 1)->bounds().x());
+  } else {
+    NOTREACHED() << "First tab inserted into the tabstrip should not animate.";
+  }
+
+  // Start at the width of the overlap in order to animate at the same speed
+  // the surrounding tabs are moving, since at this width the subsequent tab
+  // is naturally positioned at the same X coordinate.
+  bounds.set_width(tab_overlap);
+  GetTabAtModelIndex(model_index)->SetBoundsRect(bounds);
+
+  // Animate in to the full width.
+  UpdateIdealBounds();
+  AnimateToIdealBounds();
+}
+
+void TabContainer::RemoveTabFromViewModel(int index) {
+  Tab* tab = GetTabAtModelIndex(index);
+  bool tab_was_active = tab->IsActive();
+
+  UpdateHoverCard(nullptr, TabController::HoverCardUpdateType::kTabRemoved);
+
+  tabs_view_model_.Remove(index);
+  layout_helper_->RemoveTabAt(index, tab);
+
+  if (tab_was_active)
+    tab->ActiveStateChanged();
+}
+
 void TabContainer::OnTabCloseAnimationCompleted(Tab* tab) {
   DCHECK(tab->closing());
 
@@ -933,6 +962,45 @@
   layout_helper_->OnTabDestroyed(tab);
 }
 
+void TabContainer::UpdateClosingModeOnRemovedTab(int model_index,
+                                                 bool was_active) {
+  // The tab at |model_index| has already been removed from the model, but is
+  // still in |tabs_view_model_|.  Index math with care!
+  const int model_count = GetTabCount() - 1;
+  const int tab_overlap = TabStyle::GetTabOverlap();
+  if (in_tab_close() && model_count > 0 && model_index != model_count) {
+    // The user closed a tab other than the last tab. Set
+    // override_available_width_for_tabs_ so that as the user closes tabs with
+    // the mouse a tab continues to fall under the mouse.
+    int next_active_index = controller_->GetActiveIndex();
+    DCHECK(IsValidModelIndex(next_active_index));
+    if (model_index <= next_active_index) {
+      // At this point, model's internal state has already been updated.
+      // |contents| has been detached from model and the active index has been
+      // updated. But the tab for |contents| isn't removed yet. Thus, we need to
+      // fix up next_active_index based on it.
+      next_active_index++;
+    }
+    Tab* next_active_tab = GetTabAtModelIndex(next_active_index);
+    Tab* tab_being_removed = GetTabAtModelIndex(model_index);
+
+    int size_delta = tab_being_removed->width();
+    if (!tab_being_removed->data().pinned && was_active &&
+        layout_helper_->active_tab_width() >
+            layout_helper_->inactive_tab_width()) {
+      // When removing an active, non-pinned tab, an inactive tab will be made
+      // active and thus given the active width. Thus the width being removed
+      // from the container is really the current width of whichever inactive
+      // tab will be made active.
+      size_delta = next_active_tab->width();
+    }
+
+    override_available_width_for_tabs_ =
+        tabs_view_model_.ideal_bounds(model_count).right() - size_delta +
+        tab_overlap;
+  }
+}
+
 int TabContainer::GetViewInsertionIndex(
     absl::optional<tab_groups::TabGroupId> group,
     absl::optional<int> from_model_index,
diff --git a/chrome/browser/ui/views/tabs/tab_container.h b/chrome/browser/ui/views/tabs/tab_container.h
index 181707d0..01c614c0 100644
--- a/chrome/browser/ui/views/tabs/tab_container.h
+++ b/chrome/browser/ui/views/tabs/tab_container.h
@@ -40,10 +40,7 @@
 
   Tab* AddTab(std::unique_ptr<Tab> tab, int model_index, TabPinned pinned);
   void MoveTab(Tab* tab, int from_model_index, int to_model_index);
-
-  // Remove the tab from |tabs_view_model_|, but *not* from the View hierarchy,
-  // so it can be animated closed.
-  void RemoveTabFromViewModel(int index);
+  void RemoveTab(int index, bool was_active);
 
   void ScrollTabToVisible(int model_index);
 
@@ -136,8 +133,6 @@
     return override_available_width_for_tabs_;
   }
 
-  void OnTabWillBeRemovedAt(int model_index, bool was_active);
-
   // TODO (1295774): Move callers down into TabContainer so this
   // encapsulation-breaking getter can be removed.
   TabStripLayoutHelper* layout_helper() const { return layout_helper_.get(); }
@@ -162,8 +157,19 @@
  private:
   class RemoveTabDelegate;
 
+  // Invoked from |AddTab| after the newly created tab has been inserted.
+  void StartInsertTabAnimation(int model_index);
+
+  // Remove the tab from |tabs_view_model_|, but *not* from the View hierarchy,
+  // so it can be animated closed.
+  void RemoveTabFromViewModel(int index);
+
   void OnTabCloseAnimationCompleted(Tab* tab);
 
+  // Updates |override_available_width_for_tabs_|, if necessary, to account for
+  // the removal of the tab at |model_index|.
+  void UpdateClosingModeOnRemovedTab(int model_index, bool was_active);
+
   // Returns the corresponding view index of a |tab| to be inserted at
   // |to_model_index|. Used to reorder the child views of the tab container
   // so that focus order stays consistent with the visual tab order.
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_browsertest.cc b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_browsertest.cc
index c70736f..b65aad43 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_browsertest.cc
@@ -212,7 +212,7 @@
   new_tab_data.title = u"Test Tab 2";
   new_tab_data.last_committed_url =
       GURL("http://example.com/this/should/not/be/seen");
-  tab_strip()->AddTabAt(1, new_tab_data, false);
+  tab_strip()->AddTabAt(1, new_tab_data);
 
   // Cycle focus until it reaches a tab.
   while (!tab_strip()->IsFocusInTabs())
@@ -261,7 +261,7 @@
   new_tab_data.title = u"Test Tab 2";
   new_tab_data.last_committed_url =
       GURL("http://example.com/this/should/not/be/seen");
-  tab_strip()->AddTabAt(1, new_tab_data, false);
+  tab_strip()->AddTabAt(1, new_tab_data);
 
   ShowUi("default");
 
@@ -314,8 +314,8 @@
 
 IN_PROC_BROWSER_TEST_F(TabHoverCardBubbleViewBrowserTest,
                        HoverCardsSeenRatioMetric) {
-  tab_strip()->AddTabAt(1, TabRendererData(), false);
-  tab_strip()->AddTabAt(2, TabRendererData(), false);
+  tab_strip()->AddTabAt(1, TabRendererData());
+  tab_strip()->AddTabAt(2, TabRendererData());
 
   HoverMouseOverTabAt(0);
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index fcb9c2e..aed490a8 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -855,11 +855,12 @@
     tab_at(i)->StepLoadingAnimation(elapsed_time);
 }
 
-void TabStrip::AddTabAt(int model_index, TabRendererData data, bool is_active) {
+void TabStrip::AddTabAt(int model_index, TabRendererData data) {
   const bool pinned = data.pinned;
   Tab* tab = tab_container_->AddTab(
       std::make_unique<Tab>(this), model_index,
       pinned ? TabPinned::kPinned : TabPinned::kUnpinned);
+
   tab->set_context_menu_controller(&context_menu_controller_);
   tab->AddObserver(this);
   selected_tabs_.IncrementFrom(model_index);
@@ -869,16 +870,6 @@
   // callbacks.
   tab->SetData(std::move(data));
 
-  // Don't animate the first tab, it looks weird, and don't animate anything
-  // if the containing window isn't visible yet.
-  if (GetTabCount() > 1 && GetWidget() && GetWidget()->IsVisible()) {
-    StartInsertTabAnimation(model_index);
-  } else {
-    tab_container_->CompleteAnimationAndLayout();
-  }
-
-  tab_container_->UpdateAccessibleTabIndices();
-
   for (TabStripObserver& observer : observers_)
     observer.OnTabAdded(model_index);
 
@@ -938,12 +929,12 @@
 void TabStrip::RemoveTabAt(content::WebContents* contents,
                            int model_index,
                            bool was_active) {
-  StartRemoveTabAnimation(model_index, was_active);
-
-  tab_container_->UpdateAccessibleTabIndices();
+  tab_container_->RemoveTab(model_index, was_active);
 
   UpdateHoverCard(nullptr, HoverCardUpdateType::kTabRemoved);
 
+  selected_tabs_.DecrementFrom(model_index);
+
   for (TabStripObserver& observer : observers_)
     observer.OnTabRemoved(model_index);
 
@@ -1871,54 +1862,6 @@
     TouchUMA::RecordGestureAction(TouchUMA::kGestureNewTabTap);
 }
 
-void TabStrip::StartInsertTabAnimation(int model_index) {
-  tab_container_->PrepareForAnimation();
-
-  tab_container_->ExitTabClosingMode();
-
-  gfx::Rect bounds = tab_at(model_index)->bounds();
-  bounds.set_height(GetLayoutConstant(TAB_HEIGHT));
-
-  // Adjust the starting bounds of the new tab.
-  const int tab_overlap = TabStyle::GetTabOverlap();
-  if (model_index > 0) {
-    // If we have a tab to our left, start at its right edge.
-    bounds.set_x(tab_at(model_index - 1)->bounds().right() - tab_overlap);
-  } else if (model_index + 1 < GetTabCount()) {
-    // Otherwise, if we have a tab to our right, start at its left edge.
-    bounds.set_x(tab_at(model_index + 1)->bounds().x());
-  } else {
-    NOTREACHED() << "First tab inserted into the tabstrip should not animate.";
-  }
-
-  // Start at the width of the overlap in order to animate at the same speed
-  // the surrounding tabs are moving, since at this width the subsequent tab
-  // is naturally positioned at the same X coordinate.
-  bounds.set_width(tab_overlap);
-  tab_at(model_index)->SetBoundsRect(bounds);
-
-  // Animate in to the full width.
-  tab_container_->UpdateIdealBounds();
-  tab_container_->AnimateToIdealBounds();
-}
-
-void TabStrip::StartRemoveTabAnimation(int model_index, bool was_active) {
-  tab_container_->OnTabWillBeRemovedAt(model_index, was_active);
-
-  tab_container_->PrepareForAnimation();
-
-  Tab* tab = tab_at(model_index);
-  tab->SetClosing(true);
-
-  RemoveTabFromViewModel(model_index);
-
-  tab_container_->UpdateIdealBounds();
-  tab_container_->AnimateToIdealBounds();
-
-  // Animate the tab closed.
-  tab_container_->AnimateTabClosed(tab, model_index);
-}
-
 void TabStrip::StartMoveTabAnimation() {
   tab_container_->PrepareForAnimation();
   tab_container_->UpdateIdealBounds();
@@ -1997,19 +1940,6 @@
   controller_->CloseTab(model_index);
 }
 
-void TabStrip::RemoveTabFromViewModel(int index) {
-  Tab* closing_tab = tab_at(index);
-  bool closing_tab_was_active = closing_tab->IsActive();
-
-  // We still need to keep the tab alive until the remove tab animation
-  // completes. Defer destroying it until then.
-  tab_container_->RemoveTabFromViewModel(index);
-  selected_tabs_.DecrementFrom(index);
-
-  if (closing_tab_was_active)
-    closing_tab->ActiveStateChanged();
-}
-
 void TabStrip::StoppedDraggingView(TabSlotView* view, bool* is_first_view) {
   if (view &&
       view->GetTabSlotViewType() == TabSlotView::ViewType::kTabGroupHeader) {
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index e255a25e..8d03c5c 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -132,7 +132,7 @@
   void UpdateLoadingAnimations(const base::TimeDelta& elapsed_time);
 
   // Adds a tab at the specified index.
-  void AddTabAt(int model_index, TabRendererData data, bool is_active);
+  void AddTabAt(int model_index, TabRendererData data);
 
   // Moves a tab.
   void MoveTab(int from_model_index, int to_model_index, TabRendererData data);
@@ -405,17 +405,6 @@
 
   std::map<tab_groups::TabGroupId, TabGroupHeader*> GetGroupHeaders();
 
-  // Invoked from |AddTabAt| after the newly created tab has been inserted.
-  void StartInsertTabAnimation(int model_index);
-
-  // Animates the removal of the tab at |model_index|. Defers to the old
-  // animation style when appropriate.
-  void StartRemoveTabAnimation(int model_index, bool was_active);
-
-  // Animates the removal of the tab at |model_index| using the old animation
-  // style.
-  void StartFallbackRemoveTabAnimation(int model_index, bool was_active);
-
   // Invoked from |MoveTab| after |tab_data_| has been updated to animate the
   // move.
   void StartMoveTabAnimation();
@@ -440,13 +429,6 @@
   // Closes the tab at |model_index|.
   void CloseTabInternal(int model_index, CloseTabSource source);
 
-  // Removes the tab at |index| from |tabs_|.
-  void RemoveTabFromViewModel(int index);
-
-  // Cleans up the Tab from the TabStrip. This is called from the tab animation
-  // code and is not a general-purpose method.
-  void OnTabCloseAnimationCompleted(Tab* tab);
-
   // Invoked from StoppedDraggingTabs to cleanup |view|. If |view| is known
   // |is_first_view| is set to true.
   void StoppedDraggingView(TabSlotView* view, bool* is_first_view);
diff --git a/chrome/browser/ui/views/toolbar/webui_tab_counter_button.cc b/chrome/browser/ui/views/toolbar/webui_tab_counter_button.cc
index bcb9ff97..629c092a 100644
--- a/chrome/browser/ui/views/toolbar/webui_tab_counter_button.cc
+++ b/chrome/browser/ui/views/toolbar/webui_tab_counter_button.cc
@@ -575,14 +575,14 @@
       WEBUI_TAB_COUNTER_CXMENU_CLOSE_TAB,
       l10n_util::GetStringUTF16(
           IDS_WEBUI_TAB_STRIP_TAB_COUNTER_CXMENU_CLOSE_TAB),
-      ui::ImageModel::FromImageSkia(gfx::CreateVectorIcon(
-          vector_icons::kCloseIcon, gfx::kFaviconSize, SK_ColorGRAY)));
+      ui::ImageModel::FromVectorIcon(vector_icons::kCloseIcon,
+                                     ui::kColorMenuIcon, gfx::kFaviconSize));
   menu_model_->AddSeparator(ui::MenuSeparatorType::NORMAL_SEPARATOR);
   menu_model_->AddItemWithIcon(
       WEBUI_TAB_COUNTER_CXMENU_NEW_TAB,
       l10n_util::GetStringUTF16(IDS_WEBUI_TAB_STRIP_TAB_COUNTER_CXMENU_NEW_TAB),
-      ui::ImageModel::FromImageSkia(
-          gfx::CreateVectorIcon(kAddIcon, gfx::kFaviconSize, SK_ColorGRAY)));
+      ui::ImageModel::FromVectorIcon(kAddIcon, ui::kColorMenuIcon,
+                                     gfx::kFaviconSize));
   menu_runner_ = std::make_unique<views::MenuRunner>(
       menu_model_.get(), views::MenuRunner::HAS_MNEMONICS |
                              views::MenuRunner::CONTEXT_MENU |
diff --git a/chrome/browser/ui/views/user_education/tip_marquee_view.cc b/chrome/browser/ui/views/user_education/tip_marquee_view.cc
index 78f766b7..54bb664 100644
--- a/chrome/browser/ui/views/user_education/tip_marquee_view.cc
+++ b/chrome/browser/ui/views/user_education/tip_marquee_view.cc
@@ -93,8 +93,10 @@
     views::View* const placeholder_image =
         AddChildView(std::make_unique<views::View>());
     placeholder_image->SetPreferredSize(gfx::Size(150, 175));
-    placeholder_image->SetBackground(
-        views::CreateSolidBackground(SK_ColorLTGRAY));
+    // In real UI, we wouldn't use kColorMidground directly, but rather create
+    // a new color ID mapped to it (or similar).
+    placeholder_image->SetBackground(views::CreateThemedSolidBackground(
+        placeholder_image, ui::kColorMidground));
 
     views::View* const rhs_view = AddChildView(std::make_unique<views::View>());
     auto* const rhs_layout =
diff --git a/chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.cc b/chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.cc
index 61da1403..06157fa 100644
--- a/chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.cc
+++ b/chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.cc
@@ -6,10 +6,12 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/services/qrcode_generator/public/cpp/qrcode_generator_service.h"
 #include "chrome/services/qrcode_generator/public/mojom/qrcode_generator.mojom.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/color/color_provider.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/image_view.h"
@@ -35,20 +37,13 @@
         views::BoxLayout::MainAxisAlignment::kCenter);
     layout->set_cross_axis_alignment(
         views::BoxLayout::CrossAxisAlignment::kCenter);
-    const int border_radius =
-        views::LayoutProvider::Get()->GetCornerRadiusMetric(
-            views::Emphasis::kHigh);
     qr_code_image_ = AddChildViewAt(std::make_unique<views::ImageView>(), 0);
-    qr_code_image_->SetBorder(views::CreateRoundedRectBorder(
-        /*thickness=*/2, border_radius, gfx::kGoogleGrey200));
     qr_code_image_->SetHorizontalAlignment(
         views::ImageView::Alignment::kCenter);
     qr_code_image_->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
     qr_code_image_->SetImageSize(qrCodeImageSize());
     qr_code_image_->SetPreferredSize(qrCodeImageSize() +
                                      gfx::Size(kQrCodeMargin, kQrCodeMargin));
-    qr_code_image_->SetBackground(
-        views::CreateRoundedRectBackground(SK_ColorWHITE, border_radius, 2));
 
     qrcode_generator::mojom::GenerateQRCodeRequestPtr request =
         qrcode_generator::mojom::GenerateQRCodeRequest::New();
@@ -74,6 +69,20 @@
   AuthenticatorQRViewCentered& operator=(const AuthenticatorQRViewCentered&) =
       delete;
 
+  void OnThemeChanged() override {
+    views::View::OnThemeChanged();
+
+    const int border_radius =
+        views::LayoutProvider::Get()->GetCornerRadiusMetric(
+            views::Emphasis::kHigh);
+    const auto* color_provider = GetColorProvider();
+    qr_code_image_->SetBorder(views::CreateRoundedRectBorder(
+        /*thickness=*/2, border_radius,
+        color_provider->GetColor(kColorQrCodeBorder)));
+    qr_code_image_->SetBackground(views::CreateRoundedRectBackground(
+        color_provider->GetColor(kColorQrCodeBackground), border_radius, 2));
+  }
+
  private:
   qrcode_generator::mojom::QRCodeGeneratorService* qr_code_service() {
     if (!qr_code_service_remote_)
diff --git a/chrome/browser/ui/webui/inspect_ui_browsertest.cc b/chrome/browser/ui/webui/inspect_ui_browsertest.cc
index 866ef699..8da5f31 100644
--- a/chrome/browser/ui/webui/inspect_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/inspect_ui_browsertest.cc
@@ -52,13 +52,7 @@
   }
 };
 
-// Disabled due to excessive flakiness. http://crbug.com/1304812
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_InspectUIPage DISABLED_InspectUIPage
-#else
-#define MAYBE_InspectUIPage InspectUIPage
-#endif
-IN_PROC_BROWSER_TEST_F(InspectUITest, MAYBE_InspectUIPage) {
+IN_PROC_BROWSER_TEST_F(InspectUITest, InspectUIPage) {
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                            GURL(chrome::kChromeUIInspectURL)));
   ASSERT_TRUE(WebUIBrowserTest::RunJavascriptAsyncTest(
@@ -113,7 +107,13 @@
                                            GURL(chrome::kChromeUIInspectURL)));
 }
 
-IN_PROC_BROWSER_TEST_F(InspectUITest, LaunchUIDevtools) {
+// Disabled due to excessive flakiness. http://crbug.com/1304812
+#if BUILDFLAG(IS_MAC)
+#define MAYBE_LaunchUIDevtools DISABLED_LaunchUIDevtools
+#else
+#define MAYBE_LaunchUIDevtools LaunchUIDevtools
+#endif
+IN_PROC_BROWSER_TEST_F(InspectUITest, MAYBE_LaunchUIDevtools) {
   ASSERT_TRUE(embedded_test_server()->Start());
   TabStripModel* tab_strip_model = browser()->tab_strip_model();
 
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 20d809e7..7266159 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1647280729-30a5e69b6f02e84d85c3385dd2f236d1b96be257.profdata
+chrome-mac-arm-main-1647302357-e605e3a470d6c1be450e04cc8d355e7542e6a787.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 66030b9..3e6b884 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1647280729-6ca29980d40a00b4dec37a0127e6e39f12f3abb9.profdata
+chrome-mac-main-1647302357-b1d7c9a56e4da5260f62f4ed8df8cccbd9476905.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 8ecd7c9..970311f7 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1647280729-f8d04e15150920cf9b71e316b0ce83b4e0da30ee.profdata
+chrome-win32-main-1647291450-a010c9d9fe7047e6aa656acdc543689945887704.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 7f35ab0..0035aa5b 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1647280729-f5550f32a2246dcb9f47e3474436058bf3c16b61.profdata
+chrome-win64-main-1647291450-305c59221bb9bcb7144a62d700e9a145017942ed.profdata
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index a2df3ffa..2ef6f5f 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4979,12 +4979,12 @@
     "../browser/policy/messaging_layer/upload/dm_server_upload_service_unittest.cc",
     "../browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc",
     "../browser/policy/messaging_layer/upload/record_upload_request_builder_unittest.cc",
-    "../browser/policy/messaging_layer/upload/test_util.cc",
-    "../browser/policy/messaging_layer/upload/test_util.h",
     "../browser/policy/messaging_layer/upload/upload_client_unittest.cc",
     "../browser/policy/messaging_layer/upload/upload_provider_unittest.cc",
     "../browser/policy/messaging_layer/util/dm_token_retriever_provider_unittest.cc",
     "../browser/policy/messaging_layer/util/report_queue_manual_test_context_unittest.cc",
+    "../browser/policy/messaging_layer/util/test.cc",
+    "../browser/policy/messaging_layer/util/test.h",
     "../browser/policy/messaging_layer/util/user_dm_token_retriever_unittest.cc",
     "../browser/policy/profile_policy_connector_unittest.cc",
     "../browser/policy/webusb_allow_devices_for_urls_policy_handler_unittest.cc",
@@ -8223,6 +8223,7 @@
       "//chrome/browser/supervised_user:test_support",
       "//chrome/browser/supervised_user/kids_chrome_management:proto",
       "//chrome/browser/supervised_user/supervised_user_error_page:unit_tests",
+      "//chrome/browser/supervised_user/supervised_user_features",
       "//chrome/browser/supervised_user/supervised_user_features:unit_tests",
     ]
   }
diff --git a/chrome/test/android/test_support/src/org/chromium/chrome/test_support/OWNERS b/chrome/test/android/test_support/src/org/chromium/chrome/test_support/OWNERS
index 6c756cb..d655647 100644
--- a/chrome/test/android/test_support/src/org/chromium/chrome/test_support/OWNERS
+++ b/chrome/test/android/test_support/src/org/chromium/chrome/test_support/OWNERS
@@ -1,2 +1,2 @@
 per-file PaymentRequestTestBridge.java=rouslan@chromium.org
-per-file PaymentRequestTestBridge.java=maxlg@chromium.org
+per-file PaymentRequestTestBridge.java=nburris@chromium.org
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 0e296f5c..9e058b4 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -282,6 +282,7 @@
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/dark_mode_subpage_tests.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/dom_switch_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/main_view_test.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/permission_item_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/pin_to_shelf_item_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/pwa_detail_view_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/supported_links_item_test.m.js",
diff --git a/chrome/test/data/webui/app_settings/BUILD.gn b/chrome/test/data/webui/app_settings/BUILD.gn
index f0e750d7..5090821 100644
--- a/chrome/test/data/webui/app_settings/BUILD.gn
+++ b/chrome/test/data/webui/app_settings/BUILD.gn
@@ -32,7 +32,6 @@
     "app_test.ts",
     "test_app_management_browser_proxy.ts",
   ]
-  definitions = [ "//tools/typescript/definitions/metrics_private.d.ts" ]
   deps = [ "//chrome/browser/resources/app_settings:build_ts" ]
   extra_deps = [ "..:generate_definitions" ]
 }
diff --git a/chrome/test/data/webui/app_settings/test_app_management_browser_proxy.ts b/chrome/test/data/webui/app_settings/test_app_management_browser_proxy.ts
index 03da70e..7d69578 100644
--- a/chrome/test/data/webui/app_settings/test_app_management_browser_proxy.ts
+++ b/chrome/test/data/webui/app_settings/test_app_management_browser_proxy.ts
@@ -82,8 +82,4 @@
     this.fakeHandler = new FakePageHandler(this.callbackRouterRemote, app);
     this.handler = this.fakeHandler;
   }
-
-  recordEnumerationValue(metricName: string, value: number, enumSize: number) {
-    chrome.metricsPrivate.recordEnumerationValue(metricName, value, enumSize);
-  }
 }
diff --git a/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts
index 0170b1c..064aab517 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts
@@ -8,7 +8,7 @@
 import {AmbientObserver} from 'chrome://personalization/trusted/ambient/ambient_observer.js';
 import {AmbientSubpage} from 'chrome://personalization/trusted/ambient/ambient_subpage_element.js';
 import {TopicSourceItem} from 'chrome://personalization/trusted/ambient/topic_source_item_element.js';
-import {TemperatureUnit, TopicSource} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js';
+import {AmbientModeAlbum, TemperatureUnit, TopicSource} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js';
 import {Paths, PersonalizationRouter} from 'chrome://personalization/trusted/personalization_router_element.js';
 import {emptyState} from 'chrome://personalization/trusted/personalization_state.js';
 import {CrRadioButtonElement} from 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
@@ -20,6 +20,13 @@
 import {TestAmbientProvider} from './test_ambient_interface_provider.js';
 import {TestPersonalizationStore} from './test_personalization_store.js';
 
+
+export function getSelectedAlbums(
+    albums: AmbientModeAlbum[], topicSource: TopicSource): AmbientModeAlbum[] {
+  return albums.filter(
+      album => album.topicSource === topicSource && album.checked);
+}
+
 export function AmbientSubpageTest() {
   let ambientSubpageElement: AmbientSubpage|null;
   let ambientProvider: TestAmbientProvider;
@@ -464,9 +471,23 @@
     assertFalse(albums[0].album!.checked);
     assertFalse(albums[1].album!.checked);
     assertTrue(albums[2].album!.checked);
+    let selectedAlbums = getSelectedAlbums(
+        personalizationStore.data.ambient.albums,
+        personalizationStore.data.ambient.topicSource);
+    assertEquals(1, selectedAlbums!.length);
+    assertEquals('2', selectedAlbums[0]!.title);
 
+    personalizationStore.expectAction(AmbientActionName.SET_ALBUM_SELECTED);
     albums[1].$.image.click();
     assertTrue(albums[1].album!.checked);
+    await personalizationStore.waitForAction(
+        AmbientActionName.SET_ALBUM_SELECTED);
+    selectedAlbums = getSelectedAlbums(
+        personalizationStore.data.ambient.albums,
+        personalizationStore.data.ambient.topicSource);
+    assertEquals(2, selectedAlbums!.length);
+    assertEquals('1', selectedAlbums[0]!.title);
+    assertEquals('2', selectedAlbums[1]!.title);
   });
 
   test('not deselect last art album', async () => {
diff --git a/chrome/test/data/webui/cr_components/BUILD.gn b/chrome/test/data/webui/cr_components/BUILD.gn
index f9d5287..e2e44c4 100644
--- a/chrome/test/data/webui/cr_components/BUILD.gn
+++ b/chrome/test/data/webui/cr_components/BUILD.gn
@@ -20,8 +20,6 @@
 # Test files that do not require preprocessing. If adding // <if expr> to any
 # file below, move it to the list above.
 non_preprocessed_files = [
-  "app_management/app_management_test_support.ts",
-  "app_management/permission_item_test.ts",
   "customize_themes_test.ts",
   "managed_dialog_test.ts",
   "most_visited_focus_test.ts",
diff --git a/chrome/test/data/webui/cr_components/app_management/app_management_test_support.ts b/chrome/test/data/webui/cr_components/app_management/app_management_test_support.ts
deleted file mode 100644
index 21010bc..0000000
--- a/chrome/test/data/webui/cr_components/app_management/app_management_test_support.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-// 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 {App, PageCallbackRouter, PageHandlerRemote} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
-import {BrowserProxy} from 'chrome://resources/cr_components/app_management/browser_proxy.js';
-import {PermissionType, TriState} from 'chrome://resources/cr_components/app_management/permission_constants.js';
-import {createTriStatePermission} from 'chrome://resources/cr_components/app_management/permission_util.js';
-import {AppType, InstallReason, InstallSource, OptionalBool, RunOnOsLoginMode, WindowMode} from 'chrome://resources/cr_components/app_management/types.mojom-webui.js';
-import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
-
-export class TestAppManagementBrowserProxy extends TestBrowserProxy implements
-    BrowserProxy {
-  callbackRouter: PageCallbackRouter;
-  handler: PageHandlerRemote&TestBrowserProxy;
-
-  constructor() {
-    super(['recordEnumerationValue']);
-    this.handler = TestBrowserProxy.fromClass(PageHandlerRemote);
-    this.callbackRouter = new PageCallbackRouter();
-  }
-
-  recordEnumerationValue(metricName: string, value: number, enumSize: number) {
-    this.methodCalled('recordEnumerationValue', metricName, value, enumSize);
-  }
-}
-
-export function createTestApp(): App {
-  const app: App = {
-    id: 'test_loader.html',
-    type: AppType.kWeb,
-    title: 'App Title',
-    description: '',
-    version: '5.1',
-    size: '9.0MB',
-    isPinned: OptionalBool.kFalse,
-    isPolicyPinned: OptionalBool.kFalse,
-    installReason: InstallReason.kUser,
-    permissions: {},
-    hideMoreSettings: false,
-    hidePinToShelf: false,
-    isPreferredApp: false,
-    windowMode: WindowMode.kWindow,
-    resizeLocked: false,
-    hideResizeLocked: true,
-    supportedLinks: [],
-    runOnOsLogin: {loginMode: RunOnOsLoginMode.kNotRun, isManaged: false},
-    fileHandlingState: {
-      enabled: false,
-      isManaged: false,
-      userVisibleTypes: 'TXT',
-      userVisibleTypesLabel: 'Supported type: TXT',
-      learnMoreUrl: {url: 'https://google.com/'},
-    },
-    installSource: InstallSource.kUnknown,
-  };
-
-  const permissionTypes = [
-    PermissionType.kLocation,
-    PermissionType.kNotifications,
-    PermissionType.kMicrophone,
-    PermissionType.kCamera,
-  ];
-
-  for (const permissionType of permissionTypes) {
-    const permissionValue = TriState.kAsk;
-    const isManaged = false;
-    app.permissions[permissionType] =
-        createTriStatePermission(permissionType, permissionValue, isManaged);
-  }
-  return app;
-}
diff --git a/chrome/test/data/webui/cr_components/app_management/permission_item_test.ts b/chrome/test/data/webui/cr_components/app_management/permission_item_test.ts
deleted file mode 100644
index 40a002da..0000000
--- a/chrome/test/data/webui/cr_components/app_management/permission_item_test.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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.
-
-/** @fileoverview Test suite for app-manageemnt-permission-item. */
-import 'chrome://resources/cr_components/app_management/permission_item.js';
-
-import {BrowserProxy} from 'chrome://resources/cr_components/app_management/browser_proxy.js';
-import {AppManagementUserAction} from 'chrome://resources/cr_components/app_management/constants.js';
-import {TriState} from 'chrome://resources/cr_components/app_management/permission_constants.js';
-import {AppManagementPermissionItemElement} from 'chrome://resources/cr_components/app_management/permission_item.js';
-import {getPermissionValueBool} from 'chrome://resources/cr_components/app_management/util.js';
-import {assertEquals, assertFalse} from 'chrome://webui-test/chai_assert.js';
-import {waitAfterNextRender} from 'chrome://webui-test/test_util.js';
-
-import {createTestApp, TestAppManagementBrowserProxy} from './app_management_test_support.js';
-
-suite('AppManagementPermissionItemTest', function() {
-  let permissionItem: AppManagementPermissionItemElement;
-  let testProxy: TestAppManagementBrowserProxy;
-
-  setup(async function() {
-    document.body.innerHTML = '';
-    const app = createTestApp();
-    testProxy = new TestAppManagementBrowserProxy();
-    BrowserProxy.setInstance(testProxy);
-
-    permissionItem = document.createElement('app-management-permission-item');
-    permissionItem.app = app;
-    permissionItem.permissionType = 'kLocation';
-    document.body.appendChild(permissionItem);
-    await waitAfterNextRender(permissionItem);
-  });
-
-  test('Toggle permission', async function() {
-    assertFalse(getPermissionValueBool(
-        permissionItem.app, permissionItem.permissionType));
-
-    permissionItem.click();
-    const data = await testProxy.handler.whenCalled('setPermission');
-    assertEquals(data[1].value.tristateValue, TriState.kAllow);
-
-    const metricData = await testProxy.whenCalled('recordEnumerationValue');
-    assertEquals(metricData[1], AppManagementUserAction.LocationTurnedOn);
-  });
-});
diff --git a/chrome/test/data/webui/cr_components/chromeos/network/network_config_test.js b/chrome/test/data/webui/cr_components/chromeos/network/network_config_test.js
index 3da34878..3a2b1adf 100644
--- a/chrome/test/data/webui/cr_components/chromeos/network/network_config_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/network/network_config_test.js
@@ -29,6 +29,7 @@
   const kTestVpnHost = 'test-vpn-host';
   const kTestUsername = 'test-username';
   const kTestPassword = 'test-password';
+  const kTestPsk = 'test-psk';
 
   suiteSetup(function() {
     mojoApi_ = new FakeNetworkConfig();
@@ -306,7 +307,7 @@
       setMandatoryFields();
       const configProperties = networkConfig.get('configProperties_');
       assertFalse(networkConfig.vpnIsConfigured_());
-      configProperties.typeConfig.vpn.ipSec.psk = 'test-psk';
+      configProperties.typeConfig.vpn.ipSec.psk = kTestPsk;
       assertTrue(networkConfig.vpnIsConfigured_());
 
       let props = networkConfig.getPropertiesToSet_();
@@ -317,7 +318,7 @@
       assertEquals('PSK', props.typeConfig.vpn.ipSec.authenticationType);
       assertEquals(2, props.typeConfig.vpn.ipSec.ikeVersion);
       assertFalse(props.typeConfig.vpn.ipSec.saveCredentials);
-      assertEquals('test-psk', props.typeConfig.vpn.ipSec.psk);
+      assertEquals(kTestPsk, props.typeConfig.vpn.ipSec.psk);
       assertEquals('', props.typeConfig.vpn.ipSec.localIdentity);
       assertEquals('', props.typeConfig.vpn.ipSec.remoteIdentity);
 
@@ -557,6 +558,7 @@
       configProperties.name = kTestVpnName;
       configProperties.typeConfig.vpn.host = kTestVpnHost;
       configProperties.typeConfig.vpn.l2tp.username = kTestUsername;
+      configProperties.typeConfig.vpn.l2tp.password = kTestPassword;
     }
 
     test('Switch Authentication Type', function() {
@@ -652,7 +654,82 @@
       });
     });
 
-    test('Certs', function() {
+    // Checks if the values of vpnIsConfigured_() and getPropertiesToSet_() are
+    // correct when the authentication type is PSK.
+    test('PSK', function() {
+      initNetworkConfig();
+      networkConfig.set('vpnType_', 'L2TP_IPsec');
+      Polymer.dom.flush();
+
+      setMandatoryFields();
+      const configProperties = networkConfig.get('configProperties_');
+      assertFalse(networkConfig.vpnIsConfigured_());
+      configProperties.typeConfig.vpn.ipSec.psk = kTestPsk;
+      assertTrue(networkConfig.vpnIsConfigured_());
+
+      let props = networkConfig.getPropertiesToSet_();
+      const mojom = chromeos.networkConfig.mojom;
+      assertEquals(kTestVpnName, props.name);
+      assertEquals(kTestVpnHost, props.typeConfig.vpn.host);
+      assertEquals(mojom.VpnType.kL2TPIPsec, props.typeConfig.vpn.type.value);
+      assertEquals('PSK', props.typeConfig.vpn.ipSec.authenticationType);
+      assertEquals(1, props.typeConfig.vpn.ipSec.ikeVersion);
+      assertFalse(props.typeConfig.vpn.ipSec.saveCredentials);
+      assertEquals(kTestPsk, props.typeConfig.vpn.ipSec.psk);
+      assertEquals(kTestUsername, props.typeConfig.vpn.l2tp.username);
+      assertEquals(kTestPassword, props.typeConfig.vpn.l2tp.password);
+
+      networkConfig.set('vpnSaveCredentials_', true);
+      props = networkConfig.getPropertiesToSet_();
+      assertTrue(props.typeConfig.vpn.ipSec.saveCredentials);
+      assertTrue(props.typeConfig.vpn.l2tp.saveCredentials);
+    });
+
+    // Checks if values are read correctly for an existing service of PSK
+    // authentication.
+    test('Existing PSK', function() {
+      const mojom = chromeos.networkConfig.mojom;
+      const l2tp = OncMojo.getDefaultManagedProperties(
+          mojom.NetworkType.kVPN, 'someguid', kTestVpnName);
+      l2tp.typeProperties.vpn.type = mojom.VpnType.kL2TPIPsec;
+      l2tp.typeProperties.vpn.host = {activeValue: kTestVpnHost};
+      l2tp.typeProperties.vpn.ipSec = {
+        authenticationType: {activeValue: 'PSK'},
+        ikeVersion: {activeValue: 1},
+        saveCredentials: {activeValue: true},
+      };
+      l2tp.typeProperties.vpn.l2tp = {
+        username: {activeValue: kTestUsername},
+        saveCredentials: {activeValue: true},
+      };
+      setNetworkConfig(l2tp);
+      initNetworkConfig();
+
+      return flushAsync().then(() => {
+        assertEquals('L2TP_IPsec', networkConfig.get('vpnType_'));
+        assertEquals('PSK', networkConfig.get('ipsecAuthType_'));
+
+        // Populate the properties again. The values should be the same to what
+        // are set above.
+        const props = networkConfig.getPropertiesToSet_();
+        assertEquals('someguid', props.guid);
+        assertEquals(kTestVpnName, props.name);
+        assertEquals(kTestVpnHost, props.typeConfig.vpn.host);
+        assertEquals(mojom.VpnType.kL2TPIPsec, props.typeConfig.vpn.type.value);
+        assertEquals('PSK', props.typeConfig.vpn.ipSec.authenticationType);
+        assertEquals(1, props.typeConfig.vpn.ipSec.ikeVersion);
+        assertEquals(undefined, props.typeConfig.vpn.ipSec.eap);
+        assertEquals(undefined, props.typeConfig.vpn.ipSec.localIdentity);
+        assertEquals(undefined, props.typeConfig.vpn.ipSec.remoteIdentity);
+        assertEquals(kTestUsername, props.typeConfig.vpn.l2tp.username);
+        assertTrue(props.typeConfig.vpn.ipSec.saveCredentials);
+        assertTrue(props.typeConfig.vpn.l2tp.saveCredentials);
+      });
+    });
+
+    // Checks if the values of vpnIsConfigured_() and getPropertiesToSet_() are
+    // correct when the authentication type is user certificate.
+    test('Cert', function() {
       initNetworkConfigWithCerts(
           /* hasServerCa= */ true, /* hasUserCert= */ true);
       networkConfig.set('vpnType_', 'L2TP_IPsec');
@@ -666,9 +743,79 @@
           // Set all other mandatory fields. vpnIsConfigured_() should be true.
           setMandatoryFields();
           assertTrue(networkConfig.vpnIsConfigured_());
+
+          const props = networkConfig.getPropertiesToSet_();
+          const mojom = chromeos.networkConfig.mojom;
+          assertEquals(kTestVpnName, props.name);
+          assertEquals(kTestVpnHost, props.typeConfig.vpn.host);
+          assertEquals(
+              mojom.VpnType.kL2TPIPsec, props.typeConfig.vpn.type.value);
+          assertEquals('Cert', props.typeConfig.vpn.ipSec.authenticationType);
+          assertEquals(1, props.typeConfig.vpn.ipSec.ikeVersion);
+          assertEquals(1, props.typeConfig.vpn.ipSec.serverCaPems.length);
+          assertEquals(kCaPem, props.typeConfig.vpn.ipSec.serverCaPems[0]);
+          assertEquals('PKCS11Id', props.typeConfig.vpn.ipSec.clientCertType);
+          assertEquals(
+              kUserCertId, props.typeConfig.vpn.ipSec.clientCertPkcs11Id);
+          assertEquals(kTestUsername, props.typeConfig.vpn.l2tp.username);
+          assertEquals(kTestPassword, props.typeConfig.vpn.l2tp.password);
+          assertFalse(props.typeConfig.vpn.ipSec.saveCredentials);
+          assertFalse(props.typeConfig.vpn.l2tp.saveCredentials);
         });
       });
     });
+
+    // Checks if values are read correctly for an existing service of
+    // certificate authentication.
+    test('Existing Cert', function() {
+      const mojom = chromeos.networkConfig.mojom;
+      const l2tp = OncMojo.getDefaultManagedProperties(
+          mojom.NetworkType.kVPN, 'someguid', kTestVpnName);
+      l2tp.typeProperties.vpn.type = mojom.VpnType.kL2TPIPsec;
+      l2tp.typeProperties.vpn.host = {activeValue: kTestVpnHost};
+      l2tp.typeProperties.vpn.ipSec = {
+        authenticationType: {activeValue: 'Cert'},
+        clientCertType: {activeValue: 'PKCS11Id'},
+        clientCertPkcs11Id: {activeValue: kUserCertId},
+        ikeVersion: {activeValue: 1},
+        saveCredentials: {activeValue: true},
+        serverCaPems: {activeValue: [kCaPem]},
+      };
+      l2tp.typeProperties.vpn.l2tp = {
+        username: {activeValue: kTestUsername},
+        saveCredentials: {activeValue: true},
+      };
+      setNetworkConfig(l2tp);
+      initNetworkConfigWithCerts(
+          /* hasServerCa= */ true, /* hasUserCert= */ true);
+      return mojoApi_.whenCalled('getNetworkCertificates').then(() => {
+        assertEquals('L2TP_IPsec', networkConfig.get('vpnType_'));
+        assertEquals('Cert', networkConfig.get('ipsecAuthType_'));
+        assertEquals(kCaHash, networkConfig.selectedServerCaHash_);
+        assertEquals(kUserHash1, networkConfig.selectedUserCertHash_);
+
+        // Populate the properties again. The values should be the same to what
+        // are set above.
+        const props = networkConfig.getPropertiesToSet_();
+        assertEquals('someguid', props.guid);
+        assertEquals(kTestVpnName, props.name);
+        assertEquals(kTestVpnHost, props.typeConfig.vpn.host);
+        assertEquals(mojom.VpnType.kL2TPIPsec, props.typeConfig.vpn.type.value);
+        assertEquals('Cert', props.typeConfig.vpn.ipSec.authenticationType);
+        assertEquals(1, props.typeConfig.vpn.ipSec.ikeVersion);
+        assertEquals(1, props.typeConfig.vpn.ipSec.serverCaPems.length);
+        assertEquals(kCaPem, props.typeConfig.vpn.ipSec.serverCaPems[0]);
+        assertEquals('PKCS11Id', props.typeConfig.vpn.ipSec.clientCertType);
+        assertEquals(
+            kUserCertId, props.typeConfig.vpn.ipSec.clientCertPkcs11Id);
+        assertEquals(undefined, props.typeConfig.vpn.ipSec.eap);
+        assertEquals(undefined, props.typeConfig.vpn.ipSec.localIdentity);
+        assertEquals(undefined, props.typeConfig.vpn.ipSec.remoteIdentity);
+        assertEquals(kTestUsername, props.typeConfig.vpn.l2tp.username);
+        assertTrue(props.typeConfig.vpn.ipSec.saveCredentials);
+        assertTrue(props.typeConfig.vpn.l2tp.saveCredentials);
+      });
+    });
   });
 
   suite('OpenVPN', function() {
diff --git a/chrome/test/data/webui/cr_components/cr_components_browsertest.js b/chrome/test/data/webui/cr_components/cr_components_browsertest.js
index bab091c..e4840232 100644
--- a/chrome/test/data/webui/cr_components/cr_components_browsertest.js
+++ b/chrome/test/data/webui/cr_components/cr_components_browsertest.js
@@ -113,15 +113,3 @@
 TEST_F('CrComponentsLocalizedLinkTest', 'All', function() {
   mocha.run();
 });
-
-var CrComponentsAppManagementPermissionItemTest =
-    class extends CrComponentsBrowserTest {
-  /** @override */
-  get browsePreload() {
-    return 'chrome://test/test_loader.html?module=cr_components/app_management/permission_item_test.js&host=webui-test';
-  }
-};
-
-TEST_F('CrComponentsAppManagementPermissionItemTest', 'All', function() {
-  mocha.run();
-});
diff --git a/chrome/test/data/webui/media_internals/media_internals_ui_browsertest.js b/chrome/test/data/webui/media_internals/media_internals_ui_browsertest.js
index f8b000c..799081f 100644
--- a/chrome/test/data/webui/media_internals/media_internals_ui_browsertest.js
+++ b/chrome/test/data/webui/media_internals/media_internals_ui_browsertest.js
@@ -24,8 +24,7 @@
   ],
 };
 
-// Flaky on multiple bots: crbug.com/1305510
-TEST_F('MediaInternalsUIBrowserTest', 'DISABLED_Integration', function() {
+TEST_F('MediaInternalsUIBrowserTest', 'Integration', function() {
   suite('integration_tests', function() {
     // The renderer and player ids are completely arbitrarily.
     var TEST_RENDERER = 12;
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index 89aae80d..fff95334 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -47,6 +47,7 @@
     "app_management/dom_switch_test.js",
     "app_management/main_view_test.js",
     "app_management/pwa_detail_view_test.js",
+    "app_management/permission_item_test.js",
     "app_management/pin_to_shelf_item_test.js",
     "app_management/plugin_vm_detail_view_test.js",
     "app_management/managed_apps_test.js",
diff --git a/chrome/test/data/webui/settings/chromeos/app_management/permission_item_test.js b/chrome/test/data/webui/settings/chromeos/app_management/permission_item_test.js
new file mode 100644
index 0000000..008659ae
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/app_management/permission_item_test.js
@@ -0,0 +1,57 @@
+// 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.
+
+// clang-format off
+// #import 'chrome://os-settings/chromeos/os_settings.js';
+
+// #import {AppManagementStore, FakePageHandler, PermissionType, updateSelectedAppId, getPermissionValueBool} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {setupFakeHandler, replaceStore, replaceBody} from './test_util.m.js';
+// #import {flushTasks} from 'chrome://test/test_util.js';
+// clang-format on
+
+'use strict';
+
+suite('<app-management-permission-item>', () => {
+  let permissionItem;
+  let fakeHandler;
+
+  setup(async () => {
+    fakeHandler = setupFakeHandler();
+    replaceStore();
+
+    const arcOptions = {
+      type: appManagement.mojom.AppType.kArc,
+      permissions: app_management.FakePageHandler.createArcPermissions([
+        PermissionType.kCamera,
+        PermissionType.kLocation,
+        PermissionType.kNotifications,
+        PermissionType.kContacts,
+        PermissionType.kStorage,
+      ])
+    };
+
+    // Add an arc app, and pass it to permissionItem.
+    const app = await fakeHandler.addApp(null, arcOptions);
+
+    permissionItem = document.createElement('app-management-permission-item');
+    permissionItem.app = app;
+  });
+
+  test('Toggle permission', async () => {
+    permissionItem.permissionType = 'kLocation';
+
+    replaceBody(permissionItem);
+    await fakeHandler.flushPipesForTesting();
+    assertTrue(getPermissionValueBool(
+        permissionItem.app, permissionItem.permissionType));
+
+    permissionItem.click();
+    await test_util.flushTasks();
+    await fakeHandler.flushPipesForTesting();
+    // Store gets updated permission.
+    const storeData = app_management.AppManagementStore.getInstance().data;
+    assertFalse(getPermissionValueBool(
+        storeData.apps[permissionItem.app.id], permissionItem.permissionType));
+  });
+});
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
index 3993fb9..3f7c6be 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
@@ -318,6 +318,7 @@
  ['AppManagementMainView', 'main_view_test.m.js'],
  ['AppManagementManagedApp', 'managed_apps_test.m.js'],
  ['AppManagementPage', 'app_management_page_tests.m.js'],
+ ['AppManagementPermissionItem', 'permission_item_test.m.js'],
  ['AppManagementPinToShelfItem', 'pin_to_shelf_item_test.m.js'],
  ['AppManagementPluginVmDetailView', 'plugin_vm_detail_view_test.m.js'],
  ['AppManagementPwaDetailView', 'pwa_detail_view_test.m.js'],
diff --git a/chromeos/ui/base/file_icon_util.cc b/chromeos/ui/base/file_icon_util.cc
index be3ab96d..ec8840c22 100644
--- a/chromeos/ui/base/file_icon_util.cc
+++ b/chromeos/ui/base/file_icon_util.cc
@@ -195,6 +195,9 @@
           // Archive
           {".ZIP", IconType::kArchive},
           {".RAR", IconType::kArchive},
+          {".ISO", IconType::kArchive},
+          {".7Z", IconType::kArchive},
+          {".CRX", IconType::kArchive},
           {".TAR", IconType::kArchive},
           {".TAR.BZ2", IconType::kArchive},
           {".TBZ2", IconType::kArchive},
diff --git a/components/autofill/core/browser/autofill_field.h b/components/autofill/core/browser/autofill_field.h
index 68e828f..98da439 100644
--- a/components/autofill/core/browser/autofill_field.h
+++ b/components/autofill/core/browser/autofill_field.h
@@ -210,7 +210,7 @@
   // Getter and Setter methods for
   // |value_not_autofilled_over_existing_value_hash_|.
   void set_value_not_autofilled_over_existing_value_hash(
-      size_t value_not_autofilled_over_existing_value_hash) {
+      absl::optional<size_t> value_not_autofilled_over_existing_value_hash) {
     value_not_autofilled_over_existing_value_hash_ =
         value_not_autofilled_over_existing_value_hash;
   }
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index 0719de8..54e33cf 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -1787,25 +1787,35 @@
 
     // Do not override prefilled text/input field values. Selection fields are
     // excluded from this check because they may have a non-empty value.
+    // If the initiating element had a prefilled value but the autofill
+    // suggestion is present that includes the currently filled value in the
+    // field as a substring, Autofill would override the filled value in that
+    // case.
     if (base::FeatureList::IsEnabled(
-            features::kAutofillPreventOverridingPrefilledValues) &&
-        form.fields[i].form_control_type != "select-one" &&
-        !form.fields[i].value.empty()) {
-      buffer << Tr{} << field_number << "Skipped: value is prefilled";
-      std::string unused_failure_to_fill;
-      const std::u16string kEmptyCvc{};
-      const std::u16string fill_value = field_filler_.GetValueForFilling(
-          *cached_field, profile_or_credit_card, &result.fields[i],
-          optional_cvc ? *optional_cvc : kEmptyCvc, action,
-          &unused_failure_to_fill);
-      if (action == mojom::RendererFormDataAction::kFill &&
-          !fill_value.empty() && fill_value != form.fields[i].value) {
-        // Save the value that was supposed to be autofilled for this field.
-        form_structure->field(i)
-            ->set_value_not_autofilled_over_existing_value_hash(
-                base::FastHash(base::UTF16ToUTF8(fill_value)));
+            features::kAutofillPreventOverridingPrefilledValues)) {
+      if (form.fields[i].form_control_type != "select-one" &&
+          !form.fields[i].value.empty() &&
+          !FormFieldData::DeepEqual(form.fields[i], field)) {
+        buffer << Tr{} << field_number << "Skipped: value is prefilled";
+        std::string unused_failure_to_fill;
+        const std::u16string kEmptyCvc{};
+        const std::u16string fill_value = field_filler_.GetValueForFilling(
+            *cached_field, profile_or_credit_card, &result.fields[i],
+            optional_cvc ? *optional_cvc : kEmptyCvc, action,
+            &unused_failure_to_fill);
+        if (action == mojom::RendererFormDataAction::kFill &&
+            !fill_value.empty() && fill_value != form.fields[i].value) {
+          // Save the value that was supposed to be autofilled for this field.
+          form_structure->field(i)
+              ->set_value_not_autofilled_over_existing_value_hash(
+                  base::FastHash(base::UTF16ToUTF8(fill_value)));
+        }
+        continue;
       }
-      continue;
+
+      // Clear out the value in case the autofill happens for the field.
+      form_structure->field(i)
+          ->set_value_not_autofilled_over_existing_value_hash(absl::nullopt);
     }
 
     // Do not fill fields that have been edited by the user, except if the field
diff --git a/components/autofill/core/browser/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
index a154050..ccff80df 100644
--- a/components/autofill/core/browser/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
@@ -9363,7 +9363,7 @@
   form.url = GURL("https://myform.com/form.html");
   form.action = GURL("about:blank");
   FormFieldData field;
-  test::CreateTestFormField("Name", "name", "Test Name", "text", &field);
+  test::CreateTestFormField("Name", "name", "", "text", &field);
   form.fields.push_back(field);
   test::CreateTestFormField("City", "city", "Test City", "text", &field);
   form.fields.push_back(field);
@@ -9386,7 +9386,7 @@
   FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0],
                                      MakeFrontendID(std::string(), guid),
                                      &response_page_id, &response_data);
-  EXPECT_EQ(response_data.fields[0].value, u"Test Name");
+  EXPECT_EQ(response_data.fields[0].value, u"Elvis Aaron Presley");
   EXPECT_EQ(response_data.fields[1].value, u"Test City");
   EXPECT_EQ(response_data.fields[2].value, u"Tennessee");
   EXPECT_EQ(response_data.fields[3].value, u"Test Country");
@@ -9395,8 +9395,8 @@
   {
     FormStructure* form_structure;
     AutofillField* autofill_field;
-    std::vector<std::string> expected_values = {
-        "Elvis Aaron Presley", "Memphis", "", "United States", ""};
+    std::vector<std::string> expected_values = {"", "Memphis", "",
+                                                "United States", ""};
     bool found = browser_autofill_manager_->GetCachedFormAndField(
         form, form.fields[0], &form_structure, &autofill_field);
     ASSERT_TRUE(found);
@@ -9412,6 +9412,8 @@
                   base::FastHash(expected_values[i]));
       }
     }
+
+    EXPECT_TRUE(form_structure->field(0)->is_autofilled);  // No prefilled value
     EXPECT_TRUE(form_structure->field(2)->is_autofilled);  // Selection field.
 
     // Prefilled value is same as the value to be autofilled so
@@ -9435,6 +9437,51 @@
   EXPECT_EQ(response_data.fields[4].value, u"12345678901");
 }
 
+// Tests that the Autofill does override the prefilled field value since the
+// field is the initiating field for the Autofill and has a prefilled value
+// which is a substring of the autofillable value.
+TEST_F(BrowserAutofillManagerTest, AutofillOverridePrefilledValue) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeature(
+      autofill::features::kAutofillPreventOverridingPrefilledValues);
+  // Set up our form data.
+  FormData form;
+  form.name = u"MyForm";
+  form.url = GURL("https://myform.com/form.html");
+  form.action = GURL("about:blank");
+  FormFieldData field;
+  test::CreateTestFormField("Name", "name", "Test Name", "text", &field);
+  form.fields.push_back(field);
+  test::CreateTestFormField("City", "city", "Test City", "text", &field);
+  form.fields.push_back(field);
+  test::CreateTestSelectField("State", "state", "California",
+                              {"Washington", "Tennessee", "California"},
+                              {"DC", "TN", "CA"}, 3, &field);
+  form.fields.push_back(field);
+  test::CreateTestFormField("Country", "country", "Test Country", "text",
+                            &field);
+  form.fields.push_back(field);
+  std::vector<FormData> forms(1, form);
+  FormsSeen(forms);
+
+  // "Elv" is a substring of "Elvis Aaron Presley".
+  form.fields[0].value = u"Elv";
+  // Simulate editing a field.
+  browser_autofill_manager_->OnTextFieldDidChange(
+      form, form.fields.front(), gfx::RectF(), AutofillTickClock::NowTicks());
+
+  const char guid[] = "00000000-0000-0000-0000-000000000001";
+  int response_page_id = 0;
+  FormData response_data;
+  FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0],
+                                     MakeFrontendID(std::string(), guid),
+                                     &response_page_id, &response_data);
+  EXPECT_EQ(response_data.fields[0].value, u"Elvis Aaron Presley");
+  EXPECT_EQ(response_data.fields[1].value, u"Test City");
+  EXPECT_EQ(response_data.fields[2].value, u"Tennessee");
+  EXPECT_EQ(response_data.fields[3].value, u"Test Country");
+}
+
 // Desktop only tests.
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 class BrowserAutofillManagerTestForVirtualCardOption
diff --git a/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc b/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc
index 12d4f76b..c393815 100644
--- a/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc
+++ b/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc
@@ -327,9 +327,10 @@
   if (time_delta_since_last_write < kMinDelayBetweenWrites) {
     // If an event was just written, delay writing the event to disk in order to
     // limit overhead.
-    write_timer_.Start(FROM_HERE,
-                       kMinDelayBetweenWrites - time_delta_since_last_write,
-                       this, &BreadcrumbPersistentStorageManager::WriteEvents);
+    write_timer_.Start(
+        FROM_HERE, kMinDelayBetweenWrites - time_delta_since_last_write,
+        base::BindOnce(&BreadcrumbPersistentStorageManager::WriteEvents,
+                       weak_ptr_factory_.GetWeakPtr()));
   } else {
     // If the event does not fit within |kPersistedFilesizeInBytes|, rewrite the
     // file to trim old events.
diff --git a/components/feed/core/v2/config.cc b/components/feed/core/v2/config.cc
index e9c5ec4b..6c7ab94 100644
--- a/components/feed/core/v2/config.cc
+++ b/components/feed/core/v2/config.cc
@@ -4,6 +4,7 @@
 
 #include "components/feed/core/v2/config.h"
 
+#include "base/command_line.h"
 #include "base/containers/cxx20_erase.h"
 #include "base/containers/flat_set.h"
 #include "base/metrics/field_trial_params.h"
@@ -29,129 +30,133 @@
 }
 
 // Override any parameters that may be provided by Finch.
-void OverrideWithFinch(Config* config) {
-  config->max_feed_query_requests_per_day =
+void OverrideWithFinch(Config& config) {
+  config.max_feed_query_requests_per_day =
       base::GetFieldTrialParamByFeatureAsInt(
           kInterestFeedV2, "max_feed_query_requests_per_day",
-          config->max_feed_query_requests_per_day);
+          config.max_feed_query_requests_per_day);
 
-  config->max_next_page_requests_per_day =
+  config.max_next_page_requests_per_day =
       base::GetFieldTrialParamByFeatureAsInt(
           kInterestFeedV2, "max_next_page_requests_per_day",
-          config->max_next_page_requests_per_day);
+          config.max_next_page_requests_per_day);
 
-  config->max_action_upload_requests_per_day =
+  config.max_action_upload_requests_per_day =
       base::GetFieldTrialParamByFeatureAsInt(
           kInterestFeedV2, "max_action_upload_requests_per_day",
-          config->max_action_upload_requests_per_day);
+          config.max_action_upload_requests_per_day);
 
-  config->max_list_recommended_web_feeds_requests_per_day =
+  config.max_list_recommended_web_feeds_requests_per_day =
       base::GetFieldTrialParamByFeatureAsInt(
           kWebFeed, "max_list_recommended_web_feeds_requests_per_day",
-          config->max_list_recommended_web_feeds_requests_per_day);
+          config.max_list_recommended_web_feeds_requests_per_day);
 
-  config->max_list_web_feeds_requests_per_day =
+  config.max_list_web_feeds_requests_per_day =
       base::GetFieldTrialParamByFeatureAsInt(
           kWebFeed, "max_list_web_feeds_requests_per_day",
-          config->max_list_web_feeds_requests_per_day);
+          config.max_list_web_feeds_requests_per_day);
 
-  config->stale_content_threshold =
+  config.stale_content_threshold =
       base::Seconds(base::GetFieldTrialParamByFeatureAsDouble(
           kInterestFeedV2, "stale_content_threshold_seconds",
-          config->stale_content_threshold.InSecondsF()));
+          config.stale_content_threshold.InSecondsF()));
 
-  config->content_expiration_threshold =
+  config.content_expiration_threshold =
       base::Seconds(base::GetFieldTrialParamByFeatureAsDouble(
           kInterestFeedV2, "content_expiration_threshold_seconds",
-          config->content_expiration_threshold.InSecondsF()));
+          config.content_expiration_threshold.InSecondsF()));
 
-  config->background_refresh_window_length =
+  config.background_refresh_window_length =
       base::Seconds(base::GetFieldTrialParamByFeatureAsDouble(
           kInterestFeedV2, "background_refresh_window_length_seconds",
-          config->background_refresh_window_length.InSecondsF()));
+          config.background_refresh_window_length.InSecondsF()));
 
-  config->default_background_refresh_interval =
+  config.default_background_refresh_interval =
       base::Seconds(base::GetFieldTrialParamByFeatureAsDouble(
           kInterestFeedV2, "default_background_refresh_interval_seconds",
-          config->default_background_refresh_interval.InSecondsF()));
+          config.default_background_refresh_interval.InSecondsF()));
 
-  config->max_action_upload_attempts = base::GetFieldTrialParamByFeatureAsInt(
+  config.max_action_upload_attempts = base::GetFieldTrialParamByFeatureAsInt(
       kInterestFeedV2, "max_action_upload_attempts",
-      config->max_action_upload_attempts);
+      config.max_action_upload_attempts);
 
-  config->max_action_age =
+  config.max_action_age =
       base::Seconds(base::GetFieldTrialParamByFeatureAsDouble(
           kInterestFeedV2, "max_action_age_seconds",
-          config->max_action_age.InSecondsF()));
+          config.max_action_age.InSecondsF()));
 
-  config->max_action_upload_bytes = base::GetFieldTrialParamByFeatureAsInt(
+  config.max_action_upload_bytes = base::GetFieldTrialParamByFeatureAsInt(
       kInterestFeedV2, "max_action_upload_bytes",
-      config->max_action_upload_bytes);
+      config.max_action_upload_bytes);
 
-  config->model_unload_timeout =
+  config.model_unload_timeout =
       base::Seconds(base::GetFieldTrialParamByFeatureAsDouble(
           kInterestFeedV2, "model_unload_timeout_seconds",
-          config->model_unload_timeout.InSecondsF()));
+          config.model_unload_timeout.InSecondsF()));
 
-  config->load_more_trigger_lookahead = base::GetFieldTrialParamByFeatureAsInt(
+  config.load_more_trigger_lookahead = base::GetFieldTrialParamByFeatureAsInt(
       kInterestFeedV2, "load_more_trigger_lookahead",
-      config->load_more_trigger_lookahead);
+      config.load_more_trigger_lookahead);
 
-  config->load_more_trigger_scroll_distance_dp =
+  config.load_more_trigger_scroll_distance_dp =
       base::GetFieldTrialParamByFeatureAsInt(
           kInterestFeedV2Scrolling, "load_more_trigger_scroll_distance_dp",
-          config->load_more_trigger_scroll_distance_dp);
+          config.load_more_trigger_scroll_distance_dp);
 
-  config->upload_actions_on_enter_background =
+  config.upload_actions_on_enter_background =
       base::GetFieldTrialParamByFeatureAsBool(
           kInterestFeedV2, "upload_actions_on_enter_background",
-          config->upload_actions_on_enter_background);
+          config.upload_actions_on_enter_background);
 
-  config->send_signed_out_session_logs =
-      base::GetFieldTrialParamByFeatureAsBool(
-          kInterestFeedV2, "send_signed_out_session_logs",
-          config->send_signed_out_session_logs);
+  config.send_signed_out_session_logs = base::GetFieldTrialParamByFeatureAsBool(
+      kInterestFeedV2, "send_signed_out_session_logs",
+      config.send_signed_out_session_logs);
 
-  config->session_id_max_age =
-      base::Days(base::GetFieldTrialParamByFeatureAsInt(
-          kInterestFeedV2, "session_id_max_age_days",
-          config->session_id_max_age.InDays()));
+  config.session_id_max_age = base::Days(base::GetFieldTrialParamByFeatureAsInt(
+      kInterestFeedV2, "session_id_max_age_days",
+      config.session_id_max_age.InDays()));
 
-  config->max_prefetch_image_requests_per_refresh =
+  config.max_prefetch_image_requests_per_refresh =
       base::GetFieldTrialParamByFeatureAsInt(
           kInterestFeedV2, "max_prefetch_image_requests_per_refresh",
-          config->max_prefetch_image_requests_per_refresh);
+          config.max_prefetch_image_requests_per_refresh);
 
-  config->webfeed_accelerator_recent_visit_history_days =
+  config.webfeed_accelerator_recent_visit_history_days =
       base::GetFieldTrialParamByFeatureAsInt(
           kWebFeed, "webfeed_accelerator_recent_visit_history_days",
-          config->webfeed_accelerator_recent_visit_history_days);
+          config.webfeed_accelerator_recent_visit_history_days);
 
-  config->recommended_feeds_staleness_threshold =
+  config.recommended_feeds_staleness_threshold =
       base::Days(base::GetFieldTrialParamByFeatureAsInt(
           kWebFeed, "recommended_feeds_staleness_threshold_days",
-          config->recommended_feeds_staleness_threshold.InDays()));
+          config.recommended_feeds_staleness_threshold.InDays()));
 
-  config->subscribed_feeds_staleness_threshold =
+  config.subscribed_feeds_staleness_threshold =
       base::Days(base::GetFieldTrialParamByFeatureAsInt(
           kWebFeed, "subscribed_feeds_staleness_threshold_days",
-          config->subscribed_feeds_staleness_threshold.InDays()));
+          config.subscribed_feeds_staleness_threshold.InDays()));
 
-  config->web_feed_stale_content_threshold =
+  config.web_feed_stale_content_threshold =
       base::Seconds(base::GetFieldTrialParamByFeatureAsDouble(
           kWebFeed, "web_feed_stale_content_threshold_seconds",
-          config->web_feed_stale_content_threshold.InSecondsF()));
+          config.web_feed_stale_content_threshold.InSecondsF()));
 
   // Erase any capabilities with "enable_CAPABILITY = false" set.
-  base::EraseIf(config->experimental_capabilities, CapabilityDisabled);
+  base::EraseIf(config.experimental_capabilities, CapabilityDisabled);
 
-  config->max_mid_entities_per_url_entry =
+  config.max_mid_entities_per_url_entry =
       base::GetFieldTrialParamByFeatureAsInt(
           kPersonalizeFeedUnsignedUsers, "max_mid_entities_per_url_entry",
-          config->max_mid_entities_per_url_entry);
-  config->max_url_entries_in_cache = GetFieldTrialParamByFeatureAsInt(
+          config.max_mid_entities_per_url_entry);
+  config.max_url_entries_in_cache = GetFieldTrialParamByFeatureAsInt(
       kPersonalizeFeedUnsignedUsers, "max_url_entries_in_cache",
-      config->max_url_entries_in_cache);
+      config.max_url_entries_in_cache);
+}
+
+void OverrideWithSwitches(Config& config) {
+  config.use_feed_query_requests_for_web_feeds =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(
+          "webfeed-legacy-feedquery");
 }
 
 }  // namespace
@@ -160,7 +165,8 @@
   static Config* s_config = nullptr;
   if (!s_config) {
     s_config = new Config;
-    OverrideWithFinch(s_config);
+    OverrideWithFinch(*s_config);
+    OverrideWithSwitches(*s_config);
   }
   return *s_config;
 }
@@ -177,7 +183,7 @@
 }
 
 void OverrideConfigWithFinchForTesting() {
-  OverrideWithFinch(&const_cast<Config&>(GetFeedConfig()));
+  OverrideWithFinch(const_cast<Config&>(GetFeedConfig()));
 }
 
 Config::Config() = default;
diff --git a/components/feed/core/v2/config.h b/components/feed/core/v2/config.h
index e164d84d..0b942e17 100644
--- a/components/feed/core/v2/config.h
+++ b/components/feed/core/v2/config.h
@@ -97,6 +97,7 @@
 
   // Until we get the new list contents API working, keep using FeedQuery.
   // TODO(crbug/1152592): remove this when new endpoint is tested enough.
+  // Set using snippets-internals, or the --webfeed-legacy-feedquery switch.
   bool use_feed_query_requests_for_web_feeds = false;
 
   // Set of optional capabilities included in requests. See
diff --git a/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc b/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc
index d88b577e3..0fff757 100644
--- a/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc
+++ b/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc
@@ -267,7 +267,13 @@
   EXPECT_EQ(sample_received_, GetParam().expect_stable_sample);
 }
 
-TEST_P(HeapProfilerControllerFeatureTest, CanaryChannel) {
+// TODO(crbug.com/1302007): This test hangs on iPad device.
+#if BUILDFLAG(IS_IOS)
+#define MAYBE_CanaryChannel DISABLED_CanaryChannel
+#else
+#define MAYBE_CanaryChannel CanaryChannel
+#endif
+TEST_P(HeapProfilerControllerFeatureTest, MAYBE_CanaryChannel) {
   StartHeapProfiling(
       version_info::Channel::CANARY,
       base::BindRepeating(&HeapProfilerControllerTest::RecordSampleReceived,
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 706692fa..3f339b8 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -63,14 +63,8 @@
 
 // Enables the syncing of the Optimization Hints component, which provides
 // hints for what optimizations can be applied on a page load.
-const base::Feature kOptimizationHints {
-  "OptimizationHints",
-#if BUILDFLAG(IS_IOS)
-      base::FEATURE_DISABLED_BY_DEFAULT
-#else   // !BUILDFLAG(IS_IOS)
-      base::FEATURE_ENABLED_BY_DEFAULT
-#endif  // BUILDFLAG(IS_IOS)
-};
+const base::Feature kOptimizationHints{"OptimizationHints",
+                                       base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Feature flag that contains a feature param that specifies the field trials
 // that are allowed to be sent up to the Optimization Guide Server.
@@ -83,9 +77,9 @@
 
 const base::Feature kRemoteOptimizationGuideFetchingAnonymousDataConsent {
   "OptimizationHintsFetchingAnonymousDataConsent",
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
       base::FEATURE_ENABLED_BY_DEFAULT
-#else   // !BUILDFLAG(IS_ANDROID)
+#else   // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
       base::FEATURE_DISABLED_BY_DEFAULT
 #endif  // BUILDFLAG(IS_ANDROID)
 };
diff --git a/components/payments/OWNERS b/components/payments/OWNERS
index 1125b0d..5da21fdf 100644
--- a/components/payments/OWNERS
+++ b/components/payments/OWNERS
@@ -1,3 +1,3 @@
 rouslan@chromium.org
-maxlg@chromium.org
 nburris@chromium.org
+smcgruer@chromium.org
diff --git a/components/permissions/android/permission_prompt_android.cc b/components/permissions/android/permission_prompt_android.cc
index be3ae4f..0486024 100644
--- a/components/permissions/android/permission_prompt_android.cc
+++ b/components/permissions/android/permission_prompt_android.cc
@@ -47,7 +47,6 @@
 
 PermissionPromptAndroid::~PermissionPromptAndroid() {
   if (message_delegate_) {
-    message_delegate_.reset();
     return;
   }
   infobars::InfoBarManager* infobar_manager =
diff --git a/components/permissions/android/permission_prompt_android.h b/components/permissions/android/permission_prompt_android.h
index 20f4b0a..375817a 100644
--- a/components/permissions/android/permission_prompt_android.h
+++ b/components/permissions/android/permission_prompt_android.h
@@ -12,7 +12,6 @@
 #include "base/memory/weak_ptr.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/infobars/core/infobar_manager.h"
-#include "components/messages/android/message_wrapper.h"
 #include "components/permissions/permission_prompt.h"
 #include "components/permissions/permission_uma_util.h"
 #include "components/permissions/permissions_client.h"
diff --git a/components/reporting/storage/storage_queue_unittest.cc b/components/reporting/storage/storage_queue_unittest.cc
index 079a0c2e..35bfe77 100644
--- a/components/reporting/storage/storage_queue_unittest.cc
+++ b/components/reporting/storage/storage_queue_unittest.cc
@@ -992,7 +992,16 @@
   storage_queue_->Flush();
 }
 
-TEST_P(StorageQueueTest, WriteIntoNewStorageQueueReopenWriteMoreAndFlush) {
+// TODO(crbug.com/1302007): This test crashes on iPad device.
+#if BUILDFLAG(IS_IOS)
+#define MAYBE_WriteIntoNewStorageQueueReopenWriteMoreAndFlush \
+  DISABLED_WriteIntoNewStorageQueueReopenWriteMoreAndFlush
+#else
+#define MAYBE_WriteIntoNewStorageQueueReopenWriteMoreAndFlush \
+  WriteIntoNewStorageQueueReopenWriteMoreAndFlush
+#endif
+TEST_P(StorageQueueTest,
+       MAYBE_WriteIntoNewStorageQueueReopenWriteMoreAndFlush) {
   CreateTestStorageQueueOrDie(BuildStorageQueueOptionsOnlyManual());
   WriteStringOrDie(kData[0]);
   WriteStringOrDie(kData[1]);
diff --git a/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc b/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc
index f43f095..705edeb 100644
--- a/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc
+++ b/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc
@@ -20,6 +20,7 @@
     const std::string& segmentation_key,
     SegmentSelectionCallback callback) {
   stats::RecordSegmentSelectionFailure(
+      segmentation_key,
       stats::SegmentationSelectionFailureReason::kPlatformDisabled);
   base::SequencedTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(std::move(callback), SegmentSelectionResult()));
diff --git a/components/segmentation_platform/internal/dummy_segmentation_platform_service_unittest.cc b/components/segmentation_platform/internal/dummy_segmentation_platform_service_unittest.cc
index 645967d..84288ff 100644
--- a/components/segmentation_platform/internal/dummy_segmentation_platform_service_unittest.cc
+++ b/components/segmentation_platform/internal/dummy_segmentation_platform_service_unittest.cc
@@ -42,13 +42,13 @@
   SegmentSelectionResult expected;
   base::RunLoop loop;
   segmentation_platform_service_->GetSelectedSegment(
-      "some_key",
+      "test_key",
       base::BindOnce(
           &DummySegmentationPlatformServiceTest::OnGetSelectedSegment,
           base::Unretained(this), loop.QuitClosure(), expected));
   loop.Run();
   ASSERT_EQ(expected,
-            segmentation_platform_service_->GetCachedSegmentResult("some_key"));
+            segmentation_platform_service_->GetCachedSegmentResult("test_key"));
 }
 
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/execution/model_execution_manager_impl.cc b/components/segmentation_platform/internal/execution/model_execution_manager_impl.cc
index 5bb0b3f..bce51f6 100644
--- a/components/segmentation_platform/internal/execution/model_execution_manager_impl.cc
+++ b/components/segmentation_platform/internal/execution/model_execution_manager_impl.cc
@@ -128,22 +128,30 @@
   auto model_handler_it = model_handlers_.find(segment_id);
   DCHECK(model_handler_it != model_handlers_.end());
 
+  SegmentationModelHandler& handler = *model_handler_it->second;
+
   // Create an ExecutionState that will stay with this request until it has been
   // fully processed.
   auto state = std::make_unique<ExecutionState>();
   state->segment_id = segment_id;
-  state->model_handler = (*model_handler_it).second.get();
+  state->model_handler = &handler;
   state->callback = std::move(callback);
   state->total_execution_start_time = clock_->Now();
 
   ModelExecutionTraceEvent trace_event(
       "ModelExecutionManagerImpl::ExecuteModel", *state);
 
+  if (!handler.ModelAvailable()) {
+    RunModelExecutionCallback(std::move(state), 0,
+                              ModelExecutionStatus::kSkippedModelNotReady);
+    return;
+  }
+
   // It is required to have a valid and well formed segment info.
   if (metadata_utils::ValidateSegmentInfo(segment_info) !=
       metadata_utils::ValidationResult::kValidationSuccess) {
     RunModelExecutionCallback(std::move(state), 0,
-                              ModelExecutionStatus::kInvalidMetadata);
+                              ModelExecutionStatus::kSkippedInvalidMetadata);
     return;
   }
 
@@ -161,7 +169,7 @@
   if (error) {
     // Validation error occurred on model's metadata.
     RunModelExecutionCallback(std::move(state), 0,
-                              ModelExecutionStatus::kInvalidMetadata);
+                              ModelExecutionStatus::kSkippedInvalidMetadata);
     return;
   }
   state->input_tensor.insert(state->input_tensor.end(), input_tensor.begin(),
diff --git a/components/segmentation_platform/internal/execution/model_execution_manager_impl_unittest.cc b/components/segmentation_platform/internal/execution/model_execution_manager_impl_unittest.cc
index ad12c12..e4f8356 100644
--- a/components/segmentation_platform/internal/execution/model_execution_manager_impl_unittest.cc
+++ b/components/segmentation_platform/internal/execution/model_execution_manager_impl_unittest.cc
@@ -220,11 +220,29 @@
   auto segment_id =
       OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB;
   CreateModelExecutionManager({segment_id}, base::DoNothing());
-  ExecuteModel(std::make_pair(0, ModelExecutionStatus::kInvalidMetadata));
+  EXPECT_CALL(FindHandler(segment_id), ModelAvailable())
+      .WillRepeatedly(Return(true));
+  ExecuteModel(
+      std::make_pair(0, ModelExecutionStatus::kSkippedInvalidMetadata));
 
   segment_database_->SetBucketDuration(segment_id, 14,
                                        proto::TimeUnit::UNKNOWN_TIME_UNIT);
-  ExecuteModel(std::make_pair(0, ModelExecutionStatus::kInvalidMetadata));
+  ExecuteModel(
+      std::make_pair(0, ModelExecutionStatus::kSkippedInvalidMetadata));
+}
+
+TEST_F(ModelExecutionManagerTest, ModelNotReady) {
+  auto segment_id =
+      OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB;
+  CreateModelExecutionManager({segment_id}, base::DoNothing());
+
+  segment_database_->SetBucketDuration(segment_id, 3, proto::TimeUnit::HOUR);
+
+  // When the model is unavailable, the execution should fail.
+  EXPECT_CALL(FindHandler(segment_id), ModelAvailable())
+      .WillRepeatedly(Return(false));
+
+  ExecuteModel(std::make_pair(0, ModelExecutionStatus::kSkippedModelNotReady));
 }
 
 TEST_F(ModelExecutionManagerTest, OnSegmentationModelUpdatedInvalidMetadata) {
@@ -414,12 +432,14 @@
       .WillRepeatedly(Return(true));
   EXPECT_CALL(FindHandler(segment_id), ExecuteModelWithInput(_, _)).Times(0);
 
-  ExecuteModel(std::make_pair(0, ModelExecutionStatus::kInvalidMetadata));
+  ExecuteModel(
+      std::make_pair(0, ModelExecutionStatus::kSkippedInvalidMetadata));
 
   EXPECT_CALL(*feature_list_query_processor_,
               ProcessFeatureList(_, segment_id, clock_.Now(), _))
       .WillOnce(RunOnceCallback<3>(/*error=*/true, std::vector<float>{}));
-  ExecuteModel(std::make_pair(0, ModelExecutionStatus::kInvalidMetadata));
+  ExecuteModel(
+      std::make_pair(0, ModelExecutionStatus::kSkippedInvalidMetadata));
 }
 
 TEST_F(ModelExecutionManagerTest, ExecuteModelWithMultipleFeatures) {
diff --git a/components/segmentation_platform/internal/execution/model_execution_status.h b/components/segmentation_platform/internal/execution/model_execution_status.h
index fae5afd..ddda882c 100644
--- a/components/segmentation_platform/internal/execution/model_execution_status.h
+++ b/components/segmentation_platform/internal/execution/model_execution_status.h
@@ -13,8 +13,13 @@
 enum class ModelExecutionStatus {
   kSuccess = 0,
   kExecutionError = 1,
-  kInvalidMetadata = 2,
-  kMaxValue = kInvalidMetadata,
+  kSkippedInvalidMetadata = 2,
+  kSkippedModelNotReady = 3,
+  kSkippedHasFreshResults = 4,
+  kSkippedNotEnoughSignals = 5,
+  kSkippedResultNotExpired = 6,
+  kFailedToSaveResultAfterSuccess = 7,
+  kMaxValue = kFailedToSaveResultAfterSuccess,
 };
 
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/scheduler/model_execution_scheduler_impl.cc b/components/segmentation_platform/internal/scheduler/model_execution_scheduler_impl.cc
index e9e8029e..7d54139 100644
--- a/components/segmentation_platform/internal/scheduler/model_execution_scheduler_impl.cc
+++ b/components/segmentation_platform/internal/scheduler/model_execution_scheduler_impl.cc
@@ -87,10 +87,6 @@
     segment_result.set_timestamp_us(
         clock_->Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
     stats::RecordModelScore(segment_id, result.first);
-  } else {
-    stats::RecordSegmentSelectionFailure(
-        stats::SegmentationSelectionFailureReason::
-            kAtLeastOneModelFailedExecution);
   }
 
   segment_database_->SaveSegmentResult(
@@ -128,6 +124,9 @@
   // Filter out the segments computed recently.
   if (metadata_utils::HasFreshResults(segment_info, clock_->Now())) {
     VLOG(1) << "Segmentation model not executed since it has fresh results.";
+    stats::RecordModelExecutionStatus(
+        segment_info.segment_id(),
+        ModelExecutionStatus::kSkippedHasFreshResults);
     return false;
   }
 
@@ -135,15 +134,18 @@
   if (expired_only && !metadata_utils::HasExpiredOrUnavailableResult(
                           segment_info, clock_->Now())) {
     VLOG(1) << "Segmentation model not executed since results are not expired.";
+    stats::RecordModelExecutionStatus(
+        segment_info.segment_id(),
+        ModelExecutionStatus::kSkippedResultNotExpired);
     return false;
   }
 
   // Filter out segments that don't match signal collection min length.
   if (!signal_storage_config_->MeetsSignalCollectionRequirement(
           segment_info.model_metadata())) {
-    stats::RecordSegmentSelectionFailure(
-        stats::SegmentationSelectionFailureReason::
-            kAtLeastOneModelNeedsMoreSignals);
+    stats::RecordModelExecutionStatus(
+        segment_info.segment_id(),
+        ModelExecutionStatus::kSkippedNotEnoughSignals);
     VLOG(1) << "Segmentation model not executed since metadata requirements "
                "not met.";
     return false;
@@ -165,8 +167,10 @@
                                                 bool success) {
   stats::RecordModelExecutionSaveResult(segment_id, success);
   if (!success) {
-    stats::RecordSegmentSelectionFailure(
-        stats::SegmentationSelectionFailureReason::kFailedToSaveModelResult);
+    // TODO(ssid): Consider removing this enum, this is the only case where the
+    // execution status is recorded twice for the same execution request.
+    stats::RecordModelExecutionStatus(
+        segment_id, ModelExecutionStatus::kFailedToSaveResultAfterSuccess);
     return;
   }
 
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl.cc b/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
index bce9a93b..700358a 100644
--- a/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
+++ b/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
@@ -233,8 +233,11 @@
 
   OnServiceStatusChanged();
   if (!init_success) {
-    stats::RecordSegmentSelectionFailure(
-        stats::SegmentationSelectionFailureReason::kDBInitFailure);
+    for (const auto& config : configs_) {
+      stats::RecordSegmentSelectionFailure(
+          config->segmentation_key,
+          stats::SegmentationSelectionFailureReason::kDBInitFailure);
+    }
     return;
   }
 
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc b/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc
index d553888a..3320ad0 100644
--- a/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc
+++ b/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc
@@ -96,6 +96,7 @@
   {
     // Empty config.
     std::unique_ptr<Config> config = std::make_unique<Config>();
+    config->segmentation_key = "test_key";
     configs.push_back(std::move(config));
   }
 
diff --git a/components/segmentation_platform/internal/selection/segment_selector_impl.cc b/components/segmentation_platform/internal/selection/segment_selector_impl.cc
index 4024baa..fa78630c 100644
--- a/components/segmentation_platform/internal/selection/segment_selector_impl.cc
+++ b/components/segmentation_platform/internal/selection/segment_selector_impl.cc
@@ -43,11 +43,12 @@
     selected_segment_last_session_.segment = selected_segment->segment_id;
     selected_segment_last_session_.is_ready = true;
     stats::RecordSegmentSelectionFailure(
+        config_->segmentation_key,
         stats::SegmentationSelectionFailureReason::kSelectionAvailableInPrefs);
   } else {
     stats::RecordSegmentSelectionFailure(
-        stats::SegmentationSelectionFailureReason::
-            kInvalidSelectionResultInPrefs);
+        config_->segmentation_key, stats::SegmentationSelectionFailureReason::
+                                       kInvalidSelectionResultInPrefs);
   }
 }
 
@@ -99,8 +100,8 @@
               << OptimizationTarget_Name(segment_info.segment_id())
               << " does not meet signal collection requirements.";
       stats::RecordSegmentSelectionFailure(
-          stats::SegmentationSelectionFailureReason::
-              kAtLeastOneSegmentSignalsNotCollected);
+          config_->segmentation_key, stats::SegmentationSelectionFailureReason::
+                                         kAtLeastOneSegmentSignalsNotCollected);
       return false;
     }
 
@@ -110,8 +111,8 @@
               << OptimizationTarget_Name(segment_info.segment_id())
               << " has expired or unavailable result.";
       stats::RecordSegmentSelectionFailure(
-          stats::SegmentationSelectionFailureReason::
-              kAtLeastOneSegmentNotReady);
+          config_->segmentation_key, stats::SegmentationSelectionFailureReason::
+                                         kAtLeastOneSegmentNotReady);
       return false;
     }
   }
@@ -128,6 +129,7 @@
     if (!platform_options_.force_refresh_results &&
         previous_selection->selection_time + ttl_to_use > clock_->Now()) {
       stats::RecordSegmentSelectionFailure(
+          config_->segmentation_key,
           stats::SegmentationSelectionFailureReason::kSelectionTtlNotExpired);
       VLOG(1) << __func__ << ": previous selection of segment="
               << OptimizationTarget_Name(previous_selection->segment_id)
diff --git a/components/segmentation_platform/internal/service_proxy_impl_unittest.cc b/components/segmentation_platform/internal/service_proxy_impl_unittest.cc
index 96ae137a..a35b13e 100644
--- a/components/segmentation_platform/internal/service_proxy_impl_unittest.cc
+++ b/components/segmentation_platform/internal/service_proxy_impl_unittest.cc
@@ -28,6 +28,9 @@
 namespace segmentation_platform {
 
 namespace {
+
+constexpr char kTestSegmentationKey[] = "test_key";
+
 // Adds a segment info into a map, and return a copy of it.
 proto::SegmentInfo AddSegmentInfo(
     std::map<std::string, proto::SegmentInfo>* db_entries,
@@ -85,7 +88,7 @@
 
   void SetUp() override {
     auto config = std::make_unique<Config>();
-    config->segmentation_key = "test";
+    config->segmentation_key = kTestSegmentationKey;
     configs_.emplace_back(std::move(config));
     pref_service_.registry()->RegisterDictionaryPref(kSegmentationResultPref);
   }
@@ -100,8 +103,9 @@
     segment_db_ = std::make_unique<SegmentInfoDatabase>(std::move(db));
 
     result_prefs_ = std::make_unique<SegmentationResultPrefs>(&pref_service_);
-    segment_selectors_["test"] = std::make_unique<FakeSegmentSelectorImpl>(
-        result_prefs_.get(), configs_.at(0).get());
+    segment_selectors_[kTestSegmentationKey] =
+        std::make_unique<FakeSegmentSelectorImpl>(result_prefs_.get(),
+                                                  configs_.at(0).get());
     service_proxy_impl_ = std::make_unique<ServiceProxyImpl>(
         segment_db_.get(), nullptr, &configs_, &segment_selectors_);
     service_proxy_impl_->AddObserver(this);
@@ -170,7 +174,7 @@
   service_proxy_impl_->OnServiceStatusChanged(true, 7);
   db_->LoadCallback(true);
   ASSERT_EQ(client_info_.size(), 1u);
-  ASSERT_EQ(client_info_.at(0).segmentation_key, "test");
+  ASSERT_EQ(client_info_.at(0).segmentation_key, kTestSegmentationKey);
   ASSERT_EQ(client_info_.at(0).segment_status.size(), 1u);
   ServiceProxy::SegmentStatus status = client_info_.at(0).segment_status.at(0);
   ASSERT_EQ(status.segment_id,
@@ -265,10 +269,11 @@
   db_->LoadCallback(true);
 
   service_proxy_impl_->SetSelectedSegment(
-      "test", OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB);
-  ASSERT_EQ(
-      OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB,
-      static_cast<FakeSegmentSelectorImpl*>(segment_selectors_["test"].get())
-          ->new_selection());
+      kTestSegmentationKey,
+      OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB);
+  ASSERT_EQ(OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB,
+            static_cast<FakeSegmentSelectorImpl*>(
+                segment_selectors_[kTestSegmentationKey].get())
+                ->new_selection());
 }
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/stats.cc b/components/segmentation_platform/internal/stats.cc
index 2503e7ec..02f4bf5d 100644
--- a/components/segmentation_platform/internal/stats.cc
+++ b/components/segmentation_platform/internal/stats.cc
@@ -13,8 +13,7 @@
 #include "components/segmentation_platform/internal/proto/types.pb.h"
 #include "components/segmentation_platform/public/config.h"
 
-namespace segmentation_platform {
-namespace stats {
+namespace segmentation_platform::stats {
 namespace {
 // Should map to SegmentationModel variant in
 // //tools/metrics/histograms/metadata/segmentation_platform/histograms.xml.
@@ -197,18 +196,23 @@
 
 // Should map to ModelExecutionStatus variant string in
 // //tools/metrics/histograms/metadata/segmentation_platform/histograms.xml.
-std::string ModelExecutionStatusToHistogramVariant(
+absl::optional<base::StringPiece> ModelExecutionStatusToHistogramVariant(
     ModelExecutionStatus status) {
   switch (status) {
     case ModelExecutionStatus::kSuccess:
       return "Success";
     case ModelExecutionStatus::kExecutionError:
       return "ExecutionError";
-    case ModelExecutionStatus::kInvalidMetadata:
-      return "InvalidMetadata";
-    default:
-      NOTREACHED();
-      return "Unknown";
+
+    // Only record duration histograms when tflite model is executed. These
+    // cases mean the execution was skipped.
+    case ModelExecutionStatus::kSkippedInvalidMetadata:
+    case ModelExecutionStatus::kSkippedModelNotReady:
+    case ModelExecutionStatus::kSkippedHasFreshResults:
+    case ModelExecutionStatus::kSkippedNotEnoughSignals:
+    case ModelExecutionStatus::kSkippedResultNotExpired:
+    case ModelExecutionStatus::kFailedToSaveResultAfterSuccess:
+      return absl::nullopt;
   }
 }
 
@@ -423,20 +427,28 @@
                                        base::TimeDelta duration) {
   ModelExecutionStatus status = success ? ModelExecutionStatus::kSuccess
                                         : ModelExecutionStatus::kExecutionError;
+  absl::optional<base::StringPiece> status_variant =
+      ModelExecutionStatusToHistogramVariant(status);
+  if (!status_variant)
+    return;
   base::UmaHistogramTimes(
-      "SegmentationPlatform.ModelExecution.Duration.Model." +
-          OptimizationTargetToHistogramVariant(segment_id) + "." +
-          ModelExecutionStatusToHistogramVariant(status),
+      base::StrCat({"SegmentationPlatform.ModelExecution.Duration.Model.",
+                    OptimizationTargetToHistogramVariant(segment_id), ".",
+                    *status_variant}),
       duration);
 }
 
 void RecordModelExecutionDurationTotal(OptimizationTarget segment_id,
                                        ModelExecutionStatus status,
                                        base::TimeDelta duration) {
+  absl::optional<base::StringPiece> status_variant =
+      ModelExecutionStatusToHistogramVariant(status);
+  if (!status_variant)
+    return;
   base::UmaHistogramTimes(
-      "SegmentationPlatform.ModelExecution.Duration.Total." +
-          OptimizationTargetToHistogramVariant(segment_id) + "." +
-          ModelExecutionStatusToHistogramVariant(status),
+      base::StrCat({"SegmentationPlatform.ModelExecution.Duration.Total.",
+                    OptimizationTargetToHistogramVariant(segment_id), ".",
+                    *status_variant}),
       duration);
 }
 
@@ -514,9 +526,12 @@
       histogram_value_count);
 }
 
-void RecordSegmentSelectionFailure(SegmentationSelectionFailureReason reason) {
-  base::UmaHistogramEnumeration("SegmentationPlatform.SelectionFailedReason",
-                                reason);
+void RecordSegmentSelectionFailure(const std::string& segmentation_key,
+                                   SegmentationSelectionFailureReason reason) {
+  base::UmaHistogramEnumeration(
+      base::StrCat({"SegmentationPlatform.SelectionFailedReason.",
+                    SegmentationKeyToUmaName(segmentation_key)}),
+      reason);
 }
 
 void RecordModelAvailability(OptimizationTarget segment_id,
@@ -533,5 +548,4 @@
       tensor_size);
 }
 
-}  // namespace stats
-}  // namespace segmentation_platform
+}  // namespace segmentation_platform::stats
diff --git a/components/segmentation_platform/internal/stats.h b/components/segmentation_platform/internal/stats.h
index fb63787..5e1e707 100644
--- a/components/segmentation_platform/internal/stats.h
+++ b/components/segmentation_platform/internal/stats.h
@@ -14,8 +14,7 @@
 
 using optimization_guide::proto::OptimizationTarget;
 
-namespace segmentation_platform {
-namespace stats {
+namespace segmentation_platform::stats {
 
 // Keep in sync with AdaptiveToolbarSegmentSwitch in enums.xml.
 // Visible for testing.
@@ -164,7 +163,8 @@
 };
 
 // Records the reason for failure or success to compute a segment selection.
-void RecordSegmentSelectionFailure(SegmentationSelectionFailureReason reason);
+void RecordSegmentSelectionFailure(const std::string& segmentation_key,
+                                   SegmentationSelectionFailureReason reason);
 
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused. Please keep in sync with
@@ -183,7 +183,6 @@
 // structured metrics.
 void RecordTooManyInputTensors(int tensor_size);
 
-}  // namespace stats
-}  // namespace segmentation_platform
+}  // namespace segmentation_platform::stats
 
 #endif  // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_STATS_H_
diff --git a/components/services/app_service/public/cpp/BUILD.gn b/components/services/app_service/public/cpp/BUILD.gn
index 13bf72c..624bb2b 100644
--- a/components/services/app_service/public/cpp/BUILD.gn
+++ b/components/services/app_service/public/cpp/BUILD.gn
@@ -200,6 +200,8 @@
 
 source_set("intents") {
   sources = [
+    "intent.cc",
+    "intent.h",
     "intent_constants.cc",
     "intent_constants.h",
     "intent_filter_util.cc",
diff --git a/components/services/app_service/public/cpp/app_types.h b/components/services/app_service/public/cpp/app_types.h
index 3db9e0b20..4a7aabf 100644
--- a/components/services/app_service/public/cpp/app_types.h
+++ b/components/services/app_service/public/cpp/app_types.h
@@ -241,8 +241,8 @@
     const apps::mojom::OptionalBool& mojom_optional_bool);
 
 COMPONENT_EXPORT(APP_TYPES)
-absl::optional<bool> GetMojomOptionalBool(
-    const apps::mojom::OptionalBool& mojom_optional_bool);
+apps::mojom::OptionalBool GetMojomOptionalBool(
+    const absl::optional<bool>& mojom_optional_bool);
 
 COMPONENT_EXPORT(APP_TYPES)
 AppPtr ConvertMojomAppToApp(const apps::mojom::AppPtr& mojom_app);
diff --git a/components/services/app_service/public/cpp/intent.cc b/components/services/app_service/public/cpp/intent.cc
new file mode 100644
index 0000000..0a6350f
--- /dev/null
+++ b/components/services/app_service/public/cpp/intent.cc
@@ -0,0 +1,157 @@
+// 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 "components/services/app_service/public/cpp/intent.h"
+
+#include "base/files/safe_base_name.h"
+#include "components/services/app_service/public/cpp/app_types.h"
+
+namespace apps {
+
+IntentFile::IntentFile(const GURL& url) : url(url) {}
+
+IntentFile::~IntentFile() = default;
+
+Intent::Intent(const std::string& action) : action(action) {}
+
+Intent::~Intent() = default;
+
+IntentFilePtr ConvertMojomIntentFileToIntentFile(
+    const apps::mojom::IntentFilePtr& mojom_intent_file) {
+  if (!mojom_intent_file) {
+    return nullptr;
+  }
+
+  auto intent_file = std::make_unique<IntentFile>(mojom_intent_file->url);
+  if (mojom_intent_file->mime_type.has_value()) {
+    intent_file->mime_type = mojom_intent_file->mime_type.value();
+  }
+  if (mojom_intent_file->file_name.has_value()) {
+    intent_file->file_name = mojom_intent_file->file_name.value();
+  }
+  intent_file->file_size = mojom_intent_file->file_size;
+  intent_file->is_directory = GetOptionalBool(mojom_intent_file->is_directory);
+  return intent_file;
+}
+
+apps::mojom::IntentFilePtr ConvertIntentFileToMojomIntentFile(
+    const IntentFilePtr& intent_file) {
+  if (!intent_file) {
+    return nullptr;
+  }
+
+  auto mojom_intent_file = apps::mojom::IntentFile::New();
+  mojom_intent_file->url = intent_file->url;
+  if (intent_file->mime_type.has_value()) {
+    mojom_intent_file->mime_type = intent_file->mime_type.value();
+  }
+  if (intent_file->file_name.has_value()) {
+    mojom_intent_file->file_name = intent_file->file_name.value();
+  }
+  mojom_intent_file->file_size = intent_file->file_size;
+  mojom_intent_file->is_directory =
+      GetMojomOptionalBool(intent_file->is_directory);
+  return mojom_intent_file;
+}
+
+IntentPtr ConvertMojomIntentToIntent(
+    const apps::mojom::IntentPtr& mojom_intent) {
+  if (!mojom_intent) {
+    return nullptr;
+  }
+
+  auto intent = std::make_unique<Intent>(mojom_intent->action);
+
+  if (mojom_intent->url.has_value()) {
+    intent->url = mojom_intent->url.value();
+  }
+  if (mojom_intent->mime_type.has_value()) {
+    intent->mime_type = mojom_intent->mime_type.value();
+  }
+  if (mojom_intent->files.has_value()) {
+    for (const auto& file : mojom_intent->files.value()) {
+      intent->files.push_back(ConvertMojomIntentFileToIntentFile(file));
+    }
+  }
+  if (mojom_intent->activity_name.has_value()) {
+    intent->activity_name = mojom_intent->activity_name.value();
+  }
+  if (mojom_intent->drive_share_url.has_value()) {
+    intent->drive_share_url = mojom_intent->drive_share_url.value();
+  }
+  if (mojom_intent->share_text.has_value()) {
+    intent->share_text = mojom_intent->share_text.value();
+  }
+  if (mojom_intent->share_title.has_value()) {
+    intent->share_title = mojom_intent->share_title.value();
+  }
+  if (mojom_intent->start_type.has_value()) {
+    intent->start_type = mojom_intent->start_type.value();
+  }
+  if (mojom_intent->categories.has_value()) {
+    for (const auto& category : mojom_intent->categories.value()) {
+      intent->categories.push_back(category);
+    }
+  }
+  if (mojom_intent->data.has_value()) {
+    intent->data = mojom_intent->data.value();
+  }
+  intent->ui_bypassed = GetOptionalBool(mojom_intent->ui_bypassed);
+  if (mojom_intent->extras.has_value()) {
+    for (const auto& extra : mojom_intent->extras.value()) {
+      intent->extras[extra.first] = extra.second;
+    }
+  }
+  return intent;
+}
+
+apps::mojom::IntentPtr ConvertIntentToMojomIntent(const IntentPtr& intent) {
+  if (!intent) {
+    return nullptr;
+  }
+
+  auto mojom_intent = apps::mojom::Intent::New();
+  mojom_intent->action = intent->action;
+
+  if (intent->url.has_value()) {
+    mojom_intent->url = intent->url.value();
+  }
+  if (intent->mime_type.has_value()) {
+    mojom_intent->mime_type = intent->mime_type.value();
+  }
+  if (!intent->files.empty()) {
+    mojom_intent->files = std::vector<apps::mojom::IntentFilePtr>{};
+    for (const auto& file : intent->files) {
+      mojom_intent->files->push_back(ConvertIntentFileToMojomIntentFile(file));
+    }
+  }
+  if (intent->activity_name.has_value()) {
+    mojom_intent->activity_name = intent->activity_name.value();
+  }
+  if (intent->drive_share_url.has_value()) {
+    mojom_intent->drive_share_url = intent->drive_share_url.value();
+  }
+  if (intent->share_text.has_value()) {
+    mojom_intent->share_text = intent->share_text.value();
+  }
+  if (intent->share_title.has_value()) {
+    mojom_intent->share_title = intent->share_title.value();
+  }
+  if (intent->start_type.has_value()) {
+    mojom_intent->start_type = intent->start_type.value();
+  }
+  if (!intent->categories.empty()) {
+    mojom_intent->categories = intent->categories;
+  }
+  if (intent->data.has_value()) {
+    mojom_intent->data = intent->data.value();
+  }
+  mojom_intent->ui_bypassed = GetMojomOptionalBool(intent->ui_bypassed);
+  if (!intent->extras.empty()) {
+    mojom_intent->extras = intent->extras;
+  }
+  return mojom_intent;
+}
+
+}  // namespace apps
diff --git a/components/services/app_service/public/cpp/intent.h b/components/services/app_service/public/cpp/intent.h
new file mode 100644
index 0000000..e61c64c
--- /dev/null
+++ b/components/services/app_service/public/cpp/intent.h
@@ -0,0 +1,105 @@
+// 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 COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_INTENT_H_
+#define COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_INTENT_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/files/safe_base_name.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace apps {
+
+// Metadata for a single file shared through an intent.
+struct IntentFile {
+  explicit IntentFile(const GURL& url);
+  IntentFile(const IntentFile&) = delete;
+  IntentFile& operator=(const IntentFile&) = delete;
+  ~IntentFile();
+
+  // The URL of the file to share. Normally has the filesystem: scheme, but
+  // could be externalfile: or a different scheme, depending on the source.
+  GURL url;
+
+  // The following optional fields can be provided to supply additional metadata
+  // information in cases where fetching the metadata through the file would be
+  // difficult or expensive.
+
+  // File MIME type.
+  absl::optional<std::string> mime_type;
+  // Human readable file name, including extension, and not allow absolute paths
+  // or references to parent directories.
+  absl::optional<base::SafeBaseName> file_name;
+  // File size in bytes.
+  uint64_t file_size = 0;
+  // Whether this is a directory or not.
+  absl::optional<bool> is_directory;
+};
+
+using IntentFilePtr = std::unique_ptr<IntentFile>;
+
+// Action and resource handling request. This should be kept in sync with
+// ConvertIntentToValue and ConvertValueToIntent in
+// components/services/app_service/public/cpp/intent_util.*
+struct Intent {
+  explicit Intent(const std::string& action);
+  Intent(const Intent&) = delete;
+  Intent& operator=(const Intent&) = delete;
+  ~Intent();
+
+  // Intent action. e.g. view, send.
+  std::string action;
+  // The URL of the intent. e.g. https://www.google.com/.
+  absl::optional<GURL> url;
+
+  // MIME type. e.g. text/plain, image/*.
+  absl::optional<std::string> mime_type;
+
+  // The files to share.
+  std::vector<IntentFilePtr> files;
+  // The activity for the app to launch.
+  absl::optional<std::string> activity_name;
+
+  // The Drive share URL, this is only filled if the intent contains a file
+  // from Google Drive.
+  absl::optional<GURL> drive_share_url;
+  // Text to share. e.g. Share link to other app.
+  absl::optional<std::string> share_text;
+  // Title for the share.
+  absl::optional<std::string> share_title;
+  // Start type.
+  absl::optional<std::string> start_type;
+  std::vector<std::string> categories;
+  // URI
+  absl::optional<std::string> data;
+  // Whether or not the user saw the UI.
+  absl::optional<bool> ui_bypassed;
+  // Optional string extras.
+  base::flat_map<std::string, std::string> extras;
+};
+
+using IntentPtr = std::unique_ptr<Intent>;
+
+// TODO(crbug.com/1253250): Remove these functions after migrating to non-mojo
+// AppService.
+IntentFilePtr ConvertMojomIntentFileToIntentFile(
+    const apps::mojom::IntentFilePtr& mojom_intent_file);
+
+apps::mojom::IntentFilePtr ConvertIntentFileToMojomIntentFile(
+    const IntentFilePtr& intent_file);
+
+IntentPtr ConvertMojomIntentToIntent(
+    const apps::mojom::IntentPtr& mojom_intent);
+
+apps::mojom::IntentPtr ConvertIntentToMojomIntent(const IntentPtr& intent);
+
+}  // namespace apps
+
+#endif  // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_INTENT_H_
diff --git a/components/services/app_service/public/cpp/intent_util_unittest.cc b/components/services/app_service/public/cpp/intent_util_unittest.cc
index af13bc96..0b097de0 100644
--- a/components/services/app_service/public/cpp/intent_util_unittest.cc
+++ b/components/services/app_service/public/cpp/intent_util_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/services/app_service/public/cpp/intent_util.h"
 
 #include "base/values.h"
+#include "components/services/app_service/public/cpp/intent.h"
 #include "components/services/app_service/public/cpp/intent_filter_util.h"
 #include "components/services/app_service/public/cpp/intent_test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -870,3 +871,56 @@
       apps_util::CreateFileFilterForView("inode/directory", "", kLabel);
   EXPECT_FALSE(apps_util::IsGenericFileHandler(intent3, filter12));
 }
+
+// TODO(crbug.com/1253250): Remove after migrating to non-mojo AppService.
+TEST_F(IntentUtilTest, MojomConvert) {
+  const std::string action = apps_util::kIntentActionSend;
+  GURL test_url1 = GURL("https://www.google.com/");
+  GURL test_url2 = GURL("https://www.abc.com/");
+  GURL test_url3 = GURL("https://www.foo.com/");
+  const std::string mime_type = "image/jpeg";
+  const std::string activity_name = "test";
+  const std::string share_text = "share text";
+  const std::string share_title = "share title";
+  const std::string start_type = "start type";
+  const std::string category1 = "category1";
+  const std::string data = "data";
+  const apps::mojom::OptionalBool ui_bypassed =
+      apps::mojom::OptionalBool::kTrue;
+  base::flat_map<std::string, std::string> extras = {
+      {"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
+
+  auto file1 = apps::mojom::IntentFile::New();
+  file1->url = test_url1;
+  auto file2 = apps::mojom::IntentFile::New();
+  file2->url = test_url2;
+  auto files = std::vector<apps::mojom::IntentFilePtr>();
+  files.push_back(std::move(file1));
+  files.push_back(std::move(file2));
+
+  auto src_intent =
+      CreateIntent(action, test_url1, mime_type, std::move(files),
+                   activity_name, test_url3, share_text, share_title,
+                   start_type, {category1}, data, ui_bypassed, extras);
+  auto dst_intent = apps::ConvertIntentToMojomIntent(
+      apps::ConvertMojomIntentToIntent(src_intent));
+
+  EXPECT_EQ(action, dst_intent->action);
+  EXPECT_EQ(test_url1, dst_intent->url.value());
+  EXPECT_EQ(mime_type, dst_intent->mime_type.value());
+  EXPECT_EQ(2u, dst_intent->files->size());
+  EXPECT_EQ(test_url1, dst_intent->files.value()[0]->url);
+  EXPECT_EQ(test_url2, dst_intent->files.value()[1]->url);
+  EXPECT_EQ(activity_name, dst_intent->activity_name.value());
+  EXPECT_EQ(test_url3, dst_intent->drive_share_url.value());
+  EXPECT_EQ(share_text, dst_intent->share_text.value());
+  EXPECT_EQ(share_title, dst_intent->share_title.value());
+  EXPECT_EQ(start_type, dst_intent->start_type.value());
+  EXPECT_EQ(1u, dst_intent->categories->size());
+  EXPECT_EQ(category1, dst_intent->categories.value()[0]);
+  EXPECT_EQ(data, dst_intent->data.value());
+  EXPECT_EQ(ui_bypassed, dst_intent->ui_bypassed);
+  EXPECT_TRUE(dst_intent->extras.has_value());
+  EXPECT_EQ(3u, dst_intent->extras->size());
+  EXPECT_EQ(extras, dst_intent->extras.value());
+}
diff --git a/components/services/app_service/public/mojom/types.mojom b/components/services/app_service/public/mojom/types.mojom
index b759fceb..7783e70 100644
--- a/components/services/app_service/public/mojom/types.mojom
+++ b/components/services/app_service/public/mojom/types.mojom
@@ -423,6 +423,8 @@
 };
 
 // Metadata for a single file shared through an intent.
+// Mojom IntentFile struct is DEPRECATED. When adding new fields, please use
+// the IntentFile struct in components/services/app_service/public/cpp/intent.h.
 struct IntentFile {
   // The URL of the file to share. Normally has the filesystem: scheme, but
   // could be externalfile: or a different scheme, depending on the source.
@@ -445,6 +447,8 @@
 // Action and resource handling request. This should
 // be kept in sync with ConvertIntentToValue and ConvertValueToIntent in
 // components/services/app_service/public/cpp/intent_util.*
+// Mojom Intent struct is DEPRECATED. When adding new fields, please use
+// the Intent struct in components/services/app_service/public/cpp/intent.h.
 struct Intent {
   string action; // Intent action. e.g. view, send.
   url.mojom.Url? url; // The URL of the intent. e.g. https://www.google.com/.
diff --git a/components/url_formatter/spoof_checks/top_domains/make_top_domain_list_variables.cc b/components/url_formatter/spoof_checks/top_domains/make_top_domain_list_variables.cc
index a6a6438..8557d5e5 100644
--- a/components/url_formatter/spoof_checks/top_domains/make_top_domain_list_variables.cc
+++ b/components/url_formatter/spoof_checks/top_domains/make_top_domain_list_variables.cc
@@ -21,6 +21,7 @@
 
 #include <cctype>
 #include <iostream>
+#include <set>
 #include <sstream>
 #include <string>
 #include <vector>
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index b1befbf..1eaf653 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -387,8 +387,8 @@
     "attribution_reporting/aggregatable_histogram_contribution.cc",
     "attribution_reporting/aggregatable_histogram_contribution.h",
     "attribution_reporting/attribution_aggregatable_key.h",
-    "attribution_reporting/attribution_aggregatable_sources.cc",
-    "attribution_reporting/attribution_aggregatable_sources.h",
+    "attribution_reporting/attribution_aggregatable_source.cc",
+    "attribution_reporting/attribution_aggregatable_source.h",
     "attribution_reporting/attribution_aggregatable_trigger.cc",
     "attribution_reporting/attribution_aggregatable_trigger.h",
     "attribution_reporting/attribution_cookie_checker.h",
diff --git a/content/browser/aggregation_service/aggregatable_report.cc b/content/browser/aggregation_service/aggregatable_report.cc
index 8a9e37b..65ddd1a2 100644
--- a/content/browser/aggregation_service/aggregatable_report.cc
+++ b/content/browser/aggregation_service/aggregatable_report.cc
@@ -56,8 +56,7 @@
     case AggregationServicePayloadContents::AggregationMode::
         kExperimentalPoplar:
       // TODO(crbug.com/1295705): Update default processing urls.
-      return {GURL("https://server1.example.com"),
-              GURL("https://server2.example.com")};
+      return {GURL("https://server1.example"), GURL("https://server2.example")};
   }
 }
 
diff --git a/content/browser/aggregation_service/aggregatable_report_assembler_unittest.cc b/content/browser/aggregation_service/aggregatable_report_assembler_unittest.cc
index 1fc2ce5..412e2cd 100644
--- a/content/browser/aggregation_service/aggregatable_report_assembler_unittest.cc
+++ b/content/browser/aggregation_service/aggregatable_report_assembler_unittest.cc
@@ -113,8 +113,8 @@
 TEST_F(AggregatableReportAssemblerTest, BothKeyFetchesFail_ErrorReturned) {
   base::HistogramTester histograms;
 
-  AggregatableReportRequest request =
-      aggregation_service::CreateExampleRequest();
+  AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
+      AggregationServicePayloadContents::AggregationMode::kExperimentalPoplar);
   std::vector<GURL> processing_urls = request.processing_urls();
 
   EXPECT_CALL(*fetcher(), GetPublicKey(processing_urls[0], _))
@@ -139,8 +139,8 @@
 TEST_F(AggregatableReportAssemblerTest, FirstKeyFetchFails_ErrorReturned) {
   base::HistogramTester histograms;
 
-  AggregatableReportRequest request =
-      aggregation_service::CreateExampleRequest();
+  AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
+      AggregationServicePayloadContents::AggregationMode::kExperimentalPoplar);
   std::vector<GURL> processing_urls = request.processing_urls();
 
   EXPECT_CALL(*fetcher(), GetPublicKey(processing_urls[0], _))
@@ -166,8 +166,8 @@
 TEST_F(AggregatableReportAssemblerTest, SecondKeyFetchFails_ErrorReturned) {
   base::HistogramTester histograms;
 
-  AggregatableReportRequest request =
-      aggregation_service::CreateExampleRequest();
+  AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
+      AggregationServicePayloadContents::AggregationMode::kExperimentalPoplar);
   std::vector<GURL> processing_urls = request.processing_urls();
 
   EXPECT_CALL(*fetcher(), GetPublicKey(processing_urls[0], _))
@@ -194,8 +194,8 @@
        BothKeyFetchesSucceed_ValidReportReturned) {
   base::HistogramTester histograms;
 
-  AggregatableReportRequest request =
-      aggregation_service::CreateExampleRequest();
+  AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
+      AggregationServicePayloadContents::AggregationMode::kExperimentalPoplar);
 
   std::vector<GURL> processing_urls = request.processing_urls();
   std::vector<PublicKey> public_keys = {
@@ -233,7 +233,7 @@
 }
 
 TEST_F(AggregatableReportAssemblerTest,
-       SingleServerKeyFetchSucceeds_ValidReportReturned) {
+       OnlyKeyFetchSucceeds_ValidReportReturned) {
   base::HistogramTester histograms;
 
   AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
@@ -268,8 +268,7 @@
       AggregatableReportAssembler::AssemblyStatus::kOk, 1);
 }
 
-TEST_F(AggregatableReportAssemblerTest,
-       SingleServerKeyFetchFails_ErrorReturned) {
+TEST_F(AggregatableReportAssemblerTest, OnlyKeyFetchFails_ErrorReturned) {
   base::HistogramTester histograms;
 
   AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
@@ -292,11 +291,11 @@
 }
 
 TEST_F(AggregatableReportAssemblerTest,
-       KeyFetchesReturnInSwappedOrder_ValidReportReturned) {
+       TwoKeyFetchesReturnInSwappedOrder_ValidReportReturned) {
   base::HistogramTester histograms;
 
-  AggregatableReportRequest request =
-      aggregation_service::CreateExampleRequest();
+  AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
+      AggregationServicePayloadContents::AggregationMode::kExperimentalPoplar);
 
   std::vector<GURL> processing_urls = request.processing_urls();
   std::vector<PublicKey> public_keys = {
@@ -344,6 +343,7 @@
   std::vector<GURL> processing_urls = request.processing_urls();
 
   EXPECT_CALL(callback(), Run).Times(0);
+  EXPECT_CALL(*fetcher(), GetPublicKey);
   assembler()->AssembleReport(std::move(request), callback().Get());
 
   ResetAssembler();
@@ -359,31 +359,24 @@
       aggregation_service::CreateExampleRequest();
 
   std::vector<GURL> processing_urls = request.processing_urls();
-  std::vector<PublicKey> public_keys = {
-      aggregation_service::GenerateKey("id123").public_key,
-      aggregation_service::GenerateKey("456abc").public_key};
+  PublicKey public_key = aggregation_service::GenerateKey("id123").public_key;
 
   absl::optional<AggregatableReport> report =
       AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
-          aggregation_service::CloneReportRequest(request), public_keys);
+          aggregation_service::CloneReportRequest(request), {public_key});
   ASSERT_TRUE(report.has_value());
 
-  std::vector<FetchCallback> pending_callbacks_1(2);
+  std::vector<FetchCallback> pending_callbacks(2);
   EXPECT_CALL(*fetcher(), GetPublicKey(processing_urls[0], _))
-      .WillOnce(MoveArg<1>(&pending_callbacks_1.front()))
-      .WillOnce(MoveArg<1>(&pending_callbacks_1.back()));
-
-  std::vector<FetchCallback> pending_callbacks_2(2);
-  EXPECT_CALL(*fetcher(), GetPublicKey(processing_urls[1], _))
-      .WillOnce(MoveArg<1>(&pending_callbacks_2.front()))
-      .WillOnce(MoveArg<1>(&pending_callbacks_2.back()));
+      .WillOnce(MoveArg<1>(&pending_callbacks.front()))
+      .WillOnce(MoveArg<1>(&pending_callbacks.back()));
 
   EXPECT_CALL(callback(), Run(report, AssemblyStatus::kOk)).Times(2);
 
   absl::optional<AggregatableReportRequest> first_request;
   absl::optional<AggregatableReportRequest> second_request;
-  EXPECT_CALL(*report_provider(),
-              CreateFromRequestAndPublicKeys(_, public_keys))
+  EXPECT_CALL(*report_provider(), CreateFromRequestAndPublicKeys(
+                                      _, std::vector<PublicKey>{public_key}))
       .WillOnce(MoveRequestAndReturnReport(&first_request, report.value()))
       .WillOnce(MoveRequestAndReturnReport(&second_request,
                                            std::move(report.value())));
@@ -393,15 +386,10 @@
   assembler()->AssembleReport(aggregation_service::CloneReportRequest(request),
                               callback().Get());
 
-  std::move(pending_callbacks_1.front())
-      .Run(public_keys[0], PublicKeyFetchStatus::kOk);
-  std::move(pending_callbacks_1.back())
-      .Run(public_keys[0], PublicKeyFetchStatus::kOk);
-
-  std::move(pending_callbacks_2.front())
-      .Run(public_keys[1], PublicKeyFetchStatus::kOk);
-  std::move(pending_callbacks_2.back())
-      .Run(public_keys[1], PublicKeyFetchStatus::kOk);
+  std::move(pending_callbacks.front())
+      .Run(public_key, PublicKeyFetchStatus::kOk);
+  std::move(pending_callbacks.back())
+      .Run(public_key, PublicKeyFetchStatus::kOk);
 
   ASSERT_TRUE(first_request.has_value());
   EXPECT_TRUE(
@@ -419,12 +407,10 @@
        TooManySimultaneousRequests_ErrorCausedForNewRequests) {
   base::HistogramTester histograms;
 
-  std::vector<PublicKey> public_keys = {
-      aggregation_service::GenerateKey("id123").public_key,
-      aggregation_service::GenerateKey("456abc").public_key};
+  PublicKey public_key = aggregation_service::GenerateKey("id123").public_key;
   absl::optional<AggregatableReport> report =
       AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
-          aggregation_service::CreateExampleRequest(), std::move(public_keys));
+          aggregation_service::CreateExampleRequest(), {std::move(public_key)});
   ASSERT_TRUE(report.has_value());
 
   std::vector<FetchCallback> pending_callbacks;
@@ -437,7 +423,7 @@
     int current_check = 1;
 
     EXPECT_CALL(*fetcher(), GetPublicKey)
-        .Times(2 * AggregatableReportAssembler::kMaxSimultaneousRequests)
+        .Times(AggregatableReportAssembler::kMaxSimultaneousRequests)
         .WillRepeatedly(Invoke([&](const GURL& url, FetchCallback callback) {
           pending_callbacks.push_back(std::move(callback));
         }));
@@ -474,12 +460,10 @@
 
   checkpoint.Call(current_call++);
 
-  for (size_t i = 0; i < pending_callbacks.size(); ++i) {
-    // Every request has 2 pending fetch callbacks.
-    if (i % 2 == 0)
-      checkpoint.Call(current_call++);
+  for (FetchCallback& pending_callback : pending_callbacks) {
+    checkpoint.Call(current_call++);
 
-    std::move(pending_callbacks[i])
+    std::move(pending_callback)
         .Run(aggregation_service::GenerateKey("id123").public_key,
              PublicKeyFetchStatus::kOk);
   }
diff --git a/content/browser/aggregation_service/aggregatable_report_unittest.cc b/content/browser/aggregation_service/aggregatable_report_unittest.cc
index a2b432b..8327263 100644
--- a/content/browser/aggregation_service/aggregatable_report_unittest.cc
+++ b/content/browser/aggregation_service/aggregatable_report_unittest.cc
@@ -191,9 +191,10 @@
   }
 }
 
-TEST(AggregatableReportTest, ValidTwoPartyRequest_ValidReportReturned) {
-  AggregatableReportRequest request =
-      aggregation_service::CreateExampleRequest();
+TEST(AggregatableReportTest,
+     ValidExperimentalPoplarRequest_ValidReportReturned) {
+  AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
+      AggregationServicePayloadContents::AggregationMode::kExperimentalPoplar);
 
   AggregationServicePayloadContents expected_payload_contents =
       request.payload_contents();
@@ -213,7 +214,7 @@
                    expected_num_processing_urls, hpke_keys));
 }
 
-TEST(AggregatableReportTest, ValidSingleServerRequest_ValidReportReturned) {
+TEST(AggregatableReportTest, ValidTeeBasedRequest_ValidReportReturned) {
   AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
       AggregationServicePayloadContents::AggregationMode::kTeeBased);
 
@@ -284,18 +285,16 @@
       request->payload_contents();
   size_t expected_num_processing_urls = request->processing_urls().size();
 
-  std::vector<aggregation_service::TestHpkeKey> hpke_keys = {
-      aggregation_service::GenerateKey("id123"),
-      aggregation_service::GenerateKey("456abc")};
+  aggregation_service::TestHpkeKey hpke_key =
+      aggregation_service::GenerateKey("id123");
 
   absl::optional<AggregatableReport> report =
       AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
-          std::move(request.value()),
-          {hpke_keys[0].public_key, hpke_keys[1].public_key});
+          std::move(request.value()), {hpke_key.public_key});
 
   ASSERT_NO_FATAL_FAILURE(
       VerifyReport(report, expected_payload_contents, expected_shared_info,
-                   expected_num_processing_urls, hpke_keys));
+                   expected_num_processing_urls, {hpke_key}));
 }
 
 TEST(AggregatableReportTest,
@@ -351,8 +350,7 @@
 
 TEST(AggregatableReportTest, RequestCreatedWithZeroContributions) {
   AggregatableReportRequest example_request =
-      aggregation_service::CreateExampleRequest(
-          AggregationServicePayloadContents::AggregationMode::kTeeBased);
+      aggregation_service::CreateExampleRequest();
 
   AggregationServicePayloadContents payload_contents =
       example_request.payload_contents();
diff --git a/content/browser/aggregation_service/aggregation_service_features.cc b/content/browser/aggregation_service/aggregation_service_features.cc
index 811d703..c332ed5 100644
--- a/content/browser/aggregation_service/aggregation_service_features.cc
+++ b/content/browser/aggregation_service/aggregation_service_features.cc
@@ -13,6 +13,6 @@
 const base::FeatureParam<std::string>
     kPrivacySandboxAggregationServiceTrustedServerUrlParam{
         &kPrivacySandboxAggregationService, "trusted_server_url",
-        "https://server.example.com/.well-known/aggregation-service/keys.json"};
+        "https://server.example/.well-known/aggregation-service/keys.json"};
 
 }  // namespace content
diff --git a/content/browser/aggregation_service/aggregation_service_impl_unittest.cc b/content/browser/aggregation_service/aggregation_service_impl_unittest.cc
index c69f2ce..0206bda 100644
--- a/content/browser/aggregation_service/aggregation_service_impl_unittest.cc
+++ b/content/browser/aggregation_service/aggregation_service_impl_unittest.cc
@@ -170,9 +170,6 @@
   payloads.emplace_back(/*payload=*/kABCD1234AsBytes,
                         /*key_id=*/"key_1",
                         /*debug_cleartext_payload=*/absl::nullopt);
-  payloads.emplace_back(/*payload=*/kEFGH5678AsBytes,
-                        /*key_id=*/"key_2",
-                        /*debug_cleartext_payload=*/absl::nullopt);
 
   AggregatableReport report(std::move(payloads), "example_shared_info");
   assembler()->TriggerResponse(
@@ -206,9 +203,6 @@
   payloads.emplace_back(/*payload=*/kABCD1234AsBytes,
                         /*key_id=*/"key_1",
                         /*debug_cleartext_payload=*/absl::nullopt);
-  payloads.emplace_back(/*payload=*/kEFGH5678AsBytes,
-                        /*key_id=*/"key_2",
-                        /*debug_cleartext_payload=*/absl::nullopt);
 
   AggregatableReport report(std::move(payloads), "example_shared_info");
 
diff --git a/content/browser/aggregation_service/aggregation_service_test_utils.h b/content/browser/aggregation_service/aggregation_service_test_utils.h
index f9e8ffa..0a7039f 100644
--- a/content/browser/aggregation_service/aggregation_service_test_utils.h
+++ b/content/browser/aggregation_service/aggregation_service_test_utils.h
@@ -55,11 +55,9 @@
     const AggregatableReportSharedInfo& actual);
 
 // Returns an example report request, using the given parameters.
-// TODO(crbug.com/1303041): Switch default aggregation mode to `kDefault`.
 AggregatableReportRequest CreateExampleRequest(
     AggregationServicePayloadContents::AggregationMode aggregation_mode =
-        AggregationServicePayloadContents::AggregationMode::
-            kExperimentalPoplar);
+        AggregationServicePayloadContents::AggregationMode::kDefault);
 
 AggregatableReportRequest CloneReportRequest(
     const AggregatableReportRequest& request);
diff --git a/content/browser/attribution_reporting/aggregatable_attribution_utils.cc b/content/browser/attribution_reporting/aggregatable_attribution_utils.cc
index cae8ac4..18dcd897 100644
--- a/content/browser/attribution_reporting/aggregatable_attribution_utils.cc
+++ b/content/browser/attribution_reporting/aggregatable_attribution_utils.cc
@@ -11,7 +11,7 @@
 #include "base/check.h"
 #include "base/containers/flat_map.h"
 #include "content/browser/attribution_reporting/aggregatable_histogram_contribution.h"
-#include "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_aggregatable_trigger.h"
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/browser/attribution_reporting/attribution_utils.h"
@@ -22,14 +22,14 @@
 
 std::vector<AggregatableHistogramContribution> CreateAggregatableHistogram(
     const AttributionFilterData& source_filter_data,
-    const AttributionAggregatableSources& sources,
+    const AttributionAggregatableSource& source,
     const AttributionAggregatableTrigger& trigger) {
   // TODO(linnan): Log metrics for early returns.
 
   // Pairs of key id and bucket key.
   std::vector<std::pair<std::string, absl::uint128>> buckets;
-  buckets.reserve(sources.proto().sources().size());
-  for (const auto& [key_id, key] : sources.proto().sources()) {
+  buckets.reserve(source.proto().keys().size());
+  for (const auto& [key_id, key] : source.proto().keys()) {
     buckets.emplace_back(key_id,
                          absl::MakeUint128(key.high_bits(), key.low_bits()));
   }
diff --git a/content/browser/attribution_reporting/aggregatable_attribution_utils.h b/content/browser/attribution_reporting/aggregatable_attribution_utils.h
index 730781e..05da152 100644
--- a/content/browser/attribution_reporting/aggregatable_attribution_utils.h
+++ b/content/browser/attribution_reporting/aggregatable_attribution_utils.h
@@ -12,14 +12,14 @@
 namespace content {
 
 class AggregatableHistogramContribution;
-class AttributionAggregatableSources;
+class AttributionAggregatableSource;
 class AttributionAggregatableTrigger;
 class AttributionFilterData;
 
 // Creates histograms from the specified source and trigger data.
 CONTENT_EXPORT std::vector<AggregatableHistogramContribution>
 CreateAggregatableHistogram(const AttributionFilterData& source_filter_data,
-                            const AttributionAggregatableSources& sources,
+                            const AttributionAggregatableSource& source,
                             const AttributionAggregatableTrigger& trigger);
 
 }  // namespace content
diff --git a/content/browser/attribution_reporting/aggregatable_attribution_utils_unittest.cc b/content/browser/attribution_reporting/aggregatable_attribution_utils_unittest.cc
index 7a75c7d..ac00c9f9 100644
--- a/content/browser/attribution_reporting/aggregatable_attribution_utils_unittest.cc
+++ b/content/browser/attribution_reporting/aggregatable_attribution_utils_unittest.cc
@@ -9,7 +9,7 @@
 #include <vector>
 
 #include "content/browser/attribution_reporting/aggregatable_histogram_contribution.h"
-#include "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_aggregatable_trigger.h"
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/browser/attribution_reporting/attribution_reporting.pb.h"
@@ -29,8 +29,8 @@
 }  // namespace
 
 TEST(AggregatableAttributionUtilsTest, CreateAggregatableHistogram) {
-  proto::AttributionAggregatableSources sources_proto =
-      AggregatableSourcesProtoBuilder()
+  proto::AttributionAggregatableSource source_proto =
+      AggregatableSourceProtoBuilder()
           .AddKey("key1", AggregatableKeyProtoBuilder()
                               .SetHighBits(0)
                               .SetLowBits(345)
@@ -97,16 +97,16 @@
       AttributionFilterData::FromSourceFilterValues({{"filter", {"value"}}});
   ASSERT_TRUE(source_filter_data.has_value());
 
-  absl::optional<AttributionAggregatableSources> sources =
-      AttributionAggregatableSources::Create(std::move(sources_proto));
-  ASSERT_TRUE(sources.has_value());
+  absl::optional<AttributionAggregatableSource> source =
+      AttributionAggregatableSource::Create(std::move(source_proto));
+  ASSERT_TRUE(source.has_value());
 
   absl::optional<AttributionAggregatableTrigger> trigger =
       AttributionAggregatableTrigger::FromMojo(std::move(trigger_mojo));
   ASSERT_TRUE(trigger.has_value());
 
   std::vector<AggregatableHistogramContribution> contributions =
-      CreateAggregatableHistogram(*source_filter_data, *sources, *trigger);
+      CreateAggregatableHistogram(*source_filter_data, *source, *trigger);
 
   // "key3" is not present as no value is found.
   EXPECT_THAT(
diff --git a/content/browser/attribution_reporting/attribution_aggregatable_source.cc b/content/browser/attribution_reporting/attribution_aggregatable_source.cc
new file mode 100644
index 0000000..8d4e251
--- /dev/null
+++ b/content/browser/attribution_reporting/attribution_aggregatable_source.cc
@@ -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.
+
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
+
+#include <utility>
+
+#include "base/ranges/algorithm.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/common/attribution_reporting/constants.h"
+
+namespace content {
+
+// static
+absl::optional<AttributionAggregatableSource>
+AttributionAggregatableSource::Create(
+    proto::AttributionAggregatableSource proto) {
+  bool is_valid =
+      proto.keys().size() <=
+          blink::kMaxAttributionAggregatableKeysPerSourceOrTrigger &&
+      base::ranges::all_of(proto.keys(), [](const auto& key) {
+        return key.first.size() <=
+                   blink::kMaxBytesPerAttributionAggregatableKeyId &&
+               key.second.has_high_bits() && key.second.has_low_bits();
+      });
+  return is_valid ? absl::make_optional(
+                        AttributionAggregatableSource(std::move(proto)))
+                  : absl::nullopt;
+}
+
+AttributionAggregatableSource::AttributionAggregatableSource(
+    proto::AttributionAggregatableSource proto)
+    : proto_(std::move(proto)) {}
+
+AttributionAggregatableSource::AttributionAggregatableSource() = default;
+
+AttributionAggregatableSource::~AttributionAggregatableSource() = default;
+
+AttributionAggregatableSource::AttributionAggregatableSource(
+    const AttributionAggregatableSource&) = default;
+
+AttributionAggregatableSource::AttributionAggregatableSource(
+    AttributionAggregatableSource&&) = default;
+
+AttributionAggregatableSource& AttributionAggregatableSource::operator=(
+    const AttributionAggregatableSource&) = default;
+
+AttributionAggregatableSource& AttributionAggregatableSource::operator=(
+    AttributionAggregatableSource&&) = default;
+
+}  // namespace content
diff --git a/content/browser/attribution_reporting/attribution_aggregatable_source.h b/content/browser/attribution_reporting/attribution_aggregatable_source.h
new file mode 100644
index 0000000..3336f895
--- /dev/null
+++ b/content/browser/attribution_reporting/attribution_aggregatable_source.h
@@ -0,0 +1,42 @@
+// 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 CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_AGGREGATABLE_SOURCE_H_
+#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_AGGREGATABLE_SOURCE_H_
+
+#include "content/browser/attribution_reporting/attribution_reporting.pb.h"
+#include "content/common/content_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace content {
+
+// This is a wrapper of `proto::AttributionAggregatableSource`.
+class CONTENT_EXPORT AttributionAggregatableSource {
+ public:
+  // Returns `absl::nullopt` if `proto` is invalid.
+  static absl::optional<AttributionAggregatableSource> Create(
+      proto::AttributionAggregatableSource proto);
+
+  AttributionAggregatableSource();
+  ~AttributionAggregatableSource();
+
+  AttributionAggregatableSource(const AttributionAggregatableSource&);
+  AttributionAggregatableSource(AttributionAggregatableSource&&);
+
+  AttributionAggregatableSource& operator=(
+      const AttributionAggregatableSource&);
+  AttributionAggregatableSource& operator=(AttributionAggregatableSource&&);
+
+  const proto::AttributionAggregatableSource& proto() const { return proto_; }
+
+ private:
+  explicit AttributionAggregatableSource(
+      proto::AttributionAggregatableSource proto);
+
+  proto::AttributionAggregatableSource proto_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_AGGREGATABLE_SOURCE_H_
diff --git a/content/browser/attribution_reporting/attribution_aggregatable_sources.cc b/content/browser/attribution_reporting/attribution_aggregatable_sources.cc
deleted file mode 100644
index fcc3930..0000000
--- a/content/browser/attribution_reporting/attribution_aggregatable_sources.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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 "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
-
-#include <utility>
-
-#include "base/ranges/algorithm.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/attribution_reporting/constants.h"
-
-namespace content {
-
-// static
-absl::optional<AttributionAggregatableSources>
-AttributionAggregatableSources::Create(
-    proto::AttributionAggregatableSources proto) {
-  bool is_valid =
-      proto.sources().size() <=
-          blink::kMaxAttributionAggregatableKeysPerSourceOrTrigger &&
-      base::ranges::all_of(proto.sources(), [](const auto& source) {
-        return source.first.size() <=
-                   blink::kMaxBytesPerAttributionAggregatableKeyId &&
-               source.second.has_high_bits() && source.second.has_low_bits();
-      });
-  return is_valid ? absl::make_optional(
-                        AttributionAggregatableSources(std::move(proto)))
-                  : absl::nullopt;
-}
-
-AttributionAggregatableSources::AttributionAggregatableSources(
-    proto::AttributionAggregatableSources proto)
-    : proto_(std::move(proto)) {}
-
-AttributionAggregatableSources::AttributionAggregatableSources() = default;
-
-AttributionAggregatableSources::~AttributionAggregatableSources() = default;
-
-AttributionAggregatableSources::AttributionAggregatableSources(
-    const AttributionAggregatableSources&) = default;
-
-AttributionAggregatableSources::AttributionAggregatableSources(
-    AttributionAggregatableSources&&) = default;
-
-AttributionAggregatableSources& AttributionAggregatableSources::operator=(
-    const AttributionAggregatableSources&) = default;
-
-AttributionAggregatableSources& AttributionAggregatableSources::operator=(
-    AttributionAggregatableSources&&) = default;
-
-}  // namespace content
diff --git a/content/browser/attribution_reporting/attribution_aggregatable_sources.h b/content/browser/attribution_reporting/attribution_aggregatable_sources.h
deleted file mode 100644
index 0c1005e..0000000
--- a/content/browser/attribution_reporting/attribution_aggregatable_sources.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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 CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_AGGREGATABLE_SOURCES_H_
-#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_AGGREGATABLE_SOURCES_H_
-
-#include "content/browser/attribution_reporting/attribution_reporting.pb.h"
-#include "content/common/content_export.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-
-namespace content {
-
-// This is a wrapper of `proto::AttributionAggregatableSources`.
-class CONTENT_EXPORT AttributionAggregatableSources {
- public:
-  // Returns `absl::nullopt` if `proto` is invalid.
-  static absl::optional<AttributionAggregatableSources> Create(
-      proto::AttributionAggregatableSources proto);
-
-  AttributionAggregatableSources();
-  ~AttributionAggregatableSources();
-
-  AttributionAggregatableSources(const AttributionAggregatableSources&);
-  AttributionAggregatableSources(AttributionAggregatableSources&&);
-
-  AttributionAggregatableSources& operator=(
-      const AttributionAggregatableSources&);
-  AttributionAggregatableSources& operator=(AttributionAggregatableSources&&);
-
-  const proto::AttributionAggregatableSources& proto() const { return proto_; }
-
- private:
-  explicit AttributionAggregatableSources(
-      proto::AttributionAggregatableSources proto);
-
-  proto::AttributionAggregatableSources proto_;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_AGGREGATABLE_SOURCES_H_
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
index ad5669a58e..06b6f1a 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
@@ -10,7 +10,7 @@
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/time/time.h"
-#include "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/browser/attribution_reporting/attribution_host_utils.h"
 #include "content/browser/attribution_reporting/attribution_manager.h"
@@ -32,17 +32,17 @@
 
 namespace {
 
-proto::AttributionAggregatableSources ConvertToProto(
-    const blink::mojom::AttributionAggregatableSources& aggregatable_sources) {
-  proto::AttributionAggregatableSources result;
+proto::AttributionAggregatableSource ConvertToProto(
+    const blink::mojom::AttributionAggregatableSource& aggregatable_source) {
+  proto::AttributionAggregatableSource result;
 
-  for (const auto& [key_id, key_ptr] : aggregatable_sources.sources) {
+  for (const auto& [key_id, key_ptr] : aggregatable_source.keys) {
     DCHECK(key_ptr);
     proto::AttributionAggregatableKey key;
     key.set_high_bits(key_ptr->high_bits);
     key.set_low_bits(key_ptr->low_bits);
 
-    (*result.mutable_sources())[key_id] = std::move(key);
+    (*result.mutable_keys())[key_id] = std::move(key);
   }
 
   return result;
@@ -117,10 +117,10 @@
   if (!filter_data.has_value())
     return;
 
-  absl::optional<AttributionAggregatableSources> aggregatable_sources =
-      AttributionAggregatableSources::Create(
-          ConvertToProto(*data->aggregatable_sources));
-  if (!aggregatable_sources.has_value())
+  absl::optional<AttributionAggregatableSource> aggregatable_source =
+      AttributionAggregatableSource::Create(
+          ConvertToProto(*data->aggregatable_source));
+  if (!aggregatable_source.has_value())
     return;
 
   StorableSource storable_source(CommonSourceInfo(
@@ -131,7 +131,7 @@
       context.source_type, data->priority, std::move(*filter_data),
       data->debug_key ? absl::make_optional(data->debug_key->value)
                       : absl::nullopt,
-      std::move(*aggregatable_sources)));
+      std::move(*aggregatable_source)));
 
   attribution_manager_->HandleSource(std::move(storable_source));
 }
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc b/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc
index e2fe187..51e88d27 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc
@@ -14,7 +14,7 @@
 #include "base/containers/flat_map.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_manager.h"
 #include "content/browser/attribution_reporting/attribution_source_type.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
@@ -80,8 +80,8 @@
                   SourceEventIdIs(10), ConversionOriginIs(destination_origin),
                   ImpressionOriginIs(page_origin), SourcePriorityIs(20),
                   SourceDebugKeyIs(789),
-                  AggregatableSourcesAre(AttributionAggregatableSources::Create(
-                      AggregatableSourcesProtoBuilder()
+                  AggregatableSourceAre(AttributionAggregatableSource::Create(
+                      AggregatableSourceProtoBuilder()
                           .AddKey("key", AggregatableKeyProtoBuilder()
                                              .SetHighBits(5)
                                              .SetLowBits(345)
@@ -99,8 +99,8 @@
   source_data->priority = 20;
   source_data->debug_key = blink::mojom::AttributionDebugKey::New(789);
   source_data->filter_data = blink::mojom::AttributionFilterData::New();
-  source_data->aggregatable_sources =
-      AggregatableSourcesMojoBuilder()
+  source_data->aggregatable_source =
+      AggregatableSourceMojoBuilder()
           .AddKey(/*key_id=*/"key",
                   blink::mojom::AttributionAggregatableKey::New(
                       /*high_bits=*/5, /*low_bits=*/345))
@@ -162,8 +162,8 @@
     source_data->reporting_origin =
         url::Origin::Create(GURL(test_case.reporting_origin));
     source_data->filter_data = blink::mojom::AttributionFilterData::New();
-    source_data->aggregatable_sources =
-        blink::mojom::AttributionAggregatableSources::New();
+    source_data->aggregatable_source =
+        blink::mojom::AttributionAggregatableSource::New();
     data_host_remote->SourceDataAvailable(std::move(source_data));
     data_host_remote.FlushForTesting();
 
@@ -189,8 +189,8 @@
         url::Origin::Create(GURL("https://reporter.example"));
     source_data->filter_data =
         blink::mojom::AttributionFilterData::New(test_case.AsMap());
-    source_data->aggregatable_sources =
-        blink::mojom::AttributionAggregatableSources::New();
+    source_data->aggregatable_source =
+        blink::mojom::AttributionAggregatableSource::New();
     data_host_remote->SourceDataAvailable(std::move(source_data));
     data_host_remote.FlushForTesting();
 
@@ -233,8 +233,8 @@
         url::Origin::Create(GURL("https://reporter.example"));
     source_data->filter_data =
         blink::mojom::AttributionFilterData::New(test_case.filter_data);
-    source_data->aggregatable_sources =
-        blink::mojom::AttributionAggregatableSources::New();
+    source_data->aggregatable_source =
+        blink::mojom::AttributionAggregatableSource::New();
     data_host_remote->SourceDataAvailable(std::move(source_data));
     data_host_remote.FlushForTesting();
 
@@ -270,8 +270,8 @@
   source_data->destination = destination_origin;
   source_data->reporting_origin = reporting_origin;
   source_data->filter_data = blink::mojom::AttributionFilterData::New();
-  source_data->aggregatable_sources =
-      blink::mojom::AttributionAggregatableSources::New();
+  source_data->aggregatable_source =
+      blink::mojom::AttributionAggregatableSource::New();
   data_host_remote->SourceDataAvailable(std::move(source_data));
   data_host_remote.FlushForTesting();
 
@@ -307,8 +307,8 @@
   source_data->destination = destination_origin;
   source_data->reporting_origin = reporting_origin;
   source_data->filter_data = blink::mojom::AttributionFilterData::New();
-  source_data->aggregatable_sources =
-      blink::mojom::AttributionAggregatableSources::New();
+  source_data->aggregatable_source =
+      blink::mojom::AttributionAggregatableSource::New();
   data_host_remote->SourceDataAvailable(source_data.Clone());
   data_host_remote.FlushForTesting();
 
@@ -330,16 +330,16 @@
 }
 
 TEST_F(AttributionDataHostManagerImplTest,
-       SourceDataHost_AggregatableSourcesSizeCheckPerformed) {
-  struct AggregatableSourcesSizeTestCase {
+       SourceDataHost_AggregatableSourceizeCheckPerformed) {
+  struct AggregatableSourceizeTestCase {
     const char* description;
     bool valid;
     size_t key_count;
     size_t key_size;
 
-    blink::mojom::AttributionAggregatableSourcesPtr GetAggregatableSources()
+    blink::mojom::AttributionAggregatableSourcePtr GetAggregatableSource()
         const {
-      AggregatableSourcesMojoBuilder builder;
+      AggregatableSourceMojoBuilder builder;
       for (size_t i = 0u; i < key_count; ++i) {
         std::string key(key_size, 'A' + i);
         builder.AddKey(std::move(key),
@@ -350,7 +350,7 @@
     }
   };
 
-  const AggregatableSourcesSizeTestCase kTestCases[] = {
+  const AggregatableSourceizeTestCase kTestCases[] = {
       {"empty", true, 0, 0},
       {"max_keys", true,
        blink::kMaxAttributionAggregatableKeysPerSourceOrTrigger, 1},
@@ -378,7 +378,7 @@
     source_data->reporting_origin =
         url::Origin::Create(GURL("https://reporter.example"));
     source_data->filter_data = blink::mojom::AttributionFilterData::New();
-    source_data->aggregatable_sources = test_case.GetAggregatableSources();
+    source_data->aggregatable_source = test_case.GetAggregatableSource();
     data_host_remote->SourceDataAvailable(std::move(source_data));
     data_host_remote.FlushForTesting();
 
@@ -728,8 +728,8 @@
   source_data->destination = destination_origin;
   source_data->reporting_origin = reporting_origin;
   source_data->filter_data = blink::mojom::AttributionFilterData::New();
-  source_data->aggregatable_sources =
-      blink::mojom::AttributionAggregatableSources::New();
+  source_data->aggregatable_source =
+      blink::mojom::AttributionAggregatableSource::New();
 
   data_host_remote->SourceDataAvailable(std::move(source_data));
   data_host_remote.FlushForTesting();
@@ -768,8 +768,8 @@
   source_data->destination = destination_origin;
   source_data->reporting_origin = reporting_origin;
   source_data->filter_data = blink::mojom::AttributionFilterData::New();
-  source_data->aggregatable_sources =
-      blink::mojom::AttributionAggregatableSources::New();
+  source_data->aggregatable_source =
+      blink::mojom::AttributionAggregatableSource::New();
 
   data_host_remote->SourceDataAvailable(source_data.Clone());
   data_host_remote.FlushForTesting();
diff --git a/content/browser/attribution_reporting/attribution_host_utils.cc b/content/browser/attribution_reporting/attribution_host_utils.cc
index b829196..c61a7ba 100644
--- a/content/browser/attribution_reporting/attribution_host_utils.cc
+++ b/content/browser/attribution_reporting/attribution_host_utils.cc
@@ -9,7 +9,7 @@
 
 #include "base/strings/string_number_conversions.h"
 #include "base/time/time.h"
-#include "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/browser/attribution_reporting/attribution_manager.h"
 #include "content/browser/attribution_reporting/common_source_info.h"
@@ -68,7 +68,7 @@
           CommonSourceInfo::GetExpiryTime(impression.expiry, impression_time,
                                           source_type),
           source_type, impression.priority, AttributionFilterData(),
-          /*debug_key=*/absl::nullopt, AttributionAggregatableSources()));
+          /*debug_key=*/absl::nullopt, AttributionAggregatableSource()));
 
   // TODO(apaseltiner): It would be nice to be able to report an issue in
   // DevTools in the event that a debug key is present but the corresponding
diff --git a/content/browser/attribution_reporting/attribution_reporting.proto b/content/browser/attribution_reporting/attribution_reporting.proto
index 1a74fd5..93dbd76 100644
--- a/content/browser/attribution_reporting/attribution_reporting.proto
+++ b/content/browser/attribution_reporting/attribution_reporting.proto
@@ -14,9 +14,9 @@
   optional uint64 low_bits = 2;
 }
 
-// Proto equivalent of `blink::mojom::AttributionAggregatableSources`.
-message AttributionAggregatableSources {
-  map<string, AttributionAggregatableKey> sources = 1;
+// Proto equivalent of `blink::mojom::AttributionAggregatableSource`.
+message AttributionAggregatableSource {
+  map<string, AttributionAggregatableKey> keys = 1;
 }
 
 message AttributionFilterValues {
diff --git a/content/browser/attribution_reporting/attribution_src_browsertest.cc b/content/browser/attribution_reporting/attribution_src_browsertest.cc
index 3a7cd68..4d89a6ab 100644
--- a/content/browser/attribution_reporting/attribution_src_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_src_browsertest.cc
@@ -152,7 +152,7 @@
   EXPECT_EQ(source_data.front()->expiry, absl::nullopt);
   EXPECT_FALSE(source_data.front()->debug_key);
   EXPECT_THAT(source_data.front()->filter_data->filter_values, IsEmpty());
-  EXPECT_THAT(source_data.front()->aggregatable_sources->sources, IsEmpty());
+  EXPECT_THAT(source_data.front()->aggregatable_source->keys, IsEmpty());
 }
 
 // Ensure that basic source registration works with both the img attributionsrc
@@ -205,7 +205,7 @@
 
 IN_PROC_BROWSER_TEST_F(
     AttributionSrcBrowserTest,
-    AttributionSrcImg_SourceRegisteredWithAggregatableSources) {
+    AttributionSrcImg_SourceRegisteredWithAggregatableSource) {
   GURL page_url =
       https_server()->GetURL("b.test", "/page_with_impression_creator.html");
   EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
@@ -238,7 +238,7 @@
   EXPECT_EQ(source_data.front()->expiry, absl::nullopt);
   EXPECT_FALSE(source_data.front()->debug_key);
   EXPECT_THAT(
-      source_data.front()->aggregatable_sources->sources,
+      source_data.front()->aggregatable_source->keys,
       UnorderedElementsAre(
           Pair("key1",
                Pointee(AllOf(
@@ -683,7 +683,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
-                       AttributionSrcImg_InvalidAggregatableSourcesDropped) {
+                       AttributionSrcImg_InvalidAggregatableSourceDropped) {
   // Create a separate server as we cannot register a `ControllableHttpResponse`
   // after the server starts.
   auto https_server = std::make_unique<net::EmbeddedTestServer>(
@@ -741,7 +741,7 @@
   EXPECT_EQ(source_data.back()->source_event_id, 5UL);
   EXPECT_EQ(source_data.back()->destination,
             url::Origin::Create(GURL("https://d.test")));
-  EXPECT_THAT(source_data.back()->aggregatable_sources->sources, SizeIs(2));
+  EXPECT_THAT(source_data.back()->aggregatable_source->keys, SizeIs(2));
 }
 
 class AttributionSrcPrerenderBrowserTest : public AttributionSrcBrowserTest {
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index 98a7d5eb..ea5155c 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -25,7 +25,7 @@
 #include "base/ranges/algorithm.h"
 #include "base/time/time.h"
 #include "content/browser/attribution_reporting/aggregatable_histogram_contribution.h"
-#include "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/browser/attribution_reporting/attribution_info.h"
 #include "content/browser/attribution_reporting/attribution_observer_types.h"
@@ -54,18 +54,18 @@
 namespace content {
 
 // Version number of the database.
-const int AttributionStorageSql::kCurrentVersionNumber = 31;
+const int AttributionStorageSql::kCurrentVersionNumber = 32;
 
 // Earliest version which can use a |kCurrentVersionNumber| database
 // without failing.
-const int AttributionStorageSql::kCompatibleVersionNumber = 31;
+const int AttributionStorageSql::kCompatibleVersionNumber = 32;
 
 // Latest version of the database that cannot be upgraded to
 // |kCurrentVersionNumber| without razing the database.
 //
 // Note that all versions >=15 were introduced during the transitional state of
 // the Attribution Reporting API and can be removed when done.
-const int AttributionStorageSql::kDeprecatedVersionNumber = 30;
+const int AttributionStorageSql::kDeprecatedVersionNumber = 31;
 
 namespace {
 
@@ -117,7 +117,7 @@
   prefix "debug_key," \
   prefix "num_conversions," \
   prefix "aggregatable_budget_consumed," \
-  prefix "aggregatable_sources," \
+  prefix "aggregatable_source," \
   prefix "filter_data," \
   prefix "event_level_active," \
   prefix "aggregatable_active"
@@ -222,14 +222,13 @@
                    DeserializeUint64(statement.ColumnInt64(col)));
 }
 
-absl::optional<AttributionAggregatableSources> ParseAggregatableSources(
+absl::optional<AttributionAggregatableSource> ParseAggregatableSource(
     const std::string& str) {
-  proto::AttributionAggregatableSources aggregatable_sources;
-  if (!aggregatable_sources.ParseFromString(str))
+  proto::AttributionAggregatableSource aggregatable_source;
+  if (!aggregatable_source.ParseFromString(str))
     return absl::nullopt;
 
-  return AttributionAggregatableSources::Create(
-      std::move(aggregatable_sources));
+  return AttributionAggregatableSource::Create(std::move(aggregatable_source));
 }
 
 struct StoredSourceData {
@@ -266,12 +265,12 @@
   absl::optional<uint64_t> debug_key = ColumnUint64OrNull(statement, col++);
   int num_conversions = statement.ColumnInt(col++);
   int64_t aggregatable_budget_consumed = statement.ColumnInt64(col++);
-  absl::optional<AttributionAggregatableSources> aggregatable_sources =
-      ParseAggregatableSources(statement.ColumnString(col++));
+  absl::optional<AttributionAggregatableSource> aggregatable_source =
+      ParseAggregatableSource(statement.ColumnString(col++));
 
   if (!source_type.has_value() || !attribution_logic.has_value() ||
       num_conversions < 0 || aggregatable_budget_consumed < 0 ||
-      !aggregatable_sources.has_value()) {
+      !aggregatable_source.has_value()) {
     return absl::nullopt;
   }
 
@@ -295,7 +294,7 @@
                            std::move(reporting_origin), impression_time,
                            expiry_time, *source_type, priority,
                            std::move(*filter_data), debug_key,
-                           std::move(*aggregatable_sources)),
+                           std::move(*aggregatable_source)),
           *attribution_logic, *active_state, source_id),
       .num_conversions = num_conversions,
       .aggregatable_budget_consumed = aggregatable_budget_consumed};
@@ -519,7 +518,7 @@
       "reporting_origin,impression_time,expiry_time,source_type,"
       "attributed_truthfully,priority,impression_site,"
       "num_conversions,event_level_active,aggregatable_active,debug_key,"
-      "aggregatable_budget_consumed,aggregatable_sources,filter_data)"
+      "aggregatable_budget_consumed,aggregatable_source,filter_data)"
       "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0,?,?)";
   sql::Statement statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kInsertImpressionSql));
@@ -546,7 +545,7 @@
   DCHECK(active_state.has_value());
 
   statement.BindBlob(
-      15, common_info.aggregatable_sources().proto().SerializeAsString());
+      15, common_info.aggregatable_source().proto().SerializeAsString());
   statement.BindBlob(16, common_info.filter_data().Serialize());
 
   if (!statement.Run())
@@ -1926,7 +1925,7 @@
       "impression_site TEXT NOT NULL,"
       "debug_key INTEGER,"
       "aggregatable_budget_consumed INTEGER NOT NULL,"
-      "aggregatable_sources BLOB NOT NULL,"
+      "aggregatable_source BLOB NOT NULL,"
       "filter_data BLOB NOT NULL)";
   if (!db_->Execute(kImpressionTableSql))
     return false;
diff --git a/content/browser/attribution_reporting/attribution_storage_unittest.cc b/content/browser/attribution_reporting/attribution_storage_unittest.cc
index 6d052b5..2b5810c4 100644
--- a/content/browser/attribution_reporting/attribution_storage_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_unittest.cc
@@ -26,7 +26,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "content/browser/attribution_reporting/aggregatable_histogram_contribution.h"
-#include "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/browser/attribution_reporting/attribution_observer_types.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
@@ -1812,21 +1812,21 @@
                                 TriggerDebugKeyIs(33))));
 }
 
-TEST_F(AttributionStorageTest, AttributionAggregatableSources_RoundTrips) {
-  proto::AttributionAggregatableSources proto =
-      AggregatableSourcesProtoBuilder()
+TEST_F(AttributionStorageTest, AttributionAggregatableSource_RoundTrips) {
+  proto::AttributionAggregatableSource proto =
+      AggregatableSourceProtoBuilder()
           .AddKey("key", AggregatableKeyProtoBuilder()
                              .SetHighBits(5)
                              .SetLowBits(345)
                              .Build())
           .Build();
-  absl::optional<AttributionAggregatableSources> aggregatable_sources =
-      AttributionAggregatableSources::Create(std::move(proto));
-  EXPECT_TRUE(aggregatable_sources.has_value());
+  absl::optional<AttributionAggregatableSource> aggregatable_source =
+      AttributionAggregatableSource::Create(std::move(proto));
+  EXPECT_TRUE(aggregatable_source.has_value());
   storage()->StoreSource(
-      SourceBuilder().SetAggregatableSources(*aggregatable_sources).Build());
+      SourceBuilder().SetAggregatableSource(*aggregatable_source).Build());
   EXPECT_THAT(storage()->GetActiveSources(),
-              ElementsAre(AggregatableSourcesAre(*aggregatable_sources)));
+              ElementsAre(AggregatableSourceAre(*aggregatable_source)));
 }
 
 TEST_F(AttributionStorageTest, MaybeCreateAndStoreReport_ReturnsNewReport) {
diff --git a/content/browser/attribution_reporting/attribution_test_utils.cc b/content/browser/attribution_reporting/attribution_test_utils.cc
index ad48e18..f4679f92 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.cc
+++ b/content/browser/attribution_reporting/attribution_test_utils.cc
@@ -467,9 +467,9 @@
   return *this;
 }
 
-SourceBuilder& SourceBuilder::SetAggregatableSources(
-    AttributionAggregatableSources aggregatable_sources) {
-  aggregatable_sources_ = std::move(aggregatable_sources);
+SourceBuilder& SourceBuilder::SetAggregatableSource(
+    AttributionAggregatableSource aggregatable_source) {
+  aggregatable_source_ = std::move(aggregatable_source);
   return *this;
 }
 
@@ -478,7 +478,7 @@
       source_event_id_, impression_origin_, conversion_origin_,
       reporting_origin_, impression_time_,
       /*expiry_time=*/impression_time_ + expiry_, source_type_, priority_,
-      filter_data_, debug_key_, aggregatable_sources_);
+      filter_data_, debug_key_, aggregatable_source_);
 }
 
 StorableSource SourceBuilder::Build() const {
@@ -654,37 +654,36 @@
   return key_;
 }
 
-AggregatableSourcesProtoBuilder::AggregatableSourcesProtoBuilder() = default;
+AggregatableSourceProtoBuilder::AggregatableSourceProtoBuilder() = default;
 
-AggregatableSourcesProtoBuilder::~AggregatableSourcesProtoBuilder() = default;
+AggregatableSourceProtoBuilder::~AggregatableSourceProtoBuilder() = default;
 
-AggregatableSourcesProtoBuilder& AggregatableSourcesProtoBuilder::AddKey(
+AggregatableSourceProtoBuilder& AggregatableSourceProtoBuilder::AddKey(
     std::string key_id,
     proto::AttributionAggregatableKey key) {
-  (*aggregatable_sources_.mutable_sources())[std::move(key_id)] =
-      std::move(key);
+  (*aggregatable_source_.mutable_keys())[std::move(key_id)] = std::move(key);
   return *this;
 }
 
-proto::AttributionAggregatableSources AggregatableSourcesProtoBuilder::Build()
+proto::AttributionAggregatableSource AggregatableSourceProtoBuilder::Build()
     const {
-  return aggregatable_sources_;
+  return aggregatable_source_;
 }
 
-AggregatableSourcesMojoBuilder::AggregatableSourcesMojoBuilder() = default;
+AggregatableSourceMojoBuilder::AggregatableSourceMojoBuilder() = default;
 
-AggregatableSourcesMojoBuilder::~AggregatableSourcesMojoBuilder() = default;
+AggregatableSourceMojoBuilder::~AggregatableSourceMojoBuilder() = default;
 
-AggregatableSourcesMojoBuilder& AggregatableSourcesMojoBuilder::AddKey(
+AggregatableSourceMojoBuilder& AggregatableSourceMojoBuilder::AddKey(
     std::string key_id,
     blink::mojom::AttributionAggregatableKeyPtr key) {
-  sources_.sources.emplace(std::move(key_id), std::move(key));
+  aggregatable_source_.keys.emplace(std::move(key_id), std::move(key));
   return *this;
 }
 
-blink::mojom::AttributionAggregatableSourcesPtr
-AggregatableSourcesMojoBuilder::Build() const {
-  return sources_.Clone();
+blink::mojom::AttributionAggregatableSourcePtr
+AggregatableSourceMojoBuilder::Build() const {
+  return aggregatable_source_.Clone();
 }
 
 bool operator==(const AttributionTrigger::EventTriggerData& a,
@@ -716,7 +715,7 @@
                            source.reporting_origin(), source.impression_time(),
                            source.expiry_time(), source.source_type(),
                            source.priority(), source.filter_data(),
-                           source.debug_key(), source.aggregatable_sources());
+                           source.debug_key(), source.aggregatable_source());
   };
   return tie(a) == tie(b);
 }
@@ -980,8 +979,7 @@
              << ",filter_data=" << source.filter_data() << ",debug_key="
              << (source.debug_key() ? base::NumberToString(*source.debug_key())
                                     : "null")
-             << ",aggregatable_sources=" << source.aggregatable_sources()
-             << "}";
+             << ",aggregatable_source=" << source.aggregatable_source() << "}";
 }
 
 std::ostream& operator<<(std::ostream& out,
@@ -1142,14 +1140,14 @@
   return tie(a) == tie(b);
 }
 
-bool operator==(const AttributionAggregatableSources& a,
-                const AttributionAggregatableSources& b) {
-  if (a.sources().size() != b.sources().size())
+bool operator==(const AttributionAggregatableSource& a,
+                const AttributionAggregatableSource& b) {
+  if (a.keys().size() != b.keys().size())
     return false;
 
-  return base::ranges::all_of(a.sources(), [&](const auto& source) {
-    auto iter = b.sources().find(source.first);
-    return iter != b.sources().end() && iter->second == source.second;
+  return base::ranges::all_of(a.keys(), [&](const auto& key) {
+    auto iter = b.keys().find(key.first);
+    return iter != b.keys().end() && iter->second == key.second;
   });
 }
 
@@ -1166,11 +1164,11 @@
 
 std::ostream& operator<<(
     std::ostream& out,
-    const AttributionAggregatableSources& aggregatable_sources) {
-  out << "{sources=[";
+    const AttributionAggregatableSource& aggregatable_source) {
+  out << "{keys=[";
 
   const char* separator = "";
-  for (const auto& [key_id, key] : aggregatable_sources.sources()) {
+  for (const auto& [key_id, key] : aggregatable_source.keys()) {
     out << separator << key_id << ":" << key;
     separator = ", ";
   }
@@ -1179,15 +1177,15 @@
 
 }  // namespace proto
 
-bool operator==(const AttributionAggregatableSources& a,
-                const AttributionAggregatableSources& b) {
+bool operator==(const AttributionAggregatableSource& a,
+                const AttributionAggregatableSource& b) {
   return a.proto() == b.proto();
 }
 
 std::ostream& operator<<(
     std::ostream& out,
-    const AttributionAggregatableSources& aggregatable_sources) {
-  return out << "{proto=" << aggregatable_sources.proto() << "}";
+    const AttributionAggregatableSource& aggregatable_source) {
+  return out << "{proto=" << aggregatable_source.proto() << "}";
 }
 
 EventTriggerDataMatcherConfig::~EventTriggerDataMatcherConfig() = default;
diff --git a/content/browser/attribution_reporting/attribution_test_utils.h b/content/browser/attribution_reporting/attribution_test_utils.h
index 743e013d..2911cf1 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.h
+++ b/content/browser/attribution_reporting/attribution_test_utils.h
@@ -21,7 +21,7 @@
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
 #include "content/browser/attribution_reporting/aggregatable_histogram_contribution.h"
-#include "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_data_host_manager.h"
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/browser/attribution_reporting/attribution_host.h"
@@ -362,8 +362,8 @@
 
   SourceBuilder& SetDedupKeys(std::vector<uint64_t> dedup_keys);
 
-  SourceBuilder& SetAggregatableSources(
-      AttributionAggregatableSources aggregatable_sources);
+  SourceBuilder& SetAggregatableSource(
+      AttributionAggregatableSource aggregatable_source);
 
   StorableSource Build() const;
 
@@ -389,7 +389,7 @@
   // Ensure that we don't use uninitialized memory.
   StoredSource::Id source_id_{0};
   std::vector<uint64_t> dedup_keys_;
-  AttributionAggregatableSources aggregatable_sources_;
+  AttributionAggregatableSource aggregatable_source_;
 };
 
 // Returns a AttributionTrigger with default data which matches the default
@@ -508,38 +508,37 @@
   proto::AttributionAggregatableKey key_;
 };
 
-// Helper class to construct a `proto::AttributionAggregatableSources` for
+// Helper class to construct a `proto::AttributionAggregatableSource` for
 // testing.
-class AggregatableSourcesProtoBuilder {
+class AggregatableSourceProtoBuilder {
  public:
-  AggregatableSourcesProtoBuilder();
-  ~AggregatableSourcesProtoBuilder();
+  AggregatableSourceProtoBuilder();
+  ~AggregatableSourceProtoBuilder();
 
-  AggregatableSourcesProtoBuilder& AddKey(
-      std::string key_id,
-      proto::AttributionAggregatableKey key);
+  AggregatableSourceProtoBuilder& AddKey(std::string key_id,
+                                         proto::AttributionAggregatableKey key);
 
-  proto::AttributionAggregatableSources Build() const;
+  proto::AttributionAggregatableSource Build() const;
 
  private:
-  proto::AttributionAggregatableSources aggregatable_sources_;
+  proto::AttributionAggregatableSource aggregatable_source_;
 };
 
-// Helper class to construct a `blink::mojom::AttributionAggregatableSources`
+// Helper class to construct a `blink::mojom::AttributionAggregatableSource`
 // for testing.
-class AggregatableSourcesMojoBuilder {
+class AggregatableSourceMojoBuilder {
  public:
-  AggregatableSourcesMojoBuilder();
-  ~AggregatableSourcesMojoBuilder();
+  AggregatableSourceMojoBuilder();
+  ~AggregatableSourceMojoBuilder();
 
-  AggregatableSourcesMojoBuilder& AddKey(
+  AggregatableSourceMojoBuilder& AddKey(
       std::string key_id,
       blink::mojom::AttributionAggregatableKeyPtr key);
 
-  blink::mojom::AttributionAggregatableSourcesPtr Build() const;
+  blink::mojom::AttributionAggregatableSourcePtr Build() const;
 
  private:
-  blink::mojom::AttributionAggregatableSources sources_;
+  blink::mojom::AttributionAggregatableSource aggregatable_source_;
 };
 
 bool operator==(const AttributionTrigger::EventTriggerData& a,
@@ -636,12 +635,12 @@
 
 std::ostream& operator<<(std::ostream& out, StorableSource::Result status);
 
-bool operator==(const AttributionAggregatableSources& a,
-                const AttributionAggregatableSources& b);
+bool operator==(const AttributionAggregatableSource& a,
+                const AttributionAggregatableSource& b);
 
 std::ostream& operator<<(
     std::ostream& out,
-    const AttributionAggregatableSources& aggregatable_sources);
+    const AttributionAggregatableSource& aggregatable_source);
 
 std::vector<AttributionReport> GetAttributionReportsForTesting(
     AttributionManagerImpl* manager,
@@ -697,8 +696,8 @@
   return ExplainMatchResult(matcher, arg.dedup_keys(), result_listener);
 }
 
-MATCHER_P(AggregatableSourcesAre, matcher, "") {
-  return ExplainMatchResult(matcher, arg.common_info().aggregatable_sources(),
+MATCHER_P(AggregatableSourceAre, matcher, "") {
+  return ExplainMatchResult(matcher, arg.common_info().aggregatable_source(),
                             result_listener);
 }
 
diff --git a/content/browser/attribution_reporting/common_source_info.cc b/content/browser/attribution_reporting/common_source_info.cc
index f01f42d..d5f32b05 100644
--- a/content/browser/attribution_reporting/common_source_info.cc
+++ b/content/browser/attribution_reporting/common_source_info.cc
@@ -43,7 +43,7 @@
     int64_t priority,
     AttributionFilterData filter_data,
     absl::optional<uint64_t> debug_key,
-    AttributionAggregatableSources aggregatable_sources)
+    AttributionAggregatableSource aggregatable_source)
     : source_event_id_(source_event_id),
       impression_origin_(std::move(impression_origin)),
       conversion_origin_(std::move(conversion_origin)),
@@ -54,7 +54,7 @@
       priority_(priority),
       filter_data_(std::move(filter_data)),
       debug_key_(debug_key),
-      aggregatable_sources_(std::move(aggregatable_sources)) {
+      aggregatable_source_(std::move(aggregatable_source)) {
   // 30 days is the max allowed expiry for an impression.
   DCHECK_GE(base::Days(30), expiry_time - impression_time);
   // The impression must expire strictly after it occurred.
diff --git a/content/browser/attribution_reporting/common_source_info.h b/content/browser/attribution_reporting/common_source_info.h
index fafb82c..8b797c07 100644
--- a/content/browser/attribution_reporting/common_source_info.h
+++ b/content/browser/attribution_reporting/common_source_info.h
@@ -8,7 +8,7 @@
 #include <stdint.h>
 
 #include "base/time/time.h"
-#include "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/browser/attribution_reporting/attribution_source_type.h"
 #include "content/common/content_export.h"
@@ -39,7 +39,7 @@
                    int64_t priority,
                    AttributionFilterData filter_data,
                    absl::optional<uint64_t> debug_key,
-                   AttributionAggregatableSources aggregatable_sources);
+                   AttributionAggregatableSource aggregatable_source);
 
   ~CommonSourceInfo();
 
@@ -69,8 +69,8 @@
 
   absl::optional<uint64_t> debug_key() const { return debug_key_; }
 
-  const AttributionAggregatableSources& aggregatable_sources() const {
-    return aggregatable_sources_;
+  const AttributionAggregatableSource& aggregatable_source() const {
+    return aggregatable_source_;
   }
 
   void ClearDebugKey() { debug_key_ = absl::nullopt; }
@@ -98,7 +98,7 @@
   int64_t priority_;
   AttributionFilterData filter_data_;
   absl::optional<uint64_t> debug_key_;
-  AttributionAggregatableSources aggregatable_sources_;
+  AttributionAggregatableSource aggregatable_source_;
 
   // When adding new members, the corresponding `operator==()` definition in
   // `attribution_test_utils.h` should also be updated.
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index b83fe4e..3a156c59 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -293,6 +293,8 @@
   BIBI_BIND_GAMEPAD_HAPTICS_MANAGER_FOR_FENCED_FRAME = 266,
   BIBI_BIND_BATTERY_MONITOR_FOR_FENCED_FRAME = 267,
   RFH_CREATE_FENCED_FRAME_IN_SANDBOXED_FRAME = 268,
+  RFH_UNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME = 269,
+  RFH_BEFOREUNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME = 270,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/cache_storage/cache_storage_context_unittest.cc b/content/browser/cache_storage/cache_storage_context_unittest.cc
index 562bd191..7f41427 100644
--- a/content/browser/cache_storage/cache_storage_context_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_context_unittest.cc
@@ -133,75 +133,4 @@
   EXPECT_GT(result->id.value(), 0);
 }
 
-TEST_F(CacheStorageContextTest, GetDefaultBucketError) {
-  // Disable database so it will return errors when getting the default bucket.
-  quota_manager_->SetDisableDatabase(true);
-
-  mojo::Remote<blink::mojom::CacheStorage> example_remote;
-  AddReceiver(
-      example_remote.BindNewPipeAndPassReceiver(),
-      blink::StorageKey::CreateFromStringForTesting(kExampleStorageKey));
-
-  storage::QuotaManagerProxySync quota_manager_proxy_sync(
-      quota_manager_proxy());
-
-  // CacheStorage::Has
-  base::RunLoop loop_1;
-  example_remote->Has(
-      u"cache_name", /*trace_id=*/0,
-      base::BindLambdaForTesting([&](blink::mojom::CacheStorageError error) {
-        EXPECT_EQ(error, blink::mojom::CacheStorageError::kErrorStorage);
-        loop_1.Quit();
-      }));
-  loop_1.Run();
-
-  // CacheStorage::Delete
-  base::RunLoop loop_2;
-  example_remote->Delete(
-      u"cache_name", /*trace_id=*/0,
-      base::BindLambdaForTesting([&](blink::mojom::CacheStorageError error) {
-        EXPECT_EQ(error, blink::mojom::CacheStorageError::kErrorStorage);
-        loop_2.Quit();
-      }));
-  loop_2.Run();
-
-  // CacheStorage::Keys
-  base::RunLoop loop_3;
-  example_remote->Keys(
-      /*trace_id=*/0,
-      base::BindLambdaForTesting([&](const std::vector<std::u16string>& keys) {
-        EXPECT_EQ(keys, std::vector<std::u16string>());
-        loop_3.Quit();
-      }));
-  loop_3.Run();
-
-  // CacheStorage::Match
-  auto options = blink::mojom::MultiCacheQueryOptions::New();
-  options->query_options = blink::mojom::CacheQueryOptions::New();
-  options->cache_name = u"cache_name";
-
-  base::RunLoop loop_4;
-  example_remote->Match(
-      blink::mojom::FetchAPIRequest::New(), std::move(options),
-      /*in_related_fetch_event=*/false, /*in_range_fetch_event=*/false,
-      /*trace_id=*/0,
-      base::BindLambdaForTesting([&](blink::mojom::MatchResultPtr result) {
-        EXPECT_EQ(result->get_status(),
-                  blink::mojom::CacheStorageError::kErrorStorage);
-        loop_4.Quit();
-      }));
-  loop_4.Run();
-
-  // CacheStorage::Open
-  base::RunLoop loop_5;
-  example_remote->Open(
-      u"cache_name", /*trace_id=*/0,
-      base::BindLambdaForTesting([&](blink::mojom::OpenResultPtr result) {
-        EXPECT_EQ(result->get_status(),
-                  blink::mojom::CacheStorageError::kErrorStorage);
-        loop_5.Quit();
-      }));
-  loop_5.Run();
-}
-
 }  // namespace content
diff --git a/content/browser/cache_storage/cache_storage_dispatcher_host.cc b/content/browser/cache_storage/cache_storage_dispatcher_host.cc
index 5aa0291..df28f37 100644
--- a/content/browser/cache_storage/cache_storage_dispatcher_host.cc
+++ b/content/browser/cache_storage/cache_storage_dispatcher_host.cc
@@ -675,12 +675,6 @@
         TRACE_ID_GLOBAL(trace_id),
         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
 
-    // Return error if failed to retrieve bucket from QuotaManager.
-    if (!bucket_.has_value()) {
-      std::move(callback).Run(std::vector<std::u16string>());
-      return;
-    }
-
     auto cb = base::BindOnce(
         [](base::TimeTicks start_time, int64_t trace_id,
            blink::mojom::CacheStorage::KeysCallback callback,
@@ -739,13 +733,6 @@
         },
         base::TimeTicks::Now(), trace_id, std::move(callback));
 
-    // Return error if failed to retrieve bucket from QuotaManager.
-    if (!bucket_.has_value()) {
-      std::move(cb).Run(
-          MakeErrorStorage(ErrorStorageType::kDefaultBucketError));
-      return;
-    }
-
     content::CacheStorage* cache_storage = GetOrCreateCacheStorage();
     if (!cache_storage) {
       std::move(cb).Run(MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
@@ -785,14 +772,6 @@
         },
         base::TimeTicks::Now(), trace_id, std::move(callback));
 
-    // Return error if failed to retrieve bucket from QuotaManager.
-    if (!bucket_.has_value()) {
-      std::move(cb).Run(
-          /*has_cache=*/false,
-          MakeErrorStorage(ErrorStorageType::kDefaultBucketError));
-      return;
-    }
-
     content::CacheStorage* cache_storage = GetOrCreateCacheStorage();
     if (!cache_storage) {
       std::move(cb).Run(/* has_cache = */ false,
@@ -879,13 +858,6 @@
         !match_options->cache_name, in_related_fetch_event,
         in_range_fetch_event, trace_id, std::move(callback));
 
-    // Return error if failed to retrieve bucket from QuotaManager.
-    if (!bucket_.has_value()) {
-      std::move(cb).Run(MakeErrorStorage(ErrorStorageType::kDefaultBucketError),
-                        nullptr);
-      return;
-    }
-
     content::CacheStorage* cache_storage = GetOrCreateCacheStorage();
     if (!cache_storage) {
       std::move(cb).Run(CacheStorageError::kErrorNotFound, nullptr);
@@ -965,14 +937,6 @@
         weak_factory_.GetWeakPtr(), base::TimeTicks::Now(), trace_id,
         std::move(callback));
 
-    // Return error if failed to retrieve bucket from QuotaManager.
-    if (!bucket_.has_value()) {
-      std::move(cb).Run(
-          CacheStorageCacheHandle(),
-          MakeErrorStorage(ErrorStorageType::kDefaultBucketError));
-      return;
-    }
-
     if (!cache_storage) {
       std::move(cb).Run(CacheStorageCacheHandle(),
                         MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
diff --git a/content/browser/database_browsertest.cc b/content/browser/database_browsertest.cc
index 698d9b6..abb23e1 100644
--- a/content/browser/database_browsertest.cc
+++ b/content/browser/database_browsertest.cc
@@ -6,8 +6,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/download_manager.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_types.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -159,9 +157,7 @@
   CreateTable(shell());
   InsertRecord(shell(), "text");
 
-  WindowedNotificationObserver load_stop_observer(
-      NOTIFICATION_LOAD_STOP,
-      NotificationService::AllSources());
+  LoadStopObserver load_stop_observer(shell()->web_contents());
   shell()->Reload();
   load_stop_observer.Wait();
 
diff --git a/content/browser/fenced_frame/fenced_frame_browsertest.cc b/content/browser/fenced_frame/fenced_frame_browsertest.cc
index bf46850..ea996a5 100644
--- a/content/browser/fenced_frame/fenced_frame_browsertest.cc
+++ b/content/browser/fenced_frame/fenced_frame_browsertest.cc
@@ -989,6 +989,55 @@
   }
 }
 
+// Tests that an unload/beforeunload event handler won't be set from
+// fenced frames.
+IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, UnloadHandler) {
+  ASSERT_TRUE(https_server()->Start());
+  const GURL main_url =
+      https_server()->GetURL("c.test", "/fenced_frames/title1.html");
+  EXPECT_TRUE(
+      NavigateToURL(shell(), https_server()->GetURL("c.test", "/title1.html")));
+  RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host());
+  RenderFrameHostImplWrapper fenced_frame_rfh(
+      fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(),
+                                                   main_url));
+
+  const char* kConsolePattern =
+      "unload/beforeunload handlers are prohibited in fenced frames.";
+  {
+    WebContentsConsoleObserver console_observer(web_contents());
+    console_observer.SetPattern(kConsolePattern);
+    EXPECT_TRUE(ExecJs(fenced_frame_rfh.get(),
+                       "window.addEventListener('beforeunload', (e) => {});"));
+    console_observer.Wait();
+    EXPECT_EQ(1u, console_observer.messages().size());
+  }
+  {
+    WebContentsConsoleObserver console_observer(web_contents());
+    console_observer.SetPattern(kConsolePattern);
+    EXPECT_TRUE(ExecJs(fenced_frame_rfh.get(),
+                       "window.addEventListener('unload', (e) => {});"));
+    console_observer.Wait();
+    EXPECT_EQ(1u, console_observer.messages().size());
+  }
+  {
+    WebContentsConsoleObserver console_observer(web_contents());
+    console_observer.SetPattern(kConsolePattern);
+    EXPECT_TRUE(ExecJs(fenced_frame_rfh.get(),
+                       "window.onbeforeunload = function(e){};"));
+    console_observer.Wait();
+    EXPECT_EQ(1u, console_observer.messages().size());
+  }
+  {
+    WebContentsConsoleObserver console_observer(web_contents());
+    console_observer.SetPattern(kConsolePattern);
+    EXPECT_TRUE(
+        ExecJs(fenced_frame_rfh.get(), "window.onunload = function(e){};"));
+    console_observer.Wait();
+    EXPECT_EQ(1u, console_observer.messages().size());
+  }
+}
+
 INSTANTIATE_TEST_SUITE_P(FencedFrameNestedFrameBrowserTest,
                          FencedFrameNestedFrameBrowserTest,
                          testing::Combine(testing::ValuesIn(kTestParameters),
diff --git a/content/browser/indexed_db/indexed_db_context_impl.cc b/content/browser/indexed_db/indexed_db_context_impl.cc
index dbdd7912..9472e76 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.cc
+++ b/content/browser/indexed_db/indexed_db_context_impl.cc
@@ -870,10 +870,8 @@
     const blink::StorageKey& storage_key,
     mojo::PendingReceiver<blink::mojom::IDBFactory> receiver,
     storage::QuotaErrorOr<storage::BucketInfo> result) {
-  absl::optional<storage::BucketLocator> bucket =
-      result.ok() ? absl::make_optional(result->ToBucketLocator())
-                  : absl::nullopt;
-  dispatcher_host_.AddReceiver(storage_key, bucket, std::move(receiver));
+  DCHECK(result.ok());
+  dispatcher_host_.AddReceiver(storage_key, std::move(receiver));
 }
 
 void IndexedDBContextImpl::ShutdownOnIDBSequence() {
diff --git a/content/browser/indexed_db/indexed_db_context_unittest.cc b/content/browser/indexed_db/indexed_db_context_unittest.cc
index 4a2cffb0..2bf7954 100644
--- a/content/browser/indexed_db/indexed_db_context_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_context_unittest.cc
@@ -2,13 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <memory>
-
 #include "base/barrier_closure.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/run_loop.h"
-#include "base/test/bind.h"
-#include "base/test/gmock_callback_support.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread.h"
 #include "base/time/default_clock.h"
@@ -18,14 +14,11 @@
 #include "content/browser/indexed_db/indexed_db_context_impl.h"
 #include "content/browser/indexed_db/indexed_db_factory_impl.h"
 #include "content/browser/indexed_db/mock_indexed_db_callbacks.h"
-#include "content/browser/indexed_db/mock_mojo_indexed_db_callbacks.h"
-#include "content/browser/indexed_db/mock_mojo_indexed_db_database_callbacks.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "storage/browser/test/mock_quota_manager.h"
 #include "storage/browser/test/mock_quota_manager_proxy.h"
 #include "storage/browser/test/mock_special_storage_policy.h"
 #include "storage/browser/test/quota_manager_proxy_sync.h"
-#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 
@@ -117,84 +110,4 @@
   EXPECT_GT(result->id.value(), 0);
 }
 
-TEST_F(IndexedDBContextTest, GetDefaultBucketError) {
-  // Disable database so it will return errors when getting the default bucket.
-  quota_manager_->SetDisableDatabase(true);
-
-  mojo::Remote<blink::mojom::IDBFactory> example_remote;
-  indexed_db_context_->BindIndexedDB(
-      example_storage_key_, example_remote.BindNewPipeAndPassReceiver());
-
-  // IDBFactory::GetDatabaseInfo
-  base::RunLoop loop_1;
-  auto mock_callbacks =
-      std::make_unique<testing::StrictMock<MockMojoIndexedDBCallbacks>>();
-  EXPECT_CALL(*mock_callbacks,
-              Error(blink::mojom::IDBException::kUnknownError,
-                    std::u16string(
-                        u"Internal error retrieving bucket data directory.")))
-      .Times(1)
-      .WillOnce(base::test::RunClosure(loop_1.QuitClosure()));
-
-  example_remote->GetDatabaseInfo(mock_callbacks->CreateInterfacePtrAndBind());
-  loop_1.Run();
-
-  testing::Mock::VerifyAndClear(&mock_callbacks);
-
-  // IDBFactory::Open
-  base::RunLoop loop_2;
-  mock_callbacks =
-      std::make_unique<testing::StrictMock<MockMojoIndexedDBCallbacks>>();
-  auto database_callbacks =
-      std::make_unique<MockMojoIndexedDBDatabaseCallbacks>();
-  auto transaction_remote =
-      mojo::AssociatedRemote<blink::mojom::IDBTransaction>();
-  EXPECT_CALL(*mock_callbacks,
-              Error(blink::mojom::IDBException::kUnknownError,
-                    std::u16string(
-                        u"Internal error retrieving bucket data directory.")))
-      .Times(1)
-      .WillOnce(base::test::RunClosure(loop_2.QuitClosure()));
-
-  example_remote->Open(mock_callbacks->CreateInterfacePtrAndBind(),
-                       database_callbacks->CreateInterfacePtrAndBind(),
-                       u"database_name", /*version=*/1,
-                       transaction_remote.BindNewEndpointAndPassReceiver(),
-                       /*transaction_id=*/0);
-  loop_2.Run();
-
-  // IDBFactory::DeleteDatabase
-  base::RunLoop loop_3;
-  mock_callbacks =
-      std::make_unique<testing::StrictMock<MockMojoIndexedDBCallbacks>>();
-  EXPECT_CALL(*mock_callbacks,
-              Error(blink::mojom::IDBException::kUnknownError,
-                    std::u16string(
-                        u"Internal error retrieving bucket data directory.")))
-      .Times(1)
-      .WillOnce(base::test::RunClosure(loop_3.QuitClosure()));
-
-  example_remote->DeleteDatabase(mock_callbacks->CreateInterfacePtrAndBind(),
-                                 u"database_name", /*force_close=*/true);
-  loop_3.Run();
-
-  // IDBFactory::AbortTransactionsAndCompactDatabase
-  base::RunLoop loop_4;
-  example_remote->AbortTransactionsAndCompactDatabase(
-      base::BindLambdaForTesting([&](blink::mojom::IDBStatus status) {
-        EXPECT_EQ(status, blink::mojom::IDBStatus::NotFound);
-        loop_4.Quit();
-      }));
-  loop_4.Run();
-
-  // IDBFactory::AbortTransactionsForDatabase
-  base::RunLoop loop_5;
-  example_remote->AbortTransactionsForDatabase(
-      base::BindLambdaForTesting([&](blink::mojom::IDBStatus status) {
-        EXPECT_EQ(status, blink::mojom::IDBStatus::NotFound);
-        loop_5.Quit();
-      }));
-  loop_5.Run();
-}
-
 }  // namespace content
diff --git a/content/browser/indexed_db/indexed_db_dispatcher_host.cc b/content/browser/indexed_db/indexed_db_dispatcher_host.cc
index 9cd295e..9d6cda0 100644
--- a/content/browser/indexed_db/indexed_db_dispatcher_host.cc
+++ b/content/browser/indexed_db/indexed_db_dispatcher_host.cc
@@ -214,13 +214,10 @@
 
 void IndexedDBDispatcherHost::AddReceiver(
     const blink::StorageKey& storage_key,
-    absl::optional<storage::BucketLocator> bucket,
     mojo::PendingReceiver<blink::mojom::IDBFactory> pending_receiver) {
   DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (bucket.has_value())
-    DCHECK_EQ(bucket->storage_key, storage_key);
-  receivers_.Add(this, std::move(pending_receiver), bucket);
+  receivers_.Add(this, std::move(pending_receiver), storage_key);
 }
 
 void IndexedDBDispatcherHost::AddDatabaseBinding(
@@ -277,23 +274,10 @@
         pending_callbacks) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Return error if failed to retrieve bucket from the QuotaManager.
-  if (!receivers_.current_context().has_value()) {
-    auto callbacks = base::MakeRefCounted<IndexedDBCallbacks>(
-        this->AsWeakPtr(), blink::StorageKey(), std::move(pending_callbacks),
-        IDBTaskRunner());
-    IndexedDBDatabaseError error = IndexedDBDatabaseError(
-        blink::mojom::IDBException::kUnknownError,
-        u"Internal error retrieving bucket data directory.");
-    callbacks->OnError(error);
-    return;
-  }
-
-  const auto storage_key = receivers_.current_context()->storage_key;
+  const auto& storage_key = receivers_.current_context();
   auto callbacks = base::MakeRefCounted<IndexedDBCallbacks>(
       this->AsWeakPtr(), storage_key, std::move(pending_callbacks),
       IDBTaskRunner());
-
   base::FilePath indexed_db_path = indexed_db_context_->data_path();
   indexed_db_context_->GetIDBFactory()->GetDatabaseInfo(
       std::move(callbacks), storage_key, indexed_db_path);
@@ -310,19 +294,7 @@
     int64_t transaction_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Return error if failed to retrieve bucket from the QuotaManager.
-  if (!receivers_.current_context().has_value()) {
-    auto callbacks = base::MakeRefCounted<IndexedDBCallbacks>(
-        this->AsWeakPtr(), blink::StorageKey(), std::move(pending_callbacks),
-        IDBTaskRunner());
-    IndexedDBDatabaseError error = IndexedDBDatabaseError(
-        blink::mojom::IDBException::kUnknownError,
-        u"Internal error retrieving bucket data directory.");
-    callbacks->OnError(error);
-    return;
-  }
-
-  const auto storage_key = receivers_.current_context()->storage_key;
+  const auto& storage_key = receivers_.current_context();
   auto callbacks = base::MakeRefCounted<IndexedDBCallbacks>(
       this->AsWeakPtr(), storage_key, std::move(pending_callbacks),
       IDBTaskRunner());
@@ -338,7 +310,6 @@
       std::make_unique<IndexedDBPendingConnection>(
           std::move(callbacks), std::move(database_callbacks), transaction_id,
           version, std::move(create_transaction_callback));
-
   // TODO(dgrogan): Don't let a non-existing database be opened (and therefore
   // created) if this origin is already over quota.
   indexed_db_context_->GetIDBFactory()->Open(name, std::move(connection),
@@ -351,23 +322,10 @@
     bool force_close) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Return error if failed to retrieve bucket from the QuotaManager.
-  if (!receivers_.current_context().has_value()) {
-    auto callbacks = base::MakeRefCounted<IndexedDBCallbacks>(
-        this->AsWeakPtr(), blink::StorageKey(), std::move(pending_callbacks),
-        IDBTaskRunner());
-    IndexedDBDatabaseError error = IndexedDBDatabaseError(
-        blink::mojom::IDBException::kUnknownError,
-        u"Internal error retrieving bucket data directory.");
-    callbacks->OnError(error);
-    return;
-  }
-
-  const auto storage_key = receivers_.current_context()->storage_key;
+  const auto& storage_key = receivers_.current_context();
   auto callbacks = base::MakeRefCounted<IndexedDBCallbacks>(
       this->AsWeakPtr(), storage_key, std::move(pending_callbacks),
       IDBTaskRunner());
-
   base::FilePath indexed_db_path = indexed_db_context_->data_path();
   indexed_db_context_->GetIDBFactory()->DeleteDatabase(
       name, std::move(callbacks), storage_key, indexed_db_path, force_close);
@@ -377,16 +335,9 @@
     AbortTransactionsAndCompactDatabaseCallback mojo_callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Return error if failed to retrieve bucket from the QuotaManager.
-  if (!receivers_.current_context().has_value()) {
-    std::move(mojo_callback).Run(blink::mojom::IDBStatus::NotFound);
-    return;
-  }
-
-  const auto storage_key = receivers_.current_context()->storage_key;
+  const auto& storage_key = receivers_.current_context();
   base::OnceCallback<void(leveldb::Status)> callback_on_io = base::BindOnce(
       &CallCompactionStatusCallbackOnIDBThread, std::move(mojo_callback));
-
   indexed_db_context_->GetIDBFactory()->AbortTransactionsAndCompactDatabase(
       std::move(callback_on_io), storage_key);
 }
@@ -395,16 +346,9 @@
     AbortTransactionsForDatabaseCallback mojo_callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Return error if failed to retrieve bucket from the QuotaManager.
-  if (!receivers_.current_context().has_value()) {
-    std::move(mojo_callback).Run(blink::mojom::IDBStatus::NotFound);
-    return;
-  }
-
-  const auto storage_key = receivers_.current_context()->storage_key;
+  const auto& storage_key = receivers_.current_context();
   base::OnceCallback<void(leveldb::Status)> callback_on_io = base::BindOnce(
       &CallAbortStatusCallbackOnIDBThread, std::move(mojo_callback));
-
   indexed_db_context_->GetIDBFactory()->AbortTransactionsForDatabase(
       std::move(callback_on_io), storage_key);
 }
diff --git a/content/browser/indexed_db/indexed_db_dispatcher_host.h b/content/browser/indexed_db/indexed_db_dispatcher_host.h
index c96115e..4768706 100644
--- a/content/browser/indexed_db/indexed_db_dispatcher_host.h
+++ b/content/browser/indexed_db/indexed_db_dispatcher_host.h
@@ -14,7 +14,6 @@
 #include <vector>
 
 #include "base/sequence_checker.h"
-#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
 #include "components/services/storage/public/mojom/blob_storage_context.mojom-forward.h"
 #include "components/services/storage/public/mojom/file_system_access_context.mojom-forward.h"
 #include "content/browser/indexed_db/indexed_db_external_object.h"
@@ -52,7 +51,6 @@
 
   void AddReceiver(
       const blink::StorageKey& storage_key,
-      absl::optional<storage::BucketLocator> bucket,
       mojo::PendingReceiver<blink::mojom::IDBFactory> pending_receiver);
 
   void AddDatabaseBinding(
@@ -138,9 +136,7 @@
   // Shared task runner used to read blob files on.
   const scoped_refptr<base::TaskRunner> file_task_runner_;
 
-  mojo::ReceiverSet<blink::mojom::IDBFactory,
-                    absl::optional<storage::BucketLocator>>
-      receivers_;
+  mojo::ReceiverSet<blink::mojom::IDBFactory, blink::StorageKey> receivers_;
   mojo::UniqueAssociatedReceiverSet<blink::mojom::IDBDatabase>
       database_receivers_;
   mojo::UniqueAssociatedReceiverSet<blink::mojom::IDBCursor> cursor_receivers_;
diff --git a/content/browser/renderer_host/browsing_context_state.cc b/content/browser/renderer_host/browsing_context_state.cc
index 05e49e4..4ab38bb 100644
--- a/content/browser/renderer_host/browsing_context_state.cc
+++ b/content/browser/renderer_host/browsing_context_state.cc
@@ -372,10 +372,10 @@
 }
 
 void BrowsingContextState::WriteIntoTrace(
-    perfetto::TracedProto<perfetto::protos::pbzero::BrowsingContextState>
-        proto) {
+    perfetto::TracedProto<perfetto::protos::pbzero::BrowsingContextState> proto)
+    const {
   if (browsing_instance_id_.has_value())
     proto->set_browsing_instance_id(browsing_instance_id_.value().value());
 }
 
-}  // namespace content
\ No newline at end of file
+}  // namespace content
diff --git a/content/browser/renderer_host/browsing_context_state.h b/content/browser/renderer_host/browsing_context_state.h
index 57d5788..6762d21b 100644
--- a/content/browser/renderer_host/browsing_context_state.h
+++ b/content/browser/renderer_host/browsing_context_state.h
@@ -232,7 +232,7 @@
   void WriteIntoTrace(perfetto::TracedValue ctx) const;
   void WriteIntoTrace(
       perfetto::TracedProto<perfetto::protos::pbzero::BrowsingContextState>
-          proto);
+          proto) const;
 
  protected:
   friend class base::RefCounted<BrowsingContextState>;
diff --git a/content/browser/renderer_host/frame_tree_browsertest.cc b/content/browser/renderer_host/frame_tree_browsertest.cc
index 4ba8386..9648505 100644
--- a/content/browser/renderer_host/frame_tree_browsertest.cc
+++ b/content/browser/renderer_host/frame_tree_browsertest.cc
@@ -17,8 +17,6 @@
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_types.h"
 #include "content/public/browser/site_isolation_policy.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
@@ -89,10 +87,7 @@
   // Frame tree:
   //   Site-A Root -- Site-A frame1
   //              \-- Site-A frame2
-  WindowedNotificationObserver observer1(
-      content::NOTIFICATION_LOAD_STOP,
-      content::Source<NavigationController>(
-          &shell()->web_contents()->GetController()));
+  LoadStopObserver observer1(shell()->web_contents());
   EXPECT_TRUE(NavigateToURL(shell(), base_url.Resolve("frames-X-X.html")));
   observer1.Wait();
   ASSERT_EQ(2U, root->child_count());
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 2336568..f507cc5 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -6293,6 +6293,12 @@
   switch (disabler_type) {
     case blink::mojom::SuddenTerminationDisablerType::kBeforeUnloadHandler:
       DCHECK_NE(has_before_unload_handler_, present);
+      if (IsNestedWithinFencedFrame()) {
+        bad_message::ReceivedBadMessage(
+            GetProcess(),
+            bad_message::RFH_BEFOREUNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME);
+        return;
+      }
       has_before_unload_handler_ = present;
       break;
     case blink::mojom::SuddenTerminationDisablerType::kPageHideHandler:
@@ -6301,6 +6307,12 @@
       break;
     case blink::mojom::SuddenTerminationDisablerType::kUnloadHandler:
       DCHECK_NE(has_unload_handler_, present);
+      if (IsNestedWithinFencedFrame()) {
+        bad_message::ReceivedBadMessage(
+            GetProcess(),
+            bad_message::RFH_UNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME);
+        return;
+      }
       has_unload_handler_ = present;
       break;
     case blink::mojom::SuddenTerminationDisablerType::kVisibilityChangeHandler:
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 2afcba9d..57e8691 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -3641,7 +3641,9 @@
   // died due to fast shutdown versus another cause.
   fast_shutdown_started_ = true;
 
-  ProcessDied(false /* already_dead */, nullptr);
+  ChildProcessTerminationInfo info =
+      GetChildTerminationInfo(false /* already_dead */);
+  ProcessDied(info);
   return true;
 }
 
@@ -3740,7 +3742,9 @@
 }
 
 void RenderProcessHostImpl::OnChannelError() {
-  ProcessDied(true /* already_dead */, nullptr);
+  ChildProcessTerminationInfo info =
+      GetChildTerminationInfo(true /* already_dead */);
+  ProcessDied(info);
 }
 
 void RenderProcessHostImpl::OnBadMessageReceived(const IPC::Message& message) {
@@ -3932,18 +3936,16 @@
     // anymore.
     // Populates Android-only fields and closes the underlying base::Process.
     ChildProcessTerminationInfo info =
-        child_process_launcher_->GetChildTerminationInfo(
-            false /* already_dead */);
+        GetChildTerminationInfo(false /* already_dead */);
     info.status = base::TERMINATION_STATUS_NORMAL_TERMINATION;
     info.exit_code = 0;
-    PopulateTerminationInfoRendererFields(&info);
 
     // Set this before ProcessDied() so observers treat this process exit as
     // similar to the fast shutdown case.
     fast_shutdown_started_ = true;
 
     // Notify observers using the clean exit info.
-    ProcessDied(false, &info);
+    ProcessDied(info);
     return;
   }
 
@@ -3973,12 +3975,9 @@
   // anymore.
   if (IsInitializedAndNotDead()) {
     // Populates Android-only fields and closes the underlying base::Process.
-    ChildProcessTerminationInfo info =
-        child_process_launcher_->GetChildTerminationInfo(
-            false /* already_dead */);
+    ChildProcessTerminationInfo info = GetChildTerminationInfo(false);
     info.status = base::TERMINATION_STATUS_NORMAL_TERMINATION;
     info.exit_code = 0;
-    PopulateTerminationInfoRendererFields(&info);
     for (auto& observer : observers_) {
       observer.RenderProcessExited(this, info);
     }
@@ -4704,11 +4703,36 @@
       this, std::move(shm_region));
 }
 
+ChildProcessTerminationInfo RenderProcessHostImpl::GetChildTerminationInfo(
+    bool already_dead) {
+  DCHECK(child_process_launcher_);
+
+  ChildProcessTerminationInfo info;
+
+  info = child_process_launcher_->GetChildTerminationInfo(already_dead);
+  if (already_dead && info.status == base::TERMINATION_STATUS_STILL_RUNNING) {
+    // May be in case of IPC error, if it takes long time for renderer
+    // to exit. Child process will be killed in any case during
+    // child_process_launcher_.reset(). Make sure we will not broadcast
+    // RenderProcessExited with status TERMINATION_STATUS_STILL_RUNNING,
+    // since this will break WebContentsImpl logic.
+    info.status = base::TERMINATION_STATUS_PROCESS_CRASHED;
+
+    // TODO(siggi): Remove this once https://crbug.com/806661 is resolved.
+#if BUILDFLAG(IS_WIN)
+    if (info.exit_code == WAIT_TIMEOUT && g_analyze_hung_renderer)
+      g_analyze_hung_renderer(child_process_launcher_->GetProcess());
+#endif
+  }
+
+  PopulateTerminationInfoRendererFields(&info);
+
+  return info;
+}
+
 void RenderProcessHostImpl::ProcessDied(
-    bool already_dead,
-    ChildProcessTerminationInfo* known_info) {
-  TRACE_EVENT1("content", "RenderProcessHostImpl::ProcessDied", "already_dead",
-               already_dead);
+    const ChildProcessTerminationInfo& termination_info) {
+  TRACE_EVENT0("content", "RenderProcessHostImpl::ProcessDied");
   // Our child process has died.  If we didn't expect it, it's a crash.
   // In any case, we need to let everyone know it's gone.
   // The OnChannelError notification can fire multiple times due to nested
@@ -4722,31 +4746,6 @@
   // while we are dying.
   DCHECK(!deleting_soon_);
 
-  // child_process_launcher_ can be NULL in single process mode or if fast
-  // termination happened.
-  ChildProcessTerminationInfo info;
-  info.exit_code = 0;
-  if (known_info) {
-    info = *known_info;
-  } else if (child_process_launcher_.get()) {
-    info = child_process_launcher_->GetChildTerminationInfo(already_dead);
-    if (already_dead && info.status == base::TERMINATION_STATUS_STILL_RUNNING) {
-      // May be in case of IPC error, if it takes long time for renderer
-      // to exit. Child process will be killed in any case during
-      // child_process_launcher_.reset(). Make sure we will not broadcast
-      // RenderProcessExited with status TERMINATION_STATUS_STILL_RUNNING,
-      // since this will break WebContentsImpl logic.
-      info.status = base::TERMINATION_STATUS_PROCESS_CRASHED;
-
-      // TODO(siggi): Remove this once https://crbug.com/806661 is resolved.
-#if BUILDFLAG(IS_WIN)
-      if (info.exit_code == WAIT_TIMEOUT && g_analyze_hung_renderer)
-        g_analyze_hung_renderer(child_process_launcher_->GetProcess());
-#endif
-    }
-  }
-  PopulateTerminationInfoRendererFields(&info);
-
   child_process_launcher_.reset();
   is_dead_ = true;
   // Make sure no IPCs or mojo calls from the old process get dispatched after
@@ -4756,6 +4755,7 @@
   UpdateProcessPriority();
 
   // RenderProcessExited relies on the exit code set during shutdown.
+  ChildProcessTerminationInfo info = termination_info;
   if (shutdown_exit_code_ != -1)
     info.exit_code = shutdown_exit_code_;
 
@@ -4816,12 +4816,12 @@
   // OnChannelClosed() to IPC::ChannelProxy::Context on the IO thread.
   ResetChannelProxy();
 
-  // The PermissionServiceContext holds PermissionSubscriptions originating from
-  // service workers. These subscriptions observe the PermissionControllerImpl
-  // that is owned by the Profile corresponding to |this|. At this point, IPC
-  // are unbound so no new subscriptions can be made. Existing subscriptions
-  // need to be released here, as the Profile, and with it, the
-  // PermissionControllerImpl, can be destroyed anytime after
+  // The PermissionServiceContext holds PermissionSubscriptions originating
+  // from service workers. These subscriptions observe the
+  // PermissionControllerImpl that is owned by the Profile corresponding to
+  // |this|. At this point, IPC are unbound so no new subscriptions can be
+  // made. Existing subscriptions need to be released here, as the Profile,
+  // and with it, the PermissionControllerImpl, can be destroyed anytime after
   // RenderProcessHostImpl::Cleanup() returns.
   permission_service_context_.reset();
 }
@@ -5152,7 +5152,7 @@
   info.status = base::TERMINATION_STATUS_LAUNCH_FAILED;
   info.exit_code = error_code;
   PopulateTerminationInfoRendererFields(&info);
-  ProcessDied(true, &info);
+  ProcessDied(info);
 }
 
 // static
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index feb27b0..e463f057a 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -853,9 +853,18 @@
   // report those histograms to UMA.
   void CreateSharedRendererHistogramAllocator();
 
+  // Retrieves the details of the terminating child process.
+  //
+  // If the process is no longer running, this will also reset the process
+  // handle and (where applicable) reap the zombie process.
+  //
+  // |already_dead| should be set to true if we already know the process is
+  // dead. See `ChildProcessLauncher::GetChildTerminationInfo()` for more info
+  // on this flag.
+  ChildProcessTerminationInfo GetChildTerminationInfo(bool already_dead);
+
   // Handle termination of our process.
-  void ProcessDied(bool already_dead,
-                   ChildProcessTerminationInfo* known_details);
+  void ProcessDied(const ChildProcessTerminationInfo& termination_info);
 
   // Destroy all objects that can cause methods to be invoked on this object or
   // any other that hang off it.
diff --git a/content/browser/renderer_host/render_widget_host_browsertest.cc b/content/browser/renderer_host/render_widget_host_browsertest.cc
index bba8d2b..78bd3518 100644
--- a/content/browser/renderer_host/render_widget_host_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_browsertest.cc
@@ -20,7 +20,6 @@
 #include "content/browser/renderer_host/render_widget_host_input_event_router.h"
 #include "content/browser/renderer_host/render_widget_host_view_base.h"
 #include "content/browser/web_contents/web_contents_impl.h"
-#include "content/public/browser/notification_types.h"
 #include "content/public/browser/render_widget_host_observer.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -901,8 +900,7 @@
 
   // Ensure that the environment variables have the correct values in the new
   // document that is created on reloading the page.
-  WindowedNotificationObserver load_stop_observer(
-      NOTIFICATION_LOAD_STOP, NotificationService::AllSources());
+  LoadStopObserver load_stop_observer(shell()->web_contents());
   shell()->Reload();
   load_stop_observer.Wait();
 
diff --git a/content/browser/service_worker/embedded_worker_test_helper.h b/content/browser/service_worker/embedded_worker_test_helper.h
index 28b888e..44f3aa2 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.h
+++ b/content/browser/service_worker/embedded_worker_test_helper.h
@@ -110,8 +110,6 @@
   // Only used for tests that force creating a new render process.
   int new_render_process_id() const { return new_mock_render_process_id_; }
 
-  storage::MockQuotaManager* quota_manager() { return quota_manager_.get(); }
-
   storage::MockQuotaManagerProxy* quota_manager_proxy() {
     return quota_manager_proxy_.get();
   }
diff --git a/content/browser/service_worker/service_worker_registry.cc b/content/browser/service_worker/service_worker_registry.cc
index b6448f5..9c5d8a2 100644
--- a/content/browser/service_worker/service_worker_registry.cc
+++ b/content/browser/service_worker/service_worker_registry.cc
@@ -201,11 +201,7 @@
     const blink::StorageKey& key,
     NewRegistrationCallback callback,
     storage::QuotaErrorOr<storage::BucketInfo> result) {
-  // Return nullptr if GetOrCreateBucket fails.
-  if (!result.ok()) {
-    std::move(callback).Run(nullptr);
-    return;
-  }
+  DCHECK(result.ok());
   CreateInvokerAndStartRemoteCall(
       &storage::mojom::ServiceWorkerStorageControl::GetNewRegistrationId,
       base::BindOnce(&ServiceWorkerRegistry::DidGetNewRegistrationId,
diff --git a/content/browser/service_worker/service_worker_registry_unittest.cc b/content/browser/service_worker/service_worker_registry_unittest.cc
index 77ccc9f..c2f5ead 100644
--- a/content/browser/service_worker/service_worker_registry_unittest.cc
+++ b/content/browser/service_worker/service_worker_registry_unittest.cc
@@ -648,30 +648,6 @@
   EXPECT_GT(result->id.value(), 0);
 }
 
-TEST_F(ServiceWorkerRegistryTest, GetOrCreateBucketError) {
-  const GURL kScope("http://www.test.not/scope/");
-  const blink::StorageKey kKey(url::Origin::Create(kScope));
-
-  scoped_refptr<ServiceWorkerRegistration> registration;
-
-  blink::mojom::ServiceWorkerRegistrationOptions options;
-  options.scope = kScope;
-
-  helper()->quota_manager()->SetDisableDatabase(true);
-  storage::QuotaManagerProxySync quota_manager_proxy_sync(
-      quota_manager_proxy());
-
-  base::RunLoop loop;
-  registry()->CreateNewRegistration(
-      std::move(options), kKey,
-      base::BindLambdaForTesting(
-          [&](scoped_refptr<ServiceWorkerRegistration> new_registration) {
-            EXPECT_EQ(new_registration, nullptr);
-            loop.Quit();
-          }));
-  loop.Run();
-}
-
 TEST_F(ServiceWorkerRegistryTest, StoreFindUpdateDeleteRegistration) {
   const GURL kScope("http://www.test.not/scope/");
   const blink::StorageKey kKey(url::Origin::Create(kScope));
diff --git a/content/browser/session_history_browsertest.cc b/content/browser/session_history_browsertest.cc
index aa66606ff..1442768 100644
--- a/content/browser/session_history_browsertest.cc
+++ b/content/browser/session_history_browsertest.cc
@@ -12,8 +12,6 @@
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/content_navigation_policy.h"
 #include "content/public/browser/navigation_controller.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_types.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
@@ -128,17 +126,13 @@
   }
 
   void GoBack() {
-    WindowedNotificationObserver load_stop_observer(
-        NOTIFICATION_LOAD_STOP,
-        NotificationService::AllSources());
+    LoadStopObserver load_stop_observer(shell()->web_contents());
     shell()->web_contents()->GetController().GoBack();
     load_stop_observer.Wait();
   }
 
   void GoForward() {
-    WindowedNotificationObserver load_stop_observer(
-        NOTIFICATION_LOAD_STOP,
-        NotificationService::AllSources());
+    LoadStopObserver load_stop_observer(shell()->web_contents());
     shell()->web_contents()->GetController().GoForward();
     load_stop_observer.Wait();
   }
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index f5a11247..befddcf 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -90,9 +90,6 @@
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/javascript_dialog_manager.h"
 #include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_types.h"
 #include "content/public/browser/site_isolation_policy.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
@@ -253,86 +250,6 @@
   return EvalJs(ftn, "self.origin;");
 }
 
-class RedirectNotificationObserver : public NotificationObserver {
- public:
-  // Register to listen for notifications of the given type from either a
-  // specific source, or from all sources if |source| is
-  // NotificationService::AllSources().
-  RedirectNotificationObserver(int notification_type,
-                               const NotificationSource& source);
-
-  RedirectNotificationObserver(const RedirectNotificationObserver&) = delete;
-  RedirectNotificationObserver& operator=(const RedirectNotificationObserver&) =
-      delete;
-
-  ~RedirectNotificationObserver() override;
-
-  // Wait until the specified notification occurs.  If the notification was
-  // emitted between the construction of this object and this call then it
-  // returns immediately.
-  void Wait();
-
-  // Returns NotificationService::AllSources() if we haven't observed a
-  // notification yet.
-  const NotificationSource& source() const {
-    return source_;
-  }
-
-  const NotificationDetails& details() const {
-    return details_;
-  }
-
-  // NotificationObserver:
-  void Observe(int type,
-               const NotificationSource& source,
-               const NotificationDetails& details) override;
-
- private:
-  bool seen_;
-  bool seen_twice_;
-  bool running_;
-  NotificationRegistrar registrar_;
-
-  NotificationSource source_;
-  NotificationDetails details_;
-  base::RunLoop run_loop_;
-};
-
-RedirectNotificationObserver::RedirectNotificationObserver(
-    int notification_type,
-    const NotificationSource& source)
-    : seen_(false),
-      running_(false),
-      source_(NotificationService::AllSources()) {
-  registrar_.Add(this, notification_type, source);
-}
-
-RedirectNotificationObserver::~RedirectNotificationObserver() {}
-
-void RedirectNotificationObserver::Wait() {
-  if (seen_ && seen_twice_)
-    return;
-
-  running_ = true;
-  run_loop_.Run();
-  EXPECT_TRUE(seen_);
-}
-
-void RedirectNotificationObserver::Observe(
-    int type,
-    const NotificationSource& source,
-    const NotificationDetails& details) {
-  source_ = source;
-  details_ = details;
-  seen_twice_ = seen_;
-  seen_ = true;
-  if (!running_)
-    return;
-
-  run_loop_.Quit();
-  running_ = false;
-}
-
 // This observer detects when WebContents receives notification of a user
 // gesture having occurred, following a user input event targeted to
 // a RenderWidgetHost under that WebContents.
@@ -2065,9 +1982,7 @@
     GURL client_redirect_http_url(
         embedded_test_server()->GetURL("/client-redirect?" + https_url.spec()));
 
-    RedirectNotificationObserver load_observer2(
-        NOTIFICATION_LOAD_STOP, Source<NavigationController>(
-                                    &shell()->web_contents()->GetController()));
+    LoadStopObserver load_observer2(shell()->web_contents());
 
     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
                                     client_redirect_http_url));
@@ -2098,9 +2013,7 @@
     // which redirects to same-site page.
     GURL client_redirect_http_url(
         embedded_test_server()->GetURL("/client-redirect?" + http_url.spec()));
-    RedirectNotificationObserver load_observer2(
-        NOTIFICATION_LOAD_STOP, Source<NavigationController>(
-                                    &shell()->web_contents()->GetController()));
+    LoadStopObserver load_observer2(shell()->web_contents());
 
     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
                                     client_redirect_http_url));
@@ -2140,9 +2053,7 @@
         "/client-redirect?" + client_redirect_https_url.spec()));
 
     // We should wait until second client redirect get cancelled.
-    RedirectNotificationObserver load_observer2(
-        NOTIFICATION_LOAD_STOP, Source<NavigationController>(
-                                    &shell()->web_contents()->GetController()));
+    LoadStopObserver load_observer2(shell()->web_contents());
 
     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
                                     client_redirect_http_url));
diff --git a/content/browser/web_database/web_database_host_impl.cc b/content/browser/web_database/web_database_host_impl.cc
index a5b18e94..346e8cd3 100644
--- a/content/browser/web_database/web_database_host_impl.cc
+++ b/content/browser/web_database/web_database_host_impl.cc
@@ -146,11 +146,7 @@
     int32_t desired_flags,
     OpenFileCallback callback,
     storage::QuotaErrorOr<storage::BucketInfo> bucket) {
-  // Return invalid file path on GetOrCreateBucket error.
-  if (!bucket.ok()) {
-    std::move(callback).Run(base::File());
-    return;
-  }
+  DCHECK(bucket.ok());
 
   base::File file;
   const base::File* tracked_file = nullptr;
diff --git a/content/browser/web_database/web_database_host_impl_unittest.cc b/content/browser/web_database/web_database_host_impl_unittest.cc
index 36ae993f..516ee07 100644
--- a/content/browser/web_database/web_database_host_impl_unittest.cc
+++ b/content/browser/web_database/web_database_host_impl_unittest.cc
@@ -141,8 +141,6 @@
         process_id(), url);
   }
 
-  storage::MockQuotaManager* quota_manager() { return quota_manager_.get(); }
-
   storage::QuotaManagerProxy* quota_manager_proxy() {
     return quota_manager_proxy_.get();
   }
@@ -201,37 +199,6 @@
   EXPECT_GT(result->id.value(), 0);
 }
 
-TEST_F(WebDatabaseHostImplTest, GetOrCreateBucketError) {
-  const char* example_url = "http://example.com";
-  const GURL example_gurl(example_url);
-  const url::Origin example_origin = url::Origin::Create(example_gurl);
-  const std::u16string db_name = u"db_name";
-  const std::u16string suffix(u"suffix");
-  const std::u16string vfs_file_name =
-      ConstructVfsFileName(example_origin, db_name, suffix);
-
-  auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  security_policy->AddFutureIsolatedOrigins(
-      {example_origin}, ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
-  LockProcessToURL(example_gurl);
-
-  quota_manager()->SetDisableDatabase(true);
-  storage::QuotaManagerProxySync quota_manager_proxy_sync(
-      quota_manager_proxy());
-
-  base::RunLoop run_loop;
-  task_runner()->PostTask(
-      FROM_HERE, base::BindLambdaForTesting([&]() {
-        mojo::FakeMessageDispatchContext fake_dispatch_context;
-        host()->OpenFile(vfs_file_name, /*desired_flags=*/0,
-                         base::BindLambdaForTesting([&](base::File file) {
-                           EXPECT_FALSE(file.IsValid());
-                           run_loop.Quit();
-                         }));
-      }));
-  run_loop.Run();
-}
-
 TEST_F(WebDatabaseHostImplTest, BadMessagesUnauthorized) {
   const GURL correct_url("http://correct.com");
   const url::Origin correct_origin = url::Origin::Create(correct_url);
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index a723970..5c7ead2 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -318,7 +318,7 @@
 void FederatedAuthRequestImpl::Revoke(
     const GURL& provider,
     const std::string& client_id,
-    const std::string& account_id,
+    const std::string& hint,
     blink::mojom::FederatedAuthRequest::RevokeCallback callback) {
   if (HasPendingRequest()) {
     RecordRevokeStatus(RevokeStatusForMetrics::kTooManyRequests,
@@ -329,7 +329,7 @@
 
   provider_ = provider;
   client_id_ = client_id;
-  account_id_ = account_id;
+  hint_ = hint;
   delay_timer_.Reset();
   revoke_callback_ = std::move(callback);
 
@@ -591,7 +591,7 @@
     return;
   }
   network_manager_->SendRevokeRequest(
-      revocation_url, client_id_, account_id_,
+      revocation_url, client_id_, hint_,
       base::BindOnce(&FederatedAuthRequestImpl::OnRevokeResponse,
                      weak_ptr_factory_.GetWeakPtr()));
 }
@@ -611,11 +611,11 @@
     }
     if (GetSharingPermissionContext()) {
       GetSharingPermissionContext()->RevokeSharingPermissionForAccount(
-          idp_origin, origin_, account_id_);
+          idp_origin, origin_, hint_);
     }
     if (GetActiveSessionPermissionContext()) {
       GetActiveSessionPermissionContext()->RevokeActiveSession(
-          origin_, idp_origin, account_id_);
+          origin_, idp_origin, hint_);
     }
     RecordRevokeStatus(RevokeStatusForMetrics::kSuccess,
                        render_frame_host_->GetPageUkmSourceId());
@@ -637,7 +637,7 @@
                              network_manager_->IsMockIdpNetworkRequestManager();
   network_manager_.reset();
   provider_ = GURL();
-  account_id_ = std::string();
+  hint_ = std::string();
   client_id_ = std::string();
 
   if (should_run_callback)
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index 7fe3a3fe..4b36210 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -201,6 +201,8 @@
   // The account that was selected by the user. This is only applicable to the
   // mediation flow.
   std::string account_id_;
+  // Used by revocation.
+  std::string hint_;
   base::TimeTicks start_time_;
   base::TimeTicks show_accounts_dialog_time_;
   base::TimeTicks select_account_time_;
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index 73c0c3e..ecab8fcc 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -1187,7 +1187,7 @@
 }
 
 TEST_F(FederatedAuthRequestImplTest, Revoke) {
-  constexpr char kAccountId[] = "foo@bar.com";
+  constexpr char kHint[] = "foo@bar.com";
 
   auto& auth_request = CreateAuthRequest(GURL(kProviderUrl));
   auth_request.SetRequestPermissionDelegateForTests(
@@ -1213,11 +1213,11 @@
           }));
   EXPECT_CALL(*mock_request_manager_, SendRevokeRequest(_, _, _, _))
       .WillOnce(Invoke([&](const GURL& revoke_url, const std::string& client_id,
-                           const std::string& account_id,
+                           const std::string& hint,
                            IdpNetworkRequestManager::RevokeCallback callback) {
         EXPECT_EQ(kRevocationEndpoint, revoke_url.spec());
         EXPECT_EQ(kClientId, client_id);
-        EXPECT_EQ(kAccountId, account_id);
+        EXPECT_EQ(kHint, hint);
         std::move(callback).Run(RevokeResponse::kSuccess);
       }));
 
@@ -1225,7 +1225,7 @@
   ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
                                         ukm_loop.QuitClosure());
 
-  auto status = PerformRevokeRequest(kAccountId);
+  auto status = PerformRevokeRequest(kHint);
   EXPECT_EQ(RevokeStatus::kSuccess, status);
 
   ukm_loop.Run();
@@ -1237,7 +1237,7 @@
 }
 
 TEST_F(FederatedAuthRequestImplTest, RevokeNoPermission) {
-  constexpr char kAccountId[] = "foo@bar.com";
+  constexpr char kHint[] = "foo@bar.com";
 
   auto& auth_request = CreateAuthRequest(GURL(kProviderUrl));
   auth_request.SetRequestPermissionDelegateForTests(
@@ -1253,7 +1253,7 @@
   ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
                                         ukm_loop.QuitClosure());
 
-  auto status = PerformRevokeRequest(kAccountId);
+  auto status = PerformRevokeRequest(kHint);
   EXPECT_EQ(RevokeStatus::kError, status);
 
   ukm_loop.Run();
diff --git a/content/browser/webid/idp_network_request_manager.cc b/content/browser/webid/idp_network_request_manager.cc
index 8ef811e..365b063 100644
--- a/content/browser/webid/idp_network_request_manager.cc
+++ b/content/browser/webid/idp_network_request_manager.cc
@@ -525,7 +525,7 @@
 
 void IdpNetworkRequestManager::SendRevokeRequest(const GURL& revoke_url,
                                                  const std::string& client_id,
-                                                 const std::string& account_id,
+                                                 const std::string& hint,
                                                  RevokeCallback callback) {
   DCHECK(!url_loader_);
   DCHECK(!token_request_callback_);
@@ -536,16 +536,13 @@
   if (!client_id.empty())
     revoke_request_body += "client_id=" + client_id;
 
-  if (!account_id.empty()) {
-    if (!revoke_request_body.empty())
-      revoke_request_body += "&";
-    revoke_request_body += "account_id=" + account_id;
-  }
-
-  if (revoke_request_body.empty()) {
+  if (hint.empty()) {
     std::move(revoke_callback_).Run(RevokeResponse::kError);
     return;
   }
+  if (!revoke_request_body.empty())
+    revoke_request_body += "&";
+  revoke_request_body += "hint=" + hint;
 
   url_loader_ = CreateCredentialedUrlLoader(
       revoke_url, /* send_referrer= */ true, revoke_request_body);
diff --git a/content/browser/webid/idp_network_request_manager.h b/content/browser/webid/idp_network_request_manager.h
index 5b7ca77..1cd0704 100644
--- a/content/browser/webid/idp_network_request_manager.h
+++ b/content/browser/webid/idp_network_request_manager.h
@@ -162,7 +162,7 @@
   // Send a revoke token request to the IDP.
   virtual void SendRevokeRequest(const GURL& revoke_url,
                                  const std::string& client_id,
-                                 const std::string& account_id,
+                                 const std::string& hint,
                                  RevokeCallback callback);
 
   // Send logout request to a single target.
diff --git a/content/browser/webid/idp_network_request_manager_unittest.cc b/content/browser/webid/idp_network_request_manager_unittest.cc
index 6784754..954f2eab 100644
--- a/content/browser/webid/idp_network_request_manager_unittest.cc
+++ b/content/browser/webid/idp_network_request_manager_unittest.cc
@@ -154,7 +154,7 @@
 
   RevokeResponse SendRevokeRequestAndWaitForResponse(
       const char* client_id,
-      const char* account_id,
+      const char* hint,
       net::HttpStatusCode http_status = net::HTTP_NO_CONTENT) {
     GURL revocation_endpoint(kTestRevocationEndpoint);
     test_url_loader_factory().AddResponse(revocation_endpoint.spec(), "",
@@ -167,7 +167,7 @@
           status = revoke_status;
           run_loop.Quit();
         });
-    manager().SendRevokeRequest(revocation_endpoint, client_id, account_id,
+    manager().SendRevokeRequest(revocation_endpoint, client_id, hint,
                                 std::move(callback));
     run_loop.Run();
     return status;
@@ -684,7 +684,7 @@
         ASSERT_EQ(network::DataElement::Tag::kBytes, elem.type());
         const network::DataElementBytes& byte_elem =
             elem.As<network::DataElementBytes>();
-        EXPECT_EQ("client_id=xxx&account_id=yyy", byte_elem.AsStringPiece());
+        EXPECT_EQ("client_id=xxx&hint=yyy", byte_elem.AsStringPiece());
       });
   test_url_loader_factory().SetInterceptor(interceptor);
   RevokeResponse status = SendRevokeRequestAndWaitForResponse("xxx", "yyy");
diff --git a/content/browser/zoom_browsertest.cc b/content/browser/zoom_browsertest.cc
index 511a2f7..ba54b7e 100644
--- a/content/browser/zoom_browsertest.cc
+++ b/content/browser/zoom_browsertest.cc
@@ -13,8 +13,6 @@
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/host_zoom_map.h"
 #include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_types.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -251,9 +249,7 @@
 
   // Now the actual test: Reload the page and check that the main frame is
   // still properly zoomed.
-  WindowedNotificationObserver load_stop_observer(
-      NOTIFICATION_LOAD_STOP,
-      NotificationService::AllSources());
+  LoadStopObserver load_stop_observer(shell()->web_contents());
   shell()->Reload();
   load_stop_observer.Wait();
 
diff --git a/content/public/test/test_utils.cc b/content/public/test/test_utils.cc
index 4a3c423..9f3d035 100644
--- a/content/public/test/test_utils.cc
+++ b/content/public/test/test_utils.cc
@@ -417,6 +417,21 @@
   run_loop_.Quit();
 }
 
+LoadStopObserver::LoadStopObserver(WebContents* web_contents)
+    : WebContentsObserver(web_contents) {}
+
+void LoadStopObserver::Wait() {
+  if (!seen_)
+    run_loop_.Run();
+
+  EXPECT_TRUE(seen_);
+}
+
+void LoadStopObserver::DidStopLoading() {
+  seen_ = true;
+  run_loop_.Quit();
+}
+
 InProcessUtilityThreadHelper::InProcessUtilityThreadHelper() {
   RenderProcessHost::SetRunRendererInProcess(true);
 }
diff --git a/content/public/test/test_utils.h b/content/public/test/test_utils.h
index aa9a6cd..3c4e89a 100644
--- a/content/public/test/test_utils.h
+++ b/content/public/test/test_utils.h
@@ -308,6 +308,36 @@
   base::RunLoop run_loop_;
 };
 
+// Helper to wait for loading to stop on a WebContents.  It should be preferred
+// to uses of WindowedNotificationObserver for NOTIFICATION_LOAD_STOP.
+//
+// This helper class exists to avoid the following common pattern in tests:
+//   PerformAction()
+//   WaitForCompletionNotification()
+// The pattern leads to flakiness as there is a window between PerformAction
+// returning and the observers getting registered, where a notification will be
+// missed.
+//
+// Rather, one can do this:
+//   LoadStopObserver signal(web_contents)
+//   PerformAction()
+//   signal.Wait()
+class LoadStopObserver : public WebContentsObserver {
+ public:
+  explicit LoadStopObserver(WebContents* web_contents);
+
+  // Wait until at least one load stop has been observed.  Return immediately if
+  // one has been observed since construction.
+  void Wait();
+
+  // WebContentsObserver
+  void DidStopLoading() override;
+
+ private:
+  bool seen_ = false;
+  base::RunLoop run_loop_;
+};
+
 // Unit tests can use code which runs in the utility process by having it run on
 // an in-process utility thread. This eliminates having two code paths in
 // production code to deal with unit tests, and also helps with the binary
diff --git a/content/test/attribution_simulator_input_parser.cc b/content/test/attribution_simulator_input_parser.cc
index e034169..40566c8 100644
--- a/content/test/attribution_simulator_input_parser.cc
+++ b/content/test/attribution_simulator_input_parser.cc
@@ -19,7 +19,7 @@
 #include "base/test/bind.h"
 #include "base/time/time.h"
 #include "base/values.h"
-#include "content/browser/attribution_reporting/attribution_aggregatable_sources.h"
+#include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/browser/attribution_reporting/attribution_source_type.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
@@ -210,7 +210,7 @@
             source_time,
             CommonSourceInfo::GetExpiryTime(expiry, source_time, *source_type),
             *source_type, priority, std::move(filter_data), debug_key,
-            AttributionAggregatableSources())),
+            AttributionAggregatableSource())),
         std::move(source));
   }
 
diff --git a/content/test/data/attribution_reporting/databases/version_31.sql b/content/test/data/attribution_reporting/databases/version_31.sql
index c71d6be..b3fd863a 100644
--- a/content/test/data/attribution_reporting/databases/version_31.sql
+++ b/content/test/data/attribution_reporting/databases/version_31.sql
@@ -48,4 +48,6 @@
 
 CREATE INDEX contribution_aggregation_id_idx ON aggregatable_contributions(aggregation_id);
 
+INSERT INTO conversions VALUES (1,2,3,4,5,6,7,8,9);
+
 COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_30.sql b/content/test/data/attribution_reporting/databases/version_32.sql
similarity index 77%
rename from content/test/data/attribution_reporting/databases/version_30.sql
rename to content/test/data/attribution_reporting/databases/version_32.sql
index 68a96ec..8f9c6aa 100644
--- a/content/test/data/attribution_reporting/databases/version_30.sql
+++ b/content/test/data/attribution_reporting/databases/version_32.sql
@@ -2,7 +2,7 @@
 
 BEGIN TRANSACTION;
 
-CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,impression_data INTEGER NOT NULL,impression_origin TEXT NOT NULL,conversion_origin TEXT NOT NULL,reporting_origin TEXT NOT NULL,impression_time INTEGER NOT NULL,expiry_time INTEGER NOT NULL,num_conversions INTEGER NOT NULL,active INTEGER NOT NULL,conversion_destination TEXT NOT NULL,source_type INTEGER NOT NULL,attributed_truthfully INTEGER NOT NULL,priority INTEGER NOT NULL,impression_site TEXT NOT NULL,debug_key INTEGER,aggregatable_budget_consumed INTEGER NOT NULL,aggregatable_sources BLOB NOT NULL,filter_data BLOB NOT NULL);
+CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,impression_data INTEGER NOT NULL,impression_origin TEXT NOT NULL,conversion_origin TEXT NOT NULL,reporting_origin TEXT NOT NULL,impression_time INTEGER NOT NULL,expiry_time INTEGER NOT NULL,num_conversions INTEGER NOT NULL,event_level_active INTEGER NOT NULL,aggregatable_active INTEGER NOT NULL,conversion_destination TEXT NOT NULL,source_type INTEGER NOT NULL,attributed_truthfully INTEGER NOT NULL,priority INTEGER NOT NULL,impression_site TEXT NOT NULL,debug_key INTEGER,aggregatable_budget_consumed INTEGER NOT NULL,aggregatable_source BLOB NOT NULL,filter_data BLOB NOT NULL);
 
 CREATE TABLE conversions(conversion_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,impression_id INTEGER NOT NULL,conversion_data INTEGER NOT NULL,conversion_time INTEGER NOT NULL,report_time INTEGER NOT NULL,priority INTEGER NOT NULL,failed_send_attempts INTEGER NOT NULL,external_report_id TEXT NOT NULL,debug_key INTEGER);
 
@@ -17,16 +17,16 @@
 CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
 
 INSERT INTO meta VALUES('mmap_status','-1');
-INSERT INTO meta VALUES('version','30');
-INSERT INTO meta VALUES('last_compatible_version','30');
+INSERT INTO meta VALUES('version','32');
+INSERT INTO meta VALUES('last_compatible_version','32');
 
-CREATE INDEX conversion_destination_idx ON impressions(active,conversion_destination,reporting_origin);
+CREATE INDEX conversion_destination_idx ON impressions(event_level_active,aggregatable_active,conversion_destination,reporting_origin);
 
 CREATE INDEX impression_expiry_idx ON impressions(expiry_time);
 
 CREATE INDEX impression_origin_idx ON impressions(impression_origin);
 
-CREATE INDEX impression_site_reporting_origin_idx ON impressions(impression_site,reporting_origin)WHERE active=1 AND num_conversions=0;
+CREATE INDEX impression_site_reporting_origin_idx ON impressions(impression_site,reporting_origin)WHERE event_level_active=1 AND num_conversions=0 AND aggregatable_active=1 AND aggregatable_budget_consumed=0;
 
 CREATE INDEX conversion_report_idx ON conversions(report_time);
 
@@ -48,6 +48,4 @@
 
 CREATE INDEX contribution_aggregation_id_idx ON aggregatable_contributions(aggregation_id);
 
-INSERT INTO conversions VALUES (1,2,3,4,5,6,7,8,9);
-
 COMMIT;
diff --git a/docs/windows_build_instructions.md b/docs/windows_build_instructions.md
index 91dd910..59aac88 100644
--- a/docs/windows_build_instructions.md
+++ b/docs/windows_build_instructions.md
@@ -93,7 +93,7 @@
 
 Add `C:\src\depot_tools` at the front. Note: If your system PATH has a Python in it, you will be out of luck.
 
-Also, add a DEPOT_TOOLS_WIN_TOOLCHAIN system variable in the same way, and set
+Also, add a DEPOT_TOOLS_WIN_TOOLCHAIN environment variable in the same way, and set
 it to 0. This tells depot_tools to use your locally installed version of Visual
 Studio (by default, depot_tools will try to use a google-internal version).
 
diff --git a/extensions/browser/event_router.cc b/extensions/browser/event_router.cc
index fed3a765..a843c1f 100644
--- a/extensions/browser/event_router.cc
+++ b/extensions/browser/event_router.cc
@@ -34,7 +34,6 @@
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/process_map.h"
 #include "extensions/common/constants.h"
-#include "extensions/common/event_filtering_info_type_converters.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_api.h"
 #include "extensions/common/extension_messages.h"
@@ -163,26 +162,22 @@
     UserGestureState user_gesture,
     mojom::EventFilteringInfoPtr info) {
   NotifyEventDispatched(browser_context, extension_id, event_name, *event_args);
-  mojom::DispatchEventParams params;
-  params.worker_thread_id = worker_thread_id;
-  params.extension_id = extension_id;
-  params.event_name = event_name;
-  params.event_id = event_id;
-  params.is_user_gesture = user_gesture == USER_GESTURE_ENABLED;
-  params.filtering_info = info->To<EventFilteringInfo>();
+  auto params = mojom::DispatchEventParams::New();
+  params->worker_thread_id = worker_thread_id;
+  params->extension_id = extension_id;
+  params->event_name = event_name;
+  params->event_id = event_id;
+  params->is_user_gesture = user_gesture == USER_GESTURE_ENABLED;
+  params->filtering_info = std::move(info);
 
-  // TODO(crbug/1222550): Remove IPC->Send call after worker_thread_dispatcher
-  // is also mojofied.
-  if (worker_thread_id == kMainThreadId) {
-    Get(browser_context)->RouteDispatchEvent(rph, params.Clone(), *event_args);
-  } else {
-    rph->Send(new ExtensionMsg_DispatchEvent(params, *event_args));
-  }
+  Get(browser_context)->RouteDispatchEvent(rph, std::move(params), *event_args);
 }
 
 void EventRouter::RouteDispatchEvent(content::RenderProcessHost* rph,
-                                     const mojom::DispatchEventParamsPtr params,
-                                     const ListValue& event_args) {
+                                     mojom::DispatchEventParamsPtr params,
+                                     ListValue& event_args) {
+  // TODO(crbug.com/1302000) Add bindings for worker threads to be directly
+  // channel-associated.
   mojo::AssociatedRemote<mojom::EventDispatcher>& dispatcher =
       rph_dispatcher_map_[rph];
   if (!dispatcher.is_bound()) {
@@ -193,7 +188,7 @@
     channel->GetRemoteAssociatedInterface(
         dispatcher.BindNewEndpointAndPassReceiver());
   }
-  dispatcher->DispatchEvent(params.Clone(), event_args.Clone());
+  dispatcher->DispatchEvent(std::move(params), event_args.Clone());
 }
 
 // static
diff --git a/extensions/browser/event_router.h b/extensions/browser/event_router.h
index 62def670..82adadd 100644
--- a/extensions/browser/event_router.h
+++ b/extensions/browser/event_router.h
@@ -449,8 +449,8 @@
                                int64_t service_worker_version_id);
 
   void RouteDispatchEvent(content::RenderProcessHost* rph,
-                          const mojom::DispatchEventParamsPtr params,
-                          const base::ListValue& event_args);
+                          mojom::DispatchEventParamsPtr params,
+                          base::ListValue& event_args);
 
   // static
   static void DoDispatchEventToSenderBookkeeping(
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index a2a26ee..21575366 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -173,18 +173,6 @@
       traits_sources =
           [ "//extensions/common/mojom/activation_sequence_mojom_traits.cc" ]
     },
-    {
-      types = [
-        {
-          mojom = "extensions.mojom.EventFilteringInfo"
-          cpp = "::extensions::EventFilteringInfo"
-        },
-      ]
-      traits_headers =
-          [ "//extensions/common/mojom/event_dispatcher_mojom_traits.h" ]
-      traits_sources =
-          [ "//extensions/common/mojom/event_dispatcher_mojom_traits.cc" ]
-    },
   ]
   overridden_deps = [ "//content/public/common:interfaces" ]
 
@@ -251,10 +239,6 @@
     "error_utils.h",
     "event_filter.cc",
     "event_filter.h",
-    "event_filtering_info.cc",
-    "event_filtering_info.h",
-    "event_filtering_info_type_converters.cc",
-    "event_filtering_info_type_converters.h",
     "event_matcher.cc",
     "event_matcher.h",
     "extension.cc",
diff --git a/extensions/common/event_filtering_info.cc b/extensions/common/event_filtering_info.cc
deleted file mode 100644
index 865b2b85..0000000
--- a/extensions/common/event_filtering_info.cc
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) 2012 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 "extensions/common/event_filtering_info.h"
-
-namespace extensions {
-
-EventFilteringInfo::EventFilteringInfo() {}
-EventFilteringInfo::EventFilteringInfo(const EventFilteringInfo& other) =
-    default;
-EventFilteringInfo::~EventFilteringInfo() {}
-
-}  // namespace extensions
diff --git a/extensions/common/event_filtering_info.h b/extensions/common/event_filtering_info.h
deleted file mode 100644
index 04af133..0000000
--- a/extensions/common/event_filtering_info.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) 2012 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 EXTENSIONS_COMMON_EVENT_FILTERING_INFO_H_
-#define EXTENSIONS_COMMON_EVENT_FILTERING_INFO_H_
-
-#include <memory>
-#include <string>
-
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "url/gurl.h"
-
-namespace extensions {
-
-// Extra information about an event that is used in event filtering.
-//
-// This is the information that is matched against criteria specified in JS
-// extension event listeners. Eg:
-//
-// chrome.someApi.onSomeEvent.addListener(cb,
-//                                        {url: [{hostSuffix: 'google.com'}],
-//                                         tabId: 1});
-struct EventFilteringInfo {
- public:
-  EventFilteringInfo();
-  EventFilteringInfo(const EventFilteringInfo& other);
-  ~EventFilteringInfo();
-
-  absl::optional<GURL> url;
-  absl::optional<std::string> service_type;
-  absl::optional<int> instance_id;
-
-  // Note: window type & visible are Chrome concepts, so arguably
-  // doesn't belong in the extensions module. If the number of Chrome
-  // concept grows, consider a delegation model with a
-  // ChromeEventFilteringInfo class.
-  absl::optional<std::string> window_type;
-
-  // By default events related to windows are filtered based on the
-  // listener's extension. This parameter will be set if the listener
-  // didn't set any filter on window types.
-  absl::optional<bool> window_exposed_by_default;
-
-  bool is_empty() const {
-    return !url && !service_type && !instance_id && !window_type &&
-           !window_exposed_by_default;
-  }
-};
-
-}  // namespace extensions
-
-#endif  // EXTENSIONS_COMMON_EVENT_FILTERING_INFO_H_
diff --git a/extensions/common/event_filtering_info_type_converters.cc b/extensions/common/event_filtering_info_type_converters.cc
deleted file mode 100644
index c6df0a0..0000000
--- a/extensions/common/event_filtering_info_type_converters.cc
+++ /dev/null
@@ -1,45 +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 "extensions/common/event_filtering_info_type_converters.h"
-
-namespace mojo {
-
-// static
-extensions::mojom::EventFilteringInfoPtr
-TypeConverter<extensions::mojom::EventFilteringInfoPtr,
-              extensions::EventFilteringInfo>::
-    Convert(const extensions::EventFilteringInfo& input) {
-  extensions::mojom::EventFilteringInfoPtr output =
-      extensions::mojom::EventFilteringInfo::New();
-  output->url = input.url;
-  output->service_type = input.service_type;
-  output->has_instance_id = input.instance_id.has_value();
-  if (output->has_instance_id)
-    output->instance_id = input.instance_id.value();
-  output->window_type = input.window_type;
-  output->has_window_exposed_by_default =
-      input.window_exposed_by_default.has_value();
-  if (output->has_window_exposed_by_default)
-    output->window_exposed_by_default = input.window_exposed_by_default.value();
-  return output;
-}
-
-// static
-extensions::EventFilteringInfo
-TypeConverter<extensions::EventFilteringInfo,
-              extensions::mojom::EventFilteringInfo>::
-    Convert(const extensions::mojom::EventFilteringInfo& input) {
-  extensions::EventFilteringInfo output;
-  output.url = input.url;
-  output.service_type = input.service_type;
-  if (input.has_instance_id)
-    output.instance_id = input.instance_id;
-  output.window_type = input.window_type;
-  if (input.has_window_exposed_by_default)
-    output.window_exposed_by_default = input.window_exposed_by_default;
-  return output;
-}
-
-}  // namespace mojo
\ No newline at end of file
diff --git a/extensions/common/event_filtering_info_type_converters.h b/extensions/common/event_filtering_info_type_converters.h
deleted file mode 100644
index 5617457..0000000
--- a/extensions/common/event_filtering_info_type_converters.h
+++ /dev/null
@@ -1,31 +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 EXTENSIONS_COMMON_EVENT_FILTERING_INFO_TYPE_CONVERTERS_H_
-#define EXTENSIONS_COMMON_EVENT_FILTERING_INFO_TYPE_CONVERTERS_H_
-
-#include "extensions/common/event_filtering_info.h"
-#include "extensions/common/mojom/event_dispatcher.mojom.h"
-#include "mojo/public/cpp/bindings/type_converter.h"
-
-namespace mojo {
-// TODO(crbug.com/1222550): Remove these converters once
-// extensions::EventFilteringInfo is removed.
-template <>
-struct TypeConverter<extensions::mojom::EventFilteringInfoPtr,
-                     extensions::EventFilteringInfo> {
-  static extensions::mojom::EventFilteringInfoPtr Convert(
-      const extensions::EventFilteringInfo& input);
-};
-
-template <>
-struct TypeConverter<extensions::EventFilteringInfo,
-                     extensions::mojom::EventFilteringInfo> {
-  static extensions::EventFilteringInfo Convert(
-      const extensions::mojom::EventFilteringInfo& input);
-};
-
-}  // namespace mojo
-
-#endif  // EXTENSIONS_COMMON_EVENT_FILTERING_INFO_TYPE_CONVERTERS_H_
\ No newline at end of file
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index 46c169d..3767ea7e 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -28,7 +28,6 @@
 #include "extensions/common/common_param_traits.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/draggable_region.h"
-#include "extensions/common/event_filtering_info.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_guid.h"
 #include "extensions/common/extensions_client.h"
@@ -138,27 +137,6 @@
   IPC_STRUCT_TRAITS_MEMBER(service_worker_version_id)
 IPC_STRUCT_TRAITS_END()
 
-IPC_STRUCT_TRAITS_BEGIN(extensions::mojom::DispatchEventParams)
-  // If this event is for a service worker, then this is the worker thread
-  // id. Otherwise, this is 0.
-  IPC_STRUCT_TRAITS_MEMBER(worker_thread_id)
-
-  // The id of the extension to dispatch the event to.
-  IPC_STRUCT_TRAITS_MEMBER(extension_id)
-
-  // The name of the event to dispatch.
-  IPC_STRUCT_TRAITS_MEMBER(event_name)
-
-  // The id of the event for use in the EventAck response message.
-  IPC_STRUCT_TRAITS_MEMBER(event_id)
-
-  // Whether or not the event is part of a user gesture.
-  IPC_STRUCT_TRAITS_MEMBER(is_user_gesture)
-
-  // Additional filtering info for the event.
-  IPC_STRUCT_TRAITS_MEMBER(filtering_info)
-IPC_STRUCT_TRAITS_END()
-
 // Struct containing information about the sender of connect() calls that
 // originate from a tab.
 IPC_STRUCT_BEGIN(ExtensionMsg_TabConnectionInfo)
@@ -272,14 +250,6 @@
   IPC_STRUCT_TRAITS_MEMBER(serialization_format)
 IPC_STRUCT_TRAITS_END()
 
-IPC_STRUCT_TRAITS_BEGIN(extensions::EventFilteringInfo)
-  IPC_STRUCT_TRAITS_MEMBER(url)
-  IPC_STRUCT_TRAITS_MEMBER(service_type)
-  IPC_STRUCT_TRAITS_MEMBER(instance_id)
-  IPC_STRUCT_TRAITS_MEMBER(window_type)
-  IPC_STRUCT_TRAITS_MEMBER(window_exposed_by_default)
-IPC_STRUCT_TRAITS_END()
-
 // Singly-included section for custom IPC traits.
 #ifndef INTERNAL_EXTENSIONS_COMMON_EXTENSION_MESSAGES_H_
 #define INTERNAL_EXTENSIONS_COMMON_EXTENSION_MESSAGES_H_
@@ -307,13 +277,6 @@
 
 // Messages sent from the browser to the renderer:
 
-// Sent to the renderer to dispatch an event to an extension.
-// Note: |event_args| is separate from the params to avoid having the message
-// take ownership.
-IPC_MESSAGE_CONTROL2(ExtensionMsg_DispatchEvent,
-                     extensions::mojom::DispatchEventParams /* params */,
-                     base::ListValue /* event_args */)
-
 // The browser's response to the ExtensionMsg_WakeEventPage IPC.
 IPC_MESSAGE_CONTROL2(ExtensionMsg_WakeEventPageResponse,
                      int /* request_id */,
diff --git a/extensions/common/mojom/event_dispatcher.mojom b/extensions/common/mojom/event_dispatcher.mojom
index 90f9677e..611d762 100644
--- a/extensions/common/mojom/event_dispatcher.mojom
+++ b/extensions/common/mojom/event_dispatcher.mojom
@@ -18,9 +18,6 @@
                   mojo_base.mojom.DeprecatedListValue event_args);
 };
 
-// Typemapped to extensions::EventFilteringInfo.
-// TODO(yochio): Convert extensions::EventFilteringInfo usage to
-// extensions::mojom::EventFilteringInfo (https://crbug.com/1222550)
 struct EventFilteringInfo {
   url.mojom.Url? url;
 
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index 22bf85b..0ae7b02 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -32,7 +32,6 @@
 #include "extensions/common/api/messaging/message.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/cors_util.h"
-#include "extensions/common/event_filtering_info_type_converters.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_api.h"
 #include "extensions/common/extension_features.h"
@@ -1302,6 +1301,11 @@
 
 void Dispatcher::DispatchEvent(mojom::DispatchEventParamsPtr params,
                                base::Value event_args) {
+  if (params->worker_thread_id != kMainThreadId) {
+    WorkerThreadDispatcher::Get()->DispatchEvent(std::move(params),
+                                                 std::move(event_args));
+    return;
+  }
   content::RenderFrame* background_frame =
       ExtensionFrameHelper::GetBackgroundPageFrame(params->extension_id);
 
@@ -1326,7 +1330,7 @@
 
   DispatchEventHelper(params->extension_id, params->event_name,
                       base::Value::AsListValue(event_args),
-                      mojom::EventFilteringInfo::From(params->filtering_info));
+                      std::move(params->filtering_info));
 
   if (background_frame) {
     // Tell the browser process when an event has been dispatched with a lazy
diff --git a/extensions/renderer/dispatcher.h b/extensions/renderer/dispatcher.h
index e2cb9769..3376c54 100644
--- a/extensions/renderer/dispatcher.h
+++ b/extensions/renderer/dispatcher.h
@@ -284,6 +284,8 @@
   void OnDispatchOnDisconnect(int worker_thread_id,
                               const PortId& port_id,
                               const std::string& error_message);
+
+  // EventDispatcher implementation.
   void DispatchEvent(mojom::DispatchEventParamsPtr params,
                      base::Value event_args) override;
 
@@ -381,8 +383,8 @@
   // it is dependent on other messages sent on other associated channels.
   mojo::AssociatedReceiver<mojom::Renderer> receiver_;
 
-  // Extensions Dipsatch receiver. This is an associated receiver because
-  // it is dependent on other messages sent on other associated channels.
+  // Extensions Dipsatch receiver. This is an associated receiver because it is
+  // dependent on other messages sent on other associated channels.
   mojo::AssociatedReceiver<mojom::EventDispatcher> dispatcher_;
 
   // Used to hold a service worker information which is ready to execute but the
diff --git a/extensions/renderer/worker_thread_dispatcher.cc b/extensions/renderer/worker_thread_dispatcher.cc
index 3486329..40419478 100644
--- a/extensions/renderer/worker_thread_dispatcher.cc
+++ b/extensions/renderer/worker_thread_dispatcher.cc
@@ -16,7 +16,6 @@
 #include "content/public/renderer/render_thread.h"
 #include "content/public/renderer/worker_thread.h"
 #include "extensions/common/constants.h"
-#include "extensions/common/event_filtering_info_type_converters.h"
 #include "extensions/common/extension_features.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/mojom/event_dispatcher.mojom.h"
@@ -33,8 +32,6 @@
 
 namespace {
 
-base::LazyInstance<WorkerThreadDispatcher>::DestructorAtExit
-    g_worker_thread_dispatcher_instance = LAZY_INSTANCE_INITIALIZER;
 base::LazyInstance<base::ThreadLocalPointer<extensions::ServiceWorkerData>>::
     DestructorAtExit g_data_tls = LAZY_INSTANCE_INITIALIZER;
 
@@ -122,11 +119,12 @@
 
 }  // namespace
 
-WorkerThreadDispatcher::WorkerThreadDispatcher() {}
-WorkerThreadDispatcher::~WorkerThreadDispatcher() {}
+WorkerThreadDispatcher::WorkerThreadDispatcher() = default;
+WorkerThreadDispatcher::~WorkerThreadDispatcher() = default;
 
 WorkerThreadDispatcher* WorkerThreadDispatcher::Get() {
-  return g_worker_thread_dispatcher_instance.Pointer();
+  static base::NoDestructor<WorkerThreadDispatcher> dispatcher;
+  return dispatcher.get();
 }
 
 void WorkerThreadDispatcher::Init(content::RenderThread* render_thread) {
@@ -162,7 +160,6 @@
 bool WorkerThreadDispatcher::HandlesMessageOnWorkerThread(
     const IPC::Message& message) {
   return message.type() == ExtensionMsg_ResponseWorker::ID ||
-         message.type() == ExtensionMsg_DispatchEvent::ID ||
          message.type() == ExtensionMsg_DispatchOnConnect::ID ||
          message.type() == ExtensionMsg_DeliverMessage::ID ||
          message.type() == ExtensionMsg_DispatchOnDisconnect::ID ||
@@ -186,6 +183,14 @@
                                       Dispatcher::GetWorkerScriptContextSet());
 }
 
+// static
+void WorkerThreadDispatcher::DispatchEventOnWorkerThread(
+    mojom::DispatchEventParamsPtr params,
+    base::Value event_args) {
+  auto* dispatcher = WorkerThreadDispatcher::Get();
+  dispatcher->DispatchEventHelper(std::move(params), std::move(event_args));
+}
+
 bool WorkerThreadDispatcher::OnControlMessageReceived(
     const IPC::Message& message) {
   if (HandlesMessageOnWorkerThread(message)) {
@@ -305,7 +310,6 @@
   bool handled = true;
   IPC_BEGIN_MESSAGE_MAP(WorkerThreadDispatcher, message)
     IPC_MESSAGE_HANDLER(ExtensionMsg_ResponseWorker, OnResponseWorker)
-    IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchEvent, OnDispatchEvent)
     IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnConnect, OnDispatchOnConnect)
     IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnDeliverMessage)
     IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect,
@@ -353,9 +357,10 @@
                                           error);
 }
 
-void WorkerThreadDispatcher::OnDispatchEvent(
-    const mojom::DispatchEventParams& params,
-    const base::ListValue& event_args) {
+void WorkerThreadDispatcher::DispatchEventHelper(
+    mojom::DispatchEventParamsPtr params,
+    base::Value event_args) {
+  DCHECK_EQ(params->worker_thread_id, content::WorkerThread::GetCurrentId());
   ServiceWorkerData* data = g_data_tls.Pointer()->Get();
   DCHECK(data);
 
@@ -364,21 +369,32 @@
   v8::Isolate* isolate = script_context->isolate();
   v8::HandleScope handle_scope(isolate);
   std::unique_ptr<InteractionProvider::Scope> scoped_extension_interaction;
-  if (params.is_user_gesture) {
+  if (params->is_user_gesture) {
     scoped_extension_interaction =
         ExtensionInteractionProvider::Scope::ForWorker(
             script_context->v8_context());
   }
-  mojom::EventFilteringInfoPtr filtering_info =
-      mojom::EventFilteringInfo::From(params.filtering_info);
+
   data->bindings_system()->DispatchEventInContext(
-      params.event_name, &event_args, filtering_info, data->context());
+      params->event_name, &base::Value::AsListValue(event_args),
+      std::move(params->filtering_info), data->context());
   const int worker_thread_id = content::WorkerThread::GetCurrentId();
   Send(new ExtensionHostMsg_EventAckWorker(data->context()->GetExtensionID(),
                                            data->service_worker_version_id(),
-                                           worker_thread_id, params.event_id));
+                                           worker_thread_id, params->event_id));
 }
 
+void WorkerThreadDispatcher::DispatchEvent(mojom::DispatchEventParamsPtr params,
+                                           base::Value event_args) {
+  DCHECK(!worker_thread_util::IsWorkerThread());
+  const int worker_thread_id = params->worker_thread_id;
+  // base::Unretained() is safe because the worker thread dispatcher is a lazily
+  // constructed global singleton which is never destroyed.
+  PostTaskToWorkerThread(
+      worker_thread_id,
+      base::BindOnce(&WorkerThreadDispatcher::DispatchEventOnWorkerThread,
+                     std::move(params), std::move(event_args)));
+}
 void WorkerThreadDispatcher::OnDispatchOnConnect(
     int worker_thread_id,
     const PortId& target_port_id,
diff --git a/extensions/renderer/worker_thread_dispatcher.h b/extensions/renderer/worker_thread_dispatcher.h
index 6e42ee9b..11a9375 100644
--- a/extensions/renderer/worker_thread_dispatcher.h
+++ b/extensions/renderer/worker_thread_dispatcher.h
@@ -50,7 +50,8 @@
 // worker thread (this TODO formerly referred to content::ThreadSafeSender
 // which no longer exists).
 class WorkerThreadDispatcher : public content::RenderThreadObserver,
-                               public IPC::Sender {
+                               public IPC::Sender,
+                               public mojom::EventDispatcher {
  public:
   WorkerThreadDispatcher();
 
@@ -154,6 +155,8 @@
   static bool HandlesMessageOnWorkerThread(const IPC::Message& message);
   static void ForwardIPC(int worker_thread_id, const IPC::Message& message);
   static void UpdateBindingsOnWorkerThread(const ExtensionId& extension_id);
+  static void DispatchEventOnWorkerThread(mojom::DispatchEventParamsPtr params,
+                                          base::Value event_args);
 
   void OnMessageReceivedOnWorkerThread(int worker_thread_id,
                                        const IPC::Message& message);
@@ -166,8 +169,6 @@
                         bool succeeded,
                         const base::ListValue& response,
                         const std::string& error);
-  void OnDispatchEvent(const mojom::DispatchEventParams& params,
-                       const base::ListValue& event_args);
   void OnValidateMessagePort(int worker_thread_id, const PortId& id);
   void OnDispatchOnConnect(int worker_thread_id,
                            const PortId& target_port_id,
@@ -181,6 +182,9 @@
                               const PortId& port_id,
                               const std::string& error_message);
 
+  void DispatchEventHelper(mojom::DispatchEventParamsPtr params,
+                           base::Value event_args);
+
   // IPC sender. Belongs to the render thread, but thread safe.
   scoped_refptr<IPC::SyncMessageFilter> message_filter_;
 
@@ -189,6 +193,12 @@
   base::Lock task_runner_map_lock_;
   scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
   mojo::AssociatedRemote<mojom::EventRouter> event_router_remote_;
+
+  // Mojo interface implementation, called from the main thread.
+  void DispatchEvent(mojom::DispatchEventParamsPtr params,
+                     base::Value event_args) override;
+
+  friend class Dispatcher;
 };
 
 }  // namespace extensions
diff --git a/gpu/command_buffer/common/gpu_memory_buffer_support.cc b/gpu/command_buffer/common/gpu_memory_buffer_support.cc
index bd55657..9ab8841e 100644
--- a/gpu/command_buffer/common/gpu_memory_buffer_support.cc
+++ b/gpu/command_buffer/common/gpu_memory_buffer_support.cc
@@ -50,13 +50,13 @@
 #if BUILDFLAG(IS_CHROMEOS)
       // Allow odd size for CrOS.
       // TODO(https://crbug.com/1208788, https://crbug.com/1224781): Merge this
-      // with the path that uses gfx::AllowOddHeightMultiPlanarBuffers.
+      // with the path that uses gfx::IsOddHeightMultiPlanarBuffersAllowed.
       return true;
 #else
       // U and V planes are subsampled by a factor of 2.
-      if (size.width() % 2)
+      if (size.width() % 2 && !gfx::IsOddWidthMultiPlanarBuffersAllowed())
         return false;
-      if (size.height() % 2 && !gfx::AllowOddHeightMultiPlanarBuffers())
+      if (size.height() % 2 && !gfx::IsOddHeightMultiPlanarBuffersAllowed())
         return false;
       return true;
 #endif  // BUILDFLAG(IS_CHROMEOS)
diff --git a/ios/chrome/app/application_delegate/app_state.mm b/ios/chrome/app/application_delegate/app_state.mm
index cd73290f..199c2df7 100644
--- a/ios/chrome/app/application_delegate/app_state.mm
+++ b/ios/chrome/app/application_delegate/app_state.mm
@@ -14,7 +14,6 @@
 #include "base/mac/foundation_util.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
-#include "base/task/post_task.h"
 #include "components/feature_engagement/public/event_constants.h"
 #include "components/feature_engagement/public/tracker.h"
 #include "components/metrics/metrics_service.h"
@@ -59,6 +58,7 @@
 #include "ios/public/provider/chrome/browser/signin/chrome_identity_service.h"
 #import "ios/public/provider/chrome/browser/user_feedback/user_feedback_provider.h"
 #include "ios/web/public/thread/web_task_traits.h"
+#include "ios/web/public/thread/web_thread.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "ui/base/device_form_factor.h"
@@ -70,7 +70,7 @@
 namespace {
 // Helper method to post |closure| on the UI thread.
 void PostTaskOnUIThread(base::OnceClosure closure) {
-  base::PostTask(FROM_HERE, {web::WebThread::UI}, std::move(closure));
+  web::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(closure));
 }
 NSString* const kStartupAttemptReset = @"StartupAttemptReset";
 
@@ -286,8 +286,8 @@
           if (strongSelf)
             strongSelf->_savingCookies = NO;
         }));
-    base::PostTask(
-        FROM_HERE, {web::WebThread::IO}, base::BindOnce(^{
+    web::GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(^{
           net::CookieStoreIOS* store = static_cast<net::CookieStoreIOS*>(
               getter->GetURLRequestContext()->cookie_store());
           // FlushStore() runs its callback on any thread. Jump back to UI.
diff --git a/ios/chrome/browser/application_context_impl.mm b/ios/chrome/browser/application_context_impl.mm
index 6f69e10..535c43e 100644
--- a/ios/chrome/browser/application_context_impl.mm
+++ b/ios/chrome/browser/application_context_impl.mm
@@ -16,7 +16,6 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/path_service.h"
 #include "base/strings/sys_string_conversions.h"
-#include "base/task/post_task.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/time/default_clock.h"
@@ -100,8 +99,8 @@
     ApplicationContextImpl* app_context,
     mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory>
         receiver) {
-  base::PostTask(FROM_HERE, {web::WebThread::UI},
-                 base::BindOnce(&RequestProxyResolvingSocketFactoryOnUIThread,
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&RequestProxyResolvingSocketFactoryOnUIThread,
                                 app_context, std::move(receiver)));
 }
 
@@ -534,7 +533,6 @@
       GetSharedURLLoaderFactory(),
       GetApplicationContext()->GetNetworkConnectionTracker(), ::GetChannel(),
       IOSChromeGCMProfileServiceFactory::GetProductCategoryForSubtypes(),
-      base::CreateSingleThreadTaskRunner({web::WebThread::UI}),
-      base::CreateSingleThreadTaskRunner({web::WebThread::IO}),
+      web::GetUIThreadTaskRunner({}), web::GetIOThreadTaskRunner({}),
       blocking_task_runner);
 }
diff --git a/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm b/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm
index e55313a5..ba8f366a 100644
--- a/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm
+++ b/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm
@@ -109,8 +109,8 @@
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
   LazyInitialize();
 
-  base::PostTask(
-      FROM_HERE, {web::WebThread::IO},
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(
           &ChromeBrowserStateImplIOData::ClearNetworkingHistorySinceOnIOThread,
           base::Unretained(io_data_), time, std::move(completion)));
diff --git a/ios/chrome/browser/browser_state/chrome_browser_state_io_data.mm b/ios/chrome/browser/browser_state/chrome_browser_state_io_data.mm
index b276931..0afe52d 100644
--- a/ios/chrome/browser/browser_state/chrome_browser_state_io_data.mm
+++ b/ios/chrome/browser/browser_state/chrome_browser_state_io_data.mm
@@ -21,7 +21,6 @@
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
-#include "base/task/post_task.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -106,7 +105,7 @@
                                                       pref_service);
 
   scoped_refptr<base::SingleThreadTaskRunner> io_task_runner =
-      base::CreateSingleThreadTaskRunner({web::WebThread::IO});
+      web::GetIOThreadTaskRunner({});
 
   chrome_http_user_agent_settings_.reset(
       new IOSChromeHttpUserAgentSettings(pref_service));
@@ -256,8 +255,7 @@
   // read from there.
   enable_metrics_.Init(metrics::prefs::kMetricsReportingEnabled,
                        GetApplicationContext()->GetLocalState());
-  enable_metrics_.MoveToSequence(
-      base::CreateSingleThreadTaskRunner({web::WebThread::IO}));
+  enable_metrics_.MoveToSequence(web::GetIOThreadTaskRunner({}));
 }
 
 bool ChromeBrowserStateIOData::GetMetricsEnabledStateOnIOThread() const {
@@ -385,13 +383,13 @@
 
   if (!context_getters->empty()) {
     if (web::WebThread::IsThreadInitialized(web::WebThread::IO)) {
-      base::PostTask(FROM_HERE, {web::WebThread::IO},
-                     base::BindOnce(&NotifyContextGettersOfShutdownOnIO,
+      web::GetIOThreadTaskRunner({})->PostTask(
+          FROM_HERE, base::BindOnce(&NotifyContextGettersOfShutdownOnIO,
                                     std::move(context_getters)));
     }
   }
 
-  bool posted = base::DeleteSoon(FROM_HERE, {web::WebThread::IO}, this);
+  bool posted = web::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE, this);
   if (!posted)
     delete this;
 }
diff --git a/ios/chrome/browser/browser_state/off_the_record_chrome_browser_state_impl.mm b/ios/chrome/browser/browser_state/off_the_record_chrome_browser_state_impl.mm
index 34590d3a..e6af9bc 100644
--- a/ios/chrome/browser/browser_state/off_the_record_chrome_browser_state_impl.mm
+++ b/ios/chrome/browser/browser_state/off_the_record_chrome_browser_state_impl.mm
@@ -7,7 +7,6 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/user_metrics.h"
 #include "base/notreached.h"
-#include "base/task/post_task.h"
 #include "base/task/sequenced_task_runner.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
 #include "components/profile_metrics/browser_profile_type.h"
@@ -127,6 +126,6 @@
   // BrowsingDataRemover will never be destroyed and the dialog will never be
   // closed. We must do this asynchronously in order to avoid reentrancy issues.
   if (!completion.is_null()) {
-    base::PostTask(FROM_HERE, {web::WebThread::UI}, std::move(completion));
+    web::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(completion));
   }
 }
diff --git a/ios/chrome/browser/browser_state/off_the_record_chrome_browser_state_io_data.mm b/ios/chrome/browser/browser_state/off_the_record_chrome_browser_state_io_data.mm
index e68e38f..51979a0 100644
--- a/ios/chrome/browser/browser_state/off_the_record_chrome_browser_state_io_data.mm
+++ b/ios/chrome/browser/browser_state/off_the_record_chrome_browser_state_io_data.mm
@@ -12,7 +12,6 @@
 #include "base/callback_helpers.h"
 #include "base/check_op.h"
 #include "base/command_line.h"
-#include "base/task/post_task.h"
 #include "components/net_log/chrome_net_log.h"
 #include "components/prefs/pref_service.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
@@ -55,8 +54,8 @@
   // The cache for the incognito profile is in RAM.
   scoped_refptr<net::URLRequestContextGetter> getter =
       main_request_context_getter_;
-  base::PostTask(
-      FROM_HERE, {web::WebThread::IO}, base::BindOnce(^{
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(^{
         DCHECK_CURRENTLY_ON(web::WebThread::IO);
         net::HttpCache* cache = getter->GetURLRequestContext()
                                     ->http_transaction_factory()
diff --git a/ios/chrome/browser/browser_state/test_chrome_browser_state.mm b/ios/chrome/browser/browser_state/test_chrome_browser_state.mm
index 09a85e67..2b7309ce 100644
--- a/ios/chrome/browser/browser_state/test_chrome_browser_state.mm
+++ b/ios/chrome/browser/browser_state/test_chrome_browser_state.mm
@@ -15,7 +15,6 @@
 #include "base/memory/ptr_util.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
-#include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
 #include "base/test/test_file_util.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -45,8 +44,7 @@
   const base::FilePath& browser_state_path = context->GetStatePath();
   return std::make_unique<WebDataServiceWrapper>(
       browser_state_path, GetApplicationContext()->GetApplicationLocale(),
-      base::CreateSingleThreadTaskRunner({web::WebThread::UI}),
-      base::DoNothing());
+      web::GetUIThreadTaskRunner({}), base::DoNothing());
 }
 
 }  // namespace
@@ -239,8 +237,7 @@
 
 net::URLRequestContextGetter* TestChromeBrowserState::CreateRequestContext(
     ProtocolHandlerMap* protocol_handlers) {
-  return new net::TestURLRequestContextGetter(
-      base::CreateSingleThreadTaskRunner({web::WebThread::IO}));
+  return new net::TestURLRequestContextGetter(web::GetIOThreadTaskRunner({}));
 }
 
 void TestChromeBrowserState::CreateWebDataService() {
diff --git a/ios/chrome/browser/browsing_data/cache_counter.cc b/ios/chrome/browser/browsing_data/cache_counter.cc
index 5621c50..141c812 100644
--- a/ios/chrome/browser/browsing_data/cache_counter.cc
+++ b/ios/chrome/browser/browsing_data/cache_counter.cc
@@ -4,7 +4,6 @@
 
 #include "ios/chrome/browser/browsing_data/cache_counter.h"
 #include "base/bind.h"
-#include "base/task/post_task.h"
 #include "components/browsing_data/core/pref_names.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/web/public/browser_state.h"
@@ -33,8 +32,8 @@
         backend_(nullptr) {}
 
   void Count() {
-    base::PostTask(FROM_HERE, {web::WebThread::IO},
-                   base::BindRepeating(&IOThreadCacheCounter::CountInternal,
+    web::GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindRepeating(&IOThreadCacheCounter::CountInternal,
                                        base::Unretained(this), net::OK));
   }
 
@@ -84,8 +83,8 @@
         case STEP_CALLBACK: {
           result_ = rv;
 
-          base::PostTask(
-              FROM_HERE, {web::WebThread::UI},
+          web::GetUIThreadTaskRunner({})->PostTask(
+              FROM_HERE,
               base::BindOnce(&IOThreadCacheCounter::OnCountingFinished,
                              base::Unretained(this)));
 
diff --git a/ios/chrome/browser/browsing_data/cache_counter_unittest.cc b/ios/chrome/browser/browsing_data/cache_counter_unittest.cc
index bfdee818..bfcb766 100644
--- a/ios/chrome/browser/browsing_data/cache_counter_unittest.cc
+++ b/ios/chrome/browser/browsing_data/cache_counter_unittest.cc
@@ -14,7 +14,6 @@
 
 #include "base/bind.h"
 #include "base/run_loop.h"
-#include "base/task/post_task.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "components/browsing_data/core/browsing_data_utils.h"
@@ -63,8 +62,8 @@
     current_operation_ = OPERATION_ADD_ENTRY;
     next_step_ = STEP_GET_BACKEND;
 
-    base::PostTask(FROM_HERE, {web::WebThread::IO},
-                   base::BindOnce(&CacheCounterTest::CacheOperationStep,
+    web::GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(&CacheCounterTest::CacheOperationStep,
                                   base::Unretained(this), net::OK));
     WaitForIOThread();
   }
@@ -74,8 +73,8 @@
     current_operation_ = OPERATION_CLEAR_CACHE;
     next_step_ = STEP_GET_BACKEND;
 
-    base::PostTask(FROM_HERE, {web::WebThread::IO},
-                   base::BindOnce(&CacheCounterTest::CacheOperationStep,
+    web::GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(&CacheCounterTest::CacheOperationStep,
                                   base::Unretained(this), net::OK));
     WaitForIOThread();
   }
@@ -198,8 +197,8 @@
           if (current_operation_ == OPERATION_ADD_ENTRY)
             entry_->Close();
 
-          base::PostTask(FROM_HERE, {web::WebThread::UI},
-                         base::BindOnce(&CacheCounterTest::Callback,
+          web::GetUIThreadTaskRunner({})->PostTask(
+              FROM_HERE, base::BindOnce(&CacheCounterTest::Callback,
                                         base::Unretained(this)));
 
           break;
diff --git a/ios/chrome/browser/gcm/ios_chrome_gcm_profile_service_factory.mm b/ios/chrome/browser/gcm/ios_chrome_gcm_profile_service_factory.mm
index 597bef9..0cb442f 100644
--- a/ios/chrome/browser/gcm/ios_chrome_gcm_profile_service_factory.mm
+++ b/ios/chrome/browser/gcm/ios_chrome_gcm_profile_service_factory.mm
@@ -8,7 +8,6 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/no_destructor.h"
-#include "base/task/post_task.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "build/branding_buildflags.h"
@@ -52,11 +51,10 @@
     base::WeakPtr<gcm::GCMProfileService> service,
     mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory>
         receiver) {
-  base::CreateSingleThreadTaskRunner({web::WebThread::UI})
-      ->PostTask(
-          FROM_HERE,
-          base::BindOnce(&RequestProxyResolvingSocketFactoryOnUIThread, context,
-                         std::move(service), std::move(receiver)));
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
+      base::BindOnce(&RequestProxyResolvingSocketFactoryOnUIThread, context,
+                     std::move(service), std::move(receiver)));
 }
 
 }  // namespace
@@ -112,7 +110,6 @@
       GetProductCategoryForSubtypes(),
       IdentityManagerFactory::GetForBrowserState(browser_state),
       base::WrapUnique(new gcm::GCMClientFactory),
-      base::CreateSingleThreadTaskRunner({web::WebThread::UI}),
-      base::CreateSingleThreadTaskRunner({web::WebThread::IO}),
+      web::GetUIThreadTaskRunner({}), web::GetIOThreadTaskRunner({}),
       blocking_task_runner);
 }
diff --git a/ios/chrome/browser/ios_chrome_io_thread.mm b/ios/chrome/browser/ios_chrome_io_thread.mm
index f70fbdb..f32fd802 100644
--- a/ios/chrome/browser/ios_chrome_io_thread.mm
+++ b/ios/chrome/browser/ios_chrome_io_thread.mm
@@ -4,12 +4,12 @@
 
 #include "ios/chrome/browser/ios_chrome_io_thread.h"
 
-#include "base/task/post_task.h"
 #include "components/variations/net/variations_http_headers.h"
 #include "ios/chrome/browser/net/ios_chrome_network_delegate.h"
 #include "ios/chrome/common/channel_info.h"
 #include "ios/web/public/init/network_context_owner.h"
 #include "ios/web/public/thread/web_task_traits.h"
+#include "ios/web/public/thread/web_thread.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -69,7 +69,7 @@
     shared_url_loader_factory_->Detach();
 
   if (network_context_) {
-    base::DeleteSoon(FROM_HERE, {web::WebThread::IO},
-                     network_context_owner_.release());
+    web::GetIOThreadTaskRunner({})->DeleteSoon(
+        FROM_HERE, network_context_owner_.release());
   }
 }
diff --git a/ios/chrome/browser/ios_chrome_main_parts.mm b/ios/chrome/browser/ios_chrome_main_parts.mm
index 593cfcf8..fbe8b0f9 100644
--- a/ios/chrome/browser/ios_chrome_main_parts.mm
+++ b/ios/chrome/browser/ios_chrome_main_parts.mm
@@ -15,7 +15,6 @@
 #include "base/metrics/user_metrics.h"
 #include "base/path_service.h"
 #include "base/strings/sys_string_conversions.h"
-#include "base/task/post_task.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/time/default_tick_clock.h"
@@ -387,8 +386,7 @@
 
 // This will be called after the command-line has been mutated by about:flags
 void IOSChromeMainParts::SetUpFieldTrials() {
-  base::SetRecordActionTaskRunner(
-      base::CreateSingleThreadTaskRunner({web::WebThread::UI}));
+  base::SetRecordActionTaskRunner(web::GetUIThreadTaskRunner({}));
 
   // FeatureList requires VariationsIdsProvider to be created.
   variations::VariationsIdsProvider::Create(
diff --git a/ios/chrome/browser/net/chrome_cookie_store_ios_client.mm b/ios/chrome/browser/net/chrome_cookie_store_ios_client.mm
index d638a5e..0b22a35 100644
--- a/ios/chrome/browser/net/chrome_cookie_store_ios_client.mm
+++ b/ios/chrome/browser/net/chrome_cookie_store_ios_client.mm
@@ -4,7 +4,6 @@
 
 #include "ios/chrome/browser/net/chrome_cookie_store_ios_client.h"
 
-#include "base/task/post_task.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
 
@@ -16,5 +15,5 @@
 
 scoped_refptr<base::SequencedTaskRunner>
 ChromeCookieStoreIOSClient::GetTaskRunner() const {
-  return base::CreateSingleThreadTaskRunner({web::WebThread::IO});
+  return web::GetIOThreadTaskRunner({});
 }
diff --git a/ios/chrome/browser/net/cookie_util.mm b/ios/chrome/browser/net/cookie_util.mm
index 52a5c625..097c1e8 100644
--- a/ios/chrome/browser/net/cookie_util.mm
+++ b/ios/chrome/browser/net/cookie_util.mm
@@ -13,7 +13,6 @@
 #include "base/callback_helpers.h"
 #include "base/check.h"
 #include "base/memory/ref_counted.h"
-#include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/net/cookies/cookie_store_ios.h"
@@ -46,7 +45,7 @@
     net::CookieCryptoDelegate* crypto_delegate) {
   return scoped_refptr<net::SQLitePersistentCookieStore>(
       new net::SQLitePersistentCookieStore(
-          path, base::CreateSingleThreadTaskRunner({web::WebThread::IO}),
+          path, web::GetIOThreadTaskRunner({}),
           base::ThreadPool::CreateSequencedTaskRunner(
               {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
                base::TaskShutdownBehavior::BLOCK_SHUTDOWN}),
@@ -126,7 +125,8 @@
 void ClearSessionCookies(ChromeBrowserState* browser_state) {
   scoped_refptr<net::URLRequestContextGetter> getter =
       browser_state->GetRequestContext();
-  base::PostTask(FROM_HERE, {web::WebThread::IO}, base::BindOnce(^{
+  web::GetIOThreadTaskRunner({})
+      ->PostTask(FROM_HERE, base::BindOnce(^{
                    getter->GetURLRequestContext()
                        ->cookie_store()
                        ->DeleteSessionCookiesAsync(base::DoNothing());
diff --git a/ios/chrome/browser/net/ios_chrome_http_user_agent_settings.mm b/ios/chrome/browser/net/ios_chrome_http_user_agent_settings.mm
index 07ddc11e..c0ffb3f 100644
--- a/ios/chrome/browser/net/ios_chrome_http_user_agent_settings.mm
+++ b/ios/chrome/browser/net/ios_chrome_http_user_agent_settings.mm
@@ -4,7 +4,6 @@
 
 #include "ios/chrome/browser/net/ios_chrome_http_user_agent_settings.h"
 
-#include "base/task/post_task.h"
 #include "components/language/core/browser/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "ios/web/public/thread/web_task_traits.h"
@@ -23,8 +22,7 @@
   last_pref_accept_language_ = *pref_accept_language_;
   last_http_accept_language_ =
       net::HttpUtil::GenerateAcceptLanguageHeader(last_pref_accept_language_);
-  pref_accept_language_.MoveToSequence(
-      base::CreateSingleThreadTaskRunner({web::WebThread::IO}));
+  pref_accept_language_.MoveToSequence(web::GetIOThreadTaskRunner({}));
 }
 
 IOSChromeHttpUserAgentSettings::~IOSChromeHttpUserAgentSettings() {
diff --git a/ios/chrome/browser/net/ios_chrome_network_delegate.cc b/ios/chrome/browser/net/ios_chrome_network_delegate.cc
index c0341dc3..6311f79f 100644
--- a/ios/chrome/browser/net/ios_chrome_network_delegate.cc
+++ b/ios/chrome/browser/net/ios_chrome_network_delegate.cc
@@ -14,7 +14,6 @@
 #include "base/logging.h"
 #include "base/metrics/histogram.h"
 #include "base/path_service.h"
-#include "base/task/post_task.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/prefs/pref_member.h"
 #include "components/prefs/pref_service.h"
@@ -57,8 +56,7 @@
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
   if (enable_do_not_track) {
     enable_do_not_track->Init(prefs::kEnableDoNotTrack, pref_service);
-    enable_do_not_track->MoveToSequence(
-        base::CreateSingleThreadTaskRunner({web::WebThread::IO}));
+    enable_do_not_track->MoveToSequence(web::GetIOThreadTaskRunner({}));
   }
 }
 
diff --git a/ios/chrome/browser/net/ios_chrome_url_request_context_getter.cc b/ios/chrome/browser/net/ios_chrome_url_request_context_getter.cc
index 7eff447..48e34078 100644
--- a/ios/chrome/browser/net/ios_chrome_url_request_context_getter.cc
+++ b/ios/chrome/browser/net/ios_chrome_url_request_context_getter.cc
@@ -6,7 +6,6 @@
 
 #include "base/bind.h"
 #include "base/compiler_specific.h"
-#include "base/task/post_task.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state_io_data.h"
 #include "ios/chrome/browser/ios_chrome_io_thread.h"
 #include "ios/web/public/thread/web_task_traits.h"
@@ -97,7 +96,7 @@
 
 scoped_refptr<base::SingleThreadTaskRunner>
 IOSChromeURLRequestContextGetter::GetNetworkTaskRunner() const {
-  return base::CreateSingleThreadTaskRunner({web::WebThread::IO});
+  return web::GetIOThreadTaskRunner({});
 }
 
 // static
diff --git a/ios/chrome/browser/omaha/omaha_service.mm b/ios/chrome/browser/omaha/omaha_service.mm
index 281c550..7ba5c07 100644
--- a/ios/chrome/browser/omaha/omaha_service.mm
+++ b/ios/chrome/browser/omaha/omaha_service.mm
@@ -20,7 +20,6 @@
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/system/sys_info.h"
-#include "base/task/post_task.h"
 #include "base/time/time.h"
 #include "base/values.h"
 #include "build/branding_buildflags.h"
@@ -370,8 +369,8 @@
          !service->url_loader_factory_);
   service->pending_url_loader_factory_ = std::move(pending_url_loader_factory);
   service->locale_lang_ = GetApplicationContext()->GetApplicationLocale();
-  base::PostTask(FROM_HERE, {web::WebThread::IO},
-                 base::BindOnce(&OmahaService::SendOrScheduleNextPing,
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&OmahaService::SendOrScheduleNextPing,
                                 base::Unretained(service)));
 }
 
@@ -497,16 +496,16 @@
     base::OnceCallback<void(base::DictionaryValue*)> callback) {
   if (OmahaService::IsEnabled()) {
     OmahaService* service = GetInstance();
-    base::PostTask(
-        FROM_HERE, {web::WebThread::IO},
+    web::GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE,
         base::BindOnce(&OmahaService::GetDebugInformationOnIOThread,
                        base::Unretained(service), std::move(callback)));
 
   } else {
     auto result = std::make_unique<base::DictionaryValue>();
     // Invoke the callback with an empty response.
-    base::PostTask(
-        FROM_HERE, {web::WebThread::UI},
+    web::GetUIThreadTaskRunner({})->PostTask(
+        FROM_HERE,
         base::BindOnce(std::move(callback), base::Owned(result.release())));
   }
 }
@@ -776,16 +775,16 @@
   if (details) {
     // Use the correct callback based on if a one-off check is ongoing.
     if (!one_off_check_callback_.is_null()) {
-      base::PostTask(
-          FROM_HERE, {web::WebThread::UI},
+      web::GetUIThreadTaskRunner({})->PostTask(
+          FROM_HERE,
           base::BindOnce(std::move(one_off_check_callback_), *details));
       // Do not schedule another ping for one-off checks, unless
       // it canceled a scheduled ping.
       need_to_schedule_ping = scheduled_ping_canceled_;
       scheduled_ping_canceled_ = false;
     } else if (!details->is_up_to_date) {
-      base::PostTask(FROM_HERE, {web::WebThread::UI},
-                     base::BindOnce(upgrade_recommended_callback_, *details));
+      web::GetUIThreadTaskRunner({})->PostTask(
+          FROM_HERE, base::BindOnce(upgrade_recommended_callback_, *details));
     }
   }
 
@@ -820,8 +819,8 @@
                         (timer_.desired_run_time() - base::TimeTicks::Now())));
 
   // Sending the value to the callback.
-  base::PostTask(
-      FROM_HERE, {web::WebThread::UI},
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(std::move(callback), base::Owned(result.release())));
 }
 
diff --git a/ios/chrome/browser/policy/chrome_browser_cloud_management_controller_ios.mm b/ios/chrome/browser/policy/chrome_browser_cloud_management_controller_ios.mm
index e6da04d..db46789 100644
--- a/ios/chrome/browser/policy/chrome_browser_cloud_management_controller_ios.mm
+++ b/ios/chrome/browser/policy/chrome_browser_cloud_management_controller_ios.mm
@@ -7,7 +7,6 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "components/enterprise/browser/reporting/report_generator.h"
 #include "components/enterprise/browser/reporting/report_scheduler.h"
@@ -113,8 +112,7 @@
 scoped_refptr<base::SingleThreadTaskRunner>
 ChromeBrowserCloudManagementControllerIOS::GetBestEffortTaskRunner() {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
-  return base::CreateSingleThreadTaskRunner(
-      {web::WebThread::UI, base::TaskPriority::BEST_EFFORT});
+  return web::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT});
 }
 
 std::unique_ptr<enterprise_reporting::ReportingDelegateFactory>
diff --git a/ios/chrome/browser/safe_browsing/fake_safe_browsing_service.mm b/ios/chrome/browser/safe_browsing/fake_safe_browsing_service.mm
index f74a5a6..aca24b04a 100644
--- a/ios/chrome/browser/safe_browsing/fake_safe_browsing_service.mm
+++ b/ios/chrome/browser/safe_browsing/fake_safe_browsing_service.mm
@@ -5,7 +5,6 @@
 #include "ios/chrome/browser/safe_browsing/fake_safe_browsing_service.h"
 
 #include "base/callback_helpers.h"
-#include "base/task/post_task.h"
 #include "components/safe_browsing/core/browser/db/test_database_manager.h"
 #include "components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h"
 #import "ios/chrome/browser/safe_browsing/url_checker_delegate_impl.h"
@@ -33,7 +32,7 @@
                 []() { return static_cast<web::WebState*>(nullptr); }),
             /*real_time_lookup_enabled=*/false,
             /*can_rt_check_subresource_url=*/false,
-            base::CreateSingleThreadTaskRunner({web::WebThread::UI}),
+            web::GetUIThreadTaskRunner({}),
             /*url_lookup_service_on_ui=*/nullptr) {}
   ~FakeSafeBrowsingUrlCheckerImpl() override = default;
 
diff --git a/ios/chrome/browser/safe_browsing/safe_browsing_query_manager.mm b/ios/chrome/browser/safe_browsing/safe_browsing_query_manager.mm
index a498a0c..1594006a 100644
--- a/ios/chrome/browser/safe_browsing/safe_browsing_query_manager.mm
+++ b/ios/chrome/browser/safe_browsing/safe_browsing_query_manager.mm
@@ -6,10 +6,10 @@
 
 #include "base/callback_helpers.h"
 #include "base/check_op.h"
-#include "base/task/post_task.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "ios/web/public/thread/web_task_traits.h"
+#include "ios/web/public/thread/web_thread.h"
 #include "services/network/public/mojom/fetch_api.mojom.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -41,8 +41,8 @@
     observer.SafeBrowsingQueryManagerDestroyed(this);
   }
 
-  base::DeleteSoon(FROM_HERE, {web::WebThread::IO},
-                   url_checker_client_.release());
+  web::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE,
+                                             url_checker_client_.release());
 }
 
 void SafeBrowsingQueryManager::AddObserver(Observer* observer) {
@@ -67,8 +67,8 @@
   base::OnceCallback<void(bool proceed, bool show_error_page)> callback =
       base::BindOnce(&SafeBrowsingQueryManager::UrlCheckFinished,
                      weak_factory_.GetWeakPtr(), query);
-  base::PostTask(
-      FROM_HERE, {web::WebThread::IO},
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&UrlCheckerClient::CheckUrl,
                      url_checker_client_->AsWeakPtr(), std::move(url_checker),
                      query.url, query.http_method, std::move(callback)));
@@ -203,8 +203,8 @@
   DCHECK(url_checker);
 
   auto it = active_url_checkers_.find(url_checker);
-  base::PostTask(
-      FROM_HERE, {web::WebThread::UI},
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(std::move(it->second), proceed, showed_interstitial));
 
   active_url_checkers_.erase(it);
diff --git a/ios/chrome/browser/safe_browsing/safe_browsing_service_impl.mm b/ios/chrome/browser/safe_browsing/safe_browsing_service_impl.mm
index 54a1b63..a74059c 100644
--- a/ios/chrome/browser/safe_browsing/safe_browsing_service_impl.mm
+++ b/ios/chrome/browser/safe_browsing/safe_browsing_service_impl.mm
@@ -7,7 +7,6 @@
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/path_service.h"
-#include "base/task/post_task.h"
 #include "build/branding_buildflags.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
@@ -58,9 +57,8 @@
   base::FilePath safe_browsing_data_path =
       user_data_path.Append(safe_browsing::kSafeBrowsingBaseFilename);
   safe_browsing_db_manager_ = safe_browsing::V4LocalDatabaseManager::Create(
-      safe_browsing_data_path,
-      base::CreateSingleThreadTaskRunner({web::WebThread::UI}),
-      base::CreateSingleThreadTaskRunner({web::WebThread::IO}),
+      safe_browsing_data_path, web::GetUIThreadTaskRunner({}),
+      web::GetIOThreadTaskRunner({}),
       safe_browsing::ExtendedReportingLevelCallback());
 
   url_checker_delegate_ =
@@ -69,8 +67,8 @@
   io_thread_enabler_ =
       base::MakeRefCounted<IOThreadEnabler>(safe_browsing_db_manager_);
 
-  base::PostTask(
-      FROM_HERE, {web::WebThread::IO},
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&IOThreadEnabler::Initialize, io_thread_enabler_,
                      base::WrapRefCounted(this),
                      network_context_client_.BindNewPipeAndPassReceiver(),
@@ -107,8 +105,8 @@
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
 
   pref_change_registrar_.reset();
-  base::PostTask(
-      FROM_HERE, {web::WebThread::IO},
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&IOThreadEnabler::ShutDown, io_thread_enabler_));
   network_context_client_.reset();
 }
@@ -127,8 +125,7 @@
   return std::make_unique<safe_browsing::SafeBrowsingUrlCheckerImpl>(
       request_destination, url_checker_delegate_,
       web_state->CreateDefaultGetter(), can_perform_full_url_lookup,
-      can_realtime_check_subresource_url,
-      base::CreateSingleThreadTaskRunner({web::WebThread::UI}),
+      can_realtime_check_subresource_url, web::GetUIThreadTaskRunner({}),
       url_lookup_service ? url_lookup_service->GetWeakPtr() : nullptr);
 }
 
@@ -151,13 +148,12 @@
     base::OnceClosure callback) {
   if (creation_range.start() == base::Time() &&
       creation_range.end() == base::Time::Max()) {
-    base::PostTask(
-        FROM_HERE,
-        {web::WebThread::IO, base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
-        base::BindOnce(&IOThreadEnabler::ClearAllCookies, io_thread_enabler_,
-                       std::move(callback)));
+    web::GetIOThreadTaskRunner({base::TaskShutdownBehavior::BLOCK_SHUTDOWN})
+        ->PostTask(FROM_HERE,
+                   base::BindOnce(&IOThreadEnabler::ClearAllCookies,
+                                  io_thread_enabler_, std::move(callback)));
   } else {
-    base::PostTask(FROM_HERE, {web::WebThread::IO}, std::move(callback));
+    web::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, std::move(callback));
   }
 }
 
@@ -170,8 +166,8 @@
 void SafeBrowsingServiceImpl::UpdateSafeBrowsingEnabledState() {
   bool enabled =
       pref_change_registrar_->prefs()->GetBoolean(prefs::kSafeBrowsingEnabled);
-  base::PostTask(FROM_HERE, {web::WebThread::IO},
-                 base::BindOnce(&IOThreadEnabler::SetSafeBrowsingEnabled,
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&IOThreadEnabler::SetSafeBrowsingEnabled,
                                 io_thread_enabler_, enabled));
 }
 
@@ -270,8 +266,8 @@
 void SafeBrowsingServiceImpl::IOThreadEnabler::SetUpURLLoaderFactory(
     scoped_refptr<SafeBrowsingServiceImpl> safe_browsing_service) {
   DCHECK_CURRENTLY_ON(web::WebThread::IO);
-  base::PostTask(
-      FROM_HERE, {web::WebThread::UI},
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&SafeBrowsingServiceImpl::SetUpURLLoaderFactory,
                      safe_browsing_service,
                      url_loader_factory_.BindNewPipeAndPassReceiver()));
diff --git a/ios/chrome/browser/safe_browsing/safe_browsing_service_unittest.mm b/ios/chrome/browser/safe_browsing/safe_browsing_service_unittest.mm
index f6da534b..9ec1269 100644
--- a/ios/chrome/browser/safe_browsing/safe_browsing_service_unittest.mm
+++ b/ios/chrome/browser/safe_browsing/safe_browsing_service_unittest.mm
@@ -7,7 +7,6 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
-#include "base/task/post_task.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "components/prefs/pref_service.h"
@@ -84,8 +83,8 @@
     result_pending_ = true;
     url_checker_ = safe_browsing_service_->CreateUrlChecker(
         network::mojom::RequestDestination::kDocument, &web_state_);
-    base::PostTask(FROM_HERE, {web::WebThread::IO},
-                   base::BindOnce(&TestUrlCheckerClient::CheckUrlOnIOThread,
+    web::GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(&TestUrlCheckerClient::CheckUrlOnIOThread,
                                   base::Unretained(this), url));
   }
 
@@ -93,8 +92,8 @@
     result_pending_ = true;
     url_checker_ = safe_browsing_service_->CreateUrlChecker(
         network::mojom::RequestDestination::kIframe, &web_state_);
-    base::PostTask(FROM_HERE, {web::WebThread::IO},
-                   base::BindOnce(&TestUrlCheckerClient::CheckUrlOnIOThread,
+    web::GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(&TestUrlCheckerClient::CheckUrlOnIOThread,
                                   base::Unretained(this), url));
   }
 
@@ -184,16 +183,16 @@
   }
 
   void MarkUrlAsMalware(const GURL& bad_url) {
-    base::PostTask(
-        FROM_HERE, {web::WebThread::IO},
+    web::GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE,
         base::BindOnce(&SafeBrowsingServiceTest::MarkUrlAsMalwareOnIOThread,
                        base::Unretained(this), bad_url));
   }
 
   // Adds the given |safe_url| to the allowlist used by real-time checks.
   void MarkUrlAsRealTimeSafe(const GURL& safe_url) {
-    base::PostTask(
-        FROM_HERE, {web::WebThread::IO},
+    web::GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE,
         base::BindOnce(&SafeBrowsingServiceTest::MarkUrlAsSafeOnIOThread,
                        base::Unretained(this), safe_url));
   }
diff --git a/ios/chrome/browser/safe_browsing/url_checker_delegate_impl.mm b/ios/chrome/browser/safe_browsing/url_checker_delegate_impl.mm
index 0e4c112..bc465f6 100644
--- a/ios/chrome/browser/safe_browsing/url_checker_delegate_impl.mm
+++ b/ios/chrome/browser/safe_browsing/url_checker_delegate_impl.mm
@@ -4,7 +4,6 @@
 
 #include "ios/chrome/browser/safe_browsing/url_checker_delegate_impl.h"
 
-#include "base/task/post_task.h"
 #include "components/safe_browsing/core/browser/db/database_manager.h"
 #include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
 #import "components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h"
@@ -101,8 +100,8 @@
     bool has_user_gesture) {
   // Query the allow list on the UI thread to determine whether the navigation
   // can proceed.
-  base::PostTask(
-      FROM_HERE, {web::WebThread::UI},
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&HandleBlockingPageRequestOnUIThread, resource));
 }
 
diff --git a/ios/chrome/browser/share_extension/share_extension_item_receiver.mm b/ios/chrome/browser/share_extension/share_extension_item_receiver.mm
index 218e0c6..09e08ceda 100644
--- a/ios/chrome/browser/share_extension/share_extension_item_receiver.mm
+++ b/ios/chrome/browser/share_extension/share_extension_item_receiver.mm
@@ -13,7 +13,6 @@
 #include "base/metrics/user_metrics_action.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/task/post_task.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/threading/scoped_blocking_call.h"
@@ -182,9 +181,10 @@
   }
 
   __weak ShareExtensionItemReceiver* weakSelf = self;
-  base::PostTask(FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
-                   [weakSelf readingListFolderCreated];
-                 }));
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(^{
+        [weakSelf readingListFolderCreated];
+      }));
 }
 
 - (void)readingListFolderCreated {
@@ -259,12 +259,13 @@
                             SHARE_EXTENSION_SOURCE_COUNT);
 
   __weak ShareExtensionItemReceiver* weakSelf = self;
-  base::PostTask(FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
-                   [weakSelf processEntryWithType:entryType
-                                            title:entryTitle
-                                              URL:entryURL
-                                       completion:completion];
-                 }));
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(^{
+        [weakSelf processEntryWithType:entryType
+                                 title:entryTitle
+                                   URL:entryURL
+                            completion:completion];
+      }));
   return YES;
 }
 
@@ -405,9 +406,9 @@
 
   if ([files count]) {
     __weak ShareExtensionItemReceiver* weakSelf = self;
-    base::PostTask(FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
-                     [weakSelf entriesReceived:files];
-                   }));
+    web::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, base::BindOnce(^{
+                                               [weakSelf entriesReceived:files];
+                                             }));
   }
 }
 
@@ -422,14 +423,14 @@
     __block std::unique_ptr<ReadingListModel::ScopedReadingListBatchUpdate>
         batchToken(_readingListModel->BeginBatchUpdates());
     _taskRunner->PostTask(FROM_HERE, base::BindOnce(^{
-                            [weakSelf handleFileAtURL:fileURL
-                                       withCompletion:^{
-                                         base::PostTask(FROM_HERE,
-                                                        {web::WebThread::UI},
-                                                        base::BindOnce(^{
-                                                          batchToken.reset();
-                                                        }));
-                                       }];
+                            [weakSelf
+                                handleFileAtURL:fileURL
+                                 withCompletion:^{
+                                   web::GetUIThreadTaskRunner({})->PostTask(
+                                       FROM_HERE, base::BindOnce(^{
+                                         batchToken.reset();
+                                       }));
+                                 }];
                           }));
   }
 }
diff --git a/ios/chrome/browser/snapshots/snapshot_cache.mm b/ios/chrome/browser/snapshots/snapshot_cache.mm
index 262e6f1..6eabcc70 100644
--- a/ios/chrome/browser/snapshots/snapshot_cache.mm
+++ b/ios/chrome/browser/snapshots/snapshot_cache.mm
@@ -7,6 +7,8 @@
 
 #import <UIKit/UIKit.h>
 
+#include <set>
+
 #include "base/base_paths.h"
 #include "base/bind.h"
 #include "base/containers/contains.h"
diff --git a/ios/chrome/browser/snapshots/snapshot_generator.mm b/ios/chrome/browser/snapshots/snapshot_generator.mm
index 63436422..14e2b29f 100644
--- a/ios/chrome/browser/snapshots/snapshot_generator.mm
+++ b/ios/chrome/browser/snapshots/snapshot_generator.mm
@@ -13,7 +13,6 @@
 
 #include "base/bind.h"
 #include "base/check_op.h"
-#include "base/task/post_task.h"
 #import "ios/chrome/browser/snapshots/snapshot_cache.h"
 #import "ios/chrome/browser/snapshots/snapshot_generator_delegate.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
@@ -132,9 +131,9 @@
 
   if (![self canTakeSnapshot]) {
     if (completion) {
-      base::PostTask(FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
-                       completion(nil);
-                     }));
+      web::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, base::BindOnce(^{
+                                                 completion(nil);
+                                               }));
     }
     return;
   }
diff --git a/ios/chrome/browser/snapshots/snapshot_tab_helper.mm b/ios/chrome/browser/snapshots/snapshot_tab_helper.mm
index b6d3db3..87e0a0b 100644
--- a/ios/chrome/browser/snapshots/snapshot_tab_helper.mm
+++ b/ios/chrome/browser/snapshots/snapshot_tab_helper.mm
@@ -7,7 +7,6 @@
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
-#include "base/task/post_task.h"
 #import "ios/chrome/browser/snapshots/snapshot_cache.h"
 #import "ios/chrome/browser/snapshots/snapshot_generator.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
@@ -145,8 +144,8 @@
         break;
 
       bool was_loading = was_loading_during_last_snapshot_;
-      base::PostDelayedTask(
-          FROM_HERE, {web::WebThread::UI},
+      web::GetUIThreadTaskRunner({})->PostDelayedTask(
+          FROM_HERE,
           base::BindOnce(
               &SnapshotTabHelper::UpdateSnapshotWithCallback,
               weak_ptr_factory_.GetWeakPtr(),
diff --git a/ios/chrome/browser/sync/glue/sync_start_util.mm b/ios/chrome/browser/sync/glue/sync_start_util.mm
index 72e375a..4f074b0c 100644
--- a/ios/chrome/browser/sync/glue/sync_start_util.mm
+++ b/ios/chrome/browser/sync/glue/sync_start_util.mm
@@ -7,7 +7,6 @@
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/location.h"
-#include "base/task/post_task.h"
 #include "components/sync/driver/sync_service.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
@@ -51,8 +50,8 @@
 
 void StartSyncProxy(const base::FilePath& browser_state_path,
                     syncer::ModelType type) {
-  base::PostTask(
-      FROM_HERE, {web::WebThread::UI},
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&StartSyncOnUIThread, browser_state_path, type));
 }
 
diff --git a/ios/chrome/browser/sync/ios_chrome_sync_client.mm b/ios/chrome/browser/sync/ios_chrome_sync_client.mm
index 7e0c885..c4eb725 100644
--- a/ios/chrome/browser/sync/ios_chrome_sync_client.mm
+++ b/ios/chrome/browser/sync/ios_chrome_sync_client.mm
@@ -9,7 +9,6 @@
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "base/logging.h"
-#include "base/task/post_task.h"
 #include "components/autofill/core/browser/webdata/autocomplete_sync_bridge.h"
 #include "components/autofill/core/browser/webdata/autofill_profile_sync_bridge.h"
 #include "components/autofill/core/browser/webdata/autofill_wallet_metadata_sync_bridge.h"
@@ -81,8 +80,7 @@
 
   component_factory_ =
       std::make_unique<browser_sync::SyncApiComponentFactoryImpl>(
-          this, ::GetChannel(),
-          base::CreateSingleThreadTaskRunner({web::WebThread::UI}), db_thread_,
+          this, ::GetChannel(), web::GetUIThreadTaskRunner({}), db_thread_,
           profile_web_data_service_, account_web_data_service_, password_store_,
           /*account_password_store=*/nullptr,
           ios::BookmarkSyncServiceFactory::GetForBrowserState(browser_state_));
diff --git a/ios/chrome/browser/ui/image_util/image_copier.mm b/ios/chrome/browser/ui/image_util/image_copier.mm
index 801172b..70bd6b5 100644
--- a/ios/chrome/browser/ui/image_util/image_copier.mm
+++ b/ios/chrome/browser/ui/image_util/image_copier.mm
@@ -9,7 +9,6 @@
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
 #import "base/strings/sys_string_conversions.h"
-#include "base/task/post_task.h"
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
@@ -148,8 +147,8 @@
                  style:UIAlertActionStyleCancel];
 
   // Delays launching alert by |kAlertDelayInMs|.
-  base::PostDelayedTask(
-      FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
+  web::GetUIThreadTaskRunner({})->PostDelayedTask(
+      FROM_HERE, base::BindOnce(^{
         // Checks that the copy has not finished yet.
         if (callbackID == weakSelf.activeID) {
           [weakSelf.alertCoordinator start];
diff --git a/ios/chrome/browser/ui/main/scene_controller.mm b/ios/chrome/browser/ui/main/scene_controller.mm
index b3434b72..26d1269 100644
--- a/ios/chrome/browser/ui/main/scene_controller.mm
+++ b/ios/chrome/browser/ui/main/scene_controller.mm
@@ -15,7 +15,6 @@
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #include "base/strings/sys_string_conversions.h"
-#include "base/task/post_task.h"
 #include "base/time/time.h"
 #include "components/breadcrumbs/core/breadcrumb_manager_keyed_service.h"
 #include "components/breadcrumbs/core/breadcrumb_persistent_storage_manager.h"
@@ -126,6 +125,7 @@
 #import "ios/public/provider/chrome/browser/ui_utils/ui_utils_api.h"
 #import "ios/public/provider/chrome/browser/user_feedback/user_feedback_provider.h"
 #include "ios/web/public/thread/web_task_traits.h"
+#include "ios/web/public/thread/web_thread.h"
 #import "ios/web/public/web_state.h"
 #import "net/base/mac/url_conversions.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -2847,8 +2847,8 @@
       }
     };
 
-    base::PostTaskAndReply(FROM_HERE, {web::WebThread::IO}, base::DoNothing(),
-                           base::BindRepeating(cleanup));
+    web::GetIOThreadTaskRunner({})->PostTaskAndReply(
+        FROM_HERE, base::DoNothing(), base::BindRepeating(cleanup));
   }
 
   // a) The first condition can happen when the last incognito tab is closed
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn
index 08065a03..ea6b720 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn
@@ -252,6 +252,7 @@
     "//ios/chrome/browser/ui/start_surface:feature_flags",
     "//ios/chrome/browser/ui/tab_switcher/tab_grid:features",
     "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid:grid_ui_constants",
+    "//ios/chrome/browser/ui/table_view/cells:cells_constants",
     "//ios/chrome/browser/ui/thumb_strip:feature_flags",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/test/earl_grey:eg_test_support+eg2",
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h
index bd030d5b..3dccb29 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h
@@ -20,6 +20,12 @@
 // Accessibility identifier for the background of the grid.
 extern NSString* const kGridBackgroundIdentifier;
 
+// Accessibility identifier for the grid section header.
+extern NSString* const kGridSectionHeaderIdentifier;
+
+// Accessibility identifier for the suggested actions cell.
+extern NSString* const kSuggestedActionsGridCellIdentifier;
+
 // Grid styling.
 extern NSString* const kGridBackgroundColor;
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.mm
index 5ce547a..8113118 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.mm
@@ -18,6 +18,13 @@
 // Accessibility identifier for the background of the grid.
 NSString* const kGridBackgroundIdentifier = @"GridBackgroundIdentifier";
 
+// Accessibility identifier for the grid section header.
+NSString* const kGridSectionHeaderIdentifier = @"GridSectionHeaderIdentifier";
+
+// Accessibility identifier for the suggested actions cell.
+NSString* const kSuggestedActionsGridCellIdentifier =
+    @"SuggestedActionsGridCellIdentifier";
+
 // Grid styling.
 NSString* const kGridBackgroundColor = @"grid_background_color";
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_header.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_header.mm
index 4b54adb..808afba2 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_header.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_header.mm
@@ -29,7 +29,7 @@
   self = [super initWithFrame:frame];
   if (self) {
     self.backgroundColor = [UIColor colorNamed:kGridBackgroundColor];
-
+    self.accessibilityIdentifier = kGridSectionHeaderIdentifier;
     UILabel* titleLabel = [[UILabel alloc] init];
     titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
     titleLabel.font =
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.mm
index 21409742..898461b 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.mm
@@ -22,6 +22,7 @@
     self.backgroundView = [[UIView alloc] init];
     self.backgroundView.backgroundColor =
         [UIColor colorNamed:kGridBackgroundColor];
+    self.accessibilityIdentifier = kSuggestedActionsGridCellIdentifier;
   }
   return self;
 }
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
index be9a64f..e2cc26b 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/format_macros.h"
 #include "base/ios/ios_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
@@ -11,6 +12,7 @@
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h"
@@ -45,6 +47,7 @@
 using chrome_test_util::TabGridSearchModeToolbar;
 using chrome_test_util::TabGridSearchTabsButton;
 using chrome_test_util::TabGridSelectTabsMenuButton;
+using chrome_test_util::RegularTabGrid;
 
 namespace {
 char kURL1[] = "http://firstURL";
@@ -67,14 +70,15 @@
                     grey_sufficientlyVisible(), nil);
 }
 
-id<GREYMatcher> TabWithTitle(NSString* title) {
-  return grey_allOf(TabGridCell(), grey_accessibilityLabel(title),
-                    grey_sufficientlyVisible(), nil);
+id<GREYMatcher> TabWithTitle(char* title) {
+  return grey_allOf(
+      TabGridCell(),
+      grey_accessibilityLabel([NSString stringWithUTF8String:title]),
+      grey_sufficientlyVisible(), nil);
 }
 
 id<GREYMatcher> TabWithTitleAndIndex(char* title, unsigned int index) {
-  return grey_allOf(TabWithTitle([NSString stringWithUTF8String:title]),
-                    TabGridCellAtIndex(index), nil);
+  return grey_allOf(TabWithTitle(title), TabGridCellAtIndex(index), nil);
 }
 
 // Identifer for cell at given |index| in the tab grid.
@@ -123,6 +127,90 @@
   return grey_accessibilityID(kTabGridScrimIdentifier);
 }
 
+// Returns a matcher for the search results header with title set with
+// |title_id|.
+id<GREYMatcher> SearchSectionHeaderWithTitleID(int title_id) {
+  id<GREYMatcher> title_matcher =
+      grey_allOf(grey_accessibilityLabel(l10n_util::GetNSString(title_id)),
+                 grey_sufficientlyVisible(), nil);
+  return grey_allOf(grey_accessibilityID(kGridSectionHeaderIdentifier),
+                    grey_descendant(title_matcher), grey_sufficientlyVisible(),
+                    nil);
+}
+
+// Returns a matcher for the search results open tabs section header.
+id<GREYMatcher> SearchOpenTabsSectionHeader() {
+  return SearchSectionHeaderWithTitleID(
+      IDS_IOS_TABS_SEARCH_OPEN_TABS_SECTION_HEADER_TITLE);
+}
+
+// Returns a matcher for the search results suggested actions section header.
+id<GREYMatcher> SearchSuggestedActionsSectionHeader() {
+  return SearchSectionHeaderWithTitleID(IDS_IOS_TABS_SEARCH_SUGGESTED_ACTIONS);
+}
+
+// Returns a matcher for the search results open tabs section header with
+// |count| set in the value label .
+id<GREYMatcher> SearchOpenTabsHeaderWithValue(size_t count) {
+  NSString* count_str = [NSString stringWithFormat:@"%" PRIuS, count];
+  NSString* value = l10n_util::GetNSStringF(
+      IDS_IOS_TABS_SEARCH_OPEN_TABS_COUNT, base::SysNSStringToUTF16(count_str));
+  id<GREYMatcher> value_matcher = grey_allOf(grey_accessibilityLabel(value),
+                                             grey_sufficientlyVisible(), nil);
+
+  return grey_allOf(SearchOpenTabsSectionHeader(),
+                    grey_descendant(value_matcher), grey_sufficientlyVisible(),
+                    nil);
+}
+
+// Returns a matcher for the "Search on web" suggested action.
+id<GREYMatcher> SearchOnWebSuggestedAction() {
+  return grey_allOf(chrome_test_util::StaticTextWithAccessibilityLabelId(
+                        IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_WEB),
+                    grey_sufficientlyVisible(), nil);
+}
+
+// Returns a matcher for the "Search recent tabs" suggested action.
+id<GREYMatcher> SearchRecentTabsSuggestedAction() {
+  return grey_allOf(
+      chrome_test_util::StaticTextWithAccessibilityLabelId(
+          IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_RECENT_TABS),
+      grey_sufficientlyVisible(), nil);
+}
+
+// Returns a matcher for the "Search history" suggested action.
+id<GREYMatcher> SearchHistorySuggestedAction() {
+  return grey_allOf(
+      grey_accessibilityID(kTableViewTabsSearchSuggestedHistoryItemId),
+      grey_sufficientlyVisible(), nil);
+}
+
+// Returns a matcher for the "Search history (|matches_count| Found)" suggested
+// action.
+id<GREYMatcher> SearchHistorySuggestedActionWithMatches(size_t matches_count) {
+  NSString* count_str = [NSString stringWithFormat:@"%" PRIuS, matches_count];
+  NSString* history_label = l10n_util::GetNSStringF(
+      IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_HISTORY,
+      base::SysNSStringToUTF16(count_str));
+  return grey_allOf(grey_accessibilityLabel(history_label),
+                    grey_sufficientlyVisible(), nil);
+}
+
+// Returns a matcher for the search suggested actions section.
+id<GREYMatcher> SearchSuggestedActionsSection() {
+  return grey_allOf(grey_accessibilityID(kSuggestedActionsGridCellIdentifier),
+                    grey_sufficientlyVisible(), nil);
+}
+
+// Returns a matcher for the search suggested actions section with the history
+// item matches count set to |matches_count|.
+id<GREYMatcher> SearchSuggestedActionsSectionWithHistoryMatchesCount(
+    size_t matches_count) {
+  return grey_allOf(
+      SearchSuggestedActionsSection(),
+      grey_descendant(SearchHistorySuggestedActionWithMatches(matches_count)),
+      grey_sufficientlyVisible(), nil);
+}
 }  // namespace
 
 @interface TabGridTestCase : WebHttpServerChromeTestCase {
@@ -148,7 +236,11 @@
       @selector(testSearchOpenTabsContextMenuShare),
       @selector(testSearchOpenTabsContextMenuAddToReadingList),
       @selector(testSearchOpenTabsContextMenuAddToBookmarks),
-      @selector(testSearchOpenTabsContextMenuCloseTab)};
+      @selector(testSearchOpenTabsContextMenuCloseTab),
+      @selector(testOpenTabsHeaderVisibleInSearchModeWhenSearchBarIsNotEmpty),
+      @selector(testSuggestedActionsVisibleInSearchModeWhenSearchBarIsNotEmpty),
+      @selector(testSearchSuggestedActionsDisplaysCorrectHistoryMatchesCount),
+      @selector(testSearchSuggestedActionsSectionContentInRegularGrid)};
   for (SEL test : searchTests) {
     if ([self isRunningTest:test]) {
       config.features_enabled.push_back(kTabsSearch);
@@ -176,6 +268,19 @@
   web::test::SetUpSimpleHttpServer(responses);
 }
 
+- (void)tearDown {
+  [super tearDown];
+  // Ensure that pref set in testTabGridItemContextMenuAddToBookmarkGreyed is
+  // reset even if the test failed.
+  if ([self isRunningTest:@selector
+            (testTabGridItemContextMenuAddToBookmarkGreyed)]) {
+    [ChromeEarlGreyAppInterface
+        setBoolValue:YES
+         forUserPref:base::SysUTF8ToNSString(
+                         bookmarks::prefs::kEditBookmarksEnabled)];
+  }
+}
+
 // Tests entering and leaving the tab grid.
 - (void)testEnteringAndLeavingTabGrid {
   [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
@@ -443,8 +548,7 @@
       performAction:grey_tap()];
 
   // Make sure that the tab is no longer present.
-  [[EarlGrey selectElementWithMatcher:TabWithTitle([NSString
-                                          stringWithUTF8String:kTitle1])]
+  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle1)]
       assertWithMatcher:grey_nil()];
 
   [[EarlGrey selectElementWithMatcher:chrome_test_util::
@@ -946,8 +1050,7 @@
       performAction:grey_tap()];
 
   // Make sure that the tab is no longer present.
-  [[EarlGrey selectElementWithMatcher:TabWithTitle([NSString
-                                          stringWithUTF8String:kTitle1])]
+  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle1)]
       assertWithMatcher:grey_nil()];
 
   [[EarlGrey selectElementWithMatcher:chrome_test_util::
@@ -1348,12 +1451,17 @@
   [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
       performAction:grey_tap()];
 
-  // Searching with the word "Page" should match only 3 results.
   [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
       performAction:grey_typeText(@"Page")];
-  [self verifyVisibleTabsCount:3];
 
-  // Verify that search results are correct and in the expected order.
+  // Verify that the header of the open tabs section has the correct results
+  // count.
+  [[EarlGrey selectElementWithMatcher:SearchOpenTabsHeaderWithValue(3)]
+      assertWithMatcher:grey_notNil()];
+
+  // Verify that there are 3 results for the query "Page" and they are in the
+  // expected order.
+  [self verifyVisibleTabsCount:3];
   [[EarlGrey selectElementWithMatcher:TabWithTitleAndIndex(kTitle1, 0)]
       assertWithMatcher:grey_notNil()];
   [[EarlGrey selectElementWithMatcher:TabWithTitleAndIndex(kTitle2, 1)]
@@ -1361,6 +1469,200 @@
   [[EarlGrey selectElementWithMatcher:TabWithTitleAndIndex(kTitle4, 2)]
       assertWithMatcher:grey_notNil()];
 
+  // Update the search query with one that doesn't match any results.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_typeText(@"Foo")];
+
+  // Verify that the header of the open tabs section has 0 as the results count.
+  [[EarlGrey selectElementWithMatcher:SearchOpenTabsHeaderWithValue(0)]
+      assertWithMatcher:grey_notNil()];
+
+  // Verify that no tabs are visible and previously shown tabs disappeared.
+  [self verifyVisibleTabsCount:0];
+
+  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle1)]
+      assertWithMatcher:grey_nil()];
+  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
+      assertWithMatcher:grey_nil()];
+  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle4)]
+      assertWithMatcher:grey_nil()];
+}
+
+// Tests that open tabs search results header appear only when there is a query
+// on the search bar.
+- (void)testOpenTabsHeaderVisibleInSearchModeWhenSearchBarIsNotEmpty {
+  [self loadTestURLsInNewTabs];
+  [ChromeEarlGrey showTabSwitcher];
+
+  // Verify that the header doesn't exist in normal mode.
+  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
+      assertWithMatcher:grey_nil()];
+
+  // Enter search mode.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
+      performAction:grey_tap()];
+
+  // Upon entry, the search bar is empty. Verify that the header doesn't exist.
+  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
+      assertWithMatcher:grey_nil()];
+
+  // Searching with any query should render the header visible.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_typeText(@"text\n")];
+  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
+      assertWithMatcher:grey_notNil()];
+
+  // Clearing search bar text should render the header invisible again.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_clearText()];
+  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
+      assertWithMatcher:grey_nil()];
+
+  // Searching a word then canceling the search mode should hide the section
+  // header.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_typeText(@"page\n")];
+  [[EarlGrey selectElementWithMatcher:TabGridSearchCancelButton()]
+      performAction:grey_tap()];
+  [[self scrollUpViewMatcher:RegularTabGrid()
+             toSelectMatcher:SearchOpenTabsSectionHeader()]
+      assertWithMatcher:grey_nil()];
+}
+
+// Tests that suggested actions section is available whenever there is a query
+// in the normal tabs search mode.
+- (void)testSuggestedActionsVisibleInSearchModeWhenSearchBarIsNotEmpty {
+  [self loadTestURLsInNewTabs];
+  [ChromeEarlGrey showTabSwitcher];
+
+  // Verify that the suggested actions section doesn't exist in normal mode.
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
+      assertWithMatcher:grey_nil()];
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
+      assertWithMatcher:grey_nil()];
+
+  // Enter search mode.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
+      performAction:grey_tap()];
+
+  // Upon entry, the search bar is empty. Verify that the suggested actions
+  // section doesn't exist.
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
+      assertWithMatcher:grey_nil()];
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
+      assertWithMatcher:grey_nil()];
+
+  // Searching with a query with no results should show the suggested actions
+  // section.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_typeText(@"text\n")];
+  [[EarlGrey selectElementWithMatcher:SearchOpenTabsHeaderWithValue(0)]
+      assertWithMatcher:grey_notNil()];
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
+      assertWithMatcher:grey_notNil()];
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
+      assertWithMatcher:grey_notNil()];
+
+  // Clearing search bar text should hide the suggested actions section.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_clearText()];
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
+      assertWithMatcher:grey_nil()];
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
+      assertWithMatcher:grey_nil()];
+
+  // Searching with a query with results should show the suggested actions
+  // section.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_clearText()];
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_typeText(@"page 2\n")];
+
+  // Check that the header is set correctly.
+  [[EarlGrey selectElementWithMatcher:SearchOpenTabsHeaderWithValue(1)]
+      assertWithMatcher:grey_notNil()];
+
+  [[self scrollDownViewMatcher:RegularTabGrid()
+               toSelectMatcher:SearchSuggestedActionsSectionHeader()]
+      assertWithMatcher:grey_notNil()];
+  [[self scrollDownViewMatcher:RegularTabGrid()
+               toSelectMatcher:SearchSuggestedActionsSection()]
+      assertWithMatcher:grey_notNil()];
+
+  // Canceling search mode should hide the suggested actions section.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchCancelButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
+      assertWithMatcher:grey_nil()];
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
+      assertWithMatcher:grey_nil()];
+}
+
+// Tests that the search suggested actions section has the right rows in the
+// regular grid.
+- (void)testSearchSuggestedActionsSectionContentInRegularGrid {
+  [self loadTestURLsInNewTabs];
+  [ChromeEarlGrey showTabSwitcher];
+
+  // Enter search mode and enter a search query.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
+      performAction:grey_tap()];
+  // TODO(crbug.com/1306246): Scrolling doesn't work properly in very small
+  // devices. Once that is fixed a more broad query can be used for searching
+  // (eg. "page").
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_typeText(@"page 2\n")];
+
+  // Verify that the suggested actions section exist and has "Search on web",
+  // "Search recent tabs", "Search history" rows.
+  [[self scrollDownViewMatcher:RegularTabGrid()
+               toSelectMatcher:SearchSuggestedActionsSectionHeader()]
+      assertWithMatcher:grey_notNil()];
+
+  [[self
+      scrollDownViewMatcher:RegularTabGrid()
+            toSelectMatcher:grey_allOf(
+                                SearchSuggestedActionsSection(),
+                                grey_descendant(SearchOnWebSuggestedAction()),
+                                grey_descendant(
+                                    SearchRecentTabsSuggestedAction()),
+                                grey_descendant(SearchHistorySuggestedAction()),
+                                grey_sufficientlyVisible(), nil)]
+      assertWithMatcher:grey_notNil()];
+}
+
+// Tests that history row in the search suggested actions section displays the
+// correct number of matches.
+- (void)testSearchSuggestedActionsDisplaysCorrectHistoryMatchesCount {
+  [ChromeEarlGrey clearBrowsingHistory];
+  [self loadTestURLs];
+  [ChromeEarlGrey showTabSwitcher];
+
+  // Enter search mode.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
+      performAction:grey_tap()];
+
+  // Verify that the suggested actions section is not visible.
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
+      assertWithMatcher:grey_nil()];
+
+  // Searching the word "page" matches 2 items from history.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_typeText(@"page\n")];
+  [[self scrollDownViewMatcher:RegularTabGrid()
+               toSelectMatcher:
+                   SearchSuggestedActionsSectionWithHistoryMatchesCount(2)]
+      assertWithMatcher:grey_notNil()];
+
+  // Adding to the existing query " 2" will search for "page 2" and should only
+  // match 1 item from the history.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_typeText(@" 2\n")];
+  [[self scrollDownViewMatcher:RegularTabGrid()
+               toSelectMatcher:
+                   SearchSuggestedActionsSectionWithHistoryMatchesCount(1)]
+      assertWithMatcher:grey_notNil()];
+
   // Cancel search mode.
   [[EarlGrey selectElementWithMatcher:TabGridSearchCancelButton()]
       performAction:grey_tap()];
@@ -1380,7 +1682,7 @@
   [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
       performAction:grey_typeText(title2)];
 
-  [[EarlGrey selectElementWithMatcher:TabWithTitle(title2)]
+  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
       performAction:grey_tap()];
 
   [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
@@ -1407,7 +1709,7 @@
   [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
       performAction:grey_typeText(title2)];
 
-  [[EarlGrey selectElementWithMatcher:TabWithTitle(title2)]
+  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
       performAction:grey_tap()];
 
   [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
@@ -1503,7 +1805,7 @@
       performAction:grey_tap()];
 
   // Make sure that the tab is no longer present.
-  [[EarlGrey selectElementWithMatcher:TabWithTitle(title2)]
+  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
       assertWithMatcher:grey_nil()];
 }
 
@@ -1676,4 +1978,22 @@
                                           nil)] assertWithMatcher:grey_nil()];
 }
 
+// Returns an interaction that scrolls down on the view matched by |viewMatcher|
+// to search for the given |matcher|.
+- (id<GREYInteraction>)scrollDownViewMatcher:(id<GREYMatcher>)viewMatcher
+                             toSelectMatcher:(id<GREYMatcher>)matcher {
+  return [[EarlGrey selectElementWithMatcher:matcher]
+         usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 50)
+      onElementWithMatcher:viewMatcher];
+}
+
+// Returns an interaction that scrolls up on the view matched by |viewMatcher|
+// to search for the given |matcher|.
+- (id<GREYInteraction>)scrollUpViewMatcher:(id<GREYMatcher>)viewMatcher
+                           toSelectMatcher:(id<GREYMatcher>)matcher {
+  return [[EarlGrey selectElementWithMatcher:matcher]
+         usingSearchAction:grey_scrollInDirection(kGREYDirectionUp, 50)
+      onElementWithMatcher:viewMatcher];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h b/ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h
index f7e6987e..ffe4d5b 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h
@@ -64,6 +64,9 @@
 // TableViewInfoButtonCell.
 extern NSString* const kTableViewCellInfoButtonViewId;
 
+// The accessibility identifier of the TableViewTabsSearchSuggestedHistoryItem.
+extern NSString* const kTableViewTabsSearchSuggestedHistoryItemId;
+
 // Returns a padding according to the width of the current device.
 extern CGFloat HorizontalPadding();
 
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.mm b/ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.mm
index c28556e..48932935 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.mm
@@ -29,6 +29,8 @@
 NSString* const kMaskedPassword = @"••••••••";
 NSString* const kTableViewCellInfoButtonViewId =
     @"kTableViewCellInfoButtonViewId";
+NSString* const kTableViewTabsSearchSuggestedHistoryItemId =
+    @"kTableViewTabsSearchSuggestedHistoryItemId";
 
 CGFloat HorizontalPadding() {
   if (!IsSmallDevice())
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_tabs_search_suggested_history_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_tabs_search_suggested_history_item.mm
index 5ad4cd8..06550e9 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_tabs_search_suggested_history_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_tabs_search_suggested_history_item.mm
@@ -6,6 +6,7 @@
 
 #include "base/format_macros.h"
 #include "base/strings/sys_string_conversions.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ui/base/l10n/l10n_util_mac.h"
 
@@ -23,6 +24,7 @@
         imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
     self.title = l10n_util::GetNSString(
         IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_HISTORY_UNKNOWN_RESULT_COUNT);
+    self.accessibilityIdentifier = kTableViewTabsSearchSuggestedHistoryItemId;
   }
   return self;
 }
diff --git a/ios/chrome/browser/web/certificate_policy_app_agent.mm b/ios/chrome/browser/web/certificate_policy_app_agent.mm
index 562760a..0fd5354 100644
--- a/ios/chrome/browser/web/certificate_policy_app_agent.mm
+++ b/ios/chrome/browser/web/certificate_policy_app_agent.mm
@@ -110,8 +110,7 @@
   // Evict all the certificate policies except for the current entries of the
   // active sessions, for the regular and incognito browsers.
   CleanCertificatePolicyCache(
-      &_clearPoliciesTaskTracker,
-      base::CreateSingleThreadTaskRunner({web::WebThread::IO}),
+      &_clearPoliciesTaskTracker, web::GetIOThreadTaskRunner({}),
       web::BrowserState::GetCertificatePolicyCache(browserState), browserList,
       /*incognito=*/false);
 
@@ -119,8 +118,7 @@
     ChromeBrowserState* incognitoBrowserState =
         browserState->GetOffTheRecordChromeBrowserState();
     CleanCertificatePolicyCache(
-        &_clearPoliciesTaskTracker,
-        base::CreateSingleThreadTaskRunner({web::WebThread::IO}),
+        &_clearPoliciesTaskTracker, web::GetIOThreadTaskRunner({}),
         web::BrowserState::GetCertificatePolicyCache(incognitoBrowserState),
         browserList, /*incognito=*/true);
   }
diff --git a/ios/chrome/browser/web/certificate_policy_app_agent_unittest.mm b/ios/chrome/browser/web/certificate_policy_app_agent_unittest.mm
index ec655e9a..42872bf 100644
--- a/ios/chrome/browser/web/certificate_policy_app_agent_unittest.mm
+++ b/ios/chrome/browser/web/certificate_policy_app_agent_unittest.mm
@@ -169,10 +169,11 @@
     __block web::CertPolicy::Judgment judgement =
         web::CertPolicy::Judgment::UNKNOWN;
     __block bool completed = false;
-    base::PostTask(FROM_HERE, {web::WebThread::IO}, base::BindOnce(^{
-                     completed = true;
-                     judgement = cache->QueryPolicy(cert_.get(), host, status_);
-                   }));
+    web::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, base::BindOnce(^{
+                                               completed = true;
+                                               judgement = cache->QueryPolicy(
+                                                   cert_.get(), host, status_);
+                                             }));
     EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
       return completed;
     }));
@@ -184,10 +185,11 @@
   void ClearPolicyCache(
       const scoped_refptr<web::CertificatePolicyCache>& cache) {
     __block bool policies_cleared = false;
-    base::PostTask(FROM_HERE, {web::WebThread::IO}, base::BindOnce(^{
-                     cache->ClearCertificatePolicies();
-                     policies_cleared = true;
-                   }));
+    web::GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(^{
+          cache->ClearCertificatePolicies();
+          policies_cleared = true;
+        }));
     EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
       return policies_cleared;
     }));
@@ -224,8 +226,8 @@
       }
       hosts_added = true;
     };
-    base::PostTask(FROM_HERE, {web::WebThread::IO},
-                   base::BindOnce(populate_cache));
+    web::GetIOThreadTaskRunner({})->PostTask(FROM_HERE,
+                                             base::BindOnce(populate_cache));
     EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
       return hosts_added;
     }));
diff --git a/ios/chrome/browser/web/image_fetch/image_fetch_tab_helper.mm b/ios/chrome/browser/web/image_fetch/image_fetch_tab_helper.mm
index 5ad5486..ade554d 100644
--- a/ios/chrome/browser/web/image_fetch/image_fetch_tab_helper.mm
+++ b/ios/chrome/browser/web/image_fetch/image_fetch_tab_helper.mm
@@ -10,7 +10,6 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/task/post_task.h"
 #include "base/values.h"
 #include "components/image_fetcher/core/image_data_fetcher.h"
 #include "ios/chrome/browser/web/image_fetch/image_fetch_java_script_feature.h"
@@ -129,8 +128,8 @@
   DCHECK_EQ(js_callbacks_.count(call_id_), 0UL);
   js_callbacks_.insert({call_id_, std::move(callback)});
 
-  base::PostDelayedTask(
-      FROM_HERE, {web::WebThread::UI},
+  web::GetUIThreadTaskRunner({})->PostDelayedTask(
+      FROM_HERE,
       base::BindRepeating(&ImageFetchTabHelper::OnJsTimeout,
                           weak_ptr_factory_.GetWeakPtr(), call_id_),
       timeout);
diff --git a/ios/chrome/browser/webdata_services/web_data_service_factory.mm b/ios/chrome/browser/webdata_services/web_data_service_factory.mm
index 38fd0c34..4af18e9 100644
--- a/ios/chrome/browser/webdata_services/web_data_service_factory.mm
+++ b/ios/chrome/browser/webdata_services/web_data_service_factory.mm
@@ -9,7 +9,6 @@
 #include "base/check.h"
 #include "base/files/file_path.h"
 #include "base/no_destructor.h"
-#include "base/task/post_task.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/keyed_service/core/service_access_type.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
@@ -106,8 +105,7 @@
   const base::FilePath& browser_state_path = context->GetStatePath();
   return std::make_unique<WebDataServiceWrapper>(
       browser_state_path, GetApplicationContext()->GetApplicationLocale(),
-      base::CreateSingleThreadTaskRunner({web::WebThread::UI}),
-      base::DoNothing());
+      web::GetUIThreadTaskRunner({}), base::DoNothing());
 }
 
 web::BrowserState* WebDataServiceFactory::GetBrowserStateToUse(
diff --git a/ios/chrome/test/app/browsing_data_test_util.mm b/ios/chrome/test/app/browsing_data_test_util.mm
index 2450ab5c..ff079e9 100644
--- a/ios/chrome/test/app/browsing_data_test_util.mm
+++ b/ios/chrome/test/app/browsing_data_test_util.mm
@@ -8,7 +8,6 @@
 
 #include "base/logging.h"
 #include "base/task/cancelable_task_tracker.h"
-#include "base/task/post_task.h"
 #import "base/test/ios/wait_util.h"
 #include "components/browsing_data/core/browsing_data_utils.h"
 #include "components/history/core/browser/history_service.h"
@@ -91,10 +90,10 @@
                                           : GetOriginalBrowserState();
   auto cache = web::BrowserState::GetCertificatePolicyCache(browser_state);
   __block BOOL policies_cleared = NO;
-  base::PostTask(FROM_HERE, {web::WebThread::IO}, base::BindOnce(^{
-                   cache->ClearCertificatePolicies();
-                   policies_cleared = YES;
-                 }));
+  web::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, base::BindOnce(^{
+                                             cache->ClearCertificatePolicies();
+                                             policies_cleared = YES;
+                                           }));
   return WaitUntilConditionOrTimeout(2, ^{
     return policies_cleared;
   });
diff --git a/ios/components/io_thread/ios_io_thread.mm b/ios/components/io_thread/ios_io_thread.mm
index 5cfacb9..c189e7b 100644
--- a/ios/components/io_thread/ios_io_thread.mm
+++ b/ios/components/io_thread/ios_io_thread.mm
@@ -19,7 +19,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
-#include "base/task/post_task.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread.h"
 #include "base/time/time.h"
@@ -127,8 +126,7 @@
 SystemURLRequestContextGetter::SystemURLRequestContextGetter(
     IOSIOThread* io_thread)
     : io_thread_(io_thread),
-      network_task_runner_(
-          base::CreateSingleThreadTaskRunner({web::WebThread::IO})) {}
+      network_task_runner_(web::GetIOThreadTaskRunner({})) {}
 
 SystemURLRequestContextGetter::~SystemURLRequestContextGetter() {}
 
@@ -207,8 +205,8 @@
 
 void IOSIOThread::ChangedToOnTheRecord() {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
-  base::PostTask(FROM_HERE, {web::WebThread::IO},
-                 base::BindOnce(&IOSIOThread::ChangedToOnTheRecordOnIOThread,
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&IOSIOThread::ChangedToOnTheRecordOnIOThread,
                                 base::Unretained(this)));
 }
 
diff --git a/ios/components/security_interstitials/ios_blocking_page_controller_client.mm b/ios/components/security_interstitials/ios_blocking_page_controller_client.mm
index c274e942..b1c8efe 100644
--- a/ios/components/security_interstitials/ios_blocking_page_controller_client.mm
+++ b/ios/components/security_interstitials/ios_blocking_page_controller_client.mm
@@ -7,7 +7,6 @@
 #include "base/bind.h"
 #include "base/check_op.h"
 #include "base/notreached.h"
-#include "base/task/post_task.h"
 #include "components/security_interstitials/core/metrics_helper.h"
 #import "ios/web/public/navigation/navigation_manager.h"
 #include "ios/web/public/navigation/reload_type.h"
@@ -60,8 +59,8 @@
     // Closing the tab synchronously is problematic since web state is heavily
     // involved in the operation and CloseWebState interrupts it, so call
     // CloseWebState asynchronously.
-    base::PostTask(FROM_HERE, {web::WebThread::UI},
-                   base::BindOnce(&IOSBlockingPageControllerClient::Close,
+    web::GetUIThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(&IOSBlockingPageControllerClient::Close,
                                   weak_factory_.GetWeakPtr()));
   }
 }
diff --git a/ios/web/browser_state.mm b/ios/web/browser_state.mm
index 3afdc51..c60988b 100644
--- a/ios/web/browser_state.mm
+++ b/ios/web/browser_state.mm
@@ -13,7 +13,6 @@
 #include "base/memory/ref_counted.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/process/process_handle.h"
-#include "base/task/post_task.h"
 #include "base/token.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
 #include "ios/web/public/init/network_context_owner.h"
@@ -89,8 +88,8 @@
   shared_url_loader_factory_->Detach();
 
   if (network_context_) {
-    base::DeleteSoon(FROM_HERE, {web::WebThread::IO},
-                     network_context_owner_.release());
+    web::GetIOThreadTaskRunner({})->DeleteSoon(
+        FROM_HERE, network_context_owner_.release());
   }
 
   // Delete the URLDataManagerIOSBackend instance on the IO thread if it has
@@ -99,8 +98,8 @@
   // BrowserState are still accessing it on the IO thread at this point,
   // they're going to have a bad time anyway.
   if (url_data_manager_ios_backend_) {
-    bool posted = base::DeleteSoon(FROM_HERE, {web::WebThread::IO},
-                                   url_data_manager_ios_backend_);
+    bool posted = web::GetIOThreadTaskRunner({})->DeleteSoon(
+        FROM_HERE, url_data_manager_ios_backend_);
     if (!posted)
       delete url_data_manager_ios_backend_;
   }
diff --git a/ios/web/download/download_session_task_impl.mm b/ios/web/download/download_session_task_impl.mm
index f791e81..4f9e7bde 100644
--- a/ios/web/download/download_session_task_impl.mm
+++ b/ios/web/download/download_session_task_impl.mm
@@ -8,7 +8,6 @@
 
 #import "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
-#include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
 #import "ios/net/cookies/system_cookie_util.h"
 #include "ios/web/download/download_result.h"
@@ -112,8 +111,8 @@
 - (void)URLSession:(NSURLSession*)session
                     task:(NSURLSessionTask*)task
     didCompleteWithError:(NSError*)error {
-  base::PostTask(FROM_HERE, {WebThread::UI},
-                 base::BindOnce(_doneCallback, task, error));
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(_doneCallback, task, error));
 }
 
 - (void)URLSession:(NSURLSession*)session
@@ -125,8 +124,8 @@
   // thread (in net::URLFetcherFileWriter::Write()).
   dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
 
-  base::PostTask(
-      FROM_HERE, {WebThread::UI},
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(
           [](dispatch_semaphore_t semaphore, DataCallback innerDataCallback,
              NSURLSessionTask* task, NSData* data) {
@@ -306,8 +305,8 @@
       web_state_->GetBrowserState()->GetRequestContext());
 
   // net::URLRequestContextGetter must be used in the IO thread.
-  base::PostTask(
-      FROM_HERE, {WebThread::IO},
+  GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&DownloadSessionTaskImpl::GetCookiesFromContextGetter,
                      context_getter, std::move(callback)));
 }
@@ -322,8 +321,8 @@
              const net::CookieList& cookie_list) {
             NSArray<NSHTTPCookie*>* cookies =
                 SystemCookiesFromCanonicalCookieList(cookie_list);
-            base::PostTask(FROM_HERE, {WebThread::UI},
-                           base::BindOnce(std::move(callback), cookies));
+            GetUIThreadTaskRunner({})->PostTask(
+                FROM_HERE, base::BindOnce(std::move(callback), cookies));
           },
           std::move(callback)));
 }
diff --git a/ios/web/find_in_page/find_in_page_manager_impl.mm b/ios/web/find_in_page/find_in_page_manager_impl.mm
index 95b48f1..77e8802a 100644
--- a/ios/web/find_in_page/find_in_page_manager_impl.mm
+++ b/ios/web/find_in_page/find_in_page_manager_impl.mm
@@ -7,7 +7,6 @@
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #import "base/strings/sys_string_conversions.h"
-#include "base/task/post_task.h"
 #include "base/values.h"
 #import "ios/web/find_in_page/find_in_page_constants.h"
 #import "ios/web/find_in_page/find_in_page_java_script_feature.h"
@@ -16,6 +15,7 @@
 #include "ios/web/public/js_messaging/web_frame_util.h"
 #import "ios/web/public/js_messaging/web_frames_manager.h"
 #include "ios/web/public/thread/web_task_traits.h"
+#include "ios/web/public/thread/web_thread.h"
 #import "ios/web/web_state/web_state_impl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -102,8 +102,8 @@
   if (all_frames.size() == 0) {
     // No frames to search in.
     // Call asyncronously to match behavior if find was successful in frames.
-    base::PostTask(
-        FROM_HERE, {WebThread::UI},
+    GetUIThreadTaskRunner({})->PostTask(
+        FROM_HERE,
         base::BindOnce(&FindInPageManagerImpl::LastFindRequestCompleted,
                        weak_factory_.GetWeakPtr()));
     return;
@@ -122,8 +122,8 @@
       last_find_request_.DidReceiveFindResponseFromOneFrame();
       if (last_find_request_.AreAllFindResponsesReturned()) {
         // Call asyncronously to match behavior if find was done in frames.
-        base::PostTask(
-            FROM_HERE, {WebThread::UI},
+        GetUIThreadTaskRunner({})->PostTask(
+            FROM_HERE,
             base::BindOnce(&FindInPageManagerImpl::LastFindRequestCompleted,
                            weak_factory_.GetWeakPtr()));
       }
diff --git a/ios/web/init/web_main_loop.mm b/ios/web/init/web_main_loop.mm
index 92ab430..3933f84 100644
--- a/ios/web/init/web_main_loop.mm
+++ b/ios/web/init/web_main_loop.mm
@@ -17,7 +17,6 @@
 #include "base/power_monitor/power_monitor.h"
 #include "base/power_monitor/power_monitor_device_source.h"
 #include "base/process/process_metrics.h"
-#include "base/task/post_task.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/threading/thread_restrictions.h"
@@ -26,6 +25,7 @@
 #include "ios/web/public/init/ios_global_state.h"
 #include "ios/web/public/init/web_main_parts.h"
 #include "ios/web/public/thread/web_task_traits.h"
+#include "ios/web/public/thread/web_thread.h"
 #import "ios/web/public/web_client.h"
 #include "ios/web/web_sub_thread.h"
 #include "ios/web/web_thread_impl.h"
@@ -156,8 +156,8 @@
   // Teardown may start in PostMainMessageLoopRun, and during teardown we
   // need to be able to perform IO.
   base::PermanentThreadAllowance::AllowBlocking();
-  base::PostTask(FROM_HERE, {WebThread::IO},
-                 base::BindOnce(base::IgnoreResult(
+  GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(base::IgnoreResult(
                      &base::PermanentThreadAllowance::AllowBlocking)));
 
   // Also allow waiting to join threads.
@@ -166,8 +166,8 @@
   // persistent work is being done after ThreadPoolInstance::Shutdown() in order
   // to move towards atomic shutdown.
   base::PermanentThreadAllowance::AllowBaseSyncPrimitives();
-  base::PostTask(
-      FROM_HERE, {WebThread::IO},
+  GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(base::IgnoreResult(
           &base::PermanentThreadAllowance::AllowBaseSyncPrimitives)));
 
diff --git a/ios/web/js_messaging/web_frame_impl.mm b/ios/web/js_messaging/web_frame_impl.mm
index cffe11c..868ee44 100644
--- a/ios/web/js_messaging/web_frame_impl.mm
+++ b/ios/web/js_messaging/web_frame_impl.mm
@@ -16,13 +16,13 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/task/post_task.h"
 #include "base/values.h"
 #include "crypto/aead.h"
 #include "crypto/random.h"
 #import "ios/web/js_messaging/java_script_content_world.h"
 #import "ios/web/js_messaging/web_view_js_utils.h"
 #include "ios/web/public/thread/web_task_traits.h"
+#include "ios/web/public/thread/web_thread.h"
 #include "url/gurl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -244,9 +244,9 @@
       std::move(callback), std::move(timeout_callback));
   pending_requests_[message_id] = std::move(callbacks);
 
-  base::PostDelayedTask(
-      FROM_HERE, {web::WebThread::UI},
-      pending_requests_[message_id]->timeout_callback->callback(), timeout);
+  web::GetUIThreadTaskRunner({})->PostDelayedTask(
+      FROM_HERE, pending_requests_[message_id]->timeout_callback->callback(),
+      timeout);
   bool called =
       CallJavaScriptFunctionInContentWorld(name, parameters, content_world,
                                            /*reply_with_result=*/true);
diff --git a/ios/web/net/cookie_notification_bridge.mm b/ios/web/net/cookie_notification_bridge.mm
index 79b85a11..178468e 100644
--- a/ios/web/net/cookie_notification_bridge.mm
+++ b/ios/web/net/cookie_notification_bridge.mm
@@ -8,7 +8,6 @@
 
 #include "base/bind.h"
 #include "base/location.h"
-#include "base/task/post_task.h"
 #import "ios/net/cookies/cookie_store_ios.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
@@ -38,8 +37,8 @@
     NSNotification* notification) {
   DCHECK([[notification name]
       isEqualToString:NSHTTPCookieManagerCookiesChangedNotification]);
-  base::PostTask(
-      FROM_HERE, {web::WebThread::IO},
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&net::CookieStoreIOS::NotifySystemCookiesChanged));
 }
 
diff --git a/ios/web/net/cookies/wk_http_system_cookie_store.mm b/ios/web/net/cookies/wk_http_system_cookie_store.mm
index f483fa9c..9c8ed33 100644
--- a/ios/web/net/cookies/wk_http_system_cookie_store.mm
+++ b/ios/web/net/cookies/wk_http_system_cookie_store.mm
@@ -6,7 +6,6 @@
 
 #include "base/bind.h"
 #import "base/ios/block_types.h"
-#include "base/task/post_task.h"
 #import "ios/net/cookies/cookie_creation_time_manager.h"
 #include "ios/net/cookies/system_cookie_util.h"
 #include "ios/web/public/thread/web_task_traits.h"
@@ -29,7 +28,7 @@
 // SystemCookieStore should operate on IO thread.
 void RunBlockOnIOThread(ProceduralBlock block) {
   DCHECK(block != nil);
-  base::PostTask(FROM_HERE, {web::WebThread::IO}, base::BindOnce(block));
+  web::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, base::BindOnce(block));
 }
 
 // Returns wether |cookie| should be included for queries about |url|.
@@ -101,8 +100,8 @@
       creation_time_manager_->GetWeakPtr();
   NSHTTPCookie* block_cookie = cookie;
   __weak __typeof(crw_cookie_store_) block_cookie_store = crw_cookie_store_;
-  base::PostTask(
-      FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(^{
         [block_cookie_store
                  deleteCookie:block_cookie
             completionHandler:^{
@@ -130,8 +129,8 @@
   if (optional_creation_time && !optional_creation_time->is_null())
     cookie_time = *optional_creation_time;
   __weak __typeof(crw_cookie_store_) block_cookie_store = crw_cookie_store_;
-  base::PostTask(
-      FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(^{
         [block_cookie_store
                     setCookie:block_cookie
             completionHandler:^{
@@ -153,8 +152,8 @@
   base::WeakPtr<net::CookieCreationTimeManager> weak_time_manager =
       creation_time_manager_->GetWeakPtr();
   __weak __typeof(crw_cookie_store_) block_cookie_store = crw_cookie_store_;
-  base::PostTask(
-      FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(^{
         [block_cookie_store getAllCookies:^(NSArray<NSHTTPCookie*>* cookies) {
           ProceduralBlock completionHandler = ^{
             RunBlockOnIOThread(^{
@@ -212,8 +211,8 @@
       creation_time_manager_->GetWeakPtr();
   __weak __typeof(crw_cookie_store_) weak_cookie_store = crw_cookie_store_;
   GURL block_url = include_url;
-  base::PostTask(
-      FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(^{
         __typeof(weak_cookie_store) strong_cookie_store = weak_cookie_store;
         if (strong_cookie_store) {
           [strong_cookie_store
diff --git a/ios/web/network_context_owner.cc b/ios/web/network_context_owner.cc
index d152200..786ce56 100644
--- a/ios/web/network_context_owner.cc
+++ b/ios/web/network_context_owner.cc
@@ -9,7 +9,6 @@
 #include <vector>
 
 #include "base/bind.h"
-#include "base/task/post_task.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
 #include "net/url_request/url_request_context_getter.h"
@@ -24,8 +23,8 @@
     mojo::Remote<network::mojom::NetworkContext>* network_context_client)
     : request_context_(request_context) {
   DCHECK_CURRENTLY_ON(WebThread::UI);
-  base::PostTask(
-      FROM_HERE, {web::WebThread::IO},
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&NetworkContextOwner::InitializeOnIOThread,
                      // This is safe, since |this| will be deleted on the IO
                      // thread, which would have to happen afterwards.
diff --git a/ios/web/network_context_owner_unittest.cc b/ios/web/network_context_owner_unittest.cc
index cb28dd8..a1e9c82 100644
--- a/ios/web/network_context_owner_unittest.cc
+++ b/ios/web/network_context_owner_unittest.cc
@@ -9,9 +9,9 @@
 
 #include "base/bind.h"
 #include "base/run_loop.h"
-#include "base/task/post_task.h"
 #include "ios/web/public/test/web_task_environment.h"
 #include "ios/web/public/thread/web_task_traits.h"
+#include "ios/web/public/thread/web_thread.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
@@ -26,7 +26,7 @@
   NetworkContextOwnerTest()
       : saw_connection_error_(false),
         context_getter_(base::MakeRefCounted<net::TestURLRequestContextGetter>(
-            base::CreateSingleThreadTaskRunner({WebThread::IO}))) {}
+            GetIOThreadTaskRunner({}))) {}
 
   ~NetworkContextOwnerTest() override {
     // Tests should cleanup after themselves.
@@ -61,8 +61,8 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(saw_connection_error_);
 
-  base::DeleteSoon(FROM_HERE, {web::WebThread::IO},
-                   network_context_owner_.release());
+  web::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE,
+                                             network_context_owner_.release());
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(saw_connection_error_);  // other end gone
 }
@@ -80,8 +80,8 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(saw_connection_error_);
 
-  base::PostTask(
-      FROM_HERE, {web::WebThread::IO},
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(
           &net::TestURLRequestContextGetter::NotifyContextShuttingDown,
           context_getter_));
@@ -89,8 +89,8 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(saw_connection_error_);  // other end gone post-shutdown.
 
-  base::DeleteSoon(FROM_HERE, {web::WebThread::IO},
-                   network_context_owner_.release());
+  web::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE,
+                                             network_context_owner_.release());
 }
 
 }  // namespace web
diff --git a/ios/web/public/test/fakes/fake_browser_state.cc b/ios/web/public/test/fakes/fake_browser_state.cc
index 830aae8..df8976c 100644
--- a/ios/web/public/test/fakes/fake_browser_state.cc
+++ b/ios/web/public/test/fakes/fake_browser_state.cc
@@ -5,7 +5,6 @@
 #include "ios/web/public/test/fakes/fake_browser_state.h"
 
 #include "base/files/file_path.h"
-#include "base/task/post_task.h"
 #include "base/task/single_thread_task_runner.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
@@ -33,7 +32,7 @@
 
   scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner()
       const override {
-    return base::CreateSingleThreadTaskRunner({web::WebThread::IO});
+    return web::GetIOThreadTaskRunner({});
   }
 
  private:
diff --git a/ios/web/public/test/fakes/fake_cookie_store.cc b/ios/web/public/test/fakes/fake_cookie_store.cc
index cef5120..dc90696e 100644
--- a/ios/web/public/test/fakes/fake_cookie_store.cc
+++ b/ios/web/public/test/fakes/fake_cookie_store.cc
@@ -4,7 +4,6 @@
 
 #include "ios/web/public/test/fakes/fake_cookie_store.h"
 
-#include "base/task/post_task.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -21,8 +20,8 @@
 
 void FakeCookieStore::GetAllCookiesAsync(GetAllCookiesCallback callback) {
   DCHECK_CURRENTLY_ON(WebThread::IO);
-  base::PostTask(FROM_HERE, {WebThread::IO},
-                 base::BindOnce(std::move(callback), all_cookies_));
+  GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), all_cookies_));
 }
 
 void FakeCookieStore::SetCanonicalCookieAsync(
diff --git a/ios/web/security/crw_cert_verification_controller.mm b/ios/web/security/crw_cert_verification_controller.mm
index c90db78..448b862 100644
--- a/ios/web/security/crw_cert_verification_controller.mm
+++ b/ios/web/security/crw_cert_verification_controller.mm
@@ -141,10 +141,11 @@
     DCHECK(cert);
   }
   DCHECK(cert->intermediate_buffers().empty());
-  base::PostTask(FROM_HERE, {WebThread::IO}, base::BindOnce(^{
-                   self->_certPolicyCache->AllowCertForHost(
-                       cert.get(), base::SysNSStringToUTF8(host), status);
-                 }));
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(^{
+        self->_certPolicyCache->AllowCertForHost(
+            cert.get(), base::SysNSStringToUTF8(host), status);
+      }));
 }
 
 #pragma mark - Private
diff --git a/ios/web/session/session_certificate_policy_cache_impl.mm b/ios/web/session/session_certificate_policy_cache_impl.mm
index 68baeaf..9c1d5d7 100644
--- a/ios/web/session/session_certificate_policy_cache_impl.mm
+++ b/ios/web/session/session_certificate_policy_cache_impl.mm
@@ -5,7 +5,6 @@
 #import "ios/web/session/session_certificate_policy_cache_impl.h"
 
 #include "base/bind.h"
-#include "base/task/post_task.h"
 #include "ios/web/public/browser_state.h"
 #include "ios/web/public/security/certificate_policy_cache.h"
 #import "ios/web/public/session/crw_session_certificate_policy_cache_storage.h"
@@ -62,12 +61,13 @@
   DCHECK(cache.get());
   NSSet* allowed_certs = [NSSet setWithSet:allowed_certs_];
   const scoped_refptr<CertificatePolicyCache> cache_copy = cache;
-  base::PostTask(FROM_HERE, {WebThread::IO}, base::BindOnce(^{
-                   for (CRWSessionCertificateStorage* cert in allowed_certs) {
-                     cache_copy->AllowCertForHost(cert.certificate, cert.host,
-                                                  cert.status);
-                   }
-                 }));
+  GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(^{
+        for (CRWSessionCertificateStorage* cert in allowed_certs) {
+          cache_copy->AllowCertForHost(cert.certificate, cert.host,
+                                       cert.status);
+        }
+      }));
 }
 
 void SessionCertificatePolicyCacheImpl::RegisterAllowedCertificate(
@@ -93,8 +93,8 @@
                                              status:status]];
   const scoped_refptr<CertificatePolicyCache> cache =
       GetCertificatePolicyCache();
-  base::PostTask(
-      FROM_HERE, {WebThread::IO},
+  GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&CertificatePolicyCache::AllowCertForHost, cache,
                      base::RetainedRef(certificate.get()), host, status));
 }
diff --git a/ios/web/session/session_certificate_policy_cache_impl_unittest.mm b/ios/web/session/session_certificate_policy_cache_impl_unittest.mm
index df1812ab..22fd0c21 100644
--- a/ios/web/session/session_certificate_policy_cache_impl_unittest.mm
+++ b/ios/web/session/session_certificate_policy_cache_impl_unittest.mm
@@ -5,7 +5,6 @@
 #import "ios/web/session/session_certificate_policy_cache_impl.h"
 
 #include "base/bind.h"
-#include "base/task/post_task.h"
 #import "base/test/ios/wait_util.h"
 #include "ios/web/public/security/certificate_policy_cache.h"
 #import "ios/web/public/session/crw_session_certificate_policy_cache_storage.h"
@@ -37,10 +36,11 @@
   __block web::CertPolicy::Judgment judgement =
       web::CertPolicy::Judgment::UNKNOWN;
   __block bool completed = false;
-  base::PostTask(FROM_HERE, {web::WebThread::IO}, base::BindOnce(^{
-                   completed = true;
-                   judgement = cache->QueryPolicy(cert.get(), host, status);
-                 }));
+  web::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, base::BindOnce(^{
+                                             completed = true;
+                                             judgement = cache->QueryPolicy(
+                                                 cert.get(), host, status);
+                                           }));
   EXPECT_TRUE(WaitUntilConditionOrTimeout(1.0, ^{
     return completed;
   }));
diff --git a/ios/web/shell/shell_browser_state.mm b/ios/web/shell/shell_browser_state.mm
index 606dbb2..e5e98f2 100644
--- a/ios/web/shell/shell_browser_state.mm
+++ b/ios/web/shell/shell_browser_state.mm
@@ -7,7 +7,6 @@
 #include "base/base_paths.h"
 #include "base/files/file_path.h"
 #include "base/path_service.h"
-#include "base/task/post_task.h"
 #include "base/threading/thread_restrictions.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
@@ -23,8 +22,7 @@
   CHECK(base::PathService::Get(base::DIR_APP_DATA, &path_));
 
   request_context_getter_ = new ShellURLRequestContextGetter(
-      GetStatePath(), this,
-      base::CreateSingleThreadTaskRunner({web::WebThread::IO}));
+      GetStatePath(), this, web::GetIOThreadTaskRunner({}));
 }
 
 ShellBrowserState::~ShellBrowserState() {
diff --git a/ios/web/test/fakes/fake_web_frame_impl.cc b/ios/web/test/fakes/fake_web_frame_impl.cc
index 36d17aa..fcf466a4 100644
--- a/ios/web/test/fakes/fake_web_frame_impl.cc
+++ b/ios/web/test/fakes/fake_web_frame_impl.cc
@@ -10,9 +10,9 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/json/json_writer.h"
-#include "base/task/post_task.h"
 #include "base/values.h"
 #include "ios/web/public/thread/web_task_traits.h"
+#include "ios/web/public/thread/web_thread.h"
 
 namespace web {
 
@@ -117,12 +117,11 @@
   }
 
   if (force_timeout_) {
-    base::PostDelayedTask(FROM_HERE, {web::WebThread::UI},
-                          base::BindOnce(std::move(callback), nullptr),
-                          timeout);
+    web::GetUIThreadTaskRunner({})->PostDelayedTask(
+        FROM_HERE, base::BindOnce(std::move(callback), nullptr), timeout);
   } else {
-    base::PostTask(FROM_HERE, {WebThread::UI},
-                   base::BindOnce(std::move(callback), result_map_[name]));
+    GetUIThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), result_map_[name]));
   }
   return true;
 }
diff --git a/ios/web/web_thread_unittest.cc b/ios/web/web_thread_unittest.cc
index 642b6ba..7209d31 100644
--- a/ios/web/web_thread_unittest.cc
+++ b/ios/web/web_thread_unittest.cc
@@ -5,7 +5,6 @@
 #include "ios/web/public/thread/web_thread.h"
 
 #include "base/bind.h"
-#include "base/task/post_task.h"
 #include "ios/web/public/test/web_task_environment.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
@@ -31,10 +30,9 @@
 
 TEST_F(WebThreadTest, BasePostTask) {
   base::RunLoop run_loop;
-  EXPECT_TRUE(base::PostTask(
-      FROM_HERE, {WebThread::IO},
-      base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
-                     WebThread::IO)));
+  EXPECT_TRUE(GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
+                                WebThread::IO)));
   run_loop.Run();
 }
 
@@ -55,8 +53,7 @@
 }
 
 TEST_F(WebThreadTest, PostTaskViaTaskRunner) {
-  scoped_refptr<base::TaskRunner> task_runner =
-      base::CreateTaskRunner({WebThread::IO});
+  scoped_refptr<base::TaskRunner> task_runner = GetIOThreadTaskRunner({});
   base::RunLoop run_loop;
   EXPECT_TRUE(task_runner->PostTask(
       FROM_HERE, base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
@@ -66,7 +63,7 @@
 
 TEST_F(WebThreadTest, PostTaskViaSequencedTaskRunner) {
   scoped_refptr<base::SequencedTaskRunner> task_runner =
-      base::CreateSequencedTaskRunner({WebThread::IO});
+      GetIOThreadTaskRunner({});
   base::RunLoop run_loop;
   EXPECT_TRUE(task_runner->PostTask(
       FROM_HERE, base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
@@ -76,7 +73,7 @@
 
 TEST_F(WebThreadTest, PostTaskViaSingleThreadTaskRunner) {
   scoped_refptr<base::SingleThreadTaskRunner> task_runner =
-      base::CreateSingleThreadTaskRunner({WebThread::IO});
+      GetIOThreadTaskRunner({});
   base::RunLoop run_loop;
   EXPECT_TRUE(task_runner->PostTask(
       FROM_HERE, base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
diff --git a/ios/web/webui/url_data_manager_ios.cc b/ios/web/webui/url_data_manager_ios.cc
index 230353c8..a128af5a 100644
--- a/ios/web/webui/url_data_manager_ios.cc
+++ b/ios/web/webui/url_data_manager_ios.cc
@@ -15,7 +15,6 @@
 #include "base/no_destructor.h"
 #include "base/strings/string_util.h"
 #include "base/synchronization/lock.h"
-#include "base/task/post_task.h"
 #include "ios/web/public/browser_state.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
@@ -67,8 +66,8 @@
 
 void URLDataManagerIOS::AddDataSource(URLDataSourceIOSImpl* source) {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
-  base::PostTask(
-      FROM_HERE, {web::WebThread::IO},
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&AddDataSourceOnIOThread, base::Unretained(browser_state_),
                      base::WrapRefCounted(source)));
 }
@@ -109,8 +108,8 @@
   }
   if (schedule_delete) {
     // Schedule a task to delete the DataSource back on the UI thread.
-    base::PostTask(FROM_HERE, {web::WebThread::UI},
-                   base::BindOnce(&URLDataManagerIOS::DeleteDataSources));
+    web::GetUIThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(&URLDataManagerIOS::DeleteDataSources));
   }
 }
 
diff --git a/ios/web/webui/url_data_manager_ios_backend.mm b/ios/web/webui/url_data_manager_ios_backend.mm
index 613a457..5fe5b55 100644
--- a/ios/web/webui/url_data_manager_ios_backend.mm
+++ b/ios/web/webui/url_data_manager_ios_backend.mm
@@ -14,7 +14,6 @@
 #include "base/memory/ref_counted_memory.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string_util.h"
-#include "base/task/post_task.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/trace_event/trace_event.h"
 #include "ios/web/public/browser_state.h"
@@ -400,8 +399,8 @@
                      const base::WeakPtr<URLRequestChromeJob>& job) {
   DCHECK_CURRENTLY_ON(WebThread::UI);
   std::string mime_type = source->source()->GetMimeType(path);
-  base::PostTask(FROM_HERE, {WebThread::IO},
-                 base::BindOnce(&URLRequestChromeJob::MimeTypeAvailable, job,
+  GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&URLRequestChromeJob::MimeTypeAvailable, job,
                                 base::RetainedRef(source), mime_type));
 }
 
@@ -521,7 +520,7 @@
   // message loop before request for data. And correspondingly their
   // replies are put on the IO thread in the same order.
   scoped_refptr<base::SingleThreadTaskRunner> target_runner =
-      base::CreateSingleThreadTaskRunner({web::WebThread::UI});
+      web::GetUIThreadTaskRunner({});
   target_runner->PostTask(
       FROM_HERE, base::BindOnce(&GetMimeTypeOnUI, base::RetainedRef(source),
                                 path, job->weak_factory_.GetWeakPtr()));
diff --git a/ios/web/webui/url_data_source_ios_impl.cc b/ios/web/webui/url_data_source_ios_impl.cc
index 6ae4184..a8609f4 100644
--- a/ios/web/webui/url_data_source_ios_impl.cc
+++ b/ios/web/webui/url_data_source_ios_impl.cc
@@ -8,7 +8,6 @@
 #include "base/location.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/strings/string_util.h"
-#include "base/task/post_task.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
 #include "ios/web/public/webui/url_data_source_ios.h"
@@ -42,8 +41,8 @@
     // when the object is deleted.
     return;
   }
-  base::PostTask(FROM_HERE, {web::WebThread::IO},
-                 base::BindOnce(&URLDataSourceIOSImpl::SendResponseOnIOThread,
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&URLDataSourceIOSImpl::SendResponseOnIOThread,
                                 this, request_id, std::move(bytes)));
 }
 
diff --git a/ios/web_view/internal/app/application_context.mm b/ios/web_view/internal/app/application_context.mm
index 7bac0968..00e1e05 100644
--- a/ios/web_view/internal/app/application_context.mm
+++ b/ios/web_view/internal/app/application_context.mm
@@ -8,7 +8,6 @@
 #include "base/command_line.h"
 #include "base/no_destructor.h"
 #include "base/path_service.h"
-#include "base/task/post_task.h"
 #include "components/component_updater/component_updater_service.h"
 #include "components/component_updater/timer_update_scheduler.h"
 #include "components/flags_ui/pref_service_flags_storage.h"
@@ -79,8 +78,8 @@
     shared_url_loader_factory_->Detach();
 
   if (network_context_) {
-    base::DeleteSoon(FROM_HERE, {web::WebThread::IO},
-                     network_context_owner_.release());
+    web::GetIOThreadTaskRunner({})->DeleteSoon(
+        FROM_HERE, network_context_owner_.release());
   }
 }
 
diff --git a/ios/web_view/internal/autofill/cwv_autofill_data_manager.mm b/ios/web_view/internal/autofill/cwv_autofill_data_manager.mm
index ba5261c..5c990a7 100644
--- a/ios/web_view/internal/autofill/cwv_autofill_data_manager.mm
+++ b/ios/web_view/internal/autofill/cwv_autofill_data_manager.mm
@@ -9,7 +9,6 @@
 #include "base/bind.h"
 #include "base/notreached.h"
 #include "base/strings/sys_string_conversions.h"
-#include "base/task/post_task.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/personal_data_manager_observer.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
@@ -227,9 +226,9 @@
   // |personalDataDidChange| to be invoked.
   if (_personalDataManager->IsDataLoaded()) {
     NSArray<CWVAutofillProfile*>* profiles = [self profiles];
-    base::PostTask(FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
-                     completionHandler(profiles);
-                   }));
+    web::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, base::BindOnce(^{
+                                               completionHandler(profiles);
+                                             }));
   } else {
     [_fetchProfilesCompletionHandlers addObject:completionHandler];
   }
@@ -250,9 +249,9 @@
   // |personalDataDidChange| to be invoked.
   if (_personalDataManager->IsDataLoaded()) {
     NSArray<CWVCreditCard*>* creditCards = [self creditCards];
-    base::PostTask(FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
-                     completionHandler(creditCards);
-                   }));
+    web::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, base::BindOnce(^{
+                                               completionHandler(creditCards);
+                                             }));
   } else {
     [_fetchCreditCardsCompletionHandlers addObject:completionHandler];
   }
diff --git a/ios/web_view/internal/autofill/cwv_credit_card_verifier_unittest.mm b/ios/web_view/internal/autofill/cwv_credit_card_verifier_unittest.mm
index 45fbe39f..7b1af74 100644
--- a/ios/web_view/internal/autofill/cwv_credit_card_verifier_unittest.mm
+++ b/ios/web_view/internal/autofill/cwv_credit_card_verifier_unittest.mm
@@ -15,7 +15,6 @@
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/strings/sys_string_conversions.h"
-#include "base/task/post_task.h"
 #import "base/test/ios/wait_util.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
@@ -59,12 +58,12 @@
       const UserProvidedUnmaskDetails& unmask_details) override {
     unmask_details_ = unmask_details;
     // Fake the actual verification and just respond with success.
-    base::PostTask(FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
-                     autofill::AutofillClient::PaymentsRpcResult result =
-                         autofill::AutofillClient::PaymentsRpcResult::kSuccess;
-                     [credit_card_verifier_
-                         didReceiveUnmaskVerificationResult:result];
-                   }));
+    web::GetUIThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(^{
+          autofill::AutofillClient::PaymentsRpcResult result =
+              autofill::AutofillClient::PaymentsRpcResult::kSuccess;
+          [credit_card_verifier_ didReceiveUnmaskVerificationResult:result];
+        }));
   }
   void OnUnmaskPromptClosed() override {}
   bool ShouldOfferFidoAuth() const override { return false; }
diff --git a/ios/web_view/internal/ios_global_state_web_view_configuration.mm b/ios/web_view/internal/ios_global_state_web_view_configuration.mm
index fb5c021..c3a6aaf 100644
--- a/ios/web_view/internal/ios_global_state_web_view_configuration.mm
+++ b/ios/web_view/internal/ios_global_state_web_view_configuration.mm
@@ -4,7 +4,8 @@
 
 #include "ios/web/public/init/ios_global_state_configuration.h"
 
-#include "base/task/post_task.h"
+#include <dispatch/dispatch.h>
+
 #include "base/task/single_thread_task_runner.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
@@ -22,7 +23,7 @@
   dispatch_once(&once_token, ^{
     ios_web_view::InitializeGlobalState();
   });
-  return base::CreateSingleThreadTaskRunner({web::WebThread::IO});
+  return web::GetIOThreadTaskRunner({});
 }
 
 }  // namespace ios_global_state
diff --git a/ios/web_view/internal/passwords/web_view_account_password_store_factory.mm b/ios/web_view/internal/passwords/web_view_account_password_store_factory.mm
index 71eb929..76a389bb 100644
--- a/ios/web_view/internal/passwords/web_view_account_password_store_factory.mm
+++ b/ios/web_view/internal/passwords/web_view_account_password_store_factory.mm
@@ -10,7 +10,6 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/no_destructor.h"
-#include "base/task/post_task.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
 #include "components/password_manager/core/browser/login_database.h"
 #include "components/password_manager/core/browser/password_manager_constants.h"
@@ -48,8 +47,8 @@
 }
 
 void SyncEnabledOrDisabled(WebViewBrowserState* browser_state) {
-  base::PostTask(FROM_HERE, {web::WebThread::UI},
-                 base::BindOnce(&UpdateFormManager, browser_state));
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&UpdateFormManager, browser_state));
 }
 
 }  // namespace
diff --git a/ios/web_view/internal/sync/web_view_gcm_profile_service_factory.mm b/ios/web_view/internal/sync/web_view_gcm_profile_service_factory.mm
index 4671a4b5..00cecf1 100644
--- a/ios/web_view/internal/sync/web_view_gcm_profile_service_factory.mm
+++ b/ios/web_view/internal/sync/web_view_gcm_profile_service_factory.mm
@@ -8,7 +8,6 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/no_destructor.h"
-#include "base/task/post_task.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "components/gcm_driver/gcm_client_factory.h"
@@ -52,8 +51,8 @@
     base::WeakPtr<gcm::GCMProfileService> service,
     mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory>
         receiver) {
-  base::PostTask(
-      FROM_HERE, {web::WebThread::UI},
+  web::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
       base::BindOnce(&RequestProxyResolvingSocketFactoryOnUIThread, context,
                      std::move(service), std::move(receiver)));
 }
@@ -107,8 +106,7 @@
       version_info::Channel::STABLE, GetProductCategoryForSubtypes(),
       WebViewIdentityManagerFactory::GetForBrowserState(browser_state),
       base::WrapUnique(new gcm::GCMClientFactory),
-      base::CreateSingleThreadTaskRunner({web::WebThread::UI}),
-      base::CreateSingleThreadTaskRunner({web::WebThread::IO}),
+      web::GetUIThreadTaskRunner({}), web::GetIOThreadTaskRunner({}),
       blocking_task_runner);
 }
 }  // namespace ios_web_view
diff --git a/ios/web_view/internal/sync/web_view_sync_client.mm b/ios/web_view/internal/sync/web_view_sync_client.mm
index 78c1948..2f8ef06 100644
--- a/ios/web_view/internal/sync/web_view_sync_client.mm
+++ b/ios/web_view/internal/sync/web_view_sync_client.mm
@@ -11,7 +11,6 @@
 #include "base/check_op.h"
 #include "base/command_line.h"
 #include "base/notreached.h"
-#include "base/task/post_task.h"
 #include "components/autofill/core/browser/webdata/autofill_profile_sync_bridge.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/invalidation/impl/profile_invalidation_provider.h"
@@ -102,8 +101,7 @@
       sync_invalidations_service_(sync_invalidations_service) {
   component_factory_ =
       std::make_unique<browser_sync::SyncApiComponentFactoryImpl>(
-          this, version_info::Channel::STABLE,
-          base::CreateSingleThreadTaskRunner({web::WebThread::UI}),
+          this, version_info::Channel::STABLE, web::GetUIThreadTaskRunner({}),
           profile_web_data_service_->GetDBTaskRunner(),
           profile_web_data_service_, account_web_data_service_,
           profile_password_store_, account_password_store_,
diff --git a/ios/web_view/internal/web_view_browser_state.mm b/ios/web_view/internal/web_view_browser_state.mm
index 361819a9..2db6ff3 100644
--- a/ios/web_view/internal/web_view_browser_state.mm
+++ b/ios/web_view/internal/web_view_browser_state.mm
@@ -10,7 +10,6 @@
 #include "base/files/file_path.h"
 #include "base/memory/ptr_util.h"
 #include "base/path_service.h"
-#include "base/task/post_task.h"
 #include "base/threading/thread_restrictions.h"
 #include "components/autofill/core/common/autofill_prefs.h"
 #include "components/history/core/common/pref_names.h"
@@ -98,7 +97,7 @@
 
     request_context_getter_ = new WebViewURLRequestContextGetter(
         GetStatePath(), this, ApplicationContext::GetInstance()->GetNetLog(),
-        base::CreateSingleThreadTaskRunner({web::WebThread::IO}));
+        web::GetIOThreadTaskRunner({}));
 
     // Initialize prefs.
     scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry =
@@ -125,8 +124,8 @@
   BrowserStateDependencyManager::GetInstance()->DestroyBrowserStateServices(
       this);
 
-  base::PostTask(FROM_HERE, {web::WebThread::IO},
-                 base::BindOnce(&WebViewURLRequestContextGetter::ShutDown,
+  web::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&WebViewURLRequestContextGetter::ShutDown,
                                 request_context_getter_));
 }
 
diff --git a/ios/web_view/internal/webdata_services/web_view_web_data_service_wrapper_factory.mm b/ios/web_view/internal/webdata_services/web_view_web_data_service_wrapper_factory.mm
index ee69ed62..7e7d206 100644
--- a/ios/web_view/internal/webdata_services/web_view_web_data_service_wrapper_factory.mm
+++ b/ios/web_view/internal/webdata_services/web_view_web_data_service_wrapper_factory.mm
@@ -10,7 +10,6 @@
 #include "base/check.h"
 #include "base/files/file_path.h"
 #include "base/no_destructor.h"
-#include "base/task/post_task.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/keyed_service/core/service_access_type.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
@@ -89,8 +88,7 @@
   return std::make_unique<WebDataServiceWrapper>(
       browser_state_path,
       ApplicationContext::GetInstance()->GetApplicationLocale(),
-      base::CreateSingleThreadTaskRunner({web::WebThread::UI}),
-      base::DoNothing());
+      web::GetUIThreadTaskRunner({}), base::DoNothing());
 }
 
 bool WebViewWebDataServiceWrapperFactory::ServiceIsNULLWhileTesting() const {
diff --git a/media/formats/BUILD.gn b/media/formats/BUILD.gn
index 53b4268..36e7fe3 100644
--- a/media/formats/BUILD.gn
+++ b/media/formats/BUILD.gn
@@ -187,8 +187,14 @@
   sources += [
     "hls/items.cc",
     "hls/items.h",
+    "hls/media_playlist.cc",
+    "hls/media_playlist.h",
+    "hls/media_segment.cc",
+    "hls/media_segment.h",
     "hls/parse_status.cc",
     "hls/parse_status.h",
+    "hls/playlist_common.cc",
+    "hls/playlist_common.h",
     "hls/source_string.cc",
     "hls/source_string.h",
     "hls/tag_name.cc",
@@ -199,7 +205,12 @@
     "hls/types.h",
   ]
   deps += [ "//third_party/re2" ]
-  public_deps = [ "//third_party/abseil-cpp:absl" ]
+  public_deps = [
+    "//third_party/abseil-cpp:absl",
+
+    # Needed because `hls/media_playlist.h` includes GURL in its public API.
+    "//url",
+  ]
 }
 
 static_library("test_support") {
@@ -308,6 +319,7 @@
   # TODO(https://crbug.com/1266991): This should be gated behind `enable_hls_demuxer`, once that's enabled by default.
   sources += [
     "hls/items_unittest.cc",
+    "hls/media_playlist_unittest.cc",
     "hls/tags_unittest.cc",
     "hls/types_unittest.cc",
   ]
@@ -331,6 +343,15 @@
   ]
 }
 
+# TODO(https://crbug.com/1266991): This should be gated behind `enable_hls_demuxer`, once that's enabled by default.
+fuzzer_test("hls_media_playlist_fuzzer") {
+  sources = [ "hls/media_playlist_fuzzer.cc" ]
+  deps = [
+    "//base",
+    "//media",
+  ]
+}
+
 if (proprietary_codecs) {
   fuzzer_test("h264_annex_b_converter_fuzzer") {
     sources = [ "mp4/h264_annex_b_to_avc_bitstream_converter_fuzztest.cc" ]
diff --git a/media/formats/hls/items_fuzzer.cc b/media/formats/hls/items_fuzzer.cc
index 802475d..b5b3d2d 100644
--- a/media/formats/hls/items_fuzzer.cc
+++ b/media/formats/hls/items_fuzzer.cc
@@ -34,8 +34,8 @@
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   // Create a StringPiece from the given input
-  const base::StringPiece manifest(reinterpret_cast<const char*>(data), size);
-  media::hls::SourceLineIterator iterator{manifest};
+  const base::StringPiece source(reinterpret_cast<const char*>(data), size);
+  media::hls::SourceLineIterator iterator{source};
 
   while (true) {
     const auto prev_iterator = iterator;
@@ -46,7 +46,7 @@
       CHECK(result == media::hls::ParseStatusCode::kReachedEOF ||
             result == media::hls::ParseStatusCode::kInvalidEOL);
 
-      // Ensure that `manifest` is still a substring of the previous manifest
+      // Ensure that `source` is still a substring of the previous source
       CHECK(IsSubstring(iterator.SourceForTesting(),
                         prev_iterator.SourceForTesting()));
       CHECK(iterator.CurrentLineForTesting() >=
diff --git a/media/formats/hls/items_unittest.cc b/media/formats/hls/items_unittest.cc
index 6d96d4f1..7b5cc79 100644
--- a/media/formats/hls/items_unittest.cc
+++ b/media/formats/hls/items_unittest.cc
@@ -22,8 +22,8 @@
 // Calls `GetNextLineItem` for each expectation, and verifies that the result
 // matches.
 template <typename T>
-void RunTest(base::StringPiece manifest, const T& expectations) {
-  auto line_iter = SourceLineIterator(manifest);
+void RunTest(base::StringPiece source, const T& expectations) {
+  auto line_iter = SourceLineIterator(source);
 
   for (auto expectation : expectations) {
     auto result = GetNextLineItem(&line_iter);
diff --git a/media/formats/hls/media_playlist.cc b/media/formats/hls/media_playlist.cc
new file mode 100644
index 0000000..6ccf8da9
--- /dev/null
+++ b/media/formats/hls/media_playlist.cc
@@ -0,0 +1,162 @@
+// 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 "media/formats/hls/media_playlist.h"
+
+#include "base/notreached.h"
+#include "base/time/time.h"
+#include "media/formats/hls/media_segment.h"
+#include "media/formats/hls/playlist_common.h"
+#include "media/formats/hls/types.h"
+#include "url/gurl.h"
+
+namespace media::hls {
+
+MediaPlaylist::MediaPlaylist(const MediaPlaylist&) = default;
+
+MediaPlaylist::MediaPlaylist(MediaPlaylist&&) = default;
+
+MediaPlaylist& MediaPlaylist::operator=(const MediaPlaylist&) = default;
+
+MediaPlaylist& MediaPlaylist::operator=(MediaPlaylist&&) = default;
+
+MediaPlaylist::~MediaPlaylist() = default;
+
+ParseStatus::Or<MediaPlaylist> MediaPlaylist::Parse(base::StringPiece source,
+                                                    GURL uri) {
+  CHECK(uri.is_valid());
+
+  SourceLineIterator src_iter{source};
+
+  // Parse the first line of the playlist. This must be an M3U tag.
+  {
+    auto m3u_tag_result = CheckM3uTag(&src_iter);
+    if (m3u_tag_result.has_error()) {
+      return std::move(m3u_tag_result).error();
+    }
+  }
+
+  CommonParserState common_state;
+  absl::optional<InfTag> inf_tag;
+  absl::optional<XGapTag> gap_tag;
+  absl::optional<XDiscontinuityTag> discontinuity_tag;
+  std::vector<MediaSegment> segments;
+
+  // Get segments out of the playlist
+  while (true) {
+    auto item_result = GetNextLineItem(&src_iter);
+    if (item_result.has_error()) {
+      auto error = std::move(item_result).error();
+
+      // Only tolerated error is EOF, in which case we're done.
+      if (error.code() == ParseStatusCode::kReachedEOF) {
+        break;
+      }
+
+      return std::move(error);
+    }
+
+    auto item = std::move(item_result).value();
+
+    // Handle tags
+    if (auto* tag = absl::get_if<TagItem>(&item)) {
+      switch (GetTagKind(tag->name)) {
+        case TagKind::kUnknown:
+          HandleUnknownTag(*tag);
+          continue;
+        case TagKind::kCommonTag: {
+          auto error = ParseCommonTag(*tag, &common_state);
+          if (error.has_value()) {
+            return std::move(error).value();
+          }
+          continue;
+        }
+        case TagKind::kMultivariantPlaylistTag:
+          return ParseStatusCode::kMediaPlaylistHasMultivariantPlaylistTag;
+        case TagKind::kMediaPlaylistTag:
+          // Handled below
+          break;
+      }
+
+      switch (static_cast<MediaPlaylistTagName>(tag->name)) {
+        case MediaPlaylistTagName::kInf: {
+          auto error = ParseUniqueTag(*tag, inf_tag);
+          if (error.has_value()) {
+            return std::move(error).value();
+          }
+          break;
+        }
+        case MediaPlaylistTagName::kXDiscontinuity: {
+          auto error = ParseUniqueTag(*tag, discontinuity_tag);
+          if (error.has_value()) {
+            return std::move(error).value();
+          }
+          break;
+        }
+        case MediaPlaylistTagName::kXGap: {
+          auto error = ParseUniqueTag(*tag, gap_tag);
+          if (error.has_value()) {
+            return std::move(error).value();
+          }
+          break;
+        }
+        case MediaPlaylistTagName::kXEndList:
+          // TODO(crbug.com/1266991): Implement the #EXT-X-END-LIST Tag
+          break;
+        case MediaPlaylistTagName::kXIFramesOnly:
+          // TODO(crbug.com/1266991): Implement the #EXT-X-I-FRAMES-ONLY tag
+          break;
+      }
+
+      continue;
+    }
+
+    // Handle URIs
+    // `GetNextLineItem` should return either a TagItem (handled above) or a
+    // UriItem.
+    static_assert(absl::variant_size<GetNextLineItemResult>() == 2);
+    auto segment_uri_result =
+        ParseUri(absl::get<UriItem>(std::move(item)), uri);
+    if (segment_uri_result.has_error()) {
+      return std::move(segment_uri_result).error();
+    }
+    auto segment_uri = std::move(segment_uri_result).value();
+
+    // For this to be a valid media segment, we must have parsed an Inf tag
+    // since the last segment.
+    if (!inf_tag.has_value()) {
+      return ParseStatusCode::kMediaSegmentMissingInfTag;
+    }
+
+    segments.emplace_back(inf_tag->duration, std::move(segment_uri),
+                          discontinuity_tag.has_value(), gap_tag.has_value());
+
+    // Reset per-segment tags
+    inf_tag.reset();
+    gap_tag.reset();
+    discontinuity_tag.reset();
+  }
+
+  return MediaPlaylist(std::move(uri), common_state.GetVersion(),
+                       common_state.independent_segments_tag.has_value(),
+                       std::move(segments));
+}
+
+MediaPlaylist::MediaPlaylist(GURL uri,
+                             types::DecimalInteger version,
+                             bool independent_segments,
+                             std::vector<MediaSegment> segments)
+    : uri_(std::move(uri)),
+      version_(version),
+      independent_segments_(independent_segments),
+      segments_(std::move(segments)) {
+  base::TimeDelta duration;
+  for (const auto& segment : segments_) {
+    duration += base::Seconds(segment.GetDuration());
+  }
+
+  computed_duration_ = duration;
+}
+
+}  // namespace media::hls
diff --git a/media/formats/hls/media_playlist.h b/media/formats/hls/media_playlist.h
new file mode 100644
index 0000000..98db772
--- /dev/null
+++ b/media/formats/hls/media_playlist.h
@@ -0,0 +1,69 @@
+// 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 MEDIA_FORMATS_HLS_MEDIA_PLAYLIST_H_
+#define MEDIA_FORMATS_HLS_MEDIA_PLAYLIST_H_
+
+#include <vector>
+#include "base/time/time.h"
+#include "media/base/media_export.h"
+#include "media/formats/hls/types.h"
+#include "url/gurl.h"
+
+namespace media::hls {
+
+class MediaSegment;
+
+class MEDIA_EXPORT MediaPlaylist {
+ public:
+  MediaPlaylist(const MediaPlaylist&);
+  MediaPlaylist(MediaPlaylist&&);
+  MediaPlaylist& operator=(const MediaPlaylist&);
+  MediaPlaylist& operator=(MediaPlaylist&&);
+  ~MediaPlaylist();
+
+  // Returns the resolved URI of this playlist.
+  const GURL& Uri() const { return uri_; }
+
+  // Returns the HLS version number defined by the playlist. The default version
+  // is `1`.
+  types::DecimalInteger GetVersion() const { return version_; }
+
+  // Indicates that all media samples in a Segment can be decoded without
+  // information from other segments. It applies to every Media Segment in the
+  // Playlist.
+  bool AreSegmentsIndependent() const { return independent_segments_; }
+
+  // Returns all segments in this playlist, in chronological order. This vector
+  // may be copied independently of this Playlist.
+  const std::vector<MediaSegment>& GetSegments() const { return segments_; }
+
+  // Returns the sum of the duration of all segments in this playlist.
+  // Computed via the 'EXTINF' attribute, so may be slightly longer than the
+  // actual duration.
+  base::TimeDelta GetComputedDuration() const { return computed_duration_; }
+
+  // Attempts to parse the playlist represented by `source`. `uri` must be a
+  // valid, non-empty GURL referring to the URI of this playlist. If the
+  // playlist is invalid, returns an error. Otherwise, returns the parsed
+  // playlist.
+  static ParseStatus::Or<MediaPlaylist> Parse(base::StringPiece source,
+                                              GURL uri);
+
+ private:
+  MediaPlaylist(GURL uri,
+                types::DecimalInteger version,
+                bool independent_segments,
+                std::vector<MediaSegment> segments);
+
+  GURL uri_;
+  types::DecimalInteger version_;
+  bool independent_segments_;
+  std::vector<MediaSegment> segments_;
+  base::TimeDelta computed_duration_;
+};
+
+}  // namespace media::hls
+
+#endif  // MEDIA_FORMATS_HLS_MEDIA_PLAYLIST_H_
diff --git a/media/formats/hls/media_playlist_fuzzer.cc b/media/formats/hls/media_playlist_fuzzer.cc
new file mode 100644
index 0000000..3601d62a
--- /dev/null
+++ b/media/formats/hls/media_playlist_fuzzer.cc
@@ -0,0 +1,23 @@
+// 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 <cstddef>
+#include <cstdint>
+
+#include "base/check.h"
+#include "base/strings/string_piece.h"
+#include "media/formats/hls/media_playlist.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
+#include "url/gurl.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  // Create a StringPiece from the given input
+  const base::StringPiece source(reinterpret_cast<const char*>(data), size);
+
+  // Try to parse it as a media playlist
+  media::hls::MediaPlaylist::Parse(source,
+                                   GURL("http://localhost/playlist.m3u8"));
+
+  return 0;
+}
diff --git a/media/formats/hls/media_playlist_unittest.cc b/media/formats/hls/media_playlist_unittest.cc
new file mode 100644
index 0000000..0d0eb52
--- /dev/null
+++ b/media/formats/hls/media_playlist_unittest.cc
@@ -0,0 +1,221 @@
+// 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 "media/formats/hls/media_playlist.h"
+
+#include <vector>
+
+#include "base/callback_list.h"
+#include "base/location.h"
+#include "media/formats/hls/items.h"
+#include "media/formats/hls/media_segment.h"
+#include "media/formats/hls/source_string.h"
+#include "media/formats/hls/tags.h"
+#include "media/formats/hls/types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media::hls {
+
+namespace {
+
+class TestBuilder {
+ public:
+  void SetUri(GURL uri) { uri_ = std::move(uri); }
+
+  // Appends a new line to the playlist.
+  void AppendLine(base::StringPiece line) {
+    source_.append(line.data(), line.size());
+    source_.append("\n");
+  }
+
+  // Adds a new expectation for the playlist, which will be checked during
+  // `ExpectOk`.
+  template <typename Fn, typename Arg>
+  void ExpectPlaylist(Fn fn,
+                      Arg arg,
+                      base::Location location = base::Location::Current()) {
+    playlist_expectations_.emplace_back(base::BindRepeating(
+        std::move(fn), std::move(arg), std::move(location)));
+  }
+
+  // Increments the number of segments that are expected to be contained in the
+  // playlist.
+  void ExpectAdditionalSegment() { segment_expectations_.push_back({}); }
+
+  // Adds a new expectation for the latest segment in the playlist, which will
+  // be checked during `ExpectOk`.
+  template <typename Fn, typename Arg>
+  void ExpectSegment(Fn fn,
+                     Arg arg,
+                     base::Location location = base::Location::Current()) {
+    segment_expectations_.back().expectations.emplace_back(base::BindRepeating(
+        std::move(fn), std::move(arg), std::move(location)));
+  }
+
+  // Attempts to parse the playlist as-is, checking for the given
+  // error code.
+  void ExpectError(
+      ParseStatusCode code,
+      const base::Location& from = base::Location::Current()) const {
+    auto result = MediaPlaylist::Parse(source_, uri_);
+    ASSERT_TRUE(result.has_error()) << from.ToString();
+    EXPECT_EQ(std::move(result).error().code(), code) << from.ToString();
+  }
+
+  // Attempts to parse the playlist as-is, checking all playlist and segment
+  // expectations.
+  void ExpectOk(const base::Location& from = base::Location::Current()) const {
+    auto result = MediaPlaylist::Parse(source_, uri_);
+    ASSERT_TRUE(result.has_value())
+        << "Error: "
+        << ParseStatusCodeToString(std::move(result).error().code()) << "\n"
+        << from.ToString();
+    auto playlist = std::move(result).value();
+
+    for (const auto& expectation : playlist_expectations_) {
+      expectation.Run(playlist);
+    }
+
+    ASSERT_EQ(segment_expectations_.size(), playlist.GetSegments().size())
+        << from.ToString();
+    for (size_t i = 0; i < segment_expectations_.size(); ++i) {
+      const auto& segment = playlist.GetSegments().at(i);
+      const auto& expectations = segment_expectations_.at(i);
+      for (const auto& expectation : expectations.expectations) {
+        expectation.Run(segment);
+      }
+    }
+  }
+
+ private:
+  struct SegmentExpectations {
+    std::vector<base::RepeatingCallback<void(const MediaSegment&)>>
+        expectations;
+  };
+
+  std::vector<SegmentExpectations> segment_expectations_;
+  std::vector<base::RepeatingCallback<void(const MediaPlaylist&)>>
+      playlist_expectations_;
+  GURL uri_ = GURL("http://localhost/playlist.m3u8");
+  std::string source_;
+};
+
+void HasVersion(types::DecimalInteger version,
+                const base::Location& from,
+                const MediaPlaylist& playlist) {
+  EXPECT_EQ(playlist.GetVersion(), version) << from.ToString();
+}
+
+void HasDuration(types::DecimalFloatingPoint duration,
+                 const base::Location& from,
+                 const MediaSegment& segment) {
+  EXPECT_DOUBLE_EQ(segment.GetDuration(), duration) << from.ToString();
+}
+
+void HasUri(GURL uri, const base::Location& from, const MediaSegment& segment) {
+  EXPECT_EQ(segment.GetUri(), uri) << from.ToString();
+}
+
+void HasDiscontinuity(bool value,
+                      const base::Location& from,
+                      const MediaSegment& segment) {
+  EXPECT_EQ(segment.HasDiscontinuity(), value) << from.ToString();
+}
+
+void IsGap(bool value,
+           const base::Location& from,
+           const MediaSegment& segment) {
+  EXPECT_EQ(segment.IsGap(), value) << from.ToString();
+}
+
+}  // namespace
+
+TEST(HlsFormatParserTest, ParseMediaPlaylist_MissingM3u) {
+  TestBuilder builder;
+  builder.AppendLine("#EXT-X-VERSION:5");
+  builder.ExpectError(ParseStatusCode::kPlaylistMissingM3uTag);
+}
+
+TEST(HlsFormatParserTest, ParseMediaPlaylist_VersionChecks) {
+  TestBuilder builder;
+  builder.AppendLine("#EXTM3U");
+
+  {
+    // Default version is 1
+    auto fork = builder;
+    fork.ExpectPlaylist(HasVersion, 1);
+    fork.ExpectOk();
+  }
+
+  {
+    // "-1" is not a valid decimal-integer
+    auto fork = builder;
+    fork.AppendLine("#EXT-X-VERSION:-1");
+    fork.ExpectError(ParseStatusCode::kMalformedTag);
+  }
+
+  {
+    // "0" is not a valid version
+    auto fork = builder;
+    fork.AppendLine("#EXT-X-VERSION:0");
+    fork.ExpectError(ParseStatusCode::kInvalidPlaylistVersion);
+  }
+
+  for (int i = 1; i <= 10; ++i) {
+    auto fork = builder;
+    fork.AppendLine("#EXT-X-VERSION:" + base::NumberToString(i));
+    fork.ExpectPlaylist(HasVersion, i);
+    fork.ExpectOk();
+  }
+
+  for (int i : {11, 12, 100, 999}) {
+    // Versions 11+ are not supported by this parser
+    auto fork = builder;
+    fork.AppendLine("#EXT-X-VERSION:" + base::NumberToString(i));
+    fork.ExpectError(ParseStatusCode::kPlaylistHasUnsupportedVersion);
+  }
+}
+
+TEST(HlsFormatParserTest, ParseMediaPlaylist_Segments) {
+  TestBuilder builder;
+  builder.AppendLine("#EXTM3U");
+  builder.AppendLine("#EXT-X-VERSION:5");
+  builder.ExpectPlaylist(HasVersion, 5);
+
+  builder.AppendLine("#EXTINF:9.2,\t");
+  builder.AppendLine("video.ts");
+  builder.ExpectAdditionalSegment();
+  builder.ExpectSegment(HasDiscontinuity, false);
+  builder.ExpectSegment(HasDuration, 9.2);
+  builder.ExpectSegment(HasUri, GURL("http://localhost/video.ts"));
+  builder.ExpectSegment(IsGap, false);
+
+  // Segments without #EXTINF tags are not allowed
+  {
+    auto fork = builder;
+    fork.AppendLine("foobar.ts");
+    fork.ExpectError(ParseStatusCode::kMediaSegmentMissingInfTag);
+  }
+
+  builder.AppendLine("#EXTINF:9.3,foo");
+  builder.AppendLine("#EXT-X-DISCONTINUITY");
+  builder.AppendLine("foo.ts");
+  builder.ExpectAdditionalSegment();
+  builder.ExpectSegment(HasDiscontinuity, true);
+  builder.ExpectSegment(HasDuration, 9.3);
+  builder.ExpectSegment(IsGap, false);
+  builder.ExpectSegment(HasUri, GURL("http://localhost/foo.ts"));
+
+  builder.AppendLine("#EXTINF:9.2,bar");
+  builder.AppendLine("http://foo/bar.ts");
+  builder.ExpectAdditionalSegment();
+  builder.ExpectSegment(HasDiscontinuity, false);
+  builder.ExpectSegment(HasDuration, 9.2);
+  builder.ExpectSegment(IsGap, false);
+  builder.ExpectSegment(HasUri, GURL("http://foo/bar.ts"));
+
+  builder.ExpectOk();
+}
+
+}  // namespace media::hls
diff --git a/media/formats/hls/media_segment.cc b/media/formats/hls/media_segment.cc
new file mode 100644
index 0000000..7f278402e
--- /dev/null
+++ b/media/formats/hls/media_segment.cc
@@ -0,0 +1,26 @@
+// 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 "media/formats/hls/media_segment.h"
+
+#include "media/formats/hls/types.h"
+#include "url/gurl.h"
+
+namespace media::hls {
+
+MediaSegment::MediaSegment(types::DecimalFloatingPoint duration,
+                           GURL uri,
+                           bool has_discontinuity,
+                           bool is_gap)
+    : duration_(duration),
+      uri_(std::move(uri)),
+      has_discontinuity_(has_discontinuity),
+      is_gap_(is_gap) {}
+MediaSegment::~MediaSegment() = default;
+MediaSegment::MediaSegment(const MediaSegment&) = default;
+MediaSegment::MediaSegment(MediaSegment&&) = default;
+MediaSegment& MediaSegment::operator=(const MediaSegment&) = default;
+MediaSegment& MediaSegment::operator=(MediaSegment&&) = default;
+
+}  // namespace media::hls
diff --git a/media/formats/hls/media_segment.h b/media/formats/hls/media_segment.h
new file mode 100644
index 0000000..020b8551
--- /dev/null
+++ b/media/formats/hls/media_segment.h
@@ -0,0 +1,51 @@
+// 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 MEDIA_FORMATS_HLS_MEDIA_SEGMENT_H_
+#define MEDIA_FORMATS_HLS_MEDIA_SEGMENT_H_
+
+#include "media/base/media_export.h"
+#include "media/formats/hls/types.h"
+#include "url/gurl.h"
+
+namespace media::hls {
+
+class MEDIA_EXPORT MediaSegment {
+ public:
+  MediaSegment(types::DecimalFloatingPoint duration,
+               GURL uri,
+               bool has_discontinuity,
+               bool is_gap);
+  ~MediaSegment();
+  MediaSegment(const MediaSegment&);
+  MediaSegment(MediaSegment&&);
+  MediaSegment& operator=(const MediaSegment&);
+  MediaSegment& operator=(MediaSegment&&);
+
+  // The approximate duration of this media segment in seconds.
+  types::DecimalFloatingPoint GetDuration() const { return duration_; }
+
+  // The URI of the media resource. This will have already been resolved against
+  // the playlist URI. This is guaranteed to be valid and non-empty, unless
+  // `gap` is true, in which case this URI should not be used.
+  const GURL& GetUri() const { return uri_; }
+
+  // Whether there is a decoding discontinuity between the previous media
+  // segment and this one.
+  bool HasDiscontinuity() const { return has_discontinuity_; }
+
+  // If this is `true`, it indicates that the resource for this media segment is
+  // absent and the client should not attempt to fetch it.
+  bool IsGap() const { return is_gap_; }
+
+ private:
+  types::DecimalFloatingPoint duration_;
+  GURL uri_;
+  bool has_discontinuity_;
+  bool is_gap_;
+};
+
+}  // namespace media::hls
+
+#endif  // MEDIA_FORMATS_HLS_MEDIA_SEGMENT_H_
diff --git a/media/formats/hls/parse_status.cc b/media/formats/hls/parse_status.cc
index 43d4538..ee105a1 100644
--- a/media/formats/hls/parse_status.cc
+++ b/media/formats/hls/parse_status.cc
@@ -25,11 +25,12 @@
     PARSE_STATUS_CODE_CASE(kMalformedAttributeList);
     PARSE_STATUS_CODE_CASE(kAttributeListHasDuplicateNames);
     PARSE_STATUS_CODE_CASE(kMalformedVariableName);
+    PARSE_STATUS_CODE_CASE(kInvalidUri);
     PARSE_STATUS_CODE_CASE(kPlaylistMissingM3uTag);
     PARSE_STATUS_CODE_CASE(kMediaSegmentMissingInfTag);
-    PARSE_STATUS_CODE_CASE(kMediaSegmentHasMultipleInfTags);
-    PARSE_STATUS_CODE_CASE(kPlaylistSpecifiesMultipleVersions);
+    PARSE_STATUS_CODE_CASE(kPlaylistHasDuplicateTags);
     PARSE_STATUS_CODE_CASE(kPlaylistHasUnsupportedVersion);
+    PARSE_STATUS_CODE_CASE(kMediaPlaylistHasMultivariantPlaylistTag);
   }
 
   NOTREACHED();
diff --git a/media/formats/hls/parse_status.h b/media/formats/hls/parse_status.h
index ea0132e..2f28658 100644
--- a/media/formats/hls/parse_status.h
+++ b/media/formats/hls/parse_status.h
@@ -22,11 +22,12 @@
   kMalformedAttributeList,
   kAttributeListHasDuplicateNames,
   kMalformedVariableName,
+  kInvalidUri,
   kPlaylistMissingM3uTag,
   kMediaSegmentMissingInfTag,
-  kMediaSegmentHasMultipleInfTags,
-  kPlaylistSpecifiesMultipleVersions,
+  kPlaylistHasDuplicateTags,
   kPlaylistHasUnsupportedVersion,
+  kMediaPlaylistHasMultivariantPlaylistTag,
 };
 
 struct ParseStatusTraits {
diff --git a/media/formats/hls/playlist_common.cc b/media/formats/hls/playlist_common.cc
new file mode 100644
index 0000000..f174c67
--- /dev/null
+++ b/media/formats/hls/playlist_common.cc
@@ -0,0 +1,95 @@
+// 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 "media/formats/hls/playlist_common.h"
+#include "base/notreached.h"
+
+namespace media::hls {
+
+types::DecimalInteger CommonParserState::GetVersion() const {
+  if (version_tag.has_value()) {
+    return version_tag.value().version;
+  } else {
+    return 1;
+  }
+}
+
+ParseStatus::Or<M3uTag> CheckM3uTag(SourceLineIterator* src_iter) {
+  auto item_result = GetNextLineItem(src_iter);
+  if (item_result.has_error()) {
+    return ParseStatus(ParseStatusCode::kPlaylistMissingM3uTag)
+        .AddCause(std::move(item_result).error());
+  }
+
+  auto item = std::move(item_result).value();
+  if (auto* tag_item = absl::get_if<TagItem>(&item)) {
+    // The #EXTM3U tag must be the first line in the playlist
+    if (tag_item->name != ToTagName(CommonTagName::kM3u) ||
+        tag_item->content.Line() != 1) {
+      return ParseStatusCode::kPlaylistMissingM3uTag;
+    }
+
+    // Make sure the M3U tag parses correctly
+    auto result = M3uTag::Parse(*tag_item);
+    if (result.has_error()) {
+      return ParseStatus(ParseStatusCode::kPlaylistMissingM3uTag)
+          .AddCause(std::move(result).error());
+    }
+
+    return result;
+  }
+
+  return ParseStatusCode::kPlaylistMissingM3uTag;
+}
+
+void HandleUnknownTag(TagItem /*tag*/) {
+  // Unknown tags are ignored for forward-compatibility purposes.
+  // TODO(crbug.com/1266991): Should record a metric to discover common
+  // unrecognized tags.
+}
+
+absl::optional<ParseStatus> ParseCommonTag(TagItem tag,
+                                           CommonParserState* state) {
+  DCHECK(GetTagKind(tag.name) == TagKind::kCommonTag);
+
+  switch (static_cast<CommonTagName>(tag.name)) {
+    case CommonTagName::kM3u:
+      // This tag is meant to occur on the first line (which we've already
+      // checked), however the spec does not explicitly regard this as an
+      // error if it appears elsewhere as well.
+      DCHECK(tag.content.Line() != 1);
+      break;
+    case CommonTagName::kXVersion: {
+      auto error = ParseUniqueTag(tag, state->version_tag);
+      if (error.has_value()) {
+        return error;
+      }
+
+      // Max supported playlist version is 10
+      if (state->version_tag->version > 10) {
+        return ParseStatusCode::kPlaylistHasUnsupportedVersion;
+      }
+      break;
+    }
+    case CommonTagName::kXIndependentSegments: {
+      return ParseUniqueTag(tag, state->independent_segments_tag);
+    }
+    case CommonTagName::kXDefine:
+      // TODO(crbug.com/1266991): Implement variable substitution.
+      break;
+  }
+
+  return absl::nullopt;
+}
+
+ParseStatus::Or<GURL> ParseUri(UriItem item, const GURL& playlist_uri) {
+  auto resolved_uri = playlist_uri.Resolve(item.content.Str());
+  if (!resolved_uri.is_valid()) {
+    return ParseStatusCode::kInvalidUri;
+  }
+
+  return resolved_uri;
+}
+
+}  // namespace media::hls
diff --git a/media/formats/hls/playlist_common.h b/media/formats/hls/playlist_common.h
new file mode 100644
index 0000000..2753fcfb
--- /dev/null
+++ b/media/formats/hls/playlist_common.h
@@ -0,0 +1,62 @@
+// 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 MEDIA_FORMATS_HLS_PLAYLIST_COMMON_H_
+#define MEDIA_FORMATS_HLS_PLAYLIST_COMMON_H_
+
+#include "media/formats/hls/items.h"
+#include "media/formats/hls/tag_name.h"
+#include "media/formats/hls/tags.h"
+#include "media/formats/hls/types.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace media::hls {
+
+// State common to parsing both multivariant and media playlists.
+struct CommonParserState {
+  absl::optional<XVersionTag> version_tag;
+  absl::optional<XIndependentSegmentsTag> independent_segments_tag;
+
+  // Returns the version specified by `version_tag`, or the default version if
+  // the playlist did not contain a version tag.
+  types::DecimalInteger GetVersion() const;
+};
+
+// Validates that the first line of the given SourceLineIterator contains a
+// valid #EXTM3U tag.
+ParseStatus::Or<M3uTag> CheckM3uTag(SourceLineIterator* src_iter);
+
+// Handles an unknown tag.
+void HandleUnknownTag(TagItem);
+
+// Handles parsing for tags that may appear in multivariant or media playlists.
+absl::optional<ParseStatus> ParseCommonTag(TagItem, CommonParserState* state);
+
+// Attempts to parse a tag from the given item, ensuring it has not been
+// already appeared in the playlist.
+template <typename T>
+absl::optional<ParseStatus> ParseUniqueTag(TagItem tag,
+                                           absl::optional<T>& out) {
+  DCHECK(tag.name == ToTagName(T::kName));
+
+  // Ensure this tag has not already appeared.
+  if (out.has_value()) {
+    return ParseStatusCode::kPlaylistHasDuplicateTags;
+  }
+
+  auto tag_result = T::Parse(tag);
+  if (tag_result.has_error()) {
+    return std::move(tag_result).error();
+  }
+  out = std::move(tag_result).value();
+
+  return absl::nullopt;
+}
+
+ParseStatus::Or<GURL> ParseUri(UriItem item, const GURL& playlist_uri);
+
+}  // namespace media::hls
+
+#endif  // MEDIA_FORMATS_HLS_PLAYLIST_COMMON_H_
diff --git a/media/video/gpu_memory_buffer_video_frame_pool.cc b/media/video/gpu_memory_buffer_video_frame_pool.cc
index d86f49a..9b28dd3 100644
--- a/media/video/gpu_memory_buffer_video_frame_pool.cc
+++ b/media/video/gpu_memory_buffer_video_frame_pool.cc
@@ -16,6 +16,7 @@
 
 #include "base/barrier_closure.h"
 #include "base/bind.h"
+#include "base/bits.h"
 #include "base/callback_helpers.h"
 #include "base/containers/circular_deque.h"
 #include "base/containers/stack_container.h"
@@ -507,23 +508,25 @@
 }
 
 void CopyRowsToNV12Buffer(int first_row,
-                          int rows,
-                          int bytes_per_row,
+                          int y_rows,
+                          int uv_rows,
+                          int y_bytes_per_row,
+                          int uv_bytes_per_row,
                           const VideoFrame* source_frame,
                           uint8_t* dest_y,
                           int dest_stride_y,
                           uint8_t* dest_uv,
                           int dest_stride_uv) {
-  TRACE_EVENT2("media", "CopyRowsToNV12Buffer", "bytes_per_row", bytes_per_row,
-               "rows", rows);
+  TRACE_EVENT2("media", "CopyRowsToNV12Buffer", "bytes_per_row",
+               y_bytes_per_row, "rows", y_rows);
 
   if (!dest_y || !dest_uv)
     return;
 
   DCHECK_NE(dest_stride_y, 0);
   DCHECK_NE(dest_stride_uv, 0);
-  DCHECK_LE(bytes_per_row, std::abs(dest_stride_y));
-  DCHECK_LE(bytes_per_row, std::abs(dest_stride_uv));
+  DCHECK_LE(y_bytes_per_row, std::abs(dest_stride_y));
+  DCHECK_LE(uv_bytes_per_row, std::abs(dest_stride_uv));
   DCHECK_EQ(0, first_row % 2);
   DCHECK(source_frame->format() == PIXEL_FORMAT_I420 ||
          source_frame->format() == PIXEL_FORMAT_YV12 ||
@@ -533,16 +536,17 @@
                           first_row * source_frame->stride(VideoFrame::kYPlane),
                       source_frame->stride(VideoFrame::kYPlane),
                       dest_y + first_row * dest_stride_y, dest_stride_y,
-                      bytes_per_row, rows);
+                      y_bytes_per_row, y_rows);
     libyuv::CopyPlane(
         source_frame->visible_data(VideoFrame::kUVPlane) +
             first_row / 2 * source_frame->stride(VideoFrame::kUVPlane),
         source_frame->stride(VideoFrame::kUVPlane),
-        dest_uv + first_row / 2 * dest_stride_uv, dest_stride_uv, bytes_per_row,
-        rows / 2);
+        dest_uv + first_row / 2 * dest_stride_uv, dest_stride_uv,
+        uv_bytes_per_row, uv_rows);
 
     return;
   }
+
   libyuv::I420ToNV12(
       source_frame->visible_data(VideoFrame::kYPlane) +
           first_row * source_frame->stride(VideoFrame::kYPlane),
@@ -554,8 +558,8 @@
           first_row / 2 * source_frame->stride(VideoFrame::kVPlane),
       source_frame->stride(VideoFrame::kVPlane),
       dest_y + first_row * dest_stride_y, dest_stride_y,
-      dest_uv + first_row / 2 * dest_stride_uv, dest_stride_uv, bytes_per_row,
-      rows);
+      dest_uv + first_row / 2 * dest_stride_uv, dest_stride_uv, y_bytes_per_row,
+      y_rows);
 }
 
 void CopyRowsToRGB10Buffer(bool is_argb,
@@ -667,28 +671,28 @@
                     GpuVideoAcceleratorFactories::OutputFormat output_format) {
   DCHECK(gfx::Rect(video_frame->coded_size())
              .Contains(video_frame->visible_rect()));
-  DCHECK_EQ(video_frame->visible_rect().x() % 2, 0);
+
+  size_t width = video_frame->visible_rect().width();
+  size_t height = video_frame->visible_rect().height();
   gfx::Size output;
   switch (output_format) {
     case GpuVideoAcceleratorFactories::OutputFormat::I420:
     case GpuVideoAcceleratorFactories::OutputFormat::P010:
     case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB:
     case GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB:
-      if (gfx::AllowOddHeightMultiPlanarBuffers()) {
-        output = gfx::Size((video_frame->visible_rect().width() + 1) & ~1,
-                           video_frame->visible_rect().height());
-      } else {
-        DCHECK((video_frame->visible_rect().y() & 1) == 0);
-        output = gfx::Size((video_frame->visible_rect().width() + 1) & ~1,
-                           (video_frame->visible_rect().height() + 1) & ~1);
-      }
+      DCHECK_EQ(video_frame->visible_rect().x() % 2, 0);
+      DCHECK_EQ(video_frame->visible_rect().y() % 2, 0);
+      if (!gfx::IsOddWidthMultiPlanarBuffersAllowed())
+        width = base::bits::AlignUp(width, 2);
+      if (!gfx::IsOddHeightMultiPlanarBuffersAllowed())
+        height = base::bits::AlignUp(height, 2);
+      output = gfx::Size(width, height);
       break;
     case GpuVideoAcceleratorFactories::OutputFormat::XR30:
     case GpuVideoAcceleratorFactories::OutputFormat::XB30:
     case GpuVideoAcceleratorFactories::OutputFormat::RGBA:
     case GpuVideoAcceleratorFactories::OutputFormat::BGRA:
-      output = gfx::Size((video_frame->visible_rect().width() + 1) & ~1,
-                         video_frame->visible_rect().height());
+      output = gfx::Size(base::bits::AlignUp(width, 2), height);
       break;
     case GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED:
       NOTREACHED();
@@ -737,6 +741,7 @@
 
   if (output_format_ == GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED)
     passthrough = true;
+
   switch (pixel_format) {
     // Supported cases.
     case PIXEL_FORMAT_YV12:
@@ -781,19 +786,19 @@
   }
 
   // TODO(https://crbug.com/638906): Handle odd positioned video frame input.
-  if (video_frame->visible_rect().x() % 2)
-    passthrough = true;
-  if (video_frame->visible_rect().y() % 2 &&
-      !gfx::AllowOddHeightMultiPlanarBuffers()) {
+  if (video_frame->visible_rect().x() % 2 ||
+      video_frame->visible_rect().y() % 2) {
     passthrough = true;
   }
 
   // TODO(https://crbug.com/webrtc/9033): Eliminate odd size video frame input
   // cases as they are not valid.
-  if (video_frame->coded_size().width() % 2)
+  if (video_frame->coded_size().width() % 2 &&
+      !gfx::IsOddWidthMultiPlanarBuffersAllowed()) {
     passthrough = true;
+  }
   if (video_frame->coded_size().height() % 2 &&
-      !gfx::AllowOddHeightMultiPlanarBuffers()) {
+      !gfx::IsOddHeightMultiPlanarBuffersAllowed()) {
     passthrough = true;
   }
 
@@ -1001,23 +1006,45 @@
           static_cast<uint8_t*>(buffer->memory(0)), buffer->stride(0));
       break;
     }
+
     case GpuVideoAcceleratorFactories::OutputFormat::P010:
       CopyRowsToP010Buffer(
           row, rows_to_copy, coded_size.width(), video_frame,
           static_cast<uint8_t*>(buffer->memory(0)), buffer->stride(0),
           static_cast<uint8_t*>(buffer->memory(1)), buffer->stride(1));
       break;
-    case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB:
+
+    case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB: {
+      const size_t y_rows_to_copy = VideoFrame::Rows(
+          VideoFrame::kYPlane, VideoFormat(output_format), rows_to_copy);
+      const size_t uv_rows_to_copy = VideoFrame::Rows(
+          VideoFrame::kUVPlane, VideoFormat(output_format), rows_to_copy);
+      const size_t y_bytes_per_row = VideoFrame::RowBytes(
+          VideoFrame::kYPlane, VideoFormat(output_format), coded_size.width());
+      const size_t uv_bytes_per_row = VideoFrame::RowBytes(
+          VideoFrame::kUVPlane, VideoFormat(output_format), coded_size.width());
       CopyRowsToNV12Buffer(
-          row, rows_to_copy, coded_size.width(), video_frame,
+          row, y_rows_to_copy, uv_rows_to_copy, y_bytes_per_row,
+          uv_bytes_per_row, video_frame,
           static_cast<uint8_t*>(buffer->memory(0)), buffer->stride(0),
           static_cast<uint8_t*>(buffer->memory(1)), buffer->stride(1));
       break;
+    }
+
     case GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB: {
+      const size_t y_rows_to_copy = VideoFrame::Rows(
+          VideoFrame::kYPlane, VideoFormat(output_format), rows_to_copy);
+      const size_t uv_rows_to_copy = VideoFrame::Rows(
+          VideoFrame::kUVPlane, VideoFormat(output_format), rows_to_copy);
+      const size_t y_bytes_per_row = VideoFrame::RowBytes(
+          VideoFrame::kYPlane, VideoFormat(output_format), coded_size.width());
+      const size_t uv_bytes_per_row = VideoFrame::RowBytes(
+          VideoFrame::kUVPlane, VideoFormat(output_format), coded_size.width());
       gfx::GpuMemoryBuffer* buffer2 =
           frame_resources->plane_resources[1].gpu_memory_buffer.get();
       CopyRowsToNV12Buffer(
-          row, rows_to_copy, coded_size.width(), video_frame,
+          row, y_rows_to_copy, uv_rows_to_copy, y_bytes_per_row,
+          uv_bytes_per_row, video_frame,
           static_cast<uint8_t*>(buffer->memory(0)), buffer->stride(0),
           static_cast<uint8_t*>(buffer2->memory(0)), buffer2->stride(0));
       break;
diff --git a/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc b/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc
index d94cd64b..b619ccb 100644
--- a/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc
+++ b/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc
@@ -47,6 +47,15 @@
     gpu_memory_buffer_pool_.reset();
     RunUntilIdle();
     mock_gpu_factories_.reset();
+
+    if (y_data_)
+      delete[] y_data_;
+    if (u_data_)
+      delete[] u_data_;
+    if (v_data_)
+      delete[] v_data_;
+    if (uv_data_)
+      delete[] uv_data_;
   }
 
   void RunUntilIdle() {
@@ -89,6 +98,47 @@
     return video_frame;
   }
 
+  scoped_refptr<VideoFrame> CreateTestYUVVideoFrameWithOddSize(
+      int dimension,
+      size_t bit_depth = 8,
+      int visible_rect_crop = 0) {
+    const VideoPixelFormat format =
+        (bit_depth > 8) ? PIXEL_FORMAT_YUV420P10 : PIXEL_FORMAT_I420;
+    const int multiplier = format == PIXEL_FORMAT_YUV420P10 ? 2 : 1;
+
+    int dimension_aligned = (dimension + 1) & ~1;
+    y_data_ = new uint8_t[multiplier * dimension * dimension]();
+    u_data_ =
+        new uint8_t[multiplier * dimension_aligned * dimension_aligned / 4]();
+    v_data_ =
+        new uint8_t[multiplier * dimension_aligned * dimension_aligned / 4]();
+
+    // Initialize the last pixel of each plane
+    int y_size = multiplier * dimension * dimension;
+    y_data_[y_size - multiplier] = kYValue;
+    int u_v_size = multiplier * dimension_aligned * dimension_aligned / 4;
+    u_data_[u_v_size - multiplier] = kUValue;
+    v_data_[u_v_size - multiplier] = kVValue;
+
+    const gfx::Size size(dimension, dimension);
+    scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalYuvData(
+        format,  // format
+        size,    // coded_size
+        gfx::Rect(visible_rect_crop, visible_rect_crop,
+                  size.width() - visible_rect_crop * 2,
+                  size.height() - visible_rect_crop * 2),  // visible_rect
+        size,                                              // natural_size
+        size.width() * multiplier,                         // y_stride
+        dimension_aligned * multiplier / 2,                // u_stride
+        dimension_aligned * multiplier / 2,                // v_stride
+        y_data_,                                           // y_data
+        u_data_,                                           // u_data
+        v_data_,                                           // v_data
+        base::TimeDelta());                                // timestamp
+    EXPECT_TRUE(video_frame);
+    return video_frame;
+  }
+
   static scoped_refptr<VideoFrame> CreateTestYUVAVideoFrame(int dimension) {
     const int kDimension = 10;
     static uint8_t y_data[kDimension * kDimension] = {0};
@@ -119,6 +169,7 @@
   }
 
   static scoped_refptr<VideoFrame> CreateTestNV12VideoFrame(int dimension) {
+    // Set the video buffer memory dimension default to 10.
     const int kDimension = 10;
     static uint8_t y_data[kDimension * kDimension] = {0};
     // Subsampled by 2x2, two components.
@@ -142,6 +193,36 @@
     return video_frame;
   }
 
+  scoped_refptr<VideoFrame> CreateTestNV12VideoFrameWithOddSize(int dimension) {
+    // Set the video buffer memory dimension to the same size of the requested
+    // dimension.
+    int dimension_aligned = (dimension + 1) & ~1;
+    y_data_ = new uint8_t[dimension * dimension]();
+    // Subsampled by 2x2, two components.
+    uv_data_ = new uint8_t[dimension_aligned * dimension_aligned / 2]();
+
+    // Initialize the last pixel of each plane
+    y_data_[dimension * dimension - 1] = kYValue;
+    uv_data_[(dimension_aligned * dimension_aligned / 2) - 2] = kUValue;
+    uv_data_[(dimension_aligned * dimension_aligned / 2) - 1] = kVValue;
+
+    const VideoPixelFormat format = PIXEL_FORMAT_NV12;
+    const gfx::Size size(dimension, dimension);
+
+    scoped_refptr<VideoFrame> video_frame =
+        VideoFrame::WrapExternalYuvData(format,              // format
+                                        size,                // coded_size
+                                        gfx::Rect(size),     // visible_rect
+                                        size,                // natural_size
+                                        size.width(),        // y_stride
+                                        dimension_aligned,   // uv_stride
+                                        y_data_,             // y_data
+                                        uv_data_,            // uv_data
+                                        base::TimeDelta());  // timestamp
+    EXPECT_TRUE(video_frame);
+    return video_frame;
+  }
+
   // Note, the X portion is set to 1 since it may use ARGB instead of
   // XRGB on some platforms.
   uint32_t as_xr30(uint32_t r, uint32_t g, uint32_t b) {
@@ -152,6 +233,15 @@
   }
 
  protected:
+  static constexpr uint8_t kYValue = 210;
+  static constexpr uint8_t kUValue = 50;
+  static constexpr uint8_t kVValue = 150;
+
+  uint8_t* y_data_ = nullptr;
+  uint8_t* u_data_ = nullptr;
+  uint8_t* v_data_ = nullptr;
+  uint8_t* uv_data_ = nullptr;
+
   base::SimpleTestTickClock test_clock_;
   std::unique_ptr<MockGpuVideoAcceleratorFactories> mock_gpu_factories_;
   std::unique_ptr<GpuMemoryBufferVideoFramePool> gpu_memory_buffer_pool_;
@@ -203,6 +293,59 @@
   EXPECT_EQ(3u, sii_->shared_image_count());
 }
 
+TEST_F(GpuMemoryBufferVideoFramePoolTest, CreateOneHardwareFrameWithOddSize) {
+  scoped_refptr<VideoFrame> software_frame =
+      CreateTestYUVVideoFrameWithOddSize(9);
+  scoped_refptr<VideoFrame> frame;
+  gpu_memory_buffer_pool_->MaybeCreateHardwareFrame(
+      software_frame, base::BindOnce(MaybeCreateHardwareFrameCallback, &frame));
+
+  RunUntilIdle();
+
+  if (gfx::IsOddWidthMultiPlanarBuffersAllowed() &&
+      gfx::IsOddHeightMultiPlanarBuffersAllowed()) {
+    EXPECT_NE(software_frame.get(), frame.get());
+    EXPECT_EQ(PIXEL_FORMAT_I420, frame->format());
+    EXPECT_EQ(3u, frame->NumTextures());
+    EXPECT_EQ(3u, sii_->shared_image_count());
+
+    EXPECT_EQ(3u, mock_gpu_factories_->created_memory_buffers().size());
+    mock_gpu_factories_->created_memory_buffers()[0]->Map();
+    mock_gpu_factories_->created_memory_buffers()[1]->Map();
+    mock_gpu_factories_->created_memory_buffers()[2]->Map();
+
+    const auto* y_memory = reinterpret_cast<uint8_t*>(
+        mock_gpu_factories_->created_memory_buffers()[0]->memory(0));
+    const auto* u_memory = reinterpret_cast<uint8_t*>(
+        mock_gpu_factories_->created_memory_buffers()[1]->memory(0));
+    const auto* v_memory = reinterpret_cast<uint8_t*>(
+        mock_gpu_factories_->created_memory_buffers()[2]->memory(0));
+
+    // Y plane = 9x9, U and V plan = 5x5.
+    EXPECT_EQ(kYValue, software_frame->visible_data(VideoFrame::kYPlane)[80]);
+    EXPECT_EQ(kUValue, software_frame->visible_data(VideoFrame::kUPlane)[24]);
+    EXPECT_EQ(kVValue, software_frame->visible_data(VideoFrame::kVPlane)[24]);
+
+    // Compare the last pixel of each plane in |software_frame| and |frame|.
+    auto y_stride = mock_gpu_factories_->created_memory_buffers()[0]->stride(0);
+    EXPECT_EQ(software_frame->visible_data(VideoFrame::kYPlane)[80],
+              y_memory[y_stride * 8 + 8]);
+    auto u_stride = mock_gpu_factories_->created_memory_buffers()[1]->stride(0);
+    EXPECT_EQ(software_frame->visible_data(VideoFrame::kUPlane)[24],
+              u_memory[u_stride * 4 + 4]);
+    auto v_stride = mock_gpu_factories_->created_memory_buffers()[2]->stride(0);
+    EXPECT_EQ(software_frame->visible_data(VideoFrame::kVPlane)[24],
+              v_memory[v_stride * 4 + 4]);
+
+    mock_gpu_factories_->created_memory_buffers()[0]->Unmap();
+    mock_gpu_factories_->created_memory_buffers()[1]->Unmap();
+    mock_gpu_factories_->created_memory_buffers()[2]->Unmap();
+
+  } else {
+    EXPECT_EQ(software_frame.get(), frame.get());
+  }
+}
+
 // Tests the current workaround for odd positioned video frame input. Once
 // https://crbug.com/638906 is fixed, output should be different.
 TEST_F(GpuMemoryBufferVideoFramePoolTest, CreateOneHardwareFrameWithOddOrigin) {
@@ -216,6 +359,19 @@
   EXPECT_EQ(software_frame.get(), frame.get());
 }
 
+TEST_F(GpuMemoryBufferVideoFramePoolTest,
+       CreateOneHardwareFrameWithOddOriginOddSize) {
+  scoped_refptr<VideoFrame> software_frame =
+      CreateTestYUVVideoFrameWithOddSize(11, 8, 1);
+  scoped_refptr<VideoFrame> frame;
+  gpu_memory_buffer_pool_->MaybeCreateHardwareFrame(
+      software_frame, base::BindOnce(MaybeCreateHardwareFrameCallback, &frame));
+
+  RunUntilIdle();
+
+  EXPECT_EQ(software_frame.get(), frame.get());
+}
+
 TEST_F(GpuMemoryBufferVideoFramePoolTest, CreateOne10BppHardwareFrame) {
   scoped_refptr<VideoFrame> software_frame = CreateTestYUVVideoFrame(10, 10);
   scoped_refptr<VideoFrame> frame;
@@ -230,6 +386,66 @@
   EXPECT_EQ(3u, sii_->shared_image_count());
 }
 
+TEST_F(GpuMemoryBufferVideoFramePoolTest,
+       CreateOne10BppHardwareFrameWithOddSize) {
+  scoped_refptr<VideoFrame> software_frame =
+      CreateTestYUVVideoFrameWithOddSize(17, 10);
+  scoped_refptr<VideoFrame> frame;
+  gpu_memory_buffer_pool_->MaybeCreateHardwareFrame(
+      software_frame, base::BindOnce(MaybeCreateHardwareFrameCallback, &frame));
+
+  RunUntilIdle();
+
+  if (gfx::IsOddWidthMultiPlanarBuffersAllowed() &&
+      gfx::IsOddHeightMultiPlanarBuffersAllowed()) {
+    EXPECT_NE(software_frame.get(), frame.get());
+    EXPECT_EQ(PIXEL_FORMAT_I420, frame->format());
+    EXPECT_EQ(3u, frame->NumTextures());
+    EXPECT_EQ(3u, sii_->shared_image_count());
+
+    EXPECT_EQ(3u, mock_gpu_factories_->created_memory_buffers().size());
+    mock_gpu_factories_->created_memory_buffers()[0]->Map();
+    mock_gpu_factories_->created_memory_buffers()[1]->Map();
+    mock_gpu_factories_->created_memory_buffers()[2]->Map();
+
+    // Copy 10 bpp to I420.
+    const auto* y_memory = reinterpret_cast<uint8_t*>(
+        mock_gpu_factories_->created_memory_buffers()[0]->memory(0));
+    const auto* u_memory = reinterpret_cast<uint8_t*>(
+        mock_gpu_factories_->created_memory_buffers()[1]->memory(0));
+    const auto* v_memory = reinterpret_cast<uint8_t*>(
+        mock_gpu_factories_->created_memory_buffers()[2]->memory(0));
+
+    const uint16_t* y_plane_data = reinterpret_cast<uint16_t*>(
+        software_frame->visible_data(VideoFrame::kYPlane));
+    const uint16_t* u_plane_data = reinterpret_cast<uint16_t*>(
+        software_frame->visible_data(VideoFrame::kUPlane));
+    const uint16_t* v_plane_data = reinterpret_cast<uint16_t*>(
+        software_frame->visible_data(VideoFrame::kVPlane));
+
+    // Y plane = 17x17 = 289, U and V plan = 9x9.
+    EXPECT_EQ(kYValue, y_plane_data[288]);
+    EXPECT_EQ(kUValue, u_plane_data[80]);
+    EXPECT_EQ(kVValue, v_plane_data[80]);
+
+    // Compare the last pixel of each plane in |software_frame| and |frame|.
+    //  y_memory = 17x17, u_memory/v_memory = 9x9. scale = 10 bits - 8 bits  = 2
+    auto y_stride = mock_gpu_factories_->created_memory_buffers()[0]->stride(0);
+    EXPECT_EQ(y_plane_data[288] >> 2, y_memory[y_stride * 16 + 16]);
+    auto u_stride = mock_gpu_factories_->created_memory_buffers()[1]->stride(0);
+    EXPECT_EQ(u_plane_data[80] >> 2, u_memory[u_stride * 8 + 8]);
+    auto v_stride = mock_gpu_factories_->created_memory_buffers()[2]->stride(0);
+    EXPECT_EQ(v_plane_data[80] >> 2, v_memory[v_stride * 8 + 8]);
+
+    mock_gpu_factories_->created_memory_buffers()[0]->Unmap();
+    mock_gpu_factories_->created_memory_buffers()[1]->Unmap();
+    mock_gpu_factories_->created_memory_buffers()[2]->Unmap();
+
+  } else {
+    EXPECT_EQ(software_frame.get(), frame.get());
+  }
+}
+
 TEST_F(GpuMemoryBufferVideoFramePoolTest, ReuseFirstResource) {
   scoped_refptr<VideoFrame> software_frame = CreateTestYUVVideoFrame(10);
   scoped_refptr<VideoFrame> frame;
@@ -318,6 +534,66 @@
   EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
 }
 
+TEST_F(GpuMemoryBufferVideoFramePoolTest,
+       CreateOneHardwareNV12FrameWithOddSize) {
+  scoped_refptr<VideoFrame> software_frame =
+      CreateTestYUVVideoFrameWithOddSize(13);
+  scoped_refptr<VideoFrame> frame;
+  mock_gpu_factories_->SetVideoFrameOutputFormat(
+      media::GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB);
+  gpu_memory_buffer_pool_->MaybeCreateHardwareFrame(
+      software_frame, base::BindOnce(MaybeCreateHardwareFrameCallback, &frame));
+
+  RunUntilIdle();
+
+  if (gfx::IsOddWidthMultiPlanarBuffersAllowed() &&
+      gfx::IsOddHeightMultiPlanarBuffersAllowed()) {
+    EXPECT_NE(software_frame.get(), frame.get());
+    EXPECT_EQ(PIXEL_FORMAT_NV12, frame->format());
+    if (GpuMemoryBufferVideoFramePool::MultiPlaneVideoSharedImagesEnabled()) {
+      EXPECT_EQ(2u, frame->NumTextures());
+      EXPECT_EQ(2u, sii_->shared_image_count());
+
+      EXPECT_EQ(1u, mock_gpu_factories_->created_memory_buffers().size());
+      mock_gpu_factories_->created_memory_buffers()[0]->Map();
+
+      const auto* y_memory = reinterpret_cast<uint8_t*>(
+          mock_gpu_factories_->created_memory_buffers()[0]->memory(0));
+      const auto* uv_memory = reinterpret_cast<uint8_t*>(
+          mock_gpu_factories_->created_memory_buffers()[0]->memory(1));
+
+      // Y plane = 13x13 = 169, U and V plan = 7x7 = 49.
+      EXPECT_EQ(kYValue,
+                software_frame->visible_data(VideoFrame::kYPlane)[168]);
+      EXPECT_EQ(kUValue, software_frame->visible_data(VideoFrame::kUPlane)[48]);
+      EXPECT_EQ(kVValue, software_frame->visible_data(VideoFrame::kVPlane)[48]);
+
+      // Compare the last pixel of each plane in |software_frame| and |frame|.
+      // y_memory = 13x13, uv_memory = 14x 7.
+      auto y_stride =
+          mock_gpu_factories_->created_memory_buffers()[0]->stride(0);
+      EXPECT_EQ(software_frame->visible_data(VideoFrame::kYPlane)[168],
+                y_memory[y_stride * 12 + 12]);
+      auto uv_stride =
+          mock_gpu_factories_->created_memory_buffers()[0]->stride(1);
+      EXPECT_EQ(software_frame->visible_data(VideoFrame::kUPlane)[48],
+                uv_memory[uv_stride * 6 + 12]);
+      EXPECT_EQ(software_frame->visible_data(VideoFrame::kVPlane)[48],
+                uv_memory[uv_stride * 6 + 13]);
+
+      mock_gpu_factories_->created_memory_buffers()[0]->Unmap();
+    } else {
+      EXPECT_EQ(1u, frame->NumTextures());
+      EXPECT_EQ(1u, sii_->shared_image_count());
+    }
+
+    EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
+
+  } else {
+    EXPECT_EQ(software_frame.get(), frame.get());
+  }
+}
+
 TEST_F(GpuMemoryBufferVideoFramePoolTest, CreateOneHardwareNV12Frame2) {
   scoped_refptr<VideoFrame> software_frame = CreateTestYUVVideoFrame(10);
   scoped_refptr<VideoFrame> frame;
@@ -335,6 +611,59 @@
   EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
 }
 
+TEST_F(GpuMemoryBufferVideoFramePoolTest,
+       CreateOneHardwareNV12Frame2WithOddSize) {
+  scoped_refptr<VideoFrame> software_frame =
+      CreateTestYUVVideoFrameWithOddSize(5);
+  scoped_refptr<VideoFrame> frame;
+  mock_gpu_factories_->SetVideoFrameOutputFormat(
+      media::GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB);
+  gpu_memory_buffer_pool_->MaybeCreateHardwareFrame(
+      software_frame, base::BindOnce(MaybeCreateHardwareFrameCallback, &frame));
+
+  RunUntilIdle();
+
+  if (gfx::IsOddWidthMultiPlanarBuffersAllowed() &&
+      gfx::IsOddHeightMultiPlanarBuffersAllowed()) {
+    EXPECT_NE(software_frame.get(), frame.get());
+    EXPECT_EQ(PIXEL_FORMAT_NV12, frame->format());
+    EXPECT_EQ(2u, frame->NumTextures());
+    EXPECT_EQ(2u, sii_->shared_image_count());
+    EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
+
+    EXPECT_EQ(2u, mock_gpu_factories_->created_memory_buffers().size());
+    mock_gpu_factories_->created_memory_buffers()[0]->Map();
+    mock_gpu_factories_->created_memory_buffers()[1]->Map();
+
+    const auto* y_memory = reinterpret_cast<uint8_t*>(
+        mock_gpu_factories_->created_memory_buffers()[0]->memory(0));
+    const auto* uv_memory = reinterpret_cast<uint8_t*>(
+        mock_gpu_factories_->created_memory_buffers()[1]->memory(0));
+
+    // Y plane = 5x5, U and V plan = 3x3.
+    EXPECT_EQ(kYValue, software_frame->visible_data(VideoFrame::kYPlane)[24]);
+    EXPECT_EQ(kUValue, software_frame->visible_data(VideoFrame::kUPlane)[8]);
+    EXPECT_EQ(kVValue, software_frame->visible_data(VideoFrame::kVPlane)[8]);
+
+    // Compare the last pixel of each plane in |software_frame| and |frame|.
+    // y_memory = 5x5, uv_memory = 6x3.
+    auto y_stride = mock_gpu_factories_->created_memory_buffers()[0]->stride(0);
+    EXPECT_EQ(software_frame->visible_data(VideoFrame::kYPlane)[24],
+              y_memory[y_stride * 4 + 4]);
+    auto uv_stride =
+        mock_gpu_factories_->created_memory_buffers()[1]->stride(0);
+    EXPECT_EQ(software_frame->visible_data(VideoFrame::kUPlane)[8],
+              uv_memory[uv_stride * 2 + 4]);
+    EXPECT_EQ(software_frame->visible_data(VideoFrame::kVPlane)[8],
+              uv_memory[uv_stride * 2 + 5]);
+
+    mock_gpu_factories_->created_memory_buffers()[0]->Unmap();
+    mock_gpu_factories_->created_memory_buffers()[1]->Unmap();
+  } else {
+    EXPECT_EQ(software_frame.get(), frame.get());
+  }
+}
+
 TEST_F(GpuMemoryBufferVideoFramePoolTest, CreateOneHardwareFrameForNV12Input) {
   scoped_refptr<VideoFrame> software_frame = CreateTestNV12VideoFrame(10);
   scoped_refptr<VideoFrame> frame;
@@ -351,6 +680,62 @@
   EXPECT_EQ(2u, sii_->shared_image_count());
 }
 
+TEST_F(GpuMemoryBufferVideoFramePoolTest,
+       CreateOneHardwareFrameForNV12InputWithOddSize) {
+  scoped_refptr<VideoFrame> software_frame =
+      CreateTestNV12VideoFrameWithOddSize(135);
+  scoped_refptr<VideoFrame> frame;
+  mock_gpu_factories_->SetVideoFrameOutputFormat(
+      media::GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB);
+  gpu_memory_buffer_pool_->MaybeCreateHardwareFrame(
+      software_frame, base::BindOnce(MaybeCreateHardwareFrameCallback, &frame));
+
+  RunUntilIdle();
+
+  if (gfx::IsOddWidthMultiPlanarBuffersAllowed() &&
+      gfx::IsOddHeightMultiPlanarBuffersAllowed()) {
+    EXPECT_NE(software_frame.get(), frame.get());
+    EXPECT_EQ(PIXEL_FORMAT_NV12, frame->format());
+    EXPECT_EQ(2u, frame->NumTextures());
+    EXPECT_EQ(2u, sii_->shared_image_count());
+
+    EXPECT_EQ(2u, mock_gpu_factories_->created_memory_buffers().size());
+    mock_gpu_factories_->created_memory_buffers()[0]->Map();
+    mock_gpu_factories_->created_memory_buffers()[1]->Map();
+
+    const auto* y_memory = reinterpret_cast<uint8_t*>(
+        mock_gpu_factories_->created_memory_buffers()[0]->memory(0));
+    const auto* uv_memory = reinterpret_cast<uint8_t*>(
+        mock_gpu_factories_->created_memory_buffers()[1]->memory(0));
+
+    // Y plane = 135x135 = 18225, UV plan = 136x68 = 9248.
+    EXPECT_EQ(kYValue,
+              software_frame->visible_data(VideoFrame::kYPlane)[18224]);
+    EXPECT_EQ(kUValue,
+              software_frame->visible_data(VideoFrame::kUVPlane)[9246]);
+    EXPECT_EQ(kVValue,
+              software_frame->visible_data(VideoFrame::kUVPlane)[9247]);
+
+    // Compare the last pixel of each plane in |software_frame| and |frame|.
+    // y_memory = 135x135, uv_memory = 136x68.
+    auto y_stride = mock_gpu_factories_->created_memory_buffers()[0]->stride(0);
+    EXPECT_EQ(software_frame->visible_data(VideoFrame::kYPlane)[18224],
+              y_memory[y_stride * 134 + 134]);
+    auto uv_stride =
+        mock_gpu_factories_->created_memory_buffers()[1]->stride(0);
+    EXPECT_EQ(software_frame->visible_data(VideoFrame::kUVPlane)[9246],
+              uv_memory[uv_stride * 67 + 134]);
+    EXPECT_EQ(software_frame->visible_data(VideoFrame::kUVPlane)[9247],
+              uv_memory[uv_stride * 67 + 135]);
+
+    mock_gpu_factories_->created_memory_buffers()[0]->Unmap();
+    mock_gpu_factories_->created_memory_buffers()[1]->Unmap();
+
+  } else {
+    EXPECT_EQ(software_frame.get(), frame.get());
+  }
+}
+
 TEST_F(GpuMemoryBufferVideoFramePoolTest, CreateOneHardwareXR30Frame) {
   scoped_refptr<VideoFrame> software_frame = CreateTestYUVVideoFrame(10, 10);
   scoped_refptr<VideoFrame> frame;
@@ -405,6 +790,61 @@
             uv_memory[1]);
 }
 
+TEST_F(GpuMemoryBufferVideoFramePoolTest,
+       CreateOneHardwareP010FrameWithOddSize) {
+  scoped_refptr<VideoFrame> software_frame =
+      CreateTestYUVVideoFrameWithOddSize(7, 10);
+  scoped_refptr<VideoFrame> frame;
+  mock_gpu_factories_->SetVideoFrameOutputFormat(
+      media::GpuVideoAcceleratorFactories::OutputFormat::P010);
+  gpu_memory_buffer_pool_->MaybeCreateHardwareFrame(
+      software_frame, base::BindOnce(MaybeCreateHardwareFrameCallback, &frame));
+
+  RunUntilIdle();
+
+  if (gfx::IsOddWidthMultiPlanarBuffersAllowed() &&
+      gfx::IsOddHeightMultiPlanarBuffersAllowed()) {
+    EXPECT_NE(software_frame.get(), frame.get());
+    EXPECT_EQ(PIXEL_FORMAT_P016LE, frame->format());
+    EXPECT_EQ(1u, frame->NumTextures());
+    EXPECT_EQ(1u, sii_->shared_image_count());
+    EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
+
+    EXPECT_EQ(1u, mock_gpu_factories_->created_memory_buffers().size());
+    mock_gpu_factories_->created_memory_buffers()[0]->Map();
+
+    // Copy I010 To P010.
+    const uint16_t* y_memory = reinterpret_cast<uint16_t*>(
+        mock_gpu_factories_->created_memory_buffers()[0]->memory(0));
+    const uint16_t* uv_memory = reinterpret_cast<uint16_t*>(
+        mock_gpu_factories_->created_memory_buffers()[0]->memory(1));
+
+    const uint16_t* y_plane_data = reinterpret_cast<uint16_t*>(
+        software_frame->visible_data(VideoFrame::kYPlane));
+    const uint16_t* u_plane_data = reinterpret_cast<uint16_t*>(
+        software_frame->visible_data(VideoFrame::kUPlane));
+    const uint16_t* v_plane_data = reinterpret_cast<uint16_t*>(
+        software_frame->visible_data(VideoFrame::kVPlane));
+
+    // Y plane = 7x7 = 49, U and V plan = 4x4 = 16.
+    EXPECT_EQ(kYValue, y_plane_data[48]);
+    EXPECT_EQ(kUValue, u_plane_data[15]);
+    EXPECT_EQ(kVValue, v_plane_data[15]);
+    // Compare the last pixel of each plane in |software_frame| and |frame|.
+    // y_memory = 7x7, uv_memory = 8x4, scale = 16-10 = 6.
+    auto y_stride = mock_gpu_factories_->created_memory_buffers()[0]->stride(0);
+    EXPECT_EQ(y_plane_data[48], y_memory[y_stride / 2 * 6 + 6] >> 6);
+    auto uv_stride =
+        mock_gpu_factories_->created_memory_buffers()[0]->stride(1);
+    EXPECT_EQ(u_plane_data[15], uv_memory[uv_stride / 2 * 3 + 6] >> 6);
+    EXPECT_EQ(v_plane_data[15], uv_memory[uv_stride / 2 * 3 + 7] >> 6);
+
+    mock_gpu_factories_->created_memory_buffers()[0]->Unmap();
+  } else {
+    EXPECT_EQ(software_frame.get(), frame.get());
+  }
+}
+
 TEST_F(GpuMemoryBufferVideoFramePoolTest, CreateOneHardwareXR30FrameBT709) {
   scoped_refptr<VideoFrame> software_frame = CreateTestYUVVideoFrame(10, 10);
   software_frame->set_color_space(gfx::ColorSpace::CreateREC709());
diff --git a/media/video/mock_gpu_video_accelerator_factories.cc b/media/video/mock_gpu_video_accelerator_factories.cc
index 0f350c4b..c01a445 100644
--- a/media/video/mock_gpu_video_accelerator_factories.cc
+++ b/media/video/mock_gpu_video_accelerator_factories.cc
@@ -39,9 +39,7 @@
            gfx::BufferFormat::BGRA_8888 == format_);
     DCHECK(num_planes_ <= kMaxPlanes);
     for (int i = 0; i < static_cast<int>(num_planes_); ++i) {
-      bytes_[i].resize(gfx::RowSizeForBufferFormat(size_.width(), format_, i) *
-                       size_.height() /
-                       gfx::SubsamplingFactorForBufferFormat(format_, i));
+      bytes_[i].resize(gfx::PlaneSizeForBufferFormat(size_, format_, i));
     }
   }
 
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index f3b51f1..a3f1bcca 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -2030,8 +2030,12 @@
   // not be simultaneously set.
   DCHECK(!allow_port_migration || !migrate_sessions_early);
 
-  if (allow_port_migration)
+  if (allow_port_migration) {
     params_.allow_port_migration = true;
+    if (migrate_idle_sessions) {
+      params_.migrate_idle_sessions = true;
+    }
+  }
 
   if (!NetworkChangeNotifier::AreNetworkHandlesSupported())
     return;
@@ -2047,8 +2051,7 @@
   params_.migrate_sessions_on_network_change_v2 = true;
 
   if (!migrate_sessions_early) {
-    DCHECK(!migrate_idle_sessions &&
-           !retry_on_alternate_network_before_handshake);
+    DCHECK(!retry_on_alternate_network_before_handshake);
     return;
   }
 
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index f59fa12..aebe748 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -5739,6 +5739,185 @@
   EXPECT_TRUE(quic_data1.AllWriteDataConsumed());
 }
 
+TEST_P(QuicStreamFactoryTest,
+       MigratePortOnPathDegrading_MigrateIdleSession_PathValidator) {
+  if (!version_.HasIetfQuicFrames()) {
+    // Path validator is only supported in IETF QUIC.
+    return;
+  }
+  scoped_mock_network_change_notifier_ =
+      std::make_unique<ScopedMockNetworkChangeNotifier>();
+  MockNetworkChangeNotifier* mock_ncn =
+      scoped_mock_network_change_notifier_->mock_network_change_notifier();
+  mock_ncn->ForceNetworkHandlesSupported();
+  mock_ncn->SetConnectedNetworksList({kDefaultNetworkForTests});
+  // Enable migration on network change.
+  quic_params_->migrate_sessions_on_network_change_v2 = true;
+  quic_params_->allow_port_migration = true;
+  quic_params_->migrate_idle_sessions = true;
+  SetIetfConnectionMigrationFlagsAndConnectionOptions();
+  socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
+  Initialize();
+
+  scoped_mock_network_change_notifier_->mock_network_change_notifier()
+      ->NotifyNetworkMadeDefault(kDefaultNetworkForTests);
+
+  ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
+  crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+  crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+
+  // Using a testing task runner so that we can control time.
+  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
+  QuicStreamFactoryPeer::SetTaskRunner(factory_.get(), task_runner.get());
+
+  int packet_number = 1;
+  MockQuicData quic_data1(version_);
+  quic_data1.AddWrite(SYNCHRONOUS,
+                      ConstructInitialSettingsPacket(packet_number++));
+  quic_data1.AddWrite(
+      SYNCHRONOUS,
+      ConstructGetRequestPacket(packet_number++,
+                                GetNthClientInitiatedBidirectionalStreamId(0),
+                                true, true));
+  quic_data1.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
+  // The client session will receive the response first and closes its only
+  // stream.
+  quic_data1.AddRead(
+      ASYNC, ConstructOkResponsePacket(
+                 1, GetNthClientInitiatedBidirectionalStreamId(0), false,
+                 /*fin = */ true));
+  quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Pause
+  quic_data1.AddSocketDataToFactory(socket_factory_.get());
+
+  // Set up the second socket data provider that is used after migration.
+  // The response to the earlier request is read on the new socket.
+  MockQuicData quic_data2(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  client_maker_.set_connection_id(cid_on_new_path);
+  // Connectivity probe to be sent on the new path.
+  quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
+                                       packet_number++, true));
+  quic_data2.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
+  // Connectivity probe to receive from the server.
+  quic_data2.AddRead(ASYNC,
+                     server_maker_.MakeConnectivityProbingPacket(2, false));
+  quic_data2.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
+  // Ping packet to send after migration is completed.
+  quic_data2.AddWrite(ASYNC, client_maker_.MakeAckAndPingPacket(
+                                 packet_number++,
+                                 /*include_version=*/false, 2, 1));
+
+  quic_data2.AddWrite(
+      SYNCHRONOUS,
+      client_maker_.MakeRetireConnectionIdPacket(
+          packet_number++, /*include_version=*/false, /*sequence_number=*/0u));
+  quic_data2.AddSocketDataToFactory(socket_factory_.get());
+
+  // Create request and QuicHttpStream.
+  QuicStreamRequest request(factory_.get());
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(
+                scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
+                SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                true /* use_dns_aliases */,
+                /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_THAT(callback_.WaitForResult(), IsOk());
+  std::unique_ptr<HttpStream> stream = CreateStream(&request);
+  EXPECT_TRUE(stream.get());
+
+  // Cause QUIC stream to be created.
+  HttpRequestInfo request_info;
+  request_info.method = "GET";
+  request_info.url = url_;
+  request_info.traffic_annotation =
+      MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
+  EXPECT_EQ(OK, stream->InitializeStream(&request_info, true, DEFAULT_PRIORITY,
+                                         net_log_, CompletionOnceCallback()));
+
+  // Ensure that session is alive and active.
+  QuicChromiumClientSession* session = GetActiveSession(scheme_host_port_);
+  EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+  EXPECT_TRUE(HasActiveSession(scheme_host_port_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
+
+  // Send GET request on stream.
+  HttpResponseInfo response;
+  HttpRequestHeaders request_headers;
+  EXPECT_EQ(OK, stream->SendRequest(request_headers, &response,
+                                    callback_.callback()));
+  // Disable connection migration on the request streams.
+  // This should have no effect for port migration.
+  QuicChromiumClientStream* chrome_stream =
+      static_cast<QuicChromiumClientStream*>(
+          quic::test::QuicSessionPeer::GetStream(
+              session, GetNthClientInitiatedBidirectionalStreamId(0)));
+  EXPECT_TRUE(chrome_stream);
+  chrome_stream->DisableConnectionMigrationToCellularNetwork();
+
+  EXPECT_EQ(0u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
+
+  // Manually initialize the connection's self address. In real life, the
+  // initialization will be done during crypto handshake.
+  IPEndPoint ip;
+  session->GetDefaultSocket()->GetLocalAddress(&ip);
+  quic::test::QuicConnectionPeer::SetSelfAddress(session->connection(),
+                                                 ToQuicSocketAddress(ip));
+
+  // Cause the connection to report path degrading to the session.
+  // Session will start to probe a different port.
+  session->connection()->OnPathDegradingDetected();
+  EXPECT_EQ(1u, session->GetNumActiveStreams());
+  EXPECT_EQ(ERR_IO_PENDING, stream->ReadResponseHeaders(callback_.callback()));
+  // A response will be received on the current path and closes the request
+  // stream.
+  quic_data1.Resume();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_THAT(callback_.WaitForResult(), IsOk());
+  EXPECT_EQ(200, response.headers->response_code());
+  EXPECT_EQ(0u, session->GetNumActiveStreams());
+
+  EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
+
+  // The retry mechanism is internal to path validator.
+  EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
+
+  // The connection should still be alive, and not marked as going away.
+  EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+  EXPECT_TRUE(HasActiveSession(scheme_host_port_));
+
+  // Resume quic data and a connectivity probe response will be read on the new
+  // socket.
+  quic_data2.Resume();
+
+  EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+  EXPECT_TRUE(HasActiveSession(scheme_host_port_));
+  // Successful port migration causes the path no longer degrading on the same
+  // network.
+  EXPECT_EQ(0u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
+
+  // There should be pending tasks, the nearest one will complete
+  // migration to the new port.
+  task_runner->RunUntilIdle();
+
+  // Fire any outstanding quic alarms.
+  base::RunLoop().RunUntilIdle();
+
+  // Now there may be one pending task to send connectivity probe that has been
+  // cancelled due to successful migration.
+  task_runner->FastForwardUntilNoTasksRemain();
+
+  // Verify that the session is still alive.
+  EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+  EXPECT_TRUE(HasActiveSession(scheme_host_port_));
+
+  EXPECT_TRUE(quic_data1.AllReadDataConsumed());
+  EXPECT_TRUE(quic_data1.AllWriteDataConsumed());
+  EXPECT_TRUE(quic_data2.AllReadDataConsumed());
+  EXPECT_TRUE(quic_data2.AllWriteDataConsumed());
+}
+
 // This test verifies that the session marks itself GOAWAY on path degrading
 // and it does not receive any new request
 TEST_P(QuicStreamFactoryTest, GoawayOnPathDegrading) {
diff --git a/printing/printing_features.cc b/printing/printing_features.cc
index e5a9011..b14fac7 100644
--- a/printing/printing_features.cc
+++ b/printing/printing_features.cc
@@ -32,6 +32,10 @@
 const base::Feature kPrintWithReducedRasterization{
     "PrintWithReducedRasterization", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Read printer capabilities with XPS when use XPS for printing.
+const base::Feature kReadPrinterCapabilitiesWithXps{
+    "ReadPrinterCapabilitiesWithXps", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Use XPS for printing instead of GDI.
 const base::Feature kUseXpsForPrinting{"UseXpsForPrinting",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/printing/printing_features.h b/printing/printing_features.h
index 9003dab..efe6b46 100644
--- a/printing/printing_features.h
+++ b/printing/printing_features.h
@@ -28,6 +28,8 @@
 COMPONENT_EXPORT(PRINTING_BASE)
 extern const base::Feature kPrintWithReducedRasterization;
 COMPONENT_EXPORT(PRINTING_BASE)
+extern const base::Feature kReadPrinterCapabilitiesWithXps;
+COMPONENT_EXPORT(PRINTING_BASE)
 extern const base::Feature kUseXpsForPrinting;
 COMPONENT_EXPORT(PRINTING_BASE)
 extern const base::Feature kUseXpsForPrintingFromPdf;
diff --git a/services/network/first_party_sets/first_party_sets_loader.cc b/services/network/first_party_sets/first_party_sets_loader.cc
index eeb86641..47d775a 100644
--- a/services/network/first_party_sets/first_party_sets_loader.cc
+++ b/services/network/first_party_sets/first_party_sets_loader.cc
@@ -4,6 +4,7 @@
 
 #include "services/network/first_party_sets/first_party_sets_loader.h"
 
+#include <set>
 #include <utility>
 #include <vector>
 
diff --git a/sql/error_delegate_util.cc b/sql/error_delegate_util.cc
index edd4681..e88deb61 100644
--- a/sql/error_delegate_util.cc
+++ b/sql/error_delegate_util.cc
@@ -201,9 +201,11 @@
       // the user if we run our recovery code or delete our databases.
       [[fallthrough]];
     case SQLITE_PROTOCOL:
-      // Timed out while attempting to grab a lock on the database. This should
-      // not be a problem for exclusive databases, which are strongly
-      // recommended for Chrome features.
+      // Gave up while attempting to grab a lock on a WAL database at the
+      // beginning of a transaction. In theory, this should not be a problem in
+      // Chrome, because we'll only allow enabling WAL on databases with
+      // exclusive locking. However, other software on the user's system may
+      // lock our databases in a way that triggers this error.
       [[fallthrough]];
     case SQLITE_SCHEMA:
       // The database schema was changed between the time when a prepared
diff --git a/storage/browser/file_system/file_system_context.cc b/storage/browser/file_system/file_system_context.cc
index 4b22d56..808295d 100644
--- a/storage/browser/file_system/file_system_context.cc
+++ b/storage/browser/file_system/file_system_context.cc
@@ -446,11 +446,6 @@
     OpenFileSystemMode mode,
     OpenFileSystemCallback callback,
     QuotaErrorOr<BucketInfo> result) {
-  if (!result.ok()) {
-    std::move(callback).Run(GURL(), std::string(),
-                            base::File::FILE_ERROR_FAILED);
-    return;
-  }
   ResolveURLOnOpenFileSystem(storage_key, type, mode, std::move(callback));
 }
 
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 7db6a9b9..a1688c6 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -35214,7 +35214,9 @@
   },
   "android-pie-arm64-coverage-experimental-rel": {
     "additional_compile_targets": [
-      "validate_expectations"
+      "monochrome_static_initializers",
+      "validate_expectations",
+      "weblayer_shell"
     ],
     "gtest_tests": [
       {
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 8c5f98a..8f8a639 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -2185,24 +2185,6 @@
       },
     },
   },
-  'monochrome_public_bundle_fake_modules_smoke_test' : {
-    'remove_from': [
-      'android-pie-arm64-coverage-experimental-rel', # TODO(crbug.com/1190999):
-                                                     # GLIBC_2.28 Not found.
-    ],
-  },
-  'monochrome_public_bundle_smoke_test' : {
-    'remove_from': [
-      'android-pie-arm64-coverage-experimental-rel', # TODO(crbug.com/1190999):
-                                                     # GLIBC_2.28 Not found.
-    ],
-  },
-  'monochrome_public_smoke_test' : {
-    'remove_from': [
-      'android-pie-arm64-coverage-experimental-rel', # TODO(crbug.com/1190999):
-                                                     # GLIBC_2.28 Not found.
-    ],
-  },
   'monochrome_public_test_ar_apk': {
     'modifications': {
       'Nougat Phone Tester': {
@@ -3617,8 +3599,6 @@
   'webview_cts_tests': {
     'remove_from': [
       'android-11-x86-rel', # crbug.com/1165280
-      'android-pie-arm64-coverage-experimental-rel', # TODO(crbug.com/1190999):
-                                                     # GLIBC_2.28 Not found.
     ],
     'modifications': {
       'android-pie-arm64-rel': {
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index a62139c3..7f4cbdd 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -966,7 +966,9 @@
           'walleye',
         ],
         'additional_compile_targets': [
+          'monochrome_static_initializers',
           'validate_expectations',
+          'weblayer_shell',
         ],
         'test_suites': {
           'gtest_tests': 'chromium_android_gtests',
diff --git a/testing/scripts/run_finch_smoke_tests_android.py b/testing/scripts/run_finch_smoke_tests_android.py
index cf5032d..53255691 100755
--- a/testing/scripts/run_finch_smoke_tests_android.py
+++ b/testing/scripts/run_finch_smoke_tests_android.py
@@ -119,7 +119,6 @@
       'dom/collections/HTMLCollection-delete.html',
       'dom/collections/HTMLCollection-supported-property-names.html',
       'dom/collections/HTMLCollection-supported-property-indices.html',
-      'svg/pservers/reftests/radialgradient-basic-002.svg',
     ]
 
   @property
@@ -182,6 +181,7 @@
     rest_args.extend(['run',
       self.wpt_product_name(),
       '--tests=' + wpt_common.TESTS_ROOT_DIR,
+      '--test-type=' + 'testharness',
       '--device-serial',
       self._device.serial,
       '--webdriver-binary',
diff --git a/third_party/blink/public/mojom/conversions/attribution_data_host.mojom b/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
index 4538221..206c9f2 100644
--- a/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
+++ b/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
@@ -26,9 +26,9 @@
 };
 
 // Defines a list of named histogram contributions.
-struct AttributionAggregatableSources {
+struct AttributionAggregatableSource {
   // Map of key id to the key.
-  map<string, AttributionAggregatableKey> sources;
+  map<string, AttributionAggregatableKey> keys;
 };
 
 // Struct containing the trigger-side aggregatable data.
@@ -72,7 +72,7 @@
   AttributionFilterData filter_data;
 
   // Contains source-side aggregatable key pieces.
-  AttributionAggregatableSources aggregatable_sources;
+  AttributionAggregatableSource aggregatable_source;
 };
 
 // Deduplication key set by a reporting origin which prevents duplicate triggers
diff --git a/third_party/blink/renderer/core/dom/events/event_target.cc b/third_party/blink/renderer/core/dom/events/event_target.cc
index ab104b0..24d968b 100644
--- a/third_party/blink/renderer/core/dom/events/event_target.cc
+++ b/third_party/blink/renderer/core/dom/events/event_target.cc
@@ -454,6 +454,20 @@
   if (options->hasSignal() && options->signal()->aborted())
     return false;
 
+  // Unload/Beforeunload handlers are not allowed in fenced frames.
+  if (event_type == event_type_names::kUnload ||
+      event_type == event_type_names::kBeforeunload) {
+    if (const LocalDOMWindow* window = ExecutingWindow()) {
+      if (const LocalFrame* frame = window->GetFrame()) {
+        if (frame->IsInFencedFrameTree()) {
+          window->PrintErrorMessage(
+              "unload/beforeunload handlers are prohibited in fenced frames.");
+          return false;
+        }
+      }
+    }
+  }
+
   if (event_type == event_type_names::kTouchcancel ||
       event_type == event_type_names::kTouchend ||
       event_type == event_type_names::kTouchmove ||
diff --git a/third_party/blink/renderer/core/frame/attribution_response_parsing.cc b/third_party/blink/renderer/core/frame/attribution_response_parsing.cc
index dcd66bb..2bb9445c 100644
--- a/third_party/blink/renderer/core/frame/attribution_response_parsing.cc
+++ b/third_party/blink/renderer/core/frame/attribution_response_parsing.cc
@@ -119,9 +119,9 @@
 
 }  // namespace
 
-bool ParseAttributionAggregatableSources(
+bool ParseAttributionAggregatableSource(
     const AtomicString& json_string,
-    mojom::blink::AttributionAggregatableSources& sources) {
+    mojom::blink::AttributionAggregatableSource& source) {
   std::unique_ptr<JSONValue> json = ParseJSON(json_string);
   if (!json)
     return false;
@@ -134,7 +134,7 @@
 
   const wtf_size_t num_keys = array->size();
 
-  sources.sources.ReserveCapacityForSize(num_keys);
+  source.keys.ReserveCapacityForSize(num_keys);
 
   for (wtf_size_t i = 0; i < num_keys; ++i) {
     JSONValue* value = array->at(i);
@@ -156,7 +156,7 @@
     if (!key)
       return false;
 
-    sources.sources.insert(std::move(key_id), std::move(key));
+    source.keys.insert(std::move(key_id), std::move(key));
   }
 
   return true;
diff --git a/third_party/blink/renderer/core/frame/attribution_response_parsing.h b/third_party/blink/renderer/core/frame/attribution_response_parsing.h
index 4d43f592..afa6669 100644
--- a/third_party/blink/renderer/core/frame/attribution_response_parsing.h
+++ b/third_party/blink/renderer/core/frame/attribution_response_parsing.h
@@ -33,9 +33,9 @@
 // }]
 //
 // Returns whether parsing was successful.
-CORE_EXPORT bool ParseAttributionAggregatableSources(
+CORE_EXPORT bool ParseAttributionAggregatableSource(
     const AtomicString& json_string,
-    mojom::blink::AttributionAggregatableSources& sources);
+    mojom::blink::AttributionAggregatableSource& source);
 
 // Parses a debug key, which is a 64-bit unsigned integer encoded as a base-10
 // string. Returns `nullptr` on failure.
diff --git a/third_party/blink/renderer/core/frame/attribution_response_parsing_test.cc b/third_party/blink/renderer/core/frame/attribution_response_parsing_test.cc
index 8c656cf6..b85456b 100644
--- a/third_party/blink/renderer/core/frame/attribution_response_parsing_test.cc
+++ b/third_party/blink/renderer/core/frame/attribution_response_parsing_test.cc
@@ -19,24 +19,24 @@
 
 namespace {
 
-class AggregatableSourcesBuilder {
+class AggregatableSourceBuilder {
  public:
-  AggregatableSourcesBuilder() = default;
-  ~AggregatableSourcesBuilder() = default;
+  AggregatableSourceBuilder() = default;
+  ~AggregatableSourceBuilder() = default;
 
-  AggregatableSourcesBuilder& AddKey(
+  AggregatableSourceBuilder& AddKey(
       String key_id,
       mojom::blink::AttributionAggregatableKeyPtr key) {
-    sources_.sources.insert(std::move(key_id), std::move(key));
+    source_.keys.insert(std::move(key_id), std::move(key));
     return *this;
   }
 
-  mojom::blink::AttributionAggregatableSourcesPtr Build() const {
-    return sources_.Clone();
+  mojom::blink::AttributionAggregatableSourcePtr Build() const {
+    return source_.Clone();
   }
 
  private:
-  mojom::blink::AttributionAggregatableSources sources_;
+  mojom::blink::AttributionAggregatableSource source_;
 };
 
 class AttributionFilterDataBuilder {
@@ -80,25 +80,25 @@
 
 }  // namespace
 
-TEST(AttributionResponseParsingTest, ParseAttributionAggregatableSources) {
+TEST(AttributionResponseParsingTest, ParseAttributionAggregatableSource) {
   const struct {
     String description;
     AtomicString header;
     bool valid;
-    mojom::blink::AttributionAggregatableSourcesPtr sources;
+    mojom::blink::AttributionAggregatableSourcePtr source;
   } kTestCases[] = {
       {"Empty header", "", false,
-       mojom::blink::AttributionAggregatableSources::New()},
+       mojom::blink::AttributionAggregatableSource::New()},
       {"Invalid JSON", "{", false,
-       mojom::blink::AttributionAggregatableSources::New()},
+       mojom::blink::AttributionAggregatableSource::New()},
       {"Missing id field", R"([{"key_piece":"0x159"}])", false,
-       mojom::blink::AttributionAggregatableSources::New()},
+       mojom::blink::AttributionAggregatableSource::New()},
       {"Missing key_piece field", R"([{"id":"key"}])", false,
-       mojom::blink::AttributionAggregatableSources::New()},
+       mojom::blink::AttributionAggregatableSource::New()},
       {"Invalid key", R"([{"id":"key","key_piece":"0xG59"}])", false,
-       mojom::blink::AttributionAggregatableSources::New()},
+       mojom::blink::AttributionAggregatableSource::New()},
       {"One valid key", R"([{"id":"key","key_piece":"0x159"}])", true,
-       AggregatableSourcesBuilder()
+       AggregatableSourceBuilder()
            .AddKey(/*key_id=*/"key",
                    mojom::blink::AttributionAggregatableKey::New(
                        /*high_bits=*/0, /*low_bits=*/345))
@@ -107,7 +107,7 @@
        AtomicString(R"([{"id":"key1","key_piece":"0x159"},)") +
            R"({"id":"key2","key_piece":"0x50000000000000159"}])",
        true,
-       AggregatableSourcesBuilder()
+       AggregatableSourceBuilder()
            .AddKey(/*key_id=*/"key1",
                    mojom::blink::AttributionAggregatableKey::New(
                        /*high_bits=*/0, /*low_bits=*/345))
@@ -118,22 +118,21 @@
       {"Second key invalid",
        AtomicString(R"([{"id":"key1","key_piece":"0x159"},)") +
            R"({"id":"key2","key_piece":""}])",
-       false, mojom::blink::AttributionAggregatableSources::New()},
+       false, mojom::blink::AttributionAggregatableSource::New()},
   };
 
   for (const auto& test_case : kTestCases) {
-    auto sources = mojom::blink::AttributionAggregatableSources::New();
-    bool valid =
-        ParseAttributionAggregatableSources(test_case.header, *sources);
+    auto source = mojom::blink::AttributionAggregatableSource::New();
+    bool valid = ParseAttributionAggregatableSource(test_case.header, *source);
     EXPECT_EQ(test_case.valid, valid) << test_case.description;
     if (test_case.valid)
-      EXPECT_EQ(test_case.sources, sources) << test_case.description;
+      EXPECT_EQ(test_case.source, source) << test_case.description;
   }
 }
 
 TEST(AttributionResponseParsingTest,
-     ParseAttributionAggregatableSources_CheckSize) {
-  struct AttributionAggregatableSourcesSizeTestCase {
+     ParseAttributionAggregatableSource_CheckSize) {
+  struct AttributionAggregatableSourceSizeTestCase {
     String description;
     bool valid;
     wtf_size_t key_count;
@@ -154,8 +153,8 @@
       return "[" + builder.ToAtomicString() + "]";
     }
 
-    mojom::blink::AttributionAggregatableSourcesPtr GetSources() const {
-      AggregatableSourcesBuilder builder;
+    mojom::blink::AttributionAggregatableSourcePtr GetSource() const {
+      AggregatableSourceBuilder builder;
       if (!valid)
         return builder.Build();
 
@@ -177,7 +176,7 @@
     }
   };
 
-  const AttributionAggregatableSourcesSizeTestCase kTestCases[] = {
+  const AttributionAggregatableSourceSizeTestCase kTestCases[] = {
       {"empty", true, 0, 0},
       {"max_keys", true,
        blink::kMaxAttributionAggregatableKeysPerSourceOrTrigger, 1},
@@ -190,12 +189,12 @@
   };
 
   for (const auto& test_case : kTestCases) {
-    auto sources = mojom::blink::AttributionAggregatableSources::New();
+    auto source = mojom::blink::AttributionAggregatableSource::New();
     bool valid =
-        ParseAttributionAggregatableSources(test_case.GetHeader(), *sources);
+        ParseAttributionAggregatableSource(test_case.GetHeader(), *source);
     EXPECT_EQ(test_case.valid, valid) << test_case.description;
     if (test_case.valid)
-      EXPECT_EQ(test_case.GetSources(), sources) << test_case.description;
+      EXPECT_EQ(test_case.GetSource(), source) << test_case.description;
   }
 }
 
@@ -514,7 +513,7 @@
               /*priority=*/0,
               /*debug_key=*/nullptr,
               /*filter_data=*/AttributionFilterDataBuilder().Build(),
-              /*aggregatable_sources=*/AggregatableSourcesBuilder().Build()),
+              /*aggregatable_source=*/AggregatableSourceBuilder().Build()),
       },
       {
           "valid_filter_data",
@@ -535,7 +534,7 @@
               AttributionFilterDataBuilder()
                   .AddFilter("SOURCE_TYPE", {})
                   .Build(),
-              /*aggregatable_sources=*/AggregatableSourcesBuilder().Build()),
+              /*aggregatable_source=*/AggregatableSourceBuilder().Build()),
       },
       {
           "invalid_source_type_key_in_filter_data",
diff --git a/third_party/blink/renderer/core/frame/attribution_src_loader.cc b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
index ce4aec7..16873cc 100644
--- a/third_party/blink/renderer/core/frame/attribution_src_loader.cc
+++ b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
@@ -265,14 +265,14 @@
     return;
   }
 
-  source_data->aggregatable_sources =
-      mojom::blink::AttributionAggregatableSources::New();
+  source_data->aggregatable_source =
+      mojom::blink::AttributionAggregatableSource::New();
 
-  const AtomicString& aggregatable_sources_json = response.HttpHeaderField(
+  const AtomicString& aggregatable_source_json = response.HttpHeaderField(
       http_names::kAttributionReportingRegisterAggregatableSource);
-  if (!aggregatable_sources_json.IsNull() &&
-      !attribution_response_parsing::ParseAttributionAggregatableSources(
-          aggregatable_sources_json, *source_data->aggregatable_sources)) {
+  if (!aggregatable_source_json.IsNull() &&
+      !attribution_response_parsing::ParseAttributionAggregatableSource(
+          aggregatable_source_json, *source_data->aggregatable_source)) {
     return;
   }
 
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_item_iterator.cc b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_item_iterator.cc
index 100035d..3971fd686 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_item_iterator.cc
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_item_iterator.cc
@@ -128,10 +128,7 @@
       break;
 
     flex_line_idx_++;
-    if (flex_line_idx_ < next_item_idx_for_line_.size())
-      flex_item_idx_ = next_item_idx_for_line_[flex_line_idx_];
-    else
-      flex_item_idx_ = 0;
+    AdjustItemIndexForNewLine();
   }
 
   // We handle break tokens for all columns before moving to the unprocessed
@@ -152,8 +149,15 @@
   if (flex_item_idx_ == 0)
     return;
   flex_line_idx_++;
-  flex_item_idx_ = 0;
+  AdjustItemIndexForNewLine();
   next_unstarted_item_ = FindNextItem();
 }
 
+void NGFlexItemIterator::AdjustItemIndexForNewLine() {
+  if (flex_line_idx_ < next_item_idx_for_line_.size())
+    flex_item_idx_ = next_item_idx_for_line_[flex_line_idx_];
+  else
+    flex_item_idx_ = 0;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_item_iterator.h b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_item_iterator.h
index 528e96e..92d55fa 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_item_iterator.h
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_item_iterator.h
@@ -48,6 +48,7 @@
 
  private:
   NGFlexItem* FindNextItem(const NGBlockBreakToken* item_break_token = nullptr);
+  void AdjustItemIndexForNewLine();
 
   NGFlexItem* next_unstarted_item_ = nullptr;
   const HeapVector<NGFlexLine>& flex_lines_;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
index e3daafa..043508f 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
@@ -417,8 +417,16 @@
     DCHECK_EQ(result->Status(), NGLayoutResult::kSuccess);
     LogicalOffset offset(borders_.inline_start, intrinsic_block_size_);
     container_builder_.AddResult(*result, offset);
+
+    const auto& fragment =
+        To<NGPhysicalBoxFragment>(result->PhysicalFragment());
+    if (auto baseline = fragment.Baseline())
+      container_builder_.SetBaseline(offset.block_offset + *baseline);
+    if (auto last_baseline = fragment.LastBaseline())
+      container_builder_.SetLastBaseline(offset.block_offset + *last_baseline);
+
     intrinsic_block_size_ +=
-        NGFragment(writing_direction_, result->PhysicalFragment()).BlockSize();
+        NGFragment(writing_direction_, fragment).BlockSize();
     container_builder_.SetHasSeenAllChildren();
   } else if (break_status == NGBreakStatus::kBrokeBefore) {
     ConsumeRemainingFragmentainerSpace();
@@ -533,6 +541,7 @@
   builder.SetPercentageResolutionSize(
       ConstraintSpace().PercentageResolutionSize());
   builder.SetIsFixedBlockSize(padding_box_size.block_size != kIndefiniteSize);
+  builder.SetBaselineAlgorithmType(ConstraintSpace().BaselineAlgorithmType());
 
   if (ConstraintSpace().HasBlockFragmentation()) {
     SetupSpaceBuilderForFragmentation(
diff --git a/third_party/blink/renderer/modules/credentialmanager/federated_credential.cc b/third_party/blink/renderer/modules/credentialmanager/federated_credential.cc
index 499b750f..81a4dc4 100644
--- a/third_party/blink/renderer/modules/credentialmanager/federated_credential.cc
+++ b/third_party/blink/renderer/modules/credentialmanager/federated_credential.cc
@@ -337,6 +337,7 @@
 }
 
 ScriptPromise FederatedCredential::revoke(ScriptState* script_state,
+                                          const String& hint,
                                           ExceptionState& exception_state) {
   ExecutionContext* context = ExecutionContext::From(script_state);
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
@@ -353,10 +354,9 @@
     return promise;
   }
 
-  if (id().IsEmpty()) {
+  if (hint.IsEmpty()) {
     resolver->Reject(MakeGarbageCollected<DOMException>(
-        DOMExceptionCode::kInvalidStateError,
-        "No account hint was provided to navigator.credentials.get"));
+        DOMExceptionCode::kInvalidStateError, "hint cannot be empty"));
     return promise;
   }
 
@@ -371,7 +371,7 @@
   if (!MaybeRejectDueToCSP(policy, resolver, provider_url_))
     return promise;
 
-  auth_request->Revoke(provider_url_, client_id_, id(),
+  auth_request->Revoke(provider_url_, client_id_, hint,
                        WTF::Bind(&OnRevoke, WrapPersistent(resolver)));
   return promise;
 }
diff --git a/third_party/blink/renderer/modules/credentialmanager/federated_credential.h b/third_party/blink/renderer/modules/credentialmanager/federated_credential.h
index dbaee1f3..8fe4aa2 100644
--- a/third_party/blink/renderer/modules/credentialmanager/federated_credential.h
+++ b/third_party/blink/renderer/modules/credentialmanager/federated_credential.h
@@ -73,7 +73,7 @@
 
   ScriptPromise logout(ScriptState* script_state);
 
-  ScriptPromise revoke(ScriptState*, ExceptionState&);
+  ScriptPromise revoke(ScriptState*, const String& hint, ExceptionState&);
 
   static ScriptPromise logoutRps(
       ScriptState*,
diff --git a/third_party/blink/renderer/modules/credentialmanager/federated_credential.idl b/third_party/blink/renderer/modules/credentialmanager/federated_credential.idl
index 67b0536..64bc68a 100644
--- a/third_party/blink/renderer/modules/credentialmanager/federated_credential.idl
+++ b/third_party/blink/renderer/modules/credentialmanager/federated_credential.idl
@@ -31,7 +31,7 @@
 
     // https://fedidcg.github.io/FedCM/#browser-api-revocation
     [RuntimeEnabled=FedCm, CallWith=ScriptState, RaisesException]
-    Promise<void> revoke();
+    Promise<void> revoke(USVString hint);
 
     // Allows IDPs to logout the user out of all of the logged in RPs.
     [RuntimeEnabled=FedCmIdpSignout, CallWith=ScriptState]
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc b/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
index 17c10797..7def338 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
@@ -48,6 +48,17 @@
     return true;
   }
 
+  // https://www.w3.org/TR/webcodecs-flac-codec-registration
+  // https://www.w3.org/TR/webcodecs-vorbis-codec-registration
+  bool description_required = false;
+  if (config.codec() == "flac" || config.codec() == "vorbis")
+    description_required = true;
+
+  if (description_required && !config.hasDescription()) {
+    out_console_message = "Description is required.";
+    return false;
+  }
+
   media::AudioCodec codec = media::AudioCodec::kUnknown;
   bool is_codec_ambiguous = true;
   const bool parse_succeeded = ParseAudioCodecString(
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc b/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc
index d7ef31c1..e3745d0 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc
+++ b/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc
@@ -7,6 +7,7 @@
 #include "base/check.h"
 #include "base/notreached.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_index_format.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_predefined_color_space.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
@@ -603,6 +604,14 @@
   }
 }
 
+WGPUPredefinedColorSpace AsDawnEnum(
+    const V8GPUPredefinedColorSpace& webgpu_enum) {
+  switch (webgpu_enum.AsEnum()) {
+    case V8GPUPredefinedColorSpace::Enum::kSRGB:
+      return WGPUPredefinedColorSpace_Srgb;
+  }
+}
+
 template <>
 WGPUPrimitiveTopology AsDawnEnum<WGPUPrimitiveTopology>(
     const WTF::String& webgpu_enum) {
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.h b/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.h
index 74e79ef7..3ef556d 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.h
+++ b/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.h
@@ -10,6 +10,7 @@
 namespace blink {
 
 class V8GPUIndexFormat;
+class V8GPUPredefinedColorSpace;
 
 // Convert WebGPU bitfield values to Dawn enums. These have the same value.
 template <typename DawnEnum>
@@ -21,6 +22,8 @@
 template <typename DawnEnum>
 DawnEnum AsDawnEnum(const WTF::String& webgpu_enum);
 WGPUIndexFormat AsDawnEnum(const V8GPUIndexFormat& webgpu_enum);
+WGPUPredefinedColorSpace AsDawnEnum(
+    const V8GPUPredefinedColorSpace& webgpu_enum);
 
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_queue.cc b/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
index 7fee352..531eecd 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
@@ -29,6 +29,7 @@
 #include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_texture.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h"
+#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -97,6 +98,17 @@
   }
 }
 
+PredefinedColorSpace GetPredefinedColorSpace(
+    WGPUPredefinedColorSpace color_space) {
+  switch (color_space) {
+    case WGPUPredefinedColorSpace_Srgb:
+      return PredefinedColorSpace::kSRGB;
+    default:
+      NOTREACHED();
+      return PredefinedColorSpace::kSRGB;
+  }
+}
+
 WGPUTextureFormat SkColorTypeToDawnColorFormat(SkColorType sk_color_type) {
   switch (sk_color_type) {
     case SkColorType::kRGBA_8888_SkColorType:
@@ -495,10 +507,13 @@
         "({width|height|depthOrArrayLayers} equals to 0).");
   }
 
+  WGPUPredefinedColorSpace dawn_predefined_color_space =
+      AsDawnEnum(destination->colorSpace());
+
   if (!UploadContentToTexture(
           static_bitmap_image.get(), origin_in_external_image, dawn_copy_size,
           dawn_destination, destination->premultipliedAlpha(),
-          copyImage->flipY())) {
+          dawn_predefined_color_space, copyImage->flipY())) {
     exception_state.ThrowTypeError(
         "Failed to copy content from external image.");
     return;
@@ -510,6 +525,7 @@
                                       const WGPUExtent3D& copy_size,
                                       const WGPUImageCopyTexture& destination,
                                       bool dst_premultiplied_alpha,
+                                      WGPUPredefinedColorSpace dst_color_space,
                                       bool flipY) {
   PaintImage paint_image = image->PaintImageForCurrentFrame();
   SkColorType source_color_type = paint_image.GetSkImageInfo().colorType();
@@ -534,17 +550,63 @@
                              ? WGPUAlphaMode_Premultiplied
                              : WGPUAlphaMode_Unpremultiplied;
 
+  // Set color space conversion params
+  sk_sp<SkColorSpace> sk_src_color_space =
+      paint_image.GetSkImageInfo().refColorSpace();
+
+  // If source input discard the color space info(e.g. ImageBitmap created with
+  // flag colorSpaceConversion: none). Treat the source color space as sRGB.
+  if (sk_src_color_space == nullptr) {
+    sk_src_color_space = SkColorSpace::MakeSRGB();
+  }
+  sk_sp<SkColorSpace> sk_dst_color_space = PredefinedColorSpaceToSkColorSpace(
+      GetPredefinedColorSpace(dst_color_space));
+  std::array<float, 7> gamma_decode_params;
+  std::array<float, 7> gamma_encode_params;
+  std::array<float, 9> conversion_matrix;
+  if (!SkColorSpace::Equals(sk_src_color_space.get(),
+                            sk_dst_color_space.get())) {
+    skcms_TransferFunction src_transfer_fn = {};
+    skcms_TransferFunction dst_transfer_fn = {};
+
+    // Row major matrix
+    skcms_Matrix3x3 transfer_matrix = {};
+
+    sk_src_color_space->transferFn(&src_transfer_fn);
+    sk_dst_color_space->invTransferFn(&dst_transfer_fn);
+    sk_src_color_space->gamutTransformTo(sk_dst_color_space.get(),
+                                         &transfer_matrix);
+    gamma_decode_params = {src_transfer_fn.g, src_transfer_fn.a,
+                           src_transfer_fn.b, src_transfer_fn.c,
+                           src_transfer_fn.d, src_transfer_fn.e,
+                           src_transfer_fn.f};
+    gamma_encode_params = {dst_transfer_fn.g, dst_transfer_fn.a,
+                           dst_transfer_fn.b, dst_transfer_fn.c,
+                           dst_transfer_fn.d, dst_transfer_fn.e,
+                           dst_transfer_fn.f};
+
+    // From row major matrix to col major matrix
+    conversion_matrix = {transfer_matrix.vals[0][0], transfer_matrix.vals[1][0],
+                         transfer_matrix.vals[2][0], transfer_matrix.vals[0][1],
+                         transfer_matrix.vals[1][1], transfer_matrix.vals[2][1],
+                         transfer_matrix.vals[0][2], transfer_matrix.vals[1][2],
+                         transfer_matrix.vals[2][2]};
+
+    options.needsColorSpaceConversion = true;
+    options.srcTransferFunctionParameters = gamma_decode_params.data();
+    options.dstTransferFunctionParameters = gamma_encode_params.data();
+    options.conversionMatrix = conversion_matrix.data();
+  }
+
   // Handling GPU resource.
   if (image->IsTextureBacked()) {
-    // TODO(crbug.com/1197369): Delegate color space conversion to
-    // copyTextureForBrowser().
     scoped_refptr<WebGPUMailboxTexture> mailbox_texture =
         WebGPUMailboxTexture::FromStaticBitmapImage(
             GetDawnControlClient(), device_->GetHandle(),
             static_cast<WGPUTextureUsage>(WGPUTextureUsage_CopyDst |
                                           WGPUTextureUsage_CopySrc |
                                           WGPUTextureUsage_TextureBinding),
-            image, PredefinedColorSpace::kSRGB, source_color_type);
+            image, source_color_type);
 
     if (mailbox_texture != nullptr) {
       WGPUImageCopyTexture src = {};
@@ -604,13 +666,6 @@
 
   auto dest_pixels = base::span<uint8_t>(static_cast<uint8_t*>(data), size);
 
-  // TODO(crbug.com/1197369): Delegate color space conversion to
-  // copyTextureForBrowser().
-  SkImageInfo info = SkImageInfo::Make(
-      image->width(), image->height(), source_color_type,
-      image->IsPremultiplied() ? kPremul_SkAlphaType : kUnpremul_SkAlphaType,
-      SkColorSpace::MakeSRGB());
-
   bool success = paint_image.readPixels(
       paint_image.GetSkImageInfo(), dest_pixels.data(), wgpu_bytes_per_row,
       source_image_rect.x(), source_image_rect.y());
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_queue.h b/third_party/blink/renderer/modules/webgpu/gpu_queue.h
index e516257..cfa1b38 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_queue.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_queue.h
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h"
 #include "third_party/blink/renderer/modules/webgpu/dawn_object.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
 
 namespace blink {
 
@@ -83,7 +84,8 @@
                               const WGPUExtent3D& copy_size,
                               const WGPUImageCopyTexture& destination,
                               bool dst_premultiplied_alpha,
-                              bool flipY = false);
+                              WGPUPredefinedColorSpace dst_color_space,
+                              bool flipY);
   void WriteBufferImpl(GPUBuffer* buffer,
                        uint64_t buffer_offset,
                        uint64_t data_byte_length,
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.cc
index 17bb0860..80405c5 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.cc
@@ -18,7 +18,6 @@
     WGPUDevice device,
     WGPUTextureUsage usage,
     scoped_refptr<StaticBitmapImage> image,
-    PredefinedColorSpace color_space,
     SkColorType color_type) {
   DCHECK(image->IsTextureBacked());
 
@@ -34,16 +33,11 @@
       context_provider_wrapper->ContextProvider()->IsContextLost())
     return nullptr;
 
-  // Keep the same config as source image.
-  SkImageInfo info = SkImageInfo::Make(
-      image->Size().width(), image->Size().height(), color_type,
-      image->IsPremultiplied() ? kPremul_SkAlphaType : kUnpremul_SkAlphaType,
-      PredefinedColorSpaceToSkColorSpace(color_space));
-
   // Get a recyclable resource for producing WebGPU-compatible shared images.
   std::unique_ptr<RecyclableCanvasResource> recyclable_canvas_resource =
-      dawn_control_client->GetOrCreateCanvasResource(info,
-                                                     image->IsOriginTopLeft());
+      dawn_control_client->GetOrCreateCanvasResource(
+          image->PaintImageForCurrentFrame().GetSkImageInfo(),
+          image->IsOriginTopLeft());
 
   // Fallback to unstable intermediate resource copy path.
   if (!recyclable_canvas_resource) {
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h b/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h
index fb16afd..522b98c 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h
@@ -30,7 +30,6 @@
       WGPUDevice device,
       WGPUTextureUsage usage,
       scoped_refptr<StaticBitmapImage> image,
-      PredefinedColorSpace color_space,
       SkColorType color_type);
 
   static scoped_refptr<WebGPUMailboxTexture> FromCanvasResource(
diff --git a/third_party/blink/renderer/platform/graphics/paint/cull_rect.cc b/third_party/blink/renderer/platform/graphics/paint/cull_rect.cc
index 62a77276..cd3d2b0 100644
--- a/third_party/blink/renderer/platform/graphics/paint/cull_rect.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/cull_rect.cc
@@ -137,7 +137,11 @@
 
   for (const auto* t = &destination.Transform(); t != &source.Transform();
        t = t->UnaliasedParent()) {
-    DCHECK(t);
+    // TODO(wangxianzhu): This should be DCHECK(t), but for now we need to
+    // work around crbug.com/1262837 etc. Also see the TODO in
+    // FragmentData::LocalBorderBoxProperties().
+    if (!t)
+      return false;
     if (t == &root.Transform()) {
       abnormal_hierarchy = true;
       break;
diff --git a/third_party/blink/tools/blinkpy/common/checkout/baseline_optimizer_unittest.py b/third_party/blink/tools/blinkpy/common/checkout/baseline_optimizer_unittest.py
index a43f125..b0da230f 100644
--- a/third_party/blink/tools/blinkpy/common/checkout/baseline_optimizer_unittest.py
+++ b/third_party/blink/tools/blinkpy/common/checkout/baseline_optimizer_unittest.py
@@ -63,10 +63,6 @@
                 'port_name': 'win-win10.20h2',
                 'specifiers': ['Win10.20h2', 'Release']
             },
-            'Fake Test Win11': {
-                'port_name': 'win-win11',
-                'specifiers': ['Win11', 'Release']
-            },
             'Fake Test Linux': {
                 'port_name': 'linux-trusty',
                 'specifiers': ['Trusty', 'Release']
@@ -97,7 +93,7 @@
         # tests need to be adjusted accordingly.
         self.assertEqual(sorted(self.host.port_factory.all_port_names()), [
             'linux-trusty', 'mac-mac10.12', 'mac-mac10.13', 'mac-mac10.14',
-            'mac-mac10.15', 'mac-mac11', 'win-win10.20h2', 'win-win11'
+            'mac-mac10.15', 'mac-mac11', 'win-win10.20h2'
         ])
 
     def _assert_optimization(self,
diff --git a/third_party/blink/tools/blinkpy/common/system/crash_logs_unittest.py b/third_party/blink/tools/blinkpy/common/system/crash_logs_unittest.py
index c84fbea..776235e 100644
--- a/third_party/blink/tools/blinkpy/common/system/crash_logs_unittest.py
+++ b/third_party/blink/tools/blinkpy/common/system/crash_logs_unittest.py
@@ -86,17 +86,17 @@
             make_mock_crash_report_darwin('DumpRenderTree', 28526)[200:]
         files = {
             '/Users/mock/Library/Logs/DiagnosticReports/DumpRenderTree_2011-06-13-150718_quadzen.crash':
-            older_mock_crash_report,
+            older_mock_crash_report.encode('utf8', 'replace'),
             '/Users/mock/Library/Logs/DiagnosticReports/DumpRenderTree_2011-06-13-150719_quadzen.crash':
-            mock_crash_report,
+            mock_crash_report.encode('utf8', 'replace'),
             '/Users/mock/Library/Logs/DiagnosticReports/DumpRenderTree_2011-06-13-150720_quadzen.crash':
-            newer_mock_crash_report,
+            newer_mock_crash_report.encode('utf8', 'replace'),
             '/Users/mock/Library/Logs/DiagnosticReports/DumpRenderTree_2011-06-13-150721_quadzen.crash':
             None,
             '/Users/mock/Library/Logs/DiagnosticReports/DumpRenderTree_2011-06-13-150722_quadzen.crash':
-            other_process_mock_crash_report,
+            other_process_mock_crash_report.encode('utf8', 'replace'),
             '/Users/mock/Library/Logs/DiagnosticReports/DumpRenderTree_2011-06-13-150723_quadzen.crash':
-            misformatted_mock_crash_report,
+            misformatted_mock_crash_report.encode('utf8', 'replace'),
         }
         filesystem = MockFileSystem(files)
         crash_logs = CrashLogs(MockSystemHost(filesystem=filesystem))
diff --git a/third_party/blink/tools/blinkpy/common/system/executive.py b/third_party/blink/tools/blinkpy/common/system/executive.py
index 67140fbd..ce05bfe 100644
--- a/third_party/blink/tools/blinkpy/common/system/executive.py
+++ b/third_party/blink/tools/blinkpy/common/system/executive.py
@@ -223,7 +223,8 @@
                                           stdout=self.PIPE,
                                           stderr=self.PIPE)
             stdout, _ = tasklist_process.communicate()
-            stdout_reader = csv.reader(stdout.splitlines())
+            stdout_reader = csv.reader(
+                stdout.decode('utf8', 'replace').splitlines())
             for line in stdout_reader:
                 processes.append([column for column in line])
         else:
diff --git a/third_party/blink/tools/blinkpy/common/system/executive_unittest.py b/third_party/blink/tools/blinkpy/common/system/executive_unittest.py
index 55c902b9..2bdf07cd 100644
--- a/third_party/blink/tools/blinkpy/common/system/executive_unittest.py
+++ b/third_party/blink/tools/blinkpy/common/system/executive_unittest.py
@@ -31,6 +31,7 @@
 import subprocess
 import sys
 import unittest
+import six
 
 # Since we execute this script directly as part of the unit tests, we need to
 # ensure that blink/tools is in sys.path for the next imports to work correctly.
@@ -118,7 +119,7 @@
         # Elsewhere, the 'mbcs' encoding is skipped, but then we must escape any
         # non-ascii unicode characters by encoding with 'unicode_escape'. This
         # results in an extra \ on non-Win platforms.
-        if sys.platform == 'win32':
+        if sys.platform == 'win32' and six.PY2:
             expected_result = u'echo 1 a\xac'
         else:
             expected_result = u'echo 1 a\\xac'
@@ -136,15 +137,18 @@
         to Executive.run* methods, and they will return unicode()
         objects by default unless decode_output=False
         """
+        # TODO(crbug/1306209): Needs more investigation. skipping for now.
+        if sys.platform == 'win32' and six.PY3:
+            return
         unicode_tor_input = u"WebKit \u2661 Tor Arne Vestb\u00F8!"
-        if sys.platform == 'win32':
+        if sys.platform == 'win32' and six.PY2:
             encoding = 'mbcs'
         else:
             encoding = 'utf-8'
         encoded_tor = unicode_tor_input.encode(encoding)
         # On Windows, we expect the unicode->mbcs->unicode roundtrip to be
         # lossy. On other platforms, we expect a lossless roundtrip.
-        if sys.platform == 'win32':
+        if sys.platform == 'win32' and six.PY2:
             unicode_tor_output = encoded_tor.decode(encoding)
         else:
             unicode_tor_output = unicode_tor_input
diff --git a/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py b/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py
index c38f483..0ce9caf 100644
--- a/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py
+++ b/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py
@@ -488,8 +488,6 @@
 class WritableTextFileObject(WritableBinaryFileObject):
     def __init__(self, fs, path, append=False):
         super(WritableTextFileObject, self).__init__(fs, path, append)
-        if path not in self.fs.files or not append:
-            self.setup_path(path)
 
     def write(self, string):
         WritableBinaryFileObject.write(self, string)
@@ -509,6 +507,13 @@
         self.closed = False
         self.data = data
         self.offset = 0
+        try:
+            # Maintain a text version if possible to
+            # support readline.
+            data_str = data.decode('utf8', 'replace')
+            self.text = StringIO(data_str)
+        except:
+            pass
 
     def __enter__(self):
         return self
@@ -526,6 +531,12 @@
         self.offset += num_bytes
         return self.data[start:self.offset]
 
+    def readline(self, length=None):
+        return self.text.readline(length).encode('utf8', 'replace')
+
+    def readlines(self):
+        return self.text.readlines().encode('utf8', 'replace')
+
     def seek(self, offset, whence=os.SEEK_SET):
         if whence == os.SEEK_SET:
             self.offset = offset
diff --git a/third_party/blink/tools/blinkpy/common/system/platform_info.py b/third_party/blink/tools/blinkpy/common/system/platform_info.py
index 1f35add..3ad768f 100644
--- a/third_party/blink/tools/blinkpy/common/system/platform_info.py
+++ b/third_party/blink/tools/blinkpy/common/system/platform_info.py
@@ -200,11 +200,12 @@
 
     def _determine_win_version(self, win_version_tuple):
         if win_version_tuple[:2] == (10, 0):
-            # For win11 platform.win32_ver() returns (10, 0, 22000)
-            if win_version_tuple[2] >= 22000:
-                return '11'
-            else:
+            # came across instances where build number was 15063.
+            # Treat those as 1909.
+            if win_version_tuple[2] > 19000:
                 return '10.20h2'
+            else:
+                return '10.1909'
         if win_version_tuple[:2] == (6, 3):
             return '8.1'
         if win_version_tuple[:2] == (6, 2):
diff --git a/third_party/blink/tools/blinkpy/common/system/platform_info_unittest.py b/third_party/blink/tools/blinkpy/common/system/platform_info_unittest.py
index 1d72885..04de9c3 100644
--- a/third_party/blink/tools/blinkpy/common/system/platform_info_unittest.py
+++ b/third_party/blink/tools/blinkpy/common/system/platform_info_unittest.py
@@ -201,7 +201,7 @@
             self.make_info(
                 fake_sys('win32', tuple([10, 0, 1234])),
                 fake_platform(win_version_string="10.0.1234")).os_version,
-            '10.20h2')
+            '10.1909')
         self.assertEqual(
             self.make_info(
                 fake_sys('win32', tuple([10, 0, 19042])),
@@ -209,11 +209,6 @@
             '10.20h2')
         self.assertEqual(
             self.make_info(
-                fake_sys('win32', tuple([10, 0, 23000])),
-                fake_platform(win_version_string="10.0.23000")).os_version,
-            '11')
-        self.assertEqual(
-            self.make_info(
                 fake_sys('win32', tuple([6, 3, 1234])),
                 fake_platform(win_version_string="6.3.1234")).os_version,
             '8.1')
diff --git a/third_party/blink/tools/blinkpy/w3c/import_notifier_unittest.py b/third_party/blink/tools/blinkpy/w3c/import_notifier_unittest.py
index 77ee480..bc3c1934 100644
--- a/third_party/blink/tools/blinkpy/w3c/import_notifier_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/import_notifier_unittest.py
@@ -25,7 +25,7 @@
         # Mock a virtual test suite at virtual/gpu/external/wpt/foo.
         self.host.filesystem = MockFileSystem({
             MOCK_WEB_TESTS + 'VirtualTestSuites':
-            '[{"prefix": "gpu", "bases": ["external/wpt/foo"], "args": ["--foo"]}]'
+            b'[{"prefix": "gpu", "bases": ["external/wpt/foo"], "args": ["--foo"]}]'
         })
         self.git = self.host.git()
         self.local_wpt = MockLocalWPT()
diff --git a/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_multipart.py b/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_multipart.py
index 59162de..a1f724e 100644
--- a/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_multipart.py
+++ b/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_multipart.py
@@ -67,7 +67,7 @@
 
         self._generate_breakpad_symbols_if_necessary()
         f, temp_name = self._host.filesystem.open_binary_tempfile('dmp')
-        f.write('\r\n'.join(dump['upload_file_minidump']))
+        f.write(b'\r\n'.join(dump['upload_file_minidump']))
         f.close()
 
         cmd = [
diff --git a/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_multipart_unittest.py b/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_multipart_unittest.py
index 1abf688..414ab4b 100644
--- a/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_multipart_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_multipart_unittest.py
@@ -80,9 +80,6 @@
         host.filesystem.maybe_make_directory(build_dir)
         host.filesystem.exists = lambda x: True
 
-        # The mock file object returned by open_binary_file_for_reading doesn't
-        # have readline(), however, the real File object does.
-        host.filesystem.open_binary_file_for_reading = host.filesystem.open_text_file_for_reading
         dump_reader = DumpReaderMultipart(host, build_dir)
         dump_reader._file_extension = lambda: 'dmp'
         dump_reader._binaries_to_symbolize = lambda: ['content_shell']
@@ -101,9 +98,6 @@
         host.filesystem.maybe_make_directory(build_dir)
         host.filesystem.exists = lambda x: True
 
-        # The mock file object returned by open_binary_file_for_reading doesn't
-        # have readline(), however, the real File object does.
-        host.filesystem.open_binary_file_for_reading = host.filesystem.open_text_file_for_reading
         dump_reader = DumpReaderMultipart(host, build_dir)
         dump_reader._file_extension = lambda: 'dmp'
         dump_reader._binaries_to_symbolize = lambda: ['content_shell']
diff --git a/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_win_unittest.py b/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_win_unittest.py
index c531930f..31c227d0 100644
--- a/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_win_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/breakpad/dump_reader_win_unittest.py
@@ -49,9 +49,9 @@
 
         dump_file = '/crash-dumps/dump.txt'
         expected_pid = '4711'
-        host.filesystem.write_text_file(
-            dump_file, 'channel:\npid:%s\nplat:Win32\nprod:content_shell\n' %
-            expected_pid)
+        with host.filesystem.open_text_file_for_writing(dump_file) as f:
+            f.write('channel:\npid:%s\nplat:Win32\nprod:content_shell\n' %
+                    expected_pid)
         build_dir = "/mock-checkout/out/Debug"
         host.filesystem.maybe_make_directory(build_dir)
         dump_reader = DumpReaderWin(host, build_dir)
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base.py b/third_party/blink/tools/blinkpy/web_tests/port/base.py
index b798b3f8..814be9f2 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base.py
@@ -149,7 +149,6 @@
         ('mac11-arm64', 'arm64'),
         ('win7', 'x86'),
         ('win10.20h2', 'x86'),
-        ('win11', 'x86'),
         ('trusty', 'x86_64'),
         ('fuchsia', 'x86_64'),
     )
@@ -159,7 +158,7 @@
             'mac10.12', 'mac10.13', 'mac10.14', 'mac10.15', 'mac11',
             'mac11-arm64'
         ],
-        'win': ['win7', 'win10.20h2', 'win11'],
+        'win': ['win7', 'win10.20h2'],
         'linux': ['trusty'],
         'fuchsia': ['fuchsia'],
     }
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/server_process_unittest.py b/third_party/blink/tools/blinkpy/web_tests/port/server_process_unittest.py
index 158343a..fe4c9e19 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/server_process_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/server_process_unittest.py
@@ -138,7 +138,7 @@
         port_obj.host.platform.os_name = 'win'
         server_process = FakeServerProcess(
             port_obj=port_obj, name="test", cmd=["test"])
-        server_process.write("should break")
+        server_process.write(b"should break")
         self.assertTrue(server_process.has_crashed())
         self.assertIsNotNone(server_process.pid())
         self.assertIsNone(server_process._proc)
@@ -147,7 +147,7 @@
         port_obj.host.platform.os_name = 'mac'
         server_process = FakeServerProcess(
             port_obj=port_obj, name="test", cmd=["test"])
-        server_process.write("should break")
+        server_process.write(b"should break")
         self.assertTrue(server_process.has_crashed())
         self.assertIsNone(server_process._proc)
         self.assertEqual(server_process.broken_pipes, [server_process.stdin])
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/win.py b/third_party/blink/tools/blinkpy/web_tests/port/win.py
index 1a0b5dd..1fcb45cd 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/win.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/win.py
@@ -51,11 +51,10 @@
 class WinPort(base.Port):
     port_name = 'win'
 
-    SUPPORTED_VERSIONS = ('win7', 'win10.20h2', 'win11')
+    SUPPORTED_VERSIONS = ('win7', 'win10.20h2')
 
     FALLBACK_PATHS = {}
-    FALLBACK_PATHS['win11'] = ['win']
-    FALLBACK_PATHS['win10.20h2'] = ['win10'] + FALLBACK_PATHS['win11']
+    FALLBACK_PATHS['win10.20h2'] = ['win']
     FALLBACK_PATHS['win7'] = ['win7'] + FALLBACK_PATHS['win10.20h2']
 
     BUILD_REQUIREMENTS_URL = 'https://chromium.googlesource.com/chromium/src/+/main/docs/windows_build_instructions.md'
@@ -68,11 +67,9 @@
             if host.platform.os_version in ('vista', '7sp0', '7sp1'):
                 version = 'win7'
             # Same for win8, win10.1909 we treat it as win10.
-            elif host.platform.os_version in ('8', '8.1', '10.1909',
-                                              '10.20h2'):
+            elif host.platform.os_version in ('8', '8.1', '10.1909', '10.20h2',
+                                              'future'):
                 version = 'win10.20h2'
-            elif host.platform.os_version in ('11', 'future'):
-                version = 'win11'
             else:
                 version = host.platform.os_version
             port_name = port_name + '-' + version
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/win_unittest.py b/third_party/blink/tools/blinkpy/web_tests/port/win_unittest.py
index 6def3ff..d2cb460 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/win_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/win_unittest.py
@@ -91,12 +91,9 @@
         self.assert_name('win-win7', '7sp0', 'win-win7')
         self.assert_name('win-win7', 'vista', 'win-win7')
 
-        self.assert_name(None, 'win11', 'win-win11')
-        self.assert_name('win', 'win11', 'win-win11')
-
-        self.assert_name(None, 'future', 'win-win11')
-        self.assert_name('win', 'future', 'win-win11')
-        self.assert_name('win-win11', 'future', 'win-win11')
+        self.assert_name(None, 'future', 'win-win10.20h2')
+        self.assert_name('win', 'future', 'win-win10.20h2')
+        self.assert_name('win-win10.20h2', 'future', 'win-win10.20h2')
 
         with self.assertRaises(AssertionError):
             self.assert_name(None, 'w2k', 'win-win7')
@@ -110,8 +107,8 @@
             self.assertTrue(port.baseline_search_path()[i].endswith(path))
 
     def test_baseline_path(self):
-        self.assert_baseline_paths('win-win7', 'win7', 'win10', '/win')
-        self.assert_baseline_paths('win-win10.20h2', 'win10', 'win')
+        self.assert_baseline_paths('win-win7', 'win7', '/win')
+        self.assert_baseline_paths('win-win10.20h2', 'win')
 
     def test_operating_system(self):
         self.assertEqual('win', self.make_port().operating_system())
diff --git a/third_party/blink/tools/blinkpy/web_tests/print_web_test_times_unittest.py b/third_party/blink/tools/blinkpy/web_tests/print_web_test_times_unittest.py
index 425b8b9b..f3e19b4 100644
--- a/third_party/blink/tools/blinkpy/web_tests/print_web_test_times_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/print_web_test_times_unittest.py
@@ -98,6 +98,6 @@
 
     def test_path_to_file(self):
         # Tests that we can use a custom file rather than the port's default.
-        self.check(['/tmp/times_ms.json'],
+        self.check([b'/tmp/times_ms.json'],
                    'foo/bar.html 1\n',
-                   files={'/tmp/times_ms.json': '{"foo":{"bar.html": 1}}'})
+                   files={b'/tmp/times_ms.json': b'{"foo":{"bar.html": 1}}'})
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index aef99bd5..3019fe6e 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -13,6 +13,7 @@
 # Tests that fail in legacy but pass in NG
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 virtual/prerender/external/wpt/speculation-rules/prerender/opt-out.html [ Crash ]
 crbug.com/626703 virtual/no-forced-frame-updates/external/wpt/html/dom/render-blocking/header-inserted-preload-link.tentative.html [ Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/getCurrentPosition_permission_allow.https.html [ Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html [ Timeout ]
@@ -857,6 +858,7 @@
 crbug.com/591099 fast/writing-mode/flipped-blocks-inline-map-local-to-container.html [ Failure ]
 
 ### external/wpt/html/rendering
+crbug.com/591099 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-baseline.html [ Failure ]
 crbug.com/591099 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-block-size.html [ Failure ]
 crbug.com/880062 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-content-before-legend.html [ Failure ]
 crbug.com/880062 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-generated-content.html [ Failure ]
@@ -1382,6 +1384,7 @@
 crbug.com/591099 external/wpt/appmanifest/start_url-member/start_url-member-pass-manual.html [ Failure ]
 
 ### external/wpt/css/css-flexbox/
+crbug.com/591099 external/wpt/css/css-flexbox/fieldset-baseline-alignment.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-flexbox/percentage-size-quirks-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-flexbox/abspos/position-absolute-014.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-flexbox/abspos/position-absolute-015.html [ Failure ]
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
index 1ff1887..9e9bb4d 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
+++ b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
@@ -47,6 +47,7 @@
 crbug.com/1209223 external/wpt/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-same-origin-domain.sub.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 virtual/prerender/external/wpt/speculation-rules/prerender/opt-out.html [ Crash ]
 crbug.com/626703 virtual/no-forced-frame-updates/external/wpt/html/dom/render-blocking/header-inserted-preload-link.tentative.html [ Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/getCurrentPosition_permission_allow.https.html [ Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html [ Timeout ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 0f2ecbba..672a0ef 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1317,7 +1317,6 @@
 crbug.com/1284251 external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-top-left-radius-001.html [ Failure ]
 crbug.com/1284270 [ Fuchsia ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
 crbug.com/1284270 [ Linux ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
-crbug.com/1284270 [ Mac10.12 ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
 crbug.com/1284270 [ Mac10.13 ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
 crbug.com/1284270 [ Mac10.14 ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
 crbug.com/1284270 [ Mac10.15 ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
@@ -1629,6 +1628,8 @@
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/flex-container-fragmentation-003.html [ Pass ]
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/flex-container-fragmentation-004.html [ Pass ]
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/flex-container-fragmentation-007.tentative.html [ Pass ]
+virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-015.html [ Pass ]
+virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-016.html [ Pass ]
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-007.html [ Pass ]
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-008.html [ Pass ]
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-010.html [ Pass ]
@@ -3371,6 +3372,13 @@
 crbug.com/626703 [ Win ] virtual/partitioned-cookies/http/tests/inspector-protocol/network/disabled-cache-navigation.js [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 [ Mac10.12 ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Timeout ]
+crbug.com/626703 [ Mac10.12 ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/maxframes.https.html [ Timeout ]
+crbug.com/626703 [ Mac10.12 ] virtual/portals/external/wpt/portals/history/history-manipulation-inside-portal-with-subframes.html [ Timeout ]
+crbug.com/626703 [ Mac10.12 ] virtual/portals/wpt_internal/portals/create-many-portals.html [ Timeout ]
+crbug.com/626703 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/opt-out.html [ Crash ]
+crbug.com/626703 [ Win10.20h2 ] virtual/prerender/external/wpt/speculation-rules/prerender/opt-out.html [ Crash ]
+crbug.com/626703 [ Mac11 ] virtual/prerender/external/wpt/speculation-rules/prerender/opt-out.html [ Crash Skip Timeout ]
 crbug.com/626703 external/wpt/css/css-grid/subgrid/auto-track-sizing-002.html [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/css/css-masking/clip-path/clip-path-shape-001.html [ Failure ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/css/css-masking/clip-path/clip-path-shape-001.html [ Failure ]
@@ -3410,7 +3418,6 @@
 crbug.com/626703 [ Win ] virtual/prerender/external/wpt/speculation-rules/prerender/media-autoplay.html [ Skip Timeout ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/html/browsers/browsing-the-web/back-forward-cache/eligibility/broadcast-channel.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/html/cross-origin-embedder-policy/cross-origin-isolated-permission.https.html [ Timeout ]
-crbug.com/626703 [ Mac11 ] external/wpt/scroll-to-text-fragment/force-load-at-top.html [ Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/idlharness.https.window.html [ Skip Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html [ Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute.https.sub.html [ Timeout ]
@@ -3447,7 +3454,6 @@
 crbug.com/626703 [ Mac10.12 ] virtual/portals/external/wpt/portals/portal-activate-data.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/mediacapture-streams/MediaDevices-enumerateDevices-per-origin-ids.sub.https.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] virtual/fenced-frame-mparch/external/wpt/html/cross-origin-embedder-policy/anonymous-iframe/web-lock.tentative.https.window.html [ Timeout ]
-crbug.com/626703 [ Mac11-arm64 ] external/wpt/scroll-to-text-fragment/force-load-at-top.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-content/quotes-030.html [ Failure ]
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/fetch/private-network-access/fetch.https.window.html?include=from-public [ Timeout ]
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/fetch/private-network-access/service-worker.https.window.html [ Timeout ]
@@ -4343,6 +4349,8 @@
 crbug.com/660611 external/wpt/css/css-break/flexbox/flex-container-fragmentation-003.html [ Failure ]
 crbug.com/660611 external/wpt/css/css-break/flexbox/flex-container-fragmentation-004.html [ Failure ]
 crbug.com/660611 external/wpt/css/css-break/flexbox/flex-container-fragmentation-007.tentative.html [ Failure ]
+crbug.com/660611 external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-015.html [ Failure ]
+crbug.com/660611 external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-016.html [ Failure ]
 crbug.com/660611 external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-007.html [ Failure ]
 crbug.com/660611 external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-008.html [ Failure ]
 crbug.com/660611 external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-010.html [ Failure ]
@@ -5402,7 +5410,6 @@
 
 # Sheriff 2019-05-01
 crbug.com/958347 [ Linux ] external/wpt/editing/run/removeformat.html [ Crash Pass ]
-crbug.com/958426 [ Mac10.13 ] virtual/text-antialias/line-break-ascii.html [ Pass Timeout ]
 
 # This test might need to be removed.
 crbug.com/954349 fast/forms/autofocus-in-sandbox-with-allow-scripts.html [ Timeout ]
@@ -7651,10 +7658,6 @@
 # Sheriff 2022-03-01
 crbug.com/1302043 [ Linux ] virtual/plz-dedicated-worker/external/wpt/resource-timing/object-not-found-after-cross-origin-redirect.html [ Pass Timeout ]
 
-# Temporarily disable to land related CL in DevTools
-crbug.com/1288883 http/tests/devtools/elements/styles-3/spectrum.js [ Skip ]
-crbug.com/1288883 http/tests/devtools/elements/styles-4/styles-invalid-color-values.js [ Skip ]
-
 # Sheriff 2022-03-03
 crbug.com/1302571 virtual/threaded/http/tests/devtools/isolated-code-cache/cross-origin-test.js [ Skip ]
 
@@ -7670,4 +7673,11 @@
 
 # Sheriff 2022-03-10
 crbug.com/1304956 storage/indexeddb/dont-wedge.html [ Failure Pass ]
-crbug.com/1305023 [ Mac10.12 ] virtual/partitioned-cookies-first-party-sets/http/tests/inspector-protocol/network/cross-origin-isolation/coep-load-error-reporting-worker.js [ Failure Pass ]
+
+# Sheriff 2022-03-15
+crbug.com/626703 external/wpt/scroll-to-text-fragment/force-load-at-top.html [ Pass Timeout ]
+crbug.com/1269534 fast/spatial-navigation/snav-zero-margin-content.html [ Failure Pass ]
+crbug.com/1295980 virtual/fenced-frame-mparch/wpt_internal/fenced_frame/user-activation.https.html [ Failure Pass Timeout ]
+crbug.com/1280537 virtual/first-party-sets/http/tests/inspector-protocol/network/blocked-cookie-same-site-lax.js [ Failure Pass Timeout ]
+crbug.com/1277877 virtual/partitioned-cookies-first-party-sets/http/tests/inspector-protocol/network/blocked-cookie-same-site-strict.js [ Failure Pass Timeout ]
+crbug.com/1298633 http/tests/inspector-protocol/network/cross-origin-isolation/coep-load-error-reporting-worker.js [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index 14ecd50..e82251c 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: f0d3621b1a7981b26229568c093b2a2f508eef90
+Version: e0cf318a60e583451b0cc045099749b5e2d8802e
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 f1cc6fd..3fe115b 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
@@ -376,6 +376,13 @@
        {}
       ]
      ],
+     "break-after-in-parallel-flow-crash.html": [
+      "b59563295028f487543d79524ea81543b8a78046",
+      [
+       null,
+       {}
+      ]
+     ],
      "break-before-with-no-fragmentation-crash.html": [
       "fb80ec45bceec093481fa54513c606c5952628b1",
       [
@@ -214086,6 +214093,86 @@
          ]
         ]
        },
+       "context-attributes": {
+        "clearRect_alpha_false.html": [
+         "a328fbf97a4f3e2d93bf120098b0a7d76b83ffc5",
+         [
+          null,
+          [
+           [
+            "/html/canvas/element/manual/context-attributes/clearRect_alpha_false-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "drawImage_alpha_false.html": [
+         "64f38bceb1e7880aec6ad5f797063f39c2cb5e9a",
+         [
+          null,
+          [
+           [
+            "/html/canvas/element/manual/context-attributes/drawImage_alpha_false-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "fillRect_alpha_false.html": [
+         "143756eb0c7aa45e7002cc10ad90caa32876f1f9",
+         [
+          null,
+          [
+           [
+            "/html/canvas/element/manual/context-attributes/fillRect_alpha_false-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "fill_alpha_false.html": [
+         "7872a7938039f7c576a47e5e06c1655aa68b5874",
+         [
+          null,
+          [
+           [
+            "/html/canvas/element/manual/context-attributes/fill_alpha_false-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "initial_color_alpha_false.html": [
+         "9e86e5ce2c55afbed445843a3f41c79d3247c041",
+         [
+          null,
+          [
+           [
+            "/html/canvas/element/manual/context-attributes/initial_color_alpha_false-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "reset_color_alpha_false.html": [
+         "191975e0f48e68a3cd9c91ee25a0f3aa4e10ea77",
+         [
+          null,
+          [
+           [
+            "/html/canvas/element/manual/context-attributes/reset_color_alpha_false-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ]
+       },
        "drawing-images-to-the-canvas": {
         "drawimage_canvas_self.html": [
          "83cf53583ce12716fec3bef5ce483efc4506bfbb",
@@ -231904,146 +231991,6 @@
      []
     ]
    },
-   "app-history": {
-    "META.yml": [
-     "de4f6c9a335bd4b7d5e99d14d37b904fe5d54182",
-     []
-    ],
-    "OWNERS": [
-     "cc6a94b332a04fdf400165265f41194abaf67d3e",
-     []
-    ],
-    "app-history-entry": {
-     "resources": {
-      "is_uuid.js": [
-       "3b855c01b075608e27766173a2783092493bbed6",
-       []
-      ],
-      "key-navigate-back-cross-document-helper.html": [
-       "f61f9a829820411a8323fe5b3f49cab4cd1d4e32",
-       []
-      ],
-      "no-referrer-meta.html": [
-       "bd5ec391cce892199b0d448b516f6dd087c99665",
-       []
-      ],
-      "no-referrer.html": [
-       "c8b7661f42212985888d1dfdf1d8729f89c4fc35",
-       []
-      ],
-      "no-referrer.html.headers": [
-       "7ffbf17d6be5a59e5b3636adc0eb14f3e2e528c2",
-       []
-      ],
-      "opaque-origin-page.html": [
-       "5e8a71981c5f6ce101e8c3f131373d77cf17717a",
-       []
-      ],
-      "post-entries-length-to-top.html": [
-       "348a8984fe10199b12839931d4a1299c5272dd08",
-       []
-      ],
-      "post-key-to-top.html": [
-       "023141f76a3a06363fd69dd46788679c61edde3c",
-       []
-      ]
-     }
-    },
-    "focus-reset": {
-     "resources": {
-      "helpers.mjs": [
-       "e3012a0b12e618615ed90ab77528be2e8de41974",
-       []
-      ]
-     }
-    },
-    "navigate": {
-     "reload-service-worker-fetch-event-expected.txt": [
-      "cf4b285328a985e8413570fcc80e16d4501b3d09",
-      []
-     ],
-     "resources": {
-      "fetch-event-test-worker.js": [
-       "98b7dd0fb58b4142f90eca341356eb0efbd09d2f",
-       []
-      ],
-      "page-with-base-url-common.html": [
-       "8d9fedcc2b40d2eaeff55727ffa476a6e7c0e479",
-       []
-      ],
-      "service-worker-page.html": [
-       "a10ff35dce7768c6e7b6f68647207a8fa35c2a6d",
-       []
-      ]
-     },
-     "return-value": {
-      "resources": {
-       "back-forward-opaque-origin-page.html": [
-        "6cdf0a8b9866bdbf4452bacb79eb6f04aafe2b62",
-        []
-       ],
-       "helpers.js": [
-        "bf711b18ade2c881fbfa40fc8e44ddf456fa37d2",
-        []
-       ],
-       "navigate-opaque-origin-page.html": [
-        "380258769d8a45613b8f9a3aab3b3a1c26525faf",
-        []
-       ]
-      }
-     },
-     "state": {
-      "updateCurrent": {
-       "resources": {
-        "opaque-origin-page.html": [
-         "d9c273a2171ab386459bff8e35091b1d1e33fee4",
-         []
-        ]
-       }
-      }
-     }
-    },
-    "navigate-event": {
-     "cross-window": {
-      "resources": {
-       "cross-origin-iframe-helper.html": [
-        "48a5e1d63d9d2ed0f5b70ad204ec9c247b927005",
-        []
-       ],
-       "document-domain-setter.sub.html": [
-        "abe32ad8fbee196bfc6d49865750eaf827e8edc0",
-        []
-       ]
-      }
-     },
-     "resources": {
-      "meta-refresh.html": [
-       "fd453e663f94e43e2d3afbbd87b902ed657b16b9",
-       []
-      ],
-      "navigatesuccess-cross-document-helper.html": [
-       "c073184b9b343e4db8bb15e4f0a648e5e224faac",
-       []
-      ]
-     }
-    },
-    "ordering-and-transition": {
-     "README.md": [
-      "ec65b9ddbd1aebd0c0ae419b9cadd8902007d714",
-      []
-     ],
-     "resources": {
-      "helpers.mjs": [
-       "1cbf6e7460dd359ab1b20c2eb23bb047425f5699",
-       []
-      ],
-      "notify-top-early.html": [
-       "0dd796f609c0659bd489e10cb02ac4816a88960b",
-       []
-      ]
-     }
-    }
-   },
    "appmanifest": {
     "META.yml": [
      "bef14c3c795aa5a8befd4d95aeb8573f4af07ad7",
@@ -289440,7 +289387,7 @@
       "overlapping-navigations-and-traversals": {
        "tentative": {
         "README.md": [
-         "02d2e94a0dff2f28425b35a109a3fd9834a86bb9",
+         "cdd7975c81ad0ee468b46970aaac2a57dc683337",
          []
         ],
         "cross-document-traversal-same-document-nav-expected.txt": [
@@ -291702,6 +291649,32 @@
          []
         ]
        },
+       "context-attributes": {
+        "clearRect_alpha_false-ref.html": [
+         "eccfc4e2ccadd6344d6593d41fa7abe30769c792",
+         []
+        ],
+        "drawImage_alpha_false-ref.html": [
+         "2c9e5620b2ef3e8bab6342e4ac062b446855d96f",
+         []
+        ],
+        "fillRect_alpha_false-ref.html": [
+         "eccfc4e2ccadd6344d6593d41fa7abe30769c792",
+         []
+        ],
+        "fill_alpha_false-ref.html": [
+         "03265a656eb6f4f06e02853cf0bed0d19223137c",
+         []
+        ],
+        "initial_color_alpha_false-ref.html": [
+         "b67513c464865f82621ba028b41098f08401b8e9",
+         []
+        ],
+        "reset_color_alpha_false-ref.html": [
+         "d663131148a2c062c79ad98c1060daa32ce82ca0",
+         []
+        ]
+       },
        "drawing-images-to-the-canvas": {
         "drawimage_canvas_self_ref.html": [
          "9f297cacdcd81bef7093f79ebed6992110dab4d7",
@@ -307111,7 +307084,7 @@
     ]
    },
    "lint.ignore": [
-    "d273dcf68418a3b8ad5bf7dac07f25dbdab7f043",
+    "bc320dcd71e42da152519358865499226919fb4a",
     []
    ],
    "loading": {
@@ -309506,6 +309479,148 @@
      ]
     }
    },
+   "navigation-api": {
+    "META.yml": [
+     "de4f6c9a335bd4b7d5e99d14d37b904fe5d54182",
+     []
+    ],
+    "OWNERS": [
+     "cc6a94b332a04fdf400165265f41194abaf67d3e",
+     []
+    ],
+    "README.md": [
+     "068ae5b7114745e50c7c8a86fec8ef684ee02f61",
+     []
+    ],
+    "focus-reset": {
+     "resources": {
+      "helpers.mjs": [
+       "e7e9a52009dd4c5cba642d2165fa451208304e8a",
+       []
+      ]
+     }
+    },
+    "navigate-event": {
+     "cross-window": {
+      "resources": {
+       "cross-origin-iframe-helper.html": [
+        "96e8a0c1e65432f661378037dfe977e8c4d45e0b",
+        []
+       ],
+       "document-domain-setter.sub.html": [
+        "abe32ad8fbee196bfc6d49865750eaf827e8edc0",
+        []
+       ]
+      }
+     },
+     "resources": {
+      "meta-refresh.html": [
+       "fd453e663f94e43e2d3afbbd87b902ed657b16b9",
+       []
+      ],
+      "navigatesuccess-cross-document-helper.html": [
+       "aabc5015a963f65d1de8e6e9f8c2742639958dac",
+       []
+      ]
+     }
+    },
+    "navigation-history-entry": {
+     "resources": {
+      "is_uuid.js": [
+       "3b855c01b075608e27766173a2783092493bbed6",
+       []
+      ],
+      "key-navigate-back-cross-document-helper.html": [
+       "79f3c3da0ae9076b8dccfefc2d2f626e86c1a64f",
+       []
+      ],
+      "no-referrer-meta.html": [
+       "bd5ec391cce892199b0d448b516f6dd087c99665",
+       []
+      ],
+      "no-referrer.html": [
+       "c8b7661f42212985888d1dfdf1d8729f89c4fc35",
+       []
+      ],
+      "no-referrer.html.headers": [
+       "7ffbf17d6be5a59e5b3636adc0eb14f3e2e528c2",
+       []
+      ],
+      "opaque-origin-page.html": [
+       "98e2c1b3175722ce87bc7694bc1915ceadc52920",
+       []
+      ],
+      "post-entries-length-to-top.html": [
+       "c8fe005d8ef0908490b66ca7417222bafc02859a",
+       []
+      ],
+      "post-key-to-top.html": [
+       "285f345dc1fc5ccbfb75b6c513f2b5184d23d9ea",
+       []
+      ]
+     }
+    },
+    "navigation-methods": {
+     "reload-service-worker-fetch-event-expected.txt": [
+      "cf4b285328a985e8413570fcc80e16d4501b3d09",
+      []
+     ],
+     "resources": {
+      "fetch-event-test-worker.js": [
+       "98b7dd0fb58b4142f90eca341356eb0efbd09d2f",
+       []
+      ],
+      "page-with-base-url-common.html": [
+       "8d9fedcc2b40d2eaeff55727ffa476a6e7c0e479",
+       []
+      ],
+      "service-worker-page.html": [
+       "a10ff35dce7768c6e7b6f68647207a8fa35c2a6d",
+       []
+      ]
+     },
+     "return-value": {
+      "resources": {
+       "back-forward-opaque-origin-page.html": [
+        "ec633639524aaa04917eb38505e47a59d9686e24",
+        []
+       ],
+       "helpers.js": [
+        "bf711b18ade2c881fbfa40fc8e44ddf456fa37d2",
+        []
+       ],
+       "navigate-opaque-origin-page.html": [
+        "831eefdb6123a19700badfbbb08c64fc953e1b6f",
+        []
+       ]
+      }
+     }
+    },
+    "ordering-and-transition": {
+     "README.md": [
+      "653dd2198c3a23d4d6d949a9262bb48b4e630dd4",
+      []
+     ],
+     "resources": {
+      "helpers.mjs": [
+       "b5a1e6b03186bfcc063a0a49ec46575d03bae969",
+       []
+      ],
+      "notify-top-early.html": [
+       "0dd796f609c0659bd489e10cb02ac4816a88960b",
+       []
+      ]
+     }
+    },
+    "updateCurrentEntry-method": {
+     "resources": {
+      "opaque-origin-page.html": [
+       "59931458a6b8632ae7ce17286d1ebca80b88235a",
+       []
+      ]
+     }
+    }
+   },
    "navigation-timing": {
     "DIR_METADATA": [
      "c907af0b21f16fa5683875467904fd498879089c",
@@ -317954,7 +318069,11 @@
        []
       ],
       "exec.html": [
-       "757dba1688b982b44bbfe1eac22cd3f6d0f9937f",
+       "80e0c5999e7f06fb1899984f247010f8cba0e5c9",
+       []
+      ],
+      "exec.py": [
+       "835a3f07aafa9664f763c5ebd9f10209dc8579d9",
        []
       ],
       "iframe-added-post-activation.html": [
@@ -317973,6 +318092,10 @@
        "03e36f06c134e1ee0ced6c703d1b8e20f1c7511b",
        []
       ],
+      "prerender-response-code.html": [
+       "c3a680bba841294f7204d22897cca5cfcc03d4f7",
+       []
+      ],
       "prerender-state.html": [
        "914db2a9ed900ece42982c12913576086f7db928",
        []
@@ -317994,7 +318117,7 @@
        []
       ],
       "utils.js": [
-       "bbb9448190c5e1978eeebe42c008dcfc075fd3d0",
+       "6952c3b9a327aac0c20d4de68dba9857761d35e2",
        []
       ],
       "visibility-state-check.html": [
@@ -341865,2086 +341988,6 @@
      ]
     ]
    },
-   "app-history": {
-    "app-history-entry": {
-     "after-detach.html": [
-      "07591f96ae51c4095833ccec2158c68e88356b4e",
-      [
-       null,
-       {}
-      ]
-     ],
-     "current-basic.html": [
-      "102fbbb270d7e1379cb7c4d5309e87e3df2adcfa",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-across-origins.html": [
-      "b6e8e9ee7340ea07137d7f069ccc087a70e86777",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-after-bfcache-in-iframe.html": [
-      "5644d12a34090960f35d2174966c1036fb77ba9a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-after-bfcache.html": [
-      "3cfe0393ae76a4f834c630a220e4c609cdfa2865",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-after-blank-navigation.html": [
-      "60068561d14cfd309a0d72c59c558752963edb27",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-after-blob-navigation.html": [
-      "c1ce46d1fbd31cbcaa63d1f35b790ac46e5ea4a4",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-after-cross-document-forward-pruning.html": [
-      "4888a5be738558bf444ae0730a17035e6ff3b9aa",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-after-javascript-url-navigation.html": [
-      "2ed1d81fe94e9e8ad8a1b5b825dcc984d82c2124",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-after-navigations-in-multiple-windows.html": [
-      "55cb58ea0022d60fcd6f4295542d5f5bcf84d59b",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-after-srcdoc-navigation.html": [
-      "cfb1337cabfb624bb299b6bbcec769e3b0318b28",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-array-equality.html": [
-      "e6eb99863d037f77f5f465077f486639a3f8cba1",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-in-new-javascript-url-iframe.html": [
-      "de7d68173cc1fe85eeccf01dce5f2217f613e7dc",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-in-new-srcdoc-iframe.html": [
-      "7ef6b4d511fce4176782d35c29d9004fd4d4ada2",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entries-when-inactive.html": [
-      "a7073778fb5aebd97a94c91d9913c44cb60f55fc",
-      [
-       null,
-       {}
-      ]
-     ],
-     "entry-after-detach.html": [
-      "dc31f1a695b013fd8e135d12814ce3bc5e8348e3",
-      [
-       null,
-       {}
-      ]
-     ],
-     "index-not-in-entries.html": [
-      "394dfa9763685e90dd1e6766a1429f0739846846",
-      [
-       null,
-       {}
-      ]
-     ],
-     "key-id-back-cross-document.html": [
-      "9fbb7334cbb0a37c71f29d0ab8e8f5098d33fed7",
-      [
-       null,
-       {}
-      ]
-     ],
-     "key-id-back-same-document.html": [
-      "94319c3592e08a0e6a2cbf855ad8ddc232c6d1b3",
-      [
-       null,
-       {}
-      ]
-     ],
-     "key-id-location-reload-transitionWhile.html": [
-      "b26e279d33b6a86b6ec5e09dd1cf6805df4889b7",
-      [
-       null,
-       {}
-      ]
-     ],
-     "key-id-location-reload.html": [
-      "660427011d43eb48bcdfff0235f42c3f2dce19e1",
-      [
-       null,
-       {}
-      ]
-     ],
-     "key-id-location-replace-cross-origin.html": [
-      "b49c601dc26d29b921f45d20ffcb20c30bc2dccb",
-      [
-       null,
-       {}
-      ]
-     ],
-     "key-id-location-replace.html": [
-      "9d245b1e837de7f920a16377bfb5392aa41396e7",
-      [
-       null,
-       {}
-      ]
-     ],
-     "no-referrer-dynamic-url-censored.html": [
-      "91484daa00eeda75a279fb4aed1b2057ef0d9927",
-      [
-       null,
-       {}
-      ]
-     ],
-     "no-referrer-from-meta-url-censored.html": [
-      "0f23f3b539b064178df159cafd53f6e1584cae35",
-      [
-       null,
-       {}
-      ]
-     ],
-     "no-referrer-url-censored.html": [
-      "2db425368f6a0436b0e713d8bed96f7a98e98967",
-      [
-       null,
-       {}
-      ]
-     ],
-     "opaque-origin-data-url.html": [
-      "a1e765dd1bb75f2ee20a7efae64f2b7cbd464e40",
-      [
-       null,
-       {}
-      ]
-     ],
-     "opaque-origin.html": [
-      "898ca27e4fab1d34bfe9c148ff53880a8a35d8cd",
-      [
-       null,
-       {}
-      ]
-     ],
-     "sameDocument-after-fragment-navigate.html": [
-      "732b10964efe99898b4628aa892c3d6ee9950be7",
-      [
-       null,
-       {}
-      ]
-     ],
-     "sameDocument-after-navigate-restore.html": [
-      "0c8abb4eb0b0e94c32762251484049f01dd70261",
-      [
-       null,
-       {}
-      ]
-     ],
-     "sameDocument-after-navigate.html": [
-      "1e984b336a671c66caa771190ba064d8ceee9001",
-      [
-       null,
-       {}
-      ]
-     ],
-     "state-after-navigate-restore.html": [
-      "aad065034f06a840a8581b154329a13def9480f9",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
-    "currentchange-event": {
-     "currentchange-anchor-href.html": [
-      "2a93abf9937942200d4c2d28c8051456de438003",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-back-forward-cross-doc.html": [
-      "7782d11b0c31a578637f9b7c17c00f95fe6f3cec",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-back-forward-same-doc.html": [
-      "8cfe2dca1ffa4914da8503bf6bfda5b6dca087df",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-navigate-cross-doc.html": [
-      "d6a657a1b1174ce0f3ced3bba8fd617620fefc3d",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-navigate-preventDefault.html": [
-      "82f10006832e16522844cdb7fa0bcf2e6a3a8578",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-navigate-replace-cross-doc.html": [
-      "3d8ceb746b80da1564a539e080ae965aa30d03b9",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-navigate-replace-same-doc.html": [
-      "fa7006b4ebb8b09017806b71073e38147e106b87",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-navigate-replace-transitionWhile.html": [
-      "86079e777cb9059b13e6763ea31877475142d368",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-navigate-same-doc.html": [
-      "d74048a0121fbc5d226c6cc35d0562c5d4c5eae8",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-navigate-transitionWhile.html": [
-      "d6a39d204ed72c6d578556798e1b9527f58ba219",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-reload-cross-doc.html": [
-      "ae142fb5d3bc4d600037077cf5bbbbcece64b6f2",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-reload-transitionWhile.html": [
-      "d1f022f584218fec9fb6fb584175adabd7a1a286",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-app-history-updateCurrent.html": [
-      "9e3a3ec387a2ce682e433dc39c6d2e94bfb3f27e",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-history-back-same-doc.html": [
-      "a90ee1eb5ddf86ef06393eef32f651a575088c5a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-history-pushState.html": [
-      "e5468eafc3938f71c907410ed33fad1a184141ce",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-history-replaceState.html": [
-      "5a99561ebd2f929e082cfd6b7938898f03243747",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-location-api.html": [
-      "8cf52ca6d288387036a5d93a007f33c1c79deef3",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-navigate-from-initial-about-blank-same-doc-popup.html": [
-      "8473e0b272db092c9308f65b5aa11f8cbc3c5238",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-navigate-from-initial-about-blank-same-doc.html": [
-      "e595868954a64e490fb2cc851afb5676fc5c6366",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-navigate-from-initial-about-blank.html": [
-      "415fb0c6e1c104ffbb20046b79d9111791fec284",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-properties.html": [
-      "a5417fa312c1b43db84b90c7f67fa1ab88be9333",
-      [
-       null,
-       {}
-      ]
-     ],
-     "event-constructor.html": [
-      "6c489363567fd61d210ad92e8336605ea02f1704",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
-    "focus-reset": {
-     "autofocus.html": [
-      "57fdf865ab40d7c6a6ceba5470f5542ed713a735",
-      [
-       null,
-       {}
-      ]
-     ],
-     "basic.html": [
-      "fb57cf864482a1b9297c432da8687fcfcfe7edb2",
-      [
-       null,
-       {}
-      ]
-     ],
-     "multiple-transitionWhile.html": [
-      "d38df573e59536b48cc9618ad20722a5d3c22978",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
-    "navigate": {
-     "back-forward-multiple-frames.html": [
-      "80a8fb65a05fb622988e8f64ac78f16c9172869d",
-      [
-       null,
-       {}
-      ]
-     ],
-     "disambigaute-back.html": [
-      "7a4a37db4216fbbe928cd25d422aca8bf2fd3967",
-      [
-       null,
-       {}
-      ]
-     ],
-     "disambigaute-forward.html": [
-      "b6508ed3406b6d5309f382e427fbc46cc3522427",
-      [
-       null,
-       {}
-      ]
-     ],
-     "disambigaute-goto-back-multiple.html": [
-      "c39da7914f12a8a42a8471861cbcb32b6163011b",
-      [
-       null,
-       {}
-      ]
-     ],
-     "disambigaute-goto-forward-multiple.html": [
-      "08c00582d8fd3459114518ba9b6447b1a8e269a7",
-      [
-       null,
-       {}
-      ]
-     ],
-     "forward-to-pruned-entry.html": [
-      "0e33360c8674f968652b6fee70c873fa32913410",
-      [
-       null,
-       {}
-      ]
-     ],
-     "goTo-after-adding-iframe.html": [
-      "9921bcf447fb6e8c3b3b95f8d7c09c5639078de6",
-      [
-       null,
-       {}
-      ]
-     ],
-     "goTo-after-data-url.html": [
-      "ba2b56d1912abd59f8c7db1f900c7d2af13f06a0",
-      [
-       null,
-       {}
-      ]
-     ],
-     "goTo-cross-document.html": [
-      "0a7bbbe1bf97ac0bbdd3c3b314857556cc8d8bda",
-      [
-       null,
-       {}
-      ]
-     ],
-     "goTo-detach-between-navigate-and-navigatesuccess.html": [
-      "2849ee1e9a21d7889ceab67a3373b23e193fe1a9",
-      [
-       null,
-       {}
-      ]
-     ],
-     "goTo-multiple-steps.html": [
-      "562a775d2298f6e87076ef00b0a83f7ad2fc2ae3",
-      [
-       null,
-       {}
-      ]
-     ],
-     "goTo-same-document.html": [
-      "a9af0c2ae22904858f76fa6f15573c5c61d99f39",
-      [
-       null,
-       {}
-      ]
-     ],
-     "goTo-with-cross-origin-in-history.html": [
-      "3da29550b3e8a33eaf79813dd0bac7c203de04db",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-base-url.html": [
-      "b11a71a9312785fc0b151a1c3e294a18f4cfabf4",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-from-initial-about-blank-src.html": [
-      "83cd77678a544f7356a283911aa17931a1e40350",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-from-initial-about-blank.html": [
-      "fd163ddf8ceb26ae705dc0be254e4921fedf6880",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-history-state-replace.html": [
-      "b4b9559fb56cb782941210eefbaef379afdb7e5a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-history-state.html": [
-      "8853c0616e3949119edccbb71d5ac32061a023d1",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-info-and-state.html": [
-      "8890c9b9ca4c7f2673e311458a26cb5b717f2e9a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-relative-url.html": [
-      "b426da595088bc1016faf7ae3ecf5088092bac48",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-replace-cross-document.html": [
-      "0b20dc850863a6b81ccca8205f6e7b9f353e185f",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-replace-same-document.html": [
-      "dea17f43ff61141fd8cd6f17b0189b8b00389643",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-same-document.html": [
-      "bad420272c40b7e0ccf4df73e0c4cea38615debd",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-state-repeated-await.html": [
-      "d81dd054f836ded2f73237bdfb4bdd2e626dfaf9",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-state-repeated.html": [
-      "ed655d2086dfdd1d1c8b4517c88b0f3d1b8800db",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-transitionWhile-history-state.html": [
-      "56d9ea11791a89836304dfc30cf68e7f53fe69d5",
-      [
-       null,
-       {}
-      ]
-     ],
-     "reload-base-url.html": [
-      "35b29b9d2fb032f97f7bd8ba607f928aec60571e",
-      [
-       null,
-       {}
-      ]
-     ],
-     "reload-info.html": [
-      "412655f6de12df4b9fbc56e286e00a39fb90062f",
-      [
-       null,
-       {}
-      ]
-     ],
-     "reload-navigation-timing.html": [
-      "a24e33ff11a7f103d4462b8520822aef83d9282f",
-      [
-       null,
-       {}
-      ]
-     ],
-     "reload-no-args.html": [
-      "e4f7bd628b21406a5035ded3523bc7a32e9d5f13",
-      [
-       null,
-       {}
-      ]
-     ],
-     "reload-service-worker-fetch-event.html": [
-      "4d078e45f6b8d4848afcaf54a71007c0b649f54b",
-      [
-       null,
-       {}
-      ]
-     ],
-     "reload-state-and-info.html": [
-      "0811f3d9cde2fc0dab15b481d96e5b5bd04896b8",
-      [
-       null,
-       {}
-      ]
-     ],
-     "reload-state-undefined.html": [
-      "47b336ba9ee615974288974449c6ea0b48a07bd4",
-      [
-       null,
-       {}
-      ]
-     ],
-     "return-value": {
-      "back-already-detached.html": [
-       "dd665e15f66278a476cbd7083e5ea16232203b93",
-       [
-        null,
-        {}
-       ]
-      ],
-      "back-beforeunload.html": [
-       "424d353803e9984aa860f6806ea18c5947f16727",
-       [
-        null,
-        {}
-       ]
-      ],
-      "back-forward-initial-about-blank.html": [
-       "902090e3a38c673c1bf5c8db825bf5706478ea6a",
-       [
-        null,
-        {}
-       ]
-      ],
-      "back-forward-opaque-origin.html": [
-       "59849e961d76a0151959836870fb7eda47ce8867",
-       [
-        null,
-        {}
-       ]
-      ],
-      "back-forward-out-of-bounds.html": [
-       "bef0d7e71790fb25c63e87932d06eb249645bde2",
-       [
-        null,
-        {}
-       ]
-      ],
-      "back-transitionWhile-rejected.html": [
-       "b617f0bbad17f9b49afefb0868d46fe720b66b1c",
-       [
-        null,
-        {}
-       ]
-      ],
-      "back-transitionWhile.html": [
-       "a6680f6ba6f82404923cc0745ad17be1fc6a513a",
-       [
-        null,
-        {}
-       ]
-      ],
-      "back.html": [
-       "1be1d421724b6f1ed168776b9437bd92d9ee0b52",
-       [
-        null,
-        {}
-       ]
-      ],
-      "forward-already-detached.html": [
-       "c3b7de84935e64722f119293d1accc30702f35f8",
-       [
-        null,
-        {}
-       ]
-      ],
-      "forward-beforeunload.html": [
-       "c97b796e2754ea3077ce957fa1c8abb2f2faefa8",
-       [
-        null,
-        {}
-       ]
-      ],
-      "forward-transitionWhile-rejected.html": [
-       "76cbfe44fcce3cc1c4177084b1dcd75190b5a388",
-       [
-        null,
-        {}
-       ]
-      ],
-      "forward-transitionWhile.html": [
-       "86bf5470953b1b164cc824772ec6082910efee1b",
-       [
-        null,
-        {}
-       ]
-      ],
-      "forward.html": [
-       "15d454c146152aa83d13bbef7a8467724ab7a57f",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo-already-detached.html": [
-       "9752cbb3aebf906abf26c4889259c74dac5fea8c",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo-beforeunload.html": [
-       "2a63ab161137da4e655cf4212664ffd0783e0a95",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo-cross-document-preventDefault.html": [
-       "9c2301728deef44e0e5e8e76d1a419c408ff1648",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo-current.html": [
-       "0601dbefa6b8eee7e7822329d10904f351109b7a",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo-detach-cross-document.html": [
-       "95b2ec8f40d2580fedc60e618134a5faa7656434",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo-detach-same-document.html": [
-       "441f9371d036db8f50cab4834a58a86c0614a6d8",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo-invalid-key.html": [
-       "ee4d66f9aed32a1706cecb5c2090f54b75474149",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo-repeated.html": [
-       "80d43d6477741fbb52cb897a665ada5d6aca2766",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo-transitionWhile-rejected.html": [
-       "9e306043470a43e25d10056b33f386f108f7dd23",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo-transitionWhile.html": [
-       "4b98a27b3da8d783a84cdf860e10cb1f4a6f49bf",
-       [
-        null,
-        {}
-       ]
-      ],
-      "goTo.html": [
-       "81a3ef2df709facec59698e727e1c76fd73f0fb2",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-already-detached.html": [
-       "cc077e2e3fe2df73a6adf3e9ee1360e7a24d72c0",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-beforeunload.html": [
-       "2c9af273af7858ec0752d4816ec6efd8d27205ec",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-cross-document.html": [
-       "0735a23e713d814cc9695f5fa546a7188d99d934",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-detach.html": [
-       "84b1d2c0de620411943bc0a559200d973ebceb59",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-file-url.html": [
-       "1dc383509adc3260d9e55ce78ceeae3bfafcbb3b",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-initial-about-blank-cross-document.html": [
-       "d70890aedf23aa6514ae31dd2f64d5c13b88950a",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-initial-about-blank.html": [
-       "6c976039f6158c59f5cb8aecb134f9971c73372e",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-interrupted-within-onnavigate.html": [
-       "cfd3e13cef38457aa671d4d0f4f008f1633cdee0",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-interrupted.html": [
-       "0d419c0fd1498c557f6b5889000701830a538dc9",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-invalid-url.html": [
-       "a9d47d05ce3cd72d610d57484cba81cb32652ec4",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-opaque-origin.html": [
-       "51500eb8d83c121241bbcf467e010fbda43daa55",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-preventDefault.html": [
-       "e27bde78610f4d9a99b6245a43fc3fe69cf8c71c",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-rejection-order-beforeunload-unserializablestate.html": [
-       "b7005709aa6d56a8de05011f6ab3ae9f8ad32e96",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-rejection-order-detached-unserializablestate.html": [
-       "69fe45278522507e8f597be26f0cf1aaaab40eb6",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-rejection-order-invalidurl-beforeunload.html": [
-       "5346ecd02f9c5e1fbbb7f7dda3f055fc1c4e6f48",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-rejection-order-invalidurl-detached.html": [
-       "bd7981d8bc147487c76027d36a497e0d2ec9df39",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-rejection-order-invalidurl-unload.html": [
-       "8cf6c0179155fe596ce29c24fe0157063522a29e",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-rejection-order-invalidurl-unserializablestate.html": [
-       "f1ccb0c73b4ffe45d85aed5c4ea5a7a09ac84c68",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-rejection-order-unload-unserializablestate.html": [
-       "ac7976052314a48e60c5c103717789eab7841d22",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-transitionWhile-interrupted.html": [
-       "0e274d5591e8f7e0d1cf9e86207326403ee2abc5",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-transitionWhile-rejected.html": [
-       "36d95698f549225480d88e44ace3b3bdf7f09056",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-transitionWhile.html": [
-       "7809be1dec3307c0c551fca80b8833cfad07bb85",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-unload.html": [
-       "641de2c16bee44ea6914495dfa938341bcc18706",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-unserializable-state.html": [
-       "e6060ce891c5c1aeefc35f50fe585ac43593e4b1",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate.html": [
-       "eab1e8a0a127f3df225a62aa4c9301fa833cb021",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-already-detached.html": [
-       "7ecd31ae06bfe80689f91ac0e675492cd031c015",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-beforeunload.html": [
-       "7a3d9e3a0eda59eb96dd59a51b2878c7218812a5",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-detach.html": [
-       "f5b999134bcf5155bf50e7609f3a88806becdc00",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-initial-about-blank.html": [
-       "e1b9f14285ba0d777cfe03963d5b0ede115237e0",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-preventDefault.html": [
-       "3eeb72f27c9b54d54b8b480ca6677b415622df02",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-rejection-order-beforeunload-unserializablestate.html": [
-       "89c60472de7f1982cabd68757ef387260078a768",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-rejection-order-detached-unserializablestate.html": [
-       "12e5ac2d6df6a4cbafec0a306851b26e66d83ad5",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-rejection-order-unload-unserializablestate.html": [
-       "f528a68b4f77cf64e925bd1668dda349c446934e",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-transitionWhile-rejected.html": [
-       "6ad452e9cfbdf3e0137c396fed917226ce29113a",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-transitionWhile.html": [
-       "d5563d9fff730269c19dd8a16d8bf63e9d863799",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-unload.html": [
-       "a62796b5db9d8c2a6e5bf2b24ee3440f0080157a",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload-unserializable-state.html": [
-       "e87071b94f77675fc3f46a4795ec03a7cccb07ce",
-       [
-        null,
-        {}
-       ]
-      ],
-      "reload.html": [
-       "55836e790519dd583bd1a9d256068f4c67c39c39",
-       [
-        null,
-        {}
-       ]
-      ]
-     },
-     "state": {
-      "navigate-state-after-history-pushState.html": [
-       "e24fc0f31c14c990b89b5e5d1fdd4ce503b923eb",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-state-after-history-replaceState.html": [
-       "23f037e57a0cdc9d762e8b47cd1a63e3506ae56d",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-state-after-reload.html": [
-       "28b2424e93ab072e287cd520f78795f1f9389927",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-state-after-same-document-location-api-navigation.html": [
-       "49e4507211a9b4c2a4d24fc66fde3a81c524ae27",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-state-after-same-document-navigation.html": [
-       "e2c1471efed48ed759a7d0d2836cf138159eb288",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-state-cross-document-location-navigate.html": [
-       "384d8d940eebcba92ce8077dab44438d54a1ec9c",
-       [
-        null,
-        {}
-       ]
-      ],
-      "navigate-state-cross-document-restore.html": [
-       "e1f70dc27bed59205b58f8af79983f754e60904c",
-       [
-        null,
-        {}
-       ]
-      ],
-      "updateCurrent": {
-       "basic.html": [
-        "00d3f59741d237416fe8605f5e65ecfa2e3a0046",
-        [
-         null,
-         {}
-        ]
-       ],
-       "exception-order-initial-about-blank-unserializablestate.html": [
-        "9ecb79793a01528eea377c0904ba5117eb725076",
-        [
-         null,
-         {}
-        ]
-       ],
-       "exception-order-not-fully-active-unserializablestate.html": [
-        "bd13b59e49feb2785aa7e2940208ea4a32be3007",
-        [
-         null,
-         {}
-        ]
-       ],
-       "initial-about-blank.html": [
-        "1c1d91453aeb9fc4d3430dc4f1d22e06e85c4c91",
-        [
-         null,
-         {}
-        ]
-       ],
-       "no-args.html": [
-        "435f260925a213e7cec711c1ce03eb882fdfca50",
-        [
-         null,
-         {}
-        ]
-       ],
-       "not-fully-active.html": [
-        "ba1df86e3ff1885f9fb61644e54bdecda0dde762",
-        [
-         null,
-         {}
-        ]
-       ],
-       "opaque-origin.html": [
-        "898ca27e4fab1d34bfe9c148ff53880a8a35d8cd",
-        [
-         null,
-         {}
-        ]
-       ],
-       "state-after-history-pushState.html": [
-        "e24fc0f31c14c990b89b5e5d1fdd4ce503b923eb",
-        [
-         null,
-         {}
-        ]
-       ],
-       "state-after-history-replaceState.html": [
-        "440cd4897f649ec1d8d703034e87f69d1441375c",
-        [
-         null,
-         {}
-        ]
-       ],
-       "state-after-reload.html": [
-        "43d452d6b3c86d782bb32243351f690c148b889d",
-        [
-         null,
-         {}
-        ]
-       ],
-       "state-after-same-document-location-api-navigation.html": [
-        "5d57ea0dee8b2f4f513a090a4c66d7b7d8f4b79d",
-        [
-         null,
-         {}
-        ]
-       ],
-       "state-cross-document-location-navigate.html": [
-        "6e70b48063c848500c2a98663cd51df5b12e5103",
-        [
-         null,
-         {}
-        ]
-       ],
-       "state-cross-document-restore.html": [
-        "f872e6107960e03ccf5d99c82f0c4f9b5d4d61ce",
-        [
-         null,
-         {}
-        ]
-       ],
-       "state-invalid.html": [
-        "469b7d2206a4c76aa9ad1bdeeae7bc01dce9cea7",
-        [
-         null,
-         {}
-        ]
-       ]
-      }
-     }
-    },
-    "navigate-event": {
-     "cross-window": {
-      "click-crossdocument-crossorigin-sameorigindomain.sub.html": [
-       "24abe7f2ef8aa5af592540760e409e03247befaa",
-       [
-        null,
-        {}
-       ]
-      ],
-      "click-crossdocument-crossorigin.html": [
-       "6fb559ceb12b347b874b825f4d327f4559a1a227",
-       [
-        null,
-        {}
-       ]
-      ],
-      "click-crossdocument-sameorigin.html": [
-       "ed1e8a37cda9f15cf42bb3e2c69e49f787b5802e",
-       [
-        null,
-        {}
-       ]
-      ],
-      "click-samedocument-crossorigin-sameorigindomain.sub.html": [
-       "0df9ec6234bd70e238af27417f9eabbc23c5a811",
-       [
-        null,
-        {}
-       ]
-      ],
-      "click-samedocument-crossorigin.html": [
-       "7973f0d38cf5dd6fe953f18c9ec25b648ee786a9",
-       [
-        null,
-        {}
-       ]
-      ],
-      "click-samedocument-sameorigin.html": [
-       "a2532fc0269587e4efd42b75bb45e5b483336312",
-       [
-        null,
-        {}
-       ]
-      ],
-      "location-crossdocument-crossorigin-sameorigindomain.sub.html": [
-       "2623b4bbbce07623e2d1a3613e027708d5fb307e",
-       [
-        null,
-        {}
-       ]
-      ],
-      "location-crossdocument-crossorigin.html": [
-       "b0f031cf92478dff59aabb1f2de23fa8d813e073",
-       [
-        null,
-        {}
-       ]
-      ],
-      "location-crossdocument-sameorigin.html": [
-       "02329697a39e7348772974b6d3caae78467be66b",
-       [
-        null,
-        {}
-       ]
-      ],
-      "location-samedocument-crossorigin-sameorigindomain.sub.html": [
-       "ec9723619f2e29360c9e2b25e77c21a0806bb5aa",
-       [
-        null,
-        {}
-       ]
-      ],
-      "location-samedocument-crossorigin.html": [
-       "b5d4fbfd07af9dd0c1321486876b46bc035ee5be",
-       [
-        null,
-        {}
-       ]
-      ],
-      "location-samedocument-sameorigin.html": [
-       "634b4f77567334f9d53d7807fe6f63c0f4d4c241",
-       [
-        null,
-        {}
-       ]
-      ],
-      "open-crossdocument-crossorigin-sameorigindomain.sub.html": [
-       "edbcee53ef7446af8c12673c1cb6a47619e8a940",
-       [
-        null,
-        {}
-       ]
-      ],
-      "open-crossdocument-crossorigin.html": [
-       "88e975ae5dcf8b25ef1d72023ac3cfa6c58b113c",
-       [
-        null,
-        {}
-       ]
-      ],
-      "open-crossdocument-sameorigin.html": [
-       "8a23dce24192dbb43a9009c8656b687bcf98a306",
-       [
-        null,
-        {}
-       ]
-      ],
-      "open-samedocument-crossorigin-sameorigindomain.sub.html": [
-       "c072ed9d5fec43f9a823a7a0af27ac43b76a5756",
-       [
-        null,
-        {}
-       ]
-      ],
-      "open-samedocument-crossorigin.html": [
-       "e7fe633e0da5c1f91e0c6082c9b98310e3fdda7d",
-       [
-        null,
-        {}
-       ]
-      ],
-      "open-samedocument-sameorigin.html": [
-       "bab81615cbefb19161b8e42f721e5beb93063f08",
-       [
-        null,
-        {}
-       ]
-      ],
-      "submit-crossdocument-crossorigin-sameorigindomain.sub.html": [
-       "794eccfb64652a8431c44c317dc2a78c941856ef",
-       [
-        null,
-        {}
-       ]
-      ],
-      "submit-crossdocument-crossorigin.html": [
-       "4356a127bdcf9da96d36b220742500919ca71950",
-       [
-        null,
-        {}
-       ]
-      ],
-      "submit-crossdocument-sameorigin.html": [
-       "45523532243b971ed2762ed8014ed37ec072b194",
-       [
-        null,
-        {}
-       ]
-      ],
-      "submit-samedocument-crossorigin-sameorigindomain.sub.html": [
-       "4f84e6aef354bcd56a07fca945c1dc2e803de6c5",
-       [
-        null,
-        {}
-       ]
-      ],
-      "submit-samedocument-crossorigin.html": [
-       "7e25d174713420473dd01fda477811cac38b2ed0",
-       [
-        null,
-        {}
-       ]
-      ],
-      "submit-samedocument-sameorigin.html": [
-       "12de577cb66bff7edbe0fdd3217c0313cc5c401b",
-       [
-        null,
-        {}
-       ]
-      ]
-     },
-     "event-constructor.html": [
-      "7a8803fab41e3ee659a86f2a227a6b2fc261d8d6",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-anchor-cross-origin.html": [
-      "a6d395a35eb8b549faf4c9e4ab94cd197bdc6c63",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-anchor-fragment.html": [
-      "77cf94a40592bba55d7d0c021db80b012a6332d9",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-anchor-same-origin-cross-document.html": [
-      "ac8f548239cdaa01eae547190c615271019ca9c0",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-anchor-userInitiated.html": [
-      "8afe01d4c564f5d3ad087b6244bfd62c792adc07",
-      [
-       null,
-       {
-        "testdriver": true
-       }
-      ]
-     ],
-     "navigate-anchor-with-target.html": [
-      "cf693583104681235ab57f9ab51898a8464aab0c",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-appHistory-back-cross-document.html": [
-      "2be9cb9b889ace4c9576b67d24093eb0dc2ba0c5",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-appHistory-back-same-document.html": [
-      "acc71ad3fe2cda228d91ade85e162af0d23266c3",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-appHistory-navigate.html": [
-      "b48e816cdff93c6a1655ab502af3fde2990f69a5",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-destination-getState-back-forward.html": [
-      "07e66b8a5ffea676865c62df4192e5bf5531dfb9",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-destination-getState-navigate.html": [
-      "43ca7b42c47a0b4d78c44c49411a14df28d4b888",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-form-get.html": [
-      "843849154d1ec4f0b08d7b422d0268c6018e0545",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-form-reload.html": [
-      "fbab4d2136fc61db9bd8f3244bd73facc4983ed7",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-form-traverse.html": [
-      "1514b2bcf93828cef25e94190d1bcf5495b50ff3",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-form-userInitiated.html": [
-      "e1c8c2825a5ab5538e4f96bf7bf3ba84756670a0",
-      [
-       null,
-       {
-        "testdriver": true
-       }
-      ]
-     ],
-     "navigate-form-with-target.html": [
-      "dcbbdc02b183b832d36142576946436391feb359",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-form.html": [
-      "8413c48ef46d414364ce9c137bbf430d8e89bbb8",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-history-back-after-fragment.html": [
-      "307fe5761bc04d39ad4e7393033e58c5d10704dc",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-history-back-after-pushState.html": [
-      "74f353d50513403b05eb75d48c2e96344e6e871c",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-history-back-cross-document.html": [
-      "c6c8322a35ace2358157607e6a7a7774961e8a0e",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-history-go-0.html": [
-      "2070614deb9e1836348f4ddac1e6603867a2ab3a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-history-pushState.html": [
-      "b7940f17564be054325800697412752a2aabb033",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-history-replaceState.html": [
-      "dda602dcc466f22b6b5e1a94765add8f0dc4edf3",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-iframe-location.html": [
-      "8abaabb847717744d8253f3794b1bdb590b031ab",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-location.html": [
-      "ec17dcfbafe3a0f87f08cd1b4b1f600c7e83f921",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-meta-refresh.html": [
-      "4f3769d946af45dfa7082282fd20c15033622b25",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-to-javascript.html": [
-      "53ce37a0220f78eebd84d9d734770a375af0b86c",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-to-srcdoc.html": [
-      "b9d315dad428e32e251915e20aec8dcc32bfbec3",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-window-open-self.html": [
-      "1523ed81374f2a5cf380e00f830986200d6755d0",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-window-open.html": [
-      "03cd14e34b85dbe3e088e05441dae9138c79aa18",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigatesuccess-cross-document.html": [
-      "1d528c1f5f9154c1bc089315a0ae11d72b7d3820",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigatesuccess-same-document.html": [
-      "033308940d4233ec897c1b533d5be9351ee99d4a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "signal-abort-detach-in-onnavigate.html": [
-      "19ab7ae414b61545bfa11e5e86ba9f812e8c9767",
-      [
-       null,
-       {}
-      ]
-     ],
-     "signal-abort-preventDefault.html": [
-      "f9a1cd7015de038d56af0b1975c535ce9e135e6c",
-      [
-       null,
-       {}
-      ]
-     ],
-     "signal-abort-transitionWhile.html": [
-      "82d0c07eb74e4eb26c9fca718e45549ebf3481ad",
-      [
-       null,
-       {}
-      ]
-     ],
-     "signal-abort-window-stop-after-transitionWhile.html": [
-      "108ef39ffee8138caf094cf3c5ee7faac2ca8a97",
-      [
-       null,
-       {}
-      ]
-     ],
-     "signal-abort-window-stop-in-onnavigate.html": [
-      "00be9925ea9b131d08b824172986a6da2be37db8",
-      [
-       null,
-       {}
-      ]
-     ],
-     "signal-abort-window-stop.html": [
-      "03c36f7f6d683a598be27befa1919f3715b1cd59",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-after-dispatch.html": [
-      "f4cc707a48dabd22e537826e1abcbeebd8e5be8f",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-and-navigate.html": [
-      "65e9d0492784368687dc53d4ae75716d279073fe",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-appHistory-back.html": [
-      "c05e7d3bc63c3f6a1bd64046852553612c100322",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-canceled-event.html": [
-      "79ed0571a450c640f2dcf36ab8cd3a46669b7ac1",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-cross-document-same-origin.html": [
-      "249641d0ca06376a6a38f116db8e0c284ba7c0be",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-cross-origin.html": [
-      "d828c5cd0ee2fc0b0ff543779b088ccfa611360e",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-detach.html": [
-      "76b624134cbe8d25ea5868b3ae638ff573c02204",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-history-pushState.html": [
-      "81da433de04760504ff9abe442ae2b60ab30ae17",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-history-replaceState.html": [
-      "41c4949537edd8b3a5f99b342816100563ab9107",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-multiple-times-reject.html": [
-      "6f40cf63d21af0ddf35596067ff8015b5e670b90",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-multiple-times.html": [
-      "a409db86a5147113b625a5a09d46576f5512dd5f",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-on-synthetic-event.html": [
-      "a3bb80fe66ccf1ddc4335e28697b1ade17be97f4",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-reject.html": [
-      "33e0214d46c075a785224259cdf6c8a5ce388420",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-resolve.html": [
-      "32a47ec2854efe47827ab39d0226f4ab090eb64d",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transitionWhile-same-document-history-back.html": [
-      "587a6bd379108e7f3aca78332f3a267e859fa5ba",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
-    "ordering-and-transition": {
-     "back-same-document-transitionWhile-reject.html": [
-      "1255dbef7d279ff4a055b6544e4e7e86bc940a4e",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/back-same-document-transitionWhile-reject.html?currentchange",
-       {}
-      ]
-     ],
-     "back-same-document-transitionWhile.html": [
-      "e83ac55e9e8c37d2b1814217631e36d677b6a99e",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/back-same-document-transitionWhile.html?currentchange",
-       {}
-      ]
-     ],
-     "back-same-document.html": [
-      "b2bec4ddc3d152a457717f2d277a8ab0356baeaf",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/back-same-document.html?currentchange",
-       {}
-      ]
-     ],
-     "currentchange-before-popsate-transitionWhile.html": [
-      "2a0155a6ed52bff80ce06b1a797615c2d5191e01",
-      [
-       null,
-       {}
-      ]
-     ],
-     "currentchange-dispose-ordering.html": [
-      "f9d23f7dd5da79afb95bd998c618685c225c9e6a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "location-href-canceled.html": [
-      "ed776ffcad637de40399c82777e769b7b9d995e2",
-      [
-       null,
-       {}
-      ]
-     ],
-     "location-href-double-transitionWhile.html": [
-      "0168dc9347911052221af99e8489eb7f4fad9d7e",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/location-href-double-transitionWhile.html?currentchange",
-       {}
-      ]
-     ],
-     "location-href-transitionWhile-reentrant.html": [
-      "3b6215d7809494e27502174c69c30ef004555bd7",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/location-href-transitionWhile-reentrant.html?currentchange",
-       {}
-      ]
-     ],
-     "location-href-transitionWhile-reject.html": [
-      "e0495b45fc8d4d1a95e2cbbcef8e635eb4319998",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/location-href-transitionWhile-reject.html?currentchange",
-       {}
-      ]
-     ],
-     "location-href-transitionWhile.html": [
-      "988a025348268466625ad9143d53e2f5a4e9fdad",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/location-href-transitionWhile.html?currentchange",
-       {}
-      ]
-     ],
-     "navigate-canceled.html": [
-      "550284139a94de0894306e190e2deb7ea36ffb52",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-cross-document-double.html": [
-      "ba1edceead52cb79b9bcb77c317dca5e2636d817",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-cross-document-event-order.html": [
-      "1c1758a002eb8ac5e8ba8945dc6163d32a1a70f4",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigate-double-transitionWhile.html": [
-      "eb54f9e0ba4f4e6672330725889585cc9ec9227e",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/navigate-double-transitionWhile.html?currentchange",
-       {}
-      ]
-     ],
-     "navigate-in-transition-finished.html": [
-      "f84be14b545b62ae119ae26934489749a659a34d",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/navigate-in-transition-finished.html?currentchange",
-       {}
-      ]
-     ],
-     "navigate-same-document-transitionWhile-reentrant.html": [
-      "0170dec95ffe997ef63ca0ba3147e3c183b8bd8a",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/navigate-same-document-transitionWhile-reentrant.html?currentchange",
-       {}
-      ]
-     ],
-     "navigate-same-document-transitionWhile-reject.html": [
-      "f46f926de14c637e921c84ea1bba03ca6a8109d7",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/navigate-same-document-transitionWhile-reject.html?currentchange",
-       {}
-      ]
-     ],
-     "navigate-same-document.html": [
-      "1fa2709ecb355a10043f0335156a54950d414e09",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/navigate-same-document.html?currentchange",
-       {}
-      ]
-     ],
-     "navigate-transitionWhile-stop.html": [
-      "382dc9e2dd07f3574d7ef9a1b8da0249786aee05",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/navigate-transitionWhile-stop.html?currentchange",
-       {}
-      ]
-     ],
-     "navigate-transitionWhile.html": [
-      "fdc7428f5b60b51e80ee02fe8f6f196fb9a3a3bf",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/navigate-transitionWhile.html?currentchange",
-       {}
-      ]
-     ],
-     "reload-canceled.html": [
-      "0e060d227653b2b743d8c58c5ccef624e400ec19",
-      [
-       null,
-       {}
-      ]
-     ],
-     "reload-transitionWhile-reject.html": [
-      "88fbe9a4f4606c2a89cd52a28b60e3f4236fca2c",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/reload-transitionWhile-reject.html?currentchange",
-       {}
-      ]
-     ],
-     "reload-transitionWhile.html": [
-      "235cfd0f96990d8a9744eaf0d77d68c8139ee112",
-      [
-       null,
-       {}
-      ],
-      [
-       "app-history/ordering-and-transition/reload-transitionWhile.html?currentchange",
-       {}
-      ]
-     ],
-     "transition-cross-document.html": [
-      "400cccec25e5400f56c828ac6a0fdba46817811c",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transition-finished-mark-as-handled.html": [
-      "c3b2723191aa51fd9a73157b826159584f444595",
-      [
-       null,
-       {}
-      ]
-     ],
-     "transition-realms-and-identity.html": [
-      "8b825dab1a44631b8d211708873521126e6978ae",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
-    "per-entry-events": {
-     "dispose-after-bfcache.html": [
-      "db24158085953186ef874411285317a3c612d09c",
-      [
-       null,
-       {}
-      ]
-     ],
-     "dispose-cross-document.html": [
-      "22ea15bae9846d0075ef3c8092f62a732c727d2b",
-      [
-       null,
-       {}
-      ]
-     ],
-     "dispose-same-document-navigate-during.html": [
-      "e539eabda99cc42c2925a09087cb77700edd74d2",
-      [
-       null,
-       {}
-      ]
-     ],
-     "dispose-same-document-reload-with-transitionWhile.html": [
-      "a9b01eb3485f84484f5f96bded1a988563648a08",
-      [
-       null,
-       {}
-      ]
-     ],
-     "dispose-same-document-replace-with-transitionWhile.html": [
-      "7efac924ef2281a5e25e366605bcefb9c43935ff",
-      [
-       null,
-       {}
-      ]
-     ],
-     "dispose-same-document-replaceState.html": [
-      "6da4c8e98926ca854499e0c6db16caf336ae09fa",
-      [
-       null,
-       {}
-      ]
-     ],
-     "dispose-same-document-transitionWhile.html": [
-      "97ed346f8f717f457c589bf70d0fa39952e45021",
-      [
-       null,
-       {}
-      ]
-     ],
-     "dispose-same-document.html": [
-      "85e1293c2fe4fe92b647fcd2b9aef1d7d9405852",
-      [
-       null,
-       {}
-      ]
-     ]
-    }
-   },
    "audio-output": {
     "enumerateDevices-permissions-policy.https.html": [
      "4e5652e68f5e0e72ae892df46195090206f62185",
@@ -365433,49 +363476,49 @@
        ]
       ],
       "container-units-animation.html": [
-       "75178bb99950cc0054d4df5c69838eb343a85492",
+       "cf1b9a8f345ebfeb1217237f078e5e4e55f96250",
        [
         null,
         {}
        ]
       ],
       "container-units-basic.html": [
-       "09f662438d768be8e9b39bb8ce37f0b2c344ad86",
+       "166a003a29b2eb1bc0c017815a36cdcb1361c2aa",
        [
         null,
         {}
        ]
       ],
       "container-units-computational-independence.html": [
-       "5f22a290a0677bdd56077f6b25552242b7c6c945",
+       "694b665c793c820f5f80aa2c19ff4be146f72a08",
        [
         null,
         {}
        ]
       ],
       "container-units-invalidation.html": [
-       "257573a9269f2add840bb66466f12a05a094ed4e",
+       "0cb5b15f4a8e3666a5fbf60542e65fe6a5c31da8",
        [
         null,
         {}
        ]
       ],
       "container-units-selection.html": [
-       "7471ec4dbf1b84f87bcd3ec899a3b51eec53ca5b",
+       "16a44cd176618d0ed21e6cb8921fd286234441fb",
        [
         null,
         {}
        ]
       ],
       "container-units-small-viewport-fallback.html": [
-       "f78dbc1772dcd94b13dc19f60ffb5563e1aca04a",
+       "6c8851681fb62fc133a2635796e38c367e866dcb",
        [
         null,
         {}
        ]
       ],
       "container-units-typed-om.html": [
-       "9baf12d1c9059e58fb8103a8baa0d1d9997a4deb",
+       "6da3306fdfa3e88dde176d43c81813c3959a0306",
        [
         null,
         {}
@@ -365503,7 +363546,7 @@
        ]
       ],
       "display-none.html": [
-       "bae21bc3241e48392d875eb198ad47004774d78e",
+       "9a91b7c40910f92e09267274d8ed42a475ba474c",
        [
         null,
         {}
@@ -365635,6 +363678,13 @@
         {}
        ]
       ],
+      "sibling-layout-dependency.html": [
+       "5e30a998d23b03dd73f1ff290876243bd2eb9417",
+       [
+        null,
+        {}
+       ]
+      ],
       "size-container-no-principal-box.html": [
        "a1b875918bd86219edab1a0803ad1d383ebb4bc8",
        [
@@ -482587,6 +480637,2086 @@
      ]
     ]
    },
+   "navigation-api": {
+    "currententrychange-event": {
+     "anchor-click.html": [
+      "55e45561351ff1c6f43d211f1b8d7c78cfa098cc",
+      [
+       null,
+       {}
+      ]
+     ],
+     "constructor.html": [
+      "64ea8cf37ebd545cb349e4b0d863be3061851b99",
+      [
+       null,
+       {}
+      ]
+     ],
+     "history-back-same-doc.html": [
+      "bee3649395a8b1f4802de17d7fb4b21f894863e2",
+      [
+       null,
+       {}
+      ]
+     ],
+     "history-pushState.html": [
+      "c0e2d9a783cbe508bd065792ff115f62e26770a6",
+      [
+       null,
+       {}
+      ]
+     ],
+     "history-replaceState.html": [
+      "da5505a6c14a2485b2ba194d0060bdfadef53a5c",
+      [
+       null,
+       {}
+      ]
+     ],
+     "location-api.html": [
+      "f06dc8ec615942aa277a602b7070502e74396725",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-from-initial-about-blank-same-doc-popup.html": [
+      "a8aa2a25fb51c0bfc0a40c953d50dc3786ddd409",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-from-initial-about-blank-same-doc.html": [
+      "e2c56743588e0bb1919565495fed603ff6b2becf",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-from-initial-about-blank.html": [
+      "7b406c733c4a11712cabc7596daacc7eb769d5ce",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-back-forward-cross-doc.html": [
+      "df230bd9acb5daae40f30a2dfe625ebe0f8aa78f",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-back-forward-same-doc.html": [
+      "41cc4afd446e6338792a0f4cceb285082903d858",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-navigate-cross-doc.html": [
+      "2f804a307f6cbeef899411f6fd58c382b2f7f1cf",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-navigate-preventDefault.html": [
+      "997fb559492c7383e630e41c399a2106bcf58732",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-navigate-replace-cross-doc.html": [
+      "52478f22799b0593749a2b59963ed0de43ee0ff9",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-navigate-replace-same-doc.html": [
+      "a4027709687949140948681c9115d9eeff8d9307",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-navigate-replace-transitionWhile.html": [
+      "55b314a554d2542c3a8de9388135180fae2cada0",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-navigate-same-doc.html": [
+      "96de3de6d3d62b2d33d566c2e38568cb8d327f36",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-navigate-transitionWhile.html": [
+      "2132016404dd17968100315ec78fc4bf998bfb84",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-reload-cross-doc.html": [
+      "cb4279cfe53f44146bca35f6aa529997f70207fb",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-reload-transitionWhile.html": [
+      "cfa6258dc578de7fe011ed8a938b333ea8e38388",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigation-updateCurrentEntry.html": [
+      "8d548706be938d26e459cb4c5c7bf69620517f3e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "properties.html": [
+      "7048034e4f051bf1f302383fbc0d5828ede3a837",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
+    "focus-reset": {
+     "autofocus.html": [
+      "4418dfa76cde71028a8dab07fef6995f8652f86e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "basic.html": [
+      "8a62d88ba659831f8abc1579ef7b1793b025baac",
+      [
+       null,
+       {}
+      ]
+     ],
+     "multiple-transitionWhile.html": [
+      "c1d028524a5e8c0711b912f447f492193b9e8246",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
+    "navigate-event": {
+     "cross-window": {
+      "click-crossdocument-crossorigin-sameorigindomain.sub.html": [
+       "d162d8ddb883bea30aa034a41b78df626fd082a7",
+       [
+        null,
+        {}
+       ]
+      ],
+      "click-crossdocument-crossorigin.html": [
+       "2f40238912657585642363fc86bc8d355e6a78cc",
+       [
+        null,
+        {}
+       ]
+      ],
+      "click-crossdocument-sameorigin.html": [
+       "928d202045e3427afbd6462ce6cf4b3d58e6e244",
+       [
+        null,
+        {}
+       ]
+      ],
+      "click-samedocument-crossorigin-sameorigindomain.sub.html": [
+       "b7d284088081e846cadb65558bc7ed665bf6ce61",
+       [
+        null,
+        {}
+       ]
+      ],
+      "click-samedocument-crossorigin.html": [
+       "32bc4cd291ac64bea9dfb9c03b30dcb4202eeac6",
+       [
+        null,
+        {}
+       ]
+      ],
+      "click-samedocument-sameorigin.html": [
+       "fc815abb39eb182ad376ccfb55dc920f73522fd1",
+       [
+        null,
+        {}
+       ]
+      ],
+      "location-crossdocument-crossorigin-sameorigindomain.sub.html": [
+       "24824b12177d94b411cd0abff93bdb3b2d7a45fa",
+       [
+        null,
+        {}
+       ]
+      ],
+      "location-crossdocument-crossorigin.html": [
+       "79df86fdc9befb693a7db4e2bceda950373f620a",
+       [
+        null,
+        {}
+       ]
+      ],
+      "location-crossdocument-sameorigin.html": [
+       "1ac3eef874770ac61fd9acd8cc071eecc8c03991",
+       [
+        null,
+        {}
+       ]
+      ],
+      "location-samedocument-crossorigin-sameorigindomain.sub.html": [
+       "96032c14ac60a9365799fd8bfda9cdbe99f44a24",
+       [
+        null,
+        {}
+       ]
+      ],
+      "location-samedocument-crossorigin.html": [
+       "8a58be7a2b4120f64e8fdc1f8939b6cb3bc3e19e",
+       [
+        null,
+        {}
+       ]
+      ],
+      "location-samedocument-sameorigin.html": [
+       "3901b9ddbccc49323bfe4731700fa96634ccb6d0",
+       [
+        null,
+        {}
+       ]
+      ],
+      "open-crossdocument-crossorigin-sameorigindomain.sub.html": [
+       "d386e487a333c23692f21263ccfade40ac47dfa1",
+       [
+        null,
+        {}
+       ]
+      ],
+      "open-crossdocument-crossorigin.html": [
+       "73ede89cbf039e340e5000b6fd8df4fd16ca3070",
+       [
+        null,
+        {}
+       ]
+      ],
+      "open-crossdocument-sameorigin.html": [
+       "a899dd86a3a773dfc07d1d514444ac73f2e61c74",
+       [
+        null,
+        {}
+       ]
+      ],
+      "open-samedocument-crossorigin-sameorigindomain.sub.html": [
+       "b44303c18b8b8100880d22cb673454b1e3d5e4ec",
+       [
+        null,
+        {}
+       ]
+      ],
+      "open-samedocument-crossorigin.html": [
+       "65f062fcd578ba38c7c86d0dbbdeeba502cbd89d",
+       [
+        null,
+        {}
+       ]
+      ],
+      "open-samedocument-sameorigin.html": [
+       "3f3c38d85062d67dad5527d33be83783f1943cce",
+       [
+        null,
+        {}
+       ]
+      ],
+      "submit-crossdocument-crossorigin-sameorigindomain.sub.html": [
+       "36101a400041bcdcb0005b5cab4687f37bd4be87",
+       [
+        null,
+        {}
+       ]
+      ],
+      "submit-crossdocument-crossorigin.html": [
+       "007f58b1fbe473d4a8d5b5a3b0201ccfad93840d",
+       [
+        null,
+        {}
+       ]
+      ],
+      "submit-crossdocument-sameorigin.html": [
+       "93b97351d752cd063b3b7fc7ec6f68bfe0c7ef4b",
+       [
+        null,
+        {}
+       ]
+      ],
+      "submit-samedocument-crossorigin-sameorigindomain.sub.html": [
+       "9f5ba8b44afc346ca4f5a1c7817256763a0b307d",
+       [
+        null,
+        {}
+       ]
+      ],
+      "submit-samedocument-crossorigin.html": [
+       "51a313ac18147b3eaaa8ad37f1b893e749a9e64d",
+       [
+        null,
+        {}
+       ]
+      ],
+      "submit-samedocument-sameorigin.html": [
+       "f0b7b7393711a3a453954e675005a0e6a7ac518c",
+       [
+        null,
+        {}
+       ]
+      ]
+     },
+     "event-constructor.html": [
+      "501857feba4471575531965c865bcaf163d17985",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-anchor-cross-origin.html": [
+      "55ecbe749b72d9912aaf50cc69587995984b4eca",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-anchor-fragment.html": [
+      "b61edbe2c6f615e475a1b93b127a3678d669670e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-anchor-same-origin-cross-document.html": [
+      "ff5f0ad4b853be022e7d853fba6f531423dcfb1b",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-anchor-userInitiated.html": [
+      "c54f525ea58ae829c9d02d726abe7eb7ff765875",
+      [
+       null,
+       {
+        "testdriver": true
+       }
+      ]
+     ],
+     "navigate-anchor-with-target.html": [
+      "b2eb813cc467bbe98a124833c86cc6e7db417346",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-destination-getState-back-forward.html": [
+      "8fc0f2d12810a5b14dc7d028a33bcb77d6595b22",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-destination-getState-navigate.html": [
+      "9c34c5753a1197631cd1770284dfb012bb0192f6",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-form-get.html": [
+      "3c76accaabb3ad5c2bcf42d9965338c4f8ece74b",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-form-reload.html": [
+      "f18a11ebdaca671566db4722e537054dd7d315df",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-form-traverse.html": [
+      "d673537503e02f2440aabe55f303560c570c9e3b",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-form-userInitiated.html": [
+      "74fc7501dd7681be697de352c36119e37749993d",
+      [
+       null,
+       {
+        "testdriver": true
+       }
+      ]
+     ],
+     "navigate-form-with-target.html": [
+      "a02487ba7df5e49b900f75911bc26719e949263f",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-form.html": [
+      "3aee2d7ddd2e93bb0822581a5bc492d735548fa5",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-history-back-after-fragment.html": [
+      "31107fba2ff585de046b5cf64ede1795e5b0dccd",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-history-back-after-pushState.html": [
+      "0232517181a6b77cc8717db262c3662191dd2815",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-history-back-cross-document.html": [
+      "5d7c662caabb806932222e8ac1035f930ccea657",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-history-go-0.html": [
+      "6f7b277517656836813f4b92e16540524ce9b967",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-history-pushState.html": [
+      "5cd4ed8bccbd1c787f5e0e94ebb2732c573f8cf8",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-history-replaceState.html": [
+      "b5637d6c3c48220786bba75b83b8ed4dc44385f9",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-iframe-location.html": [
+      "e566cdd229dea62d6eb43734b29abd2bbfb96529",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-location.html": [
+      "e0c22d3299d3cc029a29ee2abe148bf18d47bf60",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-meta-refresh.html": [
+      "0f2994a9d9dcab3bd13665c9c04f3b90786a0129",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-navigation-back-cross-document.html": [
+      "a3641d5fcc829273ef7f680fa985b690b4fa073c",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-navigation-back-same-document.html": [
+      "22fe9fa02ec0749637175a91cca15528f6b2f761",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-navigation-navigate.html": [
+      "5629f765f69d9ec8e037be536fe0bad0d5dfee90",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-to-javascript.html": [
+      "78f490d87b95d6e1109d00e84ba320dc93a218c5",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-to-srcdoc.html": [
+      "344d130ca41d67d8617463f01d05cd245629b695",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-window-open-self.html": [
+      "4409cb2334c424e60e14b243a1b9bf61b31a361e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-window-open.html": [
+      "b0cac1821f7f886997e6a4c4d9f670d709445e5f",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigatesuccess-cross-document.html": [
+      "1d528c1f5f9154c1bc089315a0ae11d72b7d3820",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigatesuccess-same-document.html": [
+      "6007170ec153d94957b4f6bddd7f14a24b70357d",
+      [
+       null,
+       {}
+      ]
+     ],
+     "signal-abort-detach-in-onnavigate.html": [
+      "adef895565b9bc283cbc570323c617acf763104a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "signal-abort-preventDefault.html": [
+      "f281617866c0cd6a8e7e2a61ef6ba8e58cf38115",
+      [
+       null,
+       {}
+      ]
+     ],
+     "signal-abort-transitionWhile.html": [
+      "d90ee5b0837c41bd3bdce5f28284037e76aa0e0c",
+      [
+       null,
+       {}
+      ]
+     ],
+     "signal-abort-window-stop-after-transitionWhile.html": [
+      "aad3be34d7e80bc6e613f309f45089c180fb8fc4",
+      [
+       null,
+       {}
+      ]
+     ],
+     "signal-abort-window-stop-in-onnavigate.html": [
+      "dde666c93386175a50d08da6296a8d8121d73243",
+      [
+       null,
+       {}
+      ]
+     ],
+     "signal-abort-window-stop.html": [
+      "49399fb663777ea87b6c5b0686a1168557ed5e97",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-after-dispatch.html": [
+      "7f4a25cef528a1609731b2b1cc8c5139eee56a12",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-and-navigate.html": [
+      "f226371d79b35785de110a128914361785585a4b",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-canceled-event.html": [
+      "ed6dbdc85cfe0796e26fc519ca2b498fd0fdf526",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-cross-document-same-origin.html": [
+      "79e03f1c397c5d3bc6b5ee82695e188575400542",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-cross-origin.html": [
+      "ece79d263571838352ab74244df296a798708629",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-detach.html": [
+      "d6a7d536276b6af21a96de2b20d5abd05e544a7e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-history-pushState.html": [
+      "fb6152fc2289ffe74082307a439ca6de004832d1",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-history-replaceState.html": [
+      "00f85dcc0fcc958f4224cf8b3ae4f0b5a5a757f2",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-multiple-times-reject.html": [
+      "c325829001572dfd082c98d1b64fec117c3b12b4",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-multiple-times.html": [
+      "0f6caf16f1411e850522df207289c4f933b80b65",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-navigation-back.html": [
+      "a0bc453d36e3095fcb0d5c76f6fabb0c6ad3f612",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-on-synthetic-event.html": [
+      "21a32e7b4c93946f7c8a899a78d12133c2a2dfb9",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-reject.html": [
+      "2cdea05072911cf6883f90071547f20b5693c01a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-resolve.html": [
+      "2ee4fb39713cffe36724b85beda4cba9c0ca3287",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transitionWhile-same-document-history-back.html": [
+      "5e8ac39ef20ec07b78fd3ee58437dbe90a0a6a45",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
+    "navigation-history-entry": {
+     "after-detach.html": [
+      "c4ecfec44d69eaf7183691e69a1d21dd544d8ff7",
+      [
+       null,
+       {}
+      ]
+     ],
+     "current-basic.html": [
+      "78bbbb05607c3b8c5338d1c062c192936de69407",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-across-origins.html": [
+      "447273bff2c8447f40cdf038dea70165459f5064",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-after-bfcache-in-iframe.html": [
+      "b54a74995099eac8db164357c6309566707cfbc2",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-after-bfcache.html": [
+      "11bd7df931d56880163f42af33676ef76a0b74e9",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-after-blank-navigation.html": [
+      "6f6dbcc20df9bb8c0beb4921d84da359b8dbc01d",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-after-blob-navigation.html": [
+      "77c25174da2f87a04c9c642e0fa732717bc1937e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-after-cross-document-forward-pruning.html": [
+      "ddd1ad571ddb268b0ea4a147f77b3a08096f2fa6",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-after-javascript-url-navigation.html": [
+      "27a07c9e55d37c364595b393cf4282e425854f6a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-after-navigations-in-multiple-windows.html": [
+      "d1d4d86eb382cab6d9f538a6aa35cc51e64da5a0",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-after-srcdoc-navigation.html": [
+      "434b376224c6bd121c28e2667050f8fd263a4a04",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-array-equality.html": [
+      "98efb6b20c0a1ecb7f3c9fdcee9400656217a57b",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-in-new-javascript-url-iframe.html": [
+      "cd4279a7f31fb4264b01a372e62cad9fad94c660",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-in-new-srcdoc-iframe.html": [
+      "d261efd0778a787df0194fb40c59cd979680d01a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entries-when-inactive.html": [
+      "c70b6d8bf870fbbffc7214d14e436ad83613368b",
+      [
+       null,
+       {}
+      ]
+     ],
+     "entry-after-detach.html": [
+      "83962d2a8e146244720f687b5980fec6e5b0bc15",
+      [
+       null,
+       {}
+      ]
+     ],
+     "index-not-in-entries.html": [
+      "37cccd39d829be865485649762b91cc6687710af",
+      [
+       null,
+       {}
+      ]
+     ],
+     "key-id-back-cross-document.html": [
+      "f0dc182be41f27d5abae0b67e0d3fe38d06a135a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "key-id-back-same-document.html": [
+      "b5137f3e51bfcce5e29a72766f230eae1da328d7",
+      [
+       null,
+       {}
+      ]
+     ],
+     "key-id-location-reload-transitionWhile.html": [
+      "a06b645fa6f2f190cb91d4f8bb462a79c972db4e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "key-id-location-reload.html": [
+      "267906d2c38cc4cc65d3c56c8dca0c77bb551fff",
+      [
+       null,
+       {}
+      ]
+     ],
+     "key-id-location-replace-cross-origin.html": [
+      "ee0f749a9edc3435e454f96ef83a366494a1de99",
+      [
+       null,
+       {}
+      ]
+     ],
+     "key-id-location-replace.html": [
+      "ec96371e5648b2bc5e64802cdfc134ff18b18efa",
+      [
+       null,
+       {}
+      ]
+     ],
+     "no-referrer-dynamic-url-censored.html": [
+      "7a5544c4196be057e372f523c77e293de70c6292",
+      [
+       null,
+       {}
+      ]
+     ],
+     "no-referrer-from-meta-url-censored.html": [
+      "51eed0800fb02814ef90342bc2a87bb36b01c161",
+      [
+       null,
+       {}
+      ]
+     ],
+     "no-referrer-url-censored.html": [
+      "20397b8ef9c8af8bf2188257e9a57e687ef6694d",
+      [
+       null,
+       {}
+      ]
+     ],
+     "opaque-origin-data-url.html": [
+      "3275a30ef420f7220aacf85788e1ef9e3b486def",
+      [
+       null,
+       {}
+      ]
+     ],
+     "opaque-origin.html": [
+      "898ca27e4fab1d34bfe9c148ff53880a8a35d8cd",
+      [
+       null,
+       {}
+      ]
+     ],
+     "sameDocument-after-fragment-navigate.html": [
+      "acb67ee72c705563fe850b41f9aa166e634c3313",
+      [
+       null,
+       {}
+      ]
+     ],
+     "sameDocument-after-navigate-restore.html": [
+      "fd21bc222f4c5db9466b9fc56ffee5571b6e4680",
+      [
+       null,
+       {}
+      ]
+     ],
+     "sameDocument-after-navigate.html": [
+      "e1376a07f2550210be1304ae16a37df28ed3daa6",
+      [
+       null,
+       {}
+      ]
+     ],
+     "state-after-navigate-restore.html": [
+      "57fe38a426f1c4f3174b2eccaca8b0baaaff1ca5",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
+    "navigation-methods": {
+     "back-forward-multiple-frames.html": [
+      "738bfd37dcc720aa3c6b1b11208c5d8426860ece",
+      [
+       null,
+       {}
+      ]
+     ],
+     "disambigaute-back.html": [
+      "d44d435896dd8723677db8724478d862468e1cec",
+      [
+       null,
+       {}
+      ]
+     ],
+     "disambigaute-forward.html": [
+      "e252e309941037d42e3f3310c6ce9e140433db77",
+      [
+       null,
+       {}
+      ]
+     ],
+     "disambigaute-traverseTo-back-multiple.html": [
+      "03a8eb10edeb854bc3444136817256180b40709a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "disambigaute-traverseTo-forward-multiple.html": [
+      "f8e78c4b1d951d7c29f326bcdacbf1c688d3635c",
+      [
+       null,
+       {}
+      ]
+     ],
+     "forward-to-pruned-entry.html": [
+      "b8bd36ef061e8936b3dc1dbfdfb3766949cccc3d",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-base-url.html": [
+      "00fefa6608f27fe10f796f316882a628e5a0cb69",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-from-initial-about-blank-src.html": [
+      "2044b33cf027271bbedcff7462114c3b63e0d69b",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-from-initial-about-blank.html": [
+      "d494ec468cebf87a33ab9c5f1de6d0f3184a3879",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-history-state-replace.html": [
+      "f3dbce80bd1e6594fd1a712bc4b802a17a593cf4",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-history-state.html": [
+      "0541636de509c2055b1eee112322d989506de5d8",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-info-and-state.html": [
+      "828d7daea57bda8e00ad775f534e52a9e2ce6273",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-relative-url.html": [
+      "cc95d5e003826b0a309b647107a30d0054a01285",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-replace-cross-document.html": [
+      "02068d994e82747d5a5f72b1e70e7afcdae4b04d",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-replace-same-document.html": [
+      "cc2ea3ab13f54551adb688a873c4bc64052f6979",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-same-document.html": [
+      "9ffd8248f843bc42ec3f251bcf590d6d38ad2b22",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-state-repeated-await.html": [
+      "a003992d66a3b0065ff9382edef612d1636b43e0",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-state-repeated.html": [
+      "29c3f68eeb0caa33e0bec02527edcead675cde6f",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-transitionWhile-history-state.html": [
+      "c5e1036bff4fc7fa771d8d7cb91c0616a461f83e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "reload-base-url.html": [
+      "936ad75c832a13b3aff1f05d21fbf7e9a8f2f728",
+      [
+       null,
+       {}
+      ]
+     ],
+     "reload-info.html": [
+      "637f6976adbbefb08b678ec866cc25360e91f7b5",
+      [
+       null,
+       {}
+      ]
+     ],
+     "reload-navigation-timing.html": [
+      "ce03e7a7ca3db4670ed0053bcd57208c2657fa1c",
+      [
+       null,
+       {}
+      ]
+     ],
+     "reload-no-args.html": [
+      "c94eae0b935d3495c8acf239432a6b7926e50b4c",
+      [
+       null,
+       {}
+      ]
+     ],
+     "reload-service-worker-fetch-event.html": [
+      "67535c70bf79c4ed28a50c9e8ee5de384fac2161",
+      [
+       null,
+       {}
+      ]
+     ],
+     "reload-state-and-info.html": [
+      "3ee6a8b8fa0218767e4322e2e4014d12db68d93e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "reload-state-undefined.html": [
+      "ac204d4731b5d5784ca729b732caf23554b879fe",
+      [
+       null,
+       {}
+      ]
+     ],
+     "return-value": {
+      "back-already-detached.html": [
+       "f9ff04f923c80b3d224a11c155f87f391ad1fe2d",
+       [
+        null,
+        {}
+       ]
+      ],
+      "back-beforeunload.html": [
+       "82c1f589ccf58ad495715ad3fd29e5895619bf93",
+       [
+        null,
+        {}
+       ]
+      ],
+      "back-forward-initial-about-blank.html": [
+       "dfdb6611ab3f9c50b5c5aafe32acd96600850be9",
+       [
+        null,
+        {}
+       ]
+      ],
+      "back-forward-opaque-origin.html": [
+       "59849e961d76a0151959836870fb7eda47ce8867",
+       [
+        null,
+        {}
+       ]
+      ],
+      "back-forward-out-of-bounds.html": [
+       "015c090bf97e567ab3b4acd4f9858cd79fc9c725",
+       [
+        null,
+        {}
+       ]
+      ],
+      "back-transitionWhile-rejected.html": [
+       "fc171af54b986c2654ca26774fa0cfdf8f4a871d",
+       [
+        null,
+        {}
+       ]
+      ],
+      "back-transitionWhile.html": [
+       "c1161f895a54dc4b4002b775f6cadcceeb0ef2f0",
+       [
+        null,
+        {}
+       ]
+      ],
+      "back.html": [
+       "a2b13db901bfa11e438edbe89b15058b3be582e1",
+       [
+        null,
+        {}
+       ]
+      ],
+      "forward-already-detached.html": [
+       "4dfa74d9f9b9153f23ac36aeeafeb08316e902e4",
+       [
+        null,
+        {}
+       ]
+      ],
+      "forward-beforeunload.html": [
+       "87fa4baa935a7f1739b44db9a4685065f6d0a47c",
+       [
+        null,
+        {}
+       ]
+      ],
+      "forward-transitionWhile-rejected.html": [
+       "7ff0c70f0b9ae859cc9539ae4e74e028e3bbd3cd",
+       [
+        null,
+        {}
+       ]
+      ],
+      "forward-transitionWhile.html": [
+       "2b8175b619d61afee69099d710f833df398ce1aa",
+       [
+        null,
+        {}
+       ]
+      ],
+      "forward.html": [
+       "36bdba8daafd877b6ad274dfab8167110e524c03",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-already-detached.html": [
+       "33cdd6922d3f8eb90fde4cbf64cffd9c51d1fb8d",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-beforeunload.html": [
+       "f0a9069677f7cc63b6f33a6c9ac9555e7afe62dc",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-cross-document.html": [
+       "b83b1e941fd9c594adb174d9d0f2edc9cc7fcb7b",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-detach.html": [
+       "0e80e1b1305c3a257e69b81a7dbdbf2984392629",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-file-url.html": [
+       "138f6ffc124355ee9aa343e4b96097bfd0b9626f",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-initial-about-blank-cross-document.html": [
+       "8ec76b11ddb6b8c24b2c47cded029cb8a07ccea1",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-initial-about-blank.html": [
+       "9734a09d015127463af3b546e31ab5a2e92ee3b1",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-interrupted-within-onnavigate.html": [
+       "3db02c6931e41da69f8a7581ee65a5851d102cec",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-interrupted.html": [
+       "2c928254e0b64e8b5f3750cd366cf73e7edb2998",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-invalid-url.html": [
+       "5b5b442c9133016759d61ff9e7196161a0f48733",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-opaque-origin.html": [
+       "51500eb8d83c121241bbcf467e010fbda43daa55",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-preventDefault.html": [
+       "6257c5a03d6757345d1a29bd4e9115d14f6b847e",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-rejection-order-beforeunload-unserializablestate.html": [
+       "3039c92a08acd51da0dac300e4d3db1b62c18ae9",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-rejection-order-detached-unserializablestate.html": [
+       "f14df84aa3ae472390b0de855f36c517ebdc2047",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-rejection-order-invalidurl-beforeunload.html": [
+       "4873e85a2f8e05097ab2c8fbbd8e7cb30ed03068",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-rejection-order-invalidurl-detached.html": [
+       "2250a541147308c8c1116d21cc358b8e0bf4850a",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-rejection-order-invalidurl-unload.html": [
+       "d778dd662c8b80a74dd81c447dff40e537187e26",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-rejection-order-invalidurl-unserializablestate.html": [
+       "07e194ca4192cc7fbd95b2aa88fd4604cbf77d8f",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-rejection-order-unload-unserializablestate.html": [
+       "287434a7ca91a70d28fe690ef53cccb0c9092ace",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-transitionWhile-interrupted.html": [
+       "fa818b484d327c02f08762a52b0be3455985abcc",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-transitionWhile-rejected.html": [
+       "c1d4c4980251c6cc5d1a36052007657003a6acd5",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-transitionWhile.html": [
+       "2fe78e53e99f1cd59a0b2488cd60de00c0ffef17",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-unload.html": [
+       "fbc1fde6e97caed805432c9183260fb738e7b407",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate-unserializable-state.html": [
+       "36464ec3c54edfc92048a466c07f94735f725ede",
+       [
+        null,
+        {}
+       ]
+      ],
+      "navigate.html": [
+       "34ff84f0e9a17edce8b72d861176fb85a1d5787e",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-already-detached.html": [
+       "9b0780c4a9592c94a9ed101fd27ed8ef6413dd76",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-beforeunload.html": [
+       "05338acc5e7879f716b8367c3e0d3bca345c1d27",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-detach.html": [
+       "621bb598f64d6898ecde54d2bc5131f6c727617e",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-initial-about-blank.html": [
+       "9e717fee48b02c3ad71cd83f3170982e7dc57410",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-preventDefault.html": [
+       "747044b60f7baac2d792cbafeec02a18e1d80780",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-rejection-order-beforeunload-unserializablestate.html": [
+       "127f05f6d4f74f9353d52bac6a481d85886e1379",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-rejection-order-detached-unserializablestate.html": [
+       "0e00510d2ee4e5fa80d009f65e8977d7bebd854f",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-rejection-order-unload-unserializablestate.html": [
+       "85f69efcaebe65f5c56062465471a2a655e762dc",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-transitionWhile-rejected.html": [
+       "e6ec928a7368593ea2393fc395cc898716cf882c",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-transitionWhile.html": [
+       "19cea8afc28069cdc0657e01406bcf1fbda0416e",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-unload.html": [
+       "1906659d1db8968ff235e3758a8e14990e9eb825",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload-unserializable-state.html": [
+       "76fa870558a3f4055317c211ec7caf33406b3f17",
+       [
+        null,
+        {}
+       ]
+      ],
+      "reload.html": [
+       "028d5855701ad83f4ce5c56710fc6b750c79b9c9",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo-already-detached.html": [
+       "b974393df6e7445d020889398052fad5f3053543",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo-beforeunload.html": [
+       "3b2722235eecc6be300694bdd97b91a3eafb29ac",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo-cross-document-preventDefault.html": [
+       "09c91ee647ef7ffa3a9906faa4f1c14e0e830ab7",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo-current.html": [
+       "212fe992cf774cdcf5565f64ce0f4edf77b33c9f",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo-detach-cross-document.html": [
+       "8784313b70546e49d37e227ec9c0f809990b77d5",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo-detach-same-document.html": [
+       "b0308b8df8fe4c3ed975fe16b790c29a0de1891c",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo-invalid-key.html": [
+       "42be40bfa7679b44b1af1fb935f1925e79942ae3",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo-repeated.html": [
+       "d1754d6729749316216713cb63c3cc498c8292ad",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo-transitionWhile-rejected.html": [
+       "613531023fa4bc6455e359d59541d0602b8906d5",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo-transitionWhile.html": [
+       "430fbe8be79211be28b685e91733aab11a05f9ff",
+       [
+        null,
+        {}
+       ]
+      ],
+      "traverseTo.html": [
+       "8e00f5ba918cddc9d5a7df9336c3eefc05062e32",
+       [
+        null,
+        {}
+       ]
+      ]
+     },
+     "traverseTo-after-adding-iframe.html": [
+      "5ab28205510800eccc6fffed97b972929e1c0068",
+      [
+       null,
+       {}
+      ]
+     ],
+     "traverseTo-after-data-url.html": [
+      "0b21a741d3017b71fe26acd586faddbf3c5802b7",
+      [
+       null,
+       {}
+      ]
+     ],
+     "traverseTo-cross-document.html": [
+      "2540639baddd6cf9130e1f8fcfe76dd4d30852ef",
+      [
+       null,
+       {}
+      ]
+     ],
+     "traverseTo-detach-between-navigate-and-navigatesuccess.html": [
+      "9a9529578147b557c83a10f499227645b0ceb746",
+      [
+       null,
+       {}
+      ]
+     ],
+     "traverseTo-multiple-steps.html": [
+      "059eb214a8b8529779b6e0455220faca391a12d8",
+      [
+       null,
+       {}
+      ]
+     ],
+     "traverseTo-same-document.html": [
+      "1a32bce99c98e22ea426f612c26d6368adae4972",
+      [
+       null,
+       {}
+      ]
+     ],
+     "traverseTo-with-cross-origin-in-history.html": [
+      "a6b7745584a6458dca0f9722d1b793367fc94cdb",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
+    "ordering-and-transition": {
+     "back-same-document-transitionWhile-reject.html": [
+      "68cff8085d49fd8f5751d05e81088d828728fff6",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/back-same-document-transitionWhile-reject.html?currententrychange",
+       {}
+      ]
+     ],
+     "back-same-document-transitionWhile.html": [
+      "30d083c9e6dbb80b1f60401cc6ba2ddba92c6366",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/back-same-document-transitionWhile.html?currententrychange",
+       {}
+      ]
+     ],
+     "back-same-document.html": [
+      "cc303754119484bf3338e64fe7af8c3a000e50f1",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/back-same-document.html?currententrychange",
+       {}
+      ]
+     ],
+     "currententrychange-before-popstate-transitionWhile.html": [
+      "f4c36fa0ab21143fd69f4fe5079845d87231ad7f",
+      [
+       null,
+       {}
+      ]
+     ],
+     "currententrychange-dispose-ordering.html": [
+      "396a46ff969b2f3649b39850662bd37186bc0cd8",
+      [
+       null,
+       {}
+      ]
+     ],
+     "location-href-canceled.html": [
+      "f4247413d3aa2f325c920352c643857c0d09ca12",
+      [
+       null,
+       {}
+      ]
+     ],
+     "location-href-double-transitionWhile.html": [
+      "6e7c89502d60844035eea74f35530e81d63402cd",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/location-href-double-transitionWhile.html?currententrychange",
+       {}
+      ]
+     ],
+     "location-href-transitionWhile-reentrant.html": [
+      "36b23554ddb9566c4a7ec894a050944bcae0dea9",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/location-href-transitionWhile-reentrant.html?currententrychange",
+       {}
+      ]
+     ],
+     "location-href-transitionWhile-reject.html": [
+      "e3f65e81d44e0dc1fe53532953f40d4fc767ce6d",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/location-href-transitionWhile-reject.html?currententrychange",
+       {}
+      ]
+     ],
+     "location-href-transitionWhile.html": [
+      "0ae41447213ba7bb6e9ea8baf819715d0328c8e2",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/location-href-transitionWhile.html?currententrychange",
+       {}
+      ]
+     ],
+     "navigate-canceled.html": [
+      "851fa349937c207d97ada62d790d22d6a93f53b4",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-cross-document-double.html": [
+      "353cfa1b4e4b5fbc98a1f5827024ab8e73b98058",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-cross-document-event-order.html": [
+      "34a9b79fb5f01e074538e0bc228bd34a5818b263",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-double-transitionWhile.html": [
+      "4413ca4c35d93a33360d565c9fdaf40fa24fcc4b",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/navigate-double-transitionWhile.html?currententrychange",
+       {}
+      ]
+     ],
+     "navigate-in-transition-finished.html": [
+      "f8ebd6927322e5d360ad648a95995a972ecd9dbe",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/navigate-in-transition-finished.html?currententrychange",
+       {}
+      ]
+     ],
+     "navigate-same-document-transitionWhile-reentrant.html": [
+      "8cbb98e85819e20d742c2b1ac48b2fe33edce6a6",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/navigate-same-document-transitionWhile-reentrant.html?currententrychange",
+       {}
+      ]
+     ],
+     "navigate-same-document-transitionWhile-reject.html": [
+      "45ce700057053b7d7e873f6c21765e5c1ca0cc35",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/navigate-same-document-transitionWhile-reject.html?currententrychange",
+       {}
+      ]
+     ],
+     "navigate-same-document.html": [
+      "07f7bdce5d6ea10acd7801bcbed26deea01dbc98",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/navigate-same-document.html?currententrychange",
+       {}
+      ]
+     ],
+     "navigate-transitionWhile-stop.html": [
+      "9d06ba17969fa4b19db945589cf5a3933c4709e9",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/navigate-transitionWhile-stop.html?currententrychange",
+       {}
+      ]
+     ],
+     "navigate-transitionWhile.html": [
+      "d3125b0fc099877014eddfb401115f1a3833f248",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/navigate-transitionWhile.html?currententrychange",
+       {}
+      ]
+     ],
+     "reload-canceled.html": [
+      "a7ae7913c6107f89e79f9b964d3ae103bf657770",
+      [
+       null,
+       {}
+      ]
+     ],
+     "reload-transitionWhile-reject.html": [
+      "0a70c6deed4fdfe49edbe10d1956fa1d2adecf98",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/reload-transitionWhile-reject.html?currententrychange",
+       {}
+      ]
+     ],
+     "reload-transitionWhile.html": [
+      "8cc237f2dddafa98b4fcf19f9df54f35920f0a87",
+      [
+       null,
+       {}
+      ],
+      [
+       "navigation-api/ordering-and-transition/reload-transitionWhile.html?currententrychange",
+       {}
+      ]
+     ],
+     "transition-cross-document.html": [
+      "4a14a1083d4611911f4f03e60209d97643e6af38",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transition-finished-mark-as-handled.html": [
+      "859e07df139701977ccb9fa4fe7d38de2fff4f2f",
+      [
+       null,
+       {}
+      ]
+     ],
+     "transition-realms-and-identity.html": [
+      "16fb8184545e7df127dd581f61ab7e497771c474",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
+    "per-entry-events": {
+     "dispose-after-bfcache.html": [
+      "7d3ef4f81edbcb828ff2cef55d5b9e97403914c3",
+      [
+       null,
+       {}
+      ]
+     ],
+     "dispose-cross-document.html": [
+      "67f54da48d21252db1d08d71db557275fad14b75",
+      [
+       null,
+       {}
+      ]
+     ],
+     "dispose-same-document-navigate-during.html": [
+      "59d9b3cce3977ae255e25a2e96220ef92700c63c",
+      [
+       null,
+       {}
+      ]
+     ],
+     "dispose-same-document-reload-with-transitionWhile.html": [
+      "b55bfb4132923275c49185506ce203fffef4e8cf",
+      [
+       null,
+       {}
+      ]
+     ],
+     "dispose-same-document-replace-with-transitionWhile.html": [
+      "1931cd3ed13eb66a6551d9a7377c6dec3f8a29a5",
+      [
+       null,
+       {}
+      ]
+     ],
+     "dispose-same-document-replaceState.html": [
+      "a6197260a28b8137a142453fd669fbeb82b12680",
+      [
+       null,
+       {}
+      ]
+     ],
+     "dispose-same-document-transitionWhile.html": [
+      "2c0b92e7ad9a80c59863f4dd564581682abd9f8a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "dispose-same-document.html": [
+      "27806ce3c86a672701785be2453788e50b9b7388",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
+    "state": {
+     "cross-document-away-and-back.html": [
+      "25d0c1e79811cea7de5b747aab2066caaceb558e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "cross-document-location-api.html": [
+      "9c869ca1d276e465b29ae719a1e24a735e0e91b8",
+      [
+       null,
+       {}
+      ]
+     ],
+     "history-pushState.html": [
+      "1c8858c0af0c363bb12f74fbc1c76b5914264449",
+      [
+       null,
+       {}
+      ]
+     ],
+     "history-replaceState.html": [
+      "cc37949415eb8b29ac85ebd4efc8c6dd96616b8d",
+      [
+       null,
+       {}
+      ]
+     ],
+     "location-reload.html": [
+      "94d466434638ef5823dee86541a69349a4d5878c",
+      [
+       null,
+       {}
+      ]
+     ],
+     "same-document-away-and-back-location-api.html": [
+      "9cb6215089fdb56e2a241e927a8fb5366de94b62",
+      [
+       null,
+       {}
+      ]
+     ],
+     "same-document-away-and-back-navigation-api.html": [
+      "eed296d583afc722bf14f378e4b0641b64d1c16a",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
+    "updateCurrentEntry-method": {
+     "basic.html": [
+      "b4a49e5bf9ca672756cb972e09ceaf1f9a6c2d36",
+      [
+       null,
+       {}
+      ]
+     ],
+     "cross-document-away-and-back.html": [
+      "a192314050df0a2251a7b89d8eea4569862de7de",
+      [
+       null,
+       {}
+      ]
+     ],
+     "cross-document-location-api.html": [
+      "3b4ec68bae006db8149a323ffb542f7f7fbcbec8",
+      [
+       null,
+       {}
+      ]
+     ],
+     "exception-order-initial-about-blank-unserializablestate.html": [
+      "010632a40fcda3c98ff76965c61453919d6f42ba",
+      [
+       null,
+       {}
+      ]
+     ],
+     "exception-order-not-fully-active-unserializablestate.html": [
+      "1e1c1e2bae48315a5b895b1ed008b49e35c102a7",
+      [
+       null,
+       {}
+      ]
+     ],
+     "history-pushState.html": [
+      "73fb89f2a0f8d4daf76537038b1eb315cd1bb0b0",
+      [
+       null,
+       {}
+      ]
+     ],
+     "history-replaceState.html": [
+      "15472db2e777daf05d107998729b0b3f7f79035a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "initial-about-blank.html": [
+      "c28137c082a3b30fd9f575abd797c107108d0851",
+      [
+       null,
+       {}
+      ]
+     ],
+     "location-reload.html": [
+      "664ff1b23a5c2223e462c1c0feba2a991ba8044d",
+      [
+       null,
+       {}
+      ]
+     ],
+     "no-args.html": [
+      "3fd011e3d37335cacc2c02c3d6649e9a3dc22fd8",
+      [
+       null,
+       {}
+      ]
+     ],
+     "not-fully-active.html": [
+      "fce5e72c8d400e35ce42938dded89d5c837db77e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "opaque-origin.html": [
+      "898ca27e4fab1d34bfe9c148ff53880a8a35d8cd",
+      [
+       null,
+       {}
+      ]
+     ],
+     "same-document-away-and-back-location-api.html": [
+      "a1f54f5b7c21f1a8767c9ed2623bf819c7bad0d8",
+      [
+       null,
+       {}
+      ]
+     ],
+     "unserializable.html": [
+      "596ab16d621f5eb31049a251c8c591d5de504385",
+      [
+       null,
+       {}
+      ]
+     ]
+    }
+   },
    "navigation-timing": {
     "buffered-flag.window.js": [
      "c6b1e0bc8558a2908d6471fff0e7f5351d4a0022",
@@ -509558,6 +509688,15 @@
        }
       ]
      ],
+     "opt-out.html": [
+      "3bfa00c6dc8921b5888e4c24fbb2f11d0a903080",
+      [
+       null,
+       {
+        "timeout": "long"
+       }
+      ]
+     ],
      "restriction-focus.html": [
       "1149b8bd0981e8cf0109f2bca9dd974b9cd9a604",
       [
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-revoke.https.html b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-revoke.https.html
index 84a20b0..f0c3e5eb 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-revoke.https.html
+++ b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-revoke.https.html
@@ -13,7 +13,6 @@
     const provider = {
       url: provider_url || "https://idp.example/",
       clientId: "1234",
-      hint: "foo@bar.com",
     };
     return await navigator.credentials.get({
       federated:  {
@@ -24,17 +23,23 @@
 
   fedcm_test(async (t, mock) => {
     mock.revokeReturn("kSuccess");
-    await (await getCredential()).revoke();
+    await (await getCredential()).revoke("foo@bar.com");
   }, "Successfully revoking a token should resolve the promise.");
 
   fedcm_test(async (t, mock) => {
     mock.revokeReturn("kError");
-    const result = (await getCredential()).revoke();
+    const result = (await getCredential()).revoke("foo@bar.com");
     return promise_rejects_dom(t, "NetworkError", result);
   }, "Error should reject the promise.");
 
   fedcm_test(async (t, mock) => {
-    const result = getCredential("https://other-idp.example/").then((c) => c.revoke());
+    mock.revokeReturn("kError");
+    const result = (await getCredential()).revoke("");
+    return promise_rejects_dom(t, "InvalidStateError", result);
+  }, "Empty hint should reject the promise.");
+
+  fedcm_test(async (t, mock) => {
+    const result = getCredential("https://other-idp.example/").then((c) => c.revoke("foo@bar.com"));
     return promise_rejects_dom(t, "NetworkError", result);
   }, "Provider URL should honor Content-Security-Policy.");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-012.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-012.html
new file mode 100644
index 0000000..7552a741
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-012.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>
+  Tests that a flexbox expands its intrinsic block-size, due to a
+  flex item fragmenting.
+</title>
+<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#pagination">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width: 100px; height: 100px; columns: 2; column-gap: 0; column-fill: auto; background: red;">
+  <div style="display: flex; flex-direction: column; flex-wrap: wrap; background: green;">
+    <div style="display: flex; flex-wrap: wrap; flex-direction: column;">
+      <div style="line-height: 0;">
+        <div style="display: inline-block; width: 50px; height: 50px;"></div>
+        <div style="display: inline-block; width: 50px; height: 100px;"></div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-013.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-013.html
new file mode 100644
index 0000000..ed73994
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-013.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>
+  Tests that flex-items get pushed down due to a previous flex-item expanding as
+  a result of fragmentation.
+</title>
+<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#pagination">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width: 100px; height: 100px; columns: 2; column-gap: 0; column-fill: auto; background: red;">
+  <div style="display: flex; flex-direction: column; flex-wrap: wrap; height: 200px;">
+    <div style="line-height: 0; background: green; width: 25px;">
+      <div style="display: inline-block; width: 25px; height: 80px;"></div>
+      <div style="display: inline-block; width: 25px; height: 30px;"></div>
+    </div>
+    <div style="background: green; width: 25px; height: 50px;"></div>
+    <div style="background: green; width: 25px; height: 20px;"></div>
+    <div style="line-height: 0; background: green; width: 25px;">
+      <div style="display: inline-block; width: 25px; height: 30px;"></div>
+      <div style="display: inline-block; width: 25px; height: 80px;"></div>
+    </div>
+    <div style="background: green; width: 25px; height: 10px;"></div>
+    <div style="background: green; width: 25px; height: 10px;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-014.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-014.html
new file mode 100644
index 0000000..bcc32dd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-014.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>
+  Tests that flex-items *don't* get pushed down when there is no expansion.
+</title>
+<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#pagination">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width: 100px; height: 100px; columns: 2; column-gap: 0; column-fill: auto; background: red;">
+  <div style="display: flex; flex-direction: column; flex-wrap: wrap; height: 200px;">
+    <div style="line-height: 0; background: green; height: 110px; width: 25px;">
+      <div style="display: inline-block; width: 25px; height: 80px;"></div>
+      <div style="display: inline-block; width: 25px; height: 30px;"></div>
+    </div>
+    <div style="background: green; height: 90px; width: 25px;"></div>
+    <div style="line-height: 0; background: green; height: 110px; width: 25px;">
+      <div style="display: inline-block; width: 25px; height: 30px;"></div>
+      <div style="display: inline-block; width: 25px; height: 80px;"></div>
+    </div>
+    <div style="background: green; height: 90px; width: 25px;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-015.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-015.html
new file mode 100644
index 0000000..563f3663
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-015.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>
+  Tests that flex-items get pushed down due to a previous flex-item expanding as
+  a result of fragmentation.
+</title>
+<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#pagination">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width: 100px; height: 100px; columns: 5; column-gap: 0; column-fill: auto; background: red;">
+  <div style="display: flex; flex-direction: column; flex-wrap: wrap; height: 500px;">
+    <div style="background: green; width: 10px;">
+      <div style="contain: size; width: 10px; height: 60px;"></div>
+      <div style="contain: size; width: 25px; height: 50px;"></div>
+    </div>
+    <div style="background: green; order: -1; width: 10px;">
+      <div style="contain: size; width: 10px; height: 80px;"></div>
+      <div style="contain: size; width: 10px; height: 30px;"></div>
+    </div>
+    <div style="background: green; width: 10px;">
+      <div style="contain: size; width: 10px; height: 20px;"></div>
+      <div style="contain: size; width: 10px; height: 100px; background: white;"></div>
+    </div>
+    <div style="background: green; width: 10px;">
+      <div style="contain: size; width: 25px; height: 50px;"></div>
+      <div style="contain: size; width: 10px; height: 60px;"></div>
+    </div>
+    <div style="background: green; order: -1; width: 10px;">
+      <div style="contain: size; width: 10px; height: 30px;"></div>
+      <div style="contain: size; width: 10px; height: 100px;"></div>
+    </div>
+    <div style="background: green; width: 10px;">
+      <div style="contain: size; width: 10px; height: 20px;"></div>
+      <div style="contain: size; width: 10px; height: 100px;"></div>
+    </div>
+    <div style="background: green; width: 10px;">
+      <div style="contain: size; width: 10px; height: 40px;"></div>
+      <div style="contain: size; width: 10px; height: 20px;"></div>
+    </div>
+    <div style="background: green; width: 10px;">
+      <div style="contain: size; width: 10px; height: 20px;"></div>
+      <div style="contain: size; width: 10px; height: 100px;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-016.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-016.html
new file mode 100644
index 0000000..e81ad10
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-column-flex-fragmentation-016.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>
+  Tests that flex-items get pushed down due to a previous flex row expanding as
+  a result of fragmentation with margin-top.
+</title>
+<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#pagination">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<style>
+  #flex {
+    display: flex;
+    flex-direction: column;
+    flex-wrap: wrap;
+    height: 500px;
+  }
+  #flex > div {
+    background: green;
+    width: 10px;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width: 100px; height: 100px; columns: 5; column-gap: 0; column-fill: auto; background: red;">
+  <div id="flex">
+    <div>
+      <div style="contain: size; width: 10px; height: 80px;"></div>
+      <div style="contain: size; width: 10px; height: 30px;"></div>
+    </div>
+    <div style="position: relative;">
+      <div style="contain: size; width: 10px; height: 70px;"></div>
+      <div style="contain: size; width: 10px; height: 40px;"></div>
+      <div style="position: absolute; width: 10px; height: 60px; background: green;"></div>
+    </div>
+    <div style="margin-top: 10px;">
+      <div style="contain: size; width: 10px; height: 80px;"></div>
+      <div style="contain: size; width: 10px; height: 40px;"></div>
+    </div>
+    <div style="height: 100px;"></div>
+    <div style="height: 60px;"></div>
+    <div style="margin-top: 10px; position: relative;">
+      <div style="position: absolute; top: -10px; width: 10px; height: 10px; background: green;"></div>
+      <div style="contain: size; width: 10px; height: 30px;"></div>
+      <div style="contain: size; width: 10px; height: 80px;"></div>
+      <div style="position: absolute; width: 10px; height: 20px; background: green;"></div>
+    </div>
+    <div>
+      <div style="contain: size; width: 10px; height: 40px;"></div>
+      <div style="contain: size; width: 10px; height: 70px;"></div>
+    </div>
+    <div style="position: relative;">
+      <div style="contain: size; width: 10px; height: 30px;"></div>
+      <div style="contain: size; width: 10px; height: 80px;"></div>
+      <div style="contain: size; width: 10px; height: 40px;"></div>
+      <div style="position: absolute; bottom: 0px; left: -10px; width: 20px; height: 40px; background: white;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-016.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-016.html
index 7b690f1..fbd557c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-016.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-016.html
@@ -30,7 +30,7 @@
     <div style="margin-top: 10px; width: 20px; position: relative;">
       <div style="contain: size; width: 20px; height: 80px;"></div>
       <div style="contain: size; width: 20px; height: 40px;"></div>
-      <div style="position: absolute; top: -70px; width: 20px; height: 70px; background: green;"></div>
+      <div style="position: absolute; top: -60px; width: 20px; height: 60px; background: green;"></div>
     </div>
     <div style="height: 100px; width: 20px;"></div>
     <div style="height: 60px; width: 20px;"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-fieldset-004-ref.html b/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-fieldset-004-ref.html
index 198e8e1..59f7c7e2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-fieldset-004-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-fieldset-004-ref.html
@@ -26,7 +26,6 @@
   }
   legend, .innerContents {
     width: 0;
-    height: 0;
     padding: 0;
   }
   </style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/fieldset-baseline-alignment-ref.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/fieldset-baseline-alignment-ref.html
new file mode 100644
index 0000000..d68e033
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/fieldset-baseline-alignment-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<div style="display: block;">
+  baseline<fieldset style="display: inline-block;"><legend>legend</legend>content</fieldset>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/fieldset-baseline-alignment.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/fieldset-baseline-alignment.html
new file mode 100644
index 0000000..d3fdba3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/fieldset-baseline-alignment.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1305890">
+<link rel="match" href="fieldset-baseline-alignment-ref.html">
+<div style="display: flex; align-items: baseline;">
+  baseline<fieldset><legend>legend</legend>content</fieldset>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-baseline-ref.html b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-baseline-ref.html
new file mode 100644
index 0000000..ff58343
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-baseline-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<style>
+span {
+  border: solid 2px;
+  padding: 10px;
+  margin: 5px;
+}
+</style>
+<div>
+  text <span style="display: inline-block;">line1<br>line2</span>
+</div>
+<div>
+  text <span style="display: inline-flex;">line1<br>line2</span>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-baseline.html b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-baseline.html
new file mode 100644
index 0000000..23f5ad7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-baseline.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1305890">
+<link rel="match" href="fieldset-baseline-ref.html">
+<style>
+fieldset {
+  border: solid 2px;
+  padding: 10px;
+  margin: 5px;
+}
+</style>
+<div>
+  text <fieldset style="display: inline-block;">line1<br>line2</fieldset>
+</div>
+<div>
+  text <fieldset style="display: inline-flex;">line1<br>line2</fieldset>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/lint.ignore b/third_party/blink/web_tests/external/wpt/lint.ignore
index 08565dd..bc320dc 100644
--- a/third_party/blink/web_tests/external/wpt/lint.ignore
+++ b/third_party/blink/web_tests/external/wpt/lint.ignore
@@ -332,6 +332,7 @@
 SET TIMEOUT: scheduler/tentative/current-task-signal-async-abort.any.js
 SET TIMEOUT: scheduler/tentative/current-task-signal-async-priority.any.js
 SET TIMEOUT: speculation-rules/prerender/resources/activation-start.html
+SET TIMEOUT: speculation-rules/prerender/resources/prerender-response-code.html
 SET TIMEOUT: speculation-rules/prerender/resources/deferred-promise-utils.js
 SET TIMEOUT: speculation-rules/prerender/resources/utils.js
 SET TIMEOUT: html/browsers/browsing-the-web/back-forward-cache/timers.html
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/opt-out.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/opt-out.html
new file mode 100644
index 0000000..3bfa00c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/opt-out.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Check that non-successful responses result in discarding the prerender</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<body>
+<script>
+
+setup(() => assertSpeculationRulesIsSupported());
+function test_prerender_response_code(code, expectation) {
+  promise_test(async t => {
+    const {exec, tryToActivate} = await create_prerendered_page(t, {code});
+    const result = await tryToActivate();
+    assert_equals(result, expectation);
+  },`Responses with code ${code} should be ${expectation}`);
+}
+
+const expectations = {
+    activated: [200, 201, 202, 203],
+    discarded: [204, 205, 402, 404, 500, 503]
+};
+
+for (const expect in expectations) {
+    for (const code of expectations[expect])
+        test_prerender_response_code(code, expect);
+}
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/exec.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/exec.html
index 757dba1..80e0c59 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/exec.html
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/exec.html
@@ -3,6 +3,9 @@
   <script src="/common/utils.js"></script>
   <script src="/common/dispatcher/dispatcher.js"></script>
   <script>
-      new Executor(new URLSearchParams(window.location.search).get('uuid'));
+      const params = new URLSearchParams(window.location.search);
+      const uuid = params.get('uuid');
+      const discard_uuid = params.get('discard_uuid') || uuid;
+      new Executor(document.prerendering ? uuid : discard_uuid).execute();
   </script>
 </head>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/exec.py b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/exec.py
new file mode 100644
index 0000000..835a3f0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/exec.py
@@ -0,0 +1,13 @@
+from wptserve.utils import isomorphic_decode
+import os
+
+def main(request, response):
+    purpose = request.headers.get(b"purpose")
+    if (purpose == b'prefetch' and b"code" in request.GET):
+        code = int(request.GET.first(b"code"))
+    else:
+        code = 200
+
+    with open(os.path.join(os.path.dirname(isomorphic_decode(__file__)), "exec.html"), u"r") as fn:
+        response.content = fn.read()
+    response.status = code
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/prerender-response-code.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/prerender-response-code.html
new file mode 100644
index 0000000..c3a680bb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/prerender-response-code.html
@@ -0,0 +1,22 @@
+<head>
+<script src="/common/utils.js"></script>
+<script src="./utils.js"></script>
+<script>
+    const search = new URLSearchParams(location.search);
+    const uid = search.get('uid');
+    const uid1 = token();
+    const uid2 = token();
+    const bc = new BroadcastChannel(uid);
+
+    window.onload = async () => {
+        bc.addEventListener('message', ({data}) => {
+            if (data === 'close')
+                window.close();
+            else if (data === 'activate')
+                location.href = url;
+        })
+
+        startPrerendering(`/speculation-rules/prerender/resources/dual-exec.html?uid1=${uid1}&uid2=${uid2}`);
+    };
+</script>
+</head>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/utils.js b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/utils.js
index bbb9448..6952c3b 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/utils.js
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/utils.js
@@ -179,13 +179,21 @@
     });
 }
 
-async function create_prerendered_page(t) {
+async function create_prerendered_page(t, opt = {}) {
+  const baseUrl = '/speculation-rules/prerender/resources/exec.py';
   const init_uuid = token();
   const prerender_uuid = token();
+  const discard_uuid = token();
   const init_remote = new RemoteContext(init_uuid);
   const prerender_remote = new RemoteContext(prerender_uuid);
-  window.open(`/speculation-rules/prerender/resources/exec.html?uuid=${init_uuid}&init`, '_blank', 'noopener');
-  const url = `/speculation-rules/prerender/resources/exec.html?uuid=${prerender_uuid}&prerender`;
+  const discard_remote = new RemoteContext(discard_uuid);
+  window.open(`${baseUrl}?uuid=${init_uuid}&init`, '_blank', 'noopener');
+  const params = new URLSearchParams(baseUrl.search);
+  params.set('uuid', prerender_uuid);
+  params.set('discard_uuid', discard_uuid);
+  for (const p in opt)
+    params.set(p, opt[p]);
+  const url = `${baseUrl}?${params.toString()}`;
 
   await init_remote.execute_script(url => {
       const a = document.createElement('a');
@@ -198,37 +206,48 @@
       document.head.appendChild(rules);
   }, [url]);
 
-  await prerender_remote.execute_script(() => {
-      window.import_script_to_prerendered_page = src => {
-        const script = document.createElement('script');
-        script.src = src;
-        document.head.appendChild(script);
-        return new Promise(resolve => script.addEventListener('load', resolve));
-      }
-  });
+  await Promise.any([
+    prerender_remote.execute_script(() => {
+        window.import_script_to_prerendered_page = src => {
+            const script = document.createElement('script');
+            script.src = src;
+            document.head.appendChild(script);
+            return new Promise(resolve => script.addEventListener('load', resolve));
+        }
+    }), new Promise(r => t.step_timeout(r, 3000))
+    ]);
 
   t.add_cleanup(() => {
     init_remote.execute_script(() => window.close());
+    discard_remote.execute_script(() => window.close());
     prerender_remote.execute_script(() => window.close());
   });
 
-  async function activate() {
-    const prerendering = prerender_remote.execute_script(() => new Promise(resolve =>
-      document.addEventListener('prerenderingchange', () => {
-        resolve(document.prerendering);
-      })));
+  async function tryToActivate() {
+    const prerendering = prerender_remote.execute_script(() => new Promise(resolve => {
+        if (!document.prerendering)
+            resolve('activated');
+        else document.addEventListener('prerenderingchange', () => resolve('activated'));
+    }));
+
+    const discarded = discard_remote.execute_script(() => Promise.resolve('discarded'));
 
     init_remote.execute_script(url => {
-      location.href = url;
+        location.href = url;
     }, [url]);
+    return Promise.any([prerendering, discarded]);
+  }
 
-    if (await prerendering)
+  async function activate() {
+    const prerendering = await tryToActivate();
+    if (prerendering !== 'activated')
       throw new Error('Should not be prerendering at this point')
   }
 
   return {
     exec: (fn, args) => prerender_remote.execute_script(fn, args),
-    activate
+    activate,
+    tryToActivate
   };
 }
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/spectrum-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/spectrum-expected.txt
index 1bcba848..1c4229f 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/spectrum-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/spectrum-expected.txt
@@ -33,6 +33,14 @@
 hsl(1deg 100% 50% / 20%)
 Testing: hsL(1deg  100% 50%  /  20%)
 hsl(1deg 100% 50% / 20%)
+Testing: hwb(1 100% 50%)
+hwb(0deg 67% 33%)
+Testing: hwb(100grad 100% 50% / 0.2)
+hwb(0deg 67% 33% / 20%)
+Testing: hwb(1rad 20% 50% / 20%)
+hwb(57deg 20% 50% / 20%)
+Testing: hwB(1deg  20% 50%  /  20%)
+hwb(1deg 20% 50% / 20%)
 --- Testing alpha changes
 Testing: red
 #ff000000
@@ -66,6 +74,14 @@
 hsl(1deg 100% 50% / 0%)
 Testing: hsL(1deg  100% 50%  /  20%)
 hsl(1deg 100% 50% / 0%)
+Testing: hwb(1 100% 50%)
+hwb(0deg 67% 33% / 0%)
+Testing: hwb(100grad 100% 50% / 0.2)
+hwb(0deg 67% 33% / 0%)
+Testing: hwb(1rad 20% 50% / 20%)
+hwb(57deg 20% 50% / 0%)
+Testing: hwB(1deg  20% 50%  /  20%)
+hwb(1deg 20% 50% / 0%)
 --- Testing _formatViewSwitch()
 Testing: red
 rgb
@@ -84,35 +100,47 @@
 hsl
 Testing: rgb(1, 2, 3)
 hsl
-hex
+hwb
 Testing: rgba(1, 2, 3, 0.2)
 hsl
-hex
+hwb
 Testing: rgb(1, 2, 3, 0.2)
 hsl
-hex
+hwb
 Testing: rgb(1 2 3 / 20%)
 hsl
-hex
+hwb
 Testing: rgbA(1 2 3)
 hsl
-hex
+hwb
 Testing: rgba(1.5 2.6 3.1)
 hsl
-hex
+hwb
 Testing: hsl(1, 100%, 50%)
+hwb
 hex
-rgb
 Testing: hsl(1 100% 50%)
+hwb
 hex
-rgb
 Testing: hsla(1, 100%, 50%, 0.2)
+hwb
 hex
-rgb
 Testing: hsl(1 100% 50% / 20%)
+hwb
+hex
+Testing: hsL(1deg  100% 50%  /  20%)
+hwb
+hex
+Testing: hwb(1 100% 50%)
 hex
 rgb
-Testing: hsL(1deg  100% 50%  /  20%)
+Testing: hwb(100grad 100% 50% / 0.2)
+hex
+rgb
+Testing: hwb(1rad 20% 50% / 20%)
+hex
+rgb
+Testing: hwB(1deg  20% 50%  /  20%)
 hex
 rgb
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/spectrum.js b/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/spectrum.js
index 87a60c1f..02d7995 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/spectrum.js
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/spectrum.js
@@ -43,7 +43,9 @@
     {string: 'rgb(1 2 3 / 20%)', format: cf.RGB}, {string: 'rgbA(1 2 3)', format: cf.RGB},
     {string: 'rgba(1.5 2.6 3.1)', format: cf.RGB}, {string: 'hsl(1, 100%, 50%)', format: cf.HSL},
     {string: 'hsl(1 100% 50%)', format: cf.HSL}, {string: 'hsla(1, 100%, 50%, 0.2)', format: cf.HSLA},
-    {string: 'hsl(1 100% 50% / 20%)', format: cf.HSL}, {string: 'hsL(1deg  100% 50%  /  20%)', format: cf.HSL}
+    {string: 'hsl(1 100% 50% / 20%)', format: cf.HSL}, {string: 'hsL(1deg  100% 50%  /  20%)', format: cf.HSL},
+    {string: 'hwb(1 100% 50%)', format: cf.HWB}, {string: 'hwb(100grad 100% 50% / 0.2)', format: cf.HWB},
+    {string: 'hwb(1rad 20% 50% / 20%)', format: cf.HWB}, {string: 'hwB(1deg  20% 50%  /  20%)', format: cf.HWB}
   ];
 
   TestRunner.addResult('--- Testing colorString()');
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-invalid-color-values-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-invalid-color-values-expected.txt
index 51bb48f..96931e59 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-invalid-color-values-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-invalid-color-values-expected.txt
@@ -13,6 +13,7 @@
   shorthexa: #f00f
   rgb: rgb(255 0 0)
   hsl: hsl(0deg 100% 50%)
+  hwb: hwb(0deg 0% 0%)
 
 color: #F00
   simple: true
@@ -24,6 +25,7 @@
   shorthexa: #f00f
   rgb: rgb(255 0 0)
   hsl: hsl(0deg 100% 50%)
+  hwb: hwb(0deg 0% 0%)
 
 color: #F00F
   simple: true
@@ -35,6 +37,7 @@
   shorthexa: #F00F
   rgb: rgb(255 0 0)
   hsl: hsl(0deg 100% 50%)
+  hwb: hwb(0deg 0% 0%)
 
 color: #FF0000
   simple: true
@@ -46,6 +49,7 @@
   shorthexa: #f00f
   rgb: rgb(255 0 0)
   hsl: hsl(0deg 100% 50%)
+  hwb: hwb(0deg 0% 0%)
 
 color: #FF0000FF
   simple: true
@@ -57,6 +61,7 @@
   shorthexa: #f00f
   rgb: rgb(255 0 0)
   hsl: hsl(0deg 100% 50%)
+  hwb: hwb(0deg 0% 0%)
 
 color: rgb(255,0,0)
   simple: true
@@ -68,6 +73,7 @@
   shorthexa: #f00f
   rgb: rgb(255,0,0)
   hsl: hsl(0deg 100% 50%)
+  hwb: hwb(0deg 0% 0%)
 
 color: rgb(300,0,0)
   simple: true
@@ -79,6 +85,7 @@
   shorthexa: #f00f
   rgb: rgb(255 0 0)
   hsl: hsl(0deg 100% 50%)
+  hwb: hwb(0deg 0% 0%)
 
 color: rgb(255,-10,0)
   simple: true
@@ -90,6 +97,7 @@
   shorthexa: #f00f
   rgb: rgb(255 0 0)
   hsl: hsl(0deg 100% 50%)
+  hwb: hwb(0deg 0% 0%)
 
 color: rgb(110%, 0%, 0%)
   simple: true
@@ -101,6 +109,7 @@
   shorthexa: #f00f
   rgb: rgb(255 0 0)
   hsl: hsl(0deg 100% 50%)
+  hwb: hwb(0deg 0% 0%)
 
 color: rgba(0,0,0,0.5)
   simple: false
@@ -111,6 +120,7 @@
   rgba: rgba(0,0,0,0.5)
   hsl: hsl(0deg 0% 0% / 50%)
   hsla: hsl(0deg 0% 0% / 50%)
+  hwb: hwb(0deg 0% 100% / 50%)
 
 color: rgb(0,0,0,50%)
   simple: false
@@ -121,6 +131,7 @@
   rgba: rgb(0,0,0,50%)
   hsl: hsl(0deg 0% 0% / 50%)
   hsla: hsl(0deg 0% 0% / 50%)
+  hwb: hwb(0deg 0% 100% / 50%)
 
 color: rgb( 0  0   0   /   50%  )
   simple: false
@@ -131,6 +142,7 @@
   rgba: rgb( 0  0   0   /   50%  )
   hsl: hsl(0deg 0% 0% / 50%)
   hsla: hsl(0deg 0% 0% / 50%)
+  hwb: hwb(0deg 0% 100% / 50%)
 
 color: rgb(1 1 1/1)
   simple: true
@@ -140,6 +152,7 @@
   shorthexa: null
   rgb: rgb(1 1 1)
   hsl: hsl(0deg 0% 0%)
+  hwb: hwb(0deg 0% 100%)
 
 color: rgb(1 1 1/ 1)
   simple: true
@@ -149,6 +162,7 @@
   shorthexa: null
   rgb: rgb(1 1 1)
   hsl: hsl(0deg 0% 0%)
+  hwb: hwb(0deg 0% 100%)
 
 color: rgba(1.5 1.5 1.5)
   simple: true
@@ -158,6 +172,7 @@
   shorthexa: null
   rgb: rgba(1.5 1.5 1.5)
   hsl: hsl(0deg 0% 1%)
+  hwb: hwb(0deg 1% 99%)
 
 color: hsl(-120, 100%, 50%)
   simple: true
@@ -169,6 +184,7 @@
   shorthexa: #00ff
   rgb: rgb(0 0 255)
   hsl: hsl(-120, 100%, 50%)
+  hwb: hwb(240deg 0% 0%)
 
 color: hsl(-120deg, 100%, 50%)
   simple: true
@@ -180,6 +196,7 @@
   shorthexa: #00ff
   rgb: rgb(0 0 255)
   hsl: hsl(-120deg, 100%, 50%)
+  hwb: hwb(240deg 0% 0%)
 
 color: hsl(-120, 200%, 200%)
   simple: true
@@ -191,6 +208,7 @@
   shorthexa: #ffff
   rgb: rgb(255 255 255)
   hsl: hsl(-120, 200%, 200%)
+  hwb: hwb(0deg 100% 0%)
 
 color: hsl(-120, -200%, -200%)
   simple: true
@@ -202,6 +220,7 @@
   shorthexa: #000f
   rgb: rgb(0 0 0)
   hsl: hsl(0deg 0% 0%)
+  hwb: hwb(0deg 0% 100%)
 
 color: hsla(-120, -200%, -200%, -5)
   simple: false
@@ -213,6 +232,7 @@
   rgba: rgb(0 0 0 / 0%)
   hsl: hsl(0deg 0% 0% / 0%)
   hsla: hsl(0deg 0% 0% / 0%)
+  hwb: hwb(0deg 0% 100% / 0%)
 
 color: hsla(240,100%,50%,0.05)
   simple: false
@@ -223,6 +243,7 @@
   rgba: rgb(0 0 255 / 5%)
   hsl: hsl(240deg 100% 50% / 5%)
   hsla: hsla(240,100%,50%,0.05)
+  hwb: hwb(240deg 0% 0% / 5%)
 
 color: hsl(200.5,0%,50%)
   simple: true
@@ -233,6 +254,7 @@
   shorthexa: null
   rgb: rgb(128 128 128)
   hsl: hsl(200.5,0%,50%)
+  hwb: hwb(0deg 50% 50%)
 
 color: hsla(200,1.5%,50%,1)
   simple: true
@@ -242,6 +264,7 @@
   shorthexa: null
   rgb: rgb(126 128 129)
   hsl: hsl(200deg 1% 50%)
+  hwb: hwb(200deg 49% 49%)
 
 color: rgba(0,0,0,.5)
   simple: false
@@ -252,6 +275,7 @@
   rgba: rgba(0,0,0,.5)
   hsl: hsl(0deg 0% 0% / 50%)
   hsla: hsl(0deg 0% 0% / 50%)
+  hwb: hwb(0deg 0% 100% / 50%)
 
 color: hsla(.5,.5%,.5%,.5)
   simple: false
@@ -262,6 +286,7 @@
   rgba: rgb(1 1 1 / 50%)
   hsl: hsl(1deg 0% 1% / 50%)
   hsla: hsla(.5,.5%,.5%,.5)
+  hwb: hwb(1deg 0% 99% / 50%)
 
 color: hsla(100.5,50.5%,50.5%,.5)
   simple: false
@@ -272,6 +297,102 @@
   rgba: rgb(106 193 65 / 50%)
   hsl: hsl(101deg 50% 51% / 50%)
   hsla: hsla(100.5,50.5%,50.5%,.5)
+  hwb: hwb(101deg 26% 25% / 50%)
+
+color: hwb(-120 200% 200%)
+  simple: true
+  original: hwb(-120 200% 200%)
+  nickname: grey
+  hex: #808080
+  hexa: #808080ff
+  shorthexa: null
+  rgb: rgb(128 128 128)
+  hsl: hsl(0deg 0% 50%)
+  hwb: hwb(-120 200% 200%)
+
+color: hwb(-120 -200% -200%)
+  simple: true
+  original: hwb(-120 -200% -200%)
+  nickname: blue
+  hex: #0000ff
+  shorthex: #00f
+  hexa: #0000ffff
+  shorthexa: #00ff
+  rgb: rgb(0 0 255)
+  hsl: hsl(240deg 100% 50%)
+  hwb: hwb(240deg 0% 0%)
+
+color: hwb(-120 -200% -200% / -5)
+  simple: false
+  original: hwb(-120 -200% -200% / -5)
+  hexa: #0000ff00
+  shorthexa: #00f0
+  rgb: rgb(0 0 255 / 0%)
+  rgba: rgb(0 0 255 / 0%)
+  hsl: hsl(240deg 100% 50% / 0%)
+  hsla: hsl(240deg 100% 50% / 0%)
+  hwb: hwb(240deg 0% 0% / 0%)
+
+color: hwb(240 100% 50% / 0.05)
+  simple: false
+  original: hwb(240 100% 50% / 0.05)
+  hexa: #aaaaaa0d
+  shorthexa: null
+  rgb: rgb(170 170 170 / 5%)
+  rgba: rgb(170 170 170 / 5%)
+  hsl: hsl(0deg 0% 67% / 5%)
+  hsla: hsl(0deg 0% 67% / 5%)
+  hwb: hwb(0deg 67% 33% / 5%)
+
+color: hwb(200.5 0% 50%)
+  simple: true
+  original: hwb(200.5 0% 50%)
+  hex: #005480
+  hexa: #005480ff
+  shorthexa: null
+  rgb: rgb(0 84 128)
+  hsl: hsl(201deg 100% 25%)
+  hwb: hwb(200.5 0% 50%)
+
+color: hwb(200 1.5% 50% / 1)
+  simple: true
+  original: hwb(200 1.5% 50% / 1)
+  hex: #045680
+  hexa: #045680ff
+  shorthexa: null
+  rgb: rgb(4 86 128)
+  hsl: hsl(200deg 94% 26%)
+  hwb: hwb(200deg 2% 50%)
+
+color: hwb(100grad 20% 30%)
+  simple: true
+  original: hwb(100grad 20% 30%)
+  hex: #73b333
+  hexa: #73b333ff
+  shorthexa: null
+  rgb: rgb(115 179 51)
+  hsl: hsl(90deg 56% 45%)
+  hwb: hwb(100grad 20% 30%)
+
+color: hwb(1rad 5% 15%)
+  simple: true
+  original: hwb(1rad 5% 15%)
+  hex: #d9d00d
+  hexa: #d9d00dff
+  shorthexa: null
+  rgb: rgb(217 208 13)
+  hsl: hsl(57deg 89% 45%)
+  hwb: hwb(1rad 5% 15%)
+
+color: hwb(1turn 25% 15%)
+  simple: true
+  original: hwb(1turn 25% 15%)
+  hex: #d94040
+  hexa: #d94040ff
+  shorthexa: null
+  rgb: rgb(217 64 64)
+  hsl: hsl(0deg 67% 55%)
+  hwb: hwb(1turn 25% 15%)
 
 color: rgba(255, 0, 0, -5)
   simple: false
@@ -282,6 +403,7 @@
   rgba: rgb(255 0 0 / 0%)
   hsl: hsl(0deg 100% 50% / 0%)
   hsla: hsl(0deg 100% 50% / 0%)
+  hwb: hwb(0deg 0% 0% / 0%)
 
 color: rgba(255, 0, 0, 5)
   simple: true
@@ -293,6 +415,7 @@
   shorthexa: #f00f
   rgb: rgb(255 0 0)
   hsl: hsl(0deg 100% 50%)
+  hwb: hwb(0deg 0% 0%)
 
 Running: testInvalidColors
 
@@ -336,3 +459,17 @@
 
 SUCCESS: parsed invalid color hsla to null
 
+SUCCESS: parsed invalid color hwb(0,0,1) to null
+
+SUCCESS: parsed invalid color hwb(0 0% 0) to null
+
+SUCCESS: parsed invalid color hwb(a b c) to null
+
+SUCCESS: parsed invalid color hwb(0 0 0 / 0) to null
+
+SUCCESS: parsed invalid color hwb(0 0% 0% 0) to null
+
+SUCCESS: parsed invalid color hwb(0 turn 0 0 0) to null
+
+SUCCESS: parsed invalid color hwb to null
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-invalid-color-values.js b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-invalid-color-values.js
index b3b8085..0384ba5 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-invalid-color-values.js
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-invalid-color-values.js
@@ -23,7 +23,11 @@
     'hsla(-120, -200%, -200%, -5)',  // clipped to hsla(0,0%,0%,0)
     'hsla(240,100%,50%,0.05)', 'hsl(200.5,0%,50%)', 'hsla(200,1.5%,50%,1)', 'rgba(0,0,0,.5)', 'hsla(.5,.5%,.5%,.5)',
     'hsla(100.5,50.5%,50.5%,.5)',
-
+    'hwb(-120 200% 200%)',         // clipped to hwb(240 100% 100%) = hwb(0 50% 50%)
+    'hwb(-120 -200% -200%)',       // clipped to hwb(240 100% 100%)
+    'hwb(-120 -200% -200% / -5)',  // clipped to hwb(0 0% 0% / 0%)
+    'hwb(240 100% 50% / 0.05)', 'hwb(200.5 0% 50%)', 'hwb(200 1.5% 50% / 1)', 'hwb(0 0 0 /.5)', 'hwb(.5 .5% .5% .5)',
+    'hwb(100grad 20% 30%)', 'hwb(1rad 5% 15%)', 'hwb(1turn 25% 15%)',
     // Each of these has their alpha clipped [0.0, 1.0].
     'rgba(255, 0, 0, -5)',  // clipped to rgba(255,0,0,0)
     'rgba(255, 0, 0, 5)',   // clipped to rgba(255,0,0,1)
@@ -33,7 +37,8 @@
     // An invalid color, eg a value for a shorthand like 'border' which can have a color
     'none', '#00000', '#ggg', 'rgb(a,b,c)', 'rgb(a,b,c,d)', 'rgba(0 0 0 1%)', 'rgba(0,0,0,)',
     'rgba(0 0, 0)', 'rgba(1 1 1 / )', 'rgb(1 1 / 1)', 'rgb(1 1/1)', 'hsl(0,0,0)', 'hsl(0%, 0%, 0%)',
-    'hsla(0,,0,1)', 'hsl(0, 0%, 0)', 'hsl(a,b,c)', 'hsla(0,0,0,0)', 'hsla(0 0% 0% 0)', 'hsla(0 turn, 0, 0, 0)', 'hsla'
+    'hsla(0,,0,1)', 'hsl(0, 0%, 0)', 'hsl(a,b,c)', 'hsla(0,0,0,0)', 'hsla(0 0% 0% 0)', 'hsla(0 turn, 0, 0, 0)', 'hsla',
+    'hwb(0,0,1)', 'hwb(0 0% 0)', 'hwb(a b c)', 'hwb(0 0 0 / 0)', 'hwb(0 0% 0% 0)', 'hwb(0 turn 0 0 0)', 'hwb'
   ];
 
   TestRunner.runTestSuite([
@@ -74,6 +79,9 @@
       // Simple colors do not have RGBA and HSLA representations.
       if (!color.hasAlpha() && (colorFormat === cf.RGBA || colorFormat === cf.HSLA))
         continue;
+      // Skip alpha representation for HWB - only exists internally
+      if (colorFormat === cf.HWBA)
+        continue;
       // Advanced colors do not have HEX representations.
       if (color.hasAlpha() && (colorFormat === cf.ShortHEX || colorFormat === cf.HEX))
         continue;
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/encrypted-media.https.html b/third_party/blink/web_tests/wpt_internal/prerender/resources/encrypted-media.https.html
index 8bee303..332b399 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/resources/encrypted-media.https.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/resources/encrypted-media.https.html
@@ -1,8 +1,8 @@
 <!DOCTYPE html>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="utils.js"></script>
-<script src="deferred-promise-utils.js"></script>
+<script src="/speculation-rules/prerender/resources/utils.js"></script>
+<script src="/speculation-rules/prerender/resources/deferred-promise-utils.js"></script>
 <script>
 const params = new URLSearchParams(location.search);
 
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/media-device-info.https.html b/third_party/blink/web_tests/wpt_internal/prerender/resources/media-device-info.https.html
index 90cf9bb..09e85e7 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/resources/media-device-info.https.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/resources/media-device-info.https.html
@@ -2,8 +2,8 @@
 <script src="/common/utils.js"></script>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="utils.js"></script>
-<script src="deferred-promise-utils.js"></script>
+<script src="/speculation-rules/prerender/resources/utils.js"></script>
+<script src="/speculation-rules/prerender/resources/deferred-promise-utils.js"></script>
 <script>
 
 const params = new URLSearchParams(location.search);
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/media-devices-access.https.html b/third_party/blink/web_tests/wpt_internal/prerender/resources/media-devices-access.https.html
index c51265f..462ce53e 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/resources/media-devices-access.https.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/resources/media-devices-access.https.html
@@ -1,8 +1,8 @@
 <!DOCTYPE html>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="utils.js"></script>
-<script src="deferred-promise-utils.js"></script>
+<script src="/speculation-rules/prerender/resources/utils.js"></script>
+<script src="/speculation-rules/prerender/resources/deferred-promise-utils.js"></script>
 
 <script>
 const params = new URLSearchParams(location.search);
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-encrypted-media-unsupported-config.https.html b/third_party/blink/web_tests/wpt_internal/prerender/restriction-encrypted-media-unsupported-config.https.html
index 1a5598b..c1cea87 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/restriction-encrypted-media-unsupported-config.https.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/restriction-encrypted-media-unsupported-config.https.html
@@ -4,12 +4,14 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/speculation-rules/prerender/resources/utils.js"></script>
 <body>
 <script>
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('test-channel');
+  const uid = token();
+  const bc = new PrerenderChannel('test-channel', uid);
   t.add_cleanup(_ => bc.close());
 
   const gotMessage = new Promise(resolve => {
@@ -19,7 +21,7 @@
       once: true
     });
   });
-  const url = `resources/encrypted-media.https.html?config=unsupport`;
+  const url = `resources/encrypted-media.https.html?config=unsupport&uid=${uid}`;
   window.open(url, '_blank', 'noopener');
 
   const result = await gotMessage;
@@ -44,6 +46,9 @@
     assert_equals(result[i].prerendering, expected[i].prerendering,
       `prerendering${i}`);
   }
+
+  // Send a close signal to PrerenderEventCollector on the prerendered page.
+  new PrerenderChannel('close', uid).postMessage('');
 }, `the access to the Encrypted Media API should be deferred with the
     unsupported configurations until the prerendered page is activated`);
 
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-encrypted-media.https.html b/third_party/blink/web_tests/wpt_internal/prerender/restriction-encrypted-media.https.html
index ba825c6..304d625f 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/restriction-encrypted-media.https.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/restriction-encrypted-media.https.html
@@ -3,12 +3,14 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/speculation-rules/prerender/resources/utils.js"></script>
 <body>
 <script>
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('test-channel');
+  const uid = token();
+  const bc = new PrerenderChannel('test-channel', uid);
   t.add_cleanup(_ => bc.close());
 
   const gotMessage = new Promise(resolve => {
@@ -18,7 +20,7 @@
       once: true
     });
   });
-  const url = `resources/encrypted-media.https.html?config=support`;
+  const url = `resources/encrypted-media.https.html?config=support&uid=${uid}`;
   window.open(url, '_blank', 'noopener');
 
   const result = await gotMessage;
@@ -42,6 +44,9 @@
     assert_equals(result[i].prerendering, expected[i].prerendering,
       `prerendering${i}`);
   }
+
+  // Send a close signal to PrerenderEventCollector on the prerendered page.
+  new PrerenderChannel('close', uid).postMessage('');
 }, `the access to the Encrypted Media API should be deferred until the
     prerendered page is activated`);
 
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-camera.https.html b/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-camera.https.html
index d82bc703..dda9eca 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-camera.https.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-camera.https.html
@@ -1,14 +1,16 @@
 <!DOCTYPE html>
 <title>Access to the Camera of the user media device is deferred</title>
 <meta name="timeout" content="long">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="resources/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/speculation-rules/prerender/resources/utils.js"></script>
 <body>
 <script>
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('test-channel');
+  const uid = token();
+  const bc = new PrerenderChannel('test-channel', uid);
   t.add_cleanup(_ => bc.close());
 
   const gotMessage = new Promise(resolve => {
@@ -18,7 +20,7 @@
       once: true
     });
   });
-  const url = `resources/media-devices-access.https.html?video=true`;
+  const url = `resources/media-devices-access.https.html?video=true&uid=${uid}`;
   window.open(url, '_blank', 'noopener');
   // According to spec, gUM will resolve only if the window has focus.
   window.focus();
@@ -44,6 +46,9 @@
     assert_equals(result[i].prerendering, expected[i].prerendering,
       `prerendering${i}`);
   }
+
+  // Send a close signal to PrerenderEventCollector on the prerendered page.
+  new PrerenderChannel('close', uid).postMessage('');
 }, `the access to the camera of the user media should be deferred until the
     prerendered page is activated`);
 
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-device-info.https.html b/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-device-info.https.html
index f49f6c37..d50792e3 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-device-info.https.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-device-info.https.html
@@ -3,12 +3,14 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/speculation-rules/prerender/resources/utils.js"></script>
 <body>
 <script>
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('test-channel');
+  const uid = token();
+  const bc = new PrerenderChannel('test-channel', uid);
 
   const gotMessage = new Promise(resolve => {
     bc.addEventListener('message', e => {
@@ -17,7 +19,7 @@
       once: true
     });
   });
-  const url = `resources/media-device-info.https.html`;
+  const url = `resources/media-device-info.https.html?uid=${uid}`;
   window.open(url, '_blank', 'noopener');
 
   const result = await gotMessage;
@@ -42,6 +44,9 @@
       `prerendering[${i}]`);
   }
   bc.close();
+
+  // Send a close signal to PrerenderEventCollector on the prerendered page.
+  new PrerenderChannel('close', uid).postMessage('');
 }, `the access to the Media Device Info should be deferred until the prerendered
     page is activated`);
 
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-microphone.https.html b/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-microphone.https.html
index 5c638042..202840c 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-microphone.https.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/restriction-media-microphone.https.html
@@ -1,14 +1,16 @@
 <!DOCTYPE html>
 <title>Access to the Microphone of the user media device is deferred</title>
 <meta name="timeout" content="long">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="resources/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/speculation-rules/prerender/resources/utils.js"></script>
 <body>
 <script>
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('test-channel');
+  const uid = token();
+  const bc = new PrerenderChannel('test-channel', uid);
   t.add_cleanup(_ => bc.close());
 
   const gotMessage = new Promise(resolve => {
@@ -18,7 +20,7 @@
       once: true
     });
   });
-  const url = `resources/media-devices-access.https.html?&audio=true`;
+  const url = `resources/media-devices-access.https.html?&audio=true&uid=${uid}`;
   window.open(url, '_blank', 'noopener');
   // According to spec, gUM will resolve only if the window has focus.
   window.focus();
@@ -44,6 +46,9 @@
     assert_equals(result[i].prerendering, expected[i].prerendering,
       `prerendering${i}`);
   }
+
+  // Send a close signal to PrerenderEventCollector on the prerendered page.
+  new PrerenderChannel('close', uid).postMessage('');
 }, `the access to the Microphone of the user media should be deferred until the
     prerendered page is activated`);
 
diff --git a/third_party/dav1d/dav1d_generated.gni b/third_party/dav1d/dav1d_generated.gni
index 9e48e42..478811d2 100644
--- a/third_party/dav1d/dav1d_generated.gni
+++ b/third_party/dav1d/dav1d_generated.gni
@@ -12,6 +12,7 @@
   "libdav1d/src/x86/cdef_sse.asm",
   "libdav1d/src/x86/cpuid.asm",
   "libdav1d/src/x86/filmgrain16_avx2.asm",
+  "libdav1d/src/x86/filmgrain16_avx512.asm",
   "libdav1d/src/x86/filmgrain16_sse.asm",
   "libdav1d/src/x86/filmgrain_avx2.asm",
   "libdav1d/src/x86/filmgrain_avx512.asm",
diff --git a/third_party/dav1d/version/vcs_version.h b/third_party/dav1d/version/vcs_version.h
index 7f9d753..88548ec 100644
--- a/third_party/dav1d/version/vcs_version.h
+++ b/third_party/dav1d/version/vcs_version.h
@@ -1,2 +1,2 @@
 /* auto-generated, do not edit */
-#define DAV1D_VERSION "0.9.2-167-g493ffb1"
+#define DAV1D_VERSION "0.9.2-172-gb1a5189"
diff --git a/third_party/libdrm/BUILD.gn b/third_party/libdrm/BUILD.gn
index 8c1054b..b72d3ef 100644
--- a/third_party/libdrm/BUILD.gn
+++ b/third_party/libdrm/BUILD.gn
@@ -3,6 +3,20 @@
 # found in the LICENSE file.
 assert(is_linux || is_chromeos)
 
+generated_static_table_fourcc_file =
+    "$target_gen_dir/src/generated_static_table_fourcc.h"
+fourcc_file = "src/include/drm/drm_fourcc.h"
+
+action("make_generated_static_table_fourcc") {
+  script = "src/gen_table_fourcc.py"
+  args = [
+    rebase_path(fourcc_file, root_build_dir),
+    rebase_path(generated_static_table_fourcc_file),
+  ]
+  outputs = [ generated_static_table_fourcc_file ]
+  inputs = [ fourcc_file ]
+}
+
 config("libdrm_config") {
   # TODO(thomasanderson): Remove this hack once
   # https://patchwork.kernel.org/patch/10545295/ lands.
@@ -29,6 +43,9 @@
 
       # TODO(crbug.com/932060) fix unused result from asprintf in modetest.c.
       "-Wno-unused-result",
+
+      # modetest.c has an improper conversion in a printf statement.
+      "-Wno-format",
     ]
   }
 }
@@ -41,7 +58,10 @@
     "src/xf86drmRandom.c",
   ]
 
+  deps = [ ":make_generated_static_table_fourcc" ]
+
   include_dirs = [
+    get_path_info(generated_static_table_fourcc_file, "dir"),
     "src",
     "src/include",
   ]
@@ -80,5 +100,7 @@
 
   configs -= [ "//build/config/compiler:chromium_code" ]
   configs += [ "//build/config/compiler:no_chromium_code" ]
+  configs += [ ":libdrm_config" ]
+
   deps = [ ":libdrm" ]
 }
diff --git a/third_party/libdrm/README.chromium b/third_party/libdrm/README.chromium
index ea366011..570afa8 100644
--- a/third_party/libdrm/README.chromium
+++ b/third_party/libdrm/README.chromium
@@ -1,7 +1,7 @@
 Name: libdrm
 Short Name: libdrm
 URL: https://chromium.googlesource.com/chromiumos/third_party/libdrm
-Version: 2.4.85
+Version: 2.4.110
 License: MIT, GPL
 License File: NOT_SHIPPED
 Security Critical: yes
diff --git a/tools/make_gtest_filter.py b/tools/make_gtest_filter.py
index 26b0273a..c947137 100755
--- a/tools/make_gtest_filter.py
+++ b/tools/make_gtest_filter.py
@@ -172,11 +172,13 @@
   parser.add_argument('--wildcard-compress', action='store_true')
   parser.add_argument(
       '--wildcard-min-depth',
+      type=int,
       default=1,
       help="Minimum number of terms in a case before a wildcard may be " +
       "used, so that prefixes are not excessively broad.")
   parser.add_argument(
       '--wildcard-min-cases',
+      type=int,
       default=3,
       help="Minimum number of cases in a filter before folding into a " +
       "wildcard, so as to not create wildcards needlessly for small "
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 5f14f0c..cfb6db8 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -8606,6 +8606,9 @@
   <int value="266" label="BIBI_BIND_GAMEPAD_HAPTICS_MANAGER_FOR_FENCED_FRAME"/>
   <int value="267" label="BIBI_BIND_BATTERY_MONITOR_FOR_FENCED_FRAME"/>
   <int value="268" label="RFH_CREATE_FENCED_FRAME_IN_SANDBOXED_FRAME"/>
+  <int value="269" label="RFH_UNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME"/>
+  <int value="270"
+      label="RFH_BEFOREUNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions">
@@ -53754,6 +53757,7 @@
   <int value="-634122679" label="GoogleBrandedContextMenu:enabled"/>
   <int value="-634116286"
       label="OmniboxUIExperimentUnboldSuggestionText:enabled"/>
+  <int value="-633955136" label="ShowScrollableMVTOnNTPAndroid:disabled"/>
   <int value="-633274640" label="WebRTCPipeWireCapturer:enabled"/>
   <int value="-632030508" label="NativeWindowNavButtons:disabled"/>
   <int value="-631740127" label="inert-visual-viewport"/>
@@ -55700,6 +55704,7 @@
   <int value="730024226" label="enable-out-of-process-pdf"/>
   <int value="730750097" label="PermissionsBlacklist:disabled"/>
   <int value="731318054" label="WebAssemblySimd:enabled"/>
+  <int value="731593144" label="ReadPrinterCapabilitiesWithXps:enabled"/>
   <int value="731757746" label="ArcEnableDocumentsProviderInFilesApp:disabled"/>
   <int value="731775657" label="DownloadsAutoResumptionNative:disabled"/>
   <int value="731779469" label="BlinkHeapUnifiedGarbageCollection:enabled"/>
@@ -55966,6 +55971,7 @@
   <int value="921536672" label="OfflinePagesDescriptiveFailStatus:enabled"/>
   <int value="921561616" label="WebAssemblyTiering:disabled"/>
   <int value="923923308" label="WifiSyncAllowDeletes:enabled"/>
+  <int value="923947923" label="ReadPrinterCapabilitiesWithXps:disabled"/>
   <int value="924769517" label="AppServiceAdaptiveIcon:disabled"/>
   <int value="925712999" label="V8Orinoco:enabled"/>
   <int value="926844887" label="QuickDim:disabled"/>
@@ -56732,6 +56738,7 @@
   <int value="1455881930" label="V8VmFuture:enabled"/>
   <int value="1455970119" label="MacCoreLocationBackend:disabled"/>
   <int value="1457465866" label="TouchToFillPasswordSubmission:disabled"/>
+  <int value="1458041527" label="ShowScrollableMVTOnNTPAndroid:enabled"/>
   <int value="1458085218" label="MultiDisplayOverviewAndSplitView:disabled"/>
   <int value="1458255488" label="BlinkGenPropertyTrees:enabled"/>
   <int value="1458475849" label="D3D11VideoDecoder:disabled"/>
@@ -80653,6 +80660,11 @@
   <int value="0" label="Success"/>
   <int value="1" label="ExecutionError"/>
   <int value="2" label="InvalidMetadata"/>
+  <int value="3" label="SkippedModelNotReady"/>
+  <int value="4" label="SkippedHasFreshResults"/>
+  <int value="5" label="SkippedNotEnoughSignals"/>
+  <int value="6" label="SkippedResultNotExpired"/>
+  <int value="7" label="FailedToSaveResultAfterSuccess"/>
 </enum>
 
 <enum name="SegmentationPlatformSegmentationModel">
@@ -80689,9 +80701,12 @@
   <int value="3"
       label="At least one segment's signal collection is not complete"/>
   <int value="4" label="Segment selection TTL has not expired"/>
-  <int value="5" label="At least one model failed to execute"/>
-  <int value="6" label="At least one model needs more signals"/>
-  <int value="7" label="At least one model has invalid metadata"/>
+  <int value="5"
+      label="Deprecated since M101. At least one model failed to execute"/>
+  <int value="6"
+      label="Deprecated since M101. At least one model needs more signals"/>
+  <int value="7"
+      label="Deprecated since M101. At least one model has invalid metadata"/>
   <int value="8" label="Failed to write model result to DB"/>
   <int value="9" label="Invalid selection result found in prefs"/>
   <int value="10" label="Database initialization failed"/>
@@ -94528,6 +94543,18 @@
   <int value="3" label="Transferred WebContents"/>
 </enum>
 
+<enum name="WebDriveOfficeTaskResult">
+  <int value="0" label="Available"/>
+  <int value="1" label="Flag disabled"/>
+  <int value="2" label="Offline"/>
+  <int value="3" label="Not on Drive"/>
+  <int value="4" label="DriveFs interface error"/>
+  <int value="5" label="File metadata error"/>
+  <int value="6" label="Not uploaded - Empty or invalid alternate URL"/>
+  <int value="7" label="Not available for Web Drive editing"/>
+  <int value="8" label="Unexpected alternate URL"/>
+</enum>
+
 <enum name="WebFeedPageInformationRequestReason">
   <int value="0" label="User Requested Follow">
     The user requested to Follow the current web page.
diff --git a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
index 7dffdfa..60456b1 100644
--- a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+++ b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
@@ -47,7 +47,6 @@
 kron@chromium.org
 lizeb@chromium.org
 lyf@chromium.org
-maxlg@chromium.org
 manukh@chromium.org
 mcrouse@chromium.org
 mhasank@chromium.org
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index 11966b3..e86f8dc 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -1601,7 +1601,7 @@
 
 <histogram
     name="Autofill.IsValueNotAutofilledOverExistingValueSameAsSubmittedValue"
-    units="bool" expires_after="M105">
+    enum="Boolean" expires_after="M105">
   <owner>vidhanj@google.com</owner>
   <owner>koerber@google.com</owner>
   <owner>chrome-autofill-alerts@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml b/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
index 1db53349..8371060 100644
--- a/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
@@ -150,9 +150,9 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.LoadCompletedTime" units="ms"
-    expires_after="2022-04-01">
-  <owner>khorimoto@chromium.org</owner>
-  <owner>cros-system-services@google.com</owner>
+    expires_after="2023-04-01">
+  <owner>xiaohuic@chromium.org</owner>
+  <owner>assistive-eng@google.com</owner>
   <summary>
     The amount of time between the render frame host StartProvisionalLoad event
     and the render frame DocumentOnLoadCompleted event for the Chrome OS
@@ -161,9 +161,9 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.LoadDocumentTime" units="ms"
-    expires_after="2022-04-01">
-  <owner>khorimoto@chromium.org</owner>
-  <owner>cros-system-services@google.com</owner>
+    expires_after="2023-04-01">
+  <owner>xiaohuic@chromium.org</owner>
+  <owner>assistive-eng@google.com</owner>
   <summary>
     The amount of time between the render frame host StartProvisionalLoad and
     DidFinishDocumentLoad events for the Chrome OS settings page.
@@ -239,9 +239,9 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.PathVisited" enum="WebUISettingsPathHashes"
-    expires_after="2022-04-10">
-  <owner>khorimoto@chromium.org</owner>
-  <owner>cros-customization@google.com</owner>
+    expires_after="2023-04-10">
+  <owner>xiaohuic@chromium.org</owner>
+  <owner>assistive-eng@google.com</owner>
   <summary>
     Paths visited within chrome://os-settings. For evaluating popularity and
     priorities for OS Settings UI.
@@ -282,10 +282,9 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.SearchRequests"
-    enum="OsSettingSearchRequestTypes" expires_after="2022-04-17">
-  <owner>khorimoto@chromium.org</owner>
-  <owner>hsuregan@chromium.org</owner>
-  <owner>cros-customization@google.com</owner>
+    enum="OsSettingSearchRequestTypes" expires_after="2023-04-17">
+  <owner>xiaohuic@chromium.org</owner>
+  <owner>assistive-eng@google.com</owner>
   <summary>
     The number of search requests made to the Settings Search Mojo API. For
     search requests that succeeded with a response, the number of search
@@ -296,10 +295,9 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.SearchRequestsPerSession"
-    units="mojo search requests" expires_after="2022-09-01">
-  <owner>khorimoto@chromium.org</owner>
-  <owner>hsuregan@chromium.org</owner>
-  <owner>cros-customization@google.com</owner>
+    units="mojo search requests" expires_after="2023-04-17">
+  <owner>xiaohuic@chromium.org</owner>
+  <owner>assistive-eng@google.com</owner>
   <summary>
     The number of search requests made to the Settings Search Mojo API in one
     session of the settings app.
@@ -307,40 +305,36 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.SearchResultSectionSelected"
-    enum="OsSettingsSection" expires_after="2022-04-17">
-  <owner>khorimoto@chromium.org</owner>
-  <owner>hsuregan@chromium.org</owner>
-  <owner>cros-customization@google.com</owner>
+    enum="OsSettingsSection" expires_after="2023-04-17">
+  <owner>xiaohuic@chromium.org</owner>
+  <owner>assistive-eng@google.com</owner>
   <summary>
     Section search results clicked by user in the OS settings search box.
   </summary>
 </histogram>
 
 <histogram name="ChromeOS.Settings.SearchResultSettingSelected"
-    enum="OsSetting" expires_after="2022-09-01">
-  <owner>khorimoto@chromium.org</owner>
-  <owner>hsuregan@chromium.org</owner>
-  <owner>cros-customization@google.com</owner>
+    enum="OsSetting" expires_after="2023-04-17">
+  <owner>xiaohuic@chromium.org</owner>
+  <owner>assistive-eng@google.com</owner>
   <summary>
     Setting search results clicked by user in the OS settings search box.
   </summary>
 </histogram>
 
 <histogram name="ChromeOS.Settings.SearchResultSubpageSelected"
-    enum="OsSettingsSubpage" expires_after="2022-04-24">
-  <owner>khorimoto@chromium.org</owner>
-  <owner>hsuregan@chromium.org</owner>
-  <owner>cros-customization@google.com</owner>
+    enum="OsSettingsSubpage" expires_after="2023-04-17">
+  <owner>xiaohuic@chromium.org</owner>
+  <owner>assistive-eng@google.com</owner>
   <summary>
     Subpage search results clicked by user in the OS settings search box.
   </summary>
 </histogram>
 
 <histogram name="ChromeOS.Settings.SearchResultTypeSelected"
-    enum="OsSettingsSearchResultType" expires_after="2022-04-24">
-  <owner>khorimoto@chromium.org</owner>
-  <owner>hsuregan@chromium.org</owner>
-  <owner>cros-customization@google.com</owner>
+    enum="OsSettingsSearchResultType" expires_after="2023-04-17">
+  <owner>xiaohuic@google.com</owner>
+  <owner>assistive-eng@chromium.org</owner>
   <summary>
     Type of search results clicked by user in the OS settings search box.
   </summary>
diff --git a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
index b8a58326..af80158 100644
--- a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
+++ b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
@@ -398,9 +398,10 @@
   <owner>dpenning@chromium.org</owner>
   <owner>dfried@chromium.org</owner>
   <summary>
-    When {TutorialId} is aborted, the step at which the tutorial was aborted.
+    When {TutorialID} is aborted, the step at which the tutorial was aborted.
     Only logged on desktop platforms. Logged each time a tutorial is aborted.
   </summary>
+  <token key="TutorialID" variants="TutorialID"/>
 </histogram>
 
 <histogram name="Tutorial{TutorialID}.Completion" enum="BooleanSuccess"
@@ -408,9 +409,10 @@
   <owner>dpenning@chromium.org</owner>
   <owner>dfried@chromium.org</owner>
   <summary>
-    When {TutorialId} is ended, whether the tutorial was completed or aborted.
+    When {TutorialID} is ended, whether the tutorial was completed or aborted.
     Only logged on desktop platforms. Logged each time a tutorial is ended.
   </summary>
+  <token key="TutorialID" variants="TutorialID"/>
 </histogram>
 
 <histogram name="Tutorial{TutorialID}.IPHLinkClicked" enum="BooleanSuccess"
@@ -418,10 +420,11 @@
   <owner>dpenning@chromium.org</owner>
   <owner>dfried@chromium.org</owner>
   <summary>
-    When {TutorialId} button is shown from an IPHLink, whether the user clicks
+    When {TutorialID} button is shown from an IPHLink, whether the user clicks
     to start the tutorial. Only logged on desktop platforms when the user either
     clicks or dismisses the FeaturePromo.
   </summary>
+  <token key="TutorialID" variants="TutorialID"/>
 </histogram>
 
 </histograms>
diff --git a/tools/metrics/histograms/metadata/file/histograms.xml b/tools/metrics/histograms/metadata/file/histograms.xml
index 1114d3f..c0e74c1 100644
--- a/tools/metrics/histograms/metadata/file/histograms.xml
+++ b/tools/metrics/histograms/metadata/file/histograms.xml
@@ -550,6 +550,16 @@
   </summary>
 </histogram>
 
+<histogram name="FileBrowser.OfficeFiles.WebDriveOffice"
+    enum="WebDriveOfficeTaskResult" expires_after="M110">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
+  <summary>
+    Chrome OS File Browser: When a user selects MS Office files, records the
+    results of trying to enable the Web Drive Office task.
+  </summary>
+</histogram>
+
 <histogram name="FileBrowser.OpenFiles.RootType" enum="FileManagerRootType"
     expires_after="2022-04-03">
   <owner>simmonsjosh@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/input/histograms.xml b/tools/metrics/histograms/metadata/input/histograms.xml
index 226a8ee0..02047835 100644
--- a/tools/metrics/histograms/metadata/input/histograms.xml
+++ b/tools/metrics/histograms/metadata/input/histograms.xml
@@ -230,6 +230,17 @@
   </summary>
 </histogram>
 
+<histogram name="InputMethod.Assistive.MultiWord.SuggestionLength"
+    units="chars" expires_after="2022-09-01">
+  <owner>curtismcmullan@google.com</owner>
+  <owner>essential-inputs-team@google.com</owner>
+  <summary>
+    This records the length of a multi word suggestion shown to a user in chars.
+    The metric is recorded once when a suggestion visually appears in front of a
+    user.
+  </summary>
+</histogram>
+
 <histogram name="InputMethod.Assistive.NotAllowed" enum="IMEAssistiveAction"
     expires_after="2022-09-30">
   <owner>jiwan@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/mobile/histograms.xml b/tools/metrics/histograms/metadata/mobile/histograms.xml
index 42d0cb7..87c84caf 100644
--- a/tools/metrics/histograms/metadata/mobile/histograms.xml
+++ b/tools/metrics/histograms/metadata/mobile/histograms.xml
@@ -339,12 +339,12 @@
 </histogram>
 
 <histogram name="Mobile.RecentTabsManager.TotalTabsFromOtherDevicesOpenAll"
-    units="count" expires_after="2022-04-19">
+    units="count" expires_after="2023-04-19">
   <owner>sczs@chromium.org</owner>
   <owner>edchin@chromium.org</owner>
   <summary>
     Records the total number of tabs opened when Open all was selected from
-    other devices in Recent Tabs.
+    other devices in Recent Tabs, iOS only.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index 0038676..2594ab2 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -1471,7 +1471,8 @@
 </histogram>
 
 <histogram name="NewTabPage.RecipeTasks.RelatedSearchDownloadCount"
-    units="count" expires_after="2022-03-06">
+    units="count" expires_after="2022-07-01">
+  <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml
index 5c1f32b..063814e1 100644
--- a/tools/metrics/histograms/metadata/omnibox/histograms.xml
+++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -702,6 +702,16 @@
   </summary>
 </histogram>
 
+<histogram name="Omnibox.SearchPrefetch.FetchResult" enum="BooleanSuccess"
+    expires_after="2022-08-07">
+  <owner>ryansturm@chromium.org</owner>
+  <owner>chrome-omnibox-team@google.com</owner>
+  <summary>
+    Whether a prefetch request finished with a success status or a status that
+    could not be served. Recorded whenever a prefetch request receives headers.
+  </summary>
+</histogram>
+
 <histogram name="Omnibox.SearchPrefetch.PrefetchEligibilityReason"
     enum="SearchPrefetchEligibilityReason" expires_after="2022-08-07">
   <owner>ryansturm@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/payment/OWNERS b/tools/metrics/histograms/metadata/payment/OWNERS
index 17f10e9..bec98529d 100644
--- a/tools/metrics/histograms/metadata/payment/OWNERS
+++ b/tools/metrics/histograms/metadata/payment/OWNERS
@@ -3,5 +3,4 @@
 # Prefer sending CLs to the owners listed below.
 # Use chromium-metrics-reviews@google.com as a backup.
 rouslan@chromium.org
-maxlg@chromium.org
 smcgruer@chromium.org
diff --git a/tools/metrics/histograms/metadata/printing/histograms.xml b/tools/metrics/histograms/metadata/printing/histograms.xml
index fccee55..f049673 100644
--- a/tools/metrics/histograms/metadata/printing/histograms.xml
+++ b/tools/metrics/histograms/metadata/printing/histograms.xml
@@ -84,7 +84,7 @@
 
 <histogram name="Printing.CUPS.AddressResolutionResult" enum="BooleanSuccess"
     expires_after="2022-08-07">
-  <owner>skau@chromium.org</owner>
+  <owner>bmgordon@chromium.org</owner>
   <owner>cros-printing-dev@chromium.org</owner>
   <summary>
     Records whether resolution of a .local address via mDNS was successful. The
@@ -106,7 +106,7 @@
 
 <histogram name="Printing.CUPS.IppAttributesSuccess" enum="BooleanSuccess"
     expires_after="2022-08-07">
-  <owner>skau@chromium.org</owner>
+  <owner>bmgordon@chromium.org</owner>
   <owner>cros-printing-dev@chromium.org</owner>
   <summary>
     Record if the request for IPP attributes was successful during printer
@@ -118,8 +118,8 @@
 </histogram>
 
 <histogram name="Printing.CUPS.IppDeviceReachable" enum="BooleanSuccess"
-    expires_after="2022-04-17">
-  <owner>skau@chromium.org</owner>
+    expires_after="2022-10-17">
+  <owner>bmgordon@chromium.org</owner>
   <owner>cros-printing-dev@chromium.org</owner>
   <summary>
     Record if the request for IPP attributes was successful in reaching the
@@ -128,7 +128,7 @@
 </histogram>
 
 <histogram name="Printing.CUPS.JobDuration.JobCancelled" units="ms"
-    expires_after="2022-04-24">
+    expires_after="2022-10-24">
   <owner>bmgordon@chromium.org</owner>
   <owner>project-bolton@google.com</owner>
   <summary>
@@ -151,7 +151,7 @@
     expires_after="never">
 <!-- expires-never: Monitors printing health for Chrome OS. -->
 
-  <owner>skau@chromium.org</owner>
+  <owner>bmgordon@chromium.org</owner>
   <owner>cros-printing-dev@chromium.org</owner>
   <summary>
     The final status of every print job that was succesfully queued. Only used
@@ -173,8 +173,8 @@
 </histogram>
 
 <histogram name="Printing.CUPS.NearbyNetworkPrintersCount" units="printers"
-    expires_after="2022-04-17">
-  <owner>skau@chromium.org</owner>
+    expires_after="2022-10-17">
+  <owner>bmgordon@chromium.org</owner>
   <owner>project-bolton@google.com</owner>
   <summary>
     The number of detected network printers that have not been saved. Recorded
@@ -184,7 +184,7 @@
 </histogram>
 
 <histogram name="Printing.CUPS.PrintDocumentSize" units="KB"
-    expires_after="2022-04-17">
+    expires_after="2022-10-17">
   <owner>bmgordon@chromium.org</owner>
   <owner>project-bolton@google.com</owner>
   <summary>
@@ -194,8 +194,8 @@
 </histogram>
 
 <histogram name="Printing.CUPS.PrinterAdded" enum="PrinterProtocol"
-    expires_after="2022-04-17">
-  <owner>skau@chromium.org</owner>
+    expires_after="2022-10-17">
+  <owner>bmgordon@chromium.org</owner>
   <owner>src/chromeos/printing/OWNERS</owner>
   <summary>
     The protocol for a printer that was added. Used to track printer churn by
@@ -204,8 +204,8 @@
 </histogram>
 
 <histogram name="Printing.CUPS.PrinterRemoved" enum="PrinterProtocol"
-    expires_after="2022-04-17">
-  <owner>skau@chromium.org</owner>
+    expires_after="2022-10-17">
+  <owner>bmgordon@chromium.org</owner>
   <owner>src/chromeos/printing/OWNERS</owner>
   <summary>
     The protocol for a printer that was removed. Used to track printer churn by
@@ -215,7 +215,7 @@
 
 <histogram name="Printing.CUPS.PrintersDiscovered" units="printers"
     expires_after="2022-08-28">
-  <owner>skau@chromium.org</owner>
+  <owner>bmgordon@chromium.org</owner>
   <owner>src/chromeos/printing/OWNERS</owner>
   <summary>
     The number of printers shown in the discovered printers dialog during
@@ -227,7 +227,7 @@
     expires_after="never">
 <!-- expires-never: Monitors printer setup health for Chrome OS. -->
 
-  <owner>skau@chromium.org</owner>
+  <owner>bmgordon@chromium.org</owner>
   <owner>src/chromeos/printing/OWNERS</owner>
   <summary>
     The success or error code for the setup of a CUPS printer. Recorded when
@@ -282,8 +282,8 @@
 </histogram>
 
 <histogram name="Printing.CUPS.ProtocolUsed" enum="PrinterProtocol"
-    expires_after="2022-04-17">
-  <owner>skau@chromium.org</owner>
+    expires_after="2022-10-17">
+  <owner>bmgordon@chromium.org</owner>
   <owner>cros-printing-dev@chromium.org</owner>
   <summary>
     Records the protocol for a selected printer in Chrome OS. Used to track
@@ -295,7 +295,7 @@
 
 <histogram name="Printing.CUPS.TotalNetworkPrintersCount" units="printers"
     expires_after="2022-08-28">
-  <owner>skau@chromium.org</owner>
+  <owner>bmgordon@chromium.org</owner>
   <owner>project-bolton@google.com</owner>
   <summary>
     The total number of detected network printers. Recorded when the user
@@ -369,7 +369,7 @@
 <histogram name="Printing.PrintServers.ServersToQuery" units="servers"
     expires_after="2022-12-31">
   <owner>pawliczek@chromium.org</owner>
-  <owner>skau@chromium.org</owner>
+  <owner>bmgordon@chromium.org</owner>
   <summary>
     Records the number of print servers that must be queried according to
     policies. Only non-zero values are recorded. The histogram is emitted when
diff --git a/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml b/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
index 6f1a0c7..d163190 100644
--- a/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
+++ b/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
@@ -30,9 +30,7 @@
 
 <variants name="ModelExecutionStatus">
   <variant name="ExecutionError"/>
-  <variant name="InvalidMetadata"/>
   <variant name="Success"/>
-  <variant name="Unknown"/>
 </variants>
 
 <variants name="SegmentationKey">
@@ -352,6 +350,18 @@
     segmentation model.
 
     Recorded every time a {SegmentationModel} segmentation model is executed.
+
+    Before M100: The ExecutionError could mean either model not available or
+    execution failed.
+
+    In M100 in addition to ExecutionError,
+    OptimizationGuide.ModelExecutor.ExecutionStatus will also record
+    FileNotValid when model is not available.
+
+    M101: SkippedModelNotReady was added which counts execution attempts before
+    model was ready, and ExecutionError means the model failed after it was
+    ready. Also other cases when execution was skipped are added to this
+    histogram (&quot;Skipped*&quot;).
   </summary>
   <token key="SegmentationModel" variants="SegmentationModel"/>
 </histogram>
@@ -384,7 +394,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.SelectionFailedReason"
-    enum="SegmentationSelectionFailureReason" expires_after="2022-07-31">
+    enum="SegmentationSelectionFailureReason" expires_after="M101">
   <owner>ssid@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -394,6 +404,18 @@
   </summary>
 </histogram>
 
+<histogram name="SegmentationPlatform.SelectionFailedReason.{SegmentationKey}"
+    enum="SegmentationSelectionFailureReason" expires_after="2022-07-31">
+  <owner>ssid@chromium.org</owner>
+  <owner>chrome-segmentation-platform@google.com</owner>
+  <summary>
+    Records the reason why the segmentation platform was unable to return a
+    segment selection for {SegmentationKey}, or if a result was available.
+    Recorded when failure is hit when trying to compute selection, or when
+    reading the selected segment.
+  </summary>
+</histogram>
+
 <histogram
     name="SegmentationPlatform.SignalDatabase.GetSamples.DatabaseEntryCount"
     units="entries" expires_after="2022-08-28">
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 78d7126..c440a3fc 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -394,6 +394,8 @@
 crbug.com/1297360 [ fuchsia-chrome ] system_health.common_desktop/browse:media:youtube:2019 [ Skip ]
 crbug.com/1297360 [ fuchsia-chrome ] system_health.common_desktop/browse:tools:sheets:2019 [ Skip ]
 crbug.com/1297360 [ fuchsia-chrome ] system_health.common_desktop/browse:tools:earth:2020 [ Skip ]
+crbug.com/1306274 [ fuchsia-chrome ] system_health.common_desktop/browse:media:imgur [ Skip ]
+crbug.com/1306280 [ fuchsia-chrome ] system_health.common_desktop/load:media:google_images:2018 [ Skip ]
 crbug.com/1302694 [ mac ] system_health.common_desktop/browse:tools:photoshop:2021 [ Skip ]
 crbug.com/1302694 [ mac ] system_health.common_desktop/browse:tools:photoshop_warm:2021 [ Skip ]
 
diff --git a/ui/chromeos/file_manager_strings.grdp b/ui/chromeos/file_manager_strings.grdp
index 27be0750..51343bec 100644
--- a/ui/chromeos/file_manager_strings.grdp
+++ b/ui/chromeos/file_manager_strings.grdp
@@ -1279,6 +1279,15 @@
   <message name="IDS_FILE_BROWSER_RAR_ARCHIVE_FILE_TYPE" desc="RAR archive file type">
     RAR archive
   </message>
+  <message name="IDS_FILE_BROWSER_ISO_ARCHIVE_FILE_TYPE" desc="ISO archive file type">
+    ISO image
+  </message>
+  <message name="IDS_FILE_BROWSER_7Z_ARCHIVE_FILE_TYPE" desc="7z archive file type">
+    7z archive
+  </message>
+  <message name="IDS_FILE_BROWSER_CRX_ARCHIVE_FILE_TYPE" desc="CRX archive file type">
+    Chrome extension
+  </message>
   <message name="IDS_FILE_BROWSER_TAR_ARCHIVE_FILE_TYPE" desc="Tar archive file type">
     Tar archive
   </message>
diff --git a/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_7Z_ARCHIVE_FILE_TYPE.png.sha1 b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_7Z_ARCHIVE_FILE_TYPE.png.sha1
new file mode 100644
index 0000000..9c6a2e3
--- /dev/null
+++ b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_7Z_ARCHIVE_FILE_TYPE.png.sha1
@@ -0,0 +1 @@
+4000906aa4c53562a0add25c4057cad140eaa2e1
\ No newline at end of file
diff --git a/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_CRX_ARCHIVE_FILE_TYPE.png.sha1 b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_CRX_ARCHIVE_FILE_TYPE.png.sha1
new file mode 100644
index 0000000..9c6a2e3
--- /dev/null
+++ b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_CRX_ARCHIVE_FILE_TYPE.png.sha1
@@ -0,0 +1 @@
+4000906aa4c53562a0add25c4057cad140eaa2e1
\ No newline at end of file
diff --git a/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_ISO_ARCHIVE_FILE_TYPE.png.sha1 b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_ISO_ARCHIVE_FILE_TYPE.png.sha1
new file mode 100644
index 0000000..9c6a2e3
--- /dev/null
+++ b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_ISO_ARCHIVE_FILE_TYPE.png.sha1
@@ -0,0 +1 @@
+4000906aa4c53562a0add25c4057cad140eaa2e1
\ No newline at end of file
diff --git a/ui/events/fuchsia/fakes/pointer_event_utility.cc b/ui/events/fuchsia/fakes/pointer_event_utility.cc
index db44cb14..59d8bcf 100644
--- a/ui/events/fuchsia/fakes/pointer_event_utility.cc
+++ b/ui/events/fuchsia/fakes/pointer_event_utility.cc
@@ -99,13 +99,20 @@
 MouseEventBuilder& MouseEventBuilder::AddSample(
     uint32_t id,
     std::array<float, 2> position,
-    std::vector<uint8_t> pressed_buttons) {
+    std::vector<uint8_t> pressed_buttons,
+    std::array<int64_t, 2> scroll) {
   sample_ = absl::make_optional<fup::MousePointerSample>();
   sample_->set_device_id(id);
   if (!pressed_buttons.empty()) {
     sample_->set_pressed_buttons(pressed_buttons);
   }
   sample_->set_position_in_viewport(position);
+  if (scroll[0] != 0) {
+    sample_->set_scroll_h(scroll[0]);
+  }
+  if (scroll[1] != 0) {
+    sample_->set_scroll_v(scroll[1]);
+  }
   return *this;
 }
 
diff --git a/ui/events/fuchsia/fakes/pointer_event_utility.h b/ui/events/fuchsia/fakes/pointer_event_utility.h
index d4457e8c..627e2d8 100644
--- a/ui/events/fuchsia/fakes/pointer_event_utility.h
+++ b/ui/events/fuchsia/fakes/pointer_event_utility.h
@@ -52,7 +52,8 @@
   MouseEventBuilder& AddTime(zx_time_t time);
   MouseEventBuilder& AddSample(uint32_t id,
                                std::array<float, 2> position,
-                               std::vector<uint8_t> pressed_buttons);
+                               std::vector<uint8_t> pressed_buttons,
+                               std::array<int64_t, 2> scroll);
   MouseEventBuilder& AddViewParameters(
       std::array<std::array<float, 2>, 2> view,
       std::array<std::array<float, 2>, 2> viewport,
diff --git a/ui/events/fuchsia/pointer_events_handler.cc b/ui/events/fuchsia/pointer_events_handler.cc
index 43ff891..62751db 100644
--- a/ui/events/fuchsia/pointer_events_handler.cc
+++ b/ui/events/fuchsia/pointer_events_handler.cc
@@ -6,6 +6,7 @@
 
 #include <cmath>
 #include <limits>
+#include <memory>
 
 #include "base/logging.h"
 #include "base/notreached.h"
@@ -201,12 +202,13 @@
 // The gestures expect a gesture to start within the logical view space, and
 // is not tolerant of floating point drift. This function coerces just the DOWN
 // event's coordinate to start within the logical view.
-MouseEvent CreateMouseEventDraft(const fup::MouseEvent& event,
-                                 const EventType event_type,
-                                 const int pressed_buttons_flags,
-                                 const int changed_buttons_flags,
-                                 const fup::ViewParameters& view_parameters,
-                                 const fup::MouseDeviceInfo& device_info) {
+std::unique_ptr<MouseEvent> CreateMouseEventDraft(
+    const fup::MouseEvent& event,
+    const EventType event_type,
+    const int pressed_buttons_flags,
+    const int changed_buttons_flags,
+    const fup::ViewParameters& view_parameters,
+    const fup::MouseDeviceInfo& device_info) {
   DCHECK(HasValidMouseSample(event)) << "precondition";
   const auto& sample = event.pointer_sample();
 
@@ -222,13 +224,27 @@
     logical = ClampToViewSpace(logical[0], logical[1], view_parameters);
   }
 
-  // TODO(fxbug.dev/88580): Use ui::MouseWheelEvent to signal scroll.
+  auto location = gfx::PointF(logical[0], logical[1]);
+  auto root_location = gfx::PointF(sample.position_in_viewport()[0],
+                                   sample.position_in_viewport()[1]);
 
-  return MouseEvent(event_type, gfx::PointF(logical[0], logical[1]),
-                    gfx::PointF(sample.position_in_viewport()[0],
-                                sample.position_in_viewport()[1]),
-                    timestamp, pressed_buttons_flags, changed_buttons_flags,
-                    pointer_details);
+  if (event_type == ET_MOUSEWHEEL) {
+    // TODO(fxbug.dev/92938): Maybe also support ctrl+wheel event here.
+
+    const int offset_x =
+        sample.has_scroll_h() ? static_cast<int>(sample.scroll_h()) : 0;
+    const int offset_y =
+        sample.has_scroll_v() ? static_cast<int>(sample.scroll_v()) : 0;
+
+    // TODO(fxbug.dev/85388): If mouse wheel has by detent(tick) scroll offset,
+    // we can fill them into |tick_120ths|.
+    return std::make_unique<MouseWheelEvent>(
+        gfx::Vector2d(offset_x, offset_y), location, root_location, timestamp,
+        pressed_buttons_flags, changed_buttons_flags);
+  }
+  return std::make_unique<MouseEvent>(event_type, location, root_location,
+                                      timestamp, pressed_buttons_flags,
+                                      changed_buttons_flags, pointer_details);
 }
 
 }  // namespace
@@ -360,16 +376,17 @@
       // Update mouse_down_ for the next Fuchsia event.
       mouse_down_[id] = pressed_buttons;
 
-      // Handle the default case: moved buttons.
-      // This is when there are no buttons pressed either previously or
-      // currently.
-      if (changed_buttons == 0 && pressed_buttons == 0) {
-        // Handle the moved case.
-        auto draft = CreateMouseEventDraft(
-            event, ET_MOUSE_MOVED, pressed_buttons, changed_buttons,
-            mouse_view_parameters_.value(), mouse_device_info_[id]);
-        event_callback_.Run(&draft);
-      } else {
+      const bool is_wheel_event =
+          sample.has_scroll_v() || sample.has_scroll_h();
+      // Do not filterout mouse wheel here, because the wheel event may be
+      // bundled with button down and button up event. Chromium will need to
+      // split it to 2 events.
+      const bool is_button_or_drag_event =
+          (changed_buttons != 0 || pressed_buttons != 0);
+      // If button is down, use drag event instead of move event.
+      const bool is_move_event = !is_button_or_drag_event && !is_wheel_event;
+
+      if (is_button_or_drag_event) {
         // Iterate through possible mouse buttons and potentially emit an event
         // for each one.
         for (int button = EF_LEFT_MOUSE_BUTTON; button <= EF_RIGHT_MOUSE_BUTTON;
@@ -389,22 +406,45 @@
             auto draft = CreateMouseEventDraft(
                 event, event_type, button, changed_buttons,
                 mouse_view_parameters_.value(), mouse_device_info_[id]);
-            event_callback_.Run(&draft);
+            event_callback_.Run(draft.get());
           } else if (prev_down && curr_down) {
+            if (is_wheel_event) {
+              // Skip the drag event when wheel event dispatch.
+              continue;
+            }
             auto event_type = ET_MOUSE_DRAGGED;
             auto draft = CreateMouseEventDraft(
                 event, event_type, button, changed_buttons,
                 mouse_view_parameters_.value(), mouse_device_info_[id]);
-            event_callback_.Run(&draft);
+            event_callback_.Run(draft.get());
           } else if (prev_down && !curr_down) {
             auto event_type = ET_MOUSE_RELEASED;
             auto draft = CreateMouseEventDraft(
                 event, event_type, button, changed_buttons,
                 mouse_view_parameters_.value(), mouse_device_info_[id]);
-            event_callback_.Run(&draft);
+            event_callback_.Run(draft.get());
           }
         }
       }
+
+      if (is_wheel_event) {
+        // Handle the mouse scroll.
+        auto draft = CreateMouseEventDraft(
+            event, ET_MOUSEWHEEL, pressed_buttons, changed_buttons,
+            mouse_view_parameters_.value(), mouse_device_info_[id]);
+        event_callback_.Run(draft.get());
+      }
+
+      // Handle the default case: moved pointer.
+      // This is when there are no buttons pressed either previously or
+      // currently.
+      if (is_move_event) {
+        // Handle the moved case.
+        auto draft = CreateMouseEventDraft(
+            event, ET_MOUSE_MOVED, pressed_buttons, changed_buttons,
+            mouse_view_parameters_.value(), mouse_device_info_[id]);
+        event_callback_.Run(draft.get());
+      }
     }
   }
 
diff --git a/ui/events/fuchsia/pointer_events_handler_unittest.cc b/ui/events/fuchsia/pointer_events_handler_unittest.cc
index 7eb929ec..c07ad0b7 100644
--- a/ui/events/fuchsia/pointer_events_handler_unittest.cc
+++ b/ui/events/fuchsia/pointer_events_handler_unittest.cc
@@ -20,6 +20,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/events/event.h"
+#include "ui/events/event_constants.h"
 #include "ui/events/fuchsia/fakes/fake_mouse_source.h"
 #include "ui/events/fuchsia/fakes/fake_touch_source.h"
 #include "ui/events/fuchsia/fakes/pointer_event_utility.h"
@@ -89,7 +90,7 @@
       MouseEventBuilder()
           .AddTime(1111789u)
           .AddViewParameters(kRect, kRect, kIdentity)
-          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0})
+          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0}, {0, 0})
           .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2})
           .BuildAsVector();
   mouse_source_->ScheduleCallback(std::move(mouse_events));
@@ -138,7 +139,7 @@
       MouseEventBuilder()
           .AddTime(1111789u)
           .AddViewParameters(kRect, kRect, kIdentity)
-          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0 /*button id*/})
+          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0 /*button id*/}, {0, 0})
           .AddMouseDeviceInfo(kMouseDeviceId,
                               {2 /*first button id*/, 0 /*second button id*/,
                                1 /*third button id*/})
@@ -153,10 +154,11 @@
 
   // Keep Fuchsia button press -> Chrome ET_MOUSE_DRAGGED and
   // EF_RIGHT_MOUSE_BUTTON
-  events = MouseEventBuilder()
-               .AddTime(1111789u)
-               .AddSample(kMouseDeviceId, {10.f, 10.f}, {0 /*button id*/})
-               .BuildAsVector();
+  events =
+      MouseEventBuilder()
+          .AddTime(1111789u)
+          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0 /*button id*/}, {0, 0})
+          .BuildAsVector();
   mouse_source_->ScheduleCallback(std::move(events));
   RunLoopUntilIdle();
 
@@ -168,7 +170,7 @@
   // Release Fuchsia button -> Chrome ET_MOUSE_RELEASED
   events = MouseEventBuilder()
                .AddTime(1111789u)
-               .AddSample(kMouseDeviceId, {10.f, 10.f}, {})
+               .AddSample(kMouseDeviceId, {10.f, 10.f}, {}, {0, 0})
                .BuildAsVector();
   mouse_source_->ScheduleCallback(std::move(events));
   RunLoopUntilIdle();
@@ -181,7 +183,7 @@
   // Release Fuchsia button -> Chrome ET_MOUSE_MOVED
   events = MouseEventBuilder()
                .AddTime(1111789u)
-               .AddSample(kMouseDeviceId, {10.f, 10.f}, {})
+               .AddSample(kMouseDeviceId, {10.f, 10.f}, {}, {0, 0})
                .BuildAsVector();
   mouse_source_->ScheduleCallback(std::move(events));
   RunLoopUntilIdle();
@@ -205,7 +207,7 @@
       MouseEventBuilder()
           .AddTime(1111789u)
           .AddViewParameters(kRect, kRect, kIdentity)
-          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0})
+          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0}, {0, 0})
           .AddMouseDeviceInfo(kMouseDeviceId, {2, 0, 1})
           .BuildAsVector();
   mouse_source_->ScheduleCallback(std::move(events));
@@ -220,7 +222,7 @@
   // EF_LEFT_MOUSE_BUTTON
   events = MouseEventBuilder()
                .AddTime(1111789u)
-               .AddSample(kMouseDeviceId, {10.f, 10.f}, {2})
+               .AddSample(kMouseDeviceId, {10.f, 10.f}, {2}, {0, 0})
                .BuildAsVector();
   mouse_source_->ScheduleCallback(std::move(events));
   RunLoopUntilIdle();
@@ -247,7 +249,7 @@
       MouseEventBuilder()
           .AddTime(1111789u)
           .AddViewParameters(kRect, kRect, kIdentity)
-          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0, 1})
+          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0, 1}, {0, 0})
           .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2})
           .BuildAsVector();
   mouse_source_->ScheduleCallback(std::move(events));
@@ -261,6 +263,139 @@
   mouse_events.clear();
 }
 
+TEST_F(PointerEventsHandlerTest, MouseWheelEvent) {
+  std::vector<MouseWheelEvent> mouse_events;
+  pointer_handler_->StartWatching(
+      base::BindLambdaForTesting([&mouse_events](Event* event) {
+        ASSERT_EQ(event->type(), ET_MOUSEWHEEL);
+        mouse_events.push_back(*event->AsMouseWheelEvent());
+      }));
+  RunLoopUntilIdle();  // Server gets watch call.
+
+  // receive a vertical scroll
+  std::vector<fup::MouseEvent> events =
+      MouseEventBuilder()
+          .AddTime(1111789u)
+          .AddViewParameters(kRect, kRect, kIdentity)
+          .AddSample(kMouseDeviceId, {10.f, 10.f}, {}, {0, 120})
+          .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2})
+          .BuildAsVector();
+  mouse_source_->ScheduleCallback(std::move(events));
+  RunLoopUntilIdle();
+
+  ASSERT_EQ(mouse_events.size(), 1u);
+  EXPECT_EQ(mouse_events[0].type(), ET_MOUSEWHEEL);
+  EXPECT_EQ(mouse_events[0].flags(), EF_NONE);
+  EXPECT_EQ(mouse_events[0].AsMouseWheelEvent()->x_offset(), 0);
+  EXPECT_EQ(mouse_events[0].AsMouseWheelEvent()->y_offset(), 120);
+  mouse_events.clear();
+
+  // receive a horizontal scroll
+  events = MouseEventBuilder()
+               .AddTime(1111789u)
+               .AddViewParameters(kRect, kRect, kIdentity)
+               .AddSample(kMouseDeviceId, {10.f, 10.f}, {}, {120, 0})
+               .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2})
+               .BuildAsVector();
+  mouse_source_->ScheduleCallback(std::move(events));
+  RunLoopUntilIdle();
+
+  ASSERT_EQ(mouse_events.size(), 1u);
+  EXPECT_EQ(mouse_events[0].type(), ET_MOUSEWHEEL);
+  EXPECT_EQ(mouse_events[0].flags(), EF_NONE);
+  EXPECT_EQ(mouse_events[0].AsMouseWheelEvent()->x_offset(), 120);
+  EXPECT_EQ(mouse_events[0].AsMouseWheelEvent()->y_offset(), 0);
+  mouse_events.clear();
+}
+
+TEST_F(PointerEventsHandlerTest, MouseWheelEventWithButtonPressed) {
+  std::vector<std::unique_ptr<Event>> mouse_events;
+  pointer_handler_->StartWatching(
+      base::BindLambdaForTesting([&mouse_events](Event* event) {
+        ASSERT_TRUE(event->IsMouseEvent());
+        if (event->IsMouseWheelEvent()) {
+          auto e = Event::Clone(*event->AsMouseWheelEvent());
+          mouse_events.push_back(std::move(e));
+        } else if (event->IsMouseEvent()) {
+          auto e = Event::Clone(*event->AsMouseEvent());
+          mouse_events.push_back(std::move(e));
+        } else {
+          NOTREACHED();
+        }
+      }));
+  RunLoopUntilIdle();  // Server gets watch call.
+
+  // left button down
+  std::vector<fup::MouseEvent> events =
+      MouseEventBuilder()
+          .AddTime(1111000u)
+          .AddViewParameters(kRect, kRect, kIdentity)
+          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0}, {0, 0})
+          .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2})
+          .BuildAsVector();
+
+  // receive a vertical scroll with pressed button
+  events.push_back(MouseEventBuilder()
+                       .AddTime(1111789u)
+                       .AddViewParameters(kRect, kRect, kIdentity)
+                       .AddSample(kMouseDeviceId, {10.f, 10.f}, {0}, {0, 120})
+                       .Build());
+  mouse_source_->ScheduleCallback(std::move(events));
+
+  RunLoopUntilIdle();
+
+  ASSERT_EQ(mouse_events.size(), 2u);
+  EXPECT_EQ(mouse_events[0]->type(), ET_MOUSE_PRESSED);
+  EXPECT_EQ(mouse_events[0]->flags(), EF_LEFT_MOUSE_BUTTON);
+  EXPECT_EQ(mouse_events[1]->type(), ET_MOUSEWHEEL);
+  EXPECT_EQ(mouse_events[1]->flags(), EF_LEFT_MOUSE_BUTTON);
+  EXPECT_EQ(mouse_events[1]->AsMouseWheelEvent()->x_offset(), 0);
+  EXPECT_EQ(mouse_events[1]->AsMouseWheelEvent()->y_offset(), 120);
+
+  mouse_events.clear();
+}
+
+TEST_F(PointerEventsHandlerTest, MouseWheelEventWithButtonDownBundled) {
+  std::vector<std::unique_ptr<Event>> mouse_events;
+  pointer_handler_->StartWatching(
+      base::BindLambdaForTesting([&mouse_events](Event* event) {
+        ASSERT_TRUE(event->IsMouseEvent());
+        if (event->IsMouseWheelEvent()) {
+          auto e = Event::Clone(*event->AsMouseWheelEvent());
+          mouse_events.push_back(std::move(e));
+        } else if (event->IsMouseEvent()) {
+          auto e = Event::Clone(*event->AsMouseEvent());
+          mouse_events.push_back(std::move(e));
+        } else {
+          NOTREACHED();
+        }
+      }));
+  RunLoopUntilIdle();  // Server gets watch call.
+
+  // left button down and a vertical scroll bundled.
+  std::vector<fup::MouseEvent> events =
+      MouseEventBuilder()
+          .AddTime(1111000u)
+          .AddViewParameters(kRect, kRect, kIdentity)
+          .AddSample(kMouseDeviceId, {10.f, 10.f}, {0}, {0, 120})
+          .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2})
+          .BuildAsVector();
+
+  mouse_source_->ScheduleCallback(std::move(events));
+
+  RunLoopUntilIdle();
+
+  ASSERT_EQ(mouse_events.size(), 2u);
+  EXPECT_EQ(mouse_events[0]->type(), ET_MOUSE_PRESSED);
+  EXPECT_EQ(mouse_events[0]->flags(), EF_LEFT_MOUSE_BUTTON);
+  EXPECT_EQ(mouse_events[1]->type(), ET_MOUSEWHEEL);
+  EXPECT_EQ(mouse_events[1]->flags(), EF_LEFT_MOUSE_BUTTON);
+  EXPECT_EQ(mouse_events[1]->AsMouseWheelEvent()->x_offset(), 0);
+  EXPECT_EQ(mouse_events[1]->AsMouseWheelEvent()->y_offset(), 120);
+
+  mouse_events.clear();
+}
+
 TEST_F(PointerEventsHandlerTest, Phase_ChromeTouchEventTypesAreSynthesized) {
   std::vector<TouchEvent> touch_events;
   pointer_handler_->StartWatching(
diff --git a/ui/file_manager/base/gn/file_types.json5 b/ui/file_manager/base/gn/file_types.json5
index a4c63c1..0f0cc3f 100644
--- a/ui/file_manager/base/gn/file_types.json5
+++ b/ui/file_manager/base/gn/file_types.json5
@@ -309,6 +309,27 @@
   },
   {
     "type": "archive",
+    "translationKey": "ISO_ARCHIVE_FILE_TYPE",
+    "subtype": "ISO",
+    "extensions": [".iso"],
+    "mime": "application/x-iso9660-image"
+  },
+  {
+    "type": "archive",
+    "translationKey": "7Z_ARCHIVE_FILE_TYPE",
+    "subtype": "7Z",
+    "extensions": [".7z"],
+    "mime": "application/x-7z-compressed"
+  },
+  {
+    "type": "archive",
+    "translationKey": "CRX_ARCHIVE_FILE_TYPE",
+    "subtype": "CRX",
+    "extensions": [".crx"],
+    "mime": "application/x-chrome-extension"
+  },
+  {
+    "type": "archive",
     "translationKey": "TAR_ARCHIVE_FILE_TYPE",
     "subtype": "TAR",
     "extensions": [".tar"],
diff --git a/ui/file_manager/file_manager/foreground/js/naming_controller.js b/ui/file_manager/file_manager/foreground/js/naming_controller.js
index 3a388fc..089bada 100644
--- a/ui/file_manager/file_manager/foreground/js/naming_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/naming_controller.js
@@ -285,7 +285,8 @@
     try {
       input.validation_ = true;
       await validateEntryName(
-          entry, newName, false, volumeInfo, isRemovableRoot);
+          entry, newName, this.fileFilter_.isHiddenFilesVisible(), volumeInfo,
+          isRemovableRoot);
     } catch (error) {
       await this.alertDialog_.showAsync(/** @type {string} */ (error.message));
 
diff --git a/ui/file_manager/integration_tests/file_manager/office.js b/ui/file_manager/integration_tests/file_manager/office.js
index ca8405a..9df31d4 100644
--- a/ui/file_manager/integration_tests/file_manager/office.js
+++ b/ui/file_manager/integration_tests/file_manager/office.js
@@ -2,13 +2,38 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {ENTRIES, getCaller, pending, repeatUntil, RootPath, sendTestMessage} from '../test_util.js';
+import {ENTRIES, getCaller, getHistogramCount, pending, repeatUntil, RootPath, sendTestMessage} from '../test_util.js';
 import {testcase} from '../testcase.js';
 
 import {remoteCall, setupAndWaitUntilReady} from './background.js';
 import {FILE_MANAGER_EXTENSIONS_ID, FILE_MANAGER_SWA_APP_ID, FILE_SWA_BASE_URL} from './test_data.js';
 
 /**
+ * The name of the UMA to track the results of trying to enable Web Drive Office
+ * for MS Office files.
+ * @const {string}
+ */
+const WebDriveOfficeTaskResultHistogramName =
+    'FileBrowser.OfficeFiles.WebDriveOffice';
+
+/**
+ * The UMA's enumeration values (must be consistent with
+ * WebDriveOfficeTaskResult in tools/metrics/histograms/enums.xml).
+ * @enum {number}
+ */
+const WebDriveOfficeTaskResultHistogramValues = {
+  AVAILABLE: 0,
+  FLAG_DISABLED: 1,
+  OFFLINE: 2,
+  NOT_ON_DRIVE: 3,
+  DRIVE_ERROR: 4,
+  DRIVE_METADATA_ERROR: 5,
+  INVALID_ALTERNATE_URL: 6,
+  DRIVE_ALTERNATE_URL: 7,
+  UNEXPECTED_ALTERNATE_URL: 8,
+};
+
+/**
  * Returns Web Drive Office Word's task descriptor.
  *
  * @return {!chrome.fileManagerPrivate.FileTaskDescriptor}
@@ -108,6 +133,12 @@
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.smallDocx]);
 
+  let histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.NOT_ON_DRIVE);
+  chrome.test.assertEq(
+      0, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Fake chrome.fileManagerPrivate.executeTask to return
   // chrome.fileManagerPrivate.TaskResult.EMPTY.
   const fakeData = {
@@ -125,6 +156,14 @@
   chrome.test.assertFalse(
       taskDescriptor.actionId == webDriveOfficeWordDescriptor().actionId);
 
+  // Assert that a UMA sample has been reported for getting tasks for an Office
+  // file that is not on Drive.
+  histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.NOT_ON_DRIVE);
+  chrome.test.assertEq(
+      1, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Remove fakes.
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
@@ -135,6 +174,12 @@
   const appId = await setupAndWaitUntilReady(
       RootPath.DRIVE, [], [ENTRIES.smallDocxHosted]);
 
+  let histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.AVAILABLE);
+  chrome.test.assertEq(
+      0, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Fake chrome.fileManagerPrivate.executeTask to return
   // chrome.fileManagerPrivate.TaskResult.OPENED.
   const fakeData = {
@@ -150,6 +195,14 @@
   const taskDescriptor = await getExecutedTask(appId);
   chrome.test.assertEq(webDriveOfficeWordDescriptor(), taskDescriptor);
 
+  // Assert that a UMA sample has been reported for selecting an Office file on
+  // Drive.
+  histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.AVAILABLE);
+  chrome.test.assertEq(
+      1, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Remove fakes.
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
@@ -160,6 +213,12 @@
   const appId = await setupAndWaitUntilReady(
       RootPath.DRIVE, [], [ENTRIES.smallXlsxPinned]);
 
+  let histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.AVAILABLE);
+  chrome.test.assertEq(
+      0, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Fake chrome.fileManagerPrivate.executeTask to return
   // chrome.fileManagerPrivate.TaskResult.OPENED.
   const fakeData = {
@@ -175,6 +234,14 @@
   const taskDescriptor = await getExecutedTask(appId);
   chrome.test.assertEq(webDriveOfficeExcelDescriptor(), taskDescriptor);
 
+  // Assert that a UMA sample has been reported for selecting an Office file on
+  // Drive.
+  histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.AVAILABLE);
+  chrome.test.assertEq(
+      1, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Remove fakes.
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
@@ -185,6 +252,12 @@
   const appId = await setupAndWaitUntilReady(
       RootPath.DRIVE, [], [ENTRIES.smallPptxPinned]);
 
+  let histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.AVAILABLE);
+  chrome.test.assertEq(
+      0, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Fake chrome.fileManagerPrivate.executeTask to return
   // chrome.fileManagerPrivate.TaskResult.OPENED.
   const fakeData = {
@@ -200,6 +273,14 @@
   const taskDescriptor = await getExecutedTask(appId);
   chrome.test.assertEq(webDriveOfficePowerPointDescriptor(), taskDescriptor);
 
+  // Assert that a UMA sample has been reported for selecting an Office file on
+  // Drive.
+  histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.AVAILABLE);
+  chrome.test.assertEq(
+      1, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Remove fakes.
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
@@ -260,6 +341,19 @@
         taskDescriptor.actionId == webDriveOfficeWordDescriptor().actionId);
   }
 
+  // Assert that a UMA sample has been reported for selecting an Office file on
+  // Drive that doesn't have a Web Drive editing alternate URL yet.
+  let histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.DRIVE_ALTERNATE_URL);
+  chrome.test.assertEq(
+      1, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+  histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.AVAILABLE);
+  chrome.test.assertEq(
+      0, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Unselect the file that doesn't have an alternate URL.
   await remoteCall.waitAndClickElement(
       appId, `#file-list [file-name="${ENTRIES.smallDocx.nameText}"]`,
@@ -277,6 +371,14 @@
   taskDescriptor = await getExecutedTask(appId, expectedExecuteTaskCount);
   chrome.test.assertEq(webDriveOfficeWordDescriptor(), taskDescriptor);
 
+  // Assert that a UMA sample has been reported for selecting synchronized
+  // Office files on Drive.
+  histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.AVAILABLE);
+  chrome.test.assertEq(
+      1, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Remove fakes.
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
@@ -287,6 +389,12 @@
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.smallDocx]);
 
+  let histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.DRIVE_ALTERNATE_URL);
+  chrome.test.assertEq(
+      0, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Fake chrome.fileManagerPrivate.executeTask to return
   // chrome.fileManagerPrivate.TaskResult.OPENED.
   const fakeData = {
@@ -306,6 +414,14 @@
   chrome.test.assertFalse(
       taskDescriptor.actionId == webDriveOfficeWordDescriptor().actionId);
 
+  // Assert that a UMA sample has been reported for selecting an Office file on
+  // Drive that doesn't have a Web Drive editing alternate URL.
+  histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.DRIVE_ALTERNATE_URL);
+  chrome.test.assertEq(
+      1, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Remove fakes.
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
@@ -316,6 +432,12 @@
   const appId = await setupAndWaitUntilReady(
       RootPath.DRIVE, [], [ENTRIES.smallDocxPinned]);
 
+  let histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.OFFLINE);
+  chrome.test.assertEq(
+      0, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Fake chrome.fileManagerPrivate.executeTask to return
   // chrome.fileManagerPrivate.TaskResult.OPENED.
   const fakeData = {
@@ -334,6 +456,14 @@
   chrome.test.assertFalse(
       taskDescriptor.actionId == webDriveOfficeWordDescriptor().actionId);
 
+  // Assert that a UMA sample has been reported for selecting an Office file on
+  // Drive while offline.
+  histogramCount = await getHistogramCount(
+      WebDriveOfficeTaskResultHistogramName,
+      WebDriveOfficeTaskResultHistogramValues.OFFLINE);
+  chrome.test.assertEq(
+      1, histogramCount, 'Unexpected UMA metric value for Web Drive Office');
+
   // Remove fakes.
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
diff --git a/ui/file_manager/integration_tests/test_util.js b/ui/file_manager/integration_tests/test_util.js
index 36a6fb5..df36c87 100644
--- a/ui/file_manager/integration_tests/test_util.js
+++ b/ui/file_manager/integration_tests/test_util.js
@@ -826,6 +826,8 @@
     nameText: 'text.docx',
     sizeText: '8.7 KB',
     typeText: 'Office document',
+    alternateUrl: 'https://drive.google.com/open?id=smalldocxid&\
+usp=drive_fs',
   }),
 
   smallDocxHosted: new TestEntryInfo({
diff --git a/ui/gfx/buffer_format_util.cc b/ui/gfx/buffer_format_util.cc
index 72b7ea78..9d526f5 100644
--- a/ui/gfx/buffer_format_util.cc
+++ b/ui/gfx/buffer_format_util.cc
@@ -105,14 +105,14 @@
     case BufferFormat::RGBA_F16:
       return 1;
     case BufferFormat::YVU_420: {
-      static size_t factor[] = {1, 2, 2};
-      DCHECK_LT(static_cast<size_t>(plane), std::size(factor));
+      constexpr size_t factor[] = {1, 2, 2};
+      DCHECK_LT(plane, std::size(factor));
       return factor[plane];
     }
     case BufferFormat::YUV_420_BIPLANAR:
     case BufferFormat::P010: {
-      static size_t factor[] = {1, 2};
-      DCHECK_LT(static_cast<size_t>(plane), std::size(factor));
+      constexpr size_t factor[] = {1, 2};
+      DCHECK_LT(plane, std::size(factor));
       return factor[plane];
     }
   }
@@ -120,6 +120,77 @@
   return 0;
 }
 
+size_t PlaneWidthForBufferFormat(size_t width,
+                                 BufferFormat format,
+                                 size_t plane) {
+  const size_t subsample = SubsamplingFactorForBufferFormat(format, plane);
+  return (width + subsample - 1) / subsample;
+}
+
+size_t PlaneHeightForBufferFormat(size_t height,
+                                  BufferFormat format,
+                                  size_t plane) {
+  const size_t subsample = SubsamplingFactorForBufferFormat(format, plane);
+  return (height + subsample - 1) / subsample;
+}
+
+size_t BytesPerPixelForBufferFormat(BufferFormat format, size_t plane) {
+  switch (format) {
+    case BufferFormat::R_8:
+      return 1;
+    case BufferFormat::R_16:
+    case BufferFormat::RG_88:
+    case BufferFormat::BGR_565:
+    case BufferFormat::RGBA_4444:
+      return 2;
+    case BufferFormat::RG_1616:
+    case BufferFormat::BGRX_8888:
+    case BufferFormat::BGRA_1010102:
+    case BufferFormat::RGBA_1010102:
+    case BufferFormat::RGBX_8888:
+    case BufferFormat::RGBA_8888:
+    case BufferFormat::BGRA_8888:
+      return 4;
+    case BufferFormat::RGBA_F16:
+      return 8;
+    case BufferFormat::YVU_420:
+      return 1;
+    case BufferFormat::YUV_420_BIPLANAR:
+      return SubsamplingFactorForBufferFormat(format, plane);
+    case BufferFormat::P010:
+      return 2 * SubsamplingFactorForBufferFormat(format, plane);
+  }
+  NOTREACHED();
+  return 0;
+}
+
+size_t RowByteAlignmentForBufferFormat(BufferFormat format, size_t plane) {
+  switch (format) {
+    case BufferFormat::R_8:
+    case BufferFormat::R_16:
+    case BufferFormat::RG_88:
+    case BufferFormat::BGR_565:
+    case BufferFormat::RGBA_4444:
+    case BufferFormat::RG_1616:
+    case BufferFormat::BGRX_8888:
+    case BufferFormat::BGRA_1010102:
+    case BufferFormat::RGBA_1010102:
+    case BufferFormat::RGBX_8888:
+    case BufferFormat::RGBA_8888:
+    case BufferFormat::BGRA_8888:
+      return 4;
+    case BufferFormat::RGBA_F16:
+      return 8;
+    case BufferFormat::YVU_420:
+      return 1;
+    case BufferFormat::YUV_420_BIPLANAR:
+    case BufferFormat::P010:
+      return BytesPerPixelForBufferFormat(format, plane);
+  }
+  NOTREACHED();
+  return 0;
+}
+
 size_t RowSizeForBufferFormat(size_t width, BufferFormat format, size_t plane) {
   size_t row_size = 0;
   bool valid = RowSizeForBufferFormatChecked(width, format, plane, &row_size);
@@ -131,57 +202,43 @@
                                    BufferFormat format,
                                    size_t plane,
                                    size_t* size_in_bytes) {
-  base::CheckedNumeric<size_t> checked_size = width;
-  switch (format) {
-    case BufferFormat::R_8:
-      checked_size += 3;
-      if (!checked_size.IsValid())
-        return false;
-      *size_in_bytes = (checked_size & ~0x3).ValueOrDie();
-      return true;
-    case BufferFormat::R_16:
-    case BufferFormat::RG_88:
-    case BufferFormat::BGR_565:
-    case BufferFormat::RGBA_4444:
-      checked_size *= 2;
-      checked_size += 3;
-      if (!checked_size.IsValid())
-        return false;
-      *size_in_bytes = (checked_size & ~0x3).ValueOrDie();
-      return true;
-    case BufferFormat::RG_1616:
-    case BufferFormat::BGRX_8888:
-    case BufferFormat::BGRA_1010102:
-    case BufferFormat::RGBA_1010102:
-    case BufferFormat::RGBX_8888:
-    case BufferFormat::RGBA_8888:
-    case BufferFormat::BGRA_8888:
-      checked_size *= 4;
-      if (!checked_size.IsValid())
-        return false;
-      *size_in_bytes = checked_size.ValueOrDie();
-      return true;
-    case BufferFormat::RGBA_F16:
-      checked_size *= 8;
-      if (!checked_size.IsValid())
-        return false;
-      *size_in_bytes = checked_size.ValueOrDie();
-      return true;
-    case BufferFormat::YVU_420:
-      DCHECK_EQ(width % 2, 0u);
-      *size_in_bytes = width / SubsamplingFactorForBufferFormat(format, plane);
-      return true;
-    case BufferFormat::YUV_420_BIPLANAR:
-      DCHECK_EQ(width % 2, 0u);
-      *size_in_bytes = width;
-      return true;
-    case BufferFormat::P010:
-      DCHECK_EQ(width % 2, 0u);
-      *size_in_bytes = 2 * width;
-      return true;
-  }
-  NOTREACHED();
-  return false;
+  base::CheckedNumeric<size_t> checked_size =
+      PlaneWidthForBufferFormat(width, format, plane);
+  checked_size *= BytesPerPixelForBufferFormat(format, plane);
+  const size_t alignment = RowByteAlignmentForBufferFormat(format, plane);
+  checked_size = (checked_size + alignment - 1) & ~(alignment - 1);
+  if (!checked_size.IsValid())
+    return false;
+
+  *size_in_bytes = checked_size.ValueOrDie();
+  return true;
+}
+
+size_t PlaneSizeForBufferFormat(const Size& size,
+                                BufferFormat format,
+                                size_t plane) {
+  size_t plane_size = 0;
+  bool valid =
+      PlaneSizeForBufferFormatChecked(size, format, plane, &plane_size);
+  DCHECK(valid);
+  return plane_size;
+}
+
+bool PlaneSizeForBufferFormatChecked(const Size& size,
+                                     BufferFormat format,
+                                     size_t plane,
+                                     size_t* size_in_bytes) {
+  size_t row_size = 0;
+  if (!RowSizeForBufferFormatChecked(size.width(), format, plane, &row_size))
+    return false;
+  base::CheckedNumeric<size_t> checked_plane_size = row_size;
+  checked_plane_size *=
+      PlaneHeightForBufferFormat(size.height(), format, plane);
+  if (!checked_plane_size.IsValid())
+    return false;
+
+  *size_in_bytes = checked_plane_size.ValueOrDie();
+  return true;
 }
 
 size_t BufferSizeForBufferFormat(const Size& size, BufferFormat format) {
@@ -197,18 +254,14 @@
   base::CheckedNumeric<size_t> checked_size = 0;
   size_t num_planes = NumberOfPlanesForLinearBufferFormat(format);
   for (size_t i = 0; i < num_planes; ++i) {
-    size_t row_size = 0;
-    if (!RowSizeForBufferFormatChecked(size.width(), format, i, &row_size))
+    size_t plane_size = 0;
+    if (!PlaneSizeForBufferFormatChecked(size, format, i, &plane_size))
       return false;
-    base::CheckedNumeric<size_t> checked_plane_size = row_size;
-    checked_plane_size *= size.height() /
-                          SubsamplingFactorForBufferFormat(format, i);
-    if (!checked_plane_size.IsValid())
-      return false;
-    checked_size += checked_plane_size.ValueOrDie();
+    checked_size += plane_size;
     if (!checked_size.IsValid())
       return false;
   }
+
   *size_in_bytes = checked_size.ValueOrDie();
   return true;
 }
@@ -217,6 +270,7 @@
                                    BufferFormat format,
                                    size_t plane) {
   DCHECK_LT(plane, gfx::NumberOfPlanesForLinearBufferFormat(format));
+
   switch (format) {
     case BufferFormat::R_8:
     case BufferFormat::R_16:
@@ -232,23 +286,14 @@
     case BufferFormat::BGRA_8888:
     case BufferFormat::RGBA_F16:
       return 0;
-    case BufferFormat::YVU_420: {
-      static size_t offset_in_2x2_sub_sampling_sizes[] = {0, 4, 5};
-      DCHECK_LT(plane, std::size(offset_in_2x2_sub_sampling_sizes));
-      return offset_in_2x2_sub_sampling_sizes[plane] * (size.width() / 2) *
-             (size.height() / 2);
-    }
-    case gfx::BufferFormat::YUV_420_BIPLANAR: {
-      static size_t offset_in_2x2_sub_sampling_sizes[] = {0, 4};
-      DCHECK_LT(plane, std::size(offset_in_2x2_sub_sampling_sizes));
-      return offset_in_2x2_sub_sampling_sizes[plane] * (size.width() / 2) *
-             (size.height() / 2);
-    }
+    case BufferFormat::YVU_420:
+    case BufferFormat::YUV_420_BIPLANAR:
     case BufferFormat::P010: {
-      static size_t offset_in_2x2_sub_sampling_sizes[] = {0, 4};
-      DCHECK_LT(plane, std::size(offset_in_2x2_sub_sampling_sizes));
-      return 2 * offset_in_2x2_sub_sampling_sizes[plane] *
-             (size.width() / 2 + size.height() / 2);
+      size_t offset = 0;
+      for (size_t i = 0; i < plane; i++) {
+        offset += PlaneSizeForBufferFormat(size, format, i);
+      }
+      return offset;
     }
   }
   NOTREACHED();
@@ -315,8 +360,12 @@
   return "Invalid Plane";
 }
 
-bool AllowOddHeightMultiPlanarBuffers() {
+bool IsOddHeightMultiPlanarBuffersAllowed() {
   return base::FeatureList::IsEnabled(features::kOddHeightMultiPlanarBuffers);
 }
 
+bool IsOddWidthMultiPlanarBuffersAllowed() {
+  return base::FeatureList::IsEnabled(features::kOddWidthMultiPlanarBuffers);
+}
+
 }  // namespace gfx
diff --git a/ui/gfx/buffer_format_util.h b/ui/gfx/buffer_format_util.h
index 702ef5b..3951ea05 100644
--- a/ui/gfx/buffer_format_util.h
+++ b/ui/gfx/buffer_format_util.h
@@ -40,9 +40,20 @@
     size_t plane,
     size_t* size_in_bytes);
 
+// Returns the number of bytes used to the plane of a given |format|.
+GFX_EXPORT size_t PlaneSizeForBufferFormat(const Size& size,
+                                           BufferFormat format,
+                                           size_t plane);
+[[nodiscard]] GFX_EXPORT bool PlaneSizeForBufferFormatChecked(
+    const Size& size,
+    BufferFormat format,
+    size_t plane,
+    size_t* size_in_bytes);
+
 // Returns the number of bytes used to store all the planes of a given |format|.
 GFX_EXPORT size_t BufferSizeForBufferFormat(const Size& size,
                                             BufferFormat format);
+
 [[nodiscard]] GFX_EXPORT bool BufferSizeForBufferFormatChecked(
     const Size& size,
     BufferFormat format,
@@ -62,8 +73,9 @@
 // tricky when the size of the primary plane is odd, because the subsampled
 // planes will have a size that is not a divisor of the primary plane's size.
 // This indicates that odd height multiplanar formats are supported.
-GFX_EXPORT bool AllowOddHeightMultiPlanarBuffers();
+GFX_EXPORT bool IsOddHeightMultiPlanarBuffersAllowed();
 
+GFX_EXPORT bool IsOddWidthMultiPlanarBuffersAllowed();
 }  // namespace gfx
 
 #endif  // UI_GFX_BUFFER_FORMAT_UTIL_H_
diff --git a/ui/gfx/switches.cc b/ui/gfx/switches.cc
index be932ac..67a798b 100644
--- a/ui/gfx/switches.cc
+++ b/ui/gfx/switches.cc
@@ -37,7 +37,6 @@
 }  // namespace switches
 
 namespace features {
-
 const base::Feature kOddHeightMultiPlanarBuffers {
   "OddHeightMultiPlanarBuffers",
 #if BUILDFLAG(IS_MAC)
@@ -47,4 +46,7 @@
 #endif
 };
 
+const base::Feature kOddWidthMultiPlanarBuffers{
+    "OddWidthMultiPlanarBuffers", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
diff --git a/ui/gfx/switches.h b/ui/gfx/switches.h
index 442809de..e8e5d44 100644
--- a/ui/gfx/switches.h
+++ b/ui/gfx/switches.h
@@ -26,6 +26,7 @@
 
 namespace features {
 GFX_SWITCHES_EXPORT extern const base::Feature kOddHeightMultiPlanarBuffers;
+GFX_SWITCHES_EXPORT extern const base::Feature kOddWidthMultiPlanarBuffers;
 }  // namespace features
 
 #endif  // UI_GFX_SWITCHES_H_
diff --git a/ui/ozone/platform/drm/BUILD.gn b/ui/ozone/platform/drm/BUILD.gn
index b6c498e8..a35a83a 100644
--- a/ui/ozone/platform/drm/BUILD.gn
+++ b/ui/ozone/platform/drm/BUILD.gn
@@ -202,9 +202,13 @@
   deps = [
     ":gbm",
     "//base/test:test_support",
-    "//build/config/linux/libdrm",
     "//skia",
     "//testing/gtest",
+
+    # We're using this instead of the config to ensure that our tests built for
+    # linux have a controllable dependency instead of relying on whatever is on
+    # the system.
+    "//third_party/libdrm",
     "//ui/base/ime",
     "//ui/gfx",
     "//ui/gfx/linux:drm",
diff --git a/ui/ozone/platform/drm/gpu/mock_drm_device.cc b/ui/ozone/platform/drm/gpu/mock_drm_device.cc
index 1ef0fe9..6501e9a 100644
--- a/ui/ozone/platform/drm/gpu/mock_drm_device.cc
+++ b/ui/ozone/platform/drm/gpu/mock_drm_device.cc
@@ -4,6 +4,7 @@
 
 #include "ui/ozone/platform/drm/gpu/mock_drm_device.h"
 
+#include <stdint.h>
 #include <xf86drm.h>
 #include <memory>
 #include <utility>
@@ -24,6 +25,7 @@
   uint32_t object_id;
   uint32_t property_id;
   uint64_t value;
+  uint32_t cursor;
 };
 
 typedef drmModeAtomicReqItem* drmModeAtomicReqItemPtr;
diff --git a/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc b/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc
index a82227d..32c9079 100644
--- a/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc
+++ b/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc
@@ -70,20 +70,15 @@
   auto* surface = static_cast<XDGSurfaceWrapperImpl*>(data);
   DCHECK(surface);
 
-  surface->OnConfigure(serial);
-}
-
-void XDGSurfaceWrapperImpl::OnConfigure(uint32_t serial) {
   // Calls to HandleSurfaceConfigure() might end up hiding the enclosing
   // toplevel window, and deleting this object.
-  auto alive = weak_ptr_factory_.GetWeakPtr();
-
-  wayland_window_->HandleSurfaceConfigure(serial);
-
-  if (!alive)
+  auto weak_window = surface->wayland_window_->AsWeakPtr();
+  weak_window->HandleSurfaceConfigure(serial);
+  
+  if (!weak_window)
     return;
-
-  wayland_window_->OnSurfaceConfigureEvent();
+  
+  weak_window->OnSurfaceConfigureEvent();
 }
 
 xdg_surface* XDGSurfaceWrapperImpl::xdg_surface() const {
diff --git a/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h b/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h
index d90d2fd..a44a5d8 100644
--- a/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h
+++ b/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h
@@ -9,7 +9,6 @@
 
 #include <cstdint>
 
-#include "base/memory/weak_ptr.h"
 #include "ui/ozone/platform/wayland/common/wayland_object.h"
 
 namespace gfx {
@@ -44,8 +43,6 @@
                         struct xdg_surface* xdg_surface,
                         uint32_t serial);
 
-  void OnConfigure(uint32_t serial);
-
   // Non-owing WaylandWindow that uses this surface wrapper.
   WaylandWindow* const wayland_window_;
   WaylandConnection* const connection_;
@@ -53,8 +50,6 @@
   bool is_configured_ = false;
 
   wl::Object<struct xdg_surface> xdg_surface_;
-
-  base::WeakPtrFactory<XDGSurfaceWrapperImpl> weak_ptr_factory_{this};
 };
 
 }  // namespace ui
diff --git a/ui/views/controls/animated_image_view.cc b/ui/views/controls/animated_image_view.cc
index deb2a250..0029a27 100644
--- a/ui/views/controls/animated_image_view.cc
+++ b/ui/views/controls/animated_image_view.cc
@@ -11,7 +11,6 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/compositor.h"
 #include "ui/gfx/canvas.h"
-#include "ui/lottie/animation.h"
 #include "ui/views/widget/widget.h"
 
 namespace views {
@@ -49,7 +48,14 @@
   SchedulePaint();
 }
 
-void AnimatedImageView::Play() {
+void AnimatedImageView::Play(lottie::Animation::Style style) {
+  DCHECK(animated_image_);
+  Play(base::TimeDelta(), animated_image_->GetAnimationDuration(), style);
+}
+
+void AnimatedImageView::Play(base::TimeDelta start_offset,
+                             base::TimeDelta duration,
+                             lottie::Animation::Style style) {
   DCHECK(animated_image_);
   if (state_ == State::kPlaying)
     return;
@@ -58,7 +64,7 @@
 
   SetCompositorFromWidget();
 
-  animated_image_->Start();
+  animated_image_->StartSubsection(start_offset, duration, style);
 }
 
 void AnimatedImageView::Stop() {
diff --git a/ui/views/controls/animated_image_view.h b/ui/views/controls/animated_image_view.h
index 1281e48c..04d6dea 100644
--- a/ui/views/controls/animated_image_view.h
+++ b/ui/views/controls/animated_image_view.h
@@ -9,9 +9,11 @@
 #include <utility>
 
 #include "base/memory/raw_ptr.h"
+#include "base/time/time.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/compositor/compositor_animation_observer.h"
 #include "ui/gfx/geometry/vector2d.h"
+#include "ui/lottie/animation.h"
 #include "ui/views/controls/image_view_base.h"
 #include "ui/views/metadata/view_factory.h"
 #include "ui/views/views_export.h"
@@ -61,7 +63,11 @@
 
   // Plays the animation in loop and must only be called when this view has
   // access to a widget.
-  void Play();
+  void Play(lottie::Animation::Style style = lottie::Animation::Style::kLoop);
+  // Version of the above that mirrors lottie::Animation::StartSubsection().
+  void Play(base::TimeDelta start_offset,
+            base::TimeDelta duration,
+            lottie::Animation::Style style = lottie::Animation::Style::kLoop);
 
   // Stops any animation and resets it to the start frame.
   void Stop();
diff --git a/ui/webui/resources/cr_components/app_management/browser_proxy.ts b/ui/webui/resources/cr_components/app_management/browser_proxy.ts
index c2e143e..5843343 100644
--- a/ui/webui/resources/cr_components/app_management/browser_proxy.ts
+++ b/ui/webui/resources/cr_components/app_management/browser_proxy.ts
@@ -19,10 +19,6 @@
         (this.handler as PageHandlerRemote).$.bindNewPipeAndPassReceiver());
   }
 
-  recordEnumerationValue(metricName: string, value: number, enumSize: number) {
-    chrome.metricsPrivate.recordEnumerationValue(metricName, value, enumSize);
-  }
-
   static getInstance(): BrowserProxy {
     return instance || (instance = new BrowserProxy());
   }
diff --git a/ui/webui/resources/cr_components/app_management/util.ts b/ui/webui/resources/cr_components/app_management/util.ts
index 6734317..4f572466 100644
--- a/ui/webui/resources/cr_components/app_management/util.ts
+++ b/ui/webui/resources/cr_components/app_management/util.ts
@@ -5,7 +5,6 @@
 import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
 
 import {App} from './app_management.mojom-webui.js';
-import {BrowserProxy} from './browser_proxy.js';
 import {AppManagementUserAction, AppType, OptionalBool} from './constants.js';
 import {PermissionType, PermissionTypeIndex} from './permission_constants.js';
 import {isPermissionEnabled} from './permission_util.js';
@@ -121,6 +120,6 @@
     appType: AppType, userAction: AppManagementUserAction) {
   const histogram = getUserActionHistogramNameForAppType_(appType);
   const enumLength = Object.keys(AppManagementUserAction).length;
-  BrowserProxy.getInstance().recordEnumerationValue(
+  chrome.metricsPrivate.recordEnumerationValue(
       histogram, userAction, enumLength);
 }
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 b997c60..35afad4 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_config.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_config.js
@@ -2137,6 +2137,7 @@
     assert(vpn.ipSec);
     assert(vpn.l2tp);
 
+    vpn.ipSec.authenticationType = this.ipsecAuthType_;
     if (vpn.ipSec.authenticationType === IpsecAuthType.CERT) {
       vpn.ipSec.clientCertType = 'PKCS11Id';
       vpn.ipSec.clientCertPkcs11Id = this.getUserCertPkcs11Id_();
@@ -2145,6 +2146,11 @@
     vpn.ipSec.ikeVersion = 1;
     vpn.ipSec.saveCredentials = this.vpnSaveCredentials_;
     vpn.l2tp.saveCredentials = this.vpnSaveCredentials_;
+
+    // Clear IPsec fields which are only for IKEv2.
+    delete vpn.ipSec.eap;
+    delete vpn.ipSec.localIdentity;
+    delete vpn.ipSec.remoteIdentity;
   },
 
   /**