diff --git a/.gn b/.gn
index e41eb038..17561b96 100644
--- a/.gn
+++ b/.gn
@@ -71,7 +71,6 @@
   "//chrome/browser/safety_check/android:*",  # 3 errors
   "//chrome/browser/storage_access_api:*",  # 2 errors
   "//chrome/browser/touch_to_fill/android:*",  # 8 errors
-  "//chrome/notification_helper:*",  # 4 errors
   "//chrome/test:*",  # 2682 errors
 
   "//extensions/browser/api/declarative:*",  # 20 errors
diff --git a/DEPS b/DEPS
index 27c1dd4..ba4cabfb 100644
--- a/DEPS
+++ b/DEPS
@@ -199,11 +199,11 @@
   # 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': '9d171b630287cf97ec9a6014073875301fcf0508',
+  'skia_revision': 'bc2fa2b526327f68a26d9a9a17356a3837cf8563',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'ef6896f42d709cf53b8b4fe49cef4dedb2c81e32',
+  'v8_revision': '00ea60a40e748000e049a0551eb8d3f46a0db714',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -250,7 +250,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': 'c8dede7b1c632ee7f3032546856f2f92dc3bdc6c',
+  'freetype_revision': '54c5ad5c9215feca69614565be7ec2030ee46cfb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -270,7 +270,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': '1549e0ea92b7a287ff185833029a6e6e044de29c',
+  'catapult_revision': 'd2aa56920c21647be46fde595a7c21aef6b2ea3f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -278,7 +278,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'fbf4ea6ef43e49858b16b55c058dafcae661e117',
+  'devtools_frontend_revision': '7f3180d06de0d7034c6b5442cea66a7bd0d9fdfd',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -318,7 +318,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': 'aa0e1be0e8034511ba38edf9789cbbaa52888ae0',
+  'dawn_revision': 'e9f42997b213f1b287aa31698e22b2bca6976853',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -529,7 +529,7 @@
     Var('chromium_git') + '/external/github.com/toji/webvr.info.git' + '@' + 'c58ae99b9ff9e2aa4c524633519570bf33536248',
 
   'src/ios/third_party/earl_grey2/src': {
-      'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + '43f527ebfa84cb133de7fcaa358975849c374ceb',
+      'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + '8e677c10bb9ab2e29dcbb3e08f99452ad0572548',
       'condition': 'checkout_ios',
   },
 
@@ -678,7 +678,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'QK9_suyla0iK8oMKDoZtiMGi1EY1hODwvOSLcJ0LSUUC',
+          'version': 'zXeCxmhPbNT770qhdvfCQxK-TvzbsmCTISBgo4ahJXUC',
       },
     ],
     'condition': 'checkout_android',
@@ -891,7 +891,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'acd6a6281952ffb1d493c25dc176c6b00748d3ec',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '6a0313b88749854f90a7512c472a8cf203264bf2',
       'condition': 'checkout_chromeos',
   },
 
@@ -1454,7 +1454,7 @@
     Var('swiftshader_git') + '/SwiftShader.git' + '@' +  Var('swiftshader_revision'),
 
   'src/third_party/text-fragments-polyfill/src': {
-    'url': Var('chromium_git') + '/external/github.com/GoogleChromeLabs/text-fragments-polyfill.git' + '@' + '20b44319352e4245ace896c96dda24e001297dfd',
+    'url': Var('chromium_git') + '/external/github.com/GoogleChromeLabs/text-fragments-polyfill.git' + '@' + '931511d2d605415fded70eeab1232efe7e39666e',
     'condition': 'checkout_ios',
   },
 
@@ -1584,7 +1584,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c637e2ebee53eed43a146eb05d5d7b2f6a24969b',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@fdd04c5e2c18006c2e9376f8248d00c575532710',
     'condition': 'checkout_src_internal',
   },
 
@@ -1592,7 +1592,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'Gxd4JblYv3mSyMOR68cbNfitqjHZW91cnH8tI1Yr7_YC',
+        'version': 'EZv_Es9dSzd8tGsjtDhPJdYDYDpsMu5aSBuGh9zAC74C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1603,7 +1603,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'gsrJGX9eFJJwdUBlHN9YAdYTX2iehwnmdJ0b3slwRz8C',
+        'version': 'oVc_7wkq95asq6LfStch-Fwa7EA-QmxukyEO3IjBxGgC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/browser/gfx/BUILD.gn b/android_webview/browser/gfx/BUILD.gn
index d35a2804..76b108b 100644
--- a/android_webview/browser/gfx/BUILD.gn
+++ b/android_webview/browser/gfx/BUILD.gn
@@ -83,6 +83,7 @@
     "//android_webview/common",
     "//android_webview/public",
     "//base",
+    "//components/power_scheduler",
     "//components/ui_devtools:buildflags",
     "//components/viz/service",
     "//components/viz/service/main",
diff --git a/android_webview/browser/gfx/DEPS b/android_webview/browser/gfx/DEPS
index 525cf87a..9b9c31a 100644
--- a/android_webview/browser/gfx/DEPS
+++ b/android_webview/browser/gfx/DEPS
@@ -6,6 +6,7 @@
   "+android_webview/common/aw_features.h",
   "+android_webview/common/aw_switches.h",
   "+android_webview/public/browser",
+  "+components/power_scheduler",
   "+components/viz/service/main",
   "+ui/latency",
 ]
diff --git a/android_webview/browser/gfx/begin_frame_source_webview.cc b/android_webview/browser/gfx/begin_frame_source_webview.cc
index 55cf885..eabfdb2 100644
--- a/android_webview/browser/gfx/begin_frame_source_webview.cc
+++ b/android_webview/browser/gfx/begin_frame_source_webview.cc
@@ -7,6 +7,10 @@
 #include "android_webview/browser_jni_headers/RootBeginFrameSourceWebView_jni.h"
 #include "base/auto_reset.h"
 #include "base/no_destructor.h"
+#include "base/trace_event/trace_event.h"
+#include "components/power_scheduler/power_mode.h"
+#include "components/power_scheduler/power_mode_arbiter.h"
+#include "components/power_scheduler/power_mode_voter.h"
 
 namespace android_webview {
 
@@ -49,7 +53,10 @@
 BeginFrameSourceWebView::BeginFrameSourceWebView()
     : ExternalBeginFrameSource(&bfs_client_),
       bfs_client_(this),
-      parent_observer_(std::make_unique<BeginFrameObserver>(this)) {
+      parent_observer_(std::make_unique<BeginFrameObserver>(this)),
+      animation_power_mode_voter_(
+          power_scheduler::PowerModeArbiter::GetInstance()->NewVoter(
+              "PowerModeVoter.Animation")) {
   OnSetBeginFrameSourcePaused(true);
 }
 
@@ -82,10 +89,17 @@
 }
 
 void BeginFrameSourceWebView::OnNeedsBeginFrames(bool needs_begin_frames) {
-  if (observed_begin_frame_source_) {
-    if (needs_begin_frames)
+  if (needs_begin_frames) {
+    TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("cc,benchmark", "NeedsBeginFrames", this);
+    animation_power_mode_voter_->VoteFor(
+        power_scheduler::PowerMode::kAnimation);
+    if (observed_begin_frame_source_)
       observed_begin_frame_source_->AddObserver(parent_observer_.get());
-    else
+  } else {
+    TRACE_EVENT_NESTABLE_ASYNC_END0("cc,benchmark", "NeedsBeginFrames", this);
+    animation_power_mode_voter_->ResetVoteAfterTimeout(
+        power_scheduler::PowerModeVoter::kAnimationTimeout);
+    if (observed_begin_frame_source_)
       observed_begin_frame_source_->RemoveObserver(parent_observer_.get());
   }
 }
diff --git a/android_webview/browser/gfx/begin_frame_source_webview.h b/android_webview/browser/gfx/begin_frame_source_webview.h
index 1efc212..a81b8cb 100644
--- a/android_webview/browser/gfx/begin_frame_source_webview.h
+++ b/android_webview/browser/gfx/begin_frame_source_webview.h
@@ -9,6 +9,7 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/callback.h"
 #include "base/callback_helpers.h"
+#include "components/power_scheduler/power_mode_voter.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/service/frame_sinks/external_begin_frame_source_android.h"
 
@@ -59,6 +60,8 @@
   BeginFrameSourceWebView* parent_ = nullptr;
   std::unique_ptr<BeginFrameObserver> parent_observer_;
   bool inside_begin_frame_ = false;
+
+  std::unique_ptr<power_scheduler::PowerModeVoter> animation_power_mode_voter_;
 };
 
 // RootBeginFrameSourceWebView is subclass of BeginFrameSourceWebView that
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index a375707f..e57825b 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -147,8 +147,7 @@
 
             JNIUtils.setClassLoader(WebViewChromiumAwInit.class.getClassLoader());
 
-            ResourceBundle.setAvailablePakLocales(
-                    new String[] {}, AwLocaleConfig.getWebViewSupportedPakLocales());
+            ResourceBundle.setAvailablePakLocales(AwLocaleConfig.getWebViewSupportedPakLocales());
 
             BundleUtils.setIsBundle(ProductConfig.IS_BUNDLE);
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwLocaleConfig.java b/android_webview/java/src/org/chromium/android_webview/AwLocaleConfig.java
index d14456c..a9f8e5b 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwLocaleConfig.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwLocaleConfig.java
@@ -12,6 +12,6 @@
     private AwLocaleConfig() {}
 
     public static String[] getWebViewSupportedPakLocales() {
-        return ProductConfig.UNCOMPRESSED_LOCALES;
+        return ProductConfig.LOCALES;
     }
 }
diff --git a/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/WebViewApkApplication.java b/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/WebViewApkApplication.java
index ba08b2a..ed60849e 100644
--- a/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/WebViewApkApplication.java
+++ b/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/WebViewApkApplication.java
@@ -48,8 +48,7 @@
 
         // MonochromeApplication has its own locale configuration already, so call this here
         // rather than in maybeInitProcessGlobals.
-        ResourceBundle.setAvailablePakLocales(
-                new String[] {}, AwLocaleConfig.getWebViewSupportedPakLocales());
+        ResourceBundle.setAvailablePakLocales(AwLocaleConfig.getWebViewSupportedPakLocales());
     }
 
     @Override
diff --git a/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellApplication.java b/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellApplication.java
index ff17efb..8f72b250 100644
--- a/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellApplication.java
+++ b/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellApplication.java
@@ -25,7 +25,6 @@
         ContextUtils.initApplicationContext(this);
         PathUtils.setPrivateDataDirectorySuffix("webview", "WebView");
         CommandLine.initFromFile("/data/local/tmp/android-webview-command-line");
-        ResourceBundle.setAvailablePakLocales(
-                new String[] {}, AwLocaleConfig.getWebViewSupportedPakLocales());
+        ResourceBundle.setAvailablePakLocales(AwLocaleConfig.getWebViewSupportedPakLocales());
     }
 }
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 8c682269..28b9293 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -205,6 +205,8 @@
     "ambient/ui/ambient_container_view.h",
     "ambient/ui/ambient_info_view.cc",
     "ambient/ui/ambient_info_view.h",
+    "ambient/ui/ambient_shield_view.cc",
+    "ambient/ui/ambient_shield_view.h",
     "ambient/ui/ambient_view_delegate.h",
     "ambient/ui/ambient_view_ids.h",
     "ambient/ui/assistant_response_container_view.cc",
diff --git a/ash/ambient/ui/ambient_background_image_view.cc b/ash/ambient/ui/ambient_background_image_view.cc
index 7c71239..92274cd8 100644
--- a/ash/ambient/ui/ambient_background_image_view.cc
+++ b/ash/ambient/ui/ambient_background_image_view.cc
@@ -8,6 +8,7 @@
 
 #include "ash/ambient/ambient_constants.h"
 #include "ash/ambient/ui/ambient_info_view.h"
+#include "ash/ambient/ui/ambient_shield_view.h"
 #include "ash/ambient/ui/ambient_view_ids.h"
 #include "ash/ambient/ui/media_string_view.h"
 #include "ash/ambient/util/ambient_util.h"
@@ -173,6 +174,8 @@
   related_image_view_->SetProperty(
       views::kMarginsKey, gfx::Insets(0, kMarginLeftOfRelatedImageDip, 0, 0));
 
+  AddChildView(std::make_unique<AmbientShieldView>());
+
   ambient_info_view_ =
       AddChildView(std::make_unique<AmbientInfoView>(delegate_));
 
diff --git a/ash/ambient/ui/ambient_background_image_view.h b/ash/ambient/ui/ambient_background_image_view.h
index 90f0a31..0b08a44 100644
--- a/ash/ambient/ui/ambient_background_image_view.h
+++ b/ash/ambient/ui/ambient_background_image_view.h
@@ -31,7 +31,8 @@
 
   explicit AmbientBackgroundImageView(AmbientViewDelegate* delegate);
   AmbientBackgroundImageView(const AmbientBackgroundImageView&) = delete;
-  AmbientBackgroundImageView& operator=(AmbientBackgroundImageView&) = delete;
+  AmbientBackgroundImageView& operator=(const AmbientBackgroundImageView&) =
+      delete;
   ~AmbientBackgroundImageView() override;
 
   // views::View:
diff --git a/ash/ambient/ui/ambient_shield_view.cc b/ash/ambient/ui/ambient_shield_view.cc
new file mode 100644
index 0000000..344410f5
--- /dev/null
+++ b/ash/ambient/ui/ambient_shield_view.cc
@@ -0,0 +1,132 @@
+// 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 "ash/ambient/ui/ambient_shield_view.h"
+
+#include <array>
+
+#include "ash/ambient/ui/ambient_view_ids.h"
+#include "ash/style/ash_color_provider.h"
+#include "ui/compositor/layer_delegate.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/skia_paint_util.h"
+#include "ui/views/background.h"
+#include "ui/views/layout/flex_layout.h"
+#include "ui/views/layout/flex_layout_types.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+#include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
+
+namespace ash {
+
+namespace {
+
+// Gray gradient from 5% to 50%.
+constexpr std::array<SkColor, 2> kDarkModeColors{
+    SkColorSetA(gfx::kGoogleGrey900, 12),
+    SkColorSetA(gfx::kGoogleGrey900, 128)};
+
+// Gray gradient from 0% to 20%.
+constexpr std::array<SkColor, 2> kLightModeColors{
+    SkColorSetA(gfx::kGoogleGrey900, 0), SkColorSetA(gfx::kGoogleGrey900, 51)};
+
+class GradientBackground : public views::Background {
+ public:
+  enum class Orientation {
+    kHorizontal,
+    kVertical,
+    kDiagonalAscending,
+    kDiagonalDescending
+  };
+
+  GradientBackground(Orientation orientation,
+                     SkColor start_color,
+                     SkColor end_color)
+      : orientation_(orientation),
+        start_color_(start_color),
+        end_color_(end_color) {}
+
+  GradientBackground(const GradientBackground&) = delete;
+  GradientBackground& operator=(const GradientBackground&) = delete;
+
+  ~GradientBackground() override = default;
+
+  // views::Background:
+  void Paint(gfx::Canvas* canvas, views::View* view) const override {
+    const auto& bounds = view->GetContentsBounds();
+    const auto& points = GetPoints(bounds);
+
+    cc::PaintFlags flags;
+    flags.setBlendMode(SkBlendMode::kSrcOver);
+    flags.setShader(gfx::CreateGradientShader(points.front(), points.back(),
+                                              start_color_, end_color_));
+
+    canvas->DrawRect(bounds, flags);
+  }
+
+ private:
+  const std::array<gfx::Point, 2> GetPoints(const gfx::Rect& bounds) const {
+    switch (orientation_) {
+      case Orientation::kHorizontal:
+        return {bounds.left_center(), bounds.right_center()};
+      case Orientation::kVertical:
+        return {bounds.top_center(), bounds.bottom_center()};
+      case Orientation::kDiagonalAscending:
+        return {bounds.bottom_left(), bounds.top_right()};
+      case Orientation::kDiagonalDescending:
+        return {bounds.origin(), bounds.bottom_right()};
+    }
+  }
+
+  const Orientation orientation_;
+  const SkColor start_color_;
+  const SkColor end_color_;
+};
+
+std::unique_ptr<GradientBackground> CreateGradientBackground(
+    GradientBackground::Orientation orientation,
+    const std::array<SkColor, 2> colors) {
+  return std::make_unique<GradientBackground>(orientation, colors.front(),
+                                              colors.back());
+}
+
+}  // namespace
+
+AmbientShieldView::AmbientShieldView() {
+  SetID(AmbientViewID::kAmbientShieldView);
+  InitLayout();
+}
+
+AmbientShieldView::~AmbientShieldView() = default;
+
+void AmbientShieldView::InitLayout() {
+  const views::FlexSpecification kScaleUnbounded(
+      views::MinimumFlexSizeRule::kPreferred,
+      views::MaximumFlexSizeRule::kUnbounded);
+
+  views::FlexLayout* layout =
+      SetLayoutManager(std::make_unique<views::FlexLayout>());
+  layout->SetOrientation(views::LayoutOrientation::kVertical);
+  layout->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
+  layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
+
+  views::View* top = AddChildView(std::make_unique<views::View>());
+  views::View* bottom = AddChildView(std::make_unique<views::View>());
+
+  for (auto* view : {top, bottom})
+    view->SetProperty(views::kFlexBehaviorKey, kScaleUnbounded);
+
+  bool dark_mode = AshColorProvider::Get()->IsDarkModeEnabled();
+  const auto& colors = dark_mode ? kDarkModeColors : kLightModeColors;
+
+  top->SetBackground(views::CreateSolidBackground(colors.front()));
+  bottom->SetBackground(CreateGradientBackground(
+      GradientBackground::Orientation::kVertical, colors));
+}
+
+BEGIN_METADATA(AmbientShieldView, views::View)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/ambient/ui/ambient_shield_view.h b/ash/ambient/ui/ambient_shield_view.h
new file mode 100644
index 0000000..35587a9
--- /dev/null
+++ b/ash/ambient/ui/ambient_shield_view.h
@@ -0,0 +1,28 @@
+// 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 ASH_AMBIENT_UI_AMBIENT_SHIELD_VIEW_H_
+#define ASH_AMBIENT_UI_AMBIENT_SHIELD_VIEW_H_
+
+#include "ui/views/metadata/metadata_header_macros.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+class AmbientShieldView : public views::View {
+ public:
+  METADATA_HEADER(AmbientShieldView);
+
+  AmbientShieldView();
+  AmbientShieldView(const AmbientShieldView&) = delete;
+  AmbientShieldView& operator=(const AmbientShieldView&) = delete;
+  ~AmbientShieldView() override;
+
+ private:
+  void InitLayout();
+};
+
+}  // namespace ash
+
+#endif  // ASH_AMBIENT_UI_AMBIENT_SHIELD_VIEW_H_
diff --git a/ash/ambient/ui/ambient_view_ids.h b/ash/ambient/ui/ambient_view_ids.h
index 6c39d70..d87bc7b 100644
--- a/ash/ambient/ui/ambient_view_ids.h
+++ b/ash/ambient/ui/ambient_view_ids.h
@@ -22,6 +22,7 @@
   kAmbientAssistantDialogPlate,
   kAmbientMediaStringView,
   kAmbientInfoView,
+  kAmbientShieldView,
 };
 
 }  // namespace ash
diff --git a/ash/app_list/app_list_presenter_delegate_impl.cc b/ash/app_list/app_list_presenter_delegate_impl.cc
index e63ece9..967f1897 100644
--- a/ash/app_list/app_list_presenter_delegate_impl.cc
+++ b/ash/app_list/app_list_presenter_delegate_impl.cc
@@ -120,7 +120,7 @@
   Shelf* shelf =
       Shelf::ForWindow(view_->GetWidget()->GetNativeView()->GetRootWindow());
   if (!shelf_observation_.IsObservingSource(shelf))
-    shelf_observation_.Observe(shelf);
+    shelf_observation_.AddObservation(shelf);
 
   view_->SetShelfHasRoundedCorners(
       IsShelfBackgroundTypeWithRoundedCorners(shelf->GetBackgroundType()));
@@ -142,7 +142,7 @@
 
 void AppListPresenterDelegateImpl::OnClosed() {
   if (!is_visible_)
-    shelf_observation_.Reset();
+    shelf_observation_.RemoveAllObservations();
   controller_->ViewClosed();
 }
 
diff --git a/ash/app_list/app_list_presenter_delegate_impl.h b/ash/app_list/app_list_presenter_delegate_impl.h
index 214405ef..6a68806d 100644
--- a/ash/app_list/app_list_presenter_delegate_impl.h
+++ b/ash/app_list/app_list_presenter_delegate_impl.h
@@ -12,6 +12,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_observer.h"
 #include "base/macros.h"
+#include "base/scoped_multi_source_observation.h"
 #include "base/scoped_observation.h"
 #include "ui/display/display_observer.h"
 #include "ui/display/screen.h"
@@ -92,7 +93,8 @@
       display_observation_{this};
 
   // An observer that notifies AppListView when the shelf state has changed.
-  base::ScopedObservation<Shelf, ShelfObserver> shelf_observation_{this};
+  base::ScopedMultiSourceObservation<Shelf, ShelfObserver> shelf_observation_{
+      this};
 
   DISALLOW_COPY_AND_ASSIGN(AppListPresenterDelegateImpl);
 };
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index f0163f2..6883d12e 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -474,7 +474,7 @@
 
 // Controls whether new OOBE layout is shown or not.
 const base::Feature kNewOobeLayout{"NewOobeLayout",
-                                   base::FEATURE_DISABLED_BY_DEFAULT};
+                                   base::FEATURE_ENABLED_BY_DEFAULT};
 
 // ChromeOS Media App. https://crbug.com/996088.
 const base::Feature kMediaApp{"MediaApp", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/base/containers/checked_iterators.h b/base/containers/checked_iterators.h
index ce3fcfc..89a0d10 100644
--- a/base/containers/checked_iterators.h
+++ b/base/containers/checked_iterators.h
@@ -11,6 +11,7 @@
 
 #include "base/check_op.h"
 #include "base/containers/util.h"
+#include "build/build_config.h"
 
 namespace base {
 
@@ -27,77 +28,24 @@
   template <typename U>
   friend class CheckedContiguousIterator;
 
-  constexpr CheckedContiguousIterator() = default;
-
-#if defined(_LIBCPP_VERSION)
-  // The following using declaration, single argument implicit constructor and
-  // friended `__unwrap_iter` overload are required to use an optimized code
-  // path when using a CheckedContiguousIterator with libc++ algorithms such as
-  // std::copy(first, last, result), std::copy_backward(first, last, result),
-  // std::move(first, last, result) and std::move_backward(first, last, result).
-  //
-  // Each of these algorithms dispatches to a std::memmove if this is safe to do
-  // so, i.e. when all of `first`, `last` and `result` are iterators over
-  // contiguous storage of the same type modulo const qualifiers.
-  //
-  // libc++ implements this for its contiguous iterators by invoking the
-  // unqualified __unwrap_iter, which returns the underlying pointer for
-  // iterators over std::vector and std::string, and returns the original
-  // iterator otherwise.
-  //
-  // Thus in order to opt into this optimization for CCI, we need to provide our
-  // own __unwrap_iter, returning the underlying raw pointer if it is safe to do
-  // so.
-  //
-  // Furthermore, considering that std::copy is implemented as follows, the
-  // return type of __unwrap_iter(CCI) needs to be convertible to CCI, which is
-  // why an appropriate implicit single argument constructor is provided for the
-  // optimized case:
-  //
-  //     template <class InIter, class OutIter>
-  //     OutIter copy(InIter first, InIter last, OutIter result) {
-  //       return __copy(__unwrap_iter(first), __unwrap_iter(last),
-  //                     __unwrap_iter(result));
-  //     }
-  //
-  //     Unoptimized __copy() signature:
-  //     template <class InIter, class OutIter>
-  //     OutIter __copy(InIter first, InIter last, OutIter result);
-  //
-  //     Optimized __copy() signature:
-  //     template <class T, class U>
-  //     U* __copy(T* first, T* last, U* result);
-  //
-  // Finally, this single argument constructor sets all internal fields to the
-  // passed in pointer. This allows the resulting CCI to be used in other
-  // optimized calls to std::copy (or std::move, std::copy_backward,
-  // std::move_backward). However, it should not be used otherwise, since
-  // invoking any of its public API will result in a CHECK failure. This also
-  // means that callers should never use the single argument constructor
-  // directly.
-  template <typename U>
-  using PtrIfSafeToMemmove = std::enable_if_t<
-      std::is_trivially_copy_assignable<std::remove_const_t<U>>::value,
-      U*>;
-
-  template <int&... ExplicitArgumentBarrier, typename U = T>
-  constexpr CheckedContiguousIterator(PtrIfSafeToMemmove<U> ptr)
-      : start_(ptr), current_(ptr), end_(ptr) {}
-
-  template <int&... ExplicitArgumentBarrier, typename U = T>
-  friend constexpr PtrIfSafeToMemmove<U> __unwrap_iter(
-      CheckedContiguousIterator iter) {
-    return iter.current_;
-  }
+  // Required for certain libc++ algorithm optimizations that are not available
+  // for NaCl.
+#if defined(_LIBCPP_VERSION) && !defined(OS_NACL)
+  template <typename Ptr>
+  friend struct std::pointer_traits;
 #endif
 
+  constexpr CheckedContiguousIterator() = default;
+
   constexpr CheckedContiguousIterator(T* start, const T* end)
       : CheckedContiguousIterator(start, start, end) {}
+
   constexpr CheckedContiguousIterator(const T* start, T* current, const T* end)
       : start_(start), current_(current), end_(end) {
     CHECK_LE(start, current);
     CHECK_LE(current, end);
   }
+
   constexpr CheckedContiguousIterator(const CheckedContiguousIterator& other) =
       default;
 
@@ -269,4 +217,49 @@
 
 }  // namespace base
 
+#if defined(_LIBCPP_VERSION) && !defined(OS_NACL)
+// Specialize both std::__is_cpp17_contiguous_iterator and std::pointer_traits
+// for CCI in case we compile with libc++ outside of NaCl. The former is
+// required to enable certain algorithm optimizations (e.g. std::copy can be a
+// simple std::memmove under certain circumstances), and is a precursor to
+// C++20's std::contiguous_iterator concept [1]. Once we actually use C++20 it
+// will be enough to add `using iterator_concept = std::contiguous_iterator_tag`
+// to the iterator class [2], and we can get rid of this non-standard
+// specialization.
+//
+// The latter is required to obtain the underlying raw pointer without resulting
+// in CHECK failures. The important bit is the `to_address(pointer)` overload,
+// which is the standard blessed way to customize `std::to_address(pointer)` in
+// C++20 [3].
+//
+// [1] https://wg21.link/iterator.concept.contiguous
+// [2] https://wg21.link/std.iterator.tags
+// [3] https://wg21.link/pointer.traits.optmem
+namespace std {
+
+template <typename T>
+struct __is_cpp17_contiguous_iterator<::base::CheckedContiguousIterator<T>>
+    : true_type {};
+
+template <typename T>
+struct pointer_traits<::base::CheckedContiguousIterator<T>> {
+  using pointer = ::base::CheckedContiguousIterator<T>;
+  using element_type = T;
+  using difference_type = ptrdiff_t;
+
+  template <typename U>
+  using rebind = ::base::CheckedContiguousIterator<U>;
+
+  static constexpr pointer pointer_to(element_type& r) noexcept {
+    return pointer(&r, &r);
+  }
+
+  static constexpr element_type* to_address(pointer p) noexcept {
+    return p.current_;
+  }
+};
+
+}  // namespace std
+#endif
+
 #endif  // BASE_CONTAINERS_CHECKED_ITERATORS_H_
diff --git a/base/containers/checked_iterators_unittest.cc b/base/containers/checked_iterators_unittest.cc
index 9f201beb..c7afe06 100644
--- a/base/containers/checked_iterators_unittest.cc
+++ b/base/containers/checked_iterators_unittest.cc
@@ -7,6 +7,8 @@
 #include <algorithm>
 #include <iterator>
 
+#include "base/check_op.h"
+#include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -80,84 +82,77 @@
   EXPECT_GE(cbegin, begin);
 }
 
-#if defined(_LIBCPP_VERSION)
-// TODO(crbug.com/1166360): This optimization was broken by upstream libc++
-// changes. Work around the issue and re-enable the test.
-#if 0
+}  // namespace base
+
+// ChromeOS does not use the in-tree libc++, but rather a shared library that
+// lags a bit behind.
+// TODO(crbug.com/1166360): Enable this test on ChromeOS once the shared libc++
+// is sufficiently modern.
+#if defined(_LIBCPP_VERSION) && !defined(OS_NACL) && !defined(OS_CHROMEOS)
 namespace {
 
 // Helper template that wraps an iterator and disables its dereference and
 // increment operations.
+// Note: We don't simply delete these operations, because code using these
+// operations still needs to compile, even though the codepath will never be
+// taken at runtime. This will crash at runtime in case code does try to use
+// these operations.
 template <typename Iterator>
 struct DisableDerefAndIncr : Iterator {
   using Iterator::Iterator;
+  constexpr DisableDerefAndIncr(const Iterator& iter) : Iterator(iter) {}
 
-  void operator*() = delete;
-  void operator++() = delete;
-  void operator++(int) = delete;
+  constexpr typename Iterator::reference operator*() {
+    CHECK(false);
+    return Iterator::operator*();
+  }
+
+  constexpr Iterator& operator++() {
+    CHECK(false);
+    return Iterator::operator++();
+  }
+
+  constexpr Iterator operator++(int i) {
+    CHECK(false);
+    return Iterator::operator++(i);
+  }
 };
 
-template <typename Iterator>
-auto __unwrap_iter(DisableDerefAndIncr<Iterator> iter) {
-  return __unwrap_iter(static_cast<Iterator>(iter));
-}
-
 }  // namespace
 
+// Inherit `__is_cpp17_contiguous_iterator` and `pointer_traits` specializations
+// from the base class.
+namespace std {
+template <typename Iter>
+struct __is_cpp17_contiguous_iterator<DisableDerefAndIncr<Iter>>
+    : __is_cpp17_contiguous_iterator<Iter> {};
+
+template <typename Iter>
+struct pointer_traits<DisableDerefAndIncr<Iter>> : pointer_traits<Iter> {};
+}  // namespace std
+
+namespace base {
+
 // Tests that using std::copy with CheckedContiguousIterator<int> results in an
 // optimized code-path that does not invoke the iterator's dereference and
-// increment operations. This would fail to compile if std::copy was not
+// increment operations. This would fail at runtime if std::copy was not
 // optimized.
 TEST(CheckedContiguousIterator, OptimizedCopy) {
   using Iter = DisableDerefAndIncr<CheckedContiguousIterator<int>>;
-  static_assert(std::is_same<int*, decltype(__unwrap_iter(Iter()))>::value,
-                "Error: Iter should unwrap to int*");
 
   int arr_in[5] = {1, 2, 3, 4, 5};
   int arr_out[5];
 
-  Iter begin(std::begin(arr_in), std::end(arr_in));
-  Iter end(std::begin(arr_in), std::end(arr_in), std::end(arr_in));
-  std::copy(begin, end, arr_out);
-
-  EXPECT_TRUE(std::equal(std::begin(arr_in), std::end(arr_in),
-                         std::begin(arr_out), std::end(arr_out)));
-}
-#endif
-
-TEST(CheckedContiguousIterator, UnwrapIter) {
-  static_assert(
-      std::is_same<int*, decltype(__unwrap_iter(
-                             CheckedContiguousIterator<int>()))>::value,
-      "Error: CCI<int> should unwrap to int*");
-
-  static_assert(
-      std::is_same<CheckedContiguousIterator<std::string>,
-                   decltype(__unwrap_iter(
-                       CheckedContiguousIterator<std::string>()))>::value,
-      "Error: CCI<std::string> should unwrap to CCI<std::string>");
-}
-
-// While the result of std::copying into a range via a CCI can't be
-// compared to other iterators, it should be possible to re-use it in another
-// std::copy expresson.
-TEST(CheckedContiguousIterator, ReuseCopyIter) {
-  using Iter = CheckedContiguousIterator<int>;
-
-  int arr_in[5] = {1, 2, 3, 4, 5};
-  int arr_out[5];
-
-  Iter begin(std::begin(arr_in), std::end(arr_in));
-  Iter end(std::begin(arr_in), std::end(arr_in), std::end(arr_in));
+  Iter in_begin(std::begin(arr_in), std::end(arr_in));
+  Iter in_end(std::begin(arr_in), std::end(arr_in), std::end(arr_in));
   Iter out_begin(std::begin(arr_out), std::end(arr_out));
-
-  auto out_middle = std::copy_n(begin, 3, out_begin);
-  std::copy(begin + 3, end, out_middle);
+  Iter out_end = std::copy(in_begin, in_end, out_begin);
+  EXPECT_EQ(out_end, out_begin + (in_end - in_begin));
 
   EXPECT_TRUE(std::equal(std::begin(arr_in), std::end(arr_in),
                          std::begin(arr_out), std::end(arr_out)));
 }
 
-#endif
-
 }  // namespace base
+
+#endif
diff --git a/base/message_loop/message_pump_win.h b/base/message_loop/message_pump_win.h
index 786ae80..ab3a4b3 100644
--- a/base/message_loop/message_pump_win.h
+++ b/base/message_loop/message_pump_win.h
@@ -12,6 +12,7 @@
 #include <memory>
 
 #include "base/base_export.h"
+#include "base/compiler_specific.h"
 #include "base/location.h"
 #include "base/message_loop/message_pump.h"
 #include "base/observer_list.h"
@@ -152,7 +153,8 @@
                        LPARAM lparam,
                        LRESULT* result);
   void DoRunLoop() override;
-  void WaitForWork(Delegate::NextWorkInfo next_work_info);
+  NOINLINE void NOT_TAIL_CALLED
+  WaitForWork(Delegate::NextWorkInfo next_work_info);
   void HandleWorkMessage();
   void HandleTimerMessage();
   void ScheduleNativeTimer(Delegate::NextWorkInfo next_work_info);
@@ -289,7 +291,8 @@
   };
 
   void DoRunLoop() override;
-  void WaitForWork(Delegate::NextWorkInfo next_work_info);
+  NOINLINE void NOT_TAIL_CALLED
+  WaitForWork(Delegate::NextWorkInfo next_work_info);
   bool MatchCompletedIOItem(IOHandler* filter, IOItem* item);
   bool GetIOItem(DWORD timeout, IOItem* item);
   bool ProcessInternalIOItem(const IOItem& item);
diff --git a/base/synchronization/condition_variable.h b/base/synchronization/condition_variable.h
index f57ed132..13d2bb8 100644
--- a/base/synchronization/condition_variable.h
+++ b/base/synchronization/condition_variable.h
@@ -68,6 +68,7 @@
 
 #include "base/base_export.h"
 #include "base/check_op.h"
+#include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/synchronization/lock.h"
 #include "build/build_config.h"
@@ -90,8 +91,8 @@
   // Wait() releases the caller's critical section atomically as it starts to
   // sleep, and the reacquires it when it is signaled. The wait functions are
   // susceptible to spurious wakeups. (See usage note 1 for more details.)
-  void Wait();
-  void TimedWait(const TimeDelta& max_time);
+  void NOT_TAIL_CALLED Wait();
+  void NOT_TAIL_CALLED TimedWait(const TimeDelta& max_time);
 
   // Broadcast() revives all waiting threads. (See usage note 2 for more
   // details.)
diff --git a/base/synchronization/waitable_event.h b/base/synchronization/waitable_event.h
index 4a10f397..08da7e9 100644
--- a/base/synchronization/waitable_event.h
+++ b/base/synchronization/waitable_event.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 
 #include "base/base_export.h"
+#include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "build/build_config.h"
 
@@ -94,7 +95,7 @@
   //   SendToOtherThread(e);
   //   e->Wait();
   //   delete e;
-  void Wait();
+  void NOT_TAIL_CALLED Wait();
 
   // Wait up until wait_delta has passed for the event to be signaled
   // (real-time; ignores time overrides).  Returns true if the event was
@@ -102,7 +103,7 @@
   // have elapsed if this returns false.
   //
   // TimedWait can synchronise its own destruction like |Wait|.
-  bool TimedWait(const TimeDelta& wait_delta);
+  bool NOT_TAIL_CALLED TimedWait(const TimeDelta& wait_delta);
 
 #if defined(OS_WIN)
   HANDLE handle() const { return handle_.Get(); }
@@ -128,7 +129,8 @@
   //
   // If more than one WaitableEvent is signaled to unblock WaitMany, the lowest
   // index among them is returned.
-  static size_t WaitMany(WaitableEvent** waitables, size_t count);
+  static size_t NOT_TAIL_CALLED WaitMany(WaitableEvent** waitables,
+                                         size_t count);
 
   // For asynchronous waiting, see WaitableEventWatcher
 
diff --git a/build/android/gyp/proguard.py b/build/android/gyp/proguard.py
index 62c2c2d..9a257fd5 100755
--- a/build/android/gyp/proguard.py
+++ b/build/android/gyp/proguard.py
@@ -114,6 +114,15 @@
       action='append',
       help='List of name pairs separated by : mapping a feature module to a '
       'dependent feature module.')
+  parser.add_argument(
+      '--keep-rules-targets-regex',
+      metavar='KEEP_RULES_REGEX',
+      help='If passed outputs keep rules for references from all other inputs '
+      'to the subset of inputs that satisfy the KEEP_RULES_REGEX.')
+  parser.add_argument(
+      '--keep-rules-output-path',
+      help='Output path to the keep rules for references to the '
+      '--keep-rules-targets-regex inputs from the rest of the inputs.')
   parser.add_argument('--warnings-as-errors',
                       action='store_true',
                       help='Treat all warnings as errors.')
@@ -141,6 +150,11 @@
   elif not options.output_path:
     parser.error('Output path required when feature splits aren\'t used')
 
+  if bool(options.keep_rules_targets_regex) != bool(
+      options.keep_rules_output_path):
+    raise Exception('You must path both --keep-rules-targets-regex and '
+                    '--keep-rules-output-path')
+
   options.classpath = build_utils.ParseGnList(options.classpath)
   options.proguard_configs = build_utils.ParseGnList(options.proguard_configs)
   options.input_paths = build_utils.ParseGnList(options.input_paths)
@@ -411,6 +425,25 @@
   return base_context
 
 
+def _OutputKeepRules(r8_path, input_paths, classpath, targets_re_string,
+                     keep_rules_output):
+  cmd = build_utils.JavaCmd(False) + [
+      '-cp', r8_path, 'com.android.tools.r8.tracereferences.TraceReferences',
+      '--map-diagnostics:MissingDefinitionsDiagnostic', 'error', 'warning',
+      '--keep-rules', '--output', keep_rules_output
+  ]
+  targets_re = re.compile(targets_re_string)
+  for path in input_paths:
+    if targets_re.search(path):
+      cmd += ['--target', path]
+    else:
+      cmd += ['--source', path]
+  for path in classpath:
+    cmd += ['--lib', path]
+
+  build_utils.CheckOutput(cmd, print_stderr=False, fail_on_output=False)
+
+
 def _CheckForMissingSymbols(r8_path, dex_files, classpath, warnings_as_errors):
   cmd = build_utils.JavaCmd(warnings_as_errors) + [
       '-cp', r8_path, 'com.android.tools.r8.tracereferences.TraceReferences',
@@ -636,6 +669,11 @@
     if p not in libraries and p not in options.input_paths:
       libraries.append(p)
   _VerifyNoEmbeddedConfigs(options.input_paths + libraries)
+  if options.keep_rules_output_path:
+    _OutputKeepRules(options.r8_path, options.input_paths, options.classpath,
+                     options.keep_rules_targets_regex,
+                     options.keep_rules_output_path)
+    return
 
   base_context = _OptimizeWithR8(options, proguard_configs, libraries,
                                  dynamic_config_data, print_stdout)
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index c6dc423..a1355b31 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -424,17 +424,9 @@
 A list of uncompressed assets stored in the APK. Each entry has the format
 `<source-path>:<destination-path>` too.
 
-* `compressed_locales_java_list`
+* `locales_java_list`
 A string holding a Java source fragment that gives the list of locales stored
-compressed in the `//assets/` directory. E.g. `"{\"am\","\ar\",\"en-US\"}"`.
-Note that the files will be stored with the `.pak` extension (e.g.
-`//assets/en-US.pak`).
-
-* `uncompressed_locales_java_list`
-A string holding a Java source fragment that gives the list of locales stored
-uncompressed in the `//assets/stored-locales/` directory. These are used for
-the System WebView feature only. Note that the files will be stored with the
-`.pak` extension (e.g. `//assets/stored-locales/en-US.apk`).
+uncompressed as android assets.
 
 * `extra_android_manifests`
 A list of `deps_configs['android_manifest]` entries, for all resource
@@ -891,7 +883,7 @@
   """
   assets_paths = [a.split(':')[1] for a in assets]
   locales = [os.path.basename(a)[:-4] for a in assets_paths if a in locale_paks]
-  return '{%s}' % ','.join(['"%s"' % l for l in sorted(locales)])
+  return '{%s}' % ','.join('"%s"' % l for l in sorted(locales))
 
 
 def _AddJarMapping(jar_to_target, configs):
@@ -1962,11 +1954,8 @@
     config['assets'], config['uncompressed_assets'], locale_paks = (
         _MergeAssets(deps.All('android_assets')))
 
-    deps_info['compressed_locales_java_list'] = _CreateJavaLocaleListFromAssets(
-        config['assets'], locale_paks)
-    deps_info[
-        'uncompressed_locales_java_list'] = _CreateJavaLocaleListFromAssets(
-            config['uncompressed_assets'], locale_paks)
+    deps_info['locales_java_list'] = _CreateJavaLocaleListFromAssets(
+        config['uncompressed_assets'], locale_paks)
 
     config['extra_android_manifests'] = []
     for c in extra_manifest_deps:
diff --git a/build/android/java/templates/ProductConfig.template b/build/android/java/templates/ProductConfig.template
index dee8f82..4bc0d52 100644
--- a/build/android/java/templates/ProductConfig.template
+++ b/build/android/java/templates/ProductConfig.template
@@ -20,21 +20,12 @@
  *  Product configuration. Generated on a per-target basis.
  */
 public class ProductConfig {
-
-    // Sorted list of locales that have a compressed .pak within assets.
-    // Stored as an array because AssetManager.list() is slow.
-#if defined(COMPRESSED_LOCALE_LIST)
-    public static final String[] COMPRESSED_LOCALES = COMPRESSED_LOCALE_LIST;
-#else
-    public static final String[] COMPRESSED_LOCALES = {};
-#endif
-
     // Sorted list of locales that have an uncompressed .pak within assets.
     // Stored as an array because AssetManager.list() is slow.
-#if defined(UNCOMPRESSED_LOCALE_LIST)
-    public static final String[] UNCOMPRESSED_LOCALES = UNCOMPRESSED_LOCALE_LIST;
+#if defined(LOCALE_LIST)
+    public static final String[] LOCALES = LOCALE_LIST;
 #else
-    public static final String[] UNCOMPRESSED_LOCALES = {};
+    public static final String[] LOCALES = {};
 #endif
 
    public static MAYBE_FINAL boolean USE_CHROMIUM_LINKER MAYBE_USE_CHROMIUM_LINKER;
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 372d2b0..719a636 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2011,10 +2011,7 @@
         forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ])
         _rebased_build_config =
             rebase_path(invoker.build_config, root_build_dir)
-        defines += [
-          "COMPRESSED_LOCALE_LIST=" + "@FileArg($_rebased_build_config:deps_info:compressed_locales_java_list)",
-          "UNCOMPRESSED_LOCALE_LIST=" + "@FileArg($_rebased_build_config:deps_info:uncompressed_locales_java_list)",
-        ]
+        defines += [ "LOCALE_LIST=@FileArg($_rebased_build_config:deps_info:locales_java_list)" ]
       }
     }
   }
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index 7b3d259..28e57967 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -67,10 +67,11 @@
   # Use it by default on official-optimized android and Chrome OS builds, but
   # not ARC or linux-chromeos since it's been seen to not play nicely with
   # Chrome's clang. crbug.com/1033839
-  use_thin_lto = is_cfi || (is_official_build &&
-                            (is_win || target_os == "android" ||
-                             ((is_chromeos_ash || is_chromeos_lacros) &&
-                              is_chromeos_device)))
+  use_thin_lto =
+      is_cfi ||
+      (is_official_build &&
+       ((is_win && chrome_pgo_phase != 1) || target_os == "android" ||
+        ((is_chromeos_ash || is_chromeos_lacros) && is_chromeos_device)))
 
   # If true, use Goma for ThinLTO code generation where applicable.
   use_goma_thin_lto = false
diff --git a/cc/animation/BUILD.gn b/cc/animation/BUILD.gn
index 6703d94..90fb826 100644
--- a/cc/animation/BUILD.gn
+++ b/cc/animation/BUILD.gn
@@ -19,7 +19,6 @@
     "animation_host.h",
     "animation_id_provider.cc",
     "animation_id_provider.h",
-    "animation_target.h",
     "animation_timeline.cc",
     "animation_timeline.h",
     "element_animations.cc",
diff --git a/cc/animation/animation_curve.cc b/cc/animation/animation_curve.cc
index 1a46580f..d8183b5 100644
--- a/cc/animation/animation_curve.cc
+++ b/cc/animation/animation_curve.cc
@@ -9,61 +9,38 @@
 
 namespace cc {
 
-const ColorAnimationCurve* AnimationCurve::ToColorAnimationCurve() const {
-  DCHECK(Type() == AnimationCurve::COLOR);
-  return static_cast<const ColorAnimationCurve*>(this);
+bool AnimationCurve::PreservesAxisAlignment() const {
+  return true;
 }
 
-AnimationCurve::CurveType ColorAnimationCurve::Type() const {
-  return COLOR;
+bool AnimationCurve::MaximumScale(float* max_scale) const {
+  return false;
 }
 
-const FloatAnimationCurve* AnimationCurve::ToFloatAnimationCurve() const {
-  DCHECK(Type() == AnimationCurve::FLOAT);
-  return static_cast<const FloatAnimationCurve*>(this);
-}
+#define DEFINE_ANIMATION_CURVE(Name, CurveType)                                \
+  void Name##AnimationCurve::Tick(base::TimeDelta t, int property_id,          \
+                                  KeyframeModel* keyframe_model) const {       \
+    if (target_) {                                                             \
+      target_->On##Name##Animated(GetValue(t), property_id, keyframe_model);   \
+    }                                                                          \
+  }                                                                            \
+  int Name##AnimationCurve::Type() const { return AnimationCurve::CurveType; } \
+  const char* Name##AnimationCurve::TypeName() const { return #Name; }         \
+  const Name##AnimationCurve* Name##AnimationCurve::To##Name##AnimationCurve(  \
+      const AnimationCurve* c) {                                               \
+    DCHECK_EQ(AnimationCurve::CurveType, c->Type());                           \
+    return static_cast<const Name##AnimationCurve*>(c);                        \
+  }                                                                            \
+  Name##AnimationCurve* Name##AnimationCurve::To##Name##AnimationCurve(        \
+      AnimationCurve* c) {                                                     \
+    DCHECK_EQ(AnimationCurve::CurveType, c->Type());                           \
+    return static_cast<Name##AnimationCurve*>(c);                              \
+  }
 
-AnimationCurve::CurveType FloatAnimationCurve::Type() const {
-  return FLOAT;
-}
-
-const TransformAnimationCurve* AnimationCurve::ToTransformAnimationCurve()
-    const {
-  DCHECK(Type() == AnimationCurve::TRANSFORM);
-  return static_cast<const TransformAnimationCurve*>(this);
-}
-
-AnimationCurve::CurveType TransformAnimationCurve::Type() const {
-  return TRANSFORM;
-}
-
-const FilterAnimationCurve* AnimationCurve::ToFilterAnimationCurve() const {
-  DCHECK(Type() == AnimationCurve::FILTER);
-  return static_cast<const FilterAnimationCurve*>(this);
-}
-
-AnimationCurve::CurveType FilterAnimationCurve::Type() const {
-  return FILTER;
-}
-
-const ScrollOffsetAnimationCurve* AnimationCurve::ToScrollOffsetAnimationCurve()
-    const {
-  DCHECK(Type() == AnimationCurve::SCROLL_OFFSET);
-  return static_cast<const ScrollOffsetAnimationCurve*>(this);
-}
-
-ScrollOffsetAnimationCurve* AnimationCurve::ToScrollOffsetAnimationCurve() {
-  DCHECK(Type() == AnimationCurve::SCROLL_OFFSET);
-  return static_cast<ScrollOffsetAnimationCurve*>(this);
-}
-
-const SizeAnimationCurve* AnimationCurve::ToSizeAnimationCurve() const {
-  DCHECK(Type() == AnimationCurve::SIZE);
-  return static_cast<const SizeAnimationCurve*>(this);
-}
-
-AnimationCurve::CurveType SizeAnimationCurve::Type() const {
-  return SIZE;
-}
+DEFINE_ANIMATION_CURVE(Transform, TRANSFORM)
+DEFINE_ANIMATION_CURVE(Float, FLOAT)
+DEFINE_ANIMATION_CURVE(Size, SIZE)
+DEFINE_ANIMATION_CURVE(Filter, FILTER)
+DEFINE_ANIMATION_CURVE(Color, COLOR)
 
 }  // namespace cc
diff --git a/cc/animation/animation_curve.h b/cc/animation/animation_curve.h
index d7dbc993d..c62a1206 100644
--- a/cc/animation/animation_curve.h
+++ b/cc/animation/animation_curve.h
@@ -12,6 +12,7 @@
 #include "cc/paint/filter_operations.h"
 #include "ui/gfx/geometry/size_f.h"
 #include "ui/gfx/transform.h"
+#include "ui/gfx/transform_operations.h"
 
 namespace gfx {
 class TransformOperations;
@@ -19,16 +20,16 @@
 
 namespace cc {
 
-class ColorAnimationCurve;
-class FilterAnimationCurve;
-class FloatAnimationCurve;
-class ScrollOffsetAnimationCurve;
-class SizeAnimationCurve;
-class TransformAnimationCurve;
+class KeyframeModel;
+class AnimationCurve;
 
 // An animation curve is a function that returns a value given a time.
 class CC_ANIMATION_EXPORT AnimationCurve {
  public:
+  // TODO(crbug.com/1176334): we shouldn't need the curve type, long term.
+  //
+  // In the meanime, external clients of the animation machinery may well have
+  // other curve types and should be added to this enum to ensure uniqueness.
   enum CurveType {
     COLOR = 0,
     FLOAT,
@@ -40,74 +41,66 @@
     LAST_CURVE_TYPE = SIZE,
   };
 
-  virtual ~AnimationCurve() {}
+  virtual ~AnimationCurve() = default;
 
   virtual base::TimeDelta Duration() const = 0;
-  virtual CurveType Type() const = 0;
+  virtual int Type() const = 0;
+  virtual const char* TypeName() const = 0;
   virtual std::unique_ptr<AnimationCurve> Clone() const = 0;
-
-  const ColorAnimationCurve* ToColorAnimationCurve() const;
-  const FloatAnimationCurve* ToFloatAnimationCurve() const;
-  const TransformAnimationCurve* ToTransformAnimationCurve() const;
-  const FilterAnimationCurve* ToFilterAnimationCurve() const;
-  const ScrollOffsetAnimationCurve* ToScrollOffsetAnimationCurve() const;
-  const SizeAnimationCurve* ToSizeAnimationCurve() const;
-
-  ScrollOffsetAnimationCurve* ToScrollOffsetAnimationCurve();
-};
-
-class CC_ANIMATION_EXPORT ColorAnimationCurve : public AnimationCurve {
- public:
-  ~ColorAnimationCurve() override {}
-
-  virtual SkColor GetValue(base::TimeDelta t) const = 0;
-
-  CurveType Type() const override;
-};
-
-class CC_ANIMATION_EXPORT FloatAnimationCurve : public AnimationCurve {
- public:
-  ~FloatAnimationCurve() override {}
-
-  virtual float GetValue(base::TimeDelta t) const = 0;
-
-  CurveType Type() const override;
-};
-
-class CC_ANIMATION_EXPORT TransformAnimationCurve : public AnimationCurve {
- public:
-  ~TransformAnimationCurve() override {}
-
-  virtual gfx::TransformOperations GetValue(base::TimeDelta t) const = 0;
+  virtual void Tick(base::TimeDelta t,
+                    int property_id,
+                    KeyframeModel* keyframe_model) const = 0;
 
   // Returns true if this animation preserves axis alignment.
-  virtual bool PreservesAxisAlignment() const = 0;
+  virtual bool PreservesAxisAlignment() const;
 
   // Set |max_scale| to the maximum scale along any dimension during the
   // animation, of all steps (keyframes) with calculatable scale. Returns
   // false if none of the steps can calculate a scale.
-  virtual bool MaximumScale(float* max_scale) const = 0;
-
-  CurveType Type() const override;
+  virtual bool MaximumScale(float* max_scale) const;
 };
 
-class CC_ANIMATION_EXPORT FilterAnimationCurve : public AnimationCurve {
- public:
-  ~FilterAnimationCurve() override {}
+#define DECLARE_ANIMATION_CURVE_BODY(T, Name)                                  \
+ public:                                                                       \
+  static const Name##AnimationCurve* To##Name##AnimationCurve(                 \
+      const AnimationCurve* c);                                                \
+  static Name##AnimationCurve* To##Name##AnimationCurve(AnimationCurve* c);    \
+  class Target {                                                               \
+   public:                                                                     \
+    virtual ~Target() = default;                                               \
+    virtual void On##Name##Animated(const T& value,                            \
+                                    int target_property_id,                    \
+                                    KeyframeModel* keyframe_model) = 0;        \
+  };                                                                           \
+  ~Name##AnimationCurve() override = default;                                  \
+  virtual T GetValue(base::TimeDelta t) const = 0;                             \
+  void Tick(base::TimeDelta t, int property_id, KeyframeModel* keyframe_model) \
+      const override;                                                          \
+  void set_target(Target* target) { target_ = target; }                        \
+  int Type() const override;                                                   \
+  const char* TypeName() const override;                                       \
+                                                                               \
+ private:                                                                      \
+  Target* target_ = nullptr;
 
-  virtual FilterOperations GetValue(base::TimeDelta t) const = 0;
-  virtual bool HasFilterThatMovesPixels() const = 0;
+class CC_ANIMATION_EXPORT ColorAnimationCurve : public AnimationCurve {
+  DECLARE_ANIMATION_CURVE_BODY(SkColor, Color)
+};
 
-  CurveType Type() const override;
+class CC_ANIMATION_EXPORT FloatAnimationCurve : public AnimationCurve {
+  DECLARE_ANIMATION_CURVE_BODY(float, Float)
 };
 
 class CC_ANIMATION_EXPORT SizeAnimationCurve : public AnimationCurve {
- public:
-  ~SizeAnimationCurve() override {}
+  DECLARE_ANIMATION_CURVE_BODY(gfx::SizeF, Size)
+};
 
-  virtual gfx::SizeF GetValue(base::TimeDelta t) const = 0;
+class CC_ANIMATION_EXPORT FilterAnimationCurve : public AnimationCurve {
+  DECLARE_ANIMATION_CURVE_BODY(FilterOperations, Filter)
+};
 
-  CurveType Type() const override;
+class CC_ANIMATION_EXPORT TransformAnimationCurve : public AnimationCurve {
+  DECLARE_ANIMATION_CURVE_BODY(gfx::TransformOperations, Transform)
 };
 
 }  // namespace cc
diff --git a/cc/animation/animation_target.h b/cc/animation/animation_target.h
deleted file mode 100644
index 8d19f25..0000000
--- a/cc/animation/animation_target.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2017 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 CC_ANIMATION_ANIMATION_TARGET_H_
-#define CC_ANIMATION_ANIMATION_TARGET_H_
-
-#include "cc/animation/animation_export.h"
-#include "third_party/skia/include/core/SkColor.h"
-
-namespace gfx {
-class ScrollOffset;
-class SizeF;
-class TransformOperations;
-}  // namespace gfx
-
-namespace cc {
-
-class FilterOperations;
-class KeyframeModel;
-
-// An AnimationTarget is an entity that can be affected by a ticking
-// cc:KeyframeModel. Any object that expects to have an opacity update, for
-// example, should derive from this class.
-class CC_ANIMATION_EXPORT AnimationTarget {
- public:
-  virtual ~AnimationTarget() {}
-  virtual void NotifyClientFloatAnimated(float value,
-                                         int target_property_id,
-                                         KeyframeModel* keyframe_model) = 0;
-  virtual void NotifyClientFilterAnimated(const FilterOperations& filter,
-                                          int target_property_id,
-                                          KeyframeModel* keyframe_model) = 0;
-  virtual void NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                        int target_property_id,
-                                        KeyframeModel* keyframe_model) = 0;
-  virtual void NotifyClientColorAnimated(SkColor color,
-                                         int target_property_id,
-                                         KeyframeModel* keyframe_model) = 0;
-  virtual void NotifyClientTransformOperationsAnimated(
-      const gfx::TransformOperations& operations,
-      int target_property_id,
-      KeyframeModel* keyframe_model) = 0;
-  virtual void NotifyClientScrollOffsetAnimated(
-      const gfx::ScrollOffset& scroll_offset,
-      int target_property_id,
-      KeyframeModel* keyframe_model) = 0;
-};
-
-}  // namespace cc
-
-#endif  // CC_ANIMATION_ANIMATION_TARGET_H_
diff --git a/cc/animation/element_animations.cc b/cc/animation/element_animations.cc
index 4607f2e5..c92418d 100644
--- a/cc/animation/element_animations.cc
+++ b/cc/animation/element_animations.cc
@@ -205,10 +205,9 @@
   return false;
 }
 
-void ElementAnimations::NotifyClientFloatAnimated(
-    float value,
-    int target_property_id,
-    KeyframeModel* keyframe_model) {
+void ElementAnimations::OnFloatAnimated(const float& value,
+                                        int target_property_id,
+                                        KeyframeModel* keyframe_model) {
   switch (keyframe_model->target_property_type()) {
     case TargetProperty::CSS_CUSTOM_PROPERTY:
     case TargetProperty::NATIVE_PROPERTY:
@@ -233,10 +232,9 @@
   }
 }
 
-void ElementAnimations::NotifyClientFilterAnimated(
-    const FilterOperations& filters,
-    int target_property_id,
-    KeyframeModel* keyframe_model) {
+void ElementAnimations::OnFilterAnimated(const FilterOperations& filters,
+                                         int target_property_id,
+                                         KeyframeModel* keyframe_model) {
   switch (keyframe_model->target_property_type()) {
     case TargetProperty::BACKDROP_FILTER:
       if (KeyframeModelAffectsActiveElements(keyframe_model))
@@ -257,17 +255,16 @@
   }
 }
 
-void ElementAnimations::NotifyClientColorAnimated(
-    SkColor value,
-    int target_property_id,
-    KeyframeModel* keyframe_model) {
+void ElementAnimations::OnColorAnimated(const SkColor& value,
+                                        int target_property_id,
+                                        KeyframeModel* keyframe_model) {
   DCHECK_EQ(keyframe_model->target_property_type(),
             TargetProperty::CSS_CUSTOM_PROPERTY);
   OnCustomPropertyAnimated(PaintWorkletInput::PropertyValue(value),
                            keyframe_model, target_property_id);
 }
 
-void ElementAnimations::NotifyClientTransformOperationsAnimated(
+void ElementAnimations::OnTransformAnimated(
     const gfx::TransformOperations& operations,
     int target_property_id,
     KeyframeModel* keyframe_model) {
@@ -278,7 +275,7 @@
     OnTransformAnimated(ElementListType::PENDING, transform, keyframe_model);
 }
 
-void ElementAnimations::NotifyClientScrollOffsetAnimated(
+void ElementAnimations::OnScrollOffsetAnimated(
     const gfx::ScrollOffset& scroll_offset,
     int target_property_id,
     KeyframeModel* keyframe_model) {
@@ -370,6 +367,30 @@
   }
 }
 
+void ElementAnimations::AttachToCurve(AnimationCurve* c) {
+  switch (c->Type()) {
+    case AnimationCurve::COLOR:
+      ColorAnimationCurve::ToColorAnimationCurve(c)->set_target(this);
+      break;
+    case AnimationCurve::FLOAT:
+      FloatAnimationCurve::ToFloatAnimationCurve(c)->set_target(this);
+      break;
+    case AnimationCurve::TRANSFORM:
+      TransformAnimationCurve::ToTransformAnimationCurve(c)->set_target(this);
+      break;
+    case AnimationCurve::FILTER:
+      FilterAnimationCurve::ToFilterAnimationCurve(c)->set_target(this);
+      break;
+    case AnimationCurve::SCROLL_OFFSET:
+      ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(c)->set_target(
+          this);
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+}
+
 bool ElementAnimations::HasTickingKeyframeEffect() const {
   for (auto& keyframe_effect : keyframe_effects_list_) {
     if (keyframe_effect.HasTickingKeyframeModel())
diff --git a/cc/animation/element_animations.h b/cc/animation/element_animations.h
index 1586167..f8a5528 100644
--- a/cc/animation/element_animations.h
+++ b/cc/animation/element_animations.h
@@ -10,8 +10,9 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
+#include "cc/animation/animation_curve.h"
 #include "cc/animation/animation_export.h"
-#include "cc/animation/animation_target.h"
+#include "cc/animation/scroll_offset_animation_curve.h"
 #include "cc/paint/element_id.h"
 #include "cc/paint/paint_worklet_input.h"
 #include "cc/trees/property_animation_state.h"
@@ -37,7 +38,11 @@
 // of the word; this naming is a legacy leftover. A target is just an amorphous
 // blob that has properties that can be animated.
 class CC_ANIMATION_EXPORT ElementAnimations
-    : public AnimationTarget,
+    : public FloatAnimationCurve::Target,
+      public FilterAnimationCurve::Target,
+      public ColorAnimationCurve::Target,
+      public TransformAnimationCurve::Target,
+      public ScrollOffsetAnimationCurve::Target,
       public base::RefCounted<ElementAnimations> {
  public:
   static scoped_refptr<ElementAnimations> Create(AnimationHost* host,
@@ -127,25 +132,28 @@
   // that have changed since the last update.
   void UpdateClientAnimationState();
 
-  void NotifyClientFloatAnimated(float value,
-                                 int target_property_id,
-                                 KeyframeModel* keyframe_model) override;
-  void NotifyClientFilterAnimated(const FilterOperations& filter,
-                                  int target_property_id,
-                                  KeyframeModel* keyframe_model) override;
-  void NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                int target_property_id,
-                                KeyframeModel* keyframe_model) override {}
-  void NotifyClientColorAnimated(SkColor color,
-                                 int target_property_id,
-                                 KeyframeModel* keyframe_model) override;
-  void NotifyClientTransformOperationsAnimated(
-      const gfx::TransformOperations& operations,
-      int target_property_id,
-      KeyframeModel* keyframe_model) override;
-  void NotifyClientScrollOffsetAnimated(const gfx::ScrollOffset& scroll_offset,
-                                        int target_property_id,
-                                        KeyframeModel* keyframe_model) override;
+  // TODO(crbug.com/1176334): Animation targets should be attached to curves
+  // when they're created and the concrete subclass is known. This function
+  // exists as a stopgap: the animation machinery previously expected to
+  // announce a target and then pass curves that would implicitly animate the
+  // target (i.e., the machinery handled the attachment).
+  void AttachToCurve(AnimationCurve* c);
+
+  void OnFloatAnimated(const float& value,
+                       int target_property_id,
+                       KeyframeModel* keyframe_model) override;
+  void OnFilterAnimated(const FilterOperations& filter,
+                        int target_property_id,
+                        KeyframeModel* keyframe_model) override;
+  void OnColorAnimated(const SkColor& color,
+                       int target_property_id,
+                       KeyframeModel* keyframe_model) override;
+  void OnTransformAnimated(const gfx::TransformOperations& operations,
+                           int target_property_id,
+                           KeyframeModel* keyframe_model) override;
+  void OnScrollOffsetAnimated(const gfx::ScrollOffset& scroll_offset,
+                              int target_property_id,
+                              KeyframeModel* keyframe_model) override;
 
   gfx::ScrollOffset ScrollOffsetForAnimation() const;
 
diff --git a/cc/animation/element_animations_unittest.cc b/cc/animation/element_animations_unittest.cc
index 8a32c452..3a24da4 100644
--- a/cc/animation/element_animations_unittest.cc
+++ b/cc/animation/element_animations_unittest.cc
@@ -276,11 +276,11 @@
       KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   animation_->AddKeyframeModel(std::move(animation_fixed));
   PushProperties();
-  EXPECT_VECTOR2DF_EQ(initial_value, animation_impl_->keyframe_effect()
-                                         ->GetKeyframeModelById(animation1_id)
-                                         ->curve()
-                                         ->ToScrollOffsetAnimationCurve()
-                                         ->GetValue(base::TimeDelta()));
+  auto* scroll_curve = ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
+      animation_impl_->keyframe_effect()
+          ->GetKeyframeModelById(animation1_id)
+          ->curve());
+  EXPECT_VECTOR2DF_EQ(initial_value, scroll_curve->GetValue(base::TimeDelta()));
   animation_->RemoveKeyframeModel(animation1_id);
 
   // Animation without initial value set.
@@ -293,12 +293,12 @@
       KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   animation_->AddKeyframeModel(std::move(keyframe_model));
   PushProperties();
+  scroll_curve = static_cast<ScrollOffsetAnimationCurve*>(
+      animation_impl_->keyframe_effect()
+          ->GetKeyframeModelById(animation2_id)
+          ->curve());
   EXPECT_VECTOR2DF_EQ(provider_initial_value,
-                      animation_impl_->keyframe_effect()
-                          ->GetKeyframeModelById(animation2_id)
-                          ->curve()
-                          ->ToScrollOffsetAnimationCurve()
-                          ->GetValue(base::TimeDelta()));
+                      scroll_curve->GetValue(base::TimeDelta()));
   animation_->RemoveKeyframeModel(animation2_id);
 }
 
@@ -2092,9 +2092,9 @@
   EXPECT_EQ(1u, events->events_.size());
   EXPECT_EQ(AnimationEvent::TAKEOVER, events->events_[0].type);
   EXPECT_EQ(TicksFromSecondsF(123), events->events_[0].animation_start_time);
-  EXPECT_EQ(
-      target_value,
-      events->events_[0].curve->ToScrollOffsetAnimationCurve()->target_value());
+  EXPECT_EQ(target_value, static_cast<ScrollOffsetAnimationCurve*>(
+                              events->events_[0].curve.get())
+                              ->target_value());
   EXPECT_EQ(nullptr,
             animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET));
 
diff --git a/cc/animation/keyframe_effect.cc b/cc/animation/keyframe_effect.cc
index 1719eba8..7081f13 100644
--- a/cc/animation/keyframe_effect.cc
+++ b/cc/animation/keyframe_effect.cc
@@ -111,8 +111,7 @@
     StartKeyframeModels(monotonic_time);
 
   for (auto& keyframe_model : keyframe_models_) {
-    TickKeyframeModel(monotonic_time, keyframe_model.get(),
-                      element_animations_.get());
+    TickKeyframeModel(monotonic_time, keyframe_model.get());
   }
 
   last_tick_time_ = monotonic_time;
@@ -120,8 +119,7 @@
 }
 
 void KeyframeEffect::TickKeyframeModel(base::TimeTicks monotonic_time,
-                                       KeyframeModel* keyframe_model,
-                                       AnimationTarget* target) {
+                                       KeyframeModel* keyframe_model) {
   if ((keyframe_model->run_state() != KeyframeModel::STARTING &&
        keyframe_model->run_state() != KeyframeModel::RUNNING &&
        keyframe_model->run_state() != KeyframeModel::PAUSED) ||
@@ -132,39 +130,7 @@
   AnimationCurve* curve = keyframe_model->curve();
   base::TimeDelta trimmed =
       keyframe_model->TrimTimeToCurrentIteration(monotonic_time);
-
-  switch (curve->Type()) {
-    case AnimationCurve::TRANSFORM:
-      target->NotifyClientTransformOperationsAnimated(
-          curve->ToTransformAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_type(), keyframe_model);
-      break;
-    case AnimationCurve::FLOAT:
-      target->NotifyClientFloatAnimated(
-          curve->ToFloatAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_type(), keyframe_model);
-      break;
-    case AnimationCurve::FILTER:
-      target->NotifyClientFilterAnimated(
-          curve->ToFilterAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_type(), keyframe_model);
-      break;
-    case AnimationCurve::COLOR:
-      target->NotifyClientColorAnimated(
-          curve->ToColorAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_type(), keyframe_model);
-      break;
-    case AnimationCurve::SCROLL_OFFSET:
-      target->NotifyClientScrollOffsetAnimated(
-          curve->ToScrollOffsetAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_type(), keyframe_model);
-      break;
-    case AnimationCurve::SIZE:
-      target->NotifyClientSizeAnimated(
-          curve->ToSizeAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_type(), keyframe_model);
-      break;
-  }
+  curve->Tick(trimmed, keyframe_model->target_property_type(), keyframe_model);
 }
 
 void KeyframeEffect::RemoveFromTicking() {
@@ -401,6 +367,9 @@
   needs_to_start_keyframe_models_ = true;
 
   UpdateTickingState();
+  for (auto& keyframe_model : keyframe_models_) {
+    element_animations_->AttachToCurve(keyframe_model->curve());
+  }
   element_animations_->UpdateClientAnimationState();
 }
 
@@ -490,13 +459,10 @@
 
 bool KeyframeEffect::AnimationsPreserveAxisAlignment() const {
   for (const auto& keyframe_model : keyframe_models_) {
-    if (keyframe_model->is_finished() ||
-        keyframe_model->target_property_type() != TargetProperty::TRANSFORM)
+    if (keyframe_model->is_finished())
       continue;
 
-    const TransformAnimationCurve* transform_animation_curve =
-        keyframe_model->curve()->ToTransformAnimationCurve();
-    if (!transform_animation_curve->PreservesAxisAlignment())
+    if (!keyframe_model->curve()->PreservesAxisAlignment())
       return false;
   }
   return true;
@@ -505,8 +471,7 @@
 float KeyframeEffect::MaximumScale(ElementListType list_type) const {
   float maximum_scale = kInvalidScale;
   for (const auto& keyframe_model : keyframe_models_) {
-    if (keyframe_model->is_finished() ||
-        keyframe_model->target_property_type() != TargetProperty::TRANSFORM)
+    if (keyframe_model->is_finished())
       continue;
 
     if ((list_type == ElementListType::ACTIVE &&
@@ -515,10 +480,8 @@
          !keyframe_model->affects_pending_elements()))
       continue;
 
-    const TransformAnimationCurve* transform_animation_curve =
-        keyframe_model->curve()->ToTransformAnimationCurve();
     float curve_maximum_scale = kInvalidScale;
-    if (transform_animation_curve->MaximumScale(&curve_maximum_scale))
+    if (keyframe_model->curve()->MaximumScale(&curve_maximum_scale))
       maximum_scale = std::max(maximum_scale, curve_maximum_scale);
   }
   return maximum_scale;
@@ -664,8 +627,8 @@
 
     if (keyframe_model->target_property_type() ==
             TargetProperty::SCROLL_OFFSET &&
-        !keyframe_model->curve()
-             ->ToScrollOffsetAnimationCurve()
+        !ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
+             keyframe_model->curve())
              ->HasSetInitialValue()) {
       gfx::ScrollOffset current_scroll_offset;
       if (keyframe_effect_impl->HasElementInActiveList()) {
@@ -676,8 +639,10 @@
         // scroll offset will be up to date.
         current_scroll_offset = ScrollOffsetForAnimation();
       }
-      keyframe_model->curve()->ToScrollOffsetAnimationCurve()->SetInitialValue(
-          current_scroll_offset);
+      ScrollOffsetAnimationCurve* curve =
+          ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
+              keyframe_model->curve());
+      curve->SetInitialValue(current_scroll_offset);
     }
 
     // The new keyframe_model should be set to run as soon as possible.
@@ -1080,7 +1045,8 @@
                                 monotonic_time);
   takeover_event.animation_start_time = keyframe_model.start_time();
   const ScrollOffsetAnimationCurve* scroll_offset_animation_curve =
-      keyframe_model.curve()->ToScrollOffsetAnimationCurve();
+      ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
+          keyframe_model.curve());
   takeover_event.curve = scroll_offset_animation_curve->Clone();
   // Notify main thread.
   events->events_.push_back(takeover_event);
diff --git a/cc/animation/keyframe_effect.h b/cc/animation/keyframe_effect.h
index c88f807..b7e1553 100644
--- a/cc/animation/keyframe_effect.h
+++ b/cc/animation/keyframe_effect.h
@@ -81,8 +81,7 @@
 
   virtual void Tick(base::TimeTicks monotonic_time);
   static void TickKeyframeModel(base::TimeTicks monotonic_time,
-                                KeyframeModel* keyframe_model,
-                                AnimationTarget* target);
+                                KeyframeModel* keyframe_model);
   void RemoveFromTicking();
 
   void UpdateState(bool start_ready_keyframe_models, AnimationEvents* events);
diff --git a/cc/animation/keyframe_model.cc b/cc/animation/keyframe_model.cc
index c718ea9..c537c1ed 100644
--- a/cc/animation/keyframe_model.cc
+++ b/cc/animation/keyframe_model.cc
@@ -36,14 +36,6 @@
               "RunStateEnumSize should equal the number of elements in "
               "s_runStateNames");
 
-static const char* const s_curveTypeNames[] = {
-    "COLOR", "FLOAT", "TRANSFORM", "FILTER", "SCROLL_OFFSET", "SIZE"};
-
-static_assert(static_cast<int>(cc::AnimationCurve::LAST_CURVE_TYPE) + 1 ==
-                  base::size(s_curveTypeNames),
-              "CurveType enum should equal the number of elements in "
-              "s_runStateNames");
-
 }  // namespace
 
 namespace cc {
@@ -138,8 +130,8 @@
                                 base::TimeTicks monotonic_time) {
   char name_buffer[256];
   base::snprintf(name_buffer, sizeof(name_buffer), "%s-%d-%d",
-                 s_curveTypeNames[curve_->Type()],
-                 target_property_id_.target_property_type(), group_);
+                 curve_->TypeName(), target_property_id_.target_property_type(),
+                 group_);
 
   bool is_waiting_to_start =
       run_state_ == WAITING_FOR_TARGET_AVAILABILITY || run_state_ == STARTING;
diff --git a/cc/animation/keyframed_animation_curve.cc b/cc/animation/keyframed_animation_curve.cc
index 98f61d2..2d0a4fe2 100644
--- a/cc/animation/keyframed_animation_curve.cc
+++ b/cc/animation/keyframed_animation_curve.cc
@@ -466,15 +466,6 @@
   return keyframes_[i + 1]->Value().Blend(keyframes_[i]->Value(), progress);
 }
 
-bool KeyframedFilterAnimationCurve::HasFilterThatMovesPixels() const {
-  for (const auto& keyframe : keyframes_) {
-    if (keyframe->Value().HasFilterThatMovesPixels()) {
-      return true;
-    }
-  }
-  return false;
-}
-
 std::unique_ptr<KeyframedSizeAnimationCurve>
 KeyframedSizeAnimationCurve::Create() {
   return base::WrapUnique(new KeyframedSizeAnimationCurve);
diff --git a/cc/animation/keyframed_animation_curve.h b/cc/animation/keyframed_animation_curve.h
index 45f95772..cf26a10 100644
--- a/cc/animation/keyframed_animation_curve.h
+++ b/cc/animation/keyframed_animation_curve.h
@@ -292,7 +292,6 @@
 
   // FilterAnimationCurve implementation
   FilterOperations GetValue(base::TimeDelta t) const override;
-  bool HasFilterThatMovesPixels() const override;
 
  private:
   KeyframedFilterAnimationCurve();
diff --git a/cc/animation/scroll_offset_animation_curve.cc b/cc/animation/scroll_offset_animation_curve.cc
index 52bb2a8..f8e8c17 100644
--- a/cc/animation/scroll_offset_animation_curve.cc
+++ b/cc/animation/scroll_offset_animation_curve.cc
@@ -316,14 +316,26 @@
   return total_animation_duration_;
 }
 
-AnimationCurve::CurveType ScrollOffsetAnimationCurve::Type() const {
-  return SCROLL_OFFSET;
+int ScrollOffsetAnimationCurve::Type() const {
+  return AnimationCurve::SCROLL_OFFSET;
+}
+
+const char* ScrollOffsetAnimationCurve::TypeName() const {
+  return "ScrollOffset";
 }
 
 std::unique_ptr<AnimationCurve> ScrollOffsetAnimationCurve::Clone() const {
   return CloneToScrollOffsetAnimationCurve();
 }
 
+void ScrollOffsetAnimationCurve::Tick(base::TimeDelta t,
+                                      int property_id,
+                                      KeyframeModel* keyframe_model) const {
+  if (target_) {
+    target_->OnScrollOffsetAnimated(GetValue(t), property_id, keyframe_model);
+  }
+}
+
 std::unique_ptr<ScrollOffsetAnimationCurve>
 ScrollOffsetAnimationCurve::CloneToScrollOffsetAnimationCurve() const {
   std::unique_ptr<TimingFunction> timing_function(
@@ -438,4 +450,17 @@
   last_retarget_ = t;
 }
 
+const ScrollOffsetAnimationCurve*
+ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
+    const AnimationCurve* c) {
+  DCHECK_EQ(ScrollOffsetAnimationCurve::SCROLL_OFFSET, c->Type());
+  return static_cast<const ScrollOffsetAnimationCurve*>(c);
+}
+
+ScrollOffsetAnimationCurve*
+ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(AnimationCurve* c) {
+  DCHECK_EQ(ScrollOffsetAnimationCurve::SCROLL_OFFSET, c->Type());
+  return static_cast<ScrollOffsetAnimationCurve*>(c);
+}
+
 }  // namespace cc
diff --git a/cc/animation/scroll_offset_animation_curve.h b/cc/animation/scroll_offset_animation_curve.h
index 0ca6fda..041566c 100644
--- a/cc/animation/scroll_offset_animation_curve.h
+++ b/cc/animation/scroll_offset_animation_curve.h
@@ -26,6 +26,15 @@
 
 class CC_ANIMATION_EXPORT ScrollOffsetAnimationCurve : public AnimationCurve {
  public:
+  class Target {
+   public:
+    ~Target() = default;
+
+    virtual void OnScrollOffsetAnimated(const gfx::ScrollOffset& value,
+                                        int target_property_id,
+                                        KeyframeModel* keyframe_model) = 0;
+  };
+
   // Indicates how the animation duration should be computed for Ease-in-out
   // style scroll animation curves.
   enum class DurationBehavior {
@@ -39,6 +48,12 @@
     INVERSE_DELTA
   };
 
+  static const ScrollOffsetAnimationCurve* ToScrollOffsetAnimationCurve(
+      const AnimationCurve* c);
+
+  static ScrollOffsetAnimationCurve* ToScrollOffsetAnimationCurve(
+      AnimationCurve* c);
+
   // There is inherent delay in input processing; it may take many milliseconds
   // from the time of user input to when when we're actually able to handle it
   // here. This delay is represented by the |delayed_by| value. The way we have
@@ -84,12 +99,16 @@
 
   // AnimationCurve implementation
   base::TimeDelta Duration() const override;
-  CurveType Type() const override;
+  int Type() const override;
+  const char* TypeName() const override;
   std::unique_ptr<AnimationCurve> Clone() const override;
   std::unique_ptr<ScrollOffsetAnimationCurve>
   CloneToScrollOffsetAnimationCurve() const;
-
+  void Tick(base::TimeDelta t,
+            int property_id,
+            KeyframeModel* keyframe_model) const override;
   static void SetAnimationDurationForTesting(base::TimeDelta duration);
+  void set_target(Target* target) { target_ = target; }
 
  private:
   FRIEND_TEST_ALL_PREFIXES(ScrollOffsetAnimationCurveTest, ImpulseUpdateTarget);
@@ -139,6 +158,8 @@
   bool has_set_initial_value_;
 
   static base::Optional<double> animation_duration_for_testing_;
+
+  Target* target_ = nullptr;
 };
 
 }  // namespace cc
diff --git a/cc/animation/scroll_offset_animation_curve_unittest.cc b/cc/animation/scroll_offset_animation_curve_unittest.cc
index fa745c8..ce39017c 100644
--- a/cc/animation/scroll_offset_animation_curve_unittest.cc
+++ b/cc/animation/scroll_offset_animation_curve_unittest.cc
@@ -77,7 +77,6 @@
   EXPECT_GT(curve->Duration().InSecondsF(), 0);
   EXPECT_LT(curve->Duration().InSecondsF(), 0.1);
 
-  EXPECT_EQ(AnimationCurve::SCROLL_OFFSET, curve->Type());
   EXPECT_EQ(duration, curve->Duration());
 
   EXPECT_VECTOR2DF_EQ(initial_value,
@@ -108,28 +107,23 @@
 
   std::unique_ptr<AnimationCurve> clone(curve->Clone());
 
-  EXPECT_EQ(AnimationCurve::SCROLL_OFFSET, clone->Type());
   EXPECT_EQ(duration, clone->Duration());
 
-  EXPECT_VECTOR2DF_EQ(initial_value,
-                      clone->ToScrollOffsetAnimationCurve()->GetValue(
-                          base::TimeDelta::FromSecondsD(-1.0)));
+  ScrollOffsetAnimationCurve* cloned_curve =
+      ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(clone.get());
+
+  EXPECT_VECTOR2DF_EQ(initial_value, cloned_curve->GetValue(
+                                         base::TimeDelta::FromSecondsD(-1.0)));
+  EXPECT_VECTOR2DF_EQ(initial_value, cloned_curve->GetValue(base::TimeDelta()));
+  EXPECT_VECTOR2DF_NEAR(gfx::ScrollOffset(6.f, 30.f),
+                        cloned_curve->GetValue(duration * 0.5f), 0.00025);
+  EXPECT_VECTOR2DF_EQ(target_value, cloned_curve->GetValue(duration));
   EXPECT_VECTOR2DF_EQ(
-      initial_value,
-      clone->ToScrollOffsetAnimationCurve()->GetValue(base::TimeDelta()));
-  EXPECT_VECTOR2DF_NEAR(
-      gfx::ScrollOffset(6.f, 30.f),
-      clone->ToScrollOffsetAnimationCurve()->GetValue(duration * 0.5f),
-      0.00025);
-  EXPECT_VECTOR2DF_EQ(
-      target_value, clone->ToScrollOffsetAnimationCurve()->GetValue(duration));
-  EXPECT_VECTOR2DF_EQ(target_value,
-                      clone->ToScrollOffsetAnimationCurve()->GetValue(
-                          duration + base::TimeDelta::FromSecondsD(1.f)));
+      target_value,
+      cloned_curve->GetValue(duration + base::TimeDelta::FromSecondsD(1.f)));
 
   // Verify that the timing function was cloned correctly.
-  gfx::ScrollOffset value =
-      clone->ToScrollOffsetAnimationCurve()->GetValue(duration * 0.25f);
+  gfx::ScrollOffset value = cloned_curve->GetValue(duration * 0.25f);
   EXPECT_NEAR(3.0333f, value.x(), 0.0002f);
   EXPECT_NEAR(37.4168f, value.y(), 0.0002f);
 }
diff --git a/cc/animation/scroll_offset_animations_impl.cc b/cc/animation/scroll_offset_animations_impl.cc
index d6c44889..ff63928 100644
--- a/cc/animation/scroll_offset_animations_impl.cc
+++ b/cc/animation/scroll_offset_animations_impl.cc
@@ -115,7 +115,8 @@
     return true;
 
   ScrollOffsetAnimationCurve* curve =
-      keyframe_model->curve()->ToScrollOffsetAnimationCurve();
+      ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
+          keyframe_model->curve());
 
   gfx::ScrollOffset new_target =
       gfx::ScrollOffsetWithDelta(curve->target_value(), scroll_delta);
@@ -168,8 +169,8 @@
   }
 
   std::unique_ptr<ScrollOffsetAnimationCurve> new_curve =
-      keyframe_model->curve()
-          ->ToScrollOffsetAnimationCurve()
+      ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
+          keyframe_model->curve())
           ->CloneToScrollOffsetAnimationCurve();
   new_curve->ApplyAdjustment(adjustment);
 
diff --git a/cc/metrics/compositor_frame_reporter.cc b/cc/metrics/compositor_frame_reporter.cc
index 607cbd7..7a4c0f6c 100644
--- a/cc/metrics/compositor_frame_reporter.cc
+++ b/cc/metrics/compositor_frame_reporter.cc
@@ -702,23 +702,34 @@
     case FrameTerminationStatus::kReplacedByNewReporter:
       EnableReportType(FrameReportType::kDroppedFrame);
       break;
-    case FrameTerminationStatus::kDidNotProduceFrame:
-      if (frame_skip_reason_.has_value() &&
-          frame_skip_reason() == FrameSkippedReason::kNoDamage) {
-        // If this reporter was cloned, and the cloned repoter was marked as
+    case FrameTerminationStatus::kDidNotProduceFrame: {
+      const bool no_update_from_main =
+          frame_skip_reason_.has_value() &&
+          frame_skip_reason() == FrameSkippedReason::kNoDamage;
+      const bool no_update_from_compositor =
+          !has_partial_update_ && frame_skip_reason_.has_value() &&
+          frame_skip_reason() == FrameSkippedReason::kWaitingOnMain;
+
+      if (no_update_from_main) {
+        // If this reporter was cloned, and the cloned reporter was marked as
         // containing 'partial update' (i.e. missing desired updates from the
         // main-thread), but this reporter terminated with 'no damage', then
-        // reset the 'partial update' flag from the cloned reporter.
-        for (auto dependent : partial_update_dependents_) {
+        // reset the 'partial update' flag from the cloned reporter (as well as
+        // other depending reporters).
+        while (!partial_update_dependents_.empty()) {
+          auto dependent = partial_update_dependents_.front();
           if (dependent)
             dependent->set_has_partial_update(false);
+          partial_update_dependents_.pop();
         }
-      } else {
-        // If no frames were produced, it was not due to no-damage, then it is a
-        // dropped frame.
+      } else if (!no_update_from_compositor) {
+        // If rather main thread has damage or compositor thread has partial
+        // damage, then it's a dropped frame.
         EnableReportType(FrameReportType::kDroppedFrame);
       }
+
       break;
+    }
     case FrameTerminationStatus::kUnknown:
       break;
   }
@@ -755,6 +766,11 @@
     dropped_frame_counter_->OnEndFrame(args_,
                                        IsDroppedFrameAffectingSmoothness());
   }
+
+  if (discarded_partial_update_dependents_count_ > 0)
+    UMA_HISTOGRAM_CUSTOM_COUNTS(
+        "Graphics.Smoothness.Diagnostic.DiscardedDependentCount",
+        discarded_partial_update_dependents_count_, 1, 1000, 50);
 }
 
 void CompositorFrameReporter::EndCurrentStage(base::TimeTicks end_time) {
@@ -1332,18 +1348,9 @@
   // If |this| reporter is dependent on another reporter to decide about partial
   // update, then |this| should not have any such dependents.
   DCHECK(!partial_update_decider_);
-
   DCHECK(!partial_update_dependents_.empty());
-#if DCHECK_IS_ON()
-  auto f = std::find_if(partial_update_dependents_.begin(),
-                        partial_update_dependents_.end(),
-                        [target = reporter.get()](const auto& weak_ptr) {
-                          return target == weak_ptr.get();
-                        });
-  DCHECK(f != partial_update_dependents_.end());
-#endif
-  owned_partial_update_dependents_.push_back(std::move(reporter));
-  DCHECK_LT(owned_partial_update_dependents_.size(), 300u);
+  owned_partial_update_dependents_.push(std::move(reporter));
+  DiscardOldPartialUpdateReporters();
 }
 
 void CompositorFrameReporter::SetPartialUpdateDecider(
@@ -1351,8 +1358,20 @@
   DCHECK(decider);
   has_partial_update_ = true;
   partial_update_decider_ = decider;
-  decider->partial_update_dependents_.push_back(GetWeakPtr());
-  DCHECK_LT(partial_update_dependents_.size(), 300u);
+  decider->partial_update_dependents_.push(GetWeakPtr());
+  DCHECK(partial_update_dependents_.empty());
+}
+
+void CompositorFrameReporter::DiscardOldPartialUpdateReporters() {
+  DCHECK_LE(owned_partial_update_dependents_.size(),
+            partial_update_dependents_.size());
+  while (owned_partial_update_dependents_.size() > 300u) {
+    auto& dependent = owned_partial_update_dependents_.front();
+    dependent->set_has_partial_update(false);
+    partial_update_dependents_.pop();
+    owned_partial_update_dependents_.pop();
+    discarded_partial_update_dependents_count_++;
+  }
 }
 
 bool CompositorFrameReporter::MightHavePartialUpdate() const {
diff --git a/cc/metrics/compositor_frame_reporter.h b/cc/metrics/compositor_frame_reporter.h
index 94acb45..01ef2fe 100644
--- a/cc/metrics/compositor_frame_reporter.h
+++ b/cc/metrics/compositor_frame_reporter.h
@@ -7,6 +7,7 @@
 
 #include <bitset>
 #include <memory>
+#include <queue>
 #include <string>
 #include <utility>
 #include <vector>
@@ -356,6 +357,9 @@
   // This method is only used for DCheck
   base::TimeDelta SumOfStageHistory() const;
 
+  // Terminating reporters in partial_update_dependents_ after a limit.
+  void DiscardOldPartialUpdateReporters();
+
   base::TimeTicks Now() const;
 
   bool IsDroppedFrameAffectingSmoothness() const;
@@ -421,15 +425,15 @@
   // In such cases, |partial_update_dependents_| for A contains all the frames
   // that depend on A for deciding whether they had partial updates or not, and
   // |partial_update_decider_| is set to A for all these reporters.
-  std::vector<base::WeakPtr<CompositorFrameReporter>>
-      partial_update_dependents_;
+  std::queue<base::WeakPtr<CompositorFrameReporter>> partial_update_dependents_;
   base::WeakPtr<CompositorFrameReporter> partial_update_decider_;
+  uint32_t discarded_partial_update_dependents_count_ = 0;
 
   // From the above example, it may be necessary for A to keep all the
   // dependents alive until A terminates, so that the dependents can set their
   // |has_partial_update_| flags correctly. This is done by passing ownership of
   // these reporters (using |AdoptReporter()|).
-  std::vector<std::unique_ptr<CompositorFrameReporter>>
+  std::queue<std::unique_ptr<CompositorFrameReporter>>
       owned_partial_update_dependents_;
 
   base::WeakPtrFactory<CompositorFrameReporter> weak_factory_{this};
diff --git a/cc/metrics/compositor_frame_reporting_controller.cc b/cc/metrics/compositor_frame_reporting_controller.cc
index 6d477af..6713366 100644
--- a/cc/metrics/compositor_frame_reporting_controller.cc
+++ b/cc/metrics/compositor_frame_reporting_controller.cc
@@ -307,20 +307,40 @@
       // BeginMain stage, but the main-thread can make updates, which can be
       // submitted with the next frame.
       stage_reporter->OnDidNotProduceFrame(skip_reason);
+      if (skip_reason == FrameSkippedReason::kWaitingOnMain)
+        SetPartialUpdateDeciderWhenWaitingOnMain(stage_reporter);
+
       break;
     }
   }
+}
 
-  // If the compositor has no updates, and the main-thread has not responded to
-  // the begin-main-frame yet, then it is essentially a dropped frame. To handle
-  // this case, keep the reporter for the main-thread, but recreate a reporter
-  // for the dropped-frame.
-  if (skip_reason == FrameSkippedReason::kWaitingOnMain) {
-    auto reporter = RestoreReporterAtBeginImpl(id);
-    if (reporter) {
-      reporter->OnDidNotProduceFrame(skip_reason);
-      reporter->TerminateFrame(FrameTerminationStatus::kDidNotProduceFrame,
-                               Now());
+void CompositorFrameReportingController::
+    SetPartialUpdateDeciderWhenWaitingOnMain(
+        std::unique_ptr<CompositorFrameReporter>& stage_reporter) {
+  // If the compositor has no updates, and the main-thread has not responded
+  // to the begin-main-frame yet, then depending on main thread having
+  // update or not this would be a NoFrameProduced or a DroppedFrame. To
+  // handle this case , keep the reporter for the main-thread, but recreate
+  // a reporter for the current frame and link it to the reporter it depends
+  // on.
+  auto reporter = RestoreReporterAtBeginImpl(stage_reporter->frame_id());
+  if (reporter) {
+    reporter->OnDidNotProduceFrame(FrameSkippedReason::kWaitingOnMain);
+    reporter->TerminateFrame(FrameTerminationStatus::kDidNotProduceFrame,
+                             Now());
+    stage_reporter->AdoptReporter(std::move(reporter));
+  } else {
+    // The stage_reporter in this case was waiting for main, so needs to
+    // be adopted by the reporter which is waiting on Main thread's work
+    auto partial_update_decider =
+        HasOutstandingUpdatesFromMain(stage_reporter->frame_id());
+    if (partial_update_decider) {
+      stage_reporter->SetPartialUpdateDecider(partial_update_decider);
+      stage_reporter->OnDidNotProduceFrame(FrameSkippedReason::kWaitingOnMain);
+      stage_reporter->TerminateFrame(
+          FrameTerminationStatus::kDidNotProduceFrame, Now());
+      partial_update_decider->AdoptReporter(std::move(stage_reporter));
     }
   }
 }
diff --git a/cc/metrics/compositor_frame_reporting_controller.h b/cc/metrics/compositor_frame_reporting_controller.h
index bdfe6e8..7aecb39 100644
--- a/cc/metrics/compositor_frame_reporting_controller.h
+++ b/cc/metrics/compositor_frame_reporting_controller.h
@@ -138,6 +138,11 @@
       const viz::BeginFrameArgs& old_args,
       const viz::BeginFrameArgs& new_args) const;
 
+  // The arg is a reference to the unique_ptr, because depending on the state
+  // that reporter is in, its ownership might be pass or not.
+  void SetPartialUpdateDeciderWhenWaitingOnMain(
+      std::unique_ptr<CompositorFrameReporter>& reporter);
+
   const bool should_report_metrics_;
   const int layer_tree_host_id_;
 
diff --git a/cc/metrics/compositor_frame_reporting_controller_unittest.cc b/cc/metrics/compositor_frame_reporting_controller_unittest.cc
index acd8e4b9..d0bffcb 100644
--- a/cc/metrics/compositor_frame_reporting_controller_unittest.cc
+++ b/cc/metrics/compositor_frame_reporting_controller_unittest.cc
@@ -446,31 +446,6 @@
       "CompositorLatency.DroppedFrame.EndActivateToSubmitCompositorFrame", 0);
 }
 
-TEST_F(CompositorFrameReportingControllerTest, ImplFrameCausedNoDamage) {
-  base::HistogramTester histogram_tester;
-
-  SimulateBeginImplFrame();
-  reporting_controller_.OnFinishImplFrame(args_.frame_id);
-  reporting_controller_.DidNotProduceFrame(args_.frame_id,
-                                           FrameSkippedReason::kNoDamage);
-  SimulateBeginImplFrame();
-  histogram_tester.ExpectTotalCount(
-      "CompositorLatency.DroppedFrame.BeginImplFrameToSendBeginMainFrame", 0);
-  histogram_tester.ExpectBucketCount(
-      "CompositorLatency.Type",
-      CompositorFrameReporter::FrameReportType::kDroppedFrame, 0);
-
-  reporting_controller_.OnFinishImplFrame(args_.frame_id);
-  reporting_controller_.DidNotProduceFrame(args_.frame_id,
-                                           FrameSkippedReason::kWaitingOnMain);
-  SimulateBeginImplFrame();
-  histogram_tester.ExpectTotalCount(
-      "CompositorLatency.DroppedFrame.BeginImplFrameToSendBeginMainFrame", 1);
-  histogram_tester.ExpectBucketCount(
-      "CompositorLatency.Type",
-      CompositorFrameReporter::FrameReportType::kDroppedFrame, 1);
-}
-
 TEST_F(CompositorFrameReportingControllerTest, MainFrameCausedNoDamage) {
   base::HistogramTester histogram_tester;
   viz::BeginFrameId current_id_1(1, 1);
@@ -1521,6 +1496,59 @@
 }
 
 TEST_F(CompositorFrameReportingControllerTest,
+       CompositorFrameBlockedOnMainFrameWithNoDamage) {
+  viz::BeginFrameId current_id_1(1, 1);
+  viz::BeginFrameArgs args_1 = SimulateBeginFrameArgs(current_id_1);
+
+  viz::BeginFrameId current_id_2(1, 2);
+  viz::BeginFrameArgs args_2 = SimulateBeginFrameArgs(current_id_2);
+
+  viz::BeginFrameId current_id_3(1, 3);
+  viz::BeginFrameArgs args_3 = SimulateBeginFrameArgs(current_id_3);
+
+  viz::BeginFrameId current_id_4(1, 4);
+  viz::BeginFrameArgs args_4 = SimulateBeginFrameArgs(current_id_4);
+
+  reporting_controller_.WillBeginImplFrame(args_1);
+  reporting_controller_.WillBeginMainFrame(args_1);
+  reporting_controller_.OnFinishImplFrame(current_id_1);
+  EXPECT_EQ(0u, dropped_counter.total_compositor_dropped());
+  reporting_controller_.DidNotProduceFrame(args_1.frame_id,
+                                           FrameSkippedReason::kWaitingOnMain);
+
+  reporting_controller_.WillBeginImplFrame(args_2);
+  reporting_controller_.OnFinishImplFrame(args_2.frame_id);
+  reporting_controller_.DidNotProduceFrame(args_2.frame_id,
+                                           FrameSkippedReason::kWaitingOnMain);
+
+  reporting_controller_.WillBeginImplFrame(args_3);
+  reporting_controller_.OnFinishImplFrame(args_3.frame_id);
+  reporting_controller_.DidNotProduceFrame(args_3.frame_id,
+                                           FrameSkippedReason::kWaitingOnMain);
+
+  EXPECT_EQ(1u, reporting_controller_.GetBlockingReportersCount());
+  EXPECT_EQ(3u, reporting_controller_.GetBlockedReportersCount());
+
+  // All frames are waiting for the main frame
+  EXPECT_EQ(0u, dropped_counter.total_main_dropped());
+  EXPECT_EQ(0u, dropped_counter.total_compositor_dropped());
+  EXPECT_EQ(0u, dropped_counter.total_frames());
+
+  reporting_controller_.BeginMainFrameAborted(args_1.frame_id);
+  reporting_controller_.DidNotProduceFrame(args_1.frame_id,
+                                           FrameSkippedReason::kNoDamage);
+  EXPECT_EQ(0u, dropped_counter.total_compositor_dropped());
+
+  // New reporters replace older reporters
+  reporting_controller_.WillBeginImplFrame(args_4);
+  reporting_controller_.WillBeginMainFrame(args_4);
+
+  EXPECT_EQ(4u, dropped_counter.total_frames());
+  EXPECT_EQ(0u, dropped_counter.total_main_dropped());
+  EXPECT_EQ(0u, dropped_counter.total_compositor_dropped());
+}
+
+TEST_F(CompositorFrameReportingControllerTest,
        SkippedFramesFromDisplayCompositorHaveSmoothThread) {
   auto thread_type_compositor = FrameSequenceMetrics::ThreadType::kCompositor;
   reporting_controller_.SetThreadAffectsSmoothness(thread_type_compositor,
diff --git a/cc/mojo_embedder/BUILD.gn b/cc/mojo_embedder/BUILD.gn
index 63e0725c..99b982c 100644
--- a/cc/mojo_embedder/BUILD.gn
+++ b/cc/mojo_embedder/BUILD.gn
@@ -16,6 +16,7 @@
   deps = [
     "//base",
     "//cc",
+    "//components/power_scheduler",
     "//components/viz/client",
     "//components/viz/common",
     "//mojo/public/cpp/bindings",
diff --git a/cc/mojo_embedder/DEPS b/cc/mojo_embedder/DEPS
index 325a68d..3006227c 100644
--- a/cc/mojo_embedder/DEPS
+++ b/cc/mojo_embedder/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/power_scheduler",
   "+mojo/public/cpp/bindings",
   "+services/viz/public/mojom/compositing",
 ]
diff --git a/cc/mojo_embedder/async_layer_tree_frame_sink.cc b/cc/mojo_embedder/async_layer_tree_frame_sink.cc
index 095db5b..467fac7f 100644
--- a/cc/mojo_embedder/async_layer_tree_frame_sink.cc
+++ b/cc/mojo_embedder/async_layer_tree_frame_sink.cc
@@ -13,6 +13,9 @@
 #include "base/trace_event/trace_event.h"
 #include "cc/base/histograms.h"
 #include "cc/trees/layer_tree_frame_sink_client.h"
+#include "components/power_scheduler/power_mode.h"
+#include "components/power_scheduler/power_mode_arbiter.h"
+#include "components/power_scheduler/power_mode_voter.h"
 #include "components/viz/common/features.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/hit_test/hit_test_region_list.h"
@@ -47,8 +50,10 @@
       synthetic_begin_frame_source_(
           std::move(params->synthetic_begin_frame_source)),
       pipes_(std::move(params->pipes)),
-      wants_animate_only_begin_frames_(
-          params->wants_animate_only_begin_frames) {
+      wants_animate_only_begin_frames_(params->wants_animate_only_begin_frames),
+      animation_power_mode_voter_(
+          power_scheduler::PowerModeArbiter::GetInstance()->NewVoter(
+              "PowerModeVoter.Animation")) {
   DETACH_FROM_THREAD(thread_checker_);
 }
 
@@ -275,11 +280,15 @@
 void AsyncLayerTreeFrameSink::OnNeedsBeginFrames(bool needs_begin_frames) {
   DCHECK(compositor_frame_sink_ptr_);
   if (needs_begin_frames_ != needs_begin_frames) {
-    if (needs_begin_frames_) {
-      TRACE_EVENT_NESTABLE_ASYNC_END0("cc,benchmark", "NeedsBeginFrames", this);
-    } else {
+    if (needs_begin_frames) {
       TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("cc,benchmark", "NeedsBeginFrames",
                                         this);
+      animation_power_mode_voter_->VoteFor(
+          power_scheduler::PowerMode::kAnimation);
+    } else {
+      TRACE_EVENT_NESTABLE_ASYNC_END0("cc,benchmark", "NeedsBeginFrames", this);
+      animation_power_mode_voter_->ResetVoteAfterTimeout(
+          power_scheduler::PowerModeVoter::kAnimationTimeout);
     }
   }
   needs_begin_frames_ = needs_begin_frames;
diff --git a/cc/mojo_embedder/async_layer_tree_frame_sink.h b/cc/mojo_embedder/async_layer_tree_frame_sink.h
index 7bfcf46..c7efb10 100644
--- a/cc/mojo_embedder/async_layer_tree_frame_sink.h
+++ b/cc/mojo_embedder/async_layer_tree_frame_sink.h
@@ -14,6 +14,7 @@
 #include "base/single_thread_task_runner.h"
 #include "cc/mojo_embedder/mojo_embedder_export.h"
 #include "cc/trees/layer_tree_frame_sink.h"
+#include "components/power_scheduler/power_mode_voter.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/frame_timing_details_map.h"
 #include "components/viz/common/gpu/context_provider.h"
@@ -140,6 +141,8 @@
   float last_submitted_device_scale_factor_ = 1.f;
   gfx::Size last_submitted_size_in_pixels_;
 
+  std::unique_ptr<power_scheduler::PowerModeVoter> animation_power_mode_voter_;
+
   base::WeakPtrFactory<AsyncLayerTreeFrameSink> weak_factory_{this};
 };
 
diff --git a/cc/trees/layer_tree_host_unittest_animation.cc b/cc/trees/layer_tree_host_unittest_animation.cc
index b240cdb..dadeab90 100644
--- a/cc/trees/layer_tree_host_unittest_animation.cc
+++ b/cc/trees/layer_tree_host_unittest_animation.cc
@@ -377,7 +377,7 @@
         animation_child_impl->GetKeyframeModel(TargetProperty::OPACITY);
 
     const FloatAnimationCurve* curve =
-        keyframe_model->curve()->ToFloatAnimationCurve();
+        FloatAnimationCurve::ToFloatAnimationCurve(keyframe_model->curve());
     float start_opacity = curve->GetValue(base::TimeDelta());
     float end_opacity = curve->GetValue(curve->Duration());
     float linearly_interpolated_opacity =
@@ -938,8 +938,9 @@
           ScrollOffsetKeyframeEffect(*host_impl, scroll_layer_)
               .GetKeyframeModel(TargetProperty::SCROLL_OFFSET);
       DCHECK(keyframe_model);
-      ScrollOffsetAnimationCurve* curve =
-          keyframe_model->curve()->ToScrollOffsetAnimationCurve();
+      const ScrollOffsetAnimationCurve* curve =
+          ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
+              keyframe_model->curve());
 
       // Verifiy the initial and target position before the scroll offset
       // update from MT.
@@ -963,8 +964,9 @@
           ScrollOffsetKeyframeEffect(*host_impl, scroll_layer_)
               .GetKeyframeModel(TargetProperty::SCROLL_OFFSET);
       DCHECK(keyframe_model);
-      ScrollOffsetAnimationCurve* curve =
-          keyframe_model->curve()->ToScrollOffsetAnimationCurve();
+      const ScrollOffsetAnimationCurve* curve =
+          ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
+              keyframe_model->curve());
       // Verifiy the initial and target position after the scroll offset
       // update from MT
       EXPECT_EQ(KeyframeModel::RunState::STARTING, keyframe_model->run_state());
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index c9dfda0..de23a94 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -75,6 +75,8 @@
   "java/src/org/chromium/chrome/browser/app/appmenu/ManagedByMenuItemViewBinder.java",
   "java/src/org/chromium/chrome/browser/app/appmenu/UpdateMenuItemViewBinder.java",
   "java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java",
+  "java/src/org/chromium/chrome/browser/app/metrics/LaunchCauseMetrics.java",
+  "java/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetrics.java",
   "java/src/org/chromium/chrome/browser/app/reengagement/ReengagementActivity.java",
   "java/src/org/chromium/chrome/browser/app/send_tab_to_self/SendTabToSelfNotificationReceiver.java",
   "java/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java",
@@ -782,12 +784,10 @@
   "java/src/org/chromium/chrome/browser/messages/MessageContainerCoordinator.java",
   "java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java",
   "java/src/org/chromium/chrome/browser/metrics/BackgroundTaskMemoryMetricsEmitter.java",
-  "java/src/org/chromium/chrome/browser/metrics/LaunchCauseMetrics.java",
   "java/src/org/chromium/chrome/browser/metrics/LaunchMetrics.java",
   "java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java",
   "java/src/org/chromium/chrome/browser/metrics/PackageMetrics.java",
   "java/src/org/chromium/chrome/browser/metrics/PageLoadMetrics.java",
-  "java/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetrics.java",
   "java/src/org/chromium/chrome/browser/metrics/UkmRecorder.java",
   "java/src/org/chromium/chrome/browser/metrics/UmaSessionStats.java",
   "java/src/org/chromium/chrome/browser/metrics/VariationsSession.java",
@@ -873,6 +873,8 @@
   "java/src/org/chromium/chrome/browser/ntp/RecentlyClosedBridge.java",
   "java/src/org/chromium/chrome/browser/ntp/RecentlyClosedTab.java",
   "java/src/org/chromium/chrome/browser/ntp/RecentlyClosedTabManager.java",
+  "java/src/org/chromium/chrome/browser/ntp/ScrollListener.java",
+  "java/src/org/chromium/chrome/browser/ntp/ScrollableContainerDelegate.java",
   "java/src/org/chromium/chrome/browser/ntp/SnapScrollHelper.java",
   "java/src/org/chromium/chrome/browser/ntp/TitleUtil.java",
   "java/src/org/chromium/chrome/browser/ntp/cards/CardsVariationParameters.java",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index 2be1c0a..8f791aad 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -55,6 +55,9 @@
   "javatests/src/org/chromium/chrome/browser/app/appmenu/DataSaverAppMenuTest.java",
   "javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java",
   "javatests/src/org/chromium/chrome/browser/app/appmenu/TabbedAppMenuTest.java",
+  "javatests/src/org/chromium/chrome/browser/app/metrics/LaunchCauseMetricsTest.java",
+  "javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java",
+  "javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsUnitTest.java",
   "javatests/src/org/chromium/chrome/browser/app/tab_activity_glue/TabletPhoneLayoutChangeTest.java",
   "javatests/src/org/chromium/chrome/browser/app/tabmodel/ChromeNextTabPolicySupplierTest.java",
   "javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupTest.java",
@@ -270,12 +273,9 @@
   "javatests/src/org/chromium/chrome/browser/media/ui/MediaSessionTest.java",
   "javatests/src/org/chromium/chrome/browser/media/ui/PictureInPictureControllerTest.java",
   "javatests/src/org/chromium/chrome/browser/metrics/BackgroundMetricsTest.java",
-  "javatests/src/org/chromium/chrome/browser/metrics/LaunchCauseMetricsTest.java",
   "javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java",
   "javatests/src/org/chromium/chrome/browser/metrics/PageLoadMetricsTest.java",
   "javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java",
-  "javatests/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetricsTest.java",
-  "javatests/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetricsUnitTest.java",
   "javatests/src/org/chromium/chrome/browser/modaldialog/ChromeTabModalPresenterTest.java",
   "javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewRenderTest.java",
   "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java",
diff --git a/chrome/android/features/autofill_assistant/BUILD.gn b/chrome/android/features/autofill_assistant/BUILD.gn
index 27e903f4..371f3ae2 100644
--- a/chrome/android/features/autofill_assistant/BUILD.gn
+++ b/chrome/android/features/autofill_assistant/BUILD.gn
@@ -110,14 +110,9 @@
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantServiceInjector.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/BaseOnboardingCoordinator.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinator.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/DialogOnboardingCoordinator.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/FeedbackContext.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/LayoutUtils.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/OnboardingCoordinatorFactory.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/OnboardingView.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/ScrollToHideGestureListener.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/SizeListenableLinearLayout.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantActionsCarouselCoordinator.java",
@@ -163,6 +158,11 @@
     "java/src/org/chromium/chrome/browser/autofill_assistant/infobox/AssistantInfoBoxCoordinator.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/infobox/AssistantInfoBoxModel.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/infobox/AssistantInfoBoxViewBinder.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BaseOnboardingCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BottomSheetOnboardingCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/DialogOnboardingCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/OnboardingCoordinatorFactory.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/OnboardingView.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDelegate.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDrawable.java",
@@ -210,7 +210,6 @@
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantDirectActionImpl.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantLiteService.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/BaseOnboardingCoordinator.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java",
@@ -231,6 +230,7 @@
     "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/infobox/AssistantInfoBox.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/infobox/AssistantInfoBoxModel.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BaseOnboardingCoordinator.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDelegate.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScriptBridge.java",
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantActionHandlerImpl.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantActionHandlerImpl.java
index d10cf43..a2be4a4 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantActionHandlerImpl.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantActionHandlerImpl.java
@@ -12,11 +12,15 @@
 import org.chromium.base.Callback;
 import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.AssistantOnboardingResult;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.BaseOnboardingCoordinator;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.OnboardingCoordinatorFactory;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
+import org.chromium.content_public.browser.WebContents;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -82,9 +86,10 @@
     public void performOnboarding(
             String experimentIds, Bundle arguments, Callback<Boolean> callback) {
         Map<String, String> parameters = toArgumentMap(arguments);
-        BottomSheetOnboardingCoordinator coordinator =
-                new BottomSheetOnboardingCoordinator(experimentIds, parameters, mContext,
-                        mBottomSheetController, mBrowserControls, mCompositorViewHolder, mScrim);
+        BaseOnboardingCoordinator coordinator =
+                OnboardingCoordinatorFactory.createOnboardingCoordinator(
+                        /* isDialogOnboardingEnabled = */ false, experimentIds, parameters,
+                        mContext, mBottomSheetController, mBrowserControls, mCompositorViewHolder);
         coordinator.show(result -> {
             coordinator.hide();
             callback.onResult(result == AssistantOnboardingResult.ACCEPTED);
@@ -101,15 +106,17 @@
         }
 
         Map<String, String> argumentMap = toArgumentMap(arguments);
-        Callback<BottomSheetOnboardingCoordinator> afterOnboarding = (onboardingCoordinator) -> {
+        Callback<BaseOnboardingCoordinator> afterOnboarding = (onboardingCoordinator) -> {
             callback.onResult(client.performDirectAction(
                     name, experimentIds, argumentMap, onboardingCoordinator));
         };
 
         if (!AutofillAssistantPreferencesUtil.isAutofillOnboardingAccepted()) {
-            BottomSheetOnboardingCoordinator coordinator = new BottomSheetOnboardingCoordinator(
-                    experimentIds, argumentMap, mContext, mBottomSheetController, mBrowserControls,
-                    mCompositorViewHolder, mScrim);
+            BaseOnboardingCoordinator coordinator =
+                    OnboardingCoordinatorFactory.createOnboardingCoordinator(
+                            /* isDialogOnboardingEnabled = */ false, experimentIds, argumentMap,
+                            mContext, mBottomSheetController, mBrowserControls,
+                            mCompositorViewHolder);
             coordinator.show(result -> {
                 if (result != AssistantOnboardingResult.ACCEPTED) {
                     coordinator.hide();
@@ -123,6 +130,14 @@
         afterOnboarding.onResult(null);
     }
 
+    private WebContents getWebContents() {
+        Tab tab = mActivityTabProvider.get();
+        if (tab == null) {
+            return null;
+        }
+        return tab.getWebContents();
+    }
+
     /**
      * Returns a client for the current tab or {@code null} if there's no current tab or the current
      * tab doesn't have an associated browser content.
@@ -130,11 +145,11 @@
     @Nullable
     private AutofillAssistantClient getOrCreateClient() {
         ThreadUtils.assertOnUiThread();
-        Tab tab = mActivityTabProvider.get();
-
-        if (tab == null || tab.getWebContents() == null) return null;
-
-        return AutofillAssistantClient.fromWebContents(tab.getWebContents());
+        WebContents webContents = getWebContents();
+        if (webContents == null) {
+            return null;
+        }
+        return AutofillAssistantClient.fromWebContents(webContents);
     }
 
     /** Extracts string arguments from a bundle. */
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantClient.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantClient.java
index 0eebb33..048e8cf 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantClient.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantClient.java
@@ -17,6 +17,7 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.BaseOnboardingCoordinator;
 import org.chromium.chrome.browser.autofill_assistant.trigger_scripts.AssistantTriggerScriptBridge;
 import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java
index 5659806..cbea6f37 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java
@@ -26,6 +26,9 @@
 import org.chromium.chrome.browser.autofill_assistant.metrics.LiteScriptOnboarding;
 import org.chromium.chrome.browser.autofill_assistant.metrics.LiteScriptStarted;
 import org.chromium.chrome.browser.autofill_assistant.metrics.OnBoarding;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.AssistantOnboardingResult;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.BaseOnboardingCoordinator;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.OnboardingCoordinatorFactory;
 import org.chromium.chrome.browser.autofill_assistant.trigger_scripts.AssistantTriggerScriptBridge;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
@@ -178,11 +181,10 @@
             return;
         }
 
-        BottomSheetOnboardingCoordinator onboardingCoordinator =
-                new BottomSheetOnboardingCoordinator(experimentIds, parameters, context,
-                        bottomSheetController, browserControls, compositorViewHolder,
-                        bottomSheetController.getScrimCoordinator());
-
+        BaseOnboardingCoordinator onboardingCoordinator =
+                OnboardingCoordinatorFactory.createOnboardingCoordinator(
+                        /* isDialogOnboardingEnabled = */ false, experimentIds, parameters, context,
+                        bottomSheetController, browserControls, compositorViewHolder);
         onboardingCoordinator.show(result -> {
             switch (result) {
                 case AssistantOnboardingResult.DISMISSED:
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
index 83dc4e5..60ced56 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip;
 import org.chromium.chrome.browser.autofill_assistant.metrics.DropOutReason;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.BaseOnboardingCoordinator;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BaseOnboardingCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BaseOnboardingCoordinator.java
similarity index 92%
rename from chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BaseOnboardingCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BaseOnboardingCoordinator.java
index faf8f01..906b5dee 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BaseOnboardingCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BaseOnboardingCoordinator.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.autofill_assistant;
+package org.chromium.chrome.browser.autofill_assistant.onboarding;
 
 import android.content.Context;
 import android.text.SpannableString;
@@ -20,6 +20,8 @@
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.autofill_assistant.R;
+import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantMetrics;
+import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantPreferencesUtil;
 import org.chromium.chrome.browser.autofill_assistant.metrics.OnBoarding;
 import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
@@ -67,7 +69,7 @@
     @Nullable
     ScrollView mView;
 
-    BaseOnboardingCoordinator(
+    public BaseOnboardingCoordinator(
             String experimentIds, Map<String, String> parameters, Context context) {
         mExperimentIds = experimentIds;
         mParameters = parameters;
@@ -82,6 +84,10 @@
      * <p>The {@code callback} will be called when the user accepts, cancels or dismisses the
      * onboarding.
      *
+     * <p>Note that the onboarding screen will be hidden after the callback returns. Call, from the
+     * callback, {@link #hide} to hide it earlier or {@link #transferControls} to take ownership of
+     * it and possibly keep it past the end of the callback.
+     *
      * <p>The {@code targetUrl} is the initial URL Autofill Assistant is being started on. The
      * navigation to that URL is allowed, other navigations will hide Autofill Assistant.
      */
@@ -92,16 +98,10 @@
     }
 
     /**
-     * Shows onboarding and provides the result to the given callback.
-     *
-     * <p>The {@code callback} will be called when the user accepts, cancels or dismisses the
-     * onboarding.
-     *
-     * <p>Note that the onboarding screen will be hidden after the callback returns. Call, from the
-     * callback, {@link #hide} to hide it earlier or {@link #transferControls} to take ownership of
-     * it and possibly keep it past the end of the callback.
+     * Same as {@link #show(Callback, WebContents, String)}, but does not break on navigation
+     * events.
      */
-    void show(Callback<Integer> callback) {
+    public void show(Callback<Integer> callback) {
         AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_SHOWN);
         mOnboardingShown = true;
 
@@ -130,9 +130,14 @@
         return mOnboardingShown;
     }
 
-    // TODO(b/175598484): Move transferControls to bottom sheet subclass
+    /**
+     * Transfers the overlay coordinator used by the onboarding to the caller. This is intended to
+     * be used to facilitate a smooth transition between onboarding and regular script, i.e., to
+     * avoid flickering of the overlay. Not all onboarding implementations show an overlay, so this
+     * may return null.
+     */
     @Nullable
-    AssistantOverlayCoordinator transferControls() {
+    public AssistantOverlayCoordinator transferControls() {
         return null;
     }
 
@@ -312,7 +317,7 @@
 
     /** Don't animate the user interface. */
     @VisibleForTesting
-    void disableAnimationForTesting() {
+    public void disableAnimationForTesting() {
         mAnimate = false;
     }
 
@@ -320,6 +325,13 @@
     abstract void initViewImpl(Callback<Integer> callback);
     abstract void showViewImpl();
 
+    /**
+     * Returns {@code true} between the time {@link #show} is called and the time
+     * the callback has returned.
+     */
+    @VisibleForTesting
+    public abstract boolean isInProgress();
+
     @NativeMethods
     interface Natives {
         void fetchOnboardingDefinition(
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BottomSheetOnboardingCoordinator.java
similarity index 93%
rename from chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BottomSheetOnboardingCoordinator.java
index eda5cc1..fa3fa1bc 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BottomSheetOnboardingCoordinator.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.autofill_assistant;
+package org.chromium.chrome.browser.autofill_assistant.onboarding;
 
 import android.content.Context;
 import android.view.Gravity;
@@ -13,10 +13,13 @@
 import android.widget.Space;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.Callback;
 import org.chromium.chrome.autofill_assistant.R;
+import org.chromium.chrome.browser.autofill_assistant.AssistantBottomBarDelegate;
+import org.chromium.chrome.browser.autofill_assistant.AssistantBottomSheetContent;
+import org.chromium.chrome.browser.autofill_assistant.BottomSheetUtils;
+import org.chromium.chrome.browser.autofill_assistant.LayoutUtils;
 import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
 import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel;
 import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayState;
@@ -152,7 +155,7 @@
      */
     @Nullable
     @Override
-    AssistantOverlayCoordinator transferControls() {
+    public AssistantOverlayCoordinator transferControls() {
         assert isInProgress();
         mContent = null;
         AssistantOverlayCoordinator coordinator = mOverlayCoordinator;
@@ -175,12 +178,8 @@
         destroy();
     }
 
-    /**
-     * Returns {@code true} between the time {@link #show} is called and the time
-     * the callback has returned.
-     */
-    @VisibleForTesting
-    boolean isInProgress() {
+    @Override
+    public boolean isInProgress() {
         return mContent != null;
     }
 }
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/DialogOnboardingCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/DialogOnboardingCoordinator.java
similarity index 92%
rename from chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/DialogOnboardingCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/DialogOnboardingCoordinator.java
index a201463..e9684b8 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/DialogOnboardingCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/DialogOnboardingCoordinator.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.autofill_assistant;
+package org.chromium.chrome.browser.autofill_assistant.onboarding;
 
 import android.content.Context;
 import android.content.DialogInterface.OnDismissListener;
@@ -17,6 +17,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.chrome.autofill_assistant.R;
+import org.chromium.chrome.browser.autofill_assistant.LayoutUtils;
 
 import java.util.Map;
 
@@ -81,4 +82,9 @@
         }
         destroy();
     }
+
+    @Override
+    public boolean isInProgress() {
+        return mDialog != null;
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/OnboardingCoordinatorFactory.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/OnboardingCoordinatorFactory.java
similarity index 95%
rename from chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/OnboardingCoordinatorFactory.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/OnboardingCoordinatorFactory.java
index d5774d7a..c5c6aa7 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/OnboardingCoordinatorFactory.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/OnboardingCoordinatorFactory.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.autofill_assistant;
+package org.chromium.chrome.browser.autofill_assistant.onboarding;
 
 import android.content.Context;
 
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/OnboardingView.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/OnboardingView.java
similarity index 95%
rename from chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/OnboardingView.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/OnboardingView.java
index 5bd6ddf2..86999d0 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/OnboardingView.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/OnboardingView.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.autofill_assistant;
+package org.chromium.chrome.browser.autofill_assistant.onboarding;
 
 import org.chromium.base.Callback;
 import org.chromium.content_public.browser.WebContents;
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScriptBridge.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScriptBridge.java
index c9b1d1b..81b6b18 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScriptBridge.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScriptBridge.java
@@ -14,15 +14,15 @@
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.autofill_assistant.AssistantCoordinator;
-import org.chromium.chrome.browser.autofill_assistant.AssistantOnboardingResult;
 import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantClient;
 import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantPreferencesUtil;
 import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiController;
-import org.chromium.chrome.browser.autofill_assistant.BaseOnboardingCoordinator;
-import org.chromium.chrome.browser.autofill_assistant.OnboardingCoordinatorFactory;
 import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip;
 import org.chromium.chrome.browser.autofill_assistant.header.AssistantHeaderModel;
 import org.chromium.chrome.browser.autofill_assistant.metrics.LiteScriptFinishedState;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.AssistantOnboardingResult;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.BaseOnboardingCoordinator;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.OnboardingCoordinatorFactory;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncherImpl;
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinatorTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinatorTest.java
index d1317e8..b90f64a 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinatorTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinatorTest.java
@@ -49,6 +49,9 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.AssistantOnboardingResult;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.BaseOnboardingCoordinator;
+import org.chromium.chrome.browser.autofill_assistant.onboarding.OnboardingCoordinatorFactory;
 import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
 import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel;
 import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayState;
@@ -64,6 +67,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Tests {@link BottomSheetOnboardingCoordinator}
@@ -95,11 +99,17 @@
                                     .getScrimCoordinator();
     }
 
-    private BottomSheetOnboardingCoordinator createCoordinator() {
-        BottomSheetOnboardingCoordinator coordinator =
-                new BottomSheetOnboardingCoordinator("", new HashMap<String, String>(), mActivity,
-                        mBottomSheetController, mActivity.getBrowserControlsManager(),
-                        mActivity.getCompositorViewHolder(), mScrimCoordinator);
+    private BaseOnboardingCoordinator createCoordinator() {
+        return createCoordinator("", new HashMap<>());
+    }
+
+    private BaseOnboardingCoordinator createCoordinator(
+            String experimentIds, Map<String, String> parameters) {
+        BaseOnboardingCoordinator coordinator =
+                OnboardingCoordinatorFactory.createOnboardingCoordinator(
+                        /* isDialogOnboardingEnabled = */ false, experimentIds, parameters,
+                        mActivity, mBottomSheetController, mActivity.getBrowserControlsManager(),
+                        mActivity.getCompositorViewHolder());
         coordinator.disableAnimationForTesting();
         return coordinator;
     }
@@ -121,7 +131,7 @@
         AutofillAssistantPreferencesUtil.setInitialPreferences(
                 expectedResult != AssistantOnboardingResult.ACCEPTED);
 
-        BottomSheetOnboardingCoordinator coordinator = createCoordinator();
+        BaseOnboardingCoordinator coordinator = createCoordinator();
         showOnboardingAndWait(coordinator, mCallback);
 
         assertTrue(TestThreadUtils.runOnUiThreadBlocking(coordinator::isInProgress));
@@ -137,7 +147,7 @@
     @Test
     @MediumTest
     public void testOnboardingWithNoTabs() {
-        BottomSheetOnboardingCoordinator coordinator = createCoordinator();
+        BaseOnboardingCoordinator coordinator = createCoordinator();
         showOnboardingAndWait(coordinator, mCallback);
 
         onView(withId(R.id.button_init_not_ok))
@@ -153,7 +163,7 @@
     @Test
     @MediumTest
     public void testTransferControls() throws Exception {
-        BottomSheetOnboardingCoordinator coordinator = createCoordinator();
+        BaseOnboardingCoordinator coordinator = createCoordinator();
 
         List<AssistantOverlayCoordinator> capturedOverlays =
                 Collections.synchronizedList(new ArrayList<>());
@@ -179,7 +189,7 @@
     @Test
     @MediumTest
     public void testShownFlag() {
-        BottomSheetOnboardingCoordinator coordinator = createCoordinator();
+        BaseOnboardingCoordinator coordinator = createCoordinator();
         assertFalse(coordinator.getOnboardingShown());
 
         showOnboardingAndWait(coordinator, mCallback);
@@ -193,11 +203,7 @@
 
         HashMap<String, String> parameters = new HashMap();
         parameters.put("INTENT", "RENT_CAR");
-        BottomSheetOnboardingCoordinator coordinator =
-                new BottomSheetOnboardingCoordinator("", parameters, mActivity,
-                        mBottomSheetController, mActivity.getBrowserControlsManager(),
-                        mActivity.getCompositorViewHolder(), mScrimCoordinator);
-        coordinator.disableAnimationForTesting();
+        BaseOnboardingCoordinator coordinator = createCoordinator("", parameters);
         showOnboardingAndWait(coordinator, mCallback);
 
         TextView termsView = mActivity.findViewById(R.id.onboarding_subtitle);
@@ -217,11 +223,7 @@
 
         HashMap<String, String> parameters = new HashMap();
         parameters.put("INTENT", "BUY_MOVIE_TICKET");
-        BottomSheetOnboardingCoordinator coordinator =
-                new BottomSheetOnboardingCoordinator("4363482", parameters, mActivity,
-                        mBottomSheetController, mActivity.getBrowserControlsManager(),
-                        mActivity.getCompositorViewHolder(), mScrimCoordinator);
-        coordinator.disableAnimationForTesting();
+        BaseOnboardingCoordinator coordinator = createCoordinator("4363482", parameters);
         showOnboardingAndWait(coordinator, mCallback);
 
         TextView termsView = mActivity.findViewById(R.id.onboarding_subtitle);
@@ -239,12 +241,7 @@
     public void testShowStandardInformationalText() {
         AutofillAssistantPreferencesUtil.setInitialPreferences(true);
 
-        HashMap<String, String> parameters = new HashMap();
-        BottomSheetOnboardingCoordinator coordinator =
-                new BottomSheetOnboardingCoordinator("", parameters, mActivity,
-                        mBottomSheetController, mActivity.getBrowserControlsManager(),
-                        mActivity.getCompositorViewHolder(), mScrimCoordinator);
-        coordinator.disableAnimationForTesting();
+        BaseOnboardingCoordinator coordinator = createCoordinator();
         showOnboardingAndWait(coordinator, mCallback);
 
         TextView termsView = mActivity.findViewById(R.id.onboarding_subtitle);
@@ -263,10 +260,7 @@
 
         HashMap<String, String> parameters = new HashMap<>();
         parameters.put("ONBOARDING_FETCH_TIMEOUT_MS", "0");
-        BottomSheetOnboardingCoordinator coordinator =
-                new BottomSheetOnboardingCoordinator("", parameters, mActivity,
-                        mBottomSheetController, mActivity.getBrowserControlsManager(),
-                        mActivity.getCompositorViewHolder(), mScrimCoordinator);
+        BaseOnboardingCoordinator coordinator = createCoordinator("", parameters);
 
         String expectedTitle = "Title";
         String expectedMessage = "Message";
@@ -278,7 +272,6 @@
         coordinator.addEntryToStringMap("terms_and_conditions", expectedTerms);
         coordinator.addEntryToStringMap("terms_and_conditions_url", expectedTermsUrl);
 
-        coordinator.disableAnimationForTesting();
         showOnboardingAndWait(coordinator, mCallback);
 
         assertEquals(((TextView) mActivity.findViewById(R.id.onboarding_try_assistant)).getText(),
@@ -315,14 +308,10 @@
 
         HashMap<String, String> parameters = new HashMap<>();
         parameters.put("ONBOARDING_FETCH_TIMEOUT_MS", "0");
-        BottomSheetOnboardingCoordinator coordinator =
-                new BottomSheetOnboardingCoordinator("", parameters, mActivity,
-                        mBottomSheetController, mActivity.getBrowserControlsManager(),
-                        mActivity.getCompositorViewHolder(), mScrimCoordinator);
+        BaseOnboardingCoordinator coordinator = createCoordinator("", parameters);
 
         coordinator.addEntryToStringMap("terms_and_conditions", "Bad terms");
 
-        coordinator.disableAnimationForTesting();
         showOnboardingAndWait(coordinator, mCallback);
 
         TextView termsMessage = mActivity.findViewById(R.id.google_terms_message);
@@ -341,15 +330,11 @@
 
         HashMap<String, String> parameters = new HashMap<>();
         parameters.put("ONBOARDING_FETCH_TIMEOUT_MS", "0");
-        BottomSheetOnboardingCoordinator coordinator =
-                new BottomSheetOnboardingCoordinator("", parameters, mActivity,
-                        mBottomSheetController, mActivity.getBrowserControlsManager(),
-                        mActivity.getCompositorViewHolder(), mScrimCoordinator);
+        BaseOnboardingCoordinator coordinator = createCoordinator("", parameters);
 
         coordinator.addEntryToStringMap(
                 "terms_and_conditions_url", "https://www.domain.com/something");
 
-        coordinator.disableAnimationForTesting();
         showOnboardingAndWait(coordinator, mCallback);
 
         TextView termsMessage = mActivity.findViewById(R.id.google_terms_message);
@@ -379,7 +364,7 @@
 
     /** Trigger onboarding and wait until it is fully displayed. */
     private void showOnboardingAndWait(
-            BottomSheetOnboardingCoordinator coordinator, Callback<Integer> callback) {
+            BaseOnboardingCoordinator coordinator, Callback<Integer> callback) {
         TestThreadUtils.runOnUiThreadBlocking(() -> coordinator.show(callback));
         waitUntilViewMatchesCondition(withId(R.id.button_init_ok), isCompletelyDisplayed());
     }
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPreferencesUtil.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPreferencesUtil.java
index e796def..59d1ea6a 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPreferencesUtil.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPreferencesUtil.java
@@ -64,7 +64,7 @@
     }
 
     /** Returns the number of times a user has explicitly canceled a lite script. */
-    static int getAutofillAssistantNumberOfLiteScriptsCanceled() {
+    private static int getAutofillAssistantNumberOfLiteScriptsCanceled() {
         return SharedPreferencesManager.getInstance().readInt(
                 ChromePreferenceKeys.AUTOFILL_ASSISTANT_NUMBER_OF_LITE_SCRIPTS_CANCELED, 0);
     }
@@ -73,13 +73,13 @@
      * Returns whether the user has explicitly canceled the lite script at least {@code
      * LITE_SCRIPT_MAX_NUM_CANCELED_TO_OPT_OUT} times.
      */
-    static boolean isAutofillAssistantLiteScriptCancelThresholdReached() {
+    public static boolean isAutofillAssistantLiteScriptCancelThresholdReached() {
         return getAutofillAssistantNumberOfLiteScriptsCanceled()
                 >= LITE_SCRIPT_MAX_NUM_CANCELED_TO_OPT_OUT;
     }
 
     /** Increments the number of times a user has explicitly canceled a lite script. */
-    static void incrementAutofillAssistantNumberOfLiteScriptsCanceled() {
+    public static void incrementAutofillAssistantNumberOfLiteScriptsCanceled() {
         int numCanceled = getAutofillAssistantNumberOfLiteScriptsCanceled() + 1;
         SharedPreferencesManager sharedPreferencesManager = SharedPreferencesManager.getInstance();
         sharedPreferencesManager.writeInt(
@@ -97,7 +97,7 @@
     }
 
     /** Checks whether the Autofill Assistant onboarding has been accepted. */
-    static boolean isAutofillOnboardingAccepted() {
+    public static boolean isAutofillOnboardingAccepted() {
         return SharedPreferencesManager.getInstance().readBoolean(
                        ChromePreferenceKeys.AUTOFILL_ASSISTANT_ONBOARDING_ACCEPTED, false)
                 ||
@@ -121,7 +121,7 @@
      *
      * @param accept Flag indicating whether the ToS have been accepted.
      */
-    static void setInitialPreferences(boolean accept) {
+    public static void setInitialPreferences(boolean accept) {
         if (accept) {
             SharedPreferencesManager.getInstance().writeBoolean(
                     ChromePreferenceKeys.AUTOFILL_ASSISTANT_ENABLED, accept);
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
index 5787c84..a819712b 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.feed.shared.FeedFeatures;
 import org.chromium.chrome.browser.feed.shared.FeedSurfaceDelegate;
 import org.chromium.chrome.browser.feed.shared.stream.Stream;
+import org.chromium.chrome.browser.ntp.ScrollableContainerDelegate;
 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderView;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
@@ -50,7 +51,8 @@
 
     ExploreSurfaceCoordinator(ChromeActivity activity, ViewGroup parentView,
             PropertyModel containerPropertyModel, boolean hasHeader,
-            BottomSheetController bottomSheetController, Supplier<Tab> parentTabSupplier) {
+            BottomSheetController bottomSheetController, Supplier<Tab> parentTabSupplier,
+            ScrollableContainerDelegate scrollableContainerDelegate) {
         mActivity = activity;
         mHasHeader = hasHeader;
         mParentTabSupplier = parentTabSupplier;
@@ -61,8 +63,8 @@
             @Override
             public FeedSurfaceCoordinator createFeedSurfaceCoordinator(
                     boolean isInNightMode, boolean isPlaceholderShown) {
-                return internalCreateFeedSurfaceCoordinator(
-                        mHasHeader, isInNightMode, isPlaceholderShown, bottomSheetController);
+                return internalCreateFeedSurfaceCoordinator(mHasHeader, isInNightMode,
+                        isPlaceholderShown, bottomSheetController, scrollableContainerDelegate);
             }
         };
     }
@@ -88,7 +90,8 @@
 
     private FeedSurfaceCoordinator internalCreateFeedSurfaceCoordinator(boolean hasHeader,
             boolean isInNightMode, boolean isPlaceholderShown,
-            BottomSheetController bottomSheetController) {
+            BottomSheetController bottomSheetController,
+            ScrollableContainerDelegate scrollableContainerDelegate) {
         if (mExploreSurfaceNavigationDelegate == null) {
             mExploreSurfaceNavigationDelegate =
                     new ExploreSurfaceNavigationDelegate(mParentTabSupplier);
@@ -122,7 +125,8 @@
                 mActivity.getSnackbarManager(), mActivity.getTabModelSelector(),
                 mActivity.getActivityTabProvider(), null, null, sectionHeaderView,
                 feedActionOptions, isInNightMode, this, mExploreSurfaceNavigationDelegate, profile,
-                isPlaceholderShown, bottomSheetController, mActivity.getShareDelegateSupplier());
+                isPlaceholderShown, bottomSheetController, mActivity.getShareDelegateSupplier(),
+                scrollableContainerDelegate);
         feedSurfaceCoordinator.getView().setId(R.id.start_surface_explore_view);
         return feedSurfaceCoordinator;
         // TODO(crbug.com/982018): Customize surface background for incognito and dark mode.
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
index b99be602..7a19237 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.features.start_surface;
 
+import android.view.View;
+
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
@@ -11,9 +13,12 @@
 
 import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ObserverList;
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.ntp.ScrollListener;
+import org.chromium.chrome.browser.ntp.ScrollableContainerDelegate;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tasks.TasksSurface;
@@ -26,6 +31,7 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.components.user_prefs.UserPrefs;
+import org.chromium.ui.base.ViewUtils;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -93,6 +99,45 @@
     private boolean mIsSecondaryTaskInitPending;
     private FeedLoadingCoordinator mFeedLoadingCoordinator;
 
+    // Listeners used by the contained surfaces (e.g., Explore) to listen to the scroll changes on
+    // the main scrollable container of the start surface.
+    private final ObserverList<ScrollListener> mScrollListeners =
+            new ObserverList<ScrollListener>();
+
+    @Nullable
+    private AppBarLayout.OnOffsetChangedListener mOffsetChangedListenerToGenerateScrollEvents;
+
+    private class ScrollableContainerDelegateImpl implements ScrollableContainerDelegate {
+        @Override
+        public void addScrollListener(ScrollListener listener) {
+            mScrollListeners.addObserver(listener);
+        }
+        @Override
+        public void removeScrollListener(ScrollListener listener) {
+            mScrollListeners.removeObserver(listener);
+        }
+
+        @Override
+        public int getVerticalScrollOffset() {
+            // Always return a zero dummy value because the offset is directly provided
+            // by the observer.
+            return 0;
+        }
+
+        @Override
+        public int getRootViewHeight() {
+            return mActivity.getCompositorViewHolder().getHeight();
+        }
+
+        @Override
+        public int getTopPositionRelativeToContainerView(View childView) {
+            int[] pos = new int[2];
+            ViewUtils.getRelativeLayoutPosition(
+                    mActivity.getCompositorViewHolder(), childView, pos);
+            return pos[1];
+        }
+    }
+
     // TODO(http://crbug.com/1093421): Remove dependency on ChromeActivity.
     public StartSurfaceCoordinator(ChromeActivity activity, ScrimCoordinator scrimCoordinator,
             BottomSheetController sheetController,
@@ -157,6 +202,10 @@
         if (mTasksSurface != null) {
             mTasksSurface.removeFakeSearchBoxShrinkAnimation();
         }
+        if (mOffsetChangedListenerToGenerateScrollEvents != null) {
+            removeHeaderOffsetChangeListener(mOffsetChangedListenerToGenerateScrollEvents);
+            mOffsetChangedListenerToGenerateScrollEvents = null;
+        }
     }
 
     @Override
@@ -215,7 +264,7 @@
                     mSurfaceMode == SurfaceMode.SINGLE_PANE ? mTasksSurface.getBodyViewContainer()
                                                             : mActivity.getCompositorViewHolder(),
                     mPropertyModel, mSurfaceMode == SurfaceMode.SINGLE_PANE, mBottomSheetController,
-                    mParentTabSupplier);
+                    mParentTabSupplier, new ScrollableContainerDelegateImpl());
         }
         mStartSurfaceMediator.initWithNative(mSurfaceMode != SurfaceMode.NO_START_SURFACE
                         ? mActivity.getToolbarManager().getFakeboxDelegate()
@@ -352,6 +401,15 @@
                 !excludeMVTiles, hasTrendyTerms);
         mTasksSurface.getView().setId(R.id.primary_tasks_surface_view);
         mTasksSurface.addFakeSearchBoxShrinkAnimation();
+        mOffsetChangedListenerToGenerateScrollEvents = new AppBarLayout.OnOffsetChangedListener() {
+            @Override
+            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
+                for (ScrollListener scrollListener : mScrollListeners) {
+                    scrollListener.onHeaderOffsetChanged(verticalOffset);
+                }
+            }
+        };
+        addHeaderOffsetChangeListener(mOffsetChangedListenerToGenerateScrollEvents);
 
         mTasksSurfacePropertyModelChangeProcessor = PropertyModelChangeProcessor.create(
                 mPropertyModel,
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinderTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinderTest.java
index 53c9823..3ae3a14 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinderTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinderTest.java
@@ -26,12 +26,16 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.feed.FeedSurfaceCoordinator;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.ntp.ScrollableContainerDelegate;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -51,6 +55,12 @@
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
 
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock
+    private ScrollableContainerDelegate mScrollableContainerDelegate;
+
     @Before
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityFromLauncher();
@@ -60,10 +70,10 @@
         // well in debug build).
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mPropertyModel = new PropertyModel(StartSurfaceProperties.ALL_KEYS);
-            mExploreSurfaceCoordinator =
-                    new ExploreSurfaceCoordinator(mActivityTestRule.getActivity(),
-                            mActivityTestRule.getActivity().getCompositorViewHolder(),
-                            mPropertyModel, true, null, new ObservableSupplierImpl<>());
+            mExploreSurfaceCoordinator = new ExploreSurfaceCoordinator(
+                    mActivityTestRule.getActivity(),
+                    mActivityTestRule.getActivity().getCompositorViewHolder(), mPropertyModel, true,
+                    null, new ObservableSupplierImpl<>(), mScrollableContainerDelegate);
             mFeedSurfaceCoordinator =
                     mExploreSurfaceCoordinator.getFeedSurfaceCreator().createFeedSurfaceCoordinator(
                             false, /* isPlaceholderShown= */ false);
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index 8937cce..a1fb2220 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -408,6 +408,7 @@
     @MediumTest
     @Feature({"StartSurface"})
     @CommandLineFlags.Add({BASE_PARAMS + "/single/home_button_on_grid_tab_switcher/false"})
+    @DisabledTest(message = "Failing/flaky on several bots, see crbug.com/1177359")
     public void testShow_SingleAsHomepage() {
         if (!mImmediateReturn) {
             onView(withId(org.chromium.chrome.tab_ui.R.id.home_button)).perform(click());
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index 28d52b67..06e782b5 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -37,11 +37,14 @@
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
 import org.chromium.chrome.browser.ntp.NewTabPageLayout;
+import org.chromium.chrome.browser.ntp.ScrollListener;
+import org.chromium.chrome.browser.ntp.ScrollableContainerDelegate;
 import org.chromium.chrome.browser.ntp.SnapScrollHelper;
 import org.chromium.chrome.browser.ntp.cards.promo.enhanced_protection.EnhancedProtectionPromoController;
 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderView;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.share.ShareDelegate;
+import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
 import org.chromium.chrome.browser.signin.ui.PersonalizedSigninPromoView;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
@@ -136,6 +139,11 @@
     // Used for the feed header menu.
     private UserEducationHelper mUserEducationHelper;
 
+    // Used to handle things related to the main scrollable container of NTP surface.
+    private @Nullable ScrollableContainerDelegate mScrollableContainerDelegate;
+
+    private @Nullable HeaderIphScrollListener mHeaderIphScrollListener;
+
     private final Handler mHandler = new Handler();
 
     private class SignInPromoHeader implements Header {
@@ -212,6 +220,35 @@
         }
     }
 
+    private class ScrollableContainerDelegateImpl implements ScrollableContainerDelegate {
+        @Override
+        public void addScrollListener(ScrollListener listener) {
+            mStream.addScrollListener(listener);
+        }
+
+        @Override
+        public void removeScrollListener(ScrollListener listener) {
+            mStream.removeScrollListener(listener);
+        }
+
+        @Override
+        public int getVerticalScrollOffset() {
+            return mMediator.getVerticalScrollOffset();
+        }
+
+        @Override
+        public int getRootViewHeight() {
+            return mRootView.getHeight();
+        }
+
+        @Override
+        public int getTopPositionRelativeToContainerView(View childView) {
+            int[] pos = new int[2];
+            ViewUtils.getRelativeLayoutPosition(mRootView, childView, pos);
+            return pos[1];
+        }
+    }
+
     /**
      * Constructs a new FeedSurfaceCoordinator.
      *  @param activity The containing {@link ChromeActivity}.
@@ -238,7 +275,8 @@
             boolean showDarkBackground, FeedSurfaceDelegate delegate,
             @Nullable NativePageNavigationDelegate pageNavigationDelegate, Profile profile,
             boolean isPlaceholderShownInitially, BottomSheetController bottomSheetController,
-            Supplier<ShareDelegate> shareDelegateSupplier) {
+            Supplier<ShareDelegate> shareDelegateSupplier,
+            @Nullable ScrollableContainerDelegate externalScrollableContainerDelegate) {
         mStreamWrapper = FeedV2.createStreamWrapper();
 
         mActivity = activity;
@@ -254,6 +292,7 @@
         mV1ActionOptions = actionOptions;
         mTabSupplier = tabProvider;
         mShareSupplier = shareDelegateSupplier;
+        mScrollableContainerDelegate = externalScrollableContainerDelegate;
 
         Resources resources = mActivity.getResources();
         mDefaultMarginPixels = mStreamWrapper.defaultMarginPixels(activity);
@@ -286,6 +325,11 @@
         if (mEnhancedProtectionPromoController != null) {
             mEnhancedProtectionPromoController.destroy();
         }
+        if (mScrollableContainerDelegate != null && mHeaderIphScrollListener != null) {
+            mScrollableContainerDelegate.removeScrollListener(mHeaderIphScrollListener);
+        }
+        mScrollableContainerDelegate = null;
+        mHeaderIphScrollListener = null;
     }
 
     @Override
@@ -513,32 +557,6 @@
         mStream.setHeaderViews(headers);
     }
 
-    /**
-     * Determines whether the feed header position in the recycler view is suitable for IPH.
-     *
-     * @param maxPosFraction The maximal fraction of the recycler view height starting from the top
-     *                       within which the top position of the feed header can be. The value has
-     *                       to be within the range [0.0, 1.0], where at 0.0 the feed header is at
-     *                       the very top of the recycler view and at 1.0 is at the very bottom and
-     *                       hidden.
-     * @return True If the feed header is at a position that is suitable to show the IPH.
-     */
-    boolean isFeedHeaderPositionInRecyclerViewSuitableForIPH(float maxPosFraction) {
-        assert maxPosFraction >= 0.0f
-                && maxPosFraction <= 1.0f
-            : "Max position fraction should be ranging between 0.0 and 1.0";
-
-        // Get the top position of the section header view in the recycler view.
-        int[] headerPositions = new int[2];
-        mSectionHeaderView.getLocationOnScreen(headerPositions);
-        int topPosInStream = headerPositions[1] - mRootView.getTop();
-
-        if (topPosInStream < 0) return false;
-        if (topPosInStream > maxPosFraction * mRootView.getHeight()) return false;
-
-        return true;
-    }
-
     public void onOverviewShownAtLaunch(long activityCreationTimeMs) {
         mMediator.onOverviewShownAtLaunch(activityCreationTimeMs, mIsPlaceholderShownInitially);
         StartSurfaceConfiguration.recordHistogram(FEED_STREAM_CREATED_TIME_MS_UMA,
@@ -582,4 +600,64 @@
     public Stream getStreamForTesting() {
         return getStream();
     }
+
+    /**
+     * Initializes things related to the IPH which will start listening to scroll events to
+     * determine whether the IPH should be triggered.
+     */
+    public void initializeIph() {
+        // Provide a delegate for the container of the feed surface that is handled by the feed
+        // coordinator itself when not provided externally (e.g., by the Start surface).
+        if (mScrollableContainerDelegate == null) {
+            mScrollableContainerDelegate = new ScrollableContainerDelegateImpl();
+        }
+
+        FeedSurfaceCoordinator coordinator = this;
+        HeaderIphScrollListener.Delegate delegate = new HeaderIphScrollListener.Delegate() {
+            @Override
+            public Tracker getFeatureEngagementTracker() {
+                return mTracker;
+            }
+            @Override
+            public void showMenuIph() {
+                mSectionHeaderView.showMenuIph(mUserEducationHelper);
+            }
+            @Override
+            public boolean isFeedExpanded() {
+                return mMediator.isExpanded();
+            }
+            @Override
+            public boolean isSignedIn() {
+                return IdentityServicesProvider.get()
+                        .getSigninManager(Profile.getLastUsedRegularProfile())
+                        .getIdentityManager()
+                        .hasPrimaryAccount();
+            }
+            @Override
+            public boolean isFeedHeaderPositionInContainerSuitableForIPH(
+                    float headerMaxPosFraction) {
+                return coordinator.isFeedHeaderPositionInContainerSuitableForIPH(
+                        headerMaxPosFraction);
+            }
+        };
+        mHeaderIphScrollListener =
+                new HeaderIphScrollListener(delegate, mScrollableContainerDelegate);
+        mScrollableContainerDelegate.addScrollListener(mHeaderIphScrollListener);
+    }
+
+    private boolean isFeedHeaderPositionInContainerSuitableForIPH(float headerMaxPosFraction) {
+        assert headerMaxPosFraction >= 0.0f
+                && headerMaxPosFraction <= 1.0f
+            : "Max position fraction should be ranging between 0.0 and 1.0";
+
+        int topPosInStream = mScrollableContainerDelegate.getTopPositionRelativeToContainerView(
+                mSectionHeaderView);
+        if (topPosInStream < 0) return false;
+        if (topPosInStream
+                > headerMaxPosFraction * mScrollableContainerDelegate.getRootViewHeight()) {
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
index 22d83d2..a36e6ad 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
@@ -21,11 +21,11 @@
 import org.chromium.chrome.browser.feed.shared.FeedFeatures;
 import org.chromium.chrome.browser.feed.shared.stream.Stream;
 import org.chromium.chrome.browser.feed.shared.stream.Stream.ContentChangedListener;
-import org.chromium.chrome.browser.feed.shared.stream.Stream.ScrollListener;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
 import org.chromium.chrome.browser.ntp.NewTabPageLayout;
+import org.chromium.chrome.browser.ntp.ScrollListener;
 import org.chromium.chrome.browser.ntp.SnapScrollHelper;
 import org.chromium.chrome.browser.ntp.cards.SignInPromo;
 import org.chromium.chrome.browser.ntp.cards.promo.enhanced_protection.EnhancedProtectionPromoController.EnhancedProtectionPromoStateListener;
@@ -43,7 +43,6 @@
 import org.chromium.chrome.features.start_surface.StartSurfaceConfiguration;
 import org.chromium.components.browser_ui.widget.listmenu.ListMenu;
 import org.chromium.components.browser_ui.widget.listmenu.ListMenuItemProperties;
-import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.components.prefs.PrefService;
 import org.chromium.components.search_engines.TemplateUrlService.TemplateUrlServiceObserver;
 import org.chromium.components.signin.identitymanager.IdentityManager;
@@ -200,6 +199,9 @@
                 public void onScrolled(int dx, int dy) {
                     mSnapScrollHelper.handleScroll();
                 }
+
+                @Override
+                public void onHeaderOffsetChanged(int verticalOffset) {}
             };
             stream.addScrollListener(mStreamScrollListener);
         }
@@ -262,45 +264,7 @@
             if (mHasHeaderMenu) {
                 mSectionHeader.setMenuModelList(buildMenuItems());
                 mSectionHeader.setListMenuDelegate(this::onItemSelected);
-                FeedSurfaceMediator mediator = this;
-                HeaderIphScrollListener.Delegate delegate = new HeaderIphScrollListener.Delegate() {
-                    @Override
-                    public Tracker getFeatureEngagementTracker() {
-                        return mCoordinator.getFeatureEngagementTracker();
-                    }
-                    @Override
-                    public Stream getStream() {
-                        return mCoordinator.getStream();
-                    }
-                    @Override
-                    public boolean isFeedHeaderPositionInRecyclerViewSuitableForIPH(
-                            float headerMaxPosFraction) {
-                        return mCoordinator.isFeedHeaderPositionInRecyclerViewSuitableForIPH(
-                                headerMaxPosFraction);
-                    }
-                    @Override
-                    public void showMenuIph() {
-                        mCoordinator.getSectionHeaderView().showMenuIph(
-                                mCoordinator.getUserEducationHelper());
-                    }
-                    @Override
-                    public int getVerticalScrollOffset() {
-                        return mediator.getVerticalScrollOffset();
-                    }
-                    @Override
-                    public boolean isFeedExpanded() {
-                        return mSectionHeader.isExpanded();
-                    }
-                    @Override
-                    public int getRootViewHeight() {
-                        return mCoordinator.getView().getHeight();
-                    }
-                    @Override
-                    public boolean isSignedIn() {
-                        return mSigninManager.getIdentityManager().hasPrimaryAccount();
-                    }
-                };
-                stream.addScrollListener(new HeaderIphScrollListener(delegate));
+                mCoordinator.initializeIph();
                 mSigninManager.getIdentityManager().addObserver(this);
             }
         }
@@ -314,6 +278,13 @@
         MemoryPressureListener.addCallback(mMemoryPressureCallback);
     }
 
+    /**
+     * Determines whether the feed is expanded (turned on).
+     */
+    public boolean isExpanded() {
+        return mSectionHeader.isExpanded();
+    }
+
     private void initStreamHeaderViews() {
         boolean signInPromoVisible = createSignInPromoIfNeeded();
         View enhancedProtectionPromoView = null;
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/HeaderIphScrollListener.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/HeaderIphScrollListener.java
index ce7aa50c..4cd8985 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/HeaderIphScrollListener.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/HeaderIphScrollListener.java
@@ -4,10 +4,10 @@
 
 package org.chromium.chrome.browser.feed;
 
-import org.chromium.chrome.browser.feed.shared.stream.Stream;
-import org.chromium.chrome.browser.feed.shared.stream.Stream.ScrollListener;
-import org.chromium.chrome.browser.feed.shared.stream.Stream.ScrollListener.ScrollState;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.ntp.ScrollListener;
+import org.chromium.chrome.browser.ntp.ScrollListener.ScrollState;
+import org.chromium.chrome.browser.ntp.ScrollableContainerDelegate;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.components.feature_engagement.TriggerState;
@@ -27,29 +27,52 @@
  * the feed visible. The goal of conditions (2) and (3) is to show the IPH when the signals are
  * that the user wants to interact with the feed are strong.
  */
-class HeaderIphScrollListener implements ScrollListener {
+public class HeaderIphScrollListener implements ScrollListener {
     static final String TOOLBAR_TRANSITION_FRACTION_PARAM_NAME = "toolbar-transition-fraction";
     static final String MIN_SCROLL_FRACTION_PARAM_NAME = "min-scroll-fraction";
     static final String HEADER_MAX_POSITION_FRACTION_NAME = "header-max-pos-fraction";
 
-    static interface Delegate {
+    /**
+     * Delegate to handle actions that are out of the scope of the listener.
+     */
+    public static interface Delegate {
+        /**
+         * Gets the feature engagement tracker.
+         */
         Tracker getFeatureEngagementTracker();
-        Stream getStream();
-        boolean isFeedHeaderPositionInRecyclerViewSuitableForIPH(float headerMaxPosFraction);
+
+        /**
+         * Shows the menu IPH.
+         */
         void showMenuIph();
-        int getVerticalScrollOffset();
+
+        /**
+         * Determines whether the feed is expanded (turned on).
+         */
         boolean isFeedExpanded();
-        int getRootViewHeight();
+
+        /**
+         * Determines whether the user is signed in.
+         */
         boolean isSignedIn();
+
+        /**
+         * Determines whether the position of the feed header in the NTP container is suitable for
+         * showing the IPH.
+         */
+        boolean isFeedHeaderPositionInContainerSuitableForIPH(float headerMaxPosFraction);
     }
 
     private Delegate mDelegate;
+    private ScrollableContainerDelegate mScrollableContainerDelegate;
 
     private float mMinScrollFraction;
     private float mHeaderMaxPosFraction;
 
-    HeaderIphScrollListener(Delegate delegate) {
+    HeaderIphScrollListener(
+            Delegate delegate, ScrollableContainerDelegate scrollableContainerDelegate) {
         mDelegate = delegate;
+        mScrollableContainerDelegate = scrollableContainerDelegate;
 
         mMinScrollFraction = (float) ChromeFeatureList.getFieldTrialParamByFeatureAsDouble(
                 ChromeFeatureList.REPORT_FEED_USER_ACTIONS, MIN_SCROLL_FRACTION_PARAM_NAME, 0.10);
@@ -60,12 +83,30 @@
 
     @Override
     public void onScrollStateChanged(@ScrollState int state) {
+        if (state != ScrollState.IDLE) return;
+
+        maybeTriggerIPH(mScrollableContainerDelegate.getVerticalScrollOffset());
+    }
+
+    @Override
+    public void onScrolled(int dx, int dy) {}
+
+    @Override
+    public void onHeaderOffsetChanged(int verticalOffset) {
+        if (verticalOffset == 0) return;
+
+        // Negate the vertical offset because it is inversely proportional to the scroll offset.
+        // For example, a header verical offset of -50px corresponds to a scroll offset of 50px.
+        maybeTriggerIPH(-verticalOffset);
+    }
+
+    private void maybeTriggerIPH(int verticalScrollOffset) {
         // Get the feature tracker for the IPH and determine whether to show the IPH.
         final String featureForIph = FeatureConstants.FEED_HEADER_MENU_FEATURE;
         final Tracker tracker = mDelegate.getFeatureEngagementTracker();
         // Stop listening to scroll if the IPH was already displayed in the past.
         if (tracker.getTriggerState(featureForIph) == TriggerState.HAS_BEEN_DISPLAYED) {
-            mDelegate.getStream().removeScrollListener(this);
+            mScrollableContainerDelegate.removeScrollListener(this);
             return;
         }
 
@@ -74,8 +115,6 @@
             return;
         }
 
-        if (state != ScrollState.IDLE) return;
-
         // Check whether the feed is expanded.
         if (!mDelegate.isFeedExpanded()) return;
 
@@ -83,19 +122,16 @@
         if (!mDelegate.isSignedIn()) return;
 
         // Check that enough scrolling was done proportionally to the stream height.
-        if ((float) mDelegate.getVerticalScrollOffset()
-                < (float) mDelegate.getRootViewHeight() * mMinScrollFraction) {
+        if ((float) verticalScrollOffset
+                < (float) mScrollableContainerDelegate.getRootViewHeight() * mMinScrollFraction) {
             return;
         }
 
         // Check that the feed header is well positioned in the recycler view to show the IPH.
-        if (!mDelegate.isFeedHeaderPositionInRecyclerViewSuitableForIPH(mHeaderMaxPosFraction)) {
+        if (!mDelegate.isFeedHeaderPositionInContainerSuitableForIPH(mHeaderMaxPosFraction)) {
             return;
         }
 
         mDelegate.showMenuIph();
     }
-
-    @Override
-    public void onScrolled(int dx, int dy) {}
 }
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/shared/stream/Stream.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/shared/stream/Stream.java
index a55e50c..05a78ba 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/shared/stream/Stream.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/shared/stream/Stream.java
@@ -9,6 +9,8 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
+import org.chromium.chrome.browser.ntp.ScrollListener;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
@@ -184,45 +186,4 @@
          */
         default void onAddStarting(){};
     }
-
-    /** Interface users can implement to be told about changes to scrolling in the Stream. */
-    interface ScrollListener {
-        /**
-         * Constant used to denote that a scroll was performed but scroll delta could not be
-         * calculated. This normally maps to a programmatic scroll.
-         */
-        int UNKNOWN_SCROLL_DELTA = Integer.MIN_VALUE;
-
-        void onScrollStateChanged(@ScrollState int state);
-
-        /**
-         * Called when a scroll happens and provides the amount of pixels scrolled. {@link
-         * #UNKNOWN_SCROLL_DELTA} will be specified if scroll delta would not be determined. An
-         * example of this would be a scroll initiated programmatically.
-         */
-        void onScrolled(int dx, int dy);
-
-        /**
-         * Possible scroll states.
-         *
-         * <p>When adding new values, the value of {@link ScrollState#NEXT_VALUE} should be used and
-         * incremented. When removing values, {@link ScrollState#NEXT_VALUE} should not be changed,
-         * and those values should not be reused.
-         */
-        @IntDef({ScrollState.IDLE, ScrollState.DRAGGING, ScrollState.SETTLING,
-                ScrollState.NEXT_VALUE})
-        @interface ScrollState {
-            /** Stream is not scrolling */
-            int IDLE = 0;
-
-            /** Stream is currently scrolling through external means such as user input. */
-            int DRAGGING = 1;
-
-            /** Stream is animating to a final position. */
-            int SETTLING = 2;
-
-            /** The next value that should be used when adding additional values to the IntDef. */
-            int NEXT_VALUE = 3;
-        }
-    }
 }
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
index b198b8d..12e7c7b8 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
@@ -24,6 +24,7 @@
 import org.chromium.chrome.browser.feed.shared.stream.Stream;
 import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncherImpl;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
+import org.chromium.chrome.browser.ntp.ScrollListener;
 import org.chromium.chrome.browser.share.ShareDelegate;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/HeaderIphScrollListenerTest.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/HeaderIphScrollListenerTest.java
index d2fc3c0..17cdb19 100644
--- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/HeaderIphScrollListenerTest.java
+++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/HeaderIphScrollListenerTest.java
@@ -24,8 +24,10 @@
 import org.chromium.base.test.params.ParameterizedRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.feed.shared.stream.Stream;
-import org.chromium.chrome.browser.feed.shared.stream.Stream.ScrollListener.ScrollState;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.ntp.ScrollListener;
+import org.chromium.chrome.browser.ntp.ScrollListener.ScrollState;
+import org.chromium.chrome.browser.ntp.ScrollableContainerDelegate;
 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderView;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.user_education.UserEducationHelper;
@@ -51,9 +53,6 @@
             // Trigger IPH.
             parameters.add(new ParameterSet().value(true, ScrollState.IDLE,
                     TriggerState.HAS_NOT_BEEN_DISPLAYED, true, 10, true, true, true));
-            // Don't trigger the IPH because the scroll state not iDLE.
-            parameters.add(new ParameterSet().value(false, ScrollState.DRAGGING,
-                    TriggerState.HAS_NOT_BEEN_DISPLAYED, true, 10, true, true, true));
             // Don't trigger the IPH because the state is not set to has been displayed.
             parameters.add(new ParameterSet().value(false, ScrollState.IDLE,
                     TriggerState.HAS_BEEN_DISPLAYED, true, 10, true, true, true));
@@ -76,6 +75,36 @@
         }
     }
 
+    /** Parameter provider for testing the trigger of the IPH from scroll events. */
+    public static class TestParamsForOnScroll extends TestParams {
+        @Override
+        public Iterable<ParameterSet> getParameters() {
+            List<ParameterSet> parameters = new ArrayList<>();
+            for (ParameterSet parameter : super.getParameters()) {
+                parameters.add(parameter);
+            }
+            // Don't trigger the IPH because the scroll state is not IDLE.
+            parameters.add(new ParameterSet().value(false, ScrollState.DRAGGING,
+                    TriggerState.HAS_NOT_BEEN_DISPLAYED, true, 10, true, true, true));
+            return parameters;
+        }
+    }
+
+    /** Parameter provider for testing the trigger of the IPH from offset changes events. */
+    public static class TestParamsForOnOffsetChanged extends TestParams {
+        @Override
+        public Iterable<ParameterSet> getParameters() {
+            List<ParameterSet> parameters = new ArrayList<>();
+            for (ParameterSet parameter : super.getParameters()) {
+                parameters.add(parameter);
+            }
+            // Don't trigger the IPH because the vertical offset is 0.
+            parameters.add(new ParameterSet().value(false, ScrollState.IDLE,
+                    TriggerState.HAS_NOT_BEEN_DISPLAYED, true, 0, true, true, true));
+            return parameters;
+        }
+    }
+
     private static final int FEED_VIEW_HEIGHT = 100;
 
     @Mock
@@ -111,7 +140,7 @@
     @Test
     @MediumTest
     @Feature({"Feed"})
-    @ParameterAnnotations.UseMethodParameter(TestParams.class)
+    @ParameterAnnotations.UseMethodParameter(TestParamsForOnScroll.class)
     public void onScrollStateChanged_triggerIph(boolean expectEnabled, int scrollState,
             int triggerState, boolean wouldTriggerHelpUI, int verticallScrollOffset,
             boolean isFeedHeaderPositionInRecyclerViewSuitableForIPH, boolean isFeedExpanded,
@@ -122,45 +151,124 @@
         when(mTracker.wouldTriggerHelpUI(FeatureConstants.FEED_HEADER_MENU_FEATURE))
                 .thenReturn(wouldTriggerHelpUI);
 
-        HeaderIphScrollListener.Delegate delegate = new HeaderIphScrollListener.Delegate() {
+        HeaderIphScrollListener.Delegate iphDelegate = new HeaderIphScrollListener.Delegate() {
             @Override
             public Tracker getFeatureEngagementTracker() {
                 return mTracker;
             }
             @Override
-            public Stream getStream() {
-                return mStream;
-            }
-            @Override
-            public boolean isFeedHeaderPositionInRecyclerViewSuitableForIPH(
-                    float headerMaxPosFraction) {
-                return isFeedHeaderPositionInRecyclerViewSuitableForIPH;
-            }
-            @Override
             public void showMenuIph() {
                 mHasShownMenuIph = true;
             }
             @Override
-            public int getVerticalScrollOffset() {
-                return verticallScrollOffset;
+            public boolean isFeedExpanded() {
+                return isFeedExpanded;
+            }
+            @Override
+            public boolean isSignedIn() {
+                return isSignedIn;
+            }
+            @Override
+            public boolean isFeedHeaderPositionInContainerSuitableForIPH(
+                    float headerMaxPosFraction) {
+                return isFeedHeaderPositionInRecyclerViewSuitableForIPH;
+            }
+        };
+
+        ScrollableContainerDelegate scrollableContainerDelegate =
+                new ScrollableContainerDelegate() {
+                    @Override
+                    public void addScrollListener(ScrollListener listener) {}
+                    @Override
+                    public void removeScrollListener(ScrollListener listener) {}
+                    @Override
+                    public int getVerticalScrollOffset() {
+                        return verticallScrollOffset;
+                    }
+                    @Override
+                    public int getRootViewHeight() {
+                        return FEED_VIEW_HEIGHT;
+                    }
+                    @Override
+                    public int getTopPositionRelativeToContainerView(View childView) {
+                        return 0;
+                    }
+                };
+
+        // Trigger IPH through the scroll listener.
+        HeaderIphScrollListener listener =
+                new HeaderIphScrollListener(iphDelegate, scrollableContainerDelegate);
+        listener.onScrollStateChanged(scrollState);
+
+        if (expectEnabled) {
+            Assert.assertTrue(mHasShownMenuIph);
+        } else {
+            Assert.assertFalse(mHasShownMenuIph);
+        }
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"Feed"})
+    @ParameterAnnotations.UseMethodParameter(TestParamsForOnOffsetChanged.class)
+    public void onScrollStateChanged_onHeaderOffsetChanged(boolean expectEnabled, int scrollState,
+            int triggerState, boolean wouldTriggerHelpUI, int verticallScrollOffset,
+            boolean isFeedHeaderPositionInRecyclerViewSuitableForIPH, boolean isFeedExpanded,
+            boolean isSignedIn) throws Exception {
+        // Set Tracker mock.
+        when(mTracker.getTriggerState(FeatureConstants.FEED_HEADER_MENU_FEATURE))
+                .thenReturn(triggerState);
+        when(mTracker.wouldTriggerHelpUI(FeatureConstants.FEED_HEADER_MENU_FEATURE))
+                .thenReturn(wouldTriggerHelpUI);
+
+        HeaderIphScrollListener.Delegate iphDelegate = new HeaderIphScrollListener.Delegate() {
+            @Override
+            public Tracker getFeatureEngagementTracker() {
+                return mTracker;
+            }
+            @Override
+            public void showMenuIph() {
+                mHasShownMenuIph = true;
             }
             @Override
             public boolean isFeedExpanded() {
                 return isFeedExpanded;
             }
             @Override
-            public int getRootViewHeight() {
-                return FEED_VIEW_HEIGHT;
-            }
-            @Override
             public boolean isSignedIn() {
                 return isSignedIn;
             }
+            @Override
+            public boolean isFeedHeaderPositionInContainerSuitableForIPH(
+                    float headerMaxPosFraction) {
+                return isFeedHeaderPositionInRecyclerViewSuitableForIPH;
+            }
         };
 
+        ScrollableContainerDelegate scrollableContainerDelegate =
+                new ScrollableContainerDelegate() {
+                    @Override
+                    public void addScrollListener(ScrollListener listener) {}
+                    @Override
+                    public void removeScrollListener(ScrollListener listener) {}
+                    @Override
+                    public int getVerticalScrollOffset() {
+                        return 0;
+                    }
+                    @Override
+                    public int getRootViewHeight() {
+                        return FEED_VIEW_HEIGHT;
+                    }
+                    @Override
+                    public int getTopPositionRelativeToContainerView(View childView) {
+                        return 0;
+                    }
+                };
+
         // Trigger IPH through the scroll listener.
-        HeaderIphScrollListener listener = new HeaderIphScrollListener(delegate);
-        listener.onScrollStateChanged(scrollState);
+        HeaderIphScrollListener listener =
+                new HeaderIphScrollListener(iphDelegate, scrollableContainerDelegate);
+        listener.onHeaderOffsetChanged(-verticallScrollOffset);
 
         if (expectEnabled) {
             Assert.assertTrue(mHasShownMenuIph);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index f50192d..706c64d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -58,6 +58,8 @@
 import org.chromium.chrome.browser.IntentHandler.TabOpenType;
 import org.chromium.chrome.browser.accessibility_tab_switcher.OverviewListLayout;
 import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
+import org.chromium.chrome.browser.app.metrics.TabbedActivityLaunchCauseMetrics;
 import org.chromium.chrome.browser.app.tabmodel.AsyncTabParamsManagerSingleton;
 import org.chromium.chrome.browser.app.tabmodel.ChromeNextTabPolicySupplier;
 import org.chromium.chrome.browser.app.tabmodel.TabModelOrchestrator;
@@ -100,10 +102,8 @@
 import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.locale.LocaleManager;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.metrics.LaunchMetrics;
 import org.chromium.chrome.browser.metrics.MainIntentBehaviorMetrics;
-import org.chromium.chrome.browser.metrics.TabbedActivityLaunchCauseMetrics;
 import org.chromium.chrome.browser.modaldialog.ChromeTabModalPresenter;
 import org.chromium.chrome.browser.modaldialog.TabModalLifetimeHandler;
 import org.chromium.chrome.browser.multiwindow.MultiInstanceChromeTabbedActivity;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
index 6e8acd26a..24cd509b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
@@ -217,6 +217,7 @@
     private static final String YOUTUBE_LINK_PREFIX_HTTPS = "https://www.youtube.com/redirect?";
     private static final String YOUTUBE_LINK_PREFIX_HTTP = "http://www.youtube.com/redirect?";
     private static final String BRING_TAB_TO_FRONT_EXTRA = "BRING_TAB_TO_FRONT";
+    public static final String BRING_TAB_TO_FRONT_SOURCE_EXTRA = "BRING_TAB_TO_FRONT_SOURCE";
 
     /**
      * Represents popular external applications that can load a page in Chrome via intent.
@@ -308,6 +309,16 @@
         String REUSE_TAB_ORIGINAL_URL_STRING = "REUSE_TAB_ORIGINAL_URL";
     }
 
+    @IntDef({BringToFrontSource.ACTIVATE_TAB, BringToFrontSource.NOTIFICATION,
+            BringToFrontSource.SEARCH_ACTIVITY})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BringToFrontSource {
+        int INVALID = -1;
+        int ACTIVATE_TAB = 0;
+        int NOTIFICATION = 1;
+        int SEARCH_ACTIVITY = 2;
+    }
+
     /**
      * A delegate interface for users of IntentHandler.
      */
@@ -1426,10 +1437,13 @@
      * Creates an Intent that tells Chrome to bring an Activity for a particular Tab back to the
      * foreground.
      * @param tabId The id of the Tab to bring to the foreground.
+     * @param bringToFrontSource The source of the bring to front Intent, used for gathering
+     *         metrics.
      * @return Created Intent or null if this operation isn't possible.
      */
     @Nullable
-    public static Intent createTrustedBringTabToFrontIntent(int tabId) {
+    public static Intent createTrustedBringTabToFrontIntent(
+            int tabId, @BringToFrontSource int bringToFrontSource) {
         // Iterate through all {@link CustomTab}s and check whether the given tabId belongs to a
         // {@link CustomTab}. If so, return null as the client app's task cannot be foregrounded.
         for (Activity activity : ApplicationStatus.getRunningActivities()) {
@@ -1444,6 +1458,7 @@
         Intent intent = new Intent(context, ChromeLauncherActivity.class);
         intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
         intent.putExtra(BRING_TAB_TO_FRONT_EXTRA, tabId);
+        intent.putExtra(BRING_TAB_TO_FRONT_SOURCE_EXTRA, bringToFrontSource);
         IntentHandler.addTrustedIntentExtras(intent);
         return intent;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ServiceTabLauncher.java b/chrome/android/java/src/org/chromium/chrome/browser/ServiceTabLauncher.java
index fafbf00..304d927 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ServiceTabLauncher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ServiceTabLauncher.java
@@ -189,7 +189,7 @@
     }
 
     @NativeMethods
-    interface Natives {
+    public interface Natives {
         void onWebContentsForRequestAvailable(int requestId, WebContents webContents);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index 08a32a15..fca8df9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -69,6 +69,7 @@
 import org.chromium.chrome.browser.accessibility.FontSizePrefs;
 import org.chromium.chrome.browser.app.appmenu.AppMenuPropertiesDelegateImpl;
 import org.chromium.chrome.browser.app.flags.ChromeCachedFlags;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.app.tab_activity_glue.ReparentingDelegateFactory;
 import org.chromium.chrome.browser.app.tab_activity_glue.TabReparentingController;
 import org.chromium.chrome.browser.app.tabmodel.AsyncTabParamsManagerSingleton;
@@ -118,7 +119,6 @@
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.media.PictureInPictureController;
 import org.chromium.chrome.browser.metrics.ActivityTabStartupMetricsTracker;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.metrics.LaunchMetrics;
 import org.chromium.chrome.browser.metrics.UmaSessionStats;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/LaunchCauseMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/app/metrics/LaunchCauseMetrics.java
similarity index 82%
rename from chrome/android/java/src/org/chromium/chrome/browser/metrics/LaunchCauseMetrics.java
rename to chrome/android/java/src/org/chromium/chrome/browser/app/metrics/LaunchCauseMetrics.java
index 9e5328b..e3dab43 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/LaunchCauseMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/metrics/LaunchCauseMetrics.java
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.metrics;
+package org.chromium.chrome.browser.app.metrics;
 
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.view.Display;
 
@@ -17,6 +18,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CheckDiscard;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.ui.display.DisplayAndroidManager;
 
 import java.lang.annotation.Retention;
@@ -25,7 +27,8 @@
 /**
  * Computes and records metrics for what caused Chrome to be launched.
  */
-public abstract class LaunchCauseMetrics implements ApplicationStatus.ApplicationStateListener {
+public abstract class LaunchCauseMetrics implements ApplicationStatus.ApplicationStateListener,
+                                                    ApplicationStatus.ActivityStateListener {
     private static final boolean DEBUG = false;
     private static final String TAG = "LaunchCauseMetrics";
 
@@ -38,11 +41,26 @@
 
     private PerLaunchState mPerLaunchState = new PerLaunchState();
     private BetweenLaunchState mBetweenLaunchState = new BetweenLaunchState();
+    private final Activity mActivity;
+
+    @SuppressLint("StaticFieldLeak")
+    private static Activity sLastResumedActivity;
+    static {
+        ApplicationStatus.registerStateListenerForAllActivities((activity, newState) -> {
+            if (newState == ActivityState.RESUMED) sLastResumedActivity = activity;
+            if (newState == ActivityState.DESTROYED) {
+                if (activity == sLastResumedActivity) sLastResumedActivity = null;
+            }
+        });
+    }
 
     // State pertaining to the current launch, reset when Chrome is backgrounded,
     // and after computing LaunchCause.
     private static class PerLaunchState {
         boolean mReceivedIntent;
+        // Whether a ChromeActivity other than |mActivity| was last focused, used to track
+        // intentional transitions between different types of ChromeActivity.
+        boolean mOtherChromeActivityLastFocused;
         boolean mLaunchedFromRecents;
     }
 
@@ -58,7 +76,7 @@
             LaunchCause.RECENTS_OR_BACK, LaunchCause.FOREGROUND_WHEN_LOCKED,
             LaunchCause.MAIN_LAUNCHER_ICON, LaunchCause.MAIN_LAUNCHER_ICON_SHORTCUT,
             LaunchCause.HOME_SCREEN_WIDGET, LaunchCause.OPEN_IN_BROWSER_FROM_MENU,
-            LaunchCause.EXTERNAL_SEARCH_ACTION_INTENT})
+            LaunchCause.EXTERNAL_SEARCH_ACTION_INTENT, LaunchCause.NOTIFICATION})
     @Retention(RetentionPolicy.SOURCE)
     public @interface LaunchCause {
         int OTHER = 0;
@@ -72,8 +90,9 @@
         int HOME_SCREEN_WIDGET = 8;
         int OPEN_IN_BROWSER_FROM_MENU = 9;
         int EXTERNAL_SEARCH_ACTION_INTENT = 10;
+        int NOTIFICATION = 11;
 
-        int NUM_ENTRIES = 11;
+        int NUM_ENTRIES = 12;
     }
 
     /**
@@ -81,15 +100,21 @@
      *         Display, etc.
      */
     public LaunchCauseMetrics(final Activity activity) {
+        mActivity = activity;
         ApplicationStatus.registerApplicationStateListener(this);
-        ApplicationStatus.registerStateListenerForActivity((a, newState) -> {
-            if (newState == ActivityState.PAUSED) {
-                mBetweenLaunchState.mScreenOffWhenPaused = isDisplayOff(a);
-            }
-            if (newState == ActivityState.DESTROYED) {
-                ApplicationStatus.unregisterApplicationStateListener(this);
-            }
-        }, activity);
+        ApplicationStatus.registerStateListenerForActivity(this, activity);
+    }
+
+    @Override
+    public void onActivityStateChange(Activity activity, @ActivityState int newState) {
+        assert activity == mActivity;
+        if (newState == ActivityState.DESTROYED) {
+            ApplicationStatus.unregisterApplicationStateListener(this);
+            ApplicationStatus.unregisterActivityStateListener(this);
+        }
+        if (newState == ActivityState.PAUSED) {
+            mBetweenLaunchState.mScreenOffWhenPaused = isDisplayOff(mActivity);
+        }
     }
 
     @Override
@@ -154,12 +179,13 @@
 
             RecordHistogram.recordEnumeratedHistogram(
                     LAUNCH_CAUSE_HISTOGRAM, cause, LaunchCause.NUM_ENTRIES);
-        } else {
+        } else if (mPerLaunchState.mOtherChromeActivityLastFocused) {
             // Handle the case where we're intentionally transitioning between two Chrome
             // Activities while Chrome is in the foreground, and want to count that as a Launch.
             @LaunchCause
             int cause = getIntentionalTransitionCauseOrOther();
             if (cause != LaunchCause.OTHER) {
+                if (DEBUG) logLaunchCause(cause);
                 RecordHistogram.recordEnumeratedHistogram(
                         LAUNCH_CAUSE_HISTOGRAM, cause, LaunchCause.NUM_ENTRIES);
             }
@@ -201,6 +227,8 @@
      * arbitrary explicit intents targeting Chrome.
      */
     public void onReceivedIntent() {
+        mPerLaunchState.mOtherChromeActivityLastFocused =
+                sLastResumedActivity != mActivity && sLastResumedActivity instanceof ChromeActivity;
         mPerLaunchState.mReceivedIntent = true;
     }
 
@@ -263,6 +291,9 @@
             case LaunchCause.EXTERNAL_SEARCH_ACTION_INTENT:
                 launchCause = "EXTERNAL_SEARCH_ACTION_INTENT";
                 break;
+            case LaunchCause.NOTIFICATION:
+                launchCause = "NOTIFICATION";
+                break;
         }
         Log.d(TAG, "Launch Cause: " + launchCause);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetrics.java
similarity index 65%
rename from chrome/android/java/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetrics.java
rename to chrome/android/java/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetrics.java
index f7f6203..892ee65 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetrics.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.metrics;
+package org.chromium.chrome.browser.app.metrics;
 
 import android.app.Activity;
 import android.content.Intent;
@@ -10,9 +10,11 @@
 
 import org.chromium.base.IntentUtils;
 import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.ServiceTabLauncher;
 import org.chromium.chrome.browser.ShortcutHelper;
 import org.chromium.chrome.browser.searchwidget.SearchActivity;
 import org.chromium.chrome.browser.searchwidget.SearchWidgetProvider;
+import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.webapps.ShortcutSource;
 
 /**
@@ -63,6 +65,13 @@
             return LaunchCause.EXTERNAL_SEARCH_ACTION_INTENT;
         }
 
+        if (isNotificationLaunch(launchIntent)) return LaunchCause.NOTIFICATION;
+
+        if (IntentHandler.BringToFrontSource.SEARCH_ACTIVITY
+                == getBringTabToFrontSource(launchIntent)) {
+            return LaunchCause.HOME_SCREEN_WIDGET;
+        }
+
         // This is unlikely to be hit here (much more likely to see Open In Browser intents in
         // getIntentionalTransitionCauseOrOther() below), but an Intent Picker dialog could cause
         // Chrome to be backgrounded on some Android distributions, or on tiny screens. This will
@@ -89,6 +98,40 @@
             return LaunchCause.OPEN_IN_BROWSER_FROM_MENU;
         }
 
+        // Notifications should still count as an intentional transition when moving between
+        // ChromeActivitys.
+        if (isNotificationLaunch(launchIntent)) return LaunchCause.NOTIFICATION;
+
         return LaunchCause.OTHER;
     }
+
+    private boolean isNotificationLaunch(Intent intent) {
+        // ServiceWorker notification to open a new window.
+        if (intent.hasExtra(ServiceTabLauncher.LAUNCH_REQUEST_ID_EXTRA)) {
+            return true;
+        }
+
+        // For now, just assume that a source of ACTIVATE_TAB counts as a notification, as there are
+        // many reasons why a tab/webcontents might get Activated by Chrome (and plumbing all
+        // sources of tab activation is impractical), but the only ones that should be triggerable
+        // while Chrome is in the background (outside of tests) are from media/ServiceWorker
+        // notifications.
+        @IntentHandler.BringToFrontSource
+        int source = getBringTabToFrontSource(intent);
+        if (IntentHandler.BringToFrontSource.NOTIFICATION == source
+                || IntentHandler.BringToFrontSource.ACTIVATE_TAB == source) {
+            return true;
+        }
+        return false;
+    }
+
+    // Returns the source of the BringTabToFront intent, or null if it's not a BringTabToFront
+    // intent.
+    private int getBringTabToFrontSource(Intent intent) {
+        if (IntentHandler.getBringTabToFrontId(intent) == Tab.INVALID_TAB_ID) {
+            return IntentHandler.BringToFrontSource.INVALID;
+        }
+        return IntentUtils.safeGetIntExtra(intent, IntentHandler.BRING_TAB_TO_FRONT_SOURCE_EXTRA,
+                IntentHandler.BringToFrontSource.INVALID);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java
index b391b50..23c448d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java
@@ -239,7 +239,8 @@
         // Note that calling only the intent in order to activate the tab is slightly slower
         // because it will change the tab when the intent is handled, which happens after
         // Chrome gets back to the foreground.
-        Intent newIntent = IntentHandler.createTrustedBringTabToFrontIntent(mTab.getId());
+        Intent newIntent = IntentHandler.createTrustedBringTabToFrontIntent(
+                mTab.getId(), IntentHandler.BringToFrontSource.ACTIVATE_TAB);
         if (newIntent != null) {
             newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             ContextUtils.getApplicationContext().startActivity(newIntent);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java
index 8d3e59c4..dfc2cd1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java
@@ -140,8 +140,7 @@
 
         AsyncTask.takeOverAndroidThreadPool();
         JNIUtils.setClassLoader(getClassLoader());
-        ResourceBundle.setAvailablePakLocales(
-                ProductConfig.COMPRESSED_LOCALES, ProductConfig.UNCOMPRESSED_LOCALES);
+        ResourceBundle.setAvailablePakLocales(ProductConfig.LOCALES);
         LibraryLoader.getInstance().setLinkerImplementation(
                 ProductConfig.USE_CHROMIUM_LINKER, ProductConfig.USE_MODERN_LINKER);
         LibraryLoader.getInstance().enableJniChecks();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index c71f3bd..2ba586e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -31,6 +31,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.LaunchIntentDispatcher;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantFacade;
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider.CustomTabsUiType;
@@ -39,7 +40,6 @@
 import org.chromium.chrome.browser.customtabs.features.CustomTabNavigationBarController;
 import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
 import org.chromium.chrome.browser.infobar.InfoBarContainer;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
 import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
 import org.chromium.chrome.browser.page_info.ChromePageInfoControllerDelegate;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIncognitoManager.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIncognitoManager.java
index dc6861b..e0167d45 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIncognitoManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIncognitoManager.java
@@ -19,7 +19,6 @@
 import org.chromium.chrome.browser.customtabs.content.CustomTabActivityNavigationController;
 import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabProvider;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
@@ -104,11 +103,6 @@
         KEY.detachFromAllHosts(manager);
     }
 
-    public boolean isEnabledIncognitoCCT() {
-        return mIntentDataProvider.isIncognito()
-                && ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_INCOGNITO);
-    }
-
     public Profile getProfile() {
         if (mOTRProfileID == null) mOTRProfileID = OTRProfileID.createUnique("CCT:Incognito");
         return Profile.getLastUsedRegularProfile().getOffTheRecordProfile(mOTRProfileID);
@@ -116,7 +110,7 @@
 
     @Override
     public void onFinishNativeInitialization() {
-        if (isEnabledIncognitoCCT()) {
+        if (mIntentDataProvider.isIncognito()) {
             initializeIncognito();
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabLaunchCauseMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabLaunchCauseMetrics.java
index d11a9cc..55c9897b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabLaunchCauseMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabLaunchCauseMetrics.java
@@ -4,8 +4,8 @@
 
 package org.chromium.chrome.browser.customtabs;
 
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.flags.ActivityType;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 
 /**
  * LaunchCauseMetrics for CustomTabActivity.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
index f1c0b34e..3eb3ec5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
@@ -446,13 +446,11 @@
         }
 
         recordWebContentsStateOnLaunch(WebContentsState.NO_WEBCONTENTS);
-        if (mCustomTabIncognitoManager.get().isEnabledIncognitoCCT()) {
+        if (mIntentDataProvider.isIncognito()) {
             return mWebContentsFactory.createWebContentsWithWarmRenderer(
                     mCustomTabIncognitoManager.get().getProfile(), false);
         } else {
-            Profile profile = mIntentDataProvider.isIncognito()
-                    ? mProfileProvider.getPrimaryOTRProfile()
-                    : mProfileProvider.getLastUsedRegularProfile();
+            Profile profile = mProfileProvider.getLastUsedRegularProfile();
             return mWebContentsFactory.createWebContentsWithWarmRenderer(profile, false);
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
index a80d418..af17aca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
@@ -130,8 +130,10 @@
                 // the ChromeTabbedActivity.
                 tab.loadUrl(params);
 
-                // Bring Chrome to the foreground, if possible.
-                Intent intent = IntentHandler.createTrustedBringTabToFrontIntent(tab.getId());
+                // Bring Chrome to the foreground, if possible. Unless Chrome is already in the
+                // foreground, this request is most likely coming from a notification.
+                Intent intent = IntentHandler.createTrustedBringTabToFrontIntent(
+                        tab.getId(), IntentHandler.BringToFrontSource.NOTIFICATION);
                 if (intent != null) {
                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                     IntentUtils.safeStartActivity(appContext, intent);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationServiceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationServiceImpl.java
index 9a0042a..64c2480d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationServiceImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationServiceImpl.java
@@ -173,7 +173,8 @@
                                 NotificationUmaTracker.SystemNotificationType.MEDIA_CAPTURE,
                                 NOTIFICATION_NAMESPACE, notificationId));
 
-        Intent tabIntent = IntentHandler.createTrustedBringTabToFrontIntent(notificationId);
+        Intent tabIntent = IntentHandler.createTrustedBringTabToFrontIntent(
+                notificationId, IntentHandler.BringToFrontSource.NOTIFICATION);
         PendingIntentProvider contentIntent = tabIntent == null
                 ? null
                 : PendingIntentProvider.getActivity(appContext, notificationId, tabIntent, 0);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterClient.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterClient.java
index 69eed8d..c02b580 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterClient.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterClient.java
@@ -41,7 +41,8 @@
 
     @Override
     public Intent createBringTabToFrontIntent(int tabId) {
-        return IntentHandler.createTrustedBringTabToFrontIntent(tabId);
+        return IntentHandler.createTrustedBringTabToFrontIntent(
+                tabId, IntentHandler.BringToFrontSource.NOTIFICATION);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java
index a1d12b4..de2f433 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java
@@ -85,7 +85,8 @@
 
     @Override
     public Intent createBringTabToFrontIntent() {
-        return IntentHandler.createTrustedBringTabToFrontIntent(mTab.getId());
+        return IntentHandler.createTrustedBringTabToFrontIntent(
+                mTab.getId(), IntentHandler.BringToFrontSource.NOTIFICATION);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index 1036415f..bcacbc3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -426,7 +426,7 @@
                         mNewTabPageLayout, sectionHeaderView, new FeedV1ActionOptions(),
                         isInNightMode, this, mNewTabPageManager.getNavigationDelegate(), profile,
                         /* isPlaceholderShownInitially= */ false, bottomSheetController,
-                        shareDelegateSupplier);
+                        shareDelegateSupplier, /* externalScrollableContainerDelegate= */ null);
 
         // Record the timestamp at which the new tab page's construction started.
         uma.trackTimeToFirstDraw(mFeedSurfaceProvider.getView(), mConstructedTimeNs);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ScrollListener.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ScrollListener.java
new file mode 100644
index 0000000..c8a10ae
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ScrollListener.java
@@ -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.
+
+package org.chromium.chrome.browser.ntp;
+
+import androidx.annotation.IntDef;
+
+/** Interface to listen to the scroll events from the scrollable container of NTP. */
+public interface ScrollListener {
+    /**
+     * Constant used to denote that a scroll was performed but scroll delta could not be
+     * calculated. This normally maps to a programmatic scroll.
+     */
+    int UNKNOWN_SCROLL_DELTA = Integer.MIN_VALUE;
+
+    /**
+     * Called when the scroll state changes.
+     */
+    void onScrollStateChanged(@ScrollState int state);
+
+    /**
+     * Called when a scroll happens and provides the amount of pixels scrolled. {@link
+     * #UNKNOWN_SCROLL_DELTA} will be specified if scroll delta would not be determined. An
+     * example of this would be a scroll initiated programmatically.
+     */
+    void onScrolled(int dx, int dy);
+
+    /**
+     * Called when the vertical offset of the header (1st item) in the scrollable container changes.
+     * This gives the new offset of the header which is inversely proportional with the scroll
+     * offset. For example, if the scroll offset is 50px, the header offset will be -50px.
+     */
+    void onHeaderOffsetChanged(int verticalOffset);
+
+    /**
+     * Possible scroll states.
+     *
+     * <p>When adding new values, the value of {@link ScrollState#NEXT_VALUE} should be used and
+     * incremented. When removing values, {@link ScrollState#NEXT_VALUE} should not be changed,
+     * and those values should not be reused.
+     */
+    @IntDef({ScrollState.IDLE, ScrollState.DRAGGING, ScrollState.SETTLING, ScrollState.NEXT_VALUE})
+    @interface ScrollState {
+        /** Is not scrolling */
+        int IDLE = 0;
+
+        /** Is currently scrolling through external means such as user input. */
+        int DRAGGING = 1;
+
+        /** Is animating to a final position. */
+        int SETTLING = 2;
+
+        /** The next value that should be used when adding additional values to the IntDef. */
+        int NEXT_VALUE = 3;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ScrollableContainerDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ScrollableContainerDelegate.java
new file mode 100644
index 0000000..0d64eb7
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ScrollableContainerDelegate.java
@@ -0,0 +1,40 @@
+// 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.
+
+package org.chromium.chrome.browser.ntp;
+
+import android.view.View;
+
+/**
+ * Delegate that represents the scrollable container that contains the different NTP views (e.g.,
+ * omnibox, feed, etc.).
+ */
+public interface ScrollableContainerDelegate {
+    /**
+     * Adds a |listener| for the scroll events from the root surface.
+     */
+    void addScrollListener(ScrollListener listener);
+
+    /**
+     * Removes a |listener| for the scroll events from the root surface.
+     */
+    void removeScrollListener(ScrollListener listener);
+
+    /**
+     * Gets the absolute value of the vertical scroll offset on the root surface.
+     */
+    int getVerticalScrollOffset();
+
+    /**
+     * Gets the height of the view of the root surface.
+     */
+    int getRootViewHeight();
+
+    /**
+     * Gets the top position of the |childView| relative to the view of the container. The 2 views
+     * need to have a hierarchical relation in the view tree. It can be an indirect relation, e.g.,
+     * the |childView| is the grand grand child of the container view down the tree.
+     */
+    int getTopPositionRelativeToContainerView(View childView);
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index d6b6db91..5a97f32 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -481,7 +481,8 @@
             tabModel.setIndex(tabIndex, TabSelectionType.FROM_OMNIBOX);
         } else {
             // Browser is in background, bring to to foreground and switch to the tab.
-            Intent newIntent = IntentHandler.createTrustedBringTabToFrontIntent(tab.getId());
+            Intent newIntent = IntentHandler.createTrustedBringTabToFrontIntent(
+                    tab.getId(), IntentHandler.BringToFrontSource.SEARCH_ACTIVITY);
             if (newIntent != null) {
                 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 IntentUtils.safeStartActivity(ContextUtils.getApplicationContext(), newIntent);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java
index 1b8a581..89bafb5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java
@@ -95,6 +95,11 @@
     }
 
     @Override
+    public boolean areIntentLaunchesAllowedInHiddenTabsForNavigation(NavigationParams params) {
+        return false;
+    }
+
+    @Override
     public Activity getActivity() {
         return mTab.getActivity();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/BottomSheetManager.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/BottomSheetManager.java
index 8dc49511..b35a158 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/BottomSheetManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/BottomSheetManager.java
@@ -17,8 +17,6 @@
 import org.chromium.chrome.browser.browser_controls.BrowserControlsVisibilityManager;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager;
-import org.chromium.chrome.browser.fullscreen.FullscreenManager;
-import org.chromium.chrome.browser.fullscreen.FullscreenOptions;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
@@ -55,9 +53,6 @@
     /** A {@link VrModeObserver} that observers events of entering and exiting VR mode. */
     private final VrModeObserver mVrModeObserver;
 
-    /** A listener for fullscreen state changes. */
-    private final FullscreenManager.Observer mFullscreenObserver;
-
     /** A listener for browser controls offset changes. */
     private final BrowserControlsVisibilityManager.Observer mBrowserControlsObserver;
 
@@ -76,9 +71,6 @@
     /** A browser controls manager for polling browser controls offsets. */
     private BrowserControlsVisibilityManager mBrowserControlsVisibilityManager;
 
-    /** A fullscreen manager for listening to fullscreen events. */
-    private FullscreenManager mFullscreenManager;
-
     /** A token for suppressing app modal dialogs. */
     private int mAppModalToken = TokenHolder.INVALID_TOKEN;
 
@@ -127,7 +119,7 @@
     public BottomSheetManager(ManagedBottomSheetController controller,
             ActivityTabProvider tabProvider,
             BrowserControlsVisibilityManager controlsVisibilityManager,
-            FullscreenManager fullscreenManager, Supplier<ModalDialogManager> dialogManager,
+            Supplier<ModalDialogManager> dialogManager,
             Supplier<SnackbarManager> snackbarManagerSupplier,
             TabObscuringHandler obscuringDelegate,
             ObservableSupplier<Boolean> omniboxFocusStateSupplier,
@@ -136,7 +128,6 @@
         mSheetController = controller;
         mTabProvider = tabProvider;
         mBrowserControlsVisibilityManager = controlsVisibilityManager;
-        mFullscreenManager = fullscreenManager;
         mDialogManager = dialogManager;
         mSnackbarManager = snackbarManagerSupplier;
         mTabObscuringHandler = obscuringDelegate;
@@ -225,35 +216,6 @@
         };
         mBrowserControlsVisibilityManager.addObserver(mBrowserControlsObserver);
 
-        mFullscreenObserver = new FullscreenManager.Observer() {
-            /** A token held while this object is suppressing the bottom sheet. */
-            private int mToken;
-
-            @Override
-            public void onEnterFullscreen(Tab tab, FullscreenOptions options) {
-                if (mOverlayPanelManager.hasValue()
-                        && mOverlayPanelManager.get().getActivePanel() != null) {
-                    // TODO(mdjones): This should only apply to contextual search, but contextual
-                    //                search is the only implementation. Fix this to only apply to
-                    //                contextual search.
-                    mOverlayPanelManager.get().getActivePanel().closePanel(
-                            OverlayPanel.StateChangeReason.UNKNOWN, true);
-                }
-
-                if (mTabProvider.get() != tab) return;
-                int previousToken = mToken;
-                mToken = controller.suppressSheet(StateChangeReason.COMPOSITED_UI);
-                controller.unsuppressSheet(previousToken);
-            }
-
-            @Override
-            public void onExitFullscreen(Tab tab) {
-                if (mTabProvider.get() != tab) return;
-                controller.unsuppressSheet(mToken);
-            }
-        };
-        mFullscreenManager.addObserver(mFullscreenObserver);
-
         mOmniboxFocusObserver = new Callback<Boolean>() {
             /** A token held while this object is suppressing the bottom sheet. */
             private int mToken;
@@ -353,6 +315,12 @@
             }
         }
 
+        if (mOverlayPanelManager.hasValue()
+                && mOverlayPanelManager.get().getActivePanel() != null) {
+            mOverlayPanelManager.get().getActivePanel().closePanel(
+                    OverlayPanel.StateChangeReason.UNKNOWN, true);
+        }
+
         BottomSheetContent content = mSheetController.getCurrentSheetContent();
         // Content with a custom scrim lifecycle should not obscure the tab. The feature
         // is responsible for adding itself to the list of obscuring views when applicable.
@@ -431,7 +399,6 @@
         if (mLastActivityTab != null) mLastActivityTab.removeObserver(mTabObserver);
         mTabProvider.removeObserver(mActivityTabObserver);
         mSheetController.removeObserver(this);
-        mFullscreenManager.removeObserver(mFullscreenObserver);
         mBrowserControlsVisibilityManager.removeObserver(mBrowserControlsObserver);
         mOmniboxFocusStateSupplier.removeObserver(mOmniboxFocusObserver);
         VrModuleProvider.unregisterVrModeObserver(mVrModeObserver);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index 2a3d079..2a5f575 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -846,10 +846,9 @@
         BottomSheetControllerFactory.attach(mActivity.getWindowAndroid(), mBottomSheetController);
 
         mBottomSheetManager = new BottomSheetManager(mBottomSheetController, mActivityTabProvider,
-                mActivity.getBrowserControlsManager(), mActivity.getFullscreenManager(),
-                mActivity::getModalDialogManager, this::getBottomSheetSnackbarManager,
-                mTabObscuringHandler, mOmniboxFocusStateSupplier, panelManagerSupplier,
-                mStartSurfaceSupplier);
+                mActivity.getBrowserControlsManager(), mActivity::getModalDialogManager,
+                this::getBottomSheetSnackbarManager, mTabObscuringHandler,
+                mOmniboxFocusStateSupplier, panelManagerSupplier, mStartSurfaceSupplier);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
index beb992f..988d699 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
@@ -17,9 +17,9 @@
 import org.chromium.base.IntentUtils;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.customtabs.BaseCustomTabActivity;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 
 /**
  * Displays a webapp in a nearly UI-less Chrome (InfoBars still appear).
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLaunchCauseMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLaunchCauseMetrics.java
index 2582ce0..059a36eb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLaunchCauseMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLaunchCauseMetrics.java
@@ -6,7 +6,7 @@
 
 import android.app.Activity;
 
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 
 /**
  * LaunchCauseMetrics for WebappActivity.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerUnitTest.java
index 00e62c00..a3bd9156 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerUnitTest.java
@@ -594,7 +594,8 @@
     @SmallTest
     public void testIgnoreUnauthenticatedBringToFront() {
         int tabId = 1;
-        Intent intent = IntentHandler.createTrustedBringTabToFrontIntent(tabId);
+        Intent intent = IntentHandler.createTrustedBringTabToFrontIntent(
+                tabId, IntentHandler.BringToFrontSource.ACTIVATE_TAB);
         assertEquals(tabId, IntentHandler.getBringTabToFrontId(intent));
 
         intent.removeExtra("trusted_application_code_extra");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/LaunchCauseMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/LaunchCauseMetricsTest.java
similarity index 99%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/metrics/LaunchCauseMetricsTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/LaunchCauseMetricsTest.java
index 49f1fb6..2953ba4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/LaunchCauseMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/LaunchCauseMetricsTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.metrics;
+package org.chromium.chrome.browser.app.metrics;
 
 import android.app.Activity;
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java
similarity index 83%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetricsTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java
index 8d8193a..0b4c79bb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.metrics;
+package org.chromium.chrome.browser.app.metrics;
 
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
@@ -24,9 +24,14 @@
 
 import org.hamcrest.Matchers;
 import org.junit.Assert;
+import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
 
 import org.chromium.android.support.PackageManagerWrapper;
 import org.chromium.base.ActivityState;
@@ -39,17 +44,24 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.JniMocker;
 import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.LauncherShortcutActivity;
+import org.chromium.chrome.browser.ServiceTabLauncher;
+import org.chromium.chrome.browser.ServiceTabLauncherJni;
 import org.chromium.chrome.browser.bookmarkswidget.BookmarkWidgetProxy;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.searchwidget.SearchActivity;
+import org.chromium.chrome.test.ChromeBrowserTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeApplicationTestUtils;
+import org.chromium.network.mojom.ReferrerPolicy;
+import org.chromium.ui.mojom.WindowOpenDisposition;
+import org.chromium.url.GURL;
 
 import java.util.Collections;
 import java.util.List;
@@ -65,6 +77,18 @@
     public final ChromeTabbedActivityTestRule mActivityTestRule =
             new ChromeTabbedActivityTestRule();
 
+    @ClassRule
+    public static final ChromeBrowserTestRule sBrowserTestRule = new ChromeBrowserTestRule();
+
+    @Rule
+    public final JniMocker mJniMocker = new JniMocker();
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+
+    @Mock
+    private ServiceTabLauncher.Natives mServiceTabLauncherJni;
+
     private static int histogramCountForValue(int value) {
         if (!LibraryLoader.getInstance().isInitialized()) return 0;
         return RecordHistogram.getHistogramValueCountForTesting(
@@ -212,4 +236,21 @@
         ApplicationTestUtils.finishActivity(searchActivity);
         ContextUtils.initApplicationContextForTests(contextToRestore);
     }
+
+    @Test
+    @MediumTest
+    public void testServiceWorkerTabLaunch() throws Throwable {
+        final int count = 1 + histogramCountForValue(LaunchCauseMetrics.LaunchCause.NOTIFICATION);
+        mJniMocker.mock(ServiceTabLauncherJni.TEST_HOOKS, mServiceTabLauncherJni);
+        mActivityTestRule.setActivity(ApplicationTestUtils.waitForActivityWithClass(
+                ChromeTabbedActivity.class, Stage.RESUMED, () -> {
+                    ServiceTabLauncher.launchTab(0, false, new GURL("about:blank"),
+                            WindowOpenDisposition.NEW_FOREGROUND_TAB, "", ReferrerPolicy.DEFAULT,
+                            "", null);
+                }));
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(histogramCountForValue(LaunchCauseMetrics.LaunchCause.NOTIFICATION),
+                    Matchers.is(count));
+        }, ScalableTimeout.scaleTimeout(5000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetricsUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsUnitTest.java
similarity index 60%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetricsUnitTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsUnitTest.java
index aa3b4f8..1807b81 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/TabbedActivityLaunchCauseMetricsUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsUnitTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.metrics;
+package org.chromium.chrome.browser.app.metrics;
 
 import android.app.Activity;
 import android.content.Intent;
@@ -29,7 +29,9 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.UiThreadTest;
 import org.chromium.base.test.util.Batch;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
 
@@ -69,6 +71,7 @@
 
     @Test
     @SmallTest
+    @UiThreadTest
     public void testOpenInBrowserMetrics() throws Throwable {
         int count =
                 histogramCountForValue(LaunchCauseMetrics.LaunchCause.OPEN_IN_BROWSER_FROM_MENU);
@@ -86,6 +89,13 @@
         Assert.assertEquals(count,
                 histogramCountForValue(LaunchCauseMetrics.LaunchCause.OPEN_IN_BROWSER_FROM_MENU));
 
+        // Resume a different ChromeActivity to make it look like we're transitioning between
+        // ChromeActivitys.
+        ChromeTabbedActivity chromeActivity = new ChromeTabbedActivity();
+        ApplicationStatus.onStateChangeForTesting(chromeActivity, ActivityState.CREATED);
+        ApplicationStatus.onStateChangeForTesting(chromeActivity, ActivityState.STARTED);
+        ApplicationStatus.onStateChangeForTesting(chromeActivity, ActivityState.RESUMED);
+
         // Ensures we record this metric even when Chrome has already recorded a launch.
         metrics.onReceivedIntent();
         metrics.recordLaunchCause();
@@ -111,6 +121,7 @@
 
     @Test
     @SmallTest
+    @UiThreadTest
     public void testVoiceSearchResultsMetrics() throws Throwable {
         int count = histogramCountForValue(
                 LaunchCauseMetrics.LaunchCause.EXTERNAL_SEARCH_ACTION_INTENT);
@@ -119,8 +130,6 @@
 
         TabbedActivityLaunchCauseMetrics metrics = new TabbedActivityLaunchCauseMetrics(mActivity);
 
-        // Tests the case where Chrome is backgrounded either by the intent picker, or by
-        // cross-channel Open In Browser.
         metrics.onReceivedIntent();
         metrics.recordLaunchCause();
         ++count;
@@ -135,4 +144,71 @@
                 histogramCountForValue(
                         LaunchCauseMetrics.LaunchCause.EXTERNAL_SEARCH_ACTION_INTENT));
     }
+
+    @Test
+    @SmallTest
+    @UiThreadTest
+    public void testBringToFrontNotification() throws Throwable {
+        int count = histogramCountForValue(LaunchCauseMetrics.LaunchCause.NOTIFICATION);
+        Intent intent = IntentHandler.createTrustedBringTabToFrontIntent(
+                1, IntentHandler.BringToFrontSource.NOTIFICATION);
+        Mockito.when(mActivity.getIntent()).thenReturn(intent);
+
+        TabbedActivityLaunchCauseMetrics metrics = new TabbedActivityLaunchCauseMetrics(mActivity);
+
+        metrics.onReceivedIntent();
+        metrics.recordLaunchCause();
+        ++count;
+        Assert.assertEquals(
+                count, histogramCountForValue(LaunchCauseMetrics.LaunchCause.NOTIFICATION));
+
+        // Resume a different ChromeActivity to make it look like we're transitioning between
+        // ChromeActivitys.
+        ChromeTabbedActivity chromeActivity = new ChromeTabbedActivity();
+        ApplicationStatus.onStateChangeForTesting(chromeActivity, ActivityState.CREATED);
+        ApplicationStatus.onStateChangeForTesting(chromeActivity, ActivityState.STARTED);
+        ApplicationStatus.onStateChangeForTesting(chromeActivity, ActivityState.RESUMED);
+
+        metrics.onReceivedIntent();
+        metrics.recordLaunchCause();
+        ++count;
+        Assert.assertEquals(
+                count, histogramCountForValue(LaunchCauseMetrics.LaunchCause.NOTIFICATION));
+    }
+
+    @Test
+    @SmallTest
+    @UiThreadTest
+    public void testBringToFrontSearch() throws Throwable {
+        int count = histogramCountForValue(LaunchCauseMetrics.LaunchCause.HOME_SCREEN_WIDGET);
+        Intent intent = IntentHandler.createTrustedBringTabToFrontIntent(
+                1, IntentHandler.BringToFrontSource.SEARCH_ACTIVITY);
+        Mockito.when(mActivity.getIntent()).thenReturn(intent);
+
+        TabbedActivityLaunchCauseMetrics metrics = new TabbedActivityLaunchCauseMetrics(mActivity);
+
+        metrics.onReceivedIntent();
+        metrics.recordLaunchCause();
+        ++count;
+        Assert.assertEquals(
+                count, histogramCountForValue(LaunchCauseMetrics.LaunchCause.HOME_SCREEN_WIDGET));
+    }
+
+    @Test
+    @SmallTest
+    @UiThreadTest
+    public void testBringToFrontActiviteTab() throws Throwable {
+        int count = histogramCountForValue(LaunchCauseMetrics.LaunchCause.NOTIFICATION);
+        Intent intent = IntentHandler.createTrustedBringTabToFrontIntent(
+                1, IntentHandler.BringToFrontSource.ACTIVATE_TAB);
+        Mockito.when(mActivity.getIntent()).thenReturn(intent);
+
+        TabbedActivityLaunchCauseMetrics metrics = new TabbedActivityLaunchCauseMetrics(mActivity);
+
+        metrics.onReceivedIntent();
+        metrics.recordLaunchCause();
+        ++count;
+        Assert.assertEquals(
+                count, histogramCountForValue(LaunchCauseMetrics.LaunchCause.NOTIFICATION));
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
index ddf26e34..2732adc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
@@ -61,13 +61,13 @@
 import org.chromium.base.test.util.UserActionTester;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkModelObserver;
 import org.chromium.chrome.browser.bookmarks.BookmarkPromoHeader.PromoState;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
 import org.chromium.chrome.browser.offlinepages.OfflinePageItem;
 import org.chromium.chrome.browser.offlinepages.OfflineTestUtil;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
index 97ce316..cb4dcf1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
@@ -42,11 +42,11 @@
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.cc.input.BrowserControlsState;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabBrowserControlsConstraintsHelper;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java
index a7c49173..7d852b5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java
@@ -15,6 +15,7 @@
 
 import static org.chromium.chrome.browser.customtabs.CustomTabsTestUtils.addActionButtonToIntent;
 import static org.chromium.chrome.browser.customtabs.CustomTabsTestUtils.createTestBitmap;
+import static org.chromium.chrome.browser.customtabs.IncognitoCustomTabIntentDataProvider.EXTRA_FORCE_ENABLE_FOR_EXPERIMENT;
 
 import android.annotation.TargetApi;
 import android.app.NotificationManager;
@@ -164,6 +165,15 @@
         return mCustomTabActivityTestRule.getActivity();
     }
 
+    private void assertProfileUsedIsNonPrimary() throws TimeoutException {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Profile profile = Profile.fromWebContents(
+                    mCustomTabActivityTestRule.getActivity().getCurrentWebContents());
+            assertTrue(profile.isOffTheRecord());
+            assertFalse(profile.isPrimaryOTRProfile());
+        });
+    }
+
     @Test
     @MediumTest
     @Features.EnableFeatures({ChromeFeatureList.CCT_INCOGNITO})
@@ -171,6 +181,7 @@
         Intent intent = createMinimalIncognitoCustomTabIntent();
         CustomTabActivity activity = launchIncognitoCustomTab(intent);
         assertTrue(activity.getActivityTab().isIncognito());
+        assertProfileUsedIsNonPrimary();
     }
 
     @Test
@@ -245,10 +256,10 @@
     @Features.DisableFeatures({ChromeFeatureList.CCT_INCOGNITO})
     public void canLaunchFirstPartyIncognitoWithExtraWhenDisabled() throws Exception {
         Intent intent = createMinimalIncognitoCustomTabIntent();
-        intent.putExtra(
-                IncognitoCustomTabIntentDataProvider.EXTRA_FORCE_ENABLE_FOR_EXPERIMENT, true);
+        intent.putExtra(EXTRA_FORCE_ENABLE_FOR_EXPERIMENT, true);
         CustomTabActivity activity = launchIncognitoCustomTab(intent);
         assertTrue(activity.getActivityTab().isIncognito());
+        assertProfileUsedIsNonPrimary();
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index 8715393..4f53486 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -90,6 +90,7 @@
 import org.chromium.chrome.browser.TabsOpenedFromExternalAppTest;
 import org.chromium.chrome.browser.WarmupManager;
 import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider.CustomTabsUiType;
 import org.chromium.chrome.browser.browserservices.SessionDataHolder;
@@ -106,7 +107,6 @@
 import org.chromium.chrome.browser.history.HistoryItem;
 import org.chromium.chrome.browser.history.TestBrowsingHistoryObserver;
 import org.chromium.chrome.browser.infobar.InfoBarContainer;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.metrics.PageLoadMetrics;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabLaunchCauseMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabLaunchCauseMetricsTest.java
index bec616d..22f368a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabLaunchCauseMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabLaunchCauseMetricsTest.java
@@ -21,8 +21,8 @@
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.UiThreadTest;
 import org.chromium.base.test.util.Batch;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.flags.ActivityType;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
 
 /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupportTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupportTest.java
index c3ba417..9c7e48b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupportTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupportTest.java
@@ -46,6 +46,7 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
@@ -462,6 +463,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "Flaky test - see: https://crbug.com/1171147")
     public void testAcceptTosWithoutCrashUpload() throws Exception {
         setAppRestrictionsMockInitialized(true);
         setEnterpriseInfoInitializedWithDeviceOwner(true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java
index 32f93959..08ce526 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java
@@ -31,6 +31,7 @@
 
 import org.chromium.base.ActivityState;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
@@ -39,6 +40,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity2;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.omnibox.LocationBarLayout;
@@ -50,6 +52,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.util.ChromeApplicationTestUtils;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.MenuUtils;
 import org.chromium.chrome.test.util.OmniboxTestUtils;
@@ -255,8 +258,7 @@
     @Test
     @MediumTest
     @EnableFeatures("OmniboxTabSwitchSuggestions")
-    public void
-    testSwitchToTabSuggestion() throws InterruptedException {
+    public void testSwitchToTabSuggestion() throws InterruptedException {
         mTestServer = EmbeddedTestServer.createAndStartHTTPSServer(
                 InstrumentationRegistry.getInstrumentation().getContext(),
                 ServerCertificate.CERT_OK);
@@ -363,6 +365,10 @@
         mActivityTestRule.loadUrlInNewTab(testHttpsUrl3);
         Assert.assertNotEquals(mActivityTestRule.getActivity().getActivityTab(), aboutTab);
 
+        // Send Chrome to the background so Launch Cause Metrics are gathered (and this is more
+        // realistic).
+        ChromeApplicationTestUtils.fireHomeScreenIntent(mActivityTestRule.getActivity());
+
         final SearchActivity searchActivity = startSearchActivity();
         CriteriaHelper.pollUiThread(() -> {
             Tab tab = mActivityTestRule.getActivity().getActivityTab();
@@ -386,6 +392,10 @@
             Criteria.checkThat(
                     tab.getWindowAndroid().getActivityState(), Matchers.is(ActivityState.RESUMED));
             Assert.assertEquals(tab, aboutTab);
+            Criteria.checkThat(RecordHistogram.getHistogramValueCountForTesting(
+                                       LaunchCauseMetrics.LAUNCH_CAUSE_HISTOGRAM,
+                                       LaunchCauseMetrics.LaunchCause.HOME_SCREEN_WIDGET),
+                    Matchers.is(1));
         }, SEARCH_ACTIVITY_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
index feae7ac..fc65944 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -48,6 +48,7 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.FileProviderHelper;
 import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -55,7 +56,6 @@
 import org.chromium.chrome.browser.locale.DefaultSearchEnginePromoDialog;
 import org.chromium.chrome.browser.locale.DefaultSearchEnginePromoDialog.DefaultSearchEnginePromoDialogObserver;
 import org.chromium.chrome.browser.locale.LocaleManager;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.omnibox.LocationBarCoordinator;
 import org.chromium.chrome.browser.omnibox.OmniboxSuggestionType;
 import org.chromium.chrome.browser.omnibox.UrlBar;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java
index 025e6e2..a85412db 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java
@@ -34,6 +34,7 @@
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.browser.accessibility_tab_switcher.OverviewListLayout;
 import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.app.tabmodel.AsyncTabParamsManagerSingleton;
 import org.chromium.chrome.browser.app.tabmodel.ChromeTabModelFilterFactory;
 import org.chromium.chrome.browser.app.tabmodel.TabModelOrchestrator;
@@ -44,7 +45,6 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
-import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.profiles.Profile;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappModeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappModeTest.java
index dd68bf53..20f46aa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappModeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappModeTest.java
@@ -199,7 +199,8 @@
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
         // Bring the WebappActivity back via an Intent.
-        Intent intent = IntentHandler.createTrustedBringTabToFrontIntent(webappTabId);
+        Intent intent = IntentHandler.createTrustedBringTabToFrontIntent(
+                webappTabId, IntentHandler.BringToFrontSource.NOTIFICATION);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         context.startActivity(intent);
 
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 39160d2..d1bc2b48 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -636,7 +636,7 @@
     Select your keyboard:
   </message>
   <message name="IDS_HID_DETECTION_INVITATION_TEXT" desc="Text shown on top of HID detection screen inviting user to connect an input device.">
-    Searching for Bluetooth and USB devices...
+    Connect to devices
   </message>
   <message name="IDS_HID_DETECTION_PRECONDITION_TEXT" desc="Text shown on HID detection screen beneath invitation.">
     Please connect a mouse or a keyboard. If you are using a Bluetooth device, make sure it is ready to pair.
@@ -656,6 +656,9 @@
   <message name="IDS_HID_DETECTION_CONNECTED_USB_KEYBOARD" desc="Text shown on HID detection screen next to keyboard icon after USB keyboard is connected.">
     USB keyboard connected
   </message>
+  <message name="IDS_HID_DETECTION_DETECTED_TOUCHSCREEN" desc="Text shown on HID detection screen next to touchscreen icon if a touch screen was detected.">
+    Touch screen detected
+  </message>
   <message name="IDS_HID_DETECTION_PAIRED_BLUETOOTH_MOUSE" desc="Text shown on HID detection screen next to mouse icon after Bluetooth mouse is connected.">
     Bluetooth mouse paired
   </message>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_HID_DETECTION_DETECTED_TOUCHSCREEN.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_HID_DETECTION_DETECTED_TOUCHSCREEN.png.sha1
new file mode 100644
index 0000000..ebbc99c
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_HID_DETECTION_DETECTED_TOUCHSCREEN.png.sha1
@@ -0,0 +1 @@
+152c2491a54929f4b839dd3d19ba47d3c92b9060
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_HID_DETECTION_INVITATION_TEXT.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_HID_DETECTION_INVITATION_TEXT.png.sha1
new file mode 100644
index 0000000..ebbc99c
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_HID_DETECTION_INVITATION_TEXT.png.sha1
@@ -0,0 +1 @@
+152c2491a54929f4b839dd3d19ba47d3c92b9060
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index a0a51cd..10df4e46 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -611,6 +611,8 @@
     "infobars/infobar_responder.h",
     "infobars/infobar_service.cc",
     "infobars/infobar_service.h",
+    "installable/digital_asset_links/digital_asset_links_handler.cc",
+    "installable/digital_asset_links/digital_asset_links_handler.h",
     "installable/installable_utils.cc",
     "installable/installable_utils.h",
     "internal_auth.cc",
@@ -668,6 +670,8 @@
     "login_detection/oauth_login_detector.h",
     "login_detection/password_store_sites.cc",
     "login_detection/password_store_sites.h",
+    "lookalikes/digital_asset_links_cross_validator.cc",
+    "lookalikes/digital_asset_links_cross_validator.h",
     "lookalikes/lookalike_url_blocking_page.cc",
     "lookalikes/lookalike_url_blocking_page.h",
     "lookalikes/lookalike_url_controller_client.cc",
@@ -3572,6 +3576,8 @@
       "enterprise/connectors/file_system/box_api_call_flow.h",
       "enterprise/connectors/file_system/download_controller.cc",
       "enterprise/connectors/file_system/download_controller.h",
+      "enterprise/connectors/file_system/rename_handler.cc",
+      "enterprise/connectors/file_system/rename_handler.h",
       "enterprise/connectors/file_system/service_settings.cc",
       "enterprise/connectors/file_system/service_settings.h",
       "enterprise/connectors/file_system/signin_dialog_delegate.cc",
@@ -4267,8 +4273,6 @@
     sources += [
       "apps/app_service/app_notifications.cc",
       "apps/app_service/app_notifications.h",
-      "apps/app_service/app_web_contents_data.cc",
-      "apps/app_service/app_web_contents_data.h",
       "apps/app_service/arc_activity_adaptive_icon_impl.cc",
       "apps/app_service/arc_activity_adaptive_icon_impl.h",
       "apps/app_service/arc_apps.cc",
@@ -5328,8 +5332,6 @@
     ]
   } else {  # is_android || is_chromeos_ash
     sources += [
-      "installable/digital_asset_links/digital_asset_links_handler.cc",
-      "installable/digital_asset_links/digital_asset_links_handler.h",
       "media/protected_media_identifier_permission_context.cc",
       "media/protected_media_identifier_permission_context.h",
     ]
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 32f588f5..4210fd7 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4899,11 +4899,6 @@
      flag_descriptions::kTabEngagementReportingDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kTabEngagementReportingAndroid)},
 
-    {"enable-duet-tabstrip-integration",
-     flag_descriptions::kDuetTabStripIntegrationAndroidName,
-     flag_descriptions::kDuetTabStripIntegrationAndroidDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kDuetTabStripIntegrationAndroid)},
-
     {"enable-conditional-tabstrip",
      flag_descriptions::kConditionalTabStripAndroidName,
      flag_descriptions::kConditionalTabStripAndroidDescription, kOsAndroid,
diff --git a/chrome/browser/apps/app_service/app_service_proxy.cc b/chrome/browser/apps/app_service/app_service_proxy.cc
index dc19a74..192f84ab 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy.cc
@@ -220,10 +220,6 @@
   return app_registry_cache_;
 }
 
-apps::AppCapabilityAccessCache& AppServiceProxy::AppCapabilityAccessCache() {
-  return app_capability_access_cache_;
-}
-
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 apps::InstanceRegistry& AppServiceProxy::InstanceRegistry() {
   return instance_registry_;
diff --git a/chrome/browser/apps/app_service/app_service_proxy.h b/chrome/browser/apps/app_service/app_service_proxy.h
index ba24d44..3e3f6ece 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.h
+++ b/chrome/browser/apps/app_service/app_service_proxy.h
@@ -93,7 +93,6 @@
 
   mojo::Remote<apps::mojom::AppService>& AppService();
   apps::AppRegistryCache& AppRegistryCache();
-  apps::AppCapabilityAccessCache& AppCapabilityAccessCache();
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   apps::InstanceRegistry& InstanceRegistry();
diff --git a/chrome/browser/apps/app_service/app_web_contents_data.cc b/chrome/browser/apps/app_service/app_web_contents_data.cc
deleted file mode 100644
index dde7b44..0000000
--- a/chrome/browser/apps/app_service/app_web_contents_data.cc
+++ /dev/null
@@ -1,25 +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 "chrome/browser/apps/app_service/app_web_contents_data.h"
-
-namespace apps {
-
-AppWebContentsData::AppWebContentsData(content::WebContents* web_contents,
-                                       Client* client)
-    : content::WebContentsObserver(web_contents), client_(client) {
-  DCHECK(web_contents);
-  DCHECK(client);
-}
-
-void AppWebContentsData::WebContentsDestroyed() {
-  if (client_) {
-    client_->OnWebContentsDestroyed(web_contents());
-  }
-  client_ = nullptr;
-}
-
-WEB_CONTENTS_USER_DATA_KEY_IMPL(AppWebContentsData)
-
-}  // namespace apps
diff --git a/chrome/browser/apps/app_service/app_web_contents_data.h b/chrome/browser/apps/app_service/app_web_contents_data.h
deleted file mode 100644
index d7805fb..0000000
--- a/chrome/browser/apps/app_service/app_web_contents_data.h
+++ /dev/null
@@ -1,42 +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 CHROME_BROWSER_APPS_APP_SERVICE_APP_WEB_CONTENTS_DATA_H_
-#define CHROME_BROWSER_APPS_APP_SERVICE_APP_WEB_CONTENTS_DATA_H_
-
-#include "content/public/browser/web_contents_observer.h"
-#include "content/public/browser/web_contents_user_data.h"
-
-namespace apps {
-
-// AppWebContentsData is attached to the lifetime of a WebContents, and notifies
-// Observer when the WebContents is destroyed.
-class AppWebContentsData
-    : public content::WebContentsUserData<AppWebContentsData>,
-      public content::WebContentsObserver {
- public:
-  class Client {
-   public:
-    // Invoked when the WebContents is being destroyed.
-    virtual void OnWebContentsDestroyed(content::WebContents* contents) = 0;
-  };
-
-  explicit AppWebContentsData(content::WebContents* contents, Client* client);
-  AppWebContentsData(const AppWebContentsData&) = delete;
-  AppWebContentsData& operator=(const AppWebContentsData&) = delete;
-  ~AppWebContentsData() override = default;
-
- private:
-  friend class content::WebContentsUserData<AppWebContentsData>;
-  WEB_CONTENTS_USER_DATA_KEY_DECL();
-
-  // content::WebContentsObserver:
-  void WebContentsDestroyed() override;
-
-  Client* client_;
-};
-
-}  // namespace apps
-
-#endif  // CHROME_BROWSER_APPS_APP_SERVICE_APP_WEB_CONTENTS_DATA_H_
diff --git a/chrome/browser/apps/app_service/extension_apps_chromeos.cc b/chrome/browser/apps/app_service/extension_apps_chromeos.cc
index 63a7e642..786f139 100644
--- a/chrome/browser/apps/app_service/extension_apps_chromeos.cc
+++ b/chrome/browser/apps/app_service/extension_apps_chromeos.cc
@@ -426,37 +426,12 @@
     app_id = extension->id();
   }
 
-  if (media_requests_.IsNewRequest(app_id, web_contents, state)) {
-    content::WebContentsUserData<AppWebContentsData>::CreateForWebContents(
-        web_contents, this);
-  }
-
   auto result =
       media_requests_.UpdateRequests(app_id, web_contents, stream_type, state);
   ModifyCapabilityAccess(subscribers(), app_id, result.camera,
                          result.microphone);
 }
 
-void ExtensionAppsChromeOs::OnWebContentsDestroyed(
-    content::WebContents* web_contents) {
-  DCHECK(web_contents);
-
-  std::string app_id = extension_misc::kChromeAppId;
-  extensions::ExtensionRegistry* registry =
-      extensions::ExtensionRegistry::Get(profile());
-  DCHECK(registry);
-  const extensions::ExtensionSet& extensions = registry->enabled_extensions();
-  const extensions::Extension* extension =
-      extensions.GetAppByURL(web_contents->GetLastCommittedURL());
-  if (extension && Accepts(extension)) {
-    app_id = extension->id();
-  }
-
-  auto result = media_requests_.OnWebContentsDestroyed(app_id, web_contents);
-  ModifyCapabilityAccess(subscribers(), app_id, result.camera,
-                         result.microphone);
-}
-
 void ExtensionAppsChromeOs::OnNotificationDisplayed(
     const message_center::Notification& notification,
     const NotificationCommon::Metadata* const metadata) {
diff --git a/chrome/browser/apps/app_service/extension_apps_chromeos.h b/chrome/browser/apps/app_service/extension_apps_chromeos.h
index f4d4fdc..c8e63e9 100644
--- a/chrome/browser/apps/app_service/extension_apps_chromeos.h
+++ b/chrome/browser/apps/app_service/extension_apps_chromeos.h
@@ -13,7 +13,6 @@
 #include "base/scoped_observation.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/apps/app_service/app_notifications.h"
-#include "chrome/browser/apps/app_service/app_web_contents_data.h"
 #include "chrome/browser/apps/app_service/extension_apps_base.h"
 #include "chrome/browser/apps/app_service/icon_key_util.h"
 #include "chrome/browser/apps/app_service/media_requests.h"
@@ -54,8 +53,7 @@
                               public extensions::AppWindowRegistry::Observer,
                               public ArcAppListPrefs::Observer,
                               public NotificationDisplayService::Observer,
-                              public MediaCaptureDevicesDispatcher::Observer,
-                              public AppWebContentsData::Client {
+                              public MediaCaptureDevicesDispatcher::Observer {
  public:
   ExtensionAppsChromeOs(
       const mojo::Remote<apps::mojom::AppService>& app_service,
@@ -116,9 +114,6 @@
                        blink::mojom::MediaStreamType stream_type,
                        const content::MediaRequestState state) override;
 
-  // AppWebContentsData::Observer:
-  void OnWebContentsDestroyed(content::WebContents* contents) override;
-
   // NotificationDisplayService::Observer overrides.
   void OnNotificationDisplayed(
       const message_center::Notification& notification,
diff --git a/chrome/browser/apps/app_service/media_access_browsertest.cc b/chrome/browser/apps/app_service/media_access_browsertest.cc
deleted file mode 100644
index 159cca90..0000000
--- a/chrome/browser/apps/app_service/media_access_browsertest.cc
+++ /dev/null
@@ -1,671 +0,0 @@
-// Copyright (c) 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 <memory>
-
-#include "base/run_loop.h"
-#include "base/scoped_observation.h"
-#include "base/test/thread_test_helper.h"
-#include "chrome/browser/apps/app_service/app_service_proxy.h"
-#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
-#include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
-#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser_list.h"
-#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
-#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
-#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/test/browser_test.h"
-#include "extensions/common/extension.h"
-
-using extensions::Extension;
-
-namespace {
-
-bool AccessingCamera(Profile* profile, const std::string& app_id) {
-  auto accessing_camera = apps::mojom::OptionalBool::kUnknown;
-  apps::AppServiceProxy* proxy =
-      apps::AppServiceProxyFactory::GetForProfile(profile);
-  proxy->FlushMojoCallsForTesting();
-  proxy->AppCapabilityAccessCache().ForOneApp(
-      app_id, [&accessing_camera](const apps::CapabilityAccessUpdate& update) {
-        accessing_camera = update.Camera();
-      });
-  return accessing_camera == apps::mojom::OptionalBool::kTrue;
-}
-
-bool AccessingMicrophone(Profile* profile, const std::string& app_id) {
-  auto accessing_microphone = apps::mojom::OptionalBool::kUnknown;
-  apps::AppServiceProxy* proxy =
-      apps::AppServiceProxyFactory::GetForProfile(profile);
-  proxy->FlushMojoCallsForTesting();
-  proxy->AppCapabilityAccessCache().ForOneApp(
-      app_id,
-      [&accessing_microphone](const apps::CapabilityAccessUpdate& update) {
-        accessing_microphone = update.Microphone();
-      });
-  return accessing_microphone == apps::mojom::OptionalBool::kTrue;
-}
-
-class FakeMediaObserver : public MediaCaptureDevicesDispatcher::Observer {
- public:
-  explicit FakeMediaObserver(base::OnceClosure done_closure)
-      : done_closure_(std::move(done_closure)) {
-    media_dispatcher_.Observe(MediaCaptureDevicesDispatcher::GetInstance());
-  }
-  ~FakeMediaObserver() override = default;
-
-  // MediaCaptureDevicesDispatcher::Observer:
-  void OnRequestUpdate(int render_process_id,
-                       int render_frame_id,
-                       blink::mojom::MediaStreamType stream_type,
-                       const content::MediaRequestState state) override {
-    if (!done_closure_.is_null())
-      std::move(done_closure_).Run();
-    content::RunAllTasksUntilIdle();
-  }
-
- private:
-  base::OnceClosure done_closure_;
-
-  base::ScopedObservation<MediaCaptureDevicesDispatcher,
-                          MediaCaptureDevicesDispatcher::Observer>
-      media_dispatcher_{this};
-};
-
-void MediaRequestChange(int render_process_id,
-                        int render_frame_id,
-                        const GURL& url,
-                        blink::mojom::MediaStreamType stream_type,
-                        content::MediaRequestState state) {
-  if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)) {
-    base::RunLoop run_loop;
-    FakeMediaObserver fake_observer(run_loop.QuitClosure());
-
-    content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE, base::BindOnce(&MediaRequestChange, render_process_id,
-                                  render_frame_id, url, stream_type, state));
-    run_loop.Run();
-    content::RunAllTasksUntilIdle();
-    return;
-  }
-
-  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-  MediaCaptureDevicesDispatcher::GetInstance()->OnMediaRequestStateChanged(
-      render_process_id, render_frame_id, 0, url, stream_type, state);
-}
-
-void MediaRequestChangeForWebContent(content::WebContents* web_content,
-                                     const GURL& url,
-                                     blink::mojom::MediaStreamType stream_type,
-                                     content::MediaRequestState state) {
-  ASSERT_TRUE(web_content);
-  MediaRequestChange(web_content->GetMainFrame()->GetProcess()->GetID(),
-                     web_content->GetMainFrame()->GetRoutingID(), url,
-                     stream_type, state);
-}
-
-}  // namespace
-
-class MediaAccessExtensionAppsTest : public extensions::PlatformAppBrowserTest {
- public:
-  void SetUpOnMainThread() override {
-    extensions::PlatformAppBrowserTest::SetUpOnMainThread();
-
-    ASSERT_TRUE(embedded_test_server()->Start());
-  }
-
-  void UninstallApp(const std::string& app_id) {
-    apps::AppServiceProxy* proxy =
-        apps::AppServiceProxyFactory::GetForProfile(profile());
-    proxy->UninstallSilently(app_id, apps::mojom::UninstallSource::kUser);
-    proxy->FlushMojoCallsForTesting();
-  }
-
-  GURL GetUrl1() {
-    return embedded_test_server()->GetURL("app.com", "/ssl/google.html");
-  }
-
-  GURL GetUrl2() {
-    return embedded_test_server()->GetURL("app.com", "/google/google.html");
-  }
-
-  content::WebContents* GetWebContents() {
-    return browser()->tab_strip_model()->GetActiveWebContents();
-  }
-
-  base::WeakPtr<MediaAccessExtensionAppsTest> GetWeakPtr() {
-    return weak_ptr_factory_.GetWeakPtr();
-  }
-
- private:
-  base::WeakPtrFactory<MediaAccessExtensionAppsTest> weak_ptr_factory_{this};
-};
-
-IN_PROC_BROWSER_TEST_F(MediaAccessExtensionAppsTest,
-                       RequestAccessingForChromeInTabs) {
-  ui_test_utils::NavigateToURL(browser(), GetUrl1());
-
-  content::WebContents* web_content1 = GetWebContents();
-  // Request accessing the camera for |web_content1|.
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_FALSE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-
-  AddBlankTabAndShow(browser());
-  ui_test_utils::NavigateToURL(browser(), GetUrl2());
-  content::WebContents* web_content2 = GetWebContents();
-  // Request accessing the microphone for |web_content2|.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_TRUE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-
-  // Stop accessing the camera for |web_content1|.
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_CLOSING);
-  EXPECT_FALSE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_TRUE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-
-  // Stop accessing the microphone for |web_content2|.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_CLOSING);
-  EXPECT_FALSE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_FALSE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-}
-
-IN_PROC_BROWSER_TEST_F(MediaAccessExtensionAppsTest,
-                       RequestAccessingForChromeInNewBrowsers) {
-  Browser* browser1 =
-      Browser::Create(Browser::CreateParams(browser()->profile(), true));
-  ASSERT_TRUE(browser1);
-  ASSERT_NE(browser(), browser1);
-
-  AddBlankTabAndShow(browser1);
-  ui_test_utils::NavigateToURL(browser1, GetUrl1());
-  content::WebContents* web_content1 =
-      browser1->tab_strip_model()->GetActiveWebContents();
-  int render_process_id1 = web_content1->GetMainFrame()->GetProcess()->GetID();
-  int render_frame_id1 = web_content1->GetMainFrame()->GetRoutingID();
-  // Request accessing the camera and the microphone for |web_content1|.
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_TRUE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-
-  AddBlankTabAndShow(browser1);
-  ui_test_utils::NavigateToURL(browser1, GetUrl2());
-  content::WebContents* web_content2 =
-      browser1->tab_strip_model()->GetActiveWebContents();
-  int render_process_id2 = web_content2->GetMainFrame()->GetProcess()->GetID();
-  int render_frame_id2 = web_content2->GetMainFrame()->GetRoutingID();
-  // Request accessing the camera and the microphone for |web_content2|.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl2(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl2(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_TRUE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-
-  // Close the tab for |web_content2|.
-  browser1->tab_strip_model()->CloseSelectedTabs();
-  EXPECT_TRUE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_TRUE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-
-  MediaRequestChange(render_process_id2, render_frame_id2, GetUrl2(),
-                     blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-                     content::MEDIA_REQUEST_STATE_CLOSING);
-  MediaRequestChange(render_process_id2, render_frame_id2, GetUrl2(),
-                     blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-                     content::MEDIA_REQUEST_STATE_CLOSING);
-  EXPECT_TRUE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_TRUE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-
-  // Close the tab for |web_content1|.
-  browser1->tab_strip_model()->CloseSelectedTabs();
-  EXPECT_FALSE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_FALSE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-
-  MediaRequestChange(render_process_id1, render_frame_id1, GetUrl1(),
-                     blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-                     content::MEDIA_REQUEST_STATE_CLOSING);
-  MediaRequestChange(render_process_id1, render_frame_id1, GetUrl1(),
-                     blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-                     content::MEDIA_REQUEST_STATE_CLOSING);
-  EXPECT_FALSE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_FALSE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-}
-
-IN_PROC_BROWSER_TEST_F(MediaAccessExtensionAppsTest,
-                       RequestAccessingForPlatformApp) {
-  const Extension* extension =
-      LoadAndLaunchPlatformApp("context_menu", "Launched");
-  ASSERT_TRUE(extension);
-
-  content::WebContents* web_contents = GetFirstAppWindowWebContents();
-  ASSERT_TRUE(web_contents);
-
-  // Request accessing the camera for |web_contents|.
-  MediaRequestChangeForWebContent(
-      web_contents, web_contents->GetURL(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), extension->id()));
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), extension->id()));
-
-  // Request accessing the microphone for |web_contents|.
-  MediaRequestChangeForWebContent(
-      web_contents, web_contents->GetURL(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), extension->id()));
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), extension->id()));
-
-  // Stop accessing the microphone for |web_contents|.
-  MediaRequestChangeForWebContent(
-      web_contents, web_contents->GetURL(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_CLOSING);
-
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), extension->id()));
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), extension->id()));
-
-  // Stop accessing the camera for |web_contents|.
-  MediaRequestChangeForWebContent(
-      web_contents, web_contents->GetURL(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_CLOSING);
-
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), extension->id()));
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), extension->id()));
-}
-
-IN_PROC_BROWSER_TEST_F(MediaAccessExtensionAppsTest,
-                       RequestAccessingForHostApp) {
-  const Extension* extension =
-      LoadExtension(test_data_dir_.AppendASCII("app1"));
-  ASSERT_TRUE(extension);
-
-  // Navigate to the app's launch URL.
-  auto url = extensions::AppLaunchInfo::GetLaunchWebURL(extension);
-  ui_test_utils::NavigateToURL(browser(), url);
-
-  content::WebContents* web_content1 =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  ASSERT_TRUE(web_content1);
-
-  // Request accessing the camera and microphone for |web_contents|.
-  MediaRequestChangeForWebContent(
-      web_content1, url, blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  MediaRequestChangeForWebContent(
-      web_content1, url, blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), extension->id()));
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), extension->id()));
-
-  AddBlankTabAndShow(browser());
-  ui_test_utils::NavigateToURL(browser(), GetUrl1());
-  content::WebContents* web_content2 = GetWebContents();
-
-  // Request accessing the camera for |web_content2|.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), extension->id()));
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), extension->id()));
-  EXPECT_TRUE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_FALSE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-
-  // Uninstall the app.
-  std::string app_id = extension->id();
-  UninstallApp(app_id);
-
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id));
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), app_id));
-  EXPECT_TRUE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_FALSE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-
-  // Request accessing the camera for |web_content2|.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_CLOSING);
-
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id));
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), app_id));
-  EXPECT_FALSE(
-      AccessingCamera(browser()->profile(), extension_misc::kChromeAppId));
-  EXPECT_FALSE(
-      AccessingMicrophone(browser()->profile(), extension_misc::kChromeAppId));
-}
-
-class MediaAccessWebAppsTest : public web_app::WebAppControllerBrowserTest {
- public:
-  MediaAccessWebAppsTest() = default;
-  ~MediaAccessWebAppsTest() override = default;
-
-  std::string CreateWebApp(const GURL& url) const {
-    auto web_app_info = std::make_unique<WebApplicationInfo>();
-    web_app_info->start_url = url;
-    web_app_info->scope = url;
-    return web_app::InstallWebApp(browser()->profile(),
-                                  std::move(web_app_info));
-  }
-
-  void UninstallWebApp(const std::string& app_id) const {
-    web_app::UninstallWebApp(browser()->profile(), app_id);
-    apps::AppServiceProxyFactory::GetForProfile(browser()->profile())
-        ->FlushMojoCallsForTesting();
-  }
-
-  GURL GetUrl1() {
-    return https_server()->GetURL("app.com", "/ssl/google.html");
-  }
-
-  GURL GetUrl2() {
-    return https_server()->GetURL("app.com", "/google/google.html");
-  }
-
-  content::WebContents* GetWebContents() {
-    return browser()->tab_strip_model()->GetActiveWebContents();
-  }
-
-  base::WeakPtr<MediaAccessWebAppsTest> GetWeakPtr() {
-    return weak_ptr_factory_.GetWeakPtr();
-  }
-
- private:
-  base::WeakPtrFactory<MediaAccessWebAppsTest> weak_ptr_factory_{this};
-};
-
-IN_PROC_BROWSER_TEST_F(MediaAccessWebAppsTest, RequestAccessingCamera) {
-  std::string app_id = CreateWebApp(GetUrl1());
-
-  // Launch |app_id| in a new tab.
-  web_app::LaunchWebAppBrowser(browser()->profile(), app_id);
-  web_app::NavigateToURLAndWait(browser(), GetUrl1());
-
-  // Request accessing the camera for |app_id| in the new tab.
-  content::WebContents* web_content1 = GetWebContents();
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), app_id));
-
-  // Launch |app_id| in a new window.
-  content::WebContents* web_content2 = OpenApplication(app_id);
-  Browser* app_browser = BrowserList::GetInstance()->GetLastActive();
-  ASSERT_TRUE(app_browser);
-  ASSERT_NE(browser(), app_browser);
-
-  // Request accessing the camera for |app_id| in the new window.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), app_id));
-
-  // Stop accessing the camera for |app_id| in the tab.
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_CLOSING);
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), app_id));
-
-  // Stop accessing the camera for |app_id| in the window.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_CLOSING);
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id));
-
-  web_app::CloseAndWait(app_browser);
-  web_app::CloseAndWait(browser());
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id));
-}
-
-IN_PROC_BROWSER_TEST_F(MediaAccessWebAppsTest, RequestAccessingMicrophone) {
-  std::string app_id = CreateWebApp(GetUrl1());
-
-  // Launch |app_id| in a new tab.
-  web_app::LaunchWebAppBrowser(browser()->profile(), app_id);
-  web_app::NavigateToURLAndWait(browser(), GetUrl1());
-
-  // Request accessing the camera for |app_id| in the new tab.
-  content::WebContents* web_content1 = GetWebContents();
-  int render_process_id1 = web_content1->GetMainFrame()->GetProcess()->GetID();
-  int render_frame_id1 = web_content1->GetMainFrame()->GetRoutingID();
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), app_id));
-
-  // Launch |app_id| in a new window.
-  content::WebContents* web_content2 = OpenApplication(app_id);
-  int render_process_id2 = web_content2->GetMainFrame()->GetProcess()->GetID();
-  int render_frame_id2 = web_content2->GetMainFrame()->GetRoutingID();
-  Browser* app_browser = BrowserList::GetInstance()->GetLastActive();
-  ASSERT_TRUE(app_browser);
-  ASSERT_NE(browser(), app_browser);
-
-  // Request accessing the camera for |app_id| in the new window.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), app_id));
-
-  // Close browsers.
-  web_app::CloseAndWait(app_browser);
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), app_id));
-
-  // Stop accessing the camera for |app_id| in the tab.
-  MediaRequestChange(render_process_id1, render_frame_id1, GetUrl1(),
-                     blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-                     content::MEDIA_REQUEST_STATE_CLOSING);
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), app_id));
-
-  // Stop accessing the camera for |app_id| in the window.
-  MediaRequestChange(render_process_id2, render_frame_id2, GetUrl1(),
-                     blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-                     content::MEDIA_REQUEST_STATE_CLOSING);
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), app_id));
-}
-
-IN_PROC_BROWSER_TEST_F(MediaAccessWebAppsTest, RemoveApp) {
-  std::string app_id = CreateWebApp(GetUrl1());
-
-  // Launch |app_id| in a new tab.
-  web_app::LaunchWebAppBrowser(browser()->profile(), app_id);
-  web_app::NavigateToURLAndWait(browser(), GetUrl1());
-
-  // Request accessing the camera and the microphone for |app_id| in the new
-  // tab.
-  content::WebContents* web_content1 = GetWebContents();
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), app_id));
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), app_id));
-
-  // Launch |app_id| in a new window.
-  content::WebContents* web_content2 = OpenApplication(app_id);
-  Browser* app_browser = BrowserList::GetInstance()->GetLastActive();
-  ASSERT_TRUE(app_browser);
-  ASSERT_NE(browser(), app_browser);
-
-  // Request accessing the camera and the microphone for |app_id| in the new
-  // window.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), app_id));
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), app_id));
-
-  UninstallWebApp(app_id);
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id));
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), app_id));
-
-  CreateWebApp(GetUrl1());
-
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id));
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), app_id));
-}
-
-IN_PROC_BROWSER_TEST_F(MediaAccessWebAppsTest, TwoApps) {
-  std::string app_id1 = CreateWebApp(GetUrl1());
-
-  // Launch |app_id1| in a new tab.
-  web_app::LaunchWebAppBrowser(browser()->profile(), app_id1);
-  web_app::NavigateToURLAndWait(browser(), GetUrl1());
-
-  // Request accessing the camera and the microphone for |app_id1| in the new
-  // tab.
-  content::WebContents* web_content1 = GetWebContents();
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  MediaRequestChangeForWebContent(
-      web_content1, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), app_id1));
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), app_id1));
-
-  std::string app_id2 = CreateWebApp(GetUrl2());
-
-  // Launch |app_id2| in a new window.
-  content::WebContents* web_content2 = OpenApplication(app_id2);
-  Browser* app_browser = BrowserList::GetInstance()->GetLastActive();
-  ASSERT_TRUE(app_browser);
-  ASSERT_NE(browser(), app_browser);
-
-  // Request accessing the camera and the microphone for |app_id2| in the new
-  // window.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), app_id2));
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), app_id2));
-
-  UninstallWebApp(app_id1);
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id1));
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), app_id1));
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), app_id2));
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), app_id2));
-
-  // Stop accessing the camera and the microphone for |app_id2|.
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_CLOSING);
-  MediaRequestChangeForWebContent(
-      web_content2, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_CLOSING);
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id2));
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id2));
-
-  // Navigate to Url1, and check |app_id1| is not accessing the camera or the
-  // microphone, because it has been removed.
-  ui_test_utils::NavigateToURL(browser(), GetUrl1());
-  auto* web_content3 = GetWebContents();
-  MediaRequestChangeForWebContent(
-      web_content3, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  MediaRequestChangeForWebContent(
-      web_content3, GetUrl1(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id1));
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), app_id1));
-
-  // Navigate to Url2, and check |app_id2| is accessing the camera and the
-  // microphone.
-  ui_test_utils::NavigateToURL(browser(), GetUrl2());
-  auto* web_content4 = GetWebContents();
-  MediaRequestChangeForWebContent(
-      web_content4, GetUrl2(),
-      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  MediaRequestChangeForWebContent(
-      web_content4, GetUrl2(),
-      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
-      content::MEDIA_REQUEST_STATE_DONE);
-  EXPECT_TRUE(AccessingMicrophone(browser()->profile(), app_id2));
-  EXPECT_TRUE(AccessingCamera(browser()->profile(), app_id2));
-  EXPECT_FALSE(AccessingCamera(browser()->profile(), app_id1));
-  EXPECT_FALSE(AccessingMicrophone(browser()->profile(), app_id1));
-}
diff --git a/chrome/browser/apps/app_service/media_requests.cc b/chrome/browser/apps/app_service/media_requests.cc
index bd30e50..e424deb 100644
--- a/chrome/browser/apps/app_service/media_requests.cc
+++ b/chrome/browser/apps/app_service/media_requests.cc
@@ -24,21 +24,6 @@
 
 MediaRequests::~MediaRequests() = default;
 
-bool MediaRequests::IsNewRequest(const std::string& app_id,
-                                 const content::WebContents* web_contents,
-                                 const content::MediaRequestState state) {
-  if (state != content::MEDIA_REQUEST_STATE_DONE) {
-    return false;
-  }
-
-  DCHECK(web_contents);
-
-  return !HasRequest(app_id, web_contents,
-                     app_id_to_web_contents_for_camera_) &&
-         !HasRequest(app_id, web_contents,
-                     app_id_to_web_contents_for_microphone_);
-}
-
 AccessingRequest MediaRequests::UpdateRequests(
     const std::string& app_id,
     const content::WebContents* web_contents,
@@ -79,29 +64,6 @@
       MaybeRemoveRequest(app_id, app_id_to_web_contents_for_microphone_));
 }
 
-AccessingRequest MediaRequests::OnWebContentsDestroyed(
-    const std::string& app_id,
-    const content::WebContents* web_contents) {
-  return AccessingRequest(
-      MaybeRemoveRequest(app_id, web_contents,
-                         app_id_to_web_contents_for_camera_),
-      MaybeRemoveRequest(app_id, web_contents,
-                         app_id_to_web_contents_for_microphone_));
-}
-
-bool MediaRequests::HasRequest(
-    const std::string& app_id,
-    const content::WebContents* web_contents,
-    const std::map<std::string, std::set<const content::WebContents*>>&
-        app_id_to_web_contents) {
-  auto it = app_id_to_web_contents.find(app_id);
-  if (it != app_id_to_web_contents.end() &&
-      it->second.find(web_contents) != it->second.end()) {
-    return true;
-  }
-  return false;
-}
-
 base::Optional<bool> MediaRequests::MaybeAddRequest(
     const std::string& app_id,
     const content::WebContents* web_contents,
diff --git a/chrome/browser/apps/app_service/media_requests.h b/chrome/browser/apps/app_service/media_requests.h
index 7ab2b788..9df1967 100644
--- a/chrome/browser/apps/app_service/media_requests.h
+++ b/chrome/browser/apps/app_service/media_requests.h
@@ -39,13 +39,6 @@
   MediaRequests(const MediaRequests&) = delete;
   MediaRequests& operator=(const MediaRequests&) = delete;
 
-  // Returns true if there is no existing access request of both camera and
-  // microphone for |app_id| and |web_contents|, and |state| is a new request.
-  // Otherwise, return false.
-  bool IsNewRequest(const std::string& app_id,
-                    const content::WebContents* web_contents,
-                    const content::MediaRequestState state);
-
   // Updates |app_id_to_web_contents_for_camera_| and
   // |app_id_to_web_contents_for_microphone_| to record the media accessing
   // requests for |app_id|. Returns the update result AccessingRequest.
@@ -60,22 +53,7 @@
   // AccessingRequest.camera or AccessingRequest.microphone.
   AccessingRequest RemoveRequests(const std::string& app_id);
 
-  // Invoked when |web_contents| is being destroyed. Removes requests in
-  // |app_id_to_web_contents_for_camera_| and
-  // |app_id_to_web_contents_for_microphone_| for the given |app_id| and
-  // |web_contents|. If there are media accessing requests for |app_id|, returns
-  // false for AccessingRequest.camera or AccessingRequest.microphone.
-  AccessingRequest OnWebContentsDestroyed(
-      const std::string& app_id,
-      const content::WebContents* web_contents);
-
  private:
-  bool HasRequest(
-      const std::string& app_id,
-      const content::WebContents* web_contents,
-      const std::map<std::string, std::set<const content::WebContents*>>&
-          app_id_to_web_contents);
-
   base::Optional<bool> MaybeAddRequest(
       const std::string& app_id,
       const content::WebContents* web_contents,
diff --git a/chrome/browser/apps/app_service/web_apps_chromeos.cc b/chrome/browser/apps/app_service/web_apps_chromeos.cc
index 87b1341..c37740a 100644
--- a/chrome/browser/apps/app_service/web_apps_chromeos.cc
+++ b/chrome/browser/apps/app_service/web_apps_chromeos.cc
@@ -471,40 +471,12 @@
     return;
   }
 
-  if (media_requests_.IsNewRequest(app_id.value(), web_contents, state)) {
-    content::WebContentsUserData<AppWebContentsData>::CreateForWebContents(
-        web_contents, this);
-  }
-
   auto result = media_requests_.UpdateRequests(app_id.value(), web_contents,
                                                stream_type, state);
   ModifyCapabilityAccess(subscribers(), app_id.value(), result.camera,
                          result.microphone);
 }
 
-void WebAppsChromeOs::OnWebContentsDestroyed(
-    content::WebContents* web_contents) {
-  DCHECK(web_contents);
-
-  base::Optional<web_app::AppId> app_id =
-      web_app::FindInstalledAppWithUrlInScope(
-          profile(), web_contents->GetLastCommittedURL(),
-          /*window_only=*/false);
-  if (!app_id.has_value()) {
-    return;
-  }
-
-  const web_app::WebApp* web_app = GetWebApp(app_id.value());
-  if (!web_app || !Accepts(app_id.value())) {
-    return;
-  }
-
-  auto result =
-      media_requests_.OnWebContentsDestroyed(app_id.value(), web_contents);
-  ModifyCapabilityAccess(subscribers(), app_id.value(), result.camera,
-                         result.microphone);
-}
-
 void WebAppsChromeOs::OnNotificationDisplayed(
     const message_center::Notification& notification,
     const NotificationCommon::Metadata* const metadata) {
diff --git a/chrome/browser/apps/app_service/web_apps_chromeos.h b/chrome/browser/apps/app_service/web_apps_chromeos.h
index 485418e..8644a9d 100644
--- a/chrome/browser/apps/app_service/web_apps_chromeos.h
+++ b/chrome/browser/apps/app_service/web_apps_chromeos.h
@@ -11,7 +11,6 @@
 #include "base/scoped_observation.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/apps/app_service/app_notifications.h"
-#include "chrome/browser/apps/app_service/app_web_contents_data.h"
 #include "chrome/browser/apps/app_service/icon_key_util.h"
 #include "chrome/browser/apps/app_service/media_requests.h"
 #include "chrome/browser/apps/app_service/paused_apps.h"
@@ -41,8 +40,7 @@
 class WebAppsChromeOs : public WebAppsBase,
                         public ArcAppListPrefs::Observer,
                         public NotificationDisplayService::Observer,
-                        public MediaCaptureDevicesDispatcher::Observer,
-                        public AppWebContentsData::Client {
+                        public MediaCaptureDevicesDispatcher::Observer {
  public:
   WebAppsChromeOs(const mojo::Remote<apps::mojom::AppService>& app_service,
                   Profile* profile,
@@ -116,9 +114,6 @@
                        blink::mojom::MediaStreamType stream_type,
                        const content::MediaRequestState state) override;
 
-  // AppWebContentsData::Observer:
-  void OnWebContentsDestroyed(content::WebContents* contents) override;
-
   // NotificationDisplayService::Observer overrides.
   void OnNotificationDisplayed(
       const message_center::Notification& notification,
diff --git a/chrome/browser/browser_switcher/browser_switcher_sitelist.cc b/chrome/browser/browser_switcher/browser_switcher_sitelist.cc
index c1f677a..cf930db5 100644
--- a/chrome/browser/browser_switcher/browser_switcher_sitelist.cc
+++ b/chrome/browser/browser_switcher/browser_switcher_sitelist.cc
@@ -35,16 +35,35 @@
   base::StringPiece spec_without_port;
 };
 
-// Returns true if |input| contains |token|, ignoring case for ASCII
-// characters.
-bool StringContainsInsensitiveASCII(base::StringPiece input,
-                                    base::StringPiece token) {
-  const char* found =
-      std::search(input.begin(), input.end(), token.begin(), token.end(),
-                  [](char a, char b) {
+// Case-insensitively compare the hostname of |host_and_port| with the hostname
+// pattern |pattern|.
+bool MatchesHostNameAtEnd(base::StringPiece host_and_port,
+                          base::StringPiece pattern) {
+  // Use reverse_iterator to search from the *end* of the string.
+  std::reverse_iterator<const char*> found =
+      std::search(host_and_port.rbegin(), host_and_port.rend(),
+                  pattern.rbegin(), pattern.rend(), [](char a, char b) {
                     return base::ToLowerASCII(a) == base::ToLowerASCII(b);
                   });
-  return found != input.end();
+  if (found == host_and_port.rend())
+    return false;
+
+  const char* beginning = found.base() - pattern.size();
+  const char* end = found.base();
+
+  // The match should be at a domain boundary, i.e. preceded by:
+  //   (a) the beginning of the string, or
+  //   (b) a dot.
+  if (beginning != host_and_port.begin() && *(beginning - 1) != '.')
+    return false;
+
+  // The match should be at the end, i.e. followed by:
+  //   (a) the end of the string, or
+  //   (b) a port number.
+  if (*end != '\0' && *end != ':')
+    return false;
+
+  return true;
 }
 
 // Checks if the omitted prefix for a non-fully specific prefix is one of the
@@ -81,7 +100,7 @@
     return false;
   }
   // Compare hosts and ports, case-insensitive.
-  return StringContainsInsensitiveASCII(url.host_and_port, pattern);
+  return MatchesHostNameAtEnd(url.host_and_port, pattern);
 }
 
 // Checks whether |patterns| contains a pattern that matches |url|, and returns
diff --git a/chrome/browser/browser_switcher/browser_switcher_sitelist_unittest.cc b/chrome/browser/browser_switcher/browser_switcher_sitelist_unittest.cc
index ffc559c..360691c 100644
--- a/chrome/browser/browser_switcher/browser_switcher_sitelist_unittest.cc
+++ b/chrome/browser/browser_switcher/browser_switcher_sitelist_unittest.cc
@@ -122,12 +122,11 @@
   EXPECT_TRUE(ShouldSwitch(GURL("https://example.com/")));
   EXPECT_TRUE(ShouldSwitch(GURL("http://subdomain.example.com/")));
   EXPECT_TRUE(ShouldSwitch(GURL("http://example.com/foobar/")));
+  EXPECT_TRUE(ShouldSwitch(GURL("http://example.com.something.example.com/")));
   EXPECT_FALSE(ShouldSwitch(GURL("http://google.com/")));
   EXPECT_FALSE(ShouldSwitch(GURL("http://example.ca/")));
-
-  // For backwards compatibility, this should also match, even if it's not the
-  // same host.
-  EXPECT_TRUE(ShouldSwitch(GURL("https://notexample.com/")));
+  EXPECT_FALSE(ShouldSwitch(GURL("http://notexample.com/")));
+  EXPECT_FALSE(ShouldSwitch(GURL("http://example.com.invalid.com/")));
 }
 
 TEST_F(BrowserSwitcherSitelistTest, ShouldRedirectHostNotLowerCase) {
@@ -198,6 +197,7 @@
   Initialize(
       {"//example.com", "//test.com:3000", "lol.com:3000", "trololo.com"}, {});
   EXPECT_TRUE(ShouldSwitch(GURL("http://example.com:2000/something")));
+  EXPECT_TRUE(ShouldSwitch(GURL("http://foo.trololo.com:2000/something")));
   EXPECT_TRUE(ShouldSwitch(GURL("http://test.com:3000/something")));
   EXPECT_TRUE(ShouldSwitch(GURL("http://lol.com:3000/something")));
   EXPECT_TRUE(ShouldSwitch(GURL("http://trololo.com/something")));
@@ -205,6 +205,9 @@
   EXPECT_FALSE(ShouldSwitch(GURL("http://test.com:2000/something")));
   EXPECT_FALSE(ShouldSwitch(GURL("http://test.com:2000/something:3000")));
   EXPECT_FALSE(ShouldSwitch(GURL("http://test.com/something:3000")));
+  EXPECT_FALSE(ShouldSwitch(GURL("http://nottrololo.com:2000/something")));
+  EXPECT_FALSE(
+      ShouldSwitch(GURL("http://trololo.com.invalid.com:2000/something")));
 }
 
 TEST_F(BrowserSwitcherSitelistTest, ShouldPickUpPrefChanges) {
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager.cc b/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager.cc
index 3aacad0..d1381b21 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager.cc
@@ -54,13 +54,7 @@
 class BrowsingDataRemoverObserver
     : public content::BrowsingDataRemover::Observer {
  public:
-  ~BrowsingDataRemoverObserver() override {
-    // The BrowsingDataRemoverImpl notifying us is indirectly owned by Profile,
-    // so triggering ~Profile() from here causes UAF bugs. Post the profile
-    // deletion to another task to avoid this.
-    base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
-        FROM_HERE, std::move(profile_keep_alive_));
-  }
+  ~BrowsingDataRemoverObserver() override = default;
 
   // Creates an instance of BrowsingDataRemoverObserver that
   // manages its own lifetime. The instance will be deleted after
diff --git a/chrome/browser/chromeos/web_applications/print_management_app_integration_browsertest.cc b/chrome/browser/chromeos/web_applications/print_management_app_integration_browsertest.cc
index 15948af..09ff0607 100644
--- a/chrome/browser/chromeos/web_applications/print_management_app_integration_browsertest.cc
+++ b/chrome/browser/chromeos/web_applications/print_management_app_integration_browsertest.cc
@@ -16,7 +16,7 @@
                        PrintManagementAppInLauncher) {
   const GURL url(chromeos::kChromeUIPrintManagementAppUrl);
   EXPECT_NO_FATAL_FAILURE(ExpectSystemWebAppValid(
-      web_app::SystemAppType::PRINT_MANAGEMENT, url, "Print Jobs"));
+      web_app::SystemAppType::PRINT_MANAGEMENT, url, "Print jobs"));
 }
 
 INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
diff --git a/chrome/browser/chromeos/web_applications/print_management_web_app_info.cc b/chrome/browser/chromeos/web_applications/print_management_web_app_info.cc
index 7e79760..6e19da7f 100644
--- a/chrome/browser/chromeos/web_applications/print_management_web_app_info.cc
+++ b/chrome/browser/chromeos/web_applications/print_management_web_app_info.cc
@@ -19,7 +19,7 @@
       std::make_unique<WebApplicationInfo>();
   info->start_url = GURL(chromeos::kChromeUIPrintManagementAppUrl);
   info->scope = GURL(chromeos::kChromeUIPrintManagementAppUrl);
-  info->title = l10n_util::GetStringUTF16(IDS_PRINT_MANAGEMENT_APP_NAME);
+  info->title = l10n_util::GetStringUTF16(IDS_PRINT_MANAGEMENT_TITLE);
   web_app::CreateIconInfoForSystemWebApp(
       info->start_url,
       {{"print_management_192.png", 192, IDR_PRINT_MANAGEMENT_ICON}}, *info);
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc
index 247d625..a4d42ed 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -1556,6 +1556,10 @@
                                     const std::string& trigger) {
   HatsService* hats_service =
       HatsServiceFactory::GetForProfile(profile_->GetOriginalProfile(), true);
+  if (!hats_service) {
+    ShowSurveyCallback(std::move(callback), false);
+    return;
+  }
   base::RepeatingCallback<void(const base::Value*)> on_survey =
       base::AdaptCallbackForRepeating(std::move(callback));
   hats_service->LaunchSurvey(
@@ -1567,7 +1571,7 @@
                                        const std::string& trigger) {
   HatsService* hats_service =
       HatsServiceFactory::GetForProfile(profile_->GetOriginalProfile(), true);
-  bool can_show = hats_service->CanShowSurvey(trigger);
+  bool can_show = hats_service ? hats_service->CanShowSurvey(trigger) : false;
   base::DictionaryValue response;
   response.SetBoolean("canShowSurvey", can_show);
   std::move(callback).Run(&response);
diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc
index 0b298505..9f3e66a 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate.cc
@@ -44,6 +44,7 @@
 #include "chrome/browser/download/mixed_content_download_blocking.h"
 #include "chrome/browser/download/save_package_file_picker.h"
 #include "chrome/browser/enterprise/connectors/common.h"
+#include "chrome/browser/enterprise/connectors/file_system/rename_handler.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h"
 #include "chrome/browser/platform_util.h"
@@ -1632,6 +1633,18 @@
       &ChromeDownloadManagerDelegate::ConnectToQuarantineService);
 }
 
+std::unique_ptr<download::DownloadItemRenameHandler>
+ChromeDownloadManagerDelegate::GetRenameHandlerForDownload(
+    download::DownloadItem* download_item) {
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
+    defined(OS_MAC)
+  return enterprise_connectors::FileSystemRenameHandler::CreateIfNeeded(
+      download_item);
+#else
+  return nullptr;
+#endif
+}
+
 void ChromeDownloadManagerDelegate::OnCheckDownloadAllowedComplete(
     content::CheckDownloadAllowedCallback check_download_allowed_cb,
     bool storage_permission_granted,
diff --git a/chrome/browser/download/chrome_download_manager_delegate.h b/chrome/browser/download/chrome_download_manager_delegate.h
index 17ca9aa..3535714 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.h
+++ b/chrome/browser/download/chrome_download_manager_delegate.h
@@ -130,6 +130,8 @@
       content::CheckDownloadAllowedCallback check_download_allowed_cb) override;
   download::QuarantineConnectionCallback GetQuarantineConnectionCallback()
       override;
+  std::unique_ptr<download::DownloadItemRenameHandler>
+  GetRenameHandlerForDownload(download::DownloadItem* download_item) override;
 
   // Opens a download using the platform handler. DownloadItem::OpenDownload,
   // which ends up being handled by OpenDownload(), will open a download in the
diff --git a/chrome/browser/enterprise/connectors/file_system/rename_handler.cc b/chrome/browser/enterprise/connectors/file_system/rename_handler.cc
new file mode 100644
index 0000000..a81329fe
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/file_system/rename_handler.cc
@@ -0,0 +1,76 @@
+// 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 "chrome/browser/enterprise/connectors/file_system/rename_handler.h"
+
+#include <utility>
+
+#include "chrome/browser/enterprise/connectors/connectors_service.h"
+#include "components/download/public/common/download_item.h"
+#include "content/public/browser/download_item_utils.h"
+
+namespace enterprise_connectors {
+
+const base::Feature kFileSystemConnectorEnabled{
+    "FileSystemConnectorsEnabled", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// static
+base::Optional<FileSystemSettings> FileSystemRenameHandler::IsEnabled(
+    download::DownloadItem* download_item) {
+  if (!base::FeatureList::IsEnabled(kFileSystemConnectorEnabled))
+    return base::nullopt;
+
+  ConnectorsService* service = ConnectorsServiceFactory::GetForBrowserContext(
+      content::DownloadItemUtils::GetBrowserContext(download_item));
+  return service->GetFileSystemSettings(
+      download_item->GetURL(), FileSystemConnector::SEND_DOWNLOAD_TO_CLOUD);
+}
+
+// static
+std::unique_ptr<download::DownloadItemRenameHandler>
+FileSystemRenameHandler::Create(download::DownloadItem* download_item,
+                                FileSystemSettings settings) {
+  return std::make_unique<FileSystemRenameHandler>(download_item,
+                                                   std::move(settings));
+}
+
+// static
+std::unique_ptr<download::DownloadItemRenameHandler>
+FileSystemRenameHandler::CreateIfNeeded(download::DownloadItem* download_item) {
+  auto settings = IsEnabled(download_item);
+  if (!settings.has_value())
+    return nullptr;
+
+  return Create(download_item, std::move(settings.value()));
+}
+
+// Don't hold on to |download_item| since it can only be accessed from a
+// specific thread.  Get all the info we need from it now.
+FileSystemRenameHandler::FileSystemRenameHandler(
+    download::DownloadItem* download_item,
+    FileSystemSettings settings)
+    : download::DownloadItemRenameHandler(download_item),
+      settings_(std::move(settings)) {}
+
+FileSystemRenameHandler::~FileSystemRenameHandler() = default;
+
+void FileSystemRenameHandler::Start(Callback callback) {
+  download_callback_ = std::move(callback);
+
+  // TODO(alicego): Hook up to download controller.
+  NotifyResultToDownloadThread(false);
+}
+
+void FileSystemRenameHandler::OpenDownload() {}
+
+void FileSystemRenameHandler::ShowDownloadInContext() {}
+
+void FileSystemRenameHandler::NotifyResultToDownloadThread(bool success) {
+  // TODO(crbug.com/1168815): define required error messages.
+  auto reason = success ? download::DOWNLOAD_INTERRUPT_REASON_NONE
+                        : download::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+  std::move(download_callback_).Run(reason, target_path_);
+}
+
+}  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/file_system/rename_handler.h b/chrome/browser/enterprise/connectors/file_system/rename_handler.h
new file mode 100644
index 0000000..51923ff
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/file_system/rename_handler.h
@@ -0,0 +1,59 @@
+// 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 CHROME_BROWSER_ENTERPRISE_CONNECTORS_FILE_SYSTEM_RENAME_HANDLER_H_
+#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_FILE_SYSTEM_RENAME_HANDLER_H_
+
+#include "base/callback.h"
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/optional.h"
+#include "chrome/browser/enterprise/connectors/common.h"
+#include "components/download/public/common/download_interrupt_reasons.h"
+#include "components/download/public/common/download_item_rename_handler.h"
+
+namespace enterprise_connectors {
+
+// Experimental flag to enable or disable the file system connector.
+extern const base::Feature kFileSystemConnectorEnabled;
+
+// An implementation of download::DownloadItemRenameHandler that sends a
+// download item file to a cloud-based storage provider as specified in the
+// SendDownloadToCloudEnterpriseConnector policy.
+class FileSystemRenameHandler : public download::DownloadItemRenameHandler {
+ public:
+  static std::unique_ptr<download::DownloadItemRenameHandler> CreateIfNeeded(
+      download::DownloadItem* download_item);
+
+  FileSystemRenameHandler(download::DownloadItem* download_item,
+                          FileSystemSettings settings);
+  ~FileSystemRenameHandler() override;
+
+ private:
+  static base::Optional<FileSystemSettings> IsEnabled(
+      download::DownloadItem* download_item);
+
+  static std::unique_ptr<download::DownloadItemRenameHandler> Create(
+      download::DownloadItem* download_item,
+      FileSystemSettings settings);
+
+  void NotifyResultToDownloadThread(bool success);
+
+  // download::DownloadItemRenameHandler interface.
+  void Start(Callback callback) override;
+  void OpenDownload() override;
+  void ShowDownloadInContext() override;
+
+  // Fields copied from |download_item| or from policy settings.  These are
+  // constant for the life of the rename handler.
+  const base::FilePath target_path_;
+  const FileSystemSettings settings_;
+
+  // Invoked to tell the download system when the rename has completed.
+  Callback download_callback_;
+};
+
+}  // namespace enterprise_connectors
+
+#endif  // CHROME_BROWSER_ENTERPRISE_CONNECTORS_FILE_SYSTEM_RENAME_HANDLER_H_
diff --git a/chrome/browser/enterprise/connectors/file_system/rename_handler_unittest.cc b/chrome/browser/enterprise/connectors/file_system/rename_handler_unittest.cc
new file mode 100644
index 0000000..d59eb33
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/file_system/rename_handler_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <tuple>
+
+#include "base/json/json_reader.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/enterprise/connectors/common.h"
+#include "chrome/browser/enterprise/connectors/connectors_service.h"
+#include "chrome/browser/enterprise/connectors/file_system/rename_handler.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "content/public/browser/download_item_utils.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/fake_download_item.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace enterprise_connectors {
+
+constexpr char kNormalSendDownloadToCloudPref[] = R"([
+  {
+    "service_provider": "box",
+    "enterprise_id": "1234567890",
+    "enable": [
+      {
+        "url_list": ["*"],
+        "mime_types": ["text/plain", "image/png", "application/zip"]
+      }
+    ]
+  }
+])";
+
+class RenameHandlerTest : public testing::Test,
+                          public testing::WithParamInterface<bool> {
+ public:
+  RenameHandlerTest() : profile_manager_(TestingBrowserProcess::GetGlobal()) {
+    if (enable_feature_flag()) {
+      scoped_feature_list_.InitWithFeatures({kFileSystemConnectorEnabled}, {});
+    } else {
+      scoped_feature_list_.InitWithFeatures({}, {kFileSystemConnectorEnabled});
+    }
+
+    EXPECT_TRUE(profile_manager_.SetUp());
+    profile_ = profile_manager_.CreateTestingProfile("test-user");
+
+    // Make sure that from the connectors manager point of view the file system
+    // connector should be enabled.  So that the only thing that controls
+    // whether the rename handler is used or not is the feature flag.
+    profile_->GetPrefs()->Set(
+        ConnectorPref(FileSystemConnector::SEND_DOWNLOAD_TO_CLOUD),
+        *base::JSONReader::Read(kNormalSendDownloadToCloudPref));
+  }
+
+  bool enable_feature_flag() const { return GetParam(); }
+
+  Profile* profile() { return profile_; }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  TestingProfileManager profile_manager_;
+  TestingProfile* profile_;
+};
+
+TEST_P(RenameHandlerTest, Test) {
+  content::FakeDownloadItem item;
+  item.SetURL(GURL("https://any.com"));
+  content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
+
+  auto handler = FileSystemRenameHandler::CreateIfNeeded(&item);
+  ASSERT_EQ(enable_feature_flag(), handler.get() != nullptr);
+}
+
+INSTANTIATE_TEST_CASE_P(, RenameHandlerTest, testing::Bool());
+
+}  // namespace enterprise_connectors
diff --git a/chrome/browser/extensions/api/identity/identity_token_cache.cc b/chrome/browser/extensions/api/identity/identity_token_cache.cc
index 2d68024..b76e09d8 100644
--- a/chrome/browser/extensions/api/identity/identity_token_cache.cc
+++ b/chrome/browser/extensions/api/identity/identity_token_cache.cc
@@ -23,8 +23,7 @@
 IdentityTokenCacheValue IdentityTokenCacheValue::CreateRemoteConsent(
     const RemoteConsentResolutionData& resolution_data) {
   IdentityTokenCacheValue cache_value;
-  cache_value.status_ = CACHE_STATUS_REMOTE_CONSENT;
-  cache_value.resolution_data_ = resolution_data;
+  cache_value.value_ = resolution_data;
   cache_value.expiration_time_ =
       base::Time::Now() +
       base::TimeDelta::FromSeconds(
@@ -36,8 +35,7 @@
 IdentityTokenCacheValue IdentityTokenCacheValue::CreateRemoteConsentApproved(
     const std::string& consent_result) {
   IdentityTokenCacheValue cache_value;
-  cache_value.status_ = CACHE_STATUS_REMOTE_CONSENT_APPROVED;
-  cache_value.consent_result_ = consent_result;
+  cache_value.value_ = consent_result;
   cache_value.expiration_time_ =
       base::Time::Now() +
       base::TimeDelta::FromSeconds(
@@ -53,9 +51,7 @@
   DCHECK(!granted_scopes.empty());
 
   IdentityTokenCacheValue cache_value;
-  cache_value.status_ = CACHE_STATUS_TOKEN;
-  cache_value.token_ = token;
-  cache_value.granted_scopes_ = granted_scopes;
+  cache_value.value_ = TokenValue(token, granted_scopes);
 
   // Remove 20 minutes from the ttl so cached tokens will have some time
   // to live any time they are returned.
@@ -73,12 +69,26 @@
     const {
   if (is_expired())
     return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
-  else
-    return status_;
+
+  return GetStatusInternal();
+}
+
+IdentityTokenCacheValue::CacheValueStatus
+IdentityTokenCacheValue::GetStatusInternal() const {
+  if (absl::holds_alternative<RemoteConsentResolutionData>(value_)) {
+    return CACHE_STATUS_REMOTE_CONSENT;
+  } else if (absl::holds_alternative<std::string>(value_)) {
+    return CACHE_STATUS_REMOTE_CONSENT_APPROVED;
+  } else if (absl::holds_alternative<TokenValue>(value_)) {
+    return CACHE_STATUS_TOKEN;
+  } else {
+    DCHECK(absl::holds_alternative<absl::monostate>(value_));
+    return CACHE_STATUS_NOTFOUND;
+  }
 }
 
 bool IdentityTokenCacheValue::is_expired() const {
-  return status_ == CACHE_STATUS_NOTFOUND ||
+  return GetStatusInternal() == CACHE_STATUS_NOTFOUND ||
          expiration_time_ < base::Time::Now();
 }
 
@@ -88,21 +98,33 @@
 
 const RemoteConsentResolutionData& IdentityTokenCacheValue::resolution_data()
     const {
-  return resolution_data_;
+  return absl::get<RemoteConsentResolutionData>(value_);
 }
 
 const std::string& IdentityTokenCacheValue::consent_result() const {
-  return consent_result_;
+  return absl::get<std::string>(value_);
 }
 
 const std::string& IdentityTokenCacheValue::token() const {
-  return token_;
+  return absl::get<TokenValue>(value_).token;
 }
 
 const std::set<std::string>& IdentityTokenCacheValue::granted_scopes() const {
-  return granted_scopes_;
+  return absl::get<TokenValue>(value_).granted_scopes;
 }
 
+IdentityTokenCacheValue::TokenValue::TokenValue(
+    const std::string& input_token,
+    const std::set<std::string>& input_granted_scopes)
+    : token(input_token), granted_scopes(input_granted_scopes) {}
+
+IdentityTokenCacheValue::TokenValue::TokenValue(const TokenValue& other) =
+    default;
+IdentityTokenCacheValue::TokenValue&
+IdentityTokenCacheValue::TokenValue::operator=(const TokenValue& other) =
+    default;
+IdentityTokenCacheValue::TokenValue::~TokenValue() = default;
+
 IdentityTokenCache::AccessTokensKey::AccessTokensKey(
     const ExtensionTokenKey& key)
     : extension_id(key.extension_id), account_id(key.account_info.account_id) {}
diff --git a/chrome/browser/extensions/api/identity/identity_token_cache.h b/chrome/browser/extensions/api/identity/identity_token_cache.h
index f858174..f9c6b647 100644
--- a/chrome/browser/extensions/api/identity/identity_token_cache.h
+++ b/chrome/browser/extensions/api/identity/identity_token_cache.h
@@ -12,6 +12,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/extensions/api/identity/extension_token_key.h"
 #include "google_apis/gaia/oauth2_mint_token_flow.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
 
 namespace extensions {
 
@@ -43,23 +44,39 @@
   CacheValueStatus status() const;
   const base::Time& expiration_time() const;
 
+  // These getters should be used only if `status()` returns a value
+  // corresponding to the type. Otherwise, the application will crash.
+  // CACHE_STATUS_REMOTE_CONSENT:
   const RemoteConsentResolutionData& resolution_data() const;
+  // CACHE_STATUS_REMOTE_CONSENT_APPROVED:
   const std::string& consent_result() const;
+  // CACHE_STATUS_TOKEN:
   const std::string& token() const;
   const std::set<std::string>& granted_scopes() const;
 
  private:
+  struct TokenValue {
+    TokenValue(const std::string& token,
+               const std::set<std::string>& granted_scopes);
+    TokenValue(const TokenValue& other);
+    TokenValue& operator=(const TokenValue& other);
+    ~TokenValue();
+
+    std::string token;
+    std::set<std::string> granted_scopes;
+  };
+
+  CacheValueStatus GetStatusInternal() const;
+
   bool is_expired() const;
 
-  CacheValueStatus status_ = CACHE_STATUS_NOTFOUND;
   base::Time expiration_time_;
 
-  // TODO(alexilin): This class holds at any given time one of the several
-  // possible types. Consider rewriting using absl::variant
-  RemoteConsentResolutionData resolution_data_;
-  std::string consent_result_;
-  std::string token_;
-  std::set<std::string> granted_scopes_;
+  absl::variant<absl::monostate,
+                RemoteConsentResolutionData,
+                std::string,
+                TokenValue>
+      value_;
 };
 
 // In-memory cache of OAuth2 access tokens that are requested by extensions
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow.cc b/chrome/browser/extensions/api/identity/web_auth_flow.cc
index 2e1b8566..e3ff426 100644
--- a/chrome/browser/extensions/api/identity/web_auth_flow.cc
+++ b/chrome/browser/extensions/api/identity/web_auth_flow.cc
@@ -84,7 +84,6 @@
 
   // Stop listening to notifications first since some of the code
   // below may generate notifications.
-  registrar_.RemoveAll();
   WebContentsObserver::Observe(nullptr);
 
   if (!app_window_key_.empty()) {
@@ -170,11 +169,6 @@
       app_window->extension_id() == extension_misc::kIdentityApiUiAppId) {
     app_window_ = app_window;
     WebContentsObserver::Observe(app_window->web_contents());
-
-    registrar_.Add(
-        this,
-        content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
-        content::NotificationService::AllBrowserContextsAndSources());
   }
 }
 
@@ -182,7 +176,6 @@
   if (app_window->window_key() == app_window_key_ &&
       app_window->extension_id() == extension_misc::kIdentityApiUiAppId) {
     app_window_ = nullptr;
-    registrar_.RemoveAll();
     WebContentsObserver::Observe(nullptr);
 
     if (delegate_)
@@ -200,27 +193,16 @@
     delegate_->OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED);
 }
 
-void WebAuthFlow::Observe(int type,
-                          const content::NotificationSource& source,
-                          const content::NotificationDetails& details) {
-  DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED, type);
+void WebAuthFlow::InnerWebContentsCreated(
+    content::WebContents* inner_web_contents) {
   DCHECK(app_window_);
 
   if (!delegate_ || embedded_window_created_)
     return;
 
-  RenderViewHost* render_view(content::Details<RenderViewHost>(details).ptr());
-  WebContents* web_contents = WebContents::FromRenderViewHost(render_view);
-  GuestViewBase* guest = GuestViewBase::FromWebContents(web_contents);
-  WebContents* owner = guest ? guest->owner_web_contents() : nullptr;
-  if (!web_contents || owner != WebContentsObserver::web_contents())
-    return;
-
   // Switch from watching the app window to the guest inside it.
   embedded_window_created_ = true;
-  WebContentsObserver::Observe(web_contents);
-
-  registrar_.RemoveAll();
+  WebContentsObserver::Observe(inner_web_contents);
 }
 
 void WebAuthFlow::RenderProcessGone(base::TerminationStatus status) {
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow.h b/chrome/browser/extensions/api/identity/web_auth_flow.h
index ae0f5c36..bef7a9b 100644
--- a/chrome/browser/extensions/api/identity/web_auth_flow.h
+++ b/chrome/browser/extensions/api/identity/web_auth_flow.h
@@ -8,8 +8,6 @@
 #include <string>
 
 #include "base/macros.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/storage_partition_config.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "extensions/browser/app_window/app_window_registry.h"
@@ -20,8 +18,6 @@
 class WebAuthFlowTest;
 
 namespace content {
-class NotificationDetails;
-class NotificationSource;
 class StoragePartition;
 }
 
@@ -44,8 +40,7 @@
 //
 // A WebAuthFlow can be started in Mode::SILENT, which never displays
 // a window. If a window would be required, the flow fails.
-class WebAuthFlow : public content::NotificationObserver,
-                    public content::WebContentsObserver,
+class WebAuthFlow : public content::WebContentsObserver,
                     public AppWindowRegistry::Observer {
  public:
   enum Mode {
@@ -115,13 +110,10 @@
   void OnAppWindowAdded(AppWindow* app_window) override;
   void OnAppWindowRemoved(AppWindow* app_window) override;
 
-  // NotificationObserver implementation.
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
-
   // WebContentsObserver implementation.
   void DidStopLoading() override;
+  void InnerWebContentsCreated(
+      content::WebContents* inner_web_contents) override;
   void RenderProcessGone(base::TerminationStatus status) override;
   void TitleWasSet(content::NavigationEntry* entry) override;
   void DidStartNavigation(
@@ -144,8 +136,6 @@
   std::string app_window_key_;
   bool embedded_window_created_;
 
-  content::NotificationRegistrar registrar_;
-
   DISALLOW_COPY_AND_ASSIGN(WebAuthFlow);
 };
 
diff --git a/chrome/browser/extensions/protocol_handler_apitest.cc b/chrome/browser/extensions/protocol_handler_apitest.cc
index e2dac3c..45e194d5a 100644
--- a/chrome/browser/extensions/protocol_handler_apitest.cc
+++ b/chrome/browser/extensions/protocol_handler_apitest.cc
@@ -4,6 +4,7 @@
 
 #include <string>
 
+#include "build/build_config.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
 #include "chrome/browser/extensions/extension_apitest.h"
@@ -16,9 +17,22 @@
 #include "extensions/test/result_catcher.h"
 #include "third_party/blink/public/common/security/protocol_handler_security_level.h"
 
+#if defined(OS_MAC)
+#include "chrome/test/base/launchservices_utils_mac.h"
+#endif
+
 namespace extensions {
 
-using ProtocolHandlerApiTest = ExtensionApiTest;
+class ProtocolHandlerApiTest : public ExtensionApiTest {
+ public:
+  void SetUpOnMainThread() override {
+    ExtensionApiTest::SetUpOnMainThread();
+
+#if defined(OS_MAC)
+    ASSERT_TRUE(test::RegisterAppWithLaunchServices());
+#endif
+  }
+};
 
 class ProtocolHandlerChangeWaiter : public ProtocolHandlerRegistry::Observer {
  public:
@@ -40,10 +54,17 @@
   base::RunLoop run_loop_;
 };
 
+// Flaky on Mac.  https://crbug.com/1177254
+#if defined(OS_MAC)
+#define MAYBE_Registration DISABLED_Registration
+#else
+#define MAYBE_Registration Registration
+#endif
+
 // This test verifies correct registration of protocol handlers using HTML5's
 // registerProtocolHandler in extension context and its validation with relaxed
 // security checks.
-IN_PROC_BROWSER_TEST_F(ProtocolHandlerApiTest, Registration) {
+IN_PROC_BROWSER_TEST_F(ProtocolHandlerApiTest, MAYBE_Registration) {
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   // Initialize listener and result catcher before the test page is loaded to
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index 6cc18b1..bfd740ef 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -1767,9 +1767,12 @@
            inIncognitoContext ? 'incognito' : 'regular';
        var urls = [];
 
-       chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
-         if (changeInfo.status === 'complete') {
-           urls.push(tab.url);
+       chrome.tabs.onUpdated.addListener(function localListener(tabId,
+                                                                changeInfo,
+                                                                tab) {
+         if (changeInfo.status === 'loading') {
+           chrome.tabs.onUpdated.removeListener(localListener);
+           urls.push(changeInfo.url);
          }
        });
 
@@ -1800,9 +1803,7 @@
 
 }  // anonymous namespace
 
-// Flaky (crbug.com/1091141)
-IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
-                       DISABLED_TabsQuerySplit) {
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, TabsQuerySplit) {
   ExtensionTestMessageListener ready_regular("Script started regular", true);
   ExtensionTestMessageListener ready_incognito("Script started incognito",
                                                true);
@@ -1844,9 +1845,7 @@
   }
 }
 
-// Flaky (crbug.com/1091141)
-IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
-                       DISABLED_TabsQuerySpanning) {
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, TabsQuerySpanning) {
   ExtensionTestMessageListener ready_listener("Script started regular", true);
 
   // Open an incognito window.
@@ -1878,7 +1877,6 @@
             tabs_listener.message());
 }
 
-// Flaky (crbug.com/1091141)
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, TabsOnUpdatedSplit) {
   ExtensionTestMessageListener ready_regular("Script started regular", true);
   ExtensionTestMessageListener ready_incognito("Script started incognito",
@@ -1922,7 +1920,6 @@
   }
 }
 
-// Flaky (crbug.com/1091141)
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
                        TabsOnUpdatedSpanning) {
   // The spanning test differs from the Split test because it lets the
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index abefbc3..a185eeff 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -862,7 +862,7 @@
   {
     "name": "default-browser-setting",
     "owners": [ "gambard", "bling-flags@google.com" ],
-    "expiry_milestone": 89
+    "expiry_milestone": 92
   },
   {
     "name": "delay-competing-low-priority-requests",
@@ -1582,11 +1582,6 @@
     "expiry_milestone": 94
   },
   {
-    "name": "enable-duet-tabstrip-integration",
-    "owners": [ "memex-team@google.com" ],
-    "expiry_milestone": 84
-  },
-  {
     "name": "enable-encryption-migration",
     "owners": [ "fukino" ],
     "expiry_milestone": 76
@@ -1810,11 +1805,6 @@
     "expiry_milestone": 90
   },
   {
-    "name": "enable-horizontal-tab-switcher",
-    "owners": [ "memex-team@google.com" ],
-    "expiry_milestone": 77
-  },
-  {
     "name": "enable-hosted-app-quit-notification",
     "owners": [ "ccameron" ],
     "expiry_milestone": 77
@@ -2671,7 +2661,7 @@
   {
     "name": "expanded-tab-strip",
     "owners": [ "mouraroberto@google.com", "gambard", "bling-flags@google.com" ],
-    "expiry_milestone": 90
+    "expiry_milestone": 92
   },
   {
     "name": "expensive-background-timer-throttling",
@@ -3449,7 +3439,7 @@
   {
     "name": "mobile-google-srp",
     "owners": [ "gambard", "bling-flags@google.com" ],
-    "expiry_milestone": 88
+    "expiry_milestone": 92
   },
   {
     "name": "mobile-identity-consistency",
@@ -4550,7 +4540,7 @@
   {
     "name": "settings-refresh",
     "owners": [ "gambard", "bling-flags@google.com" ],
-    "expiry_milestone": 89
+    "expiry_milestone": 92
   },
   {
     "name": "share-button-in-top-toolbar",
@@ -5040,7 +5030,7 @@
   {
     "name": "use-js-error-page",
     "owners": [ "gambard", "bling-flags@google.com" ],
-    "expiry_milestone": 89
+    "expiry_milestone": 92
   },
   {
     "name": "use-lookalikes-for-navigation-suggestions",
@@ -5181,7 +5171,7 @@
   {
     "name": "web-view-native-context-menu",
     "owners": ["gambard","bling-flags@google.com"],
-    "expiry_milestone": 90
+    "expiry_milestone": 92
   },
   {
     "name": "webpage-alternative-text-zoom",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 046a069..960a4da4 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2855,10 +2855,6 @@
 const char kDirectActionsDescription[] =
     "Enables direct actions (Android Q and more).";
 
-const char kDuetTabStripIntegrationAndroidName[] = "Duet-TabStrip Integration";
-const char kDuetTabStripIntegrationAndroidDescription[] =
-    "Allows users to access integration of Duet and TabStrip.";
-
 const char kAutofillManualFallbackAndroidName[] =
     "Enable Autofill manual fallback for Addresses and Payments (Android)";
 const char kAutofillManualFallbackAndroidDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 72f71ee..9b79841bc 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -555,9 +555,6 @@
 extern const char kDownloadLaterDebugOnWifiName[];
 extern const char kDownloadLaterDebugOnWifiNameDescription[];
 
-extern const char kDuetTabStripIntegrationAndroidName[];
-extern const char kDuetTabStripIntegrationAndroidDescription[];
-
 extern const char kEnableLoginDetectionName[];
 extern const char kEnableLoginDetectionDescription[];
 
diff --git a/chrome/browser/installable/digital_asset_links/digital_asset_links_handler.h b/chrome/browser/installable/digital_asset_links/digital_asset_links_handler.h
index a7d6bf9..b529340 100644
--- a/chrome/browser/installable/digital_asset_links/digital_asset_links_handler.h
+++ b/chrome/browser/installable/digital_asset_links/digital_asset_links_handler.h
@@ -75,7 +75,6 @@
       const std::string& manifest_url,
       RelationshipCheckResultCallback callback);
 
- private:
   // Generic DAL verifier.
   bool CheckDigitalAssetLinkRelationship(
       const std::string& web_domain,
@@ -84,6 +83,7 @@
       const std::map<std::string, std::string>& target_values,
       RelationshipCheckResultCallback callback);
 
+ private:
   void OnURLLoadComplete(std::string relationship,
                          base::Optional<std::string> fingerprint,
                          std::map<std::string, std::string> target_values,
diff --git a/chrome/browser/lacros/cert_db_initializer_impl.cc b/chrome/browser/lacros/cert_db_initializer_impl.cc
index d3ff701..ee434f5 100644
--- a/chrome/browser/lacros/cert_db_initializer_impl.cc
+++ b/chrome/browser/lacros/cert_db_initializer_impl.cc
@@ -22,65 +22,8 @@
 #include "crypto/nss_util_internal.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
-// Includes for `IsEnabledForEarlyAccess()`.
-#include "base/containers/flat_set.h"
-#include "base/feature_list.h"
-#include "base/no_destructor.h"
-#include "chrome/browser/signin/identity_manager_factory.h"
-
 namespace {
 
-// When this feature is enabled, certs are available to everyone.
-const base::Feature kLacrosEnableCerts{"LacrosEnableCerts",
-                                       base::FEATURE_DISABLED_BY_DEFAULT};
-
-// TODO(crbug.com/1145946): Enable certificate database initialization for
-// everyone when the policy stack is ready (expected to happen before Feb 2021).
-bool IsEnabledForEarlyAccess(Profile* profile) {
-  if (base::FeatureList::IsEnabled(kLacrosEnableCerts)) {
-    return true;
-  }
-
-  static base::NoDestructor<base::flat_set<std::string>> allowlist{
-      {"bartfab@google.com",       "darin@google.com",
-       "dhaddock@google.com",      "edcourtney@google.com",
-       "erikchen@google.com",      "fangzhoug@google.com",
-       "fukino@google.com",        "gianluca@google.com",
-       "heiserya@google.com",      "hidehiko@google.com",
-       "huangs@google.com",        "huanr@google.com",
-       "igorcov@google.com",       "jamescook@google.com",
-       "jennyz@google.com",        "jorgelo@google.com",
-       "ketakid@google.com",       "lakpamarthy@google.com",
-       "leolai@google.com",        "liaoyuke@google.com",
-       "maguschen@google.com",     "marinakz@google.com",
-       "miersh@google.com",        "mkarkada@google.com",
-       "okalitova@google.com",     "oshima@google.com",
-       "pmarko@google.com",        "pucchakayala@google.com",
-       "rbock@google.com",         "rjkroege@google.com",
-       "rogerta@google.com",       "satorux@google.com",
-       "sinhak@google.com",        "songsuk@google.com",
-       "srinivassista@google.com", "svenzheng@google.com",
-       "willmcleod@google.com",    "ythjkt@google.com",
-       "yusukes@google.com",       "nrpeter@google.com",
-       "miersh@managedchrome.com", "omrilio@google.com",
-       "thomasriedl@google.com",   "kuscher@google.com",
-       "andrewgray@google.com",    "acostinas@google.com",
-       "amraboelkher@google.com",  "anastasiian@google.com",
-       "anqing@google.com",        "apotapchuk@google.com",
-       "asumaneev@google.com",     "djmm@google.com",
-       "emaxx@google.com",         "hendrich@google.com",
-       "marcgrimme@google.com",    "maxkirsch@google.com",
-       "mslus@google.com",         "okalitova@google.com",
-       "omorsi@google.com",        "roddis@google.com"}};
-
-  signin::IdentityManager* identity_manager =
-      IdentityManagerFactory::GetForProfile(profile);
-  return base::Contains(*allowlist, identity_manager
-                                        ->GetPrimaryAccountInfo(
-                                            signin::ConsentLevel::kNotRequired)
-                                        .email);
-}
-
 bool InitializeCertDbOnWorkerThread(bool should_load_chaps,
                                     base::FilePath software_nss_db_path) {
   crypto::EnsureNSSInit();
@@ -204,12 +147,6 @@
 void CertDbInitializerImpl::WaitForCertDbReady() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  if (!IsEnabledForEarlyAccess(profile_)) {
-    LOG(WARNING) << "Certificate initialization is skipped.";
-    OnCertDbInitializationFinished(false);
-    return;
-  }
-
   if (!profile_->IsMainProfile()) {
     OnCertDbInitializationFinished(false);
     return;
diff --git a/chrome/browser/lookalikes/digital_asset_links_cross_validator.cc b/chrome/browser/lookalikes/digital_asset_links_cross_validator.cc
new file mode 100644
index 0000000..fe7a14c
--- /dev/null
+++ b/chrome/browser/lookalikes/digital_asset_links_cross_validator.cc
@@ -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.
+
+#include "chrome/browser/lookalikes/digital_asset_links_cross_validator.h"
+
+#include "chrome/browser/profiles/profile.h"
+
+namespace {
+const char* kDigitalAssetLinkRecordType = "lookalikes/allowlist";
+}
+
+DigitalAssetLinkCrossValidator::DigitalAssetLinkCrossValidator(
+    Profile* profile,
+    const url::Origin& lookalike_domain,
+    const url::Origin& target_domain,
+    ResultCallback callback)
+    : lookalike_domain_(lookalike_domain),
+      target_domain_(target_domain),
+      callback_(std::move(callback)),
+      asset_link_handler_(
+          std::make_unique<digital_asset_links::DigitalAssetLinksHandler>(
+              profile->GetURLLoaderFactory())) {}
+
+DigitalAssetLinkCrossValidator::~DigitalAssetLinkCrossValidator() = default;
+
+void DigitalAssetLinkCrossValidator::Start() {
+  // Fetch and validate the manifest from the lookalike site.
+  asset_link_handler_->CheckDigitalAssetLinkRelationship(
+      lookalike_domain_.Serialize(), kDigitalAssetLinkRecordType, base::nullopt,
+      {{"namespace", "web"}, {"site", target_domain_.Serialize()}},
+      base::BindOnce(
+          &DigitalAssetLinkCrossValidator::OnFetchLookalikeManifestComplete,
+          base::Unretained(this)));
+}
+
+void DigitalAssetLinkCrossValidator::OnFetchLookalikeManifestComplete(
+    digital_asset_links::RelationshipCheckResult result) {
+  // Fail if the first manifest failed.
+  if (result != digital_asset_links::RelationshipCheckResult::kSuccess) {
+    std::move(callback_).Run(false);
+    return;
+  }
+  // Swap current and target domains and validate the new manifest.
+  asset_link_handler_->CheckDigitalAssetLinkRelationship(
+      target_domain_.Serialize(), kDigitalAssetLinkRecordType, base::nullopt,
+      {{"namespace", "web"}, {"site", lookalike_domain_.Serialize()}},
+      base::BindOnce(
+          &DigitalAssetLinkCrossValidator::OnFetchTargetManifestComplete,
+          base::Unretained(this)));
+}
+
+void DigitalAssetLinkCrossValidator::OnFetchTargetManifestComplete(
+    digital_asset_links::RelationshipCheckResult result) {
+  std::move(callback_).Run(
+      result == digital_asset_links::RelationshipCheckResult::kSuccess);
+}
diff --git a/chrome/browser/lookalikes/digital_asset_links_cross_validator.h b/chrome/browser/lookalikes/digital_asset_links_cross_validator.h
new file mode 100644
index 0000000..c3ec6ae
--- /dev/null
+++ b/chrome/browser/lookalikes/digital_asset_links_cross_validator.h
@@ -0,0 +1,49 @@
+// 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 CHROME_BROWSER_LOOKALIKES_DIGITAL_ASSET_LINKS_CROSS_VALIDATOR_H_
+#define CHROME_BROWSER_LOOKALIKES_DIGITAL_ASSET_LINKS_CROSS_VALIDATOR_H_
+
+#include "base/bind.h"
+#include "chrome/browser/installable/digital_asset_links/digital_asset_links_handler.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+class Profile;
+
+// Fetches and validates Digital Asset Links manifests from the lookalike and
+// target sites.
+// |lookalike_domain| is the lookalike domain, |target_domain| is the domain
+// that the lookalike domain matched to.
+// Runs |callback| with true if |lookalike_domain|'s manifest contains a
+// valid entry for |target_domain| AND |target_domain|'s manifest
+// contains a valid entry for |lookalike_domain|. Runs |callback| with false in
+// all other cases.
+class DigitalAssetLinkCrossValidator {
+ public:
+  using ResultCallback = base::OnceCallback<void(bool)>;
+
+  DigitalAssetLinkCrossValidator(Profile* profile,
+                                 const url::Origin& lookalike_domain,
+                                 const url::Origin& target_domain,
+                                 ResultCallback callback);
+  ~DigitalAssetLinkCrossValidator();
+
+  void Start();
+
+ private:
+  void OnFetchLookalikeManifestComplete(
+      digital_asset_links::RelationshipCheckResult result);
+  void OnFetchTargetManifestComplete(
+      digital_asset_links::RelationshipCheckResult result);
+
+  const url::Origin lookalike_domain_;
+  const url::Origin target_domain_;
+  ResultCallback callback_;
+
+  std::unique_ptr<digital_asset_links::DigitalAssetLinksHandler>
+      asset_link_handler_;
+};
+
+#endif  // CHROME_BROWSER_LOOKALIKES_DIGITAL_ASSET_LINKS_CROSS_VALIDATOR_H_
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc
index 56a982b5..6a82104 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc
@@ -13,6 +13,7 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
@@ -24,6 +25,7 @@
 #include "chrome/browser/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/reputation/reputation_service.h"
+#include "components/lookalikes/core/features.h"
 #include "components/lookalikes/core/lookalike_url_ui_util.h"
 #include "components/lookalikes/core/lookalike_url_util.h"
 #include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
@@ -123,7 +125,6 @@
                        weak_factory_.GetWeakPtr()));
     return content::NavigationThrottle::DEFER;
   }
-
   return PerformChecks(service->GetLatestEngagedSites());
 }
 
@@ -132,19 +133,19 @@
 }
 
 ThrottleCheckResult LookalikeUrlNavigationThrottle::ShowInterstitial(
-    const GURL& safe_url,
-    const GURL& url,
+    const GURL& safe_domain,
+    const GURL& lookalike_domain,
     ukm::SourceId source_id,
     LookalikeUrlMatchType match_type) {
   content::NavigationHandle* handle = navigation_handle();
   content::WebContents* web_contents = handle->GetWebContents();
 
   auto controller = std::make_unique<LookalikeUrlControllerClient>(
-      web_contents, url, safe_url);
+      web_contents, lookalike_domain, safe_domain);
 
   std::unique_ptr<LookalikeUrlBlockingPage> blocking_page(
       new LookalikeUrlBlockingPage(
-          web_contents, safe_url, url, source_id, match_type,
+          web_contents, safe_domain, lookalike_domain, source_id, match_type,
           handle->IsSignedExchangeInnerResponse(), std::move(controller)));
 
   base::Optional<std::string> error_page_contents =
@@ -161,8 +162,8 @@
   content::Referrer referrer(handle->GetReferrer().url,
                              handle->GetReferrer().policy);
   LookalikeUrlTabStorage::GetOrCreate(handle->GetWebContents())
-      ->OnLookalikeInterstitialShown(url, referrer, handle->GetRedirectChain());
-
+      ->OnLookalikeInterstitialShown(lookalike_domain, referrer,
+                                     handle->GetRedirectChain());
   return ThrottleCheckResult(content::NavigationThrottle::CANCEL,
                              net::ERR_BLOCKED_BY_CLIENT, error_page_contents);
 }
@@ -182,15 +183,68 @@
   return std::make_unique<LookalikeUrlNavigationThrottle>(navigation_handle);
 }
 
-void LookalikeUrlNavigationThrottle::PerformChecksDeferred(
-    const std::vector<DomainInfo>& engaged_sites) {
-  ThrottleCheckResult result = PerformChecks(engaged_sites);
+ThrottleCheckResult
+LookalikeUrlNavigationThrottle::CheckManifestsAndMaybeShowInterstitial(
+    const GURL& safe_domain,
+    const GURL& lookalike_domain,
+    ukm::SourceId source_id,
+    LookalikeUrlMatchType match_type) {
+  RecordUMAFromMatchType(match_type);
 
-  if (result.action() == content::NavigationThrottle::PROCEED) {
+  // Punycode interstitial doesn't have a target site, so safe_domain isn't
+  // valid.
+  if (!base::FeatureList::IsEnabled(
+          lookalikes::features::kLookalikeDigitalAssetLinks) ||
+      !safe_domain.is_valid()) {
+    return ShowInterstitial(safe_domain, lookalike_domain, source_id,
+                            match_type);
+  }
+
+  const url::Origin lookalike_origin =
+      url::Origin::Create(navigation_handle()->GetURL());
+  const url::Origin target_origin = url::Origin::Create(safe_domain);
+  DigitalAssetLinkCrossValidator::ResultCallback callback = base::BindOnce(
+      &LookalikeUrlNavigationThrottle::OnManifestValidationResult,
+      weak_factory_.GetWeakPtr(), safe_domain, lookalike_domain, source_id,
+      match_type);
+  DCHECK(!digital_asset_link_validator_);
+  // This assumes each navigation has its own throttle.
+  // TODO(crbug.com/1175385): Consider moving this to LookalikeURLService.
+  digital_asset_link_validator_ =
+      std::make_unique<DigitalAssetLinkCrossValidator>(
+          profile_, lookalike_origin, target_origin, std::move(callback));
+  digital_asset_link_validator_->Start();
+  return NavigationThrottle::DEFER;
+}
+
+void LookalikeUrlNavigationThrottle::OnManifestValidationResult(
+    const GURL& safe_domain,
+    const GURL& lookalike_domain,
+    ukm::SourceId source_id,
+    LookalikeUrlMatchType match_type,
+    bool validation_succeeded) {
+  if (validation_succeeded) {
     Resume();
     return;
   }
+  ThrottleCheckResult result =
+      ShowInterstitial(safe_domain, lookalike_domain, source_id, match_type);
+  CancelDeferredNavigation(result);
+}
 
+void LookalikeUrlNavigationThrottle::PerformChecksDeferred(
+    const std::vector<DomainInfo>& engaged_sites) {
+  ThrottleCheckResult result = PerformChecks(engaged_sites);
+  if (result.action() == NavigationThrottle::DEFER) {
+    // Already deferred by PerformChecks(), don't defer again. PerformChecks()
+    // is responsible for scheduling the cancellation/resumption of the
+    // navigation.
+    return;
+  }
+  if (result.action() == NavigationThrottle::PROCEED) {
+    Resume();
+    return;
+  }
   CancelDeferredNavigation(result);
 }
 
@@ -249,7 +303,7 @@
   }
 
   if (!first_is_lookalike && !last_is_lookalike) {
-    return content::NavigationThrottle::PROCEED;
+    return NavigationThrottle::PROCEED;
   }
   // IMPORTANT: Do not modify first_is_lookalike or last_is_lookalike beyond
   // this line. See crbug.com/1138138 for an example bug.
@@ -261,15 +315,13 @@
 
   if (first_is_lookalike &&
       ShouldBlockLookalikeUrlNavigation(first_match_type)) {
-    RecordUMAFromMatchType(first_match_type);
-    return ShowInterstitial(first_suggested_url, first_url, source_id,
-                            first_match_type);
+    return CheckManifestsAndMaybeShowInterstitial(
+        first_suggested_url, first_url, source_id, first_match_type);
   }
 
   if (last_is_lookalike && ShouldBlockLookalikeUrlNavigation(last_match_type)) {
-    RecordUMAFromMatchType(last_match_type);
-    return ShowInterstitial(last_suggested_url, last_url, source_id,
-                            last_match_type);
+    return CheckManifestsAndMaybeShowInterstitial(last_suggested_url, last_url,
+                                                  source_id, last_match_type);
   }
 
   RecordUMAFromMatchType(first_is_lookalike ? first_match_type
@@ -278,7 +330,7 @@
   RecordUkmForLookalikeUrlBlockingPage(
       source_id, first_is_lookalike ? first_match_type : last_match_type,
       LookalikeUrlBlockingPageUserAction::kInterstitialNotShown);
-  return content::NavigationThrottle::PROCEED;
+  return NavigationThrottle::PROCEED;
 }
 
 bool LookalikeUrlNavigationThrottle::IsLookalikeUrl(
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle.h b/chrome/browser/lookalikes/lookalike_url_navigation_throttle.h
index a3c85a1..0e743f7a 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle.h
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/lookalikes/digital_asset_links_cross_validator.h"
 #include "chrome/browser/lookalikes/lookalike_url_blocking_page.h"
 #include "components/site_engagement/core/mojom/site_engagement_details.mojom.h"
 #include "components/url_formatter/url_formatter.h"
@@ -38,6 +39,8 @@
 //
 // Remember to update //docs/idn.md with the appropriate information if you
 // modify the lookalike heuristics.
+//
+// This throttle assumes that two navigations never share the same throttle.
 class LookalikeUrlNavigationThrottle : public content::NavigationThrottle {
  public:
   explicit LookalikeUrlNavigationThrottle(content::NavigationHandle* handle);
@@ -57,10 +60,14 @@
  private:
   // Performs synchronous top domain and engaged site checks on the navigated
   // and redirected urls. Uses |engaged_sites| for the engaged site checks.
+  // This function can also defer the check and schedule a
+  // cancellation/resumption if additional checks need to be done such as
+  // validating Digital Asset Links manifests.
   ThrottleCheckResult PerformChecks(
       const std::vector<DomainInfo>& engaged_sites);
 
-  // A void-returning variant, only used with deferred throttle results.
+  // A void-returning variant, only used with deferred throttle results (e.g.
+  // when we need to fetch engaged sites list or digital asset link manifests).
   void PerformChecksDeferred(const std::vector<DomainInfo>& engaged_sites);
 
   // Returns whether |url| is a lookalike, setting |match_type| and
@@ -70,13 +77,40 @@
                       LookalikeUrlMatchType* match_type,
                       GURL* suggested_url);
 
+  // Shows a full page interstitial. |safe_domain| is the domain suggested as
+  // safe by the interstitial. |lookalike_domain| is the domain that triggered
+  // the warning.
+  // This function can display two types of interstitials depending on the
+  // value of |safe_domain|:
+  // - If |safe_domain| is a valid URL, it displays a lookalike interstitial
+  // that suggests the user to go to |safe_domain| instead.
+  // - Otherwise, it displays the punycode interstitial which doesn't suggest a
+  // safe URL.
   ThrottleCheckResult ShowInterstitial(const GURL& safe_domain,
-                                       const GURL& url,
+                                       const GURL& lookalike_domain,
                                        ukm::SourceId source_id,
                                        LookalikeUrlMatchType match_type);
 
+  // Checks digital asset links of |lookalike_domain| and |safe_domain| and
+  // shows a full page interstitial if either manifest validation fails.
+  ThrottleCheckResult CheckManifestsAndMaybeShowInterstitial(
+      const GURL& safe_domain,
+      const GURL& lookalike_domain,
+      ukm::SourceId source_id,
+      LookalikeUrlMatchType match_type);
+
+  // Callback for digital asset link manifest validations.
+  void OnManifestValidationResult(const GURL& safe_domain,
+                                  const GURL& lookalike_domain,
+                                  ukm::SourceId source_id,
+                                  LookalikeUrlMatchType match_type,
+                                  bool validation_success);
+
   Profile* profile_;
   bool use_test_profile_ = false;
+
+  std::unique_ptr<DigitalAssetLinkCrossValidator> digital_asset_link_validator_;
+
   base::WeakPtrFactory<LookalikeUrlNavigationThrottle> weak_factory_{this};
 };
 
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
index a5b60d3..fba6d0f 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
@@ -41,6 +41,7 @@
 #include "content/public/test/content_mock_cert_verifier.h"
 #include "content/public/test/signed_exchange_browser_test_helper.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/url_loader_interceptor.h"
 #include "net/cert/mock_cert_verifier.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/cert_test_util.h"
@@ -193,7 +194,7 @@
 
 class LookalikeUrlNavigationThrottleBrowserTest
     : public InProcessBrowserTest,
-      public testing::WithParamInterface<std::tuple<bool, bool>> {
+      public testing::WithParamInterface<std::tuple<bool, bool, bool>> {
  protected:
   void SetUp() override {
     std::vector<base::test::ScopedFeatureList::FeatureAndParams>
@@ -218,6 +219,15 @@
       disabled_features.push_back(
           lookalikes::features::kLookalikeInterstitialForPunycode);
     }
+
+    if (digital_asset_links_enabled()) {
+      enabled_features.emplace_back(
+          lookalikes::features::kLookalikeDigitalAssetLinks,
+          base::FieldTrialParams());
+    } else {
+      disabled_features.push_back(
+          lookalikes::features::kLookalikeDigitalAssetLinks);
+    }
     feature_list_.InitWithFeaturesAndParameters(enabled_features,
                                                 disabled_features);
     reputation::InitializeSafetyTipConfig();
@@ -226,6 +236,7 @@
 
   bool target_embedding_enabled() const { return std::get<0>(GetParam()); }
   bool punycode_interstitial_enabled() const { return std::get<1>(GetParam()); }
+  bool digital_asset_links_enabled() const { return std::get<2>(GetParam()); }
 
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
@@ -419,9 +430,12 @@
   base::SimpleTestClock test_clock_;
 };
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         LookalikeUrlNavigationThrottleBrowserTest,
-                         testing::Combine(testing::Bool(), testing::Bool()));
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    LookalikeUrlNavigationThrottleBrowserTest,
+    testing::Combine(testing::Bool() /* target_embedding_enabled */,
+                     testing::Bool() /* punycode_interstitial_enabled */,
+                     testing::Bool() /* digital_asset_links_enabled */));
 
 // Navigating to a non-IDN shouldn't show an interstitial or record metrics.
 IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
@@ -1465,7 +1479,8 @@
     All,
     LookalikeUrlNavigationThrottleSignedExchangeBrowserTest,
     testing::Combine(testing::Bool() /* target_embedding_enabled */,
-                     testing::Bool() /* punycode_interstitial_enabled */));
+                     testing::Bool() /* punycode_interstitial_enabled */,
+                     testing::Bool() /* digital_asset_links_enabled */));
 
 // Navigates to a 127.0.0.1 URL that serves a signed exchange for
 // google-com.example.org. This navigation should be blocked by the target
@@ -1588,3 +1603,191 @@
 // TODO(meacer): Add a test for a failed SGX response. It should be treated
 // as a normal redirect. In fact, InnerAndOuterUrlsLookalikes_ShouldBlock
 // is actually testing this right now, fix it.
+
+// Tests for Digital Asset Links.
+class LookalikeUrlNavigationThrottleDigitalAssetLinksBrowserTest
+    : public LookalikeUrlNavigationThrottleBrowserTest {
+ public:
+  struct TestSite {
+    std::string hostname;
+    std::string manifest;
+  };
+
+  // Sets up the site |lookalike_hostname| to serve a manifest with contents
+  // |lookalike_manifest|, and the site |target_hostname| to serve a manifest
+  // with contents |target_manifest| and navigates to |lookalike_hostname|.
+  // Expects an interstitial with the suggested hostname
+  // |expected_suggested_hostname|.
+  void TestExpectInterstitial(const char* lookalike_hostname,
+                              const std::string& lookalike_manifest,
+                              const char* target_hostname,
+                              const std::string& target_manifest,
+                              const char* expected_suggested_hostname) {
+    const GURL kNavigatedUrl = MakeURL(lookalike_hostname);
+    const std::vector<TestSite> sites{
+        {lookalike_hostname, lookalike_manifest},
+        {target_hostname, target_manifest},
+    };
+    SetUpManifests(sites);
+
+    TestMetricsRecordedAndInterstitialShown(
+        browser(), kNavigatedUrl, MakeURL(expected_suggested_hostname),
+        NavigationSuggestionEvent::kMatchSkeletonTop500);
+    CheckUkm({kNavigatedUrl}, "MatchType",
+             LookalikeUrlMatchType::kSkeletonMatchTop500);
+  }
+
+  // Sets up the site |lookalike_hostname| to serve a manifest with contents
+  // |lookalike_manifest|, and the site |target_hostname| to serve a manifest
+  // with contents |target_manifest| and navigates to |lookalike_hostname|.
+  // Expects no interstitial.
+  void TestNoInterstitial(const char* lookalike_hostname,
+                          const std::string& lookalike_manifest,
+                          const char* target_hostname,
+                          const std::string& target_manifest) {
+    const std::vector<TestSite> sites{
+        {lookalike_hostname, lookalike_manifest},
+        {target_hostname, target_manifest},
+    };
+    SetUpManifests(sites);
+
+    TestInterstitialNotShown(browser(), MakeURL(lookalike_hostname));
+    CheckNoUkm();
+  }
+
+  void SetUpManifests(const std::vector<TestSite>& sites) {
+    url_loader_interceptor_ =
+        std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
+            &LookalikeUrlNavigationThrottleDigitalAssetLinksBrowserTest::
+                OnIntercept,
+            base::Unretained(this), sites));
+  }
+
+  void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
+
+  bool OnIntercept(const std::vector<TestSite>& sites,
+                   content::URLLoaderInterceptor::RequestParams* params) {
+    for (const TestSite& site : sites) {
+      if (params->url_request.url == MakeManifestURL(site.hostname)) {
+        if (!site.manifest.empty()) {
+          // Serve manifest contents:
+          std::string headers =
+              "HTTP/1.1 200 OK\nContent-Type: application/json; "
+              "charset=utf-8\n";
+          content::URLLoaderInterceptor::WriteResponse(headers, site.manifest,
+                                                       params->client.get());
+        } else {
+          // Serve error:
+          params->client->OnComplete(
+              network::URLLoaderCompletionStatus(net::ERR_CONNECTION_RESET));
+        }
+        return true;
+      }
+      if (params->url_request.url == MakeURL(site.hostname)) {
+        content::URLLoaderInterceptor::WriteResponse(
+            "HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\n",
+            "<html>Test page</html>", params->client.get());
+        return true;
+      }
+    }
+    return false;
+  }
+
+  static GURL MakeManifestURL(const std::string& hostname) {
+    return GURL("https://" + hostname + "/.well-known/assetlinks.json");
+  }
+
+  static GURL MakeURL(const std::string& hostname) {
+    return GURL("https://" + hostname);
+  }
+
+  static std::string MakeManifestWithTarget(const char* target_domain,
+                                            bool invalid = false) {
+    const char* const format = R"([{
+        "relation": ["%s"],
+        "target": {
+          "namespace": "web",
+          "site": "https://%s"
+        }
+      }]
+      )";
+    // Go through MakeURL to convert target_domain to punycode.
+    return base::StringPrintf(format,
+                              (invalid ? "junkvalue" : "lookalikes/allowlist"),
+                              MakeURL(target_domain).host().c_str());
+  }
+
+ private:
+  std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    LookalikeUrlNavigationThrottleDigitalAssetLinksBrowserTest,
+    testing::Combine(testing::Bool() /* target_embedding_enabled */,
+                     testing::Bool() /* punycode_interstitial_enabled */,
+                     testing::Values(true) /* digital_asset_links_enabled */));
+
+// Neither site serves a manifest.
+IN_PROC_BROWSER_TEST_P(
+    LookalikeUrlNavigationThrottleDigitalAssetLinksBrowserTest,
+    NoAssetLinks_ShowInterstitial) {
+  TestExpectInterstitial("googlé.com", std::string(), "google.com",
+                         std::string(),
+                         /*expected_suggested_hostname=*/"google.com");
+}
+
+// Both lookalike and target sites serve valid asset link manifests pointing to
+// each other.
+IN_PROC_BROWSER_TEST_P(
+    LookalikeUrlNavigationThrottleDigitalAssetLinksBrowserTest,
+    ValidAssetLinks_IgnoreInterstitial) {
+  TestNoInterstitial("googlé.com", MakeManifestWithTarget("google.com"),
+                     "google.com", MakeManifestWithTarget("googlé.com"));
+}
+
+// Both lookalike and target sites serve asset links. Lookalike site's manifest
+// has an entry for the target, but the target site's manifest doesn't have one
+// for the lookalike.
+IN_PROC_BROWSER_TEST_P(
+    LookalikeUrlNavigationThrottleDigitalAssetLinksBrowserTest,
+    ValidAssetLinks_TargetManifestNotMatching_IgnoreInterstitial) {
+  TestExpectInterstitial("góógle.com", MakeManifestWithTarget("google.com"),
+                         "google.com", MakeManifestWithTarget("site.test"),
+                         /*expected_suggested_hostname=*/"google.com");
+}
+
+// Both lookalike and target sites serve asset links. Lookalike site's manifest
+// has an entry for the target, but the target site's manifest doesn't have
+// valid content.
+IN_PROC_BROWSER_TEST_P(
+    LookalikeUrlNavigationThrottleDigitalAssetLinksBrowserTest,
+    ValidAssetLinks_TargetManifestInvalid_IgnoreInterstitial) {
+  TestExpectInterstitial("góógle.com", MakeManifestWithTarget("google.com"),
+                         "google.com",
+                         MakeManifestWithTarget("góógle.com", /*invalid=*/true),
+                         /*expected_suggested_hostname=*/"google.com");
+}
+
+// Both lookalike and target sites are subdomains and serve valid asset links.
+// However, lookalike heuristics match to eTLD+1s, so the lookalike's manifest
+// will fail to validate because it points to the full URL instead.
+// TODO(meacer): Support this use case.
+IN_PROC_BROWSER_TEST_P(
+    LookalikeUrlNavigationThrottleDigitalAssetLinksBrowserTest,
+    ValidSubdomainAssetLinks_NoMatch_ShowInterstitial) {
+  TestExpectInterstitial(
+      "docs.góógle.com", MakeManifestWithTarget("docs.google.com"),
+      "docs.google.com", MakeManifestWithTarget("docs.góógle.com"),
+      /*expected_suggested_hostname=*/"google.com");
+}
+
+// Both lookalike and target sites are subdomains and serve valid asset links.
+// This time, the lookalike's manifest points to the eTLD+1 of the matching
+// domain so the validation is successful.
+IN_PROC_BROWSER_TEST_P(
+    LookalikeUrlNavigationThrottleDigitalAssetLinksBrowserTest,
+    ValidSubdomainAssetLinks_IgnoreInterstitial) {
+  TestNoInterstitial("docs.góógle.com", MakeManifestWithTarget("google.com"),
+                     "google.com", MakeManifestWithTarget("docs.góógle.com"));
+}
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc
index 94ddb42..406b970 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/lookalikes/lookalike_url_navigation_throttle.h"
 
+#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
diff --git a/chrome/browser/notifications/alert_dispatcher_mojo.mm b/chrome/browser/notifications/alert_dispatcher_mojo.mm
index ebf6aeb..296c4a8 100644
--- a/chrome/browser/notifications/alert_dispatcher_mojo.mm
+++ b/chrome/browser/notifications/alert_dispatcher_mojo.mm
@@ -24,7 +24,9 @@
 - (void)closeNotificationWithId:(NSString*)notificationId
                       profileId:(NSString*)profileId
                       incognito:(BOOL)incognito {
-  // TODO(knollr): Implement.
+  [[self serviceProxy] closeNotificationWithId:notificationId
+                                     profileId:profileId
+                                     incognito:incognito];
 }
 
 - (void)closeAllNotifications {
diff --git a/chrome/browser/notifications/notification_alert_service_bridge.mm b/chrome/browser/notifications/notification_alert_service_bridge.mm
index 5248693..b7803496 100644
--- a/chrome/browser/notifications/notification_alert_service_bridge.mm
+++ b/chrome/browser/notifications/notification_alert_service_bridge.mm
@@ -96,7 +96,13 @@
 - (void)closeNotificationWithId:(NSString*)notificationId
                       profileId:(NSString*)profileId
                       incognito:(BOOL)incognito {
-  // TODO(knollr): implement.
+  auto profileIdentifier = notifications::mojom::ProfileIdentifier::New(
+      base::SysNSStringToUTF8(profileId), incognito);
+  auto notificationIdentifier =
+      notifications::mojom::NotificationIdentifier::New(
+          base::SysNSStringToUTF8(notificationId),
+          std::move(profileIdentifier));
+  _service->CloseNotification(std::move(notificationIdentifier));
 }
 
 - (void)closeAllNotifications {
diff --git a/chrome/browser/notifications/notification_alert_service_bridge_unittest.mm b/chrome/browser/notifications/notification_alert_service_bridge_unittest.mm
index a8ca3cf9..b7234cd 100644
--- a/chrome/browser/notifications/notification_alert_service_bridge_unittest.mm
+++ b/chrome/browser/notifications/notification_alert_service_bridge_unittest.mm
@@ -20,6 +20,10 @@
 class MockNotificationService
     : public notifications::mojom::MacNotificationService {
  public:
+  MOCK_METHOD(void,
+              CloseNotification,
+              (notifications::mojom::NotificationIdentifierPtr),
+              (override));
   MOCK_METHOD(void, CloseAllNotifications, (), (override));
 };
 
@@ -81,6 +85,24 @@
   run_loop.Run();
 }
 
+TEST_F(NotificationAlertServiceBridgeTest, CloseNotification) {
+  base::RunLoop run_loop;
+  EXPECT_CALL(mock_service_, CloseNotification)
+      .WillOnce(
+          [&](notifications::mojom::NotificationIdentifierPtr identifier) {
+            ASSERT_TRUE(identifier);
+            EXPECT_EQ("notificationId", identifier->id);
+            ASSERT_TRUE(identifier->profile);
+            EXPECT_EQ("profileId", identifier->profile->id);
+            EXPECT_TRUE(identifier->profile->incognito);
+            run_loop.Quit();
+          });
+  [bridge_ closeNotificationWithId:@"notificationId"
+                         profileId:@"profileId"
+                         incognito:YES];
+  run_loop.Run();
+}
+
 TEST_F(NotificationAlertServiceBridgeTest, CloseAllNotifications) {
   base::RunLoop run_loop;
   EXPECT_CALL(mock_service_, CloseAllNotifications).WillOnce([&]() {
diff --git a/chrome/browser/policy/chrome_browser_policy_connector.cc b/chrome/browser/policy/chrome_browser_policy_connector.cc
index 7770ca2..fd2085d 100644
--- a/chrome/browser/policy/chrome_browser_policy_connector.cc
+++ b/chrome/browser/policy/chrome_browser_policy_connector.cc
@@ -99,6 +99,11 @@
   device_management_service->ScheduleInitialization(
       kServiceInitializationStartupDelay);
 
+#if defined(OS_ANDROID)
+  pollicy_cache_updater_ = std::make_unique<android::PolicyCacheUpdater>(
+      GetPolicyService(), GetHandlerList());
+#endif
+
   InitInternal(local_state, std::move(device_management_service));
 }
 
diff --git a/chrome/browser/policy/chrome_browser_policy_connector.h b/chrome/browser/policy/chrome_browser_policy_connector.h
index 2e669eb..e5683cee 100644
--- a/chrome/browser/policy/chrome_browser_policy_connector.h
+++ b/chrome/browser/policy/chrome_browser_policy_connector.h
@@ -16,6 +16,10 @@
 #include "build/chromeos_buildflags.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
 
+#if defined(OS_ANDROID)
+#include "components/policy/core/browser/android/policy_cache_updater_android.h"
+#endif
+
 class PrefService;
 
 namespace policy {
@@ -95,6 +99,10 @@
 
   ConfigurationPolicyProvider* command_line_provider_ = nullptr;
 
+#if defined(OS_ANDROID)
+  std::unique_ptr<android::PolicyCacheUpdater> pollicy_cache_updater_;
+#endif
+
   DISALLOW_COPY_AND_ASSIGN(ChromeBrowserPolicyConnector);
 };
 
diff --git a/chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.cc b/chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.cc
index 26dde53e..567b7a4 100644
--- a/chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.cc
+++ b/chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.cc
@@ -154,9 +154,6 @@
           origin),
       expected_final_status_(expected_final_status),
       observer_(this),
-      new_render_view_host_(nullptr),
-      was_hidden_(false),
-      was_shown_(false),
       should_be_shown_(expected_final_status == FINAL_STATUS_USED),
       skip_final_checks_(ignore_final_status) {}
 
@@ -173,7 +170,7 @@
   // WebContents, at the end of a navigation caused by a call to
   // NavigateToURLImpl().
   if (final_status() == FINAL_STATUS_USED)
-    EXPECT_TRUE(new_render_view_host_);
+    EXPECT_TRUE(new_main_frame_);
 
   EXPECT_EQ(should_be_shown_, was_shown_);
 }
@@ -186,28 +183,27 @@
   return true;
 }
 
-void TestNoStatePrefetchContents::OnRenderViewHostCreated(
-    RenderViewHost* new_render_view_host) {
-  // Used to make sure the RenderViewHost is hidden and, if used,
-  // subsequently shown.
-  observer_.Add(new_render_view_host->GetWidget());
+void TestNoStatePrefetchContents::RenderFrameCreated(
+    content::RenderFrameHost* frame_host) {
+  if (!frame_host->GetParent()) {
+    // Used to make sure the main frame widget is hidden and, if used,
+    // subsequently shown.
+    observer_.Add(frame_host->GetRenderWidgetHost());
+    new_main_frame_ = frame_host;
+  }
 
-  new_render_view_host_ = new_render_view_host;
-
-  NoStatePrefetchContents::OnRenderViewHostCreated(new_render_view_host);
+  NoStatePrefetchContents::RenderFrameCreated(frame_host);
 }
 
 void TestNoStatePrefetchContents::RenderWidgetHostVisibilityChanged(
     content::RenderWidgetHost* widget_host,
     bool became_visible) {
-  EXPECT_EQ(new_render_view_host_->GetWidget(), widget_host);
+  EXPECT_EQ(new_main_frame_->GetRenderWidgetHost(), widget_host);
 
-  if (!became_visible) {
-    was_hidden_ = true;
-  } else {
-    // A prerendered RenderViewHost should only be shown after being removed
+  if (became_visible) {
+    // A prerendered main frame should only be shown after being removed
     // from the NoStatePrefetchContents for display.
-    EXPECT_FALSE(GetRenderViewHost());
+    EXPECT_FALSE(GetMainFrame());
     was_shown_ = true;
   }
 }
diff --git a/chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.h b/chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.h
index c2083c6..2d4c750e 100644
--- a/chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.h
+++ b/chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.h
@@ -109,8 +109,10 @@
   FinalStatus expected_final_status() const { return expected_final_status_; }
 
  private:
-  void OnRenderViewHostCreated(
-      content::RenderViewHost* new_render_view_host) override;
+  // WebContentsObserver overrides.
+  void RenderFrameCreated(content::RenderFrameHost* frame_host) override;
+
+  // RenderWidgetHostObserver overrides.
   void RenderWidgetHostVisibilityChanged(content::RenderWidgetHost* widget_host,
                                          bool became_visible) override;
   void RenderWidgetHostDestroyed(
@@ -120,13 +122,11 @@
 
   ScopedObserver<content::RenderWidgetHost, content::RenderWidgetHostObserver>
       observer_;
-  // The RenderViewHost created for the prerender, if any.
-  content::RenderViewHost* new_render_view_host_;
-  // Set to true when the prerendering RenderWidget is hidden.
-  bool was_hidden_;
+  // The main frame created for the prerender, if any.
+  content::RenderFrameHost* new_main_frame_ = nullptr;
   // Set to true when the prerendering RenderWidget is shown, after having been
   // hidden.
-  bool was_shown_;
+  bool was_shown_ = false;
   // Expected final value of was_shown_.  Defaults to true for
   // FINAL_STATUS_USED, and false otherwise.
   bool should_be_shown_;
diff --git a/chrome/browser/privacy_sandbox/android/java/res/xml/privacy_sandbox_preferences.xml b/chrome/browser/privacy_sandbox/android/java/res/xml/privacy_sandbox_preferences.xml
index d650ca1..0152578 100644
--- a/chrome/browser/privacy_sandbox/android/java/res/xml/privacy_sandbox_preferences.xml
+++ b/chrome/browser/privacy_sandbox/android/java/res/xml/privacy_sandbox_preferences.xml
@@ -6,15 +6,17 @@
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
 
-    <org.chromium.components.browser_ui.settings.TextMessagePreference
-        android:summary="@string/privacy_sandbox_description"
+    <org.chromium.components.browser_ui.settings.ChromeBasePreference
+        android:selectable="false"
+        app:layout="@layout/privacy_sandbox_header"
         app:allowDividerBelow="false" />
 
-    <PreferenceCategory
-        android:title="@string/privacy_sandbox_trial_title"/>
-
     <org.chromium.components.browser_ui.settings.TextMessagePreference
-        android:key="privacy_sandbox_trial_description"
+        android:title="@string/privacy_sandbox_description_title"
+        app:allowDividerBelow="false" />
+
+    <org.chromium.components.browser_ui.settings.LongSummaryTextMessagePreference
+        android:summary="@string/privacy_sandbox_description"
         app:allowDividerBelow="false" />
 
     <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
@@ -23,8 +25,8 @@
         android:persistent="false"
         app:allowDividerBelow="false" />
 
-    <org.chromium.components.browser_ui.settings.TextMessagePreference
-        android:summary="@string/privacy_sandbox_toggle_description"
+    <org.chromium.components.browser_ui.settings.LongSummaryTextMessagePreference
+        android:key="privacy_sandbox_toggle_description"
         app:allowDividerBelow="false" />
 
 </PreferenceScreen>
diff --git a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSettingsFragment.java b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSettingsFragment.java
index ceca5a3..2225c44 100644
--- a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSettingsFragment.java
+++ b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSettingsFragment.java
@@ -6,10 +6,6 @@
 
 import android.content.Context;
 import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
 
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
@@ -27,7 +23,7 @@
  */
 public class PrivacySandboxSettingsFragment
         extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener {
-    public static final String TRIAL_DESCRIPTION_PREFERENCE = "privacy_sandbox_trial_description";
+    public static final String TOGGLE_DESCRIPTION_PREFERENCE = "privacy_sandbox_toggle_description";
     public static final String TOGGLE_PREFERENCE = "privacy_sandbox_toggle";
 
     public static CharSequence getStatusString(Context context) {
@@ -44,10 +40,10 @@
         // Add all preferences and set the title.
         getActivity().setTitle(R.string.prefs_privacy_sandbox);
         SettingsUtils.addPreferencesFromResource(this, R.xml.privacy_sandbox_preferences);
-        // Format the trial description, which has bullet points.
-        findPreference(TRIAL_DESCRIPTION_PREFERENCE)
+        // Format the toggle description, which has bullet points.
+        findPreference(TOGGLE_DESCRIPTION_PREFERENCE)
                 .setSummary(SpanApplier.applySpans(
-                        getContext().getString(R.string.privacy_sandbox_trial_description),
+                        getContext().getString(R.string.privacy_sandbox_toggle_description),
                         new SpanInfo("<li1>", "</li1>", new ChromeBulletSpan(getContext())),
                         new SpanInfo("<li2>", "</li2>", new ChromeBulletSpan(getContext()))));
 
@@ -69,18 +65,6 @@
         return true;
     }
 
-    @Override
-    public View onCreateView(
-            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        LinearLayout view =
-                (LinearLayout) super.onCreateView(inflater, container, savedInstanceState);
-        LinearLayout headerView =
-                (LinearLayout) inflater.inflate(R.layout.privacy_sandbox_header, view, false);
-        // Add the header view to the top.
-        view.addView(headerView, 0);
-        return view;
-    }
-
     private ChromeManagedPreferenceDelegate createManagedPreferenceDelegate() {
         return preference -> {
             if (!TOGGLE_PREFERENCE.equals(preference.getKey())) return false;
diff --git a/chrome/browser/resources/about_nacl/BUILD.gn b/chrome/browser/resources/about_nacl/BUILD.gn
index 67a1b9e7b..2f5c8f90 100644
--- a/chrome/browser/resources/about_nacl/BUILD.gn
+++ b/chrome/browser/resources/about_nacl/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":about_nacl" ]
 }
 
diff --git a/chrome/browser/resources/accessibility/BUILD.gn b/chrome/browser/resources/accessibility/BUILD.gn
index e4e5f1e..f11c750 100644
--- a/chrome/browser/resources/accessibility/BUILD.gn
+++ b/chrome/browser/resources/accessibility/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":accessibility" ]
 }
 
diff --git a/chrome/browser/resources/bluetooth_internals/BUILD.gn b/chrome/browser/resources/bluetooth_internals/BUILD.gn
index 6e6f22a..26a9f6d 100644
--- a/chrome/browser/resources/bluetooth_internals/BUILD.gn
+++ b/chrome/browser/resources/bluetooth_internals/BUILD.gn
@@ -7,7 +7,6 @@
 import("//ui/webui/resources/tools/generate_grd.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [
     ":adapter_broker",
     ":adapter_page",
diff --git a/chrome/browser/resources/browser_switch/internals/BUILD.gn b/chrome/browser/resources/browser_switch/internals/BUILD.gn
index 570cc867..321fd857 100644
--- a/chrome/browser/resources/browser_switch/internals/BUILD.gn
+++ b/chrome/browser/resources/browser_switch/internals/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":browser_switch_internals" ]
 }
 
diff --git a/chrome/browser/resources/certificate_viewer/BUILD.gn b/chrome/browser/resources/certificate_viewer/BUILD.gn
index 75f6c32..91d49fa 100644
--- a/chrome/browser/resources/certificate_viewer/BUILD.gn
+++ b/chrome/browser/resources/certificate_viewer/BUILD.gn
@@ -6,7 +6,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":certificate_viewer" ]
 }
 
diff --git a/chrome/browser/resources/chromeos/login/active_directory_password_change.css b/chrome/browser/resources/chromeos/login/active_directory_password_change.css
deleted file mode 100644
index 90fe9a97..0000000
--- a/chrome/browser/resources/chromeos/login/active_directory_password_change.css
+++ /dev/null
@@ -1,34 +0,0 @@
-/* Copyright 2015 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. */
-
-:host {
-  height: 528px; /* Should be the same as #gaia-signin. */
-  width: 448px; /* Should be the same as #gaia-signin. */
-}
-
-:host {
-  bottom: 0;
-  display: block;
-  left: 0;
-  position: absolute;
-  right: 0;
-  top: 0;
-}
-
-iron-icon[icon='cr:warning'] {
-  color: var(--google-yellow-500);
-  margin-bottom: 0;
-  margin-inline-end: 15px;
-  margin-inline-start: 0;
-  margin-top: 0;
-}
-
-#navigation {
-  color: white;
-  left: 0;
-  position: absolute;
-  right: 0;
-  top: 0;
-  z-index: 1;
-}
diff --git a/chrome/browser/resources/chromeos/login/active_directory_password_change.html b/chrome/browser/resources/chromeos/login/active_directory_password_change.html
index a8ce639..b4a9617 100644
--- a/chrome/browser/resources/chromeos/login/active_directory_password_change.html
+++ b/chrome/browser/resources/chromeos/login/active_directory_password_change.html
@@ -26,8 +26,45 @@
 -->
 <dom-module id="active-directory-password-change-element">
   <template>
-    <style include="oobe-common"></style>
-    <link rel="stylesheet" href="active_directory_password_change.css">
+    <style include="oobe-common">
+      :host-context(html:not([new-layout])) :host {
+        bottom: 0;
+        display: block;
+        height: 528px; /* Should be the same as #gaia-signin. */
+        left: 0;
+        position: absolute;
+        right: 0;
+        top: 0;
+        width: 448px; /* Should be the same as #gaia-signin. */
+      }
+
+      :host-context([orientation=horizontal]) cr-input {
+        padding-inline-end: 40px;
+        padding-inline-start: 40px;
+      }
+
+      :host-context([orientation=horizontal]) #welcomeMessage {
+        padding-inline-end: 40px;
+        padding-inline-start: 40px;
+      }
+
+      iron-icon[icon='cr:warning'] {
+        color: var(--google-yellow-500);
+        margin-bottom: 0;
+        margin-inline-end: 15px;
+        margin-inline-start: 0;
+        margin-top: 0;
+      }
+
+      #navigation {
+        color: white;
+        left: 0;
+        position: absolute;
+        right: 0;
+        top: 0;
+        z-index: 1;
+      }
+    </style>
     <link rel="stylesheet" href="gaia_card_parameters.css">
     <neon-animated-pages id="animatedPages" class="fit"
         entry-animation="fade-in-animation" exit-animation="fade-out-animation"
diff --git a/chrome/browser/resources/components/BUILD.gn b/chrome/browser/resources/components/BUILD.gn
index 5f65e31..8b0d1fea 100644
--- a/chrome/browser/resources/components/BUILD.gn
+++ b/chrome/browser/resources/components/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":components" ]
 }
 
diff --git a/chrome/browser/resources/conflicts/BUILD.gn b/chrome/browser/resources/conflicts/BUILD.gn
index a993915..0f12e84 100644
--- a/chrome/browser/resources/conflicts/BUILD.gn
+++ b/chrome/browser/resources/conflicts/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":about_conflicts" ]
 }
 
diff --git a/chrome/browser/resources/domain_reliability_internals/BUILD.gn b/chrome/browser/resources/domain_reliability_internals/BUILD.gn
index bbe7b4e9..85ec78d 100644
--- a/chrome/browser/resources/domain_reliability_internals/BUILD.gn
+++ b/chrome/browser/resources/domain_reliability_internals/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":domain_reliability_internals" ]
 }
 
diff --git a/chrome/browser/resources/download_internals/BUILD.gn b/chrome/browser/resources/download_internals/BUILD.gn
index 52b8603a..a48e5274 100644
--- a/chrome/browser/resources/download_internals/BUILD.gn
+++ b/chrome/browser/resources/download_internals/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [
     ":download_internals",
     ":download_internals_browser_proxy",
diff --git a/chrome/browser/resources/engagement/BUILD.gn b/chrome/browser/resources/engagement/BUILD.gn
index cf79ba37..af05632 100644
--- a/chrome/browser/resources/engagement/BUILD.gn
+++ b/chrome/browser/resources/engagement/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   closure_flags = default_closure_args + mojom_js_args + [
                     "js_module_root=" + rebase_path(".", root_build_dir),
                     "js_module_root=" + rebase_path(
diff --git a/chrome/browser/resources/gaia_auth_host/BUILD.gn b/chrome/browser/resources/gaia_auth_host/BUILD.gn
index e1470d6..624d610 100644
--- a/chrome/browser/resources/gaia_auth_host/BUILD.gn
+++ b/chrome/browser/resources/gaia_auth_host/BUILD.gn
@@ -76,7 +76,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   closure_flags =
       default_closure_args + [
         "js_module_root=../../chrome/browser/resources/gaia_auth_host/",
diff --git a/chrome/browser/resources/internals/notifications/BUILD.gn b/chrome/browser/resources/internals/notifications/BUILD.gn
index 99a6c91..6d229985 100644
--- a/chrome/browser/resources/internals/notifications/BUILD.gn
+++ b/chrome/browser/resources/internals/notifications/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [
     ":notifications_internals",
     ":notifications_internals_browser_proxy",
diff --git a/chrome/browser/resources/internals/query_tiles/BUILD.gn b/chrome/browser/resources/internals/query_tiles/BUILD.gn
index 6d3f2cb3..7c82b1e 100644
--- a/chrome/browser/resources/internals/query_tiles/BUILD.gn
+++ b/chrome/browser/resources/internals/query_tiles/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [
     ":query_tiles_internals",
     ":query_tiles_internals_browser_proxy",
diff --git a/chrome/browser/resources/local_state/BUILD.gn b/chrome/browser/resources/local_state/BUILD.gn
index 47ea708c..996b02bd 100644
--- a/chrome/browser/resources/local_state/BUILD.gn
+++ b/chrome/browser/resources/local_state/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":local_state" ]
 }
 
diff --git a/chrome/browser/resources/media/BUILD.gn b/chrome/browser/resources/media/BUILD.gn
index e743b970..e8751110 100644
--- a/chrome/browser/resources/media/BUILD.gn
+++ b/chrome/browser/resources/media/BUILD.gn
@@ -34,7 +34,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [
     ":media_data_table",
     ":media_engagement",
diff --git a/chrome/browser/resources/media_router/BUILD.gn b/chrome/browser/resources/media_router/BUILD.gn
index 6b913e1..74c70532 100644
--- a/chrome/browser/resources/media_router/BUILD.gn
+++ b/chrome/browser/resources/media_router/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":media_router_internals" ]
 }
 
diff --git a/chrome/browser/resources/memory_internals/BUILD.gn b/chrome/browser/resources/memory_internals/BUILD.gn
index 59c5fc88..6ab9dbc 100644
--- a/chrome/browser/resources/memory_internals/BUILD.gn
+++ b/chrome/browser/resources/memory_internals/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":memory_internals" ]
 }
 
diff --git a/chrome/browser/resources/new_tab_page/modules/cart/module.html b/chrome/browser/resources/new_tab_page/modules/cart/module.html
index 061adc3..4aded240 100644
--- a/chrome/browser/resources/new_tab_page/modules/cart/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/cart/module.html
@@ -6,16 +6,16 @@
     width: 100%;
   }
 
+  :host(:hover) .side-scroll-button {
+    visibility: visible;
+  }
+
   #moduleContent {
     display: flex;
     height: 100%;
     position: relative;
   }
 
-  #moduleContent:hover .side-scroll-button {
-    visibility: visible;
-  }
-
   #cartCarousel {
     display: inline-block;
     overflow-x: hidden;
@@ -28,12 +28,12 @@
     border-radius: 4px;
     display: inline-flex;
     flex-direction: column;
-    height: 160px;
+    height: 140px;
     margin: 0 4px;
     outline: none;
     position: relative;
     text-decoration: none;
-    width: 120px;
+    width: 118px;
   }
 
   .cart-item:hover cr-icon-button {
@@ -47,12 +47,14 @@
 
   .cart-title {
     color: var(--cr-primary-text-color);
-    display: block;
-    font-size: 12px;
+    display: flex;
+    flex-direction: row;
+    font-size: 13px;
     height: 20px;
+    justify-content: center;
     margin-inline-end: auto;
     margin-inline-start: auto;
-    margin-top: 6px;
+    margin-top: 4px;
     text-align: center;
     width: 72px;
   }
@@ -81,8 +83,7 @@
   }
 
   .thumbnail-container {
-    height: 100%;
-    margin-top: 12px;
+    margin-top: 4px;
     text-align: center;
     width: auto;
   }
@@ -101,16 +102,15 @@
   .thumbnail-img {
     border: 2px solid white;
     border-radius: 50%;
-    height: 46px;
+    height: 44px;
     object-fit: cover;
-    width: 46px;
+    width: 44px;
   }
 
   .thumbnail-fallback {
     height: 48px;
+    margin-top: 8px;
     position: relative;
-    top: 50%;
-    transform: translateY(-50%);
     width: 102px;
   }
 
diff --git a/chrome/browser/resources/new_tab_page/modules/cart/module.js b/chrome/browser/resources/new_tab_page/modules/cart/module.js
index 8ab899a..e0887f4 100644
--- a/chrome/browser/resources/new_tab_page/modules/cart/module.js
+++ b/chrome/browser/resources/new_tab_page/modules/cart/module.js
@@ -362,4 +362,4 @@
 /** @type {!ModuleDescriptor} */
 export const chromeCartDescriptor = new ModuleDescriptor(
     /*id=*/ 'chrome_cart',
-    /*heightPx=*/ 238, createCartElement);
+    /*heightPx=*/ 216, createCartElement);
diff --git a/chrome/browser/resources/new_tab_page/modules/module_wrapper.html b/chrome/browser/resources/new_tab_page/modules/module_wrapper.html
index b4c45403..1a17f54e 100644
--- a/chrome/browser/resources/new_tab_page/modules/module_wrapper.html
+++ b/chrome/browser/resources/new_tab_page/modules/module_wrapper.html
@@ -11,6 +11,7 @@
 
   #impressionProbe {
     height: 27px;
+    pointer-events: none;
     position: absolute;
     width: 100%;
   }
diff --git a/chrome/browser/resources/ntp4/BUILD.gn b/chrome/browser/resources/ntp4/BUILD.gn
index 30961b6..14a22c20 100644
--- a/chrome/browser/resources/ntp4/BUILD.gn
+++ b/chrome/browser/resources/ntp4/BUILD.gn
@@ -8,7 +8,6 @@
 import("//ui/webui/resources/tools/generate_grd.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":apps_page" ]
 }
 
diff --git a/chrome/browser/resources/ntp4/incognito_tab.html b/chrome/browser/resources/ntp4/incognito_tab.html
index cdfc86c..4b25b7d 100644
--- a/chrome/browser/resources/ntp4/incognito_tab.html
+++ b/chrome/browser/resources/ntp4/incognito_tab.html
@@ -10,11 +10,9 @@
 <title>$i18n{title}</title>
 <meta name="viewport" content="width=device-width">
 <link id="incognitothemecss" rel="stylesheet">
-<script src="chrome://resources/js/assert.js"></script>
-<script src="chrome://resources/js/util.js"></script>
 <script>
 // Until themes can clear the cache, force-reload the theme stylesheet.
-$('incognitothemecss').href =
+document.querySelector('#incognitothemecss').href =
     'chrome://theme/css/incognito_tab_theme.css?' + Date.now();
 </script>
 <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
@@ -51,8 +49,7 @@
   </div>
   <a class="learn-more-button" href="$i18n{learnMoreLink}">$i18n{learnMore}</a>
 </div>
-<script src="chrome://resources/js/cr.js"></script>
-<script src="incognito_tab.js"></script>
+<script type="module" src="incognito_tab.js"></script>
 <!-- Lazy-load cr_elements to avoid performance penalty introduced by loading Polymer -->
 <script type="module" src="chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js" async></script>
 <script type="module" src="chrome://resources/cr_elements/policy/cr_tooltip_icon.m.js" async></script>
diff --git a/chrome/browser/resources/ntp4/incognito_tab.js b/chrome/browser/resources/ntp4/incognito_tab.js
index f9b4218..503d34c 100644
--- a/chrome/browser/resources/ntp4/incognito_tab.js
+++ b/chrome/browser/resources/ntp4/incognito_tab.js
@@ -2,10 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {addWebUIListener} from 'chrome://resources/js/cr.m.js';
+import {$} from 'chrome://resources/js/util.m.js';
+
 window.addEventListener('load', function() {
   let cookieSettingsUrl;
 
-  cr.addWebUIListener('theme-changed', themeData => {
+  addWebUIListener('theme-changed', themeData => {
     document.documentElement.setAttribute(
         'hascustombackground', themeData.hasCustomBackground);
     $('incognitothemecss').href =
@@ -13,7 +16,7 @@
   });
   chrome.send('observeThemeChanges');
 
-  cr.addWebUIListener('cookie-controls-changed', dict => {
+  addWebUIListener('cookie-controls-changed', dict => {
     $('cookie-controls-tooltip-icon').hidden = !dict.enforced;
     $('cookie-controls-tooltip-icon').iconClass = dict.icon;
     $('cookie-controls-toggle').disabled = dict.enforced;
@@ -43,3 +46,4 @@
     document.documentElement.setAttribute('bookmarkbarattached', attached);
   },
 };
+window.ntp = ntp;
diff --git a/chrome/browser/resources/offline_pages/BUILD.gn b/chrome/browser/resources/offline_pages/BUILD.gn
index 4465cf5..4fbbb40 100644
--- a/chrome/browser/resources/offline_pages/BUILD.gn
+++ b/chrome/browser/resources/offline_pages/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [
     ":offline_internals",
     ":offline_internals_browser_proxy",
diff --git a/chrome/browser/resources/omnibox/BUILD.gn b/chrome/browser/resources/omnibox/BUILD.gn
index 53ee0e5..a40e5fa 100644
--- a/chrome/browser/resources/omnibox/BUILD.gn
+++ b/chrome/browser/resources/omnibox/BUILD.gn
@@ -59,7 +59,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   closure_flags = default_closure_args + mojom_js_args +
                   [ "js_module_root=" + rebase_path(".", root_build_dir) ]
   deps = [
diff --git a/chrome/browser/resources/predictors/BUILD.gn b/chrome/browser/resources/predictors/BUILD.gn
index 7e80eb9..da68881 100644
--- a/chrome/browser/resources/predictors/BUILD.gn
+++ b/chrome/browser/resources/predictors/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [
     ":autocomplete_action_predictor",
     ":predictors",
diff --git a/chrome/browser/resources/quota_internals/BUILD.gn b/chrome/browser/resources/quota_internals/BUILD.gn
index b76101d6..0bfaf0b3e 100644
--- a/chrome/browser/resources/quota_internals/BUILD.gn
+++ b/chrome/browser/resources/quota_internals/BUILD.gn
@@ -20,7 +20,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [
     ":event_handler",
     ":message_dispatcher",
diff --git a/chrome/browser/resources/reset_password/BUILD.gn b/chrome/browser/resources/reset_password/BUILD.gn
index 06859952..1b89b8d9 100644
--- a/chrome/browser/resources/reset_password/BUILD.gn
+++ b/chrome/browser/resources/reset_password/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   closure_flags = default_closure_args + mojom_js_args + [
                     "js_module_root=" + rebase_path(
                             "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/reset_password",
diff --git a/chrome/browser/resources/sandbox_internals/BUILD.gn b/chrome/browser/resources/sandbox_internals/BUILD.gn
index 2cdeb00..7fdeecb 100644
--- a/chrome/browser/resources/sandbox_internals/BUILD.gn
+++ b/chrome/browser/resources/sandbox_internals/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   if (is_win) {
     deps = [ ":sandbox_internals_win" ]
   }
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
index c890eadf..022948ee 100644
--- a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
+++ b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
@@ -8,6 +8,11 @@
       #minimumSizeSample {
         text-align: end;
       }
+
+      div[id$='FontPreview'] {
+        /* Override default style from cr-page-host-style */
+        line-height: initial;
+      }
     </style>
     <div class="cr-row first">
       <div class="flex cr-padded-text" aria-hidden="true">
diff --git a/chrome/browser/resources/settings/autofill_page/autofill_page.html b/chrome/browser/resources/settings/autofill_page/autofill_page.html
index 6510541..c7087c7 100644
--- a/chrome/browser/resources/settings/autofill_page/autofill_page.html
+++ b/chrome/browser/resources/settings/autofill_page/autofill_page.html
@@ -21,7 +21,7 @@
             on-click="onPaymentsClick_"
             role-description="$i18n{subpageArrowRoleDescription}"></cr-link-row>
         <cr-link-row id="addressesManagerButton"
-            start-icon="settings20:location-on" label="$i18n{addressesTitle}"
+            start-icon="settings:location-on" label="$i18n{addressesTitle}"
             on-click="onAddressesClick_"
             role-description="$i18n{subpageArrowRoleDescription}"></cr-link-row>
       </div>
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index 5d33377..1f1d6fa 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -503,6 +503,7 @@
     "chromeos/os_search_page/os_search_page.m.js",
     "chromeos/os_search_page/os_search_selection_dialog.m.js",
     "chromeos/os_settings_icons_css.m.js",
+    "chromeos/os_settings_menu/os_settings_menu.m.js",
     "chromeos/os_settings_page/main_page_behavior.m.js",
     "chromeos/os_settings_routes.m.js",
     "chromeos/os_settings_search_box/os_search_result_row.m.js",
@@ -1244,7 +1245,7 @@
     "os_search_page:closure_compile_module",
 
     #"os_settings_main:closure_compile_module",
-    #"os_settings_menu:closure_compile_module",
+    "os_settings_menu:closure_compile_module",
     "os_settings_page:closure_compile_module",
     "os_settings_search_box:closure_compile_module",
 
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index 5965c119..b4c05e8 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -85,6 +85,7 @@
 import './os_apps_page/app_management_page/shared_vars.m.js';
 import './os_apps_page/app_management_page/toggle_row.m.js';
 import './os_apps_page/app_management_page/uninstall_button.m.js';
+import './os_settings_menu/os_settings_menu.m.js';
 
 export {AboutPageBrowserProxyImpl, BrowserChannel, UpdateStatus} from '../about_page/about_page_browser_proxy.m.js';
 export {LifetimeBrowserProxy, LifetimeBrowserProxyImpl} from '../lifetime_browser_proxy.m.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn
index 41ea4417..447d6f95 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//chrome/browser/resources/settings/chromeos/os_settings.gni")
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
@@ -19,18 +20,18 @@
   ]
 }
 
-# TODO: Uncomment as the Polymer3 migration makes progress.
-#js_type_check("closure_compile_module") {
-#  is_polymer3 = true
-#  deps = [
-#    ":os_settings_menu.m"
-#  ]
-#}
+js_type_check("closure_compile_module") {
+  is_polymer3 = true
+  deps = [ ":os_settings_menu.m" ]
+}
 
 js_library("os_settings_menu.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "..:os_route.m",
+    "../..:router.m",
+    "//third_party/polymer/v3_0/components-chromium/iron-collapse:iron-collapse",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
   extra_deps = [ ":os_settings_menu_module" ]
 }
@@ -45,4 +46,6 @@
   js_file = "os_settings_menu.js"
   html_file = "os_settings_menu.html"
   html_type = "dom-module"
+  auto_imports = os_settings_auto_imports
+  namespace_rewrites = os_settings_namespace_rewrites
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
index 441ef50..eaaae27e 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
@@ -7,7 +7,6 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-selector/iron-selector.html">
-<link rel="import" href="../../i18n_setup.html">
 <link rel="import" href="../../router.html">
 <link rel="import" href="../../settings_shared_css.html">
 <link rel="import" href="../os_icons.html">
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.js b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.js
index d3260109..8fef148 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.js
@@ -53,7 +53,6 @@
   currentRouteChanged(newRoute) {
     const urlSearchQuery =
         settings.Router.getInstance().getQueryParameters().get('search');
-
     // If the route navigated to by a search result is in the advanced
     // section, the advanced menu will expand.
     if (urlSearchQuery && settings.routes.ADVANCED &&
diff --git a/chrome/browser/resources/settings/icons.html b/chrome/browser/resources/settings/icons.html
index ad5d253..53f6cba 100644
--- a/chrome/browser/resources/settings/icons.html
+++ b/chrome/browser/resources/settings/icons.html
@@ -14,7 +14,6 @@
       <g id="experiment"><path d="M17.2667 14.7583L12.5 8.18332V4.23332H14.1667V2.56665H5.83332V4.23332H7.49998V8.17498L2.61665 14.9166C2.24998 15.425 2.19998 16.0917 2.48332 16.65C2.76665 17.2083 3.34165 17.5583 3.96665 17.5583H16.05C16.9667 17.5583 17.7167 16.8083 17.7167 15.8917C17.7167 15.4583 17.5416 15.0583 17.2667 14.7583Z" fill="#5F6368"></path></g>
       <g id="googleg"><path d="M16.58 8H9v2.75h4.47c-.24 1.2-1.42 3.27-4.47 3.27-2.72 0-4.93-2.25-4.93-5.02S6.28 3.98 9 3.98c1.54 0 2.57.66 3.17 1.22l2.19-2.12C12.97 1.79 11.16 1 9 1 4.58 1 1 4.58 1 9s3.58 8 8 8c4.62 0 7.68-3.25 7.68-7.82 0-.46-.04-.83-.1-1.18z"></path></g>
       <g id="incognito" fill="#5F6368"><circle cx="6.8" cy="12.964" r="1.764"/><path d="M10 0C4.473 0 0 4.473 0 10s4.473 10 10 10 10-4.473 10-10S15.527 0 10 0zM7.619 4.1a.696.696 0 0 1 .881-.419l1.473.492 1.463-.492a.716.716 0 0 1 .883.419l1.608 4.291H6.02l1.6-4.291zm5.517 11.328a2.463 2.463 0 0 1-2.445-2.256c-.682-.436-1.237-.162-1.455-.017a2.45 2.45 0 0 1-2.445 2.263 2.471 2.471 0 0 1-2.464-2.463 2.47 2.47 0 0 1 2.463-2.464c1.165 0 2.138.809 2.391 1.9a1.934 1.934 0 0 1 1.546.009 2.462 2.462 0 0 1 2.392-1.909 2.47 2.47 0 0 1 2.462 2.463 2.435 2.435 0 0 1-2.445 2.474zM16.31 9.8H3.637v-.709H16.31V9.8h-.001z"/><circle cx="13.136" cy="12.964" r="1.764"/></g>
-      <g id="location-on"><path d="M10,2 C6.95928571,2 4.5,4.504 4.5,7.6 C4.5,11.8 10,18 10,18 C10,18 15.5,11.8 15.5,7.6 C15.5,4.504 13.0407143,2 10,2 Z M10,9.5 C8.896,9.5 8,8.604 8,7.5 C8,6.396 8.896,5.5 10,5.5 C11.104,5.5 12,6.396 12,7.5 C12,8.604 11.104,9.5 10,9.5 Z"></path></g>
       <g id="vpn-key"><path d="M10.4727273,8 C9.87272727,6.2525 8.26181818,5 6.36363636,5 C3.95272727,5 2,7.01375 2,9.5 C2,11.98625 3.95272727,14 6.36363636,14 C8.26181818,14 9.87272727,12.7475 10.4727273,11 L13.6363636,11 L13.6363636,14 L16.5454545,14 L16.5454545,11 L18,11 L18,8 L10.4727273,8 Z M6.36363636,11 C5.56,11 4.90909091,10.32875 4.90909091,9.5 C4.90909091,8.67125 5.56,8 6.36363636,8 C7.16727273,8 7.81818182,8.67125 7.81818182,9.5 C7.81818182,10.32875 7.16727273,11 6.36363636,11 Z"></path></g>
       <g id="cloud-off"><path d="M16.4732571,13.3443682 C16.8002856,12.9882746 17,12.5134184 17,11.9922 C17,10.8882 16.104,9.9922 15,9.9922 L13.494,9.9922 L13.494,9.0002 C13.494,7.0672 11.927,5.5002 9.994,5.5002 C9.5847901,5.5002 9.1930204,5.57089988 8.82954884,5.70065995 L7.33083687,4.20194798 C8.11843435,3.75577808 9.02717677,3.5002 10,3.5002 C12.71,3.5002 14.957,5.4612 15.411,8.0412 C17.424,8.2502 19,9.9312 19,12.0002 C19,13.0718701 18.5784721,14.0451601 17.8921876,14.7632987 L16.4732571,13.3443682 Z M17.8711111,17 L16.8711111,18 L14.8713111,16.0002 L6,16.0002 C3.239,16.0002 1,13.7622 1,11.0002 C1,8.58475294 2.71868905,6.59044755 4.99627833,6.12516722 L2,3.12888889 L3,2.12888889 L17.8711111,17 Z M6.86331111,7.9922 L6,7.9922 C4.343,7.9922 3,9.3352 3,10.9922 C3,12.6492 4.343,13.9922 6,13.9922 L12.8633111,13.9922 L6.86331111,7.9922 Z"></path></g>
       <!-- The polygon ("+" shape) within this icon will always be filled with
@@ -35,6 +34,9 @@
       <g id="ads">
         <path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm0 14H5V8h14v10z"></path>
       </g>
+      <g id="ads-off">
+        <path d="M6.83,4H20c1.11,0,2,0.9,2,2v12c0,0.34-0.09,0.66-0.23,0.94L20,17.17V8h-9.17L6.83,4z M20.49,23.31L17.17,20H4 c-1.11,0-2-0.9-2-2V6c0-0.34,0.08-0.66,0.23-0.94L0.69,3.51L2.1,2.1l19.8,19.8L20.49,23.31z M15.17,18l-10-10H4v10H15.17z"></path>
+      </g>
 
       <!-- Cookie SVG obtained from rolfe@ -->
       <g id="cookie">
@@ -69,6 +71,7 @@
 
       <!-- vr-headset SVG obtained from amyroberts@ -->
       <g id="vr-headset"><path d="M20.907 6.678A2.54 2.54 0 0019.16 6H4.9c-.659 0-1.28.24-1.747.678a2.229 2.229 0 00-.723 1.637v7.37c0 .618.256 1.2.723 1.637A2.54 2.54 0 004.9 18h3.424c.448 0 .884-.114 1.268-.33.384-.216.697-.522.908-.893l.967-1.68a.572.572 0 01.16-.74.67.67 0 01.806 0c.235.18.302.49.16.74l.967 1.68c.21.365.524.677.908.893.384.216.82.33 1.268.33h3.424c.659 0 1.28-.24 1.747-.678.467-.437.723-1.02.723-1.637v-7.37c0-.618-.256-1.2-.723-1.637zM7.83 13.8c-1.328 0-2.4-1.08-2.4-2.4 0-1.32 1.08-2.4 2.4-2.4 1.32 0 2.4 1.08 2.4 2.4 0 1.32-1.072 2.4-2.4 2.4zm8.4 0c-1.328 0-2.4-1.08-2.4-2.4 0-1.32 1.08-2.4 2.4-2.4 1.32 0 2.4 1.08 2.4 2.4 0 1.32-1.072 2.4-2.4 2.4z"></path></g>
+      <g id="vr-headset-off"><path d="M2.81,2.81L1.39,4.22L3.68,6.5C3.26,6.87,3,7.41,3,8v8c0,1.1,0.9,2,2,2h3.53c1.42,0,2.02-1.24,2.03-1.27l0.91-1.76 c0.08-0.16,0.22-0.26,0.37-0.31l1.11,1.11l0.49,0.96c0.01,0.03,0.52,1.08,1.71,1.25l4.63,4.63l1.41-1.41L2.81,2.81z M9.87,12.7 C9.59,13.46,8.85,14,8,14c-1.1,0-2-0.9-2-2c0-0.85,0.54-1.59,1.3-1.87l0,0L9.87,12.7L9.87,12.7z M16.7,13.87l3.62,3.62 C20.74,17.13,21,16.59,21,16V8c0-1.1-0.9-2-2-2H8.83c0,0,5.3,5.3,5.3,5.3C14.41,10.54,15.15,10,16,10c1.1,0,2,0.9,2,2 C18,12.85,17.46,13.59,16.7,13.87z"></path></g>
 
       <!--
       These icons are copied from Polymer's iron-icons and kept in sorted order.
@@ -82,33 +85,52 @@
 </if>
       <g id="check-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path></g>
       <g id="clipboard"><path d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"></path></g>
+      <g id="clipboard-off"><path d="M21.19,21.19L2.81,2.81L1.39,4.22L3,5.83V19c0,1.1,0.9,2,2,2h13.17l1.61,1.61L21.19,21.19z M5,19V7.83L16.17,19H5z M17,8V5 h2v11.17l2,2V5c0-1.1-0.9-2-2-2h-4.18C14.4,1.84,13.3,1,12,1S9.6,1.84,9.18,3H5.83l5,5H17z M12,3c0.55,0,1,0.45,1,1s-0.45,1-1,1 s-1-0.45-1-1S11.45,3,12,3z"></path></g>
       <g id="cloud"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"></path></g>
       <g id="code"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"></path></g>
+      <g id="code-off"><path d="M19.17,12l-4.58-4.59L16,6l6,6l-3.59,3.59L17,14.17L19.17,12z M1.39,4.22l4.19,4.19L2,12l6,6l1.41-1.41L4.83,12L7,9.83 l12.78,12.78l1.41-1.41L2.81,2.81L1.39,4.22z"></path></g>
       <g id="content-copy"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"></path></g>
       <g id="exit-to-app"><path d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path></g>
+      <g id="file-download-off"><path d="M15.6 12.4L9 5.8V3H15V9H19L15.6 12.4ZM5 20H18.4V19.5L19 19L19.5 18.5L12.7 11.7L9 7.9L5.6 4.5L4.5 5.6L7.9 9H5L12 16L13.5 14.5L17 18H5V20Z"></path></g>
+      <g id="file-editing-off"><path d="M19.5,16.67l1.09-1.09L22,17l-1.09,1.09L19.5,16.67z M17,14.17L17,13h2l0,3.17L17,14.17z M2.1,2.1L0.69,3.51l3.32,3.32L4,18 c0,1.1,0.89,2,1.99,2H12v-2H6V8.83L19.17,22H14v2h8v-2L2.1,2.1z M6.83,4H12v5h5v2h2V8l-6-6H6C5.66,2,5.34,2.09,5.06,2.24L6.83,4z"></path></g>
       <g id="hid-device"><path d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-10 7H8v3H6v-3H3v-2h3V8h2v3h3v2zm4.5 2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4-3c-.83 0-1.5-.67-1.5-1.5S18.67 9 19.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g>
       <g id="language"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"></path></g>
-      <g id="midi"><path d="M21,19.1H3V5h18V19.1z M21,3H3C1.9,3,1,3.9,1,5v14c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V5C23,3.9,22.1,3,21,3z"></path><path fill="none" d="M21,19.1H3V5h18V19.1z M21,3H3C1.9,3,1,3.9,1,5v14c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V5C23,3.9,22.1,3,21,3z"></path><path d="M14,5h3v8h-3V5z"></path><path d="M15,12h1v8h-1V12z"></path><path fill="none" d="M0,0h24v24H0V0z"></path><rect x="7" y="5" width="3" height="8"></rect><rect x="8" y="12" width="1" height="8"></rect></g>
+      <g id="location-on"><path d="M12,2C8.13,2,5,5.13,5,9c0,5.34,4.21,6.79,6.03,12.28C11.17,21.7,11.55,22,12,22s0.83-0.3,0.97-0.72 C14.79,15.79,19,14.34,19,9C19,5.13,15.87,2,12,2z M12,11.5c-1.38,0-2.5-1.12-2.5-2.5c0-1.38,1.12-2.5,2.5-2.5s2.5,1.12,2.5,2.5 C14.5,10.38,13.38,11.5,12,11.5z"></path></g>
+      <g id="location-off"><path d="M12,6.88c1.38,0,2.5,1.12,2.5,2.5c0,0.64-0.25,1.21-0.64,1.65l3.38,3.38C18.24,12.95,19,11.39,19,9.13c0-3.87-3.13-7-7-7 c-1.94,0-3.7,0.79-4.97,2.07l3.32,3.32C10.79,7.13,11.36,6.88,12,6.88z"></path><path d="M2.81,2.81L1.39,4.22l3.72,3.72C5.04,8.33,5,8.72,5,9.13c0,5.34,4.21,6.79,6.03,12.28c0.14,0.42,0.52,0.72,0.97,0.72 s0.83-0.3,0.97-0.72c0.49-1.49,1.17-2.68,1.88-3.74l4.93,4.93l1.41-1.41L2.81,2.81z"></path></g>
+      <g id="mic-off"><path d="M19,11h-2c0,0.91-0.25,1.76-0.68,2.49l1.45,1.45C18.54,13.82,19,12.47,19,11z M2.81,2.81L1.39,4.22l11.66,11.66 C12.71,15.96,12.36,16,12,16c-2.76,0-5-2.24-5-5H5c0,3.53,2.61,6.43,6,6.92V21h2v-3.08c0.57-0.08,1.12-0.24,1.64-0.45l5.14,5.14 l1.41-1.41L2.81,2.81z M15,11V5c0-1.66-1.34-3-3-3S9,3.34,9,5v1.17l5.81,5.81C14.92,11.67,15,11.35,15,11z"></path></g>
+      <g id="midi"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M14,14.5h0.25V19h-4.5v-4.5H10 c0.55,0,1-0.45,1-1V5h2v8.5C13,14.05,13.45,14.5,14,14.5z M5,5h2v8.5c0,0.55,0.45,1,1,1h0.25V19H5V5z M19,19h-3.25v-4.5H16 c0.55,0,1-0.45,1-1V5h2V19z"></path></g>
+      <g id="midi-off"><path d="M21.19,21.19L2.81,2.81L1.39,4.22L3,5.83V19c0,1.1,0.9,2,2,2h13.17l1.61,1.61L21.19,21.19z M8.25,19H5V7.83l2,2v3.67 c0,0.55,0.45,1,1,1h0.25V19z M9.75,19v-4.5H10c0.46,0,0.82-0.31,0.94-0.73l3.31,3.31V19H9.75z M11,8.17L5.83,3H19c1.1,0,2,0.9,2,2 v13.17l-2-2V5h-2v8.5c0,0.19-0.07,0.36-0.16,0.51L13,10.17V5h-2V8.17z"></path></g>
       <g id="music-note"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"></path></g>
       <g id="notifications"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"></path></g>
+      <g id="notifications-off"><path d="M18,10c0-2.79-1.91-5.14-4.5-5.8V3.5C13.5,2.67,12.83,2,12,2s-1.5,0.67-1.5,1.5v0.7C9.64,4.42,8.87,4.84,8.21,5.38 L18,15.17V10z M12,22c1.1,0,2-0.9,2-2h-4C10,21.1,10.9,22,12,22z M2.81,2.81L1.39,4.22L6.1,8.93C6.04,9.28,6,9.63,6,10v7H4v2h12.17 l3.61,3.61l1.41-1.41L2.81,2.81z"></path></g>
+      <g id="open-in-new-off"><path d="M17.59,5H14V3h7v7h-2V6.41l-4.88,4.88l-1.41-1.41L17.59,5z M19,12v4.17l2,2V12H19z M19.78,22.61L18.17,21H5 c-1.11,0-2-0.9-2-2V5.83L1.39,4.22l1.41-1.41l18.38,18.38L19.78,22.61z M16.17,19l-4.88-4.88L9.7,15.71L8.29,14.3l1.59-1.59L5,7.83 V19H16.17z M7.83,5H12V3H5.83L7.83,5z"></path></g>
       <g id="pdf"><path d="M7 11.5h1v-1H7v1zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9.5 8.5c0 .83-.67 1.5-1.5 1.5H7v2H5.5V9H8c.83 0 1.5.67 1.5 1.5v1zm10-1H17v1h1.5V13H17v2h-1.5V9h4v1.5zm-5 3c0 .83-.67 1.5-1.5 1.5h-2.5V9H13c.83 0 1.5.67 1.5 1.5v3zm-2.5 0h1v-3h-1v3z"></path><path fill="none" d="M0 0h24v24H0z"></path></g>
       <g id="palette"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g>
       <g id="payment-handler"><path d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"></path></g>
+      <g id="payment-handler-off"><path d="M6.83,4H20c1.11,0,2,0.89,2,2v12c0,0.34-0.08,0.66-0.23,0.94L20,17.17V12h-5.17l-4-4H20V6H8.83 L6.83,4z M20.49,23.31L17.17,20H4c-1.11,0-2-0.89-2-2L2.01,6c0-0.34,0.08-0.66,0.23-0.93L0.69,3.51L2.1,2.1l19.8,19.8L20.49,23.31z M4,6.83V8h1.17L4,6.83z M15.17,18l-6-6H4v6H15.17z"></path></g>
       <g id="insecure-content"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></g>
       <g id="person"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"></path></g>
       <g id="photo"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></g>
+      <g id="photo-off"><path d="M21,5c0-1.1-0.9-2-2-2H5.83L21,18.17V5z"></path><path d="M2.81,2.81L1.39,4.22L3,5.83V19c0,1.1,0.9,2,2,2h13.17l1.61,1.61l1.41-1.41L2.81,2.81z M6,17l3-4l2.25,3l0.82-1.1l2.1,2.1 H6z"></path></g>
       <g id="power-settings-new"><path d="M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z"></path></g>
       <g id="protocol-handler"><path d="M21.72 11.33l-6.644-7.035a.97.97 0 0 0-1.38-.01l-1.67 1.72-1.617-1.712a.97.97 0 0 0-1.38-.01l-6.737 6.935c-.187.191-.29.447-.292.719-.002.272.099.529.28.722l6.644 7.034a.949.949 0 0 0 1.38.011l1.671-1.718 1.615 1.71a.949.949 0 0 0 1.381.01l6.74-6.935a1.054 1.054 0 0 0 .01-1.44zM6.947 12.464l3.657 3.785-.974.98-5.273-5.456 5.349-5.378.929.962-3.677 3.7a.998.998 0 0 0-.292.702 1 1 0 0 0 .28.705zm7.35 4.768l-.931-.963 3.68-3.7a1.012 1.012 0 0 0 .007-1.407l-3.656-3.784.974-.98 5.273 5.456-5.348 5.378z"></path></g>
+      <g id="protocol-handler-off"><path d="M7.95,5.12l0.73-0.8C8.88,4.11,9.15,4,9.42,4c0.27,0,0.54,0.11,0.74,0.32L12,6.34l1.85-2.01C14.04,4.11,14.31,4,14.58,4 c0.27,0,0.54,0.11,0.74,0.32l6.42,7c0.35,0.38,0.35,0.97,0,1.35l-2.98,3.25l-1.42-1.42l2.3-2.51l-5.06-5.52l-1.23,1.34l3.21,3.51 c0.35,0.38,0.35,0.97,0,1.35l-0.51,0.56L7.95,5.12z M19.78,22.61l-3.73-3.73l-0.73,0.8c-0.2,0.22-0.47,0.32-0.74,0.32 c-0.27,0-0.54-0.11-0.74-0.32L12,17.66l-1.85,2.01C9.96,19.89,9.69,20,9.42,20c-0.27,0-0.54-0.11-0.74-0.32l-6.42-7 c-0.35-0.38-0.35-0.97,0-1.35l2.98-3.25L1.39,4.22l1.41-1.41l18.38,18.38L19.78,22.61z M10.64,16.18l-3.21-3.51 c-0.35-0.38-0.35-0.97,0-1.35l0.51-0.56L6.66,9.49L4.36,12l5.06,5.52L10.64,16.18z"></path></g>
       <g id="refresh"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"></path></g>
       <g id="restore"><path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"></path></g>
       <g id="rotate-right"><path d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"></path></g>
       <g id="save-original"><path d="M11 17H6V4h5v4h4v2h2V7l-5-5H6c-1.1 0-2 .9-2 2v13c0 1.1.9 2 2 2h5v-2zm9 5h-8v-2h8v2zm0-7l-4 4-4-4h3v-3h2v3h3z"></path></g>
-      <g id="sensors"><path d="M10 8.5c-0.8 0-1.5 0.7-1.5 1.5s0.7 1.5 1.5 1.5s1.5-0.7 1.5-1.5S10.8 8.5 10 8.5z M7.6 5.8 C6.2 6.7 5.2 8.2 5.2 10c0 1.8 1 3.4 2.4 4.2l0.8-1.4c-1-0.6-1.6-1.6-1.6-2.8c0-1.2 0.6-2.2 1.6-2.8L7.6 5.8z M14.8 10 c0-1.8-1-3.4-2.4-4.2l-0.8 1.4c0.9 0.6 1.6 1.6 1.6 2.8c0 1.2-0.6 2.2-1.6 2.8l0.8 1.4C13.8 13.4 14.8 11.8 14.8 10z M6 3 c-2.4 1.4-4 4-4 7c0 3 1.6 5.6 4 7l0.8-1.4c-1.9-1.1-3.2-3.2-3.2-5.6c0-2.4 1.3-4.5 3.2-5.6L6 3z M13.2 4.4 c1.9 1.1 3.2 3.2 3.2 5.6c0 2.4-1.3 4.5-3.2 5.6L14 17c2.4-1.4 4-4 4-7c0-3-1.6-5.6-4-7L13.2 4.4z"></path></g>
+      <g id="sensors"><path d="M7.76,16.24C6.67,15.16,6,13.66,6,12s0.67-3.16,1.76-4.24l1.42,1.42C8.45,9.9,8,10.9,8,12c0,1.1,0.45,2.1,1.17,2.83 L7.76,16.24z M16.24,16.24C17.33,15.16,18,13.66,18,12s-0.67-3.16-1.76-4.24l-1.42,1.42C15.55,9.9,16,10.9,16,12 c0,1.1-0.45,2.1-1.17,2.83L16.24,16.24z M12,10c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S13.1,10,12,10z M20,12 c0,2.21-0.9,4.21-2.35,5.65l1.42,1.42C20.88,17.26,22,14.76,22,12s-1.12-5.26-2.93-7.07l-1.42,1.42C19.1,7.79,20,9.79,20,12z M6.35,6.35L4.93,4.93C3.12,6.74,2,9.24,2,12s1.12,5.26,2.93,7.07l1.42-1.42C4.9,16.21,4,14.21,4,12S4.9,7.79,6.35,6.35z"></path></g>
+      <g id="sensors-off"><path d="M16.94,6.91l-1.41,1.45c0.9,0.94,1.46,2.22,1.46,3.64c0,0.65-0.12,1.26-0.33,1.83l1.52,1.52c0.52-1,0.81-2.15,0.81-3.36 C18.99,10.02,18.21,8.21,16.94,6.91z M19.77,4l-1.41,1.45C19.98,7.13,21,9.44,21,12.01c0,1.78-0.48,3.44-1.32,4.84l1.46,1.46 c1.18-1.8,1.87-3.96,1.87-6.29C23,8.88,21.77,6.05,19.77,4z M11.83,9c0.06,0,0.11,0,0.17,0c1.66,0,3,1.34,3,3c0,0.06,0,0.11,0,0.17 L11.83,9z M20.49,23.31L12.17,15c-0.06,0-0.11,0-0.17,0c-1.66,0-3-1.34-3-3c0-0.06,0-0.11,0-0.17l-1.67-1.67 C7.13,10.74,7.01,11.35,7.01,12c0,1.42,0.56,2.7,1.46,3.64l-1.41,1.45c-1.27-1.3-2.05-3.1-2.05-5.09c0-1.21,0.29-2.36,0.8-3.36 L4.32,7.15C3.48,8.55,3,10.21,3,11.99c0,2.57,1.02,4.88,2.64,6.56L4.23,20C2.23,17.95,1,15.12,1,11.99c0-2.33,0.69-4.5,1.87-6.29 L0.69,3.51L2.1,2.1l19.8,19.8L20.49,23.31z"></path></g>
       <g id="serial-port"><path d="M22 9V7h-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-2h2v-2h-2v-2h2v-2h-2V9h2zm-4 10H4V5h14v14zM6 13h5v4H6zm6-6h4v3h-4zM6 7h5v5H6zm6 4h4v6h-4z"></path></g>
+      <g id="serial-port-off"><path d="M7.83,5H18v10.17L19.83,17H22v-2h-2v-2h2v-2h-2V9h2V7h-2V5c0-1.1-0.9-2-2-2H5.83L7.83,5z M12,9.17V7h4v3h-3.17L12,9.17z M9.83,7H11v1.17L9.83,7z M13.83,11H16v2.17L13.83,11z M18,21c0.06,0,0.11,0,0.16-0.01l2.32,2.32l1.41-1.41L2.1,2.1L0.69,3.51 l1.32,1.32C2,4.89,2,4.94,2,5v14c0,1.1,0.9,2,2,2H18z M4,19V6.83l2,2V12h3.17l1,1H6v4h5v-3.17l1,1V17h2.17l2,2H4z"></path></g>
+      <g id="sync-off"><path d="M12 1.05V4.05C16.35 4.05 19.95 7.65 19.95 12C19.95 13.35 19.5 14.7 18.9 15.9L17.4 14.4C17.85 13.65 18 12.9 18 12C18 8.7 15.3 6 12 6V8.85L8.1 4.95L12 1.05ZM19.35 22.5L15.9 19.05L12 22.95V19.95C7.65 19.95 4.05 16.35 4.05 12C4.05 10.65 4.5 9.3 5.1 8.1L1.5 4.65L3.15 3L21 20.85L19.35 22.5ZM12 15.15L6.45 9.6C6.15 10.35 6 11.1 6 12C6 15.3 8.7 18 12 18V15.15Z"></path></g>
       <g id="sync-disabled"><path d="M10 6.35V4.26c-.8.21-1.55.54-2.23.96l1.46 1.46c.25-.12.5-.24.77-.33zm-7.14-.94l2.36 2.36C4.45 8.99 4 10.44 4 12c0 2.21.91 4.2 2.36 5.64L4 20h6v-6l-2.24 2.24C6.68 15.15 6 13.66 6 12c0-1 .25-1.94.68-2.77l8.08 8.08c-.25.13-.5.25-.77.34v2.09c.8-.21 1.55-.54 2.23-.96l2.36 2.36 1.27-1.27L4.14 4.14 2.86 5.41zM20 4h-6v6l2.24-2.24C17.32 8.85 18 10.34 18 12c0 1-.25 1.94-.68 2.77l1.46 1.46C19.55 15.01 20 13.56 20 12c0-2.21-.91-4.2-2.36-5.64L20 4z"></path></g>
       <g id="sync-problem"><path d="M3 12c0 2.21.91 4.2 2.36 5.64L3 20h6v-6l-2.24 2.24C5.68 15.15 5 13.66 5 12c0-2.61 1.67-4.83 4-5.65V4.26C5.55 5.15 3 8.27 3 12zm8 5h2v-2h-2v2zM21 4h-6v6l2.24-2.24C18.32 8.85 19 10.34 19 12c0 2.61-1.67 4.83-4 5.65v2.09c3.45-.89 6-4.01 6-7.74 0-2.21-.91-4.2-2.36-5.64L21 4zm-10 9h2V7h-2v6z"></path></g>
       <g id="usb"><path d="M15 7v4h1v2h-3V5h2l-3-4-3 4h2v8H8v-2.07c.7-.37 1.2-1.08 1.2-1.93 0-1.21-.99-2.2-2.2-2.2-1.21 0-2.2.99-2.2 2.2 0 .85.5 1.56 1.2 1.93V13c0 1.11.89 2 2 2h3v3.05c-.71.37-1.2 1.1-1.2 1.95 0 1.22.99 2.2 2.2 2.2 1.21 0 2.2-.98 2.2-2.2 0-.85-.49-1.58-1.2-1.95V15h3c1.11 0 2-.89 2-2v-2h1V7h-4z"></path></g>
+      <g id="usb-off"><path d="M15,8h4v4h-1v2c0,0.34-0.08,0.66-0.23,0.94L16,13.17V12h-1V8z M11,8.17l2,2V6h2l-3-4L9,6h2V8.17z M13,16v2.28 c0.6,0.34,1,0.98,1,1.72c0,1.1-0.9,2-2,2s-2-0.9-2-2c0-0.74,0.4-1.37,1-1.72V16H8c-1.11,0-2-0.89-2-2v-2.28C5.4,11.38,5,10.74,5,10 c0-0.59,0.26-1.13,0.68-1.49L1.39,4.22l1.41-1.41l18.38,18.38l-1.41,1.41L13.17,16H13z M11,14v-0.17l-2.51-2.51 c-0.14,0.16-0.31,0.29-0.49,0.4V14H11z"></path></g>
+      <g id="videocam-off"><path d="M18,15.17v-1.65l4,3.98v-11l-4,3.98V6c0-1.1-0.9-2-2-2H6.83L18,15.17z"></path><path d="M18,18L2.1,2.1L0.69,3.51l1.56,1.56C2.09,5.35,2,5.66,2,6v12c0,1.1,0.9,2,2,2h12c0.34,0,0.65-0.09,0.93-0.24l3.56,3.56 l1.41-1.41L18,18L18,18z"></path></g>
       <g id="volume-up"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"></path></g>
+      <g id="volume-up-off"><path d="M16.25,13.42C16.4,12.97,16.5,12.5,16.5,12c0-1.77-1.02-3.29-2.5-4.03v3.2L16.25,13.42z"></path><path d="M19,12c0,1.21-0.31,2.34-0.85,3.32l1.46,1.46C20.48,15.39,21,13.76,21,12c0-4.28-2.99-7.86-7-8.77v2.06 C16.89,6.15,19,8.83,19,12z"></path><path d="M2.81,2.81L1.39,4.22L6.17,9H3v6h4l5,5v-5.17l3.32,3.32c-0.42,0.23-0.85,0.43-1.32,0.56v2.06c1-0.23,1.94-0.62,2.79-1.15 l2.99,2.99l1.41-1.41L2.81,2.81z"></path><polygon points="12,9.17 12,4 9.41,6.59"></polygon></g>
       <g id="bluetooth-scanning"><path d="M14.24 12.01l2.32 2.32c.28-.72.44-1.51.44-2.33 0-.82-.16-1.59-.43-2.31l-2.33 2.32zm5.29-5.3l-1.26 1.26c.63 1.21.98 2.57.98 4.02s-.36 2.82-.98 4.02l1.2 1.2a9.936 9.936 0 0 0 1.54-5.31c-.01-1.89-.55-3.67-1.48-5.19zm-3.82 1L10 2H9v7.59L4.41 5 3 6.41 8.59 12 3 17.59 4.41 19 9 14.41V22h1l5.71-5.71-4.3-4.29 4.3-4.29zM11 5.83l1.88 1.88L11 9.59V5.83zm1.88 10.46L11 18.17v-3.76l1.88 1.88z"></path></g>
       <g id="bluetooth"><path d="M17.71 7.71L12 2h-1v7.59L6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 11 14.41V22h1l5.71-5.71-4.3-4.29 4.3-4.29zM13 5.83l1.88 1.88L13 9.59V5.83zm1.88 10.46L13 18.17v-3.76l1.88 1.88z"></path></g>
 <if expr="not chromeos">
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html
index ba0ddd7..40376f0 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -126,9 +126,7 @@
                 allow-option-icon="cr:file-download"
                 block-option-label
                     ="$i18n{siteSettingsAutomaticDownloadsBlocked}"
-                block-option-icon="settings:block">
-            <!-- TODO(https://crbug.com/1033607): use corresponding block
-                  icons instead of generic ones throughout this page -->
+                block-option-icon="settings:file-download-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.AUTOMATIC_DOWNLOADS]]"
@@ -169,7 +167,7 @@
                     "$i18n{siteSettingsBackgroundSyncAllowed}"
                 allow-option-icon="cr:sync"
                 block-option-label="$i18n{siteSettingsBackgroundSyncBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:sync-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.BACKGROUND_SYNC]]"
@@ -214,7 +212,7 @@
                 block-option-label="$i18n{siteSettingsCameraBlocked}"
                 block-option-sub-label=
                     "$i18n{siteSettingsCameraBlockedSubLabel}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:videocam-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.CAMERA]]" read-only-list
@@ -274,7 +272,7 @@
                 allow-option-label="$i18n{siteSettingsImagesAllowed}"
                 allow-option-icon="settings:photo"
                 block-option-label="$i18n{siteSettingsImagesBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:photo-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.IMAGES]]"
@@ -338,11 +336,11 @@
                 category="[[ContentSettingsTypes.GEOLOCATION]]"
                 allow-option-label=
                     "$i18n{siteSettingsLocationAllowed}"
-                allow-option-icon="settings20:location-on"
+                allow-option-icon="settings:location-on"
                 block-option-label="$i18n{siteSettingsLocationBlocked}"
                 block-option-sub-label=
                     "$i18n{siteSettingsLocationBlockedSubLabel}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:location-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.GEOLOCATION]]" read-only-list
@@ -403,7 +401,7 @@
                 allow-option-label="$i18n{siteSettingsJavascriptAllowed}"
                 allow-option-icon="settings:code"
                 block-option-label="$i18n{siteSettingsJavascriptBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:code-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.JAVASCRIPT]]"
@@ -440,7 +438,7 @@
                 allow-option-label="$i18n{siteSettingsSoundAllowed}"
                 allow-option-icon="settings:volume-up"
                 block-option-label="$i18n{siteSettingsSoundBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:volume-up-off">
             </settings-category-default-radio-group>
           </template>
           <template is="dom-if" if="[[!enableContentSettingsRedesign_]]">
@@ -495,7 +493,7 @@
                 allow-option-icon="cr:mic"
                 block-option-label="$i18n{siteSettingsMicBlocked}"
                 block-option-sub-label="$i18n{siteSettingsMicBlockedSubLabel}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:mic-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.MIC]]" read-only-list
@@ -535,7 +533,7 @@
                 block-option-label="$i18n{siteSettingsMotionSensorsBlocked}"
                 block-option-sub-label=
                     "$i18n{siteSettingsMotionSensorsBlockedSubLabel}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:sensors-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.SENSORS]]" read-only-list
@@ -594,7 +592,7 @@
                     pref="[[prefs.generated.notification]]"
                     label="$i18n{siteSettingsNotificationsBlocked}"
                     sub-label="$i18n{siteSettingsNotificationsBlockedSubLabel}"
-                    icon="settings:block">
+                    icon="settings:notifications-off">
                 </settings-collapse-radio-button>
               </settings-radio-group>
             </div>
@@ -651,7 +649,7 @@
                 allow-option-label="$i18n{siteSettingsPopupsAllowed}"
                 allow-option-icon="cr:open-in-new"
                 block-option-label="$i18n{siteSettingsPopupsBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:open-in-new-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.POPUPS]]"
@@ -690,7 +688,7 @@
                   allow-option-label="$i18n{siteSettingsAdsAllowed}"
                   allow-option-icon="settings:ads"
                   block-option-label="$i18n{siteSettingsAdsBlocked}"
-                  block-option-icon="settings:block">
+                  block-option-icon="settings:ads-off">
               </settings-category-default-radio-group>
               <category-setting-exceptions
                   category="[[ContentSettingsTypes.ADS]]"
@@ -731,7 +729,7 @@
                     "$i18n{siteSettingsMidiAllowed}"
                 allow-option-icon="settings:midi"
                 block-option-label="$i18n{siteSettingsMidiBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:midi-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.MIDI_DEVICES]]" read-only-list
@@ -767,7 +765,7 @@
                 allow-option-label="$i18n{siteSettingsUsbAllowed}"
                 allow-option-icon="settings:usb"
                 block-option-label="$i18n{siteSettingsUsbBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:usb-off">
             </settings-category-default-radio-group>
           </template>
           <template is="dom-if" if="[[!enableContentSettingsRedesign_]]">
@@ -796,7 +794,7 @@
                     "$i18n{siteSettingsSerialPortsAllowed}"
                 allow-option-icon="settings:serial-port"
                 block-option-label="$i18n{siteSettingsSerialPortsBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:serial-port-off">
             </settings-category-default-radio-group>
           </template>
           <template is="dom-if" if="[[!enableContentSettingsRedesign_]]">
@@ -843,7 +841,7 @@
                 allow-option-icon="settings:save-original"
                 block-option-label=
                     "$i18n{siteSettingsFileSystemWriteBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:file-editing-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.FILE_SYSTEM_WRITE]]"
@@ -966,7 +964,7 @@
                 allow-option-label="$i18n{siteSettingsClipboardAllowed}"
                 allow-option-icon="settings:clipboard"
                 block-option-label="$i18n{siteSettingsClipboardBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:clipboard-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.CLIPBOARD]]"
@@ -1005,7 +1003,7 @@
                       "$i18n{siteSettingsPaymentHandlersAllowed}"
                   allow-option-icon="settings:payment-handler"
                   block-option-label="$i18n{siteSettingsPaymentHandlersBlocked}"
-                  block-option-icon="settings:block">
+                  block-option-icon="settings:payment-handler-off">
               </settings-category-default-radio-group>
               <category-setting-exceptions
                   category="[[ContentSettingsTypes.PAYMENT_HANDLER]]"
@@ -1067,7 +1065,7 @@
                 allow-option-label="$i18n{siteSettingsVrAllowed}"
                 allow-option-icon="settings:vr-headset"
                 block-option-label="$i18n{siteSettingsVrBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:vr-headset-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.VR]]"
@@ -1107,7 +1105,7 @@
                 allow-option-label="$i18n{siteSettingsArAllowed}"
                 allow-option-icon="settings:vr-headset"
                 block-option-label="$i18n{siteSettingsArBlocked}"
-                block-option-icon="settings:block">
+                block-option-icon="settings:vr-headset-off">
             </settings-category-default-radio-group>
             <category-setting-exceptions
                 category="[[ContentSettingsTypes.AR]]"
diff --git a/chrome/browser/resources/settings/site_settings/site_details.html b/chrome/browser/resources/settings/site_settings/site_details.html
index cf100e2d..196985e 100644
--- a/chrome/browser/resources/settings/site_settings/site_details.html
+++ b/chrome/browser/resources/settings/site_settings/site_details.html
@@ -123,7 +123,7 @@
     </div>
     <div class="list-frame">
       <site-details-permission category="[[ContentSettingsTypes.GEOLOCATION]]"
-          icon="cr:location-on" label="$i18n{siteSettingsLocation}">
+          icon="settings:location-on" label="$i18n{siteSettingsLocation}">
       </site-details-permission>
       <site-details-permission category="[[ContentSettingsTypes.CAMERA]]"
           icon="cr:videocam" label="$i18n{siteSettingsCamera}">
diff --git a/chrome/browser/resources/settings/site_settings_page/site_settings_page.js b/chrome/browser/resources/settings/site_settings_page/site_settings_page.js
index 58e6652..5b93103 100644
--- a/chrome/browser/resources/settings/site_settings_page/site_settings_page.js
+++ b/chrome/browser/resources/settings/site_settings_page/site_settings_page.js
@@ -141,7 +141,7 @@
       route: routes.SITE_SETTINGS_LOCATION,
       id: Id.GEOLOCATION,
       label: 'siteSettingsLocation',
-      icon: 'cr:location-on',
+      icon: 'settings:location-on',
       enabledLabel: redesignEnabled ? 'siteSettingsLocationAllowed' :
                                       'siteSettingsAskBeforeAccessing',
       disabledLabel: redesignEnabled ? 'siteSettingsLocationBlocked' :
diff --git a/chrome/browser/resources/sync_file_system_internals/BUILD.gn b/chrome/browser/resources/sync_file_system_internals/BUILD.gn
index 4caa1c3..6c2c3de 100644
--- a/chrome/browser/resources/sync_file_system_internals/BUILD.gn
+++ b/chrome/browser/resources/sync_file_system_internals/BUILD.gn
@@ -54,7 +54,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [
     ":dump_database",
     ":extension_statuses",
diff --git a/chrome/browser/resources/tab_strip/BUILD.gn b/chrome/browser/resources/tab_strip/BUILD.gn
index 54cc2f3..e51d545 100644
--- a/chrome/browser/resources/tab_strip/BUILD.gn
+++ b/chrome/browser/resources/tab_strip/BUILD.gn
@@ -86,7 +86,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [
     ":alert_indicator",
     ":alert_indicators",
diff --git a/chrome/browser/resources/usb_internals/BUILD.gn b/chrome/browser/resources/usb_internals/BUILD.gn
index 428e248..77f2670b 100644
--- a/chrome/browser/resources/usb_internals/BUILD.gn
+++ b/chrome/browser/resources/usb_internals/BUILD.gn
@@ -50,7 +50,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   closure_flags = default_closure_args + mojom_js_args + [
                     "js_module_root=" + rebase_path(".", root_build_dir),
                     "js_module_root=" + rebase_path(
diff --git a/chrome/browser/resources/video_tutorials/BUILD.gn b/chrome/browser/resources/video_tutorials/BUILD.gn
index a1d9ebd..aebba94 100644
--- a/chrome/browser/resources/video_tutorials/BUILD.gn
+++ b/chrome/browser/resources/video_tutorials/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":video_player" ]
 }
 
diff --git a/chrome/browser/resources/webapks/BUILD.gn b/chrome/browser/resources/webapks/BUILD.gn
index a20f8a8..d73e270 100644
--- a/chrome/browser/resources/webapks/BUILD.gn
+++ b/chrome/browser/resources/webapks/BUILD.gn
@@ -8,7 +8,6 @@
 import("//ui/webui/resources/tools/generate_grd.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":about_webapks" ]
 }
 
diff --git a/chrome/browser/resources/webui_js_error/BUILD.gn b/chrome/browser/resources/webui_js_error/BUILD.gn
index 35637a8..c927802 100644
--- a/chrome/browser/resources/webui_js_error/BUILD.gn
+++ b/chrome/browser/resources/webui_js_error/BUILD.gn
@@ -68,7 +68,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":webui_js_error" ]
 }
 
diff --git a/chrome/browser/translate/translate_model_service_browsertest.cc b/chrome/browser/translate/translate_model_service_browsertest.cc
index 107fd65..69d769ea 100644
--- a/chrome/browser/translate/translate_model_service_browsertest.cc
+++ b/chrome/browser/translate/translate_model_service_browsertest.cc
@@ -13,6 +13,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -246,8 +247,17 @@
       "LanguageDetection.TFLiteModel.WasModelAvailableForDetection", false, 1);
 }
 
+// Disabled due to flake: https://crbug.com/1177331
+#if defined(OS_MAC)
+#define MAYBE_LanguageDetectionModelAvailableForDetection \
+  DISABLED_LanguageDetectionModelAvailableForDetection
+#else
+#define MAYBE_LanguageDetectionModelAvailableForDetection \
+  LanguageDetectionModelAvailableForDetection
+#endif
+
 IN_PROC_BROWSER_TEST_F(TranslateModelServiceBrowserTest,
-                       LanguageDetectionModelAvailableForDetection) {
+                       MAYBE_LanguageDetectionModelAvailableForDetection) {
   base::HistogramTester histogram_tester;
   OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
       ->OverrideTargetModelFileForTesting(
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index ff832fd..f774e1bc 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3930,6 +3930,7 @@
       "views/payments/error_message_view_controller.h",
       "views/payments/order_summary_view_controller.cc",
       "views/payments/order_summary_view_controller.h",
+      "views/payments/payment_credential_enrollment_view.cc",
       "views/payments/payment_handler_modal_dialog_manager_delegate.cc",
       "views/payments/payment_handler_modal_dialog_manager_delegate.h",
       "views/payments/payment_handler_web_flow_view_controller.cc",
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 95fa6007..a2dc77f3 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -779,29 +779,36 @@
       </message>
 
       <!-- Privacy Sandbox Settings. Used by //chrome/browser/privacy_sandbox. -->
-      <message name="IDS_PREFS_PRIVACY_SANDBOX" desc="Title for the 'Privacy sandbox' page.">
-        Privacy sandbox
+      <message name="IDS_PREFS_PRIVACY_SANDBOX" desc="Title for the 'Privacy Sandbox' page.">
+        Privacy Sandbox
       </message>
       <message name="IDS_PRIVACY_SANDBOX_STATUS_ENABLED" desc="Status text, displayed when the Privacy sandbox APIs are enabled.">
-        Trial is enabled
+        Trial features are on
       </message>
       <message name="IDS_PRIVACY_SANDBOX_STATUS_DISABLED" desc="Status text, displayed when the Privacy sandbox APIs are disabled.">
-        Trial is disabled
+        Trial features are off
       </message>
-      <message name="IDS_PRIVACY_SANDBOX_DESCRIPTION" desc="">
-        Privacy Sandbox is a set of new technologies that will eventually protect people from cross-site tracking mechanisms, like third-party cookies, while preserving web content and services that depend on those mechanisms today. Chrome is working with website developers, publishers, advertisers, and other browsers to develop this new technology.
+      <message name="IDS_PRIVACY_SANDBOX_DESCRIPTION_TITLE" desc="Title for the description text of 'Privacy Sandbox'.">
+        About Privacy Sandbox
       </message>
-      <message name="IDS_PRIVACY_SANDBOX_TRIAL_TITLE" desc="">
-        Privacy Sandbox trial
-      </message>
-      <message name="IDS_PRIVACY_SANDBOX_TRIAL_DESCRIPTION" desc="">
-        Privacy Sandbox is not widely released yet. During this trial period:\n<ph name="BEGIN_LIST_ITEM1">&lt;li1&gt;</ph>Website and app developers can try out the ability to create relevant experiences without accessing uniquely identifiable information about you<ph name="END_LIST_ITEM1">&lt;/li1&gt;</ph>\n<ph name="BEGIN_LIST_ITEM2">&lt;li2&gt;</ph>Users can opt out of the trial using the controls on this page.<ph name="END_LIST_ITEM2">&lt;/li2&gt;</ph>
+      <message name="IDS_PRIVACY_SANDBOX_DESCRIPTION" desc="Description of the 'Privacy Sandbox'.">
+        Privacy Sandbox is an ongoing initiative to preserve the open web that will help safeguard you from tracking mechanisms.
+
+Today, websites rely on many technologies, like third-party cookies, for important services like showing relevant ads and measuring a site’s performance.
+
+Privacy Sandbox preserves the vitality of the open web by creating better ways to perform these services – without breaking sites, and while preventing you from being surreptitiously tracked across the web.
+
+Privacy Sandbox is still in active development and is available in selected regions. For now, sites may try out Privacy Sandbox while continuing to use current web technologies like third-party cookies.
       </message>
       <message name="IDS_PRIVACY_SANDBOX_TOGGLE" desc="Title for the Privacy Sandbox toggle.">
-        Crowdmasking
+        Enable Privacy Sandbox
       </message>
-      <message name="IDS_PRIVACY_SANDBOX_TOGGLE_DESCRIPTION" desc="">
-        When on, websites won’t be able to use information that personally identifies you to make their sites more relevant. Similar to a crowd in a concert, sites can only know that thousands of users share a similar interest.\n\nYour “crowd” is based on sites you’ve visited. Advertisers can only see what sites the whole crowd visits, not what sites you personally visit.
+      <message name="IDS_PRIVACY_SANDBOX_TOGGLE_DESCRIPTION" desc="Description for the Privacy Sandbox toggle.">
+        When enabled, sites may use the privacy-preserving techniques shown here to provide their content and services. These include alternatives to cross-site tracking. More trials may be added over time.
+
+<ph name="BEGIN_LIST_ITEM1">&lt;li1&gt;</ph>Advertisers can learn when thousands of users share a similar interest – like a crowd at a concert — and select ads for the crowd, rather than an individual person.<ph name="END_LIST_ITEM1">&lt;/li1&gt;</ph>
+
+<ph name="BEGIN_LIST_ITEM2">&lt;li2&gt;</ph>Advertisers can study the effectiveness of ads in a way that does not track you across sites.<ph name="END_LIST_ITEM2">&lt;/li2&gt;</ph>
       </message>
       <message name="IDS_PRIVACY_SANDBOX_SNACKBAR_MESSAGE" desc="The text displayed in the snackbar, which gives the user an option to navigate to the Privacy Sandbox settings page. 'Privacy sandbox' has TC ID 5753235213964358658.">
         Explore the Privacy Sandbox
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PREFS_PRIVACY_SANDBOX.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PREFS_PRIVACY_SANDBOX.png.sha1
index c2faed2..dd5919f 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PREFS_PRIVACY_SANDBOX.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PREFS_PRIVACY_SANDBOX.png.sha1
@@ -1 +1 @@
-c7e4215bdaca99c5779f861088625bf12a23893f
\ No newline at end of file
+7bb5f25463428800bba94e9fbbea9b34b42c997c
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_DESCRIPTION.png.sha1
index e390b7c6..dd5919f 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_DESCRIPTION.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_DESCRIPTION.png.sha1
@@ -1 +1 @@
-ad621d4b76db8fe8a958472c451b28a81cf65dfe
\ No newline at end of file
+7bb5f25463428800bba94e9fbbea9b34b42c997c
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_DESCRIPTION_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_DESCRIPTION_TITLE.png.sha1
new file mode 100644
index 0000000..dd5919f
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_DESCRIPTION_TITLE.png.sha1
@@ -0,0 +1 @@
+7bb5f25463428800bba94e9fbbea9b34b42c997c
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_STATUS_DISABLED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_STATUS_DISABLED.png.sha1
index ac15a45..28c8b4e 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_STATUS_DISABLED.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_STATUS_DISABLED.png.sha1
@@ -1 +1 @@
-ff94b6c14abc962b81d61ff7943303d0add5de84
\ No newline at end of file
+7f115a6556858b151b4d0bc976f07b274db84c7d
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_STATUS_ENABLED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_STATUS_ENABLED.png.sha1
index a0eb8847..af79b366 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_STATUS_ENABLED.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_STATUS_ENABLED.png.sha1
@@ -1 +1 @@
-4e7fbde10721a1496a8a78e30f16931fc4476524
\ No newline at end of file
+10dcebf7feff1e55fc57ff45e46b834e991a81a9
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TOGGLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TOGGLE.png.sha1
index e390b7c6..dd5919f 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TOGGLE.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TOGGLE.png.sha1
@@ -1 +1 @@
-ad621d4b76db8fe8a958472c451b28a81cf65dfe
\ No newline at end of file
+7bb5f25463428800bba94e9fbbea9b34b42c997c
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TOGGLE_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TOGGLE_DESCRIPTION.png.sha1
index e390b7c6..dd5919f 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TOGGLE_DESCRIPTION.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TOGGLE_DESCRIPTION.png.sha1
@@ -1 +1 @@
-ad621d4b76db8fe8a958472c451b28a81cf65dfe
\ No newline at end of file
+7bb5f25463428800bba94e9fbbea9b34b42c997c
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TRIAL_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TRIAL_DESCRIPTION.png.sha1
deleted file mode 100644
index e390b7c6..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TRIAL_DESCRIPTION.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ad621d4b76db8fe8a958472c451b28a81cf65dfe
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TRIAL_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TRIAL_TITLE.png.sha1
deleted file mode 100644
index e390b7c6..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_TRIAL_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ad621d4b76db8fe8a958472c451b28a81cf65dfe
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc
index a25f9e5..bd41b2e 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc
@@ -166,9 +166,7 @@
 
 void AppServiceAppWindowCrostiniTracker::OnWindowDestroying(
     aura::Window* window) {
-  base::EraseIf(activation_permissions_, [&window](const auto& element) {
-    return element.first == window;
-  });
+  activation_permissions_.erase(window);
 }
 
 void AppServiceAppWindowCrostiniTracker::OnAppLaunchRequested(
@@ -177,6 +175,8 @@
   crostini_app_display_.Register(app_id, display_id);
   // Remove the old permissions and add a permission for every window the app
   // currently has open.
+  for (aura::Window* window : activation_permissions_)
+    exo::RevokePermissionToActivate(window);
   activation_permissions_.clear();
   ash::ShelfModel* model = app_service_controller_->owner()->shelf_model();
   int index = model->ItemIndexByAppID(app_id);
@@ -192,10 +192,9 @@
   if (!launcher_item_controller)
     return;
   for (AppWindowBase* app_window : launcher_item_controller->windows()) {
-    activation_permissions_.emplace(
-        app_window->GetNativeWindow(),
-        exo::GrantPermissionToActivate(app_window->GetNativeWindow(),
-                                       kSelfActivationTimeout));
+    exo::GrantPermissionToActivate(app_window->GetNativeWindow(),
+                                   kSelfActivationTimeout);
+    activation_permissions_.insert(app_window->GetNativeWindow());
   }
 }
 
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.h b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.h
index 25f8d0d..e8258aad 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.h
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_UI_ASH_LAUNCHER_APP_SERVICE_APP_SERVICE_APP_WINDOW_CROSTINI_TRACKER_H_
 
 #include "ash/public/cpp/shelf_types.h"
-#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "chrome/browser/ui/ash/launcher/crostini_app_display.h"
 
 class AppServiceAppWindowLauncherController;
@@ -53,10 +53,9 @@
 
   CrostiniAppDisplay crostini_app_display_;
 
-  // Permission objects that allow this controller to manage which application
-  // windows can activate themselves.
-  base::flat_map<aura::Window*, std::unique_ptr<exo::Permission>>
-      activation_permissions_;
+  // Windows that have been granted the permission to activate via the
+  // exo::Permission window property.
+  base::flat_set<aura::Window*> activation_permissions_;
 };
 
 #endif  // CHROME_BROWSER_UI_ASH_LAUNCHER_APP_SERVICE_APP_SERVICE_APP_WINDOW_CROSTINI_TRACKER_H_
diff --git a/chrome/browser/ui/ash/launcher/app_service/exo_app_type_resolver.cc b/chrome/browser/ui/ash/launcher/app_service/exo_app_type_resolver.cc
index 3b420e6..bf7de42 100644
--- a/chrome/browser/ui/ash/launcher/app_service/exo_app_type_resolver.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/exo_app_type_resolver.cc
@@ -10,6 +10,7 @@
 #include "chromeos/crosapi/cpp/crosapi_constants.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "components/arc/arc_util.h"
+#include "components/exo/permission.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/base/class_property.h"
 
@@ -30,6 +31,11 @@
   if (IsLacrosAppId(app_id)) {
     out_properties_container.SetProperty(
         aura::client::kAppType, static_cast<int>(ash::AppType::LACROS));
+    // Lacros is trusted not to abuse window activation, so grant it a
+    // non-expiring permission to activate.
+    out_properties_container.SetProperty(
+        exo::kPermissionKey,
+        new exo::Permission(exo::Permission::Capability::kActivate));
   } else if (arc::GetTaskIdFromWindowAppId(app_id) != arc::kNoTaskId) {
     out_properties_container.SetProperty(
         aura::client::kAppType, static_cast<int>(ash::AppType::ARC_APP));
diff --git a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
index 1c6e94d..5de891f 100644
--- a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
@@ -55,9 +55,6 @@
 #include "components/policy/policy_constants.h"
 #include "content/public/browser/native_web_keyboard_event.h"
 #include "content/public/browser/navigation_controller.h"
-#include "content/public/browser/notification_registrar.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_types.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
@@ -85,33 +82,6 @@
 
 namespace {
 
-// Counts the number of RenderViewHosts created.
-class CountRenderViewHosts : public content::NotificationObserver {
- public:
-  CountRenderViewHosts() : count_(0) {
-    registrar_.Add(this,
-                   content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
-                   content::NotificationService::AllSources());
-  }
-  ~CountRenderViewHosts() override {}
-
-  int GetRenderViewHostCreatedCount() const { return count_; }
-
- private:
-  // content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override {
-    count_++;
-  }
-
-  content::NotificationRegistrar registrar_;
-
-  int count_;
-
-  DISALLOW_COPY_AND_ASSIGN(CountRenderViewHosts);
-};
-
 class CloseObserver : public content::WebContentsObserver {
  public:
   explicit CloseObserver(WebContents* contents)
@@ -205,21 +175,16 @@
       const base::string16& expected_title = base::ASCIIToUTF16("PASS")) {
     GURL url(embedded_test_server()->GetURL(test_name));
 
-    CountRenderViewHosts counter;
-
     ui_test_utils::NavigateToURL(browser, url);
 
     // Since the popup blocker blocked the window.open, there should be only one
-    // tab.
+    // tab and window in the profile.
     EXPECT_EQ(1u, chrome::GetBrowserCount(browser->profile()));
     EXPECT_EQ(1, browser->tab_strip_model()->count());
     WebContents* web_contents =
         browser->tab_strip_model()->GetActiveWebContents();
     EXPECT_EQ(url, web_contents->GetURL());
 
-    // And no new RVH created.
-    EXPECT_EQ(0, counter.GetRenderViewHostCreatedCount());
-
     ui_test_utils::TabAddedWaiter tab_add(browser);
 
     // Launch the blocked popup.
diff --git a/chrome/browser/ui/browser_navigator_browsertest.cc b/chrome/browser/ui/browser_navigator_browsertest.cc
index 4afbc64..3920c4d 100644
--- a/chrome/browser/ui/browser_navigator_browsertest.cc
+++ b/chrome/browser/ui/browser_navigator_browsertest.cc
@@ -38,7 +38,6 @@
 #include "components/omnibox/browser/omnibox_view.h"
 #include "components/prefs/pref_service.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/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -223,14 +222,6 @@
             browser->tab_strip_model()->GetActiveWebContents()->GetURL());
 }
 
-void BrowserNavigatorTest::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED, type);
-  ++created_tab_contents_count_;
-}
-
 // Subclass of TestNavigationObserver that saves ChromeNavigationUIData.
 class TestNavigationUIDataObserver : public content::TestNavigationObserver {
  public:
@@ -297,16 +288,6 @@
 IN_PROC_BROWSER_TEST_F(BrowserNavigatorTest, Disposition_SingletonTabExisting) {
   const GURL singleton_url1("http://maps.google.com/");
 
-  // Register for a notification if an additional WebContents was instantiated.
-  // Opening a Singleton tab that is already opened should not be opening a new
-  // tab nor be creating a new WebContents object.
-  content::NotificationRegistrar registrar;
-
-  // As the registrar object goes out of scope, this will get unregistered
-  registrar.Add(this,
-                content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
-                content::NotificationService::AllSources());
-
   chrome::AddSelectedTabWithURL(browser(), singleton_url1,
                                 ui::PAGE_TRANSITION_LINK);
   chrome::AddSelectedTabWithURL(browser(), GetGoogleURL(),
@@ -314,10 +295,9 @@
 
   // We should have one browser with 3 tabs, the 3rd selected.
   EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
+  EXPECT_EQ(3, browser()->tab_strip_model()->count());
   EXPECT_EQ(2, browser()->tab_strip_model()->active_index());
 
-  unsigned int previous_tab_contents_count = created_tab_contents_count_ = 0;
-
   // Navigate to singleton_url1.
   NavigateParams params(MakeNavigateParams());
   params.disposition = WindowOpenDisposition::SINGLETON_TAB;
@@ -329,7 +309,8 @@
   EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
 
   // No tab contents should have been created
-  EXPECT_EQ(previous_tab_contents_count, created_tab_contents_count_);
+  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
+  EXPECT_EQ(3, browser()->tab_strip_model()->count());
 }
 
 IN_PROC_BROWSER_TEST_F(BrowserNavigatorTest,
diff --git a/chrome/browser/ui/browser_navigator_browsertest.h b/chrome/browser/ui/browser_navigator_browsertest.h
index c6379bb..e6fbc54 100644
--- a/chrome/browser/ui/browser_navigator_browsertest.h
+++ b/chrome/browser/ui/browser_navigator_browsertest.h
@@ -12,7 +12,6 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/test/base/in_process_browser_test.h"
-#include "content/public/browser/notification_types.h"
 
 class Profile;
 
@@ -23,8 +22,7 @@
 // Browsertest class for testing the browser navigation. It is also a base class
 // for the |BrowserGuestModeNavigation| which tests navigation while in guest
 // mode.
-class BrowserNavigatorTest : public InProcessBrowserTest,
-                             public content::NotificationObserver {
+class BrowserNavigatorTest : public InProcessBrowserTest {
  protected:
   NavigateParams MakeNavigateParams() const;
   NavigateParams MakeNavigateParams(Browser* browser) const;
@@ -40,11 +38,6 @@
                                     const ui::PageTransition& page_transition);
   void RunDoNothingIfIncognitoIsForcedTest(const GURL& url);
 
-  // content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
-
   bool OpenPOSTURLInNewForegroundTabAndGetTitle(const GURL& url,
                                                 const std::string& post_data,
                                                 bool is_browser_initiated,
diff --git a/chrome/browser/ui/test/test_browser_dialog.cc b/chrome/browser/ui/test/test_browser_dialog.cc
index f64c72d..dc4c9ee 100644
--- a/chrome/browser/ui/test/test_browser_dialog.cc
+++ b/chrome/browser/ui/test/test_browser_dialog.cc
@@ -100,7 +100,7 @@
   widgets_ = added;
 
   if (added.size() != 1) {
-    DLOG(INFO) << "VerifyUi(): Expected 1 added widget; got " << added.size();
+    LOG(INFO) << "VerifyUi(): Expected 1 added widget; got " << added.size();
     if (added.size() > 1) {
       base::string16 widget_title_log =
           base::ASCIIToUTF16("Added Widgets are: ");
@@ -108,7 +108,7 @@
         widget_title_log += widget->widget_delegate()->GetWindowTitle() +
                             base::ASCIIToUTF16(" ");
       }
-      DLOG(INFO) << widget_title_log;
+      LOG(INFO) << widget_title_log;
     }
     return false;
   }
@@ -132,7 +132,7 @@
   const std::string screenshot_name = base::StrCat(
       {test_info->test_case_name(), "_", test_info->name(), "_", baseline_});
   if (!VerifyPixelUi(dialog_widget, "BrowserUiDialog", screenshot_name)) {
-    DLOG(INFO) << "VerifyUi(): Pixel compare failed.";
+    LOG(INFO) << "VerifyUi(): Pixel compare failed.";
     return false;
   }
   if (is_active)
@@ -153,7 +153,7 @@
       screen->GetDisplayNearestWindow(native_window).work_area();
 
   const bool dialog_in_bounds = display_work_area.Contains(dialog_bounds);
-  DLOG_IF(INFO, !dialog_in_bounds)
+  LOG_IF(INFO, !dialog_in_bounds)
       << "VerifyUi(): Dialog bounds " << dialog_bounds.ToString()
       << " outside of display work area " << display_work_area.ToString();
   return dialog_in_bounds;
diff --git a/chrome/browser/ui/views/payments/payment_credential_enrollment_view.cc b/chrome/browser/ui/views/payments/payment_credential_enrollment_view.cc
new file mode 100644
index 0000000..8c11b19
--- /dev/null
+++ b/chrome/browser/ui/views/payments/payment_credential_enrollment_view.cc
@@ -0,0 +1,20 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/payments/content/payment_credential_enrollment_view.h"
+
+namespace payments {
+
+// static
+base::WeakPtr<PaymentCredentialEnrollmentView>
+PaymentCredentialEnrollmentView::Create(
+    const PaymentUIObserver* payment_ui_observer) {
+  // TODO(crbug.com/1176368): Return a PaymentCredentialEnrollmentDialogView.
+  return nullptr;
+}
+
+PaymentCredentialEnrollmentView::PaymentCredentialEnrollmentView() = default;
+PaymentCredentialEnrollmentView::~PaymentCredentialEnrollmentView() = default;
+
+}  // namespace payments
diff --git a/chrome/browser/vr/animation.cc b/chrome/browser/vr/animation.cc
index ffebccd..9fb452d 100644
--- a/chrome/browser/vr/animation.cc
+++ b/chrome/browser/vr/animation.cc
@@ -9,7 +9,6 @@
 #include "base/numerics/ranges.h"
 #include "base/stl_util.h"
 #include "cc/animation/animation_curve.h"
-#include "cc/animation/animation_target.h"
 #include "cc/animation/keyframe_effect.h"
 #include "cc/animation/keyframed_animation_curve.h"
 #include "chrome/browser/vr/elements/ui_element.h"
@@ -109,34 +108,91 @@
 template <typename T>
 struct AnimationTraits {};
 
-#define DEFINE_ANIMATION_TRAITS(value_type, name, notify_name)                \
+#define DEFINE_ANIMATION_TRAITS(value_type, name)                             \
   template <>                                                                 \
   struct AnimationTraits<value_type> {                                        \
     typedef value_type ValueType;                                             \
+    typedef cc::name##AnimationCurve::Target TargetType;                      \
     typedef cc::name##AnimationCurve CurveType;                               \
     typedef cc::Keyframed##name##AnimationCurve KeyframedCurveType;           \
     typedef cc::name##Keyframe KeyframeType;                                  \
-    static const CurveType* ToDerivedCurve(const cc::AnimationCurve& curve) { \
-      return curve.To##name##AnimationCurve();                                \
+    static const CurveType* ToDerivedCurve(const cc::AnimationCurve* curve) { \
+      return cc::name##AnimationCurve::To##name##AnimationCurve(curve);       \
     }                                                                         \
-    static void NotifyClientValueAnimated(                                    \
-        cc::AnimationTarget* animation_target,                                \
-        const ValueType& target_value,                                        \
-        int target_property) {                                                \
-      animation_target->NotifyClient##notify_name##Animated(                  \
-          target_value, target_property, nullptr);                            \
+    static void OnValueAnimated(cc::name##AnimationCurve::Target* target,     \
+                                const ValueType& target_value,                \
+                                int target_property) {                        \
+      target->On##name##Animated(target_value, target_property, nullptr);     \
     }                                                                         \
   }
 
-DEFINE_ANIMATION_TRAITS(float, Float, Float);
-DEFINE_ANIMATION_TRAITS(gfx::TransformOperations,
-                        Transform,
-                        TransformOperations);
-DEFINE_ANIMATION_TRAITS(gfx::SizeF, Size, Size);
-DEFINE_ANIMATION_TRAITS(SkColor, Color, Color);
+DEFINE_ANIMATION_TRAITS(float, Float);
+DEFINE_ANIMATION_TRAITS(gfx::TransformOperations, Transform);
+DEFINE_ANIMATION_TRAITS(gfx::SizeF, Size);
+DEFINE_ANIMATION_TRAITS(SkColor, Color);
 
 #undef DEFINE_ANIMATION_TRAITS
 
+template <typename ValueType>
+void TransitionValueTo(Animation* animation,
+                       typename AnimationTraits<ValueType>::TargetType* target,
+                       base::TimeTicks monotonic_time,
+                       int target_property,
+                       const ValueType& from,
+                       const ValueType& to) {
+  DCHECK(target);
+
+  if (animation->transition().target_properties.find(target_property) ==
+      animation->transition().target_properties.end()) {
+    AnimationTraits<ValueType>::OnValueAnimated(target, to, target_property);
+    return;
+  }
+
+  cc::KeyframeModel* running_keyframe_model =
+      animation->GetRunningKeyframeModelForProperty(target_property);
+
+  ValueType effective_current = from;
+
+  if (running_keyframe_model) {
+    const auto* curve = AnimationTraits<ValueType>::ToDerivedCurve(
+        running_keyframe_model->curve());
+
+    if (running_keyframe_model->IsFinishedAt(monotonic_time)) {
+      effective_current = curve->GetValue(GetEndTime(running_keyframe_model));
+    } else {
+      if (SufficientlyEqual(
+              to, curve->GetValue(GetEndTime(running_keyframe_model)))) {
+        return;
+      }
+      if (SufficientlyEqual(
+              to, curve->GetValue(GetStartTime(running_keyframe_model)))) {
+        ReverseKeyframeModel(monotonic_time, running_keyframe_model);
+        return;
+      }
+    }
+  } else if (SufficientlyEqual(to, from)) {
+    return;
+  }
+
+  animation->RemoveKeyframeModels(target_property);
+
+  std::unique_ptr<typename AnimationTraits<ValueType>::KeyframedCurveType>
+      curve(AnimationTraits<ValueType>::KeyframedCurveType::Create());
+
+  curve->AddKeyframe(AnimationTraits<ValueType>::KeyframeType::Create(
+      base::TimeDelta(), effective_current, CreateTransitionTimingFunction()));
+
+  curve->AddKeyframe(AnimationTraits<ValueType>::KeyframeType::Create(
+      animation->transition().duration, to, CreateTransitionTimingFunction()));
+
+  curve->set_target(target);
+
+  animation->AddKeyframeModel(cc::KeyframeModel::Create(
+      std::move(curve), Animation::GetNextKeyframeModelId(),
+      Animation::GetNextGroupId(),
+      cc::KeyframeModel::TargetPropertyId(target_property)));
+}
+
 }  // namespace
 
 int Animation::GetNextKeyframeModelId() {
@@ -178,16 +234,13 @@
 
 void Animation::TickInternal(base::TimeTicks monotonic_time,
                              bool include_infinite_animations) {
-  DCHECK(target_);
-
   StartKeyframeModels(monotonic_time, include_infinite_animations);
 
   for (auto& keyframe_model : keyframe_models_) {
     if (!include_infinite_animations &&
         keyframe_model->iterations() == std::numeric_limits<double>::infinity())
       continue;
-    cc::KeyframeEffect::TickKeyframeModel(monotonic_time, keyframe_model.get(),
-                                          target_);
+    cc::KeyframeEffect::TickKeyframeModel(monotonic_time, keyframe_model.get());
   }
 
   // Remove finished keyframe_models.
@@ -222,35 +275,41 @@
   transition_.duration = delta;
 }
 
-void Animation::TransitionFloatTo(base::TimeTicks monotonic_time,
+void Animation::TransitionFloatTo(cc::FloatAnimationCurve::Target* target,
+                                  base::TimeTicks monotonic_time,
                                   int target_property,
-                                  float current,
-                                  float target) {
-  TransitionValueTo<float>(monotonic_time, target_property, current, target);
+                                  float from,
+                                  float to) {
+  TransitionValueTo<float>(this, target, monotonic_time, target_property, from,
+                           to);
 }
 
 void Animation::TransitionTransformOperationsTo(
+    cc::TransformAnimationCurve::Target* target,
     base::TimeTicks monotonic_time,
     int target_property,
-    const gfx::TransformOperations& current,
-    const gfx::TransformOperations& target) {
-  TransitionValueTo<gfx::TransformOperations>(monotonic_time, target_property,
-                                              current, target);
+    const gfx::TransformOperations& from,
+    const gfx::TransformOperations& to) {
+  TransitionValueTo<gfx::TransformOperations>(this, target, monotonic_time,
+                                              target_property, from, to);
 }
 
-void Animation::TransitionSizeTo(base::TimeTicks monotonic_time,
+void Animation::TransitionSizeTo(cc::SizeAnimationCurve::Target* target,
+                                 base::TimeTicks monotonic_time,
                                  int target_property,
-                                 const gfx::SizeF& current,
-                                 const gfx::SizeF& target) {
-  TransitionValueTo<gfx::SizeF>(monotonic_time, target_property, current,
-                                target);
+                                 const gfx::SizeF& from,
+                                 const gfx::SizeF& to) {
+  TransitionValueTo<gfx::SizeF>(this, target, monotonic_time, target_property,
+                                from, to);
 }
 
-void Animation::TransitionColorTo(base::TimeTicks monotonic_time,
+void Animation::TransitionColorTo(cc::ColorAnimationCurve::Target* target,
+                                  base::TimeTicks monotonic_time,
                                   int target_property,
-                                  SkColor current,
-                                  SkColor target) {
-  TransitionValueTo<SkColor>(monotonic_time, target_property, current, target);
+                                  SkColor from,
+                                  SkColor to) {
+  TransitionValueTo<SkColor>(this, target, monotonic_time, target_property,
+                             from, to);
 }
 
 bool Animation::IsAnimatingProperty(int property) const {
@@ -310,62 +369,6 @@
   }
 }
 
-template <typename ValueType>
-void Animation::TransitionValueTo(base::TimeTicks monotonic_time,
-                                  int target_property,
-                                  const ValueType& current,
-                                  const ValueType& target) {
-  DCHECK(target_);
-
-  if (transition_.target_properties.find(target_property) ==
-      transition_.target_properties.end()) {
-    AnimationTraits<ValueType>::NotifyClientValueAnimated(target_, target,
-                                                          target_property);
-    return;
-  }
-
-  cc::KeyframeModel* running_keyframe_model =
-      GetRunningKeyframeModelForProperty(target_property);
-
-  ValueType effective_current = current;
-
-  if (running_keyframe_model) {
-    const auto* curve = AnimationTraits<ValueType>::ToDerivedCurve(
-        *running_keyframe_model->curve());
-
-    if (running_keyframe_model->IsFinishedAt(monotonic_time)) {
-      effective_current = curve->GetValue(GetEndTime(running_keyframe_model));
-    } else {
-      if (SufficientlyEqual(
-              target, curve->GetValue(GetEndTime(running_keyframe_model)))) {
-        return;
-      }
-      if (SufficientlyEqual(
-              target, curve->GetValue(GetStartTime(running_keyframe_model)))) {
-        ReverseKeyframeModel(monotonic_time, running_keyframe_model);
-        return;
-      }
-    }
-  } else if (SufficientlyEqual(target, current)) {
-    return;
-  }
-
-  RemoveKeyframeModels(target_property);
-
-  std::unique_ptr<typename AnimationTraits<ValueType>::KeyframedCurveType>
-      curve(AnimationTraits<ValueType>::KeyframedCurveType::Create());
-
-  curve->AddKeyframe(AnimationTraits<ValueType>::KeyframeType::Create(
-      base::TimeDelta(), effective_current, CreateTransitionTimingFunction()));
-
-  curve->AddKeyframe(AnimationTraits<ValueType>::KeyframeType::Create(
-      transition_.duration, target, CreateTransitionTimingFunction()));
-
-  AddKeyframeModel(cc::KeyframeModel::Create(
-      std::move(curve), GetNextKeyframeModelId(), GetNextGroupId(),
-      cc::KeyframeModel::TargetPropertyId(target_property)));
-}
-
 cc::KeyframeModel* Animation::GetRunningKeyframeModelForProperty(
     int target_property) const {
   for (auto& keyframe_model : keyframe_models_) {
@@ -397,7 +400,7 @@
     return default_value;
   }
   const auto* curve = AnimationTraits<ValueType>::ToDerivedCurve(
-      *running_keyframe_model->curve());
+      running_keyframe_model->curve());
   return curve->GetValue(GetEndTime(running_keyframe_model));
 }
 
diff --git a/chrome/browser/vr/animation.h b/chrome/browser/vr/animation.h
index 646cd68..f7b8c0e8 100644
--- a/chrome/browser/vr/animation.h
+++ b/chrome/browser/vr/animation.h
@@ -9,15 +9,12 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "cc/animation/animation_curve.h"
 #include "cc/animation/keyframe_model.h"
 #include "chrome/browser/vr/transition.h"
 #include "chrome/browser/vr/vr_ui_export.h"
 #include "third_party/skia/include/core/SkColor.h"
 
-namespace cc {
-class AnimationTarget;
-}  // namespace cc
-
 namespace gfx {
 class SizeF;
 class TransformOperations;
@@ -40,9 +37,6 @@
   Animation();
   ~Animation();
 
-  cc::AnimationTarget* target() const { return target_; }
-  void set_target(cc::AnimationTarget* target) { target_ = target; }
-
   void AddKeyframeModel(std::unique_ptr<cc::KeyframeModel> keyframe_model);
   void RemoveKeyframeModel(int keyframe_model_id);
   void RemoveKeyframeModels(int target_property);
@@ -68,22 +62,27 @@
 
   // TODO(754820): Remove duplicate code from the transition functions
   // by using templates.
-  void TransitionFloatTo(base::TimeTicks monotonic_time,
+  void TransitionFloatTo(cc::FloatAnimationCurve::Target* target,
+                         base::TimeTicks monotonic_time,
                          int target_property,
-                         float current,
-                         float target);
-  void TransitionTransformOperationsTo(base::TimeTicks monotonic_time,
-                                       int target_property,
-                                       const gfx::TransformOperations& current,
-                                       const gfx::TransformOperations& target);
-  void TransitionSizeTo(base::TimeTicks monotonic_time,
+                         float from,
+                         float to);
+  void TransitionTransformOperationsTo(
+      cc::TransformAnimationCurve::Target* target,
+      base::TimeTicks monotonic_time,
+      int target_property,
+      const gfx::TransformOperations& from,
+      const gfx::TransformOperations& to);
+  void TransitionSizeTo(cc::SizeAnimationCurve::Target* target,
+                        base::TimeTicks monotonic_time,
                         int target_property,
-                        const gfx::SizeF& current,
-                        const gfx::SizeF& target);
-  void TransitionColorTo(base::TimeTicks monotonic_time,
+                        const gfx::SizeF& from,
+                        const gfx::SizeF& to);
+  void TransitionColorTo(cc::ColorAnimationCurve::Target* target,
+                         base::TimeTicks monotonic_time,
                          int target_property,
-                         SkColor current,
-                         SkColor target);
+                         SkColor from,
+                         SkColor to);
 
   bool IsAnimatingProperty(int property) const;
 
@@ -94,25 +93,19 @@
   gfx::SizeF GetTargetSizeValue(int target_property,
                                 const gfx::SizeF& default_value) const;
   SkColor GetTargetColorValue(int target_property, SkColor default_value) const;
+  cc::KeyframeModel* GetRunningKeyframeModelForProperty(
+      int target_property) const;
 
  private:
   void TickInternal(base::TimeTicks monotonic_time,
                     bool include_infinite_animations);
   void StartKeyframeModels(base::TimeTicks monotonic_time,
                            bool include_infinite_animations);
-  template <typename ValueType>
-  void TransitionValueTo(base::TimeTicks monotonic_time,
-                         int target_property,
-                         const ValueType& current,
-                         const ValueType& target);
-  cc::KeyframeModel* GetRunningKeyframeModelForProperty(
-      int target_property) const;
   cc::KeyframeModel* GetKeyframeModelForProperty(int target_property) const;
   template <typename ValueType>
   ValueType GetTargetValue(int target_property,
                            const ValueType& default_value) const;
 
-  cc::AnimationTarget* target_ = nullptr;
   KeyframeModels keyframe_models_;
   Transition transition_;
 
diff --git a/chrome/browser/vr/animation_unittest.cc b/chrome/browser/vr/animation_unittest.cc
index 268e440..991f2c0 100644
--- a/chrome/browser/vr/animation_unittest.cc
+++ b/chrome/browser/vr/animation_unittest.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/vr/animation.h"
 
-#include "cc/animation/animation_target.h"
+#include "cc/animation/animation_curve.h"
 #include "cc/test/geometry_test_utils.h"
 #include "chrome/browser/vr/target_property.h"
 #include "chrome/browser/vr/test/animation_utils.h"
@@ -18,7 +18,10 @@
 
 static constexpr float kNoise = 1e-6f;
 
-class TestAnimationTarget : public cc::AnimationTarget {
+class TestAnimationTarget : public cc::SizeAnimationCurve::Target,
+                            public cc::TransformAnimationCurve::Target,
+                            public cc::FloatAnimationCurve::Target,
+                            public cc::ColorAnimationCurve::Target {
  public:
   TestAnimationTarget() {
     layout_offset_.AppendTranslate(0, 0, 0);
@@ -35,16 +38,15 @@
   float opacity() const { return opacity_; }
   SkColor background_color() const { return background_color_; }
 
-  void NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                int target_property_id,
-                                cc::KeyframeModel* keyframe_model) override {
+  void OnSizeAnimated(const gfx::SizeF& size,
+                      int target_property_id,
+                      cc::KeyframeModel* keyframe_model) override {
     size_ = size;
   }
 
-  void NotifyClientTransformOperationsAnimated(
-      const gfx::TransformOperations& operations,
-      int target_property_id,
-      cc::KeyframeModel* keyframe_model) override {
+  void OnTransformAnimated(const gfx::TransformOperations& operations,
+                           int target_property_id,
+                           cc::KeyframeModel* keyframe_model) override {
     if (target_property_id == LAYOUT_OFFSET) {
       layout_offset_ = operations;
     } else {
@@ -52,24 +54,17 @@
     }
   }
 
-  void NotifyClientFloatAnimated(float opacity,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override {
+  void OnFloatAnimated(const float& opacity,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override {
     opacity_ = opacity;
   }
 
-  void NotifyClientColorAnimated(SkColor color,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override {
+  void OnColorAnimated(const SkColor& color,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override {
     background_color_ = color;
   }
-  void NotifyClientFilterAnimated(const cc::FilterOperations& filter,
-                                  int target_property_id,
-                                  cc::KeyframeModel* keyframe_model) override {}
-  void NotifyClientScrollOffsetAnimated(
-      const gfx::ScrollOffset& scroll_offset,
-      int target_property_id,
-      cc::KeyframeModel* keyframe_model) override {}
 
  private:
   gfx::TransformOperations layout_offset_;
@@ -82,10 +77,11 @@
 TEST(AnimationTest, AddRemoveKeyframeModels) {
   Animation animation;
   EXPECT_TRUE(animation.keyframe_models().empty());
+  TestAnimationTarget target;
 
-  animation.AddKeyframeModel(CreateBoundsAnimation(1, 1, gfx::SizeF(10, 100),
-                                                   gfx::SizeF(20, 200),
-                                                   MicrosecondsToDelta(10000)));
+  animation.AddKeyframeModel(
+      CreateBoundsAnimation(&target, 1, 1, gfx::SizeF(10, 100),
+                            gfx::SizeF(20, 200), MicrosecondsToDelta(10000)));
   EXPECT_EQ(1ul, animation.keyframe_models().size());
   EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_type());
 
@@ -93,14 +89,16 @@
   from_operations.AppendTranslate(10, 100, 1000);
   gfx::TransformOperations to_operations;
   to_operations.AppendTranslate(20, 200, 2000);
-  animation.AddKeyframeModel(CreateTransformAnimation(
-      2, 2, from_operations, to_operations, MicrosecondsToDelta(10000)));
+  animation.AddKeyframeModel(
+      CreateTransformAnimation(&target, 2, 2, from_operations, to_operations,
+                               MicrosecondsToDelta(10000)));
 
   EXPECT_EQ(2ul, animation.keyframe_models().size());
   EXPECT_EQ(TRANSFORM, animation.keyframe_models()[1]->target_property_type());
 
-  animation.AddKeyframeModel(CreateTransformAnimation(
-      3, 3, from_operations, to_operations, MicrosecondsToDelta(10000)));
+  animation.AddKeyframeModel(
+      CreateTransformAnimation(&target, 3, 3, from_operations, to_operations,
+                               MicrosecondsToDelta(10000)));
   EXPECT_EQ(3ul, animation.keyframe_models().size());
   EXPECT_EQ(TRANSFORM, animation.keyframe_models()[2]->target_property_type());
 
@@ -115,11 +113,10 @@
 TEST(AnimationTest, AnimationLifecycle) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
 
-  animation.AddKeyframeModel(CreateBoundsAnimation(1, 1, gfx::SizeF(10, 100),
-                                                   gfx::SizeF(20, 200),
-                                                   MicrosecondsToDelta(10000)));
+  animation.AddKeyframeModel(
+      CreateBoundsAnimation(&target, 1, 1, gfx::SizeF(10, 100),
+                            gfx::SizeF(20, 200), MicrosecondsToDelta(10000)));
   EXPECT_EQ(1ul, animation.keyframe_models().size());
   EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_type());
   EXPECT_EQ(cc::KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY,
@@ -144,11 +141,10 @@
 TEST(AnimationTest, AnimationQueue) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
 
-  animation.AddKeyframeModel(CreateBoundsAnimation(1, 1, gfx::SizeF(10, 100),
-                                                   gfx::SizeF(20, 200),
-                                                   MicrosecondsToDelta(10000)));
+  animation.AddKeyframeModel(
+      CreateBoundsAnimation(&target, 1, 1, gfx::SizeF(10, 100),
+                            gfx::SizeF(20, 200), MicrosecondsToDelta(10000)));
   EXPECT_EQ(1ul, animation.keyframe_models().size());
   EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_type());
   EXPECT_EQ(cc::KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY,
@@ -160,16 +156,17 @@
             animation.keyframe_models()[0]->run_state());
   EXPECT_SIZEF_EQ(gfx::SizeF(10, 100), target.size());
 
-  animation.AddKeyframeModel(CreateBoundsAnimation(2, 2, gfx::SizeF(10, 100),
-                                                   gfx::SizeF(20, 200),
-                                                   MicrosecondsToDelta(10000)));
+  animation.AddKeyframeModel(
+      CreateBoundsAnimation(&target, 2, 2, gfx::SizeF(10, 100),
+                            gfx::SizeF(20, 200), MicrosecondsToDelta(10000)));
 
   gfx::TransformOperations from_operations;
   from_operations.AppendTranslate(10, 100, 1000);
   gfx::TransformOperations to_operations;
   to_operations.AppendTranslate(20, 200, 2000);
-  animation.AddKeyframeModel(CreateTransformAnimation(
-      3, 2, from_operations, to_operations, MicrosecondsToDelta(10000)));
+  animation.AddKeyframeModel(
+      CreateTransformAnimation(&target, 3, 2, from_operations, to_operations,
+                               MicrosecondsToDelta(10000)));
 
   EXPECT_EQ(3ul, animation.keyframe_models().size());
   EXPECT_EQ(BOUNDS, animation.keyframe_models()[1]->target_property_type());
@@ -202,7 +199,6 @@
 TEST(AnimationTest, FinishedTransition) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {OPACITY};
   transition.duration = MsToDelta(10);
@@ -213,7 +209,7 @@
 
   float from = 1.0f;
   float to = 0.0f;
-  animation.TransitionFloatTo(start_time, OPACITY, from, to);
+  animation.TransitionFloatTo(&target, start_time, OPACITY, from, to);
 
   animation.Tick(start_time);
   EXPECT_EQ(from, target.opacity());
@@ -221,7 +217,7 @@
   // We now simulate a long pause where the element hasn't been ticked (eg, it
   // may have been hidden). If this happens, the unticked transition must still
   // be treated as having finished.
-  animation.TransitionFloatTo(start_time + MsToDelta(1000), OPACITY,
+  animation.TransitionFloatTo(&target, start_time + MsToDelta(1000), OPACITY,
                               target.opacity(), 1.0f);
 
   animation.Tick(start_time + MsToDelta(1000));
@@ -231,7 +227,6 @@
 TEST(AnimationTest, OpacityTransitions) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {OPACITY};
   transition.duration = MicrosecondsToDelta(10000);
@@ -242,7 +237,7 @@
 
   float from = 1.0f;
   float to = 0.5f;
-  animation.TransitionFloatTo(start_time, OPACITY, from, to);
+  animation.TransitionFloatTo(&target, start_time, OPACITY, from, to);
 
   EXPECT_EQ(from, target.opacity());
   animation.Tick(start_time);
@@ -250,7 +245,7 @@
   // Scheduling a redundant, approximately equal transition should be ignored.
   int keyframe_model_id = animation.keyframe_models().front()->id();
   float nearby = to + kNoise;
-  animation.TransitionFloatTo(start_time, OPACITY, from, nearby);
+  animation.TransitionFloatTo(&target, start_time, OPACITY, from, nearby);
   EXPECT_EQ(keyframe_model_id, animation.keyframe_models().front()->id());
 
   animation.Tick(start_time + MicrosecondsToDelta(5000));
@@ -264,7 +259,6 @@
 TEST(AnimationTest, ReversedOpacityTransitions) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {OPACITY};
   transition.duration = MicrosecondsToDelta(10000);
@@ -275,7 +269,7 @@
 
   float from = 1.0f;
   float to = 0.5f;
-  animation.TransitionFloatTo(start_time, OPACITY, from, to);
+  animation.TransitionFloatTo(&target, start_time, OPACITY, from, to);
 
   EXPECT_EQ(from, target.opacity());
   animation.Tick(start_time);
@@ -285,8 +279,8 @@
   EXPECT_GT(from, value_before_reversing);
   EXPECT_LT(to, value_before_reversing);
 
-  animation.TransitionFloatTo(start_time + MicrosecondsToDelta(1000), OPACITY,
-                              target.opacity(), from);
+  animation.TransitionFloatTo(&target, start_time + MicrosecondsToDelta(1000),
+                              OPACITY, target.opacity(), from);
   animation.Tick(start_time + MicrosecondsToDelta(1000));
   EXPECT_FLOAT_EQ(value_before_reversing, target.opacity());
 
@@ -299,7 +293,6 @@
   float tolerance = 0.0f;
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {LAYOUT_OFFSET};
   transition.duration = MicrosecondsToDelta(10000);
@@ -312,8 +305,8 @@
   gfx::TransformOperations to;
   to.AppendTranslate(8, 0, 0);
 
-  animation.TransitionTransformOperationsTo(start_time, LAYOUT_OFFSET, from,
-                                            to);
+  animation.TransitionTransformOperationsTo(&target, start_time, LAYOUT_OFFSET,
+                                            from, to);
 
   EXPECT_TRUE(from.ApproximatelyEqual(target.layout_offset(), tolerance));
   animation.Tick(start_time);
@@ -322,8 +315,8 @@
   int keyframe_model_id = animation.keyframe_models().front()->id();
   gfx::TransformOperations nearby = to;
   nearby.at(0).translate.x += kNoise;
-  animation.TransitionTransformOperationsTo(start_time, LAYOUT_OFFSET, from,
-                                            nearby);
+  animation.TransitionTransformOperationsTo(&target, start_time, LAYOUT_OFFSET,
+                                            from, nearby);
   EXPECT_EQ(keyframe_model_id, animation.keyframe_models().front()->id());
 
   animation.Tick(start_time + MicrosecondsToDelta(5000));
@@ -339,7 +332,6 @@
   float tolerance = 0.0f;
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {TRANSFORM};
   transition.duration = MicrosecondsToDelta(10000);
@@ -354,7 +346,8 @@
   to.AppendRotate(1, 0, 0, 0);
   to.AppendScale(1, 1, 1);
 
-  animation.TransitionTransformOperationsTo(start_time, TRANSFORM, from, to);
+  animation.TransitionTransformOperationsTo(&target, start_time, TRANSFORM,
+                                            from, to);
 
   EXPECT_TRUE(from.ApproximatelyEqual(target.operations(), tolerance));
   animation.Tick(start_time);
@@ -363,8 +356,8 @@
   int keyframe_model_id = animation.keyframe_models().front()->id();
   gfx::TransformOperations nearby = to;
   nearby.at(0).translate.x += kNoise;
-  animation.TransitionTransformOperationsTo(start_time, TRANSFORM, from,
-                                            nearby);
+  animation.TransitionTransformOperationsTo(&target, start_time, TRANSFORM,
+                                            from, nearby);
   EXPECT_EQ(keyframe_model_id, animation.keyframe_models().front()->id());
 
   animation.Tick(start_time + MicrosecondsToDelta(5000));
@@ -380,7 +373,6 @@
   float tolerance = 0.0f;
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {TRANSFORM};
   transition.duration = MicrosecondsToDelta(10000);
@@ -395,7 +387,8 @@
   to.AppendRotate(1, 0, 0, 0);
   to.AppendScale(1, 1, 1);
 
-  animation.TransitionTransformOperationsTo(start_time, TRANSFORM, from, to);
+  animation.TransitionTransformOperationsTo(&target, start_time, TRANSFORM,
+                                            from, to);
 
   EXPECT_TRUE(from.ApproximatelyEqual(target.operations(), tolerance));
   animation.Tick(start_time);
@@ -406,8 +399,8 @@
   EXPECT_GT(to.at(0).translate.x, target.operations().at(0).translate.x);
 
   animation.TransitionTransformOperationsTo(
-      start_time + MicrosecondsToDelta(1000), TRANSFORM, target.operations(),
-      from);
+      &target, start_time + MicrosecondsToDelta(1000), TRANSFORM,
+      target.operations(), from);
   animation.Tick(start_time + MicrosecondsToDelta(1000));
   EXPECT_TRUE(value_before_reversing.ApproximatelyEqual(target.operations(),
                                                         tolerance));
@@ -419,7 +412,6 @@
 TEST(AnimationTest, BoundsTransitions) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {BOUNDS};
   transition.duration = MicrosecondsToDelta(10000);
@@ -430,7 +422,7 @@
   gfx::SizeF from = target.size();
   gfx::SizeF to(20.0f, 20.0f);
 
-  animation.TransitionSizeTo(start_time, BOUNDS, from, to);
+  animation.TransitionSizeTo(&target, start_time, BOUNDS, from, to);
 
   EXPECT_FLOAT_SIZE_EQ(from, target.size());
   animation.Tick(start_time);
@@ -439,7 +431,7 @@
   int keyframe_model_id = animation.keyframe_models().front()->id();
   gfx::SizeF nearby = to;
   nearby.set_width(to.width() + kNoise);
-  animation.TransitionSizeTo(start_time, BOUNDS, from, nearby);
+  animation.TransitionSizeTo(&target, start_time, BOUNDS, from, nearby);
   EXPECT_EQ(keyframe_model_id, animation.keyframe_models().front()->id());
 
   animation.Tick(start_time + MicrosecondsToDelta(5000));
@@ -455,7 +447,6 @@
 TEST(AnimationTest, ReversedBoundsTransitions) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {BOUNDS};
   transition.duration = MicrosecondsToDelta(10000);
@@ -466,7 +457,7 @@
   gfx::SizeF from = target.size();
   gfx::SizeF to(20.0f, 20.0f);
 
-  animation.TransitionSizeTo(start_time, BOUNDS, from, to);
+  animation.TransitionSizeTo(&target, start_time, BOUNDS, from, to);
 
   EXPECT_FLOAT_SIZE_EQ(from, target.size());
   animation.Tick(start_time);
@@ -478,8 +469,8 @@
   EXPECT_LT(from.height(), target.size().height());
   EXPECT_GT(to.height(), target.size().height());
 
-  animation.TransitionSizeTo(start_time + MicrosecondsToDelta(1000), BOUNDS,
-                             target.size(), from);
+  animation.TransitionSizeTo(&target, start_time + MicrosecondsToDelta(1000),
+                             BOUNDS, target.size(), from);
   animation.Tick(start_time + MicrosecondsToDelta(1000));
   EXPECT_FLOAT_SIZE_EQ(value_before_reversing, target.size());
 
@@ -490,7 +481,6 @@
 TEST(AnimationTest, BackgroundColorTransitions) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {BACKGROUND_COLOR};
   transition.duration = MicrosecondsToDelta(10000);
@@ -501,7 +491,7 @@
   SkColor from = SK_ColorRED;
   SkColor to = SK_ColorGREEN;
 
-  animation.TransitionColorTo(start_time, BACKGROUND_COLOR, from, to);
+  animation.TransitionColorTo(&target, start_time, BACKGROUND_COLOR, from, to);
 
   EXPECT_EQ(from, target.background_color());
   animation.Tick(start_time);
@@ -521,7 +511,6 @@
 TEST(AnimationTest, ReversedBackgroundColorTransitions) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {BACKGROUND_COLOR};
   transition.duration = MicrosecondsToDelta(10000);
@@ -532,7 +521,7 @@
   SkColor from = SK_ColorRED;
   SkColor to = SK_ColorGREEN;
 
-  animation.TransitionColorTo(start_time, BACKGROUND_COLOR, from, to);
+  animation.TransitionColorTo(&target, start_time, BACKGROUND_COLOR, from, to);
 
   EXPECT_EQ(from, target.background_color());
   animation.Tick(start_time);
@@ -546,7 +535,7 @@
   EXPECT_EQ(0u, SkColorGetB(target.background_color()));
   EXPECT_EQ(255u, SkColorGetA(target.background_color()));
 
-  animation.TransitionColorTo(start_time + MicrosecondsToDelta(1000),
+  animation.TransitionColorTo(&target, start_time + MicrosecondsToDelta(1000),
                               BACKGROUND_COLOR, target.background_color(),
                               from);
   animation.Tick(start_time + MicrosecondsToDelta(1000));
@@ -559,7 +548,6 @@
 TEST(AnimationTest, DoubleReversedTransitions) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {OPACITY};
   transition.duration = MicrosecondsToDelta(10000);
@@ -570,7 +558,7 @@
 
   float from = 1.0f;
   float to = 0.5f;
-  animation.TransitionFloatTo(start_time, OPACITY, from, to);
+  animation.TransitionFloatTo(&target, start_time, OPACITY, from, to);
 
   EXPECT_EQ(from, target.opacity());
   animation.Tick(start_time);
@@ -580,8 +568,8 @@
   EXPECT_GT(from, value_before_reversing);
   EXPECT_LT(to, value_before_reversing);
 
-  animation.TransitionFloatTo(start_time + MicrosecondsToDelta(1000), OPACITY,
-                              target.opacity(), from);
+  animation.TransitionFloatTo(&target, start_time + MicrosecondsToDelta(1000),
+                              OPACITY, target.opacity(), from);
   animation.Tick(start_time + MicrosecondsToDelta(1000));
   EXPECT_FLOAT_EQ(value_before_reversing, target.opacity());
 
@@ -589,8 +577,8 @@
   value_before_reversing = target.opacity();
   // If the code for reversing transitions does not account for an existing time
   // offset, then reversing a second time will give incorrect values.
-  animation.TransitionFloatTo(start_time + MicrosecondsToDelta(1500), OPACITY,
-                              target.opacity(), to);
+  animation.TransitionFloatTo(&target, start_time + MicrosecondsToDelta(1500),
+                              OPACITY, target.opacity(), to);
   animation.Tick(start_time + MicrosecondsToDelta(1500));
   EXPECT_FLOAT_EQ(value_before_reversing, target.opacity());
 }
@@ -598,7 +586,6 @@
 TEST(AnimationTest, RedundantTransition) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {OPACITY};
   transition.duration = MicrosecondsToDelta(10000);
@@ -609,7 +596,7 @@
 
   float from = 1.0f;
   float to = 0.5f;
-  animation.TransitionFloatTo(start_time, OPACITY, from, to);
+  animation.TransitionFloatTo(&target, start_time, OPACITY, from, to);
 
   EXPECT_EQ(from, target.opacity());
   animation.Tick(start_time);
@@ -619,7 +606,8 @@
 
   // While an existing transition is in progress to the same value, we should
   // not start a new transition.
-  animation.TransitionFloatTo(start_time, OPACITY, target.opacity(), to);
+  animation.TransitionFloatTo(&target, start_time, OPACITY, target.opacity(),
+                              to);
 
   EXPECT_EQ(1lu, animation.keyframe_models().size());
   EXPECT_EQ(value_before_redundant_transition, target.opacity());
@@ -628,7 +616,6 @@
 TEST(AnimationTest, TransitionToSameValue) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   Transition transition;
   transition.target_properties = {OPACITY};
   transition.duration = MicrosecondsToDelta(10000);
@@ -640,7 +627,7 @@
   // Transitioning to the same value should be a no-op.
   float from = 1.0f;
   float to = 1.0f;
-  animation.TransitionFloatTo(start_time, OPACITY, from, to);
+  animation.TransitionFloatTo(&target, start_time, OPACITY, from, to);
   EXPECT_EQ(from, target.opacity());
   EXPECT_TRUE(animation.keyframe_models().empty());
 }
@@ -648,7 +635,6 @@
 TEST(AnimationTest, CorrectTargetValue) {
   TestAnimationTarget target;
   Animation animation;
-  animation.set_target(&target);
   base::TimeDelta duration = MicrosecondsToDelta(10000);
 
   float from_opacity = 1.0f;
@@ -673,14 +659,14 @@
       kEpsilon));
 
   // Add keyframe_models.
+  animation.AddKeyframeModel(CreateOpacityAnimation(&target, 2, 1, from_opacity,
+                                                    to_opacity, duration));
   animation.AddKeyframeModel(
-      CreateOpacityAnimation(2, 1, from_opacity, to_opacity, duration));
-  animation.AddKeyframeModel(
-      CreateBoundsAnimation(1, 1, from_bounds, to_bounds, duration));
-  animation.AddKeyframeModel(
-      CreateBackgroundColorAnimation(3, 1, from_color, to_color, duration));
-  animation.AddKeyframeModel(
-      CreateTransformAnimation(4, 1, from_transform, to_transform, duration));
+      CreateBoundsAnimation(&target, 1, 1, from_bounds, to_bounds, duration));
+  animation.AddKeyframeModel(CreateBackgroundColorAnimation(
+      &target, 3, 1, from_color, to_color, duration));
+  animation.AddKeyframeModel(CreateTransformAnimation(
+      &target, 4, 1, from_transform, to_transform, duration));
 
   base::TimeTicks start_time = MicrosecondsToTicks(1000000);
   animation.Tick(start_time);
diff --git a/chrome/browser/vr/elements/button.cc b/chrome/browser/vr/elements/button.cc
index c99bf772..9d49fed 100644
--- a/chrome/browser/vr/elements/button.cc
+++ b/chrome/browser/vr/elements/button.cc
@@ -151,16 +151,16 @@
   hit_plane_->SetCornerRadii(radii);
 }
 
-void Button::NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                      int target_property_id,
-                                      cc::KeyframeModel* animation) {
+void Button::OnSizeAnimated(const gfx::SizeF& size,
+                            int target_property_id,
+                            cc::KeyframeModel* animation) {
   // We could have OnSetSize called in UiElement's Notify handler instead, but
   // this may have expensive implications (such as regenerating textures on
   // every frame of an animation).  For now, keep this elements-specific.
   if (target_property_id == BOUNDS) {
     OnSetSize(size);
   }
-  UiElement::NotifyClientSizeAnimated(size, target_property_id, animation);
+  UiElement::OnSizeAnimated(size, target_property_id, animation);
 }
 
 const Sounds& Button::GetSounds() const {
diff --git a/chrome/browser/vr/elements/button.h b/chrome/browser/vr/elements/button.h
index 0eb3425..6945736 100644
--- a/chrome/browser/vr/elements/button.h
+++ b/chrome/browser/vr/elements/button.h
@@ -65,9 +65,9 @@
   void OnSetName() override;
   void OnSetSize(const gfx::SizeF& size) override;
   void OnSetCornerRadii(const CornerRadii& radii) override;
-  void NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                int target_property_id,
-                                cc::KeyframeModel* keyframe_model) override;
+  void OnSizeAnimated(const gfx::SizeF& size,
+                      int target_property_id,
+                      cc::KeyframeModel* keyframe_model) override;
   virtual void OnStateUpdated();
 
  private:
diff --git a/chrome/browser/vr/elements/content_element.cc b/chrome/browser/vr/elements/content_element.cc
index 1ea50fa..b858466 100644
--- a/chrome/browser/vr/elements/content_element.cc
+++ b/chrome/browser/vr/elements/content_element.cc
@@ -131,13 +131,13 @@
     text_input_delegate_->UpdateInput(info.current);
 }
 
-void ContentElement::NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                              int target_property_id,
-                                              cc::KeyframeModel* animation) {
+void ContentElement::OnSizeAnimated(const gfx::SizeF& size,
+                                    int target_property_id,
+                                    cc::KeyframeModel* animation) {
   if (target_property_id == BOUNDS && on_size_changed_callback_) {
     on_size_changed_callback_.Run(size);
   }
-  UiElement::NotifyClientSizeAnimated(size, target_property_id, animation);
+  UiElement::OnSizeAnimated(size, target_property_id, animation);
 }
 
 bool ContentElement::OnBeginFrame(const gfx::Transform& head_pose) {
diff --git a/chrome/browser/vr/elements/content_element.h b/chrome/browser/vr/elements/content_element.h
index 39d175f..232e4de 100644
--- a/chrome/browser/vr/elements/content_element.h
+++ b/chrome/browser/vr/elements/content_element.h
@@ -39,9 +39,9 @@
   void RequestFocus() override;
   void RequestUnfocus() override;
   void UpdateInput(const EditedText& info) override;
-  void NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                int target_property_id,
-                                cc::KeyframeModel* animation) override;
+  void OnSizeAnimated(const gfx::SizeF& size,
+                      int target_property_id,
+                      cc::KeyframeModel* animation) override;
 
   void SetOverlayTextureId(unsigned int texture_id);
   void SetOverlayTextureLocation(GlTextureLocation location);
diff --git a/chrome/browser/vr/elements/disc_button.cc b/chrome/browser/vr/elements/disc_button.cc
index 7d5748a..d7b8c3e 100644
--- a/chrome/browser/vr/elements/disc_button.cc
+++ b/chrome/browser/vr/elements/disc_button.cc
@@ -31,10 +31,10 @@
   NOTREACHED();
 }
 
-void DiscButton::NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                          int target_property_id,
-                                          cc::KeyframeModel* keyframe_model) {
-  Button::NotifyClientSizeAnimated(size, target_property_id, keyframe_model);
+void DiscButton::OnSizeAnimated(const gfx::SizeF& size,
+                                int target_property_id,
+                                cc::KeyframeModel* keyframe_model) {
+  Button::OnSizeAnimated(size, target_property_id, keyframe_model);
   if (target_property_id == BOUNDS) {
     background()->SetSize(size.width(), size.height());
     background()->SetCornerRadius(size.width() * 0.5f);  // Creates a circle.
diff --git a/chrome/browser/vr/elements/disc_button.h b/chrome/browser/vr/elements/disc_button.h
index feff06e..7a0fc218 100644
--- a/chrome/browser/vr/elements/disc_button.h
+++ b/chrome/browser/vr/elements/disc_button.h
@@ -29,9 +29,9 @@
 
  private:
   void OnSetCornerRadii(const CornerRadii& radii) override;
-  void NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                int target_property_id,
-                                cc::KeyframeModel* keyframe_model) override;
+  void OnSizeAnimated(const gfx::SizeF& size,
+                      int target_property_id,
+                      cc::KeyframeModel* keyframe_model) override;
 
   DISALLOW_COPY_AND_ASSIGN(DiscButton);
 };
diff --git a/chrome/browser/vr/elements/environment/background.cc b/chrome/browser/vr/elements/environment/background.cc
index 363c3bc7..dc7f1b5 100644
--- a/chrome/browser/vr/elements/environment/background.cc
+++ b/chrome/browser/vr/elements/environment/background.cc
@@ -187,18 +187,19 @@
 }
 
 void Background::SetNormalFactor(float factor) {
-  animation().TransitionFloatTo(last_frame_time(), NORMAL_COLOR_FACTOR,
+  animation().TransitionFloatTo(this, last_frame_time(), NORMAL_COLOR_FACTOR,
                                 normal_factor_, factor);
 }
 
 void Background::SetIncognitoFactor(float factor) {
-  animation().TransitionFloatTo(last_frame_time(), INCOGNITO_COLOR_FACTOR,
+  animation().TransitionFloatTo(this, last_frame_time(), INCOGNITO_COLOR_FACTOR,
                                 incognito_factor_, factor);
 }
 
 void Background::SetFullscreenFactor(float factor) {
-  animation().TransitionFloatTo(last_frame_time(), FULLSCREEN_COLOR_FACTOR,
-                                fullscreen_factor_, factor);
+  animation().TransitionFloatTo(this, last_frame_time(),
+                                FULLSCREEN_COLOR_FACTOR, fullscreen_factor_,
+                                factor);
 }
 
 void Background::CreateBackgroundTexture() {
@@ -221,9 +222,9 @@
                   provider_, &fullscreen_gradient_surface_);
 }
 
-void Background::NotifyClientFloatAnimated(float value,
-                                           int target_property_id,
-                                           cc::KeyframeModel* keyframe_model) {
+void Background::OnFloatAnimated(const float& value,
+                                 int target_property_id,
+                                 cc::KeyframeModel* keyframe_model) {
   switch (target_property_id) {
     case NORMAL_COLOR_FACTOR:
       normal_factor_ = value;
@@ -235,8 +236,7 @@
       fullscreen_factor_ = value;
       break;
     default:
-      UiElement::NotifyClientFloatAnimated(value, target_property_id,
-                                           keyframe_model);
+      UiElement::OnFloatAnimated(value, target_property_id, keyframe_model);
   }
 }
 
diff --git a/chrome/browser/vr/elements/environment/background.h b/chrome/browser/vr/elements/environment/background.h
index ff3a22f..e484a55f 100644
--- a/chrome/browser/vr/elements/environment/background.h
+++ b/chrome/browser/vr/elements/environment/background.h
@@ -71,9 +71,9 @@
   void CreateBackgroundTexture();
   void CreateGradientTextures();
 
-  void NotifyClientFloatAnimated(float value,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override;
+  void OnFloatAnimated(const float& value,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override;
 
   std::unique_ptr<SkBitmap> initialization_bitmap_;
   std::unique_ptr<SkBitmap> initialization_normal_gradient_bitmap_;
diff --git a/chrome/browser/vr/elements/environment/grid.cc b/chrome/browser/vr/elements/environment/grid.cc
index 3e1ff8a..1ad8730 100644
--- a/chrome/browser/vr/elements/environment/grid.cc
+++ b/chrome/browser/vr/elements/environment/grid.cc
@@ -52,17 +52,17 @@
 Grid::~Grid() {}
 
 void Grid::SetGridColor(SkColor color) {
-  animation().TransitionColorTo(last_frame_time(), GRID_COLOR, grid_color_,
-                                color);
+  animation().TransitionColorTo(this, last_frame_time(), GRID_COLOR,
+                                grid_color_, color);
 }
 
-void Grid::NotifyClientColorAnimated(SkColor color,
-                                     int target_property_id,
-                                     cc::KeyframeModel* keyframe_model) {
+void Grid::OnColorAnimated(const SkColor& color,
+                           int target_property_id,
+                           cc::KeyframeModel* keyframe_model) {
   if (target_property_id == GRID_COLOR) {
     grid_color_ = color;
   } else {
-    Rect::NotifyClientColorAnimated(color, target_property_id, keyframe_model);
+    Rect::OnColorAnimated(color, target_property_id, keyframe_model);
   }
 }
 
diff --git a/chrome/browser/vr/elements/environment/grid.h b/chrome/browser/vr/elements/environment/grid.h
index bad3a1d..23438f1 100644
--- a/chrome/browser/vr/elements/environment/grid.h
+++ b/chrome/browser/vr/elements/environment/grid.h
@@ -23,9 +23,9 @@
   SkColor grid_color() const { return grid_color_; }
   void SetGridColor(SkColor grid_color);
 
-  void NotifyClientColorAnimated(SkColor color,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override;
+  void OnColorAnimated(const SkColor& color,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override;
 
   int gridline_count() const { return gridline_count_; }
   void set_gridline_count(int gridline_count) {
diff --git a/chrome/browser/vr/elements/keyboard.cc b/chrome/browser/vr/elements/keyboard.cc
index bcaeb1b..e4d7e37 100644
--- a/chrome/browser/vr/elements/keyboard.cc
+++ b/chrome/browser/vr/elements/keyboard.cc
@@ -55,11 +55,11 @@
   result->local_hit_point = gfx::PointF(0, 0);
 }
 
-void Keyboard::NotifyClientFloatAnimated(float value,
-                                         int target_property_id,
-                                         cc::KeyframeModel* animation) {
+void Keyboard::OnFloatAnimated(const float& value,
+                               int target_property_id,
+                               cc::KeyframeModel* animation) {
   DCHECK(target_property_id == OPACITY);
-  UiElement::NotifyClientFloatAnimated(value, target_property_id, animation);
+  UiElement::OnFloatAnimated(value, target_property_id, animation);
   UpdateDelegateVisibility();
 }
 
diff --git a/chrome/browser/vr/elements/keyboard.h b/chrome/browser/vr/elements/keyboard.h
index f8e01be..10461ae 100644
--- a/chrome/browser/vr/elements/keyboard.h
+++ b/chrome/browser/vr/elements/keyboard.h
@@ -27,9 +27,9 @@
   void OnTouchStateUpdated(bool is_touching, const gfx::PointF& touch_position);
   void HitTest(const HitTestRequest& request,
                HitTestResult* result) const final;
-  void NotifyClientFloatAnimated(float value,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override;
+  void OnFloatAnimated(const float& value,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override;
 
   void OnHoverEnter(const gfx::PointF& position,
                     base::TimeTicks timestamp) override;
diff --git a/chrome/browser/vr/elements/oval.cc b/chrome/browser/vr/elements/oval.cc
index 744d8c6c..a11ab601 100644
--- a/chrome/browser/vr/elements/oval.cc
+++ b/chrome/browser/vr/elements/oval.cc
@@ -13,10 +13,10 @@
 Oval::Oval() = default;
 Oval::~Oval() = default;
 
-void Oval::NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                    int target_property_id,
-                                    cc::KeyframeModel* keyframe_model) {
-  Rect::NotifyClientSizeAnimated(size, target_property_id, keyframe_model);
+void Oval::OnSizeAnimated(const gfx::SizeF& size,
+                          int target_property_id,
+                          cc::KeyframeModel* keyframe_model) {
+  Rect::OnSizeAnimated(size, target_property_id, keyframe_model);
   if (target_property_id == BOUNDS)
     SetCornerRadius(0.5f * std::min(size.height(), size.width()));
 }
diff --git a/chrome/browser/vr/elements/oval.h b/chrome/browser/vr/elements/oval.h
index 0a5bab3d..e3ae4c7 100644
--- a/chrome/browser/vr/elements/oval.h
+++ b/chrome/browser/vr/elements/oval.h
@@ -18,9 +18,9 @@
   ~Oval() override;
 
  private:
-  void NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                int target_property_id,
-                                cc::KeyframeModel* keyframe_model) override;
+  void OnSizeAnimated(const gfx::SizeF& size,
+                      int target_property_id,
+                      cc::KeyframeModel* keyframe_model) override;
 
   DISALLOW_COPY_AND_ASSIGN(Oval);
 };
diff --git a/chrome/browser/vr/elements/rect.cc b/chrome/browser/vr/elements/rect.cc
index b820980f..39b10a1 100644
--- a/chrome/browser/vr/elements/rect.cc
+++ b/chrome/browser/vr/elements/rect.cc
@@ -19,24 +19,24 @@
 }
 
 void Rect::SetCenterColor(SkColor color) {
-  animation().TransitionColorTo(last_frame_time(), BACKGROUND_COLOR,
+  animation().TransitionColorTo(this, last_frame_time(), BACKGROUND_COLOR,
                                 center_color_, color);
 }
 
 void Rect::SetEdgeColor(SkColor color) {
-  animation().TransitionColorTo(last_frame_time(), FOREGROUND_COLOR,
+  animation().TransitionColorTo(this, last_frame_time(), FOREGROUND_COLOR,
                                 edge_color_, color);
 }
 
-void Rect::NotifyClientColorAnimated(SkColor color,
-                                     int target_property_id,
-                                     cc::KeyframeModel* animation) {
+void Rect::OnColorAnimated(const SkColor& color,
+                           int target_property_id,
+                           cc::KeyframeModel* animation) {
   if (target_property_id == BACKGROUND_COLOR) {
     center_color_ = color;
   } else if (target_property_id == FOREGROUND_COLOR) {
     edge_color_ = color;
   } else {
-    UiElement::NotifyClientColorAnimated(color, target_property_id, animation);
+    UiElement::OnColorAnimated(color, target_property_id, animation);
   }
 }
 
@@ -50,18 +50,17 @@
 }
 
 void Rect::SetLocalOpacity(float opacity) {
-  animation().TransitionFloatTo(last_frame_time(), LOCAL_OPACITY,
+  animation().TransitionFloatTo(this, last_frame_time(), LOCAL_OPACITY,
                                 local_opacity_, opacity);
 }
 
-void Rect::NotifyClientFloatAnimated(float value,
-                                     int target_property_id,
-                                     cc::KeyframeModel* keyframe_model) {
+void Rect::OnFloatAnimated(const float& value,
+                           int target_property_id,
+                           cc::KeyframeModel* keyframe_model) {
   if (target_property_id == LOCAL_OPACITY) {
     local_opacity_ = value;
   } else {
-    UiElement::NotifyClientFloatAnimated(value, target_property_id,
-                                         keyframe_model);
+    UiElement::OnFloatAnimated(value, target_property_id, keyframe_model);
   }
 }
 
diff --git a/chrome/browser/vr/elements/rect.h b/chrome/browser/vr/elements/rect.h
index 612fa38..85fd6b4 100644
--- a/chrome/browser/vr/elements/rect.h
+++ b/chrome/browser/vr/elements/rect.h
@@ -32,18 +32,18 @@
   SkColor edge_color() const { return edge_color_; }
   void SetEdgeColor(SkColor color);
 
-  void NotifyClientColorAnimated(SkColor color,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override;
+  void OnColorAnimated(const SkColor& color,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override;
 
   void Render(UiElementRenderer* renderer,
               const CameraModel& model) const override;
 
   void SetLocalOpacity(float opacity);
 
-  void NotifyClientFloatAnimated(float value,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override;
+  void OnFloatAnimated(const float& value,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override;
 
   float ComputedAndLocalOpacityForTest() const override;
 
diff --git a/chrome/browser/vr/elements/spinner.cc b/chrome/browser/vr/elements/spinner.cc
index 4aa19a94..bbc0431 100644
--- a/chrome/browser/vr/elements/spinner.cc
+++ b/chrome/browser/vr/elements/spinner.cc
@@ -73,6 +73,7 @@
       cc::FloatKeyframe::Create(base::TimeDelta(), 0.0f, nullptr));
   curve->AddKeyframe(
       cc::FloatKeyframe::Create(kRotationDuration, 360.0f, nullptr));
+  curve->set_target(this);
 
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
@@ -90,6 +91,8 @@
                                                  CreateTimingFunction()));
   }
 
+  curve->set_target(this);
+
   keyframe_model = cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
       Animation::GetNextGroupId(),
@@ -105,6 +108,8 @@
         kSweepDuration * i, kMaxAngle * i, CreateTimingFunction()));
   }
 
+  curve->set_target(this);
+
   keyframe_model = cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
       Animation::GetNextGroupId(),
@@ -132,9 +137,9 @@
   return gfx::Size(texture_width_, texture_width_);
 }
 
-void Spinner::NotifyClientFloatAnimated(float value,
-                                        int target_property_id,
-                                        cc::KeyframeModel* keyframe_model) {
+void Spinner::OnFloatAnimated(const float& value,
+                              int target_property_id,
+                              cc::KeyframeModel* keyframe_model) {
   switch (target_property_id) {
     case SPINNER_ANGLE_SWEEP:
       texture_->SetAngleSweep(value);
@@ -146,8 +151,8 @@
       texture_->SetRotation(value);
       break;
     default:
-      TexturedElement::NotifyClientFloatAnimated(value, target_property_id,
-                                                 keyframe_model);
+      TexturedElement::OnFloatAnimated(value, target_property_id,
+                                       keyframe_model);
   }
 }
 
diff --git a/chrome/browser/vr/elements/spinner.h b/chrome/browser/vr/elements/spinner.h
index 10b54f9..f6ff51b9 100644
--- a/chrome/browser/vr/elements/spinner.h
+++ b/chrome/browser/vr/elements/spinner.h
@@ -30,9 +30,9 @@
   bool TextureDependsOnMeasurement() const override;
   gfx::Size MeasureTextureSize() override;
 
-  void NotifyClientFloatAnimated(float value,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override;
+  void OnFloatAnimated(const float& value,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override;
   std::unique_ptr<SpinnerTexture> texture_;
   int texture_width_;
 
diff --git a/chrome/browser/vr/elements/throbber.cc b/chrome/browser/vr/elements/throbber.cc
index 7d36ca62..465d686 100644
--- a/chrome/browser/vr/elements/throbber.cc
+++ b/chrome/browser/vr/elements/throbber.cc
@@ -20,9 +20,9 @@
 Throbber::Throbber() = default;
 Throbber::~Throbber() = default;
 
-void Throbber::NotifyClientFloatAnimated(float value,
-                                         int target_property_id,
-                                         cc::KeyframeModel* animation) {
+void Throbber::OnFloatAnimated(const float& value,
+                               int target_property_id,
+                               cc::KeyframeModel* animation) {
   if (target_property_id == CIRCLE_GROW) {
     DCHECK(!IsAnimatingProperty(TRANSFORM));
     DCHECK(!IsAnimatingProperty(OPACITY));
@@ -35,7 +35,7 @@
     SetOpacity(opacity_before_animation_ * (1.0 - animation_progress));
     return;
   }
-  Rect::NotifyClientFloatAnimated(value, target_property_id, animation);
+  Rect::OnFloatAnimated(value, target_property_id, animation);
 }
 
 void Throbber::SetCircleGrowAnimationEnabled(bool enabled) {
@@ -62,6 +62,7 @@
   curve->AddKeyframe(cc::FloatKeyframe::Create(
       base::TimeDelta::FromMilliseconds(kCircleGrowAnimationTimeMs), kEndScale,
       nullptr));
+  curve->set_target(this);
 
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
diff --git a/chrome/browser/vr/elements/throbber.h b/chrome/browser/vr/elements/throbber.h
index c05288c3..b414be2 100644
--- a/chrome/browser/vr/elements/throbber.h
+++ b/chrome/browser/vr/elements/throbber.h
@@ -18,9 +18,9 @@
   Throbber();
   ~Throbber() override;
 
-  void NotifyClientFloatAnimated(float value,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override;
+  void OnFloatAnimated(const float& value,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override;
 
   void SetCircleGrowAnimationEnabled(bool enabled);
 
diff --git a/chrome/browser/vr/elements/ui_element.cc b/chrome/browser/vr/elements/ui_element.cc
index 579e60a09..7f4ec41 100644
--- a/chrome/browser/vr/elements/ui_element.cc
+++ b/chrome/browser/vr/elements/ui_element.cc
@@ -92,16 +92,13 @@
 EventHandlers::EventHandlers(const EventHandlers& other) = default;
 
 UiElement::UiElement() : id_(AllocateId()) {
-  animation_.set_target(this);
   layout_offset_.AppendTranslate(0, 0, 0);
   transform_operations_.AppendTranslate(0, 0, 0);
   transform_operations_.AppendRotate(1, 0, 0, 0);
   transform_operations_.AppendScale(1, 1, 1);
 }
 
-UiElement::~UiElement() {
-  animation_.set_target(nullptr);
-}
+UiElement::~UiElement() = default;
 
 void UiElement::SetName(UiElementName name) {
   name_ = name;
@@ -309,7 +306,7 @@
 }
 
 void UiElement::SetSize(float width, float height) {
-  animation_.TransitionSizeTo(last_frame_time_, BOUNDS, size_,
+  animation_.TransitionSizeTo(this, last_frame_time_, BOUNDS, size_,
                               gfx::SizeF(width, height));
   OnSetSize(gfx::SizeF(width, height));
 }
@@ -384,8 +381,8 @@
   gfx::TransformOperation& op = operations.at(0);
   op.translate = {x, y, 0};
   op.Bake();
-  animation_.TransitionTransformOperationsTo(last_frame_time_, LAYOUT_OFFSET,
-                                             layout_offset_, operations);
+  animation_.TransitionTransformOperationsTo(
+      this, last_frame_time_, LAYOUT_OFFSET, layout_offset_, operations);
 }
 
 void UiElement::SetTranslate(float x, float y, float z) {
@@ -400,7 +397,7 @@
   gfx::TransformOperation& op = operations.at(kTranslateIndex);
   op.translate = {x, y, z};
   op.Bake();
-  animation_.TransitionTransformOperationsTo(last_frame_time_, TRANSFORM,
+  animation_.TransitionTransformOperationsTo(this, last_frame_time_, TRANSFORM,
                                              transform_operations_, operations);
 }
 
@@ -420,7 +417,7 @@
   op.rotate.axis = {x, y, z};
   op.rotate.angle = degrees;
   op.Bake();
-  animation_.TransitionTransformOperationsTo(last_frame_time_, TRANSFORM,
+  animation_.TransitionTransformOperationsTo(this, last_frame_time_, TRANSFORM,
                                              transform_operations_, operations);
 }
 
@@ -436,12 +433,13 @@
   gfx::TransformOperation& op = operations.at(kScaleIndex);
   op.scale = {x, y, z};
   op.Bake();
-  animation_.TransitionTransformOperationsTo(last_frame_time_, TRANSFORM,
+  animation_.TransitionTransformOperationsTo(this, last_frame_time_, TRANSFORM,
                                              transform_operations_, operations);
 }
 
 void UiElement::SetOpacity(float opacity) {
-  animation_.TransitionFloatTo(last_frame_time_, OPACITY, opacity_, opacity);
+  animation_.TransitionFloatTo(this, last_frame_time_, OPACITY, opacity_,
+                               opacity);
 }
 
 void UiElement::SetCornerRadii(const CornerRadii& radii) {
@@ -765,16 +763,15 @@
                              distance);
 }
 
-void UiElement::NotifyClientFloatAnimated(float value,
-                                          int target_property_id,
-                                          cc::KeyframeModel* keyframe_model) {
+void UiElement::OnFloatAnimated(const float& value,
+                                int target_property_id,
+                                cc::KeyframeModel* keyframe_model) {
   opacity_ = base::ClampToRange(value, 0.0f, 1.0f);
 }
 
-void UiElement::NotifyClientTransformOperationsAnimated(
-    const gfx::TransformOperations& operations,
-    int target_property_id,
-    cc::KeyframeModel* keyframe_model) {
+void UiElement::OnTransformAnimated(const gfx::TransformOperations& operations,
+                                    int target_property_id,
+                                    cc::KeyframeModel* keyframe_model) {
   if (target_property_id == TRANSFORM) {
     transform_operations_ = operations;
   } else if (target_property_id == LAYOUT_OFFSET) {
@@ -786,15 +783,19 @@
   world_space_transform_dirty_ = true;
 }
 
-void UiElement::NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                         int target_property_id,
-                                         cc::KeyframeModel* keyframe_model) {
+void UiElement::OnSizeAnimated(const gfx::SizeF& size,
+                               int target_property_id,
+                               cc::KeyframeModel* keyframe_model) {
   if (size_ == size)
     return;
   size_ = size;
   world_space_transform_dirty_ = true;
 }
 
+void UiElement::OnColorAnimated(const SkColor& size,
+                                int target_property_id,
+                                cc::KeyframeModel* keyframe_model) {}
+
 void UiElement::SetTransitionedProperties(
     const std::set<TargetProperty>& properties) {
   std::set<int> converted_properties(properties.begin(), properties.end());
diff --git a/chrome/browser/vr/elements/ui_element.h b/chrome/browser/vr/elements/ui_element.h
index 61ebe85..045482d 100644
--- a/chrome/browser/vr/elements/ui_element.h
+++ b/chrome/browser/vr/elements/ui_element.h
@@ -12,7 +12,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
-#include "cc/animation/animation_target.h"
+#include "cc/animation/animation_curve.h"
 #include "chrome/browser/vr/animation.h"
 #include "chrome/browser/vr/audio_delegate.h"
 #include "chrome/browser/vr/databinding/binding_base.h"
@@ -96,7 +96,10 @@
   float distance_to_plane;
 };
 
-class VR_UI_EXPORT UiElement : public cc::AnimationTarget {
+class VR_UI_EXPORT UiElement : public cc::FloatAnimationCurve::Target,
+                               public cc::TransformAnimationCurve::Target,
+                               public cc::SizeAnimationCurve::Target,
+                               public cc::ColorAnimationCurve::Target {
  public:
   UiElement();
   ~UiElement() override;
@@ -390,27 +393,18 @@
   gfx::PointF GetUnitRectangleCoordinates(
       const gfx::Point3F& world_point) const;
 
-  // cc::AnimationTarget
-  void NotifyClientFloatAnimated(float value,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override;
-  void NotifyClientTransformOperationsAnimated(
-      const gfx::TransformOperations& operations,
-      int target_property_id,
-      cc::KeyframeModel* keyframe_model) override;
-  void NotifyClientSizeAnimated(const gfx::SizeF& size,
-                                int target_property_id,
-                                cc::KeyframeModel* keyframe_model) override;
-  void NotifyClientFilterAnimated(const cc::FilterOperations& filter,
-                                  int target_property_id,
-                                  cc::KeyframeModel* keyframe_model) override {}
-  void NotifyClientColorAnimated(SkColor color,
-                                 int target_property_id,
-                                 cc::KeyframeModel* keyframe_model) override {}
-  void NotifyClientScrollOffsetAnimated(
-      const gfx::ScrollOffset& scroll_offset,
-      int target_property_id,
-      cc::KeyframeModel* keyframe_model) override {}
+  void OnFloatAnimated(const float& value,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override;
+  void OnTransformAnimated(const gfx::TransformOperations& operations,
+                           int target_property_id,
+                           cc::KeyframeModel* keyframe_model) override;
+  void OnSizeAnimated(const gfx::SizeF& size,
+                      int target_property_id,
+                      cc::KeyframeModel* keyframe_model) override;
+  void OnColorAnimated(const SkColor& size,
+                       int target_property_id,
+                       cc::KeyframeModel* keyframe_model) override;
 
   void SetTransitionedProperties(const std::set<TargetProperty>& properties);
   void SetTransitionDuration(base::TimeDelta delta);
diff --git a/chrome/browser/vr/elements/ui_element_unittest.cc b/chrome/browser/vr/elements/ui_element_unittest.cc
index 8589c40..1d9c0f6b 100644
--- a/chrome/browser/vr/elements/ui_element_unittest.cc
+++ b/chrome/browser/vr/elements/ui_element_unittest.cc
@@ -253,9 +253,9 @@
   scene.RunFirstFrameForTest();
   auto rect = std::make_unique<UiElement>();
   rect->SetSize(10, 100);
-  rect->AddKeyframeModel(CreateBoundsAnimation(1, 1, gfx::SizeF(10, 100),
-                                               gfx::SizeF(20, 200),
-                                               MicrosecondsToDelta(10000)));
+  rect->AddKeyframeModel(
+      CreateBoundsAnimation(rect.get(), 1, 1, gfx::SizeF(10, 100),
+                            gfx::SizeF(20, 200), MicrosecondsToDelta(10000)));
   UiElement* rect_ptr = rect.get();
   scene.AddUiElement(kRoot, std::move(rect));
   base::TimeTicks start_time = MicrosecondsToTicks(1);
@@ -277,8 +277,9 @@
   from_operations.AppendTranslate(10, 100, 1000);
   gfx::TransformOperations to_operations;
   to_operations.AppendTranslate(20, 200, 2000);
-  rect_ptr->AddKeyframeModel(CreateTransformAnimation(
-      2, 2, from_operations, to_operations, MicrosecondsToDelta(10000)));
+  rect_ptr->AddKeyframeModel(
+      CreateTransformAnimation(rect_ptr, 2, 2, from_operations, to_operations,
+                               MicrosecondsToDelta(10000)));
 
   base::TimeTicks start_time = MicrosecondsToTicks(1);
   EXPECT_TRUE(scene.OnBeginFrame(start_time, kStartHeadPose));
diff --git a/chrome/browser/vr/test/animation_utils.cc b/chrome/browser/vr/test/animation_utils.cc
index f84adba..51d6993c 100644
--- a/chrome/browser/vr/test/animation_utils.cc
+++ b/chrome/browser/vr/test/animation_utils.cc
@@ -10,6 +10,7 @@
 namespace vr {
 
 std::unique_ptr<cc::KeyframeModel> CreateTransformAnimation(
+    cc::TransformAnimationCurve::Target* target,
     int id,
     int group,
     const gfx::TransformOperations& from,
@@ -20,6 +21,7 @@
   curve->AddKeyframe(
       cc::TransformKeyframe::Create(base::TimeDelta(), from, nullptr));
   curve->AddKeyframe(cc::TransformKeyframe::Create(duration, to, nullptr));
+  curve->set_target(target);
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
       std::move(curve), id, group,
       cc::KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
@@ -27,6 +29,7 @@
 }
 
 std::unique_ptr<cc::KeyframeModel> CreateBoundsAnimation(
+    cc::SizeAnimationCurve::Target* target,
     int id,
     int group,
     const gfx::SizeF& from,
@@ -37,6 +40,7 @@
   curve->AddKeyframe(
       cc::SizeKeyframe::Create(base::TimeDelta(), from, nullptr));
   curve->AddKeyframe(cc::SizeKeyframe::Create(duration, to, nullptr));
+  curve->set_target(target);
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
       std::move(curve), id, group,
       cc::KeyframeModel::TargetPropertyId(TargetProperty::BOUNDS)));
@@ -44,6 +48,7 @@
 }
 
 std::unique_ptr<cc::KeyframeModel> CreateOpacityAnimation(
+    cc::FloatAnimationCurve::Target* target,
     int id,
     int group,
     float from,
@@ -54,6 +59,7 @@
   curve->AddKeyframe(
       cc::FloatKeyframe::Create(base::TimeDelta(), from, nullptr));
   curve->AddKeyframe(cc::FloatKeyframe::Create(duration, to, nullptr));
+  curve->set_target(target);
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
       std::move(curve), id, group,
       cc::KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
@@ -61,6 +67,7 @@
 }
 
 std::unique_ptr<cc::KeyframeModel> CreateBackgroundColorAnimation(
+    cc::ColorAnimationCurve::Target* target,
     int id,
     int group,
     SkColor from,
@@ -71,6 +78,7 @@
   curve->AddKeyframe(
       cc::ColorKeyframe::Create(base::TimeDelta(), from, nullptr));
   curve->AddKeyframe(cc::ColorKeyframe::Create(duration, to, nullptr));
+  curve->set_target(target);
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
       std::move(curve), id, group,
       cc::KeyframeModel::TargetPropertyId(TargetProperty::BACKGROUND_COLOR)));
diff --git a/chrome/browser/vr/test/animation_utils.h b/chrome/browser/vr/test/animation_utils.h
index 4a959507..536b5d3 100644
--- a/chrome/browser/vr/test/animation_utils.h
+++ b/chrome/browser/vr/test/animation_utils.h
@@ -16,6 +16,7 @@
 class UiElement;
 
 std::unique_ptr<cc::KeyframeModel> CreateTransformAnimation(
+    cc::TransformAnimationCurve::Target* target,
     int id,
     int group,
     const gfx::TransformOperations& from,
@@ -23,6 +24,7 @@
     base::TimeDelta duration);
 
 std::unique_ptr<cc::KeyframeModel> CreateBoundsAnimation(
+    cc::SizeAnimationCurve::Target* target,
     int id,
     int group,
     const gfx::SizeF& from,
@@ -30,6 +32,7 @@
     base::TimeDelta duration);
 
 std::unique_ptr<cc::KeyframeModel> CreateOpacityAnimation(
+    cc::FloatAnimationCurve::Target* target,
     int id,
     int group,
     float from,
@@ -37,6 +40,7 @@
     base::TimeDelta duration);
 
 std::unique_ptr<cc::KeyframeModel> CreateBackgroundColorAnimation(
+    cc::ColorAnimationCurve::Target* target,
     int id,
     int group,
     SkColor from,
diff --git a/chrome/browser/vr/ui_scene_creator.cc b/chrome/browser/vr/ui_scene_creator.cc
index e277453..243f84b 100644
--- a/chrome/browser/vr/ui_scene_creator.cc
+++ b/chrome/browser/vr/ui_scene_creator.cc
@@ -19,7 +19,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "cc/animation/animation_curve.h"
-#include "cc/animation/animation_target.h"
 #include "cc/animation/keyframe_effect.h"
 #include "cc/animation/keyframed_animation_curve.h"
 #include "chrome/app/vector_icons/vector_icons.h"
@@ -1003,6 +1002,8 @@
       cc::CubicBezierTimingFunction::CreatePreset(
           cc::CubicBezierTimingFunction::EaseType::EASE)));
 
+  curve->set_target(e);
+
   e->AddKeyframeModel(cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
       Animation::GetNextGroupId(),
@@ -1089,6 +1090,8 @@
       cc::CubicBezierTimingFunction::CreatePreset(
           cc::CubicBezierTimingFunction::EaseType::EASE)));
 
+  curve->set_target(e);
+
   e->AddKeyframeModel(cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
       Animation::GetNextGroupId(),
diff --git a/chrome/browser/vr/ui_scene_unittest.cc b/chrome/browser/vr/ui_scene_unittest.cc
index b614ef8..fce50d4 100644
--- a/chrome/browser/vr/ui_scene_unittest.cc
+++ b/chrome/browser/vr/ui_scene_unittest.cc
@@ -200,7 +200,7 @@
   UiScene scene;
   auto element = std::make_unique<UiElement>();
   element->AddKeyframeModel(CreateBackgroundColorAnimation(
-      1, 1, SK_ColorBLACK, SK_ColorWHITE, MsToDelta(1000)));
+      element.get(), 1, 1, SK_ColorBLACK, SK_ColorWHITE, MsToDelta(1000)));
   UiElement* element_ptr = element.get();
   scene.AddUiElement(kRoot, std::move(element));
   EXPECT_TRUE(scene.OnBeginFrame(MsToTicks(1), kStartHeadPose));
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index a254684..3bc7ee3 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1613023195-8377895b1b15403ecc837429f439cadb3042b611.profdata
+chrome-linux-master-1613044789-15ec05d079ab67299de34510cb04d649684043bf.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index dc0abb4..d868440 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1613023195-94b18c9dfd197defef9d6948d2f80fcdef826da3.profdata
+chrome-mac-master-1613044789-816b8279568428b4275c365c959703c7954160e2.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 05c5fe79..e400c8fd 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1612925989-93e1219af687f337c15db98178231c8ee649131f.profdata
+chrome-win64-master-1613055160-5242692ac3423259c37a60e2fdfebfd341a8ade9.profdata
diff --git a/chrome/notification_helper/BUILD.gn b/chrome/notification_helper/BUILD.gn
index 07237b12..7d4de239 100644
--- a/chrome/notification_helper/BUILD.gn
+++ b/chrome/notification_helper/BUILD.gn
@@ -28,6 +28,7 @@
     "//base",
     "//build/win:default_exe_manifest",
     "//chrome/common:version_header",
+    "//chrome/install_static:install_static_util",
     "//chrome/install_static:primary_module",
     "//components/crash/core/app",
     "//components/crash/core/app:crash_export_thunks",
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index 68ca9bd..a56a2e8 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -165,6 +165,7 @@
     "//components/no_state_prefetch/common",
     "//components/no_state_prefetch/renderer",
     "//components/omnibox/common",
+    "//components/optimization_guide/content/renderer",
     "//components/page_image_annotation/content/renderer",
     "//components/page_load_metrics/renderer",
     "//components/paint_preview/buildflags",
diff --git a/chrome/renderer/DEPS b/chrome/renderer/DEPS
index 07dc825..e2da1f5 100644
--- a/chrome/renderer/DEPS
+++ b/chrome/renderer/DEPS
@@ -30,6 +30,7 @@
   "+components/no_state_prefetch/common",
   "+components/no_state_prefetch/renderer",
   "+components/offline_pages/buildflags",
+  "+components/optimization_guide/content/renderer",
   "+components/page_load_metrics/common",
   "+components/page_load_metrics/renderer",
   "+components/paint_preview/buildflags",
diff --git a/chrome/renderer/autofill/form_autofill_browsertest.cc b/chrome/renderer/autofill/form_autofill_browsertest.cc
index af15def..5847a954 100644
--- a/chrome/renderer/autofill/form_autofill_browsertest.cc
+++ b/chrome/renderer/autofill/form_autofill_browsertest.cc
@@ -398,8 +398,9 @@
     ExpectLabels(html, id_attributes, name_attributes, labels, names, values);
   }
 
-  typedef void (*FillFormFunction)(const FormData& form,
-                                   const WebFormControlElement& element);
+  typedef std::vector<WebFormControlElement> (*FillFormFunction)(
+      const FormData& form,
+      const WebFormControlElement& element);
 
   typedef WebString (*GetValueFunction)(WebFormControlElement element);
 
@@ -2152,7 +2153,7 @@
     EXPECT_EQ(0, firstname.SelectionEnd());
   }
 
-  void TestClearPreviewedFormWithElement(const char* html) {
+  void TestClearPreviewedElements(const char* html) {
     LoadHTML(html);
     WebLocalFrame* web_frame = GetMainFrame();
     ASSERT_NE(nullptr, web_frame);
@@ -2161,27 +2162,28 @@
     std::vector<FormData> forms = form_cache.ExtractNewForms(nullptr);
     ASSERT_EQ(1U, forms.size());
 
+    std::vector<WebFormControlElement> elements;
+    elements.push_back(GetInputElementById("firstname"));
+    elements.push_back(GetInputElementById("lastname"));
+    elements.push_back(GetInputElementById("email"));
+    elements.push_back(GetInputElementById("email2"));
+    elements.push_back(GetInputElementById("phone"));
+    WebInputElement& firstname = *blink::ToWebInputElement(&elements[0]);
+    WebInputElement& lastname = *blink::ToWebInputElement(&elements[1]);
+
     // Set the auto-filled attribute.
-    WebInputElement firstname = GetInputElementById("firstname");
-    firstname.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement lastname = GetInputElementById("lastname");
-    lastname.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement email = GetInputElementById("email");
-    email.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement email2 = GetInputElementById("email2");
-    email2.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement phone = GetInputElementById("phone");
-    phone.SetAutofillState(WebAutofillState::kPreviewed);
+    for (WebFormControlElement& e : elements) {
+      e.SetAutofillState(WebAutofillState::kPreviewed);
+    }
 
     // Set the suggested values on two of the elements.
     lastname.SetSuggestedValue(WebString::FromASCII("Earp"));
-    email.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
-    email2.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
-    phone.SetSuggestedValue(WebString::FromASCII("650-777-9999"));
+    elements[2].SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+    elements[3].SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+    elements[4].SetSuggestedValue(WebString::FromASCII("650-777-9999"));
 
     // Clear the previewed fields.
-    EXPECT_TRUE(
-        ClearPreviewedFormWithElement(lastname, WebAutofillState::kNotFilled));
+    ClearPreviewedElements(elements, lastname, WebAutofillState::kNotFilled);
 
     // Fields with empty suggestions suggestions are not modified.
     EXPECT_EQ(ASCIIToUTF16("Wyatt"), firstname.Value().Utf16());
@@ -2189,18 +2191,12 @@
     EXPECT_TRUE(firstname.IsAutofilled());
 
     // Verify the previewed fields are cleared.
-    EXPECT_TRUE(lastname.Value().IsEmpty());
-    EXPECT_TRUE(lastname.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(lastname.IsAutofilled());
-    EXPECT_TRUE(email.Value().IsEmpty());
-    EXPECT_TRUE(email.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(email.IsAutofilled());
-    EXPECT_TRUE(email2.Value().IsEmpty());
-    EXPECT_TRUE(email2.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(email2.IsAutofilled());
-    EXPECT_TRUE(phone.Value().IsEmpty());
-    EXPECT_TRUE(phone.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(phone.IsAutofilled());
+    for (size_t i = 1; i < elements.size(); ++i) {
+      SCOPED_TRACE(testing::Message() << "Element " << i);
+      EXPECT_TRUE(elements[i].Value().IsEmpty());
+      EXPECT_TRUE(elements[i].SuggestedValue().IsEmpty());
+      EXPECT_FALSE(elements[i].IsAutofilled());
+    }
 
     // Verify that the cursor position has been updated.
     EXPECT_EQ(0, lastname.SelectionStart());
@@ -2216,28 +2212,29 @@
     std::vector<FormData> forms = form_cache.ExtractNewForms(nullptr);
     ASSERT_EQ(1U, forms.size());
 
+    std::vector<WebFormControlElement> elements;
+    elements.push_back(GetInputElementById("firstname"));
+    elements.push_back(GetInputElementById("lastname"));
+    elements.push_back(GetInputElementById("email"));
+    elements.push_back(GetInputElementById("email2"));
+    elements.push_back(GetInputElementById("phone"));
+    WebInputElement& firstname = *blink::ToWebInputElement(&elements[0]);
+    WebInputElement& lastname = *blink::ToWebInputElement(&elements[1]);
+
     // Set the auto-filled attribute.
-    WebInputElement firstname = GetInputElementById("firstname");
-    firstname.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement lastname = GetInputElementById("lastname");
-    lastname.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement email = GetInputElementById("email");
-    email.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement email2 = GetInputElementById("email2");
-    email2.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement phone = GetInputElementById("phone");
-    phone.SetAutofillState(WebAutofillState::kPreviewed);
+    for (WebFormControlElement& e : elements) {
+      e.SetAutofillState(WebAutofillState::kPreviewed);
+    }
 
     // Set the suggested values on all of the elements.
     firstname.SetSuggestedValue(WebString::FromASCII("Wyatt"));
     lastname.SetSuggestedValue(WebString::FromASCII("Earp"));
-    email.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
-    email2.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
-    phone.SetSuggestedValue(WebString::FromASCII("650-777-9999"));
+    elements[2].SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+    elements[3].SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+    elements[4].SetSuggestedValue(WebString::FromASCII("650-777-9999"));
 
     // Clear the previewed fields.
-    EXPECT_TRUE(
-        ClearPreviewedFormWithElement(firstname, WebAutofillState::kNotFilled));
+    ClearPreviewedElements(elements, firstname, WebAutofillState::kNotFilled);
 
     // Fields with non-empty values are restored.
     EXPECT_EQ(ASCIIToUTF16("W"), firstname.Value().Utf16());
@@ -2247,18 +2244,12 @@
     EXPECT_EQ(1, firstname.SelectionEnd());
 
     // Verify the previewed fields are cleared.
-    EXPECT_TRUE(lastname.Value().IsEmpty());
-    EXPECT_TRUE(lastname.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(lastname.IsAutofilled());
-    EXPECT_TRUE(email.Value().IsEmpty());
-    EXPECT_TRUE(email.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(email.IsAutofilled());
-    EXPECT_TRUE(email2.Value().IsEmpty());
-    EXPECT_TRUE(email2.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(email2.IsAutofilled());
-    EXPECT_TRUE(phone.Value().IsEmpty());
-    EXPECT_TRUE(phone.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(phone.IsAutofilled());
+    for (size_t i = 1; i < elements.size(); ++i) {
+      SCOPED_TRACE(testing::Message() << "Element " << i);
+      EXPECT_TRUE(elements[i].Value().IsEmpty());
+      EXPECT_TRUE(elements[i].SuggestedValue().IsEmpty());
+      EXPECT_FALSE(elements[i].IsAutofilled());
+    }
   }
 
   void TestClearPreviewedFormWithAutofilledInitiatingNode(const char* html) {
@@ -2270,28 +2261,29 @@
     std::vector<FormData> forms = form_cache.ExtractNewForms(nullptr);
     ASSERT_EQ(1U, forms.size());
 
+    std::vector<WebFormControlElement> elements;
+    elements.push_back(GetInputElementById("firstname"));
+    elements.push_back(GetInputElementById("lastname"));
+    elements.push_back(GetInputElementById("email"));
+    elements.push_back(GetInputElementById("email2"));
+    elements.push_back(GetInputElementById("phone"));
+    WebInputElement& firstname = *blink::ToWebInputElement(&elements[0]);
+    WebInputElement& lastname = *blink::ToWebInputElement(&elements[1]);
+
     // Set the auto-filled attribute.
-    WebInputElement firstname = GetInputElementById("firstname");
-    firstname.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement lastname = GetInputElementById("lastname");
-    lastname.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement email = GetInputElementById("email");
-    email.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement email2 = GetInputElementById("email2");
-    email2.SetAutofillState(WebAutofillState::kPreviewed);
-    WebInputElement phone = GetInputElementById("phone");
-    phone.SetAutofillState(WebAutofillState::kPreviewed);
+    for (WebFormControlElement& e : elements) {
+      e.SetAutofillState(WebAutofillState::kPreviewed);
+    }
 
     // Set the suggested values on all of the elements.
     firstname.SetSuggestedValue(WebString::FromASCII("Wyatt"));
     lastname.SetSuggestedValue(WebString::FromASCII("Earp"));
-    email.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
-    email2.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
-    phone.SetSuggestedValue(WebString::FromASCII("650-777-9999"));
+    elements[2].SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+    elements[3].SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+    elements[4].SetSuggestedValue(WebString::FromASCII("650-777-9999"));
 
     // Clear the previewed fields.
-    EXPECT_TRUE(ClearPreviewedFormWithElement(firstname,
-                                              WebAutofillState::kAutofilled));
+    ClearPreviewedElements(elements, firstname, WebAutofillState::kAutofilled);
 
     // Fields with non-empty values are restored.
     EXPECT_EQ(ASCIIToUTF16("W"), firstname.Value().Utf16());
@@ -2301,18 +2293,12 @@
     EXPECT_EQ(1, firstname.SelectionEnd());
 
     // Verify the previewed fields are cleared.
-    EXPECT_TRUE(lastname.Value().IsEmpty());
-    EXPECT_TRUE(lastname.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(lastname.IsAutofilled());
-    EXPECT_TRUE(email.Value().IsEmpty());
-    EXPECT_TRUE(email.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(email.IsAutofilled());
-    EXPECT_TRUE(email2.Value().IsEmpty());
-    EXPECT_TRUE(email2.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(email2.IsAutofilled());
-    EXPECT_TRUE(phone.Value().IsEmpty());
-    EXPECT_TRUE(phone.SuggestedValue().IsEmpty());
-    EXPECT_FALSE(phone.IsAutofilled());
+    for (size_t i = 1; i < elements.size(); ++i) {
+      SCOPED_TRACE(testing::Message() << "Element " << i);
+      EXPECT_TRUE(elements[i].Value().IsEmpty());
+      EXPECT_TRUE(elements[i].SuggestedValue().IsEmpty());
+      EXPECT_FALSE(elements[i].IsAutofilled());
+    }
   }
 
   void TestClearOnlyAutofilledFields(const char* html) {
@@ -5101,8 +5087,8 @@
       true);
 }
 
-TEST_F(FormAutofillTest, ClearPreviewedFormWithElement) {
-  TestClearPreviewedFormWithElement(
+TEST_F(FormAutofillTest, ClearPreviewedElements) {
+  TestClearPreviewedElements(
       "<FORM name='TestForm' action='http://abc.com' method='post'>"
       "  <INPUT type='text' id='firstname' value='Wyatt'/>"
       "  <INPUT type='text' id='lastname'/>"
@@ -5114,7 +5100,7 @@
 }
 
 TEST_F(FormAutofillTest, ClearPreviewedFormWithElementForUnownedForm) {
-  TestClearPreviewedFormWithElement(
+  TestClearPreviewedElements(
       "<HEAD><TITLE>store checkout</TITLE></HEAD>"
       "<INPUT type='text' id='firstname' value='Wyatt'/>"
       "<INPUT type='text' id='lastname'/>"
diff --git a/chrome/renderer/chrome_render_frame_observer.cc b/chrome/renderer/chrome_render_frame_observer.cc
index 629aec6d..578cb595 100644
--- a/chrome/renderer/chrome_render_frame_observer.cc
+++ b/chrome/renderer/chrome_render_frame_observer.cc
@@ -27,6 +27,7 @@
 #include "components/crash/core/common/crash_key.h"
 #include "components/no_state_prefetch/renderer/no_state_prefetch_helper.h"
 #include "components/offline_pages/buildflags/buildflags.h"
+#include "components/optimization_guide/content/renderer/page_text_agent.h"
 #include "components/translate/content/renderer/translate_agent.h"
 #include "components/translate/core/common/translate_util.h"
 #include "components/web_cache/renderer/web_cache_impl.h"
@@ -124,6 +125,7 @@
     web_cache::WebCacheImpl* web_cache_impl)
     : content::RenderFrameObserver(render_frame),
       translate_agent_(nullptr),
+      page_text_agent_(new optimization_guide::PageTextAgent(render_frame)),
       web_cache_impl_(web_cache_impl) {
   render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
       base::BindRepeating(
@@ -477,9 +479,19 @@
 
 void ChromeRenderFrameObserver::CapturePageText(
     blink::WebMeaningfulLayout layout_type) {
-  if (!ShouldCapturePageTextForTranslateOrPhishing(layout_type)) {
+  bool capture_for_translate_phishing =
+      ShouldCapturePageTextForTranslateOrPhishing(layout_type);
+
+  uint64_t capture_max_size =
+      capture_for_translate_phishing ? kMaxIndexChars : 0;
+  auto text_callback = page_text_agent_->MaybeRequestTextDumpOnLayoutEvent(
+      layout_type, &capture_max_size);
+  bool capture_for_opt_guide = !!text_callback;
+
+  if (!capture_for_translate_phishing && !capture_for_opt_guide) {
     return;
   }
+  DCHECK_GT(capture_max_size, 0U);
 
   base::string16 contents;
   {
@@ -487,7 +499,9 @@
     TRACE_EVENT0("renderer", "ChromeRenderFrameObserver::CapturePageText");
 
     contents = WebFrameContentDumper::DumpFrameTreeAsText(
-                   render_frame()->GetWebFrame(), kMaxIndexChars)
+                   render_frame()->GetWebFrame(),
+                   // TODO(crbug/1163244): Move everything to be uint32.
+                   static_cast<size_t>(capture_max_size))
                    .Utf16();
   }
 
@@ -498,6 +512,10 @@
     translate_agent_->PageCaptured(contents);
   }
 
+  if (text_callback) {
+    std::move(text_callback).Run(contents);
+  }
+
 #if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
   // Will swap out the string.
   if (phishing_classifier_) {
diff --git a/chrome/renderer/chrome_render_frame_observer.h b/chrome/renderer/chrome_render_frame_observer.h
index fe02e577..c049797 100644
--- a/chrome/renderer/chrome_render_frame_observer.h
+++ b/chrome/renderer/chrome_render_frame_observer.h
@@ -23,6 +23,10 @@
 class Size;
 }
 
+namespace optimization_guide {
+class PageTextAgent;
+}
+
 namespace safe_browsing {
 class PhishingClassifierDelegate;
 }
@@ -126,6 +130,7 @@
 
   // Have the same lifetime as us.
   translate::TranslateAgent* translate_agent_;
+  optimization_guide::PageTextAgent* page_text_agent_;
 #if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
   safe_browsing::PhishingClassifierDelegate* phishing_classifier_ = nullptr;
 #endif
diff --git a/chrome/renderer/chrome_render_frame_observer_browsertest.cc b/chrome/renderer/chrome_render_frame_observer_browsertest.cc
index e3944ed6..c77c11c4d 100644
--- a/chrome/renderer/chrome_render_frame_observer_browsertest.cc
+++ b/chrome/renderer/chrome_render_frame_observer_browsertest.cc
@@ -7,13 +7,18 @@
 #include <tuple>
 
 #include "base/bind.h"
+#include "base/callback.h"
 #include "base/command_line.h"
 #include "base/run_loop.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/chrome_render_view_test.h"
 #include "components/no_state_prefetch/renderer/no_state_prefetch_helper.h"
+#include "components/optimization_guide/content/renderer/page_text_agent.h"
 #include "components/translate/content/common/translate.mojom.h"
 #include "components/translate/content/renderer/translate_agent.h"
 #include "components/translate/core/common/translate_constants.h"
@@ -22,7 +27,10 @@
 #include "content/public/renderer/render_view.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
 #include "third_party/blink/public/web/web_frame_widget.h"
 #include "third_party/blink/public/web/web_local_frame.h"
@@ -60,6 +68,35 @@
   mojo::ReceiverSet<translate::mojom::ContentTranslateDriver> receivers_;
 };
 
+class TestOptGuideConsumer
+    : public optimization_guide::mojom::PageTextConsumer {
+ public:
+  TestOptGuideConsumer() = default;
+  ~TestOptGuideConsumer() override = default;
+
+  base::string16 text() const { return base::StrCat(chunks_); }
+  bool on_chunks_end_called() const { return on_chunks_end_called_; }
+  size_t num_chunks() const { return chunks_.size(); }
+
+  void Bind(mojo::PendingReceiver<optimization_guide::mojom::PageTextConsumer>
+                pending_receiver) {
+    receiver_.Bind(std::move(pending_receiver));
+  }
+
+  // optimization_guide::mojom::PageTextConsumer:
+  void OnTextDumpChunk(const base::string16& chunk) override {
+    ASSERT_FALSE(on_chunks_end_called_);
+    chunks_.push_back(chunk);
+  }
+
+  void OnChunksEnd() override { on_chunks_end_called_ = true; }
+
+ private:
+  mojo::Receiver<optimization_guide::mojom::PageTextConsumer> receiver_{this};
+  std::vector<base::string16> chunks_;
+  bool on_chunks_end_called_ = false;
+};
+
 }  // namespace
 
 // Constants for UMA statistic collection.
@@ -183,7 +220,46 @@
   EXPECT_FALSE(fake_translate_driver_.page_level_translation_critiera_met_);
 }
 
-#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
+TEST_F(ChromeRenderFrameObserverTest, OptGuideGetsText) {
+  optimization_guide::PageTextAgent* agent =
+      optimization_guide::PageTextAgent::Get(render_frame());
+  ASSERT_TRUE(agent);
+  render_frame()->GetRemoteAssociatedInterfaces()->OverrideBinderForTesting(
+      optimization_guide::mojom::PageTextService::Name_,
+      base::BindRepeating(
+          [&](optimization_guide::PageTextAgent* agent,
+              mojo::ScopedInterfaceEndpointHandle handle) {
+            agent->Bind(mojo::PendingAssociatedReceiver<
+                        optimization_guide::mojom::PageTextService>(
+                std::move(handle)));
+          },
+          agent));
+
+  mojo::PendingRemote<optimization_guide::mojom::PageTextConsumer>
+      consumer_remote;
+  TestOptGuideConsumer consumer;
+  consumer.Bind(consumer_remote.InitWithNewPipeAndPassReceiver());
+
+  auto request = optimization_guide::mojom::PageTextDumpRequest::New();
+  request->max_size = 123;
+  request->event = optimization_guide::mojom::TextDumpEvent::kFirstLayout;
+
+  mojo::AssociatedRemote<optimization_guide::mojom::PageTextService>
+      text_service;
+  render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(&text_service);
+  text_service->RequestPageTextDump(std::move(request),
+                                    std::move(consumer_remote));
+  base::RunLoop().RunUntilIdle();
+
+  base::HistogramTester histogram_tester;
+  LoadHTML("<html><body>foo</body></html>");
+  histogram_tester.ExpectTotalCount(kTranslateCaptureText, 2);
+
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(base::ASCIIToUTF16("foo"), consumer.text());
+  EXPECT_TRUE(consumer.on_chunks_end_called());
+}
 
 class ChromeRenderFrameObserverNoTranslateNorPhishingTest
     : public ChromeRenderFrameObserverTest {
@@ -210,6 +286,49 @@
   EXPECT_FALSE(fake_translate_driver_.page_level_translation_critiera_met_);
 }
 
+TEST_F(ChromeRenderFrameObserverNoTranslateNorPhishingTest, OptGuideGetsText) {
+  optimization_guide::PageTextAgent* agent =
+      optimization_guide::PageTextAgent::Get(render_frame());
+  ASSERT_TRUE(agent);
+  render_frame()->GetRemoteAssociatedInterfaces()->OverrideBinderForTesting(
+      optimization_guide::mojom::PageTextService::Name_,
+      base::BindRepeating(
+          [&](optimization_guide::PageTextAgent* agent,
+              mojo::ScopedInterfaceEndpointHandle handle) {
+            agent->Bind(mojo::PendingAssociatedReceiver<
+                        optimization_guide::mojom::PageTextService>(
+                std::move(handle)));
+          },
+          agent));
+
+  mojo::PendingRemote<optimization_guide::mojom::PageTextConsumer>
+      consumer_remote;
+  TestOptGuideConsumer consumer;
+  consumer.Bind(consumer_remote.InitWithNewPipeAndPassReceiver());
+
+  auto request = optimization_guide::mojom::PageTextDumpRequest::New();
+  request->max_size = 123;
+  request->event = optimization_guide::mojom::TextDumpEvent::kFirstLayout;
+
+  mojo::AssociatedRemote<optimization_guide::mojom::PageTextService>
+      text_service;
+  render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(&text_service);
+  text_service->RequestPageTextDump(std::move(request),
+                                    std::move(consumer_remote));
+  base::RunLoop().RunUntilIdle();
+
+  base::HistogramTester histogram_tester;
+  LoadHTML("<html><body>foo</body></html>");
+  histogram_tester.ExpectTotalCount(kTranslateCaptureText, 1);
+
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(base::ASCIIToUTF16("foo"), consumer.text());
+  EXPECT_TRUE(consumer.on_chunks_end_called());
+}
+
+#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
+
 class ChromeRenderFrameObserverNoTranslateYesPhishingTest
     : public ChromeRenderFrameObserverTest {
  public:
diff --git a/chrome/services/mac_notifications/mac_notification_service_ns.h b/chrome/services/mac_notifications/mac_notification_service_ns.h
index b138292b..350bdb9 100644
--- a/chrome/services/mac_notifications/mac_notification_service_ns.h
+++ b/chrome/services/mac_notifications/mac_notification_service_ns.h
@@ -30,6 +30,8 @@
   ~MacNotificationServiceNS() override;
 
   // notifications::mojom::MacNotificationService:
+  void CloseNotification(
+      notifications::mojom::NotificationIdentifierPtr identifier) override;
   void CloseAllNotifications() override;
 
  private:
diff --git a/chrome/services/mac_notifications/mac_notification_service_ns.mm b/chrome/services/mac_notifications/mac_notification_service_ns.mm
index 393d11f..0a723e2 100644
--- a/chrome/services/mac_notifications/mac_notification_service_ns.mm
+++ b/chrome/services/mac_notifications/mac_notification_service_ns.mm
@@ -9,6 +9,8 @@
 
 #include <utility>
 
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/services/mac_notifications/public/cpp/notification_constants_mac.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 @interface AlertNSNotificationCenterDelegate
@@ -34,6 +36,30 @@
   [notification_center_ setDelegate:nil];
 }
 
+void MacNotificationServiceNS::CloseNotification(
+    notifications::mojom::NotificationIdentifierPtr identifier) {
+  NSString* notification_id = base::SysUTF8ToNSString(identifier->id);
+  NSString* profile_id = base::SysUTF8ToNSString(identifier->profile->id);
+  bool incognito = identifier->profile->incognito;
+
+  for (NSUserNotification* toast in
+       [notification_center_ deliveredNotifications]) {
+    NSString* toast_id =
+        [toast.userInfo objectForKey:notification_constants::kNotificationId];
+    NSString* toast_profile_id = [toast.userInfo
+        objectForKey:notification_constants::kNotificationProfileId];
+    BOOL toast_incognito = [[toast.userInfo
+        objectForKey:notification_constants::kNotificationIncognito] boolValue];
+
+    if ([notification_id isEqualToString:toast_id] &&
+        [profile_id isEqualToString:toast_profile_id] &&
+        incognito == toast_incognito) {
+      [notification_center_ removeDeliveredNotification:toast];
+      break;
+    }
+  }
+}
+
 void MacNotificationServiceNS::CloseAllNotifications() {
   [notification_center_ removeAllDeliveredNotifications];
 }
diff --git a/chrome/services/mac_notifications/mac_notification_service_ns_unittest.mm b/chrome/services/mac_notifications/mac_notification_service_ns_unittest.mm
index 2e06b1b..295ba71e 100644
--- a/chrome/services/mac_notifications/mac_notification_service_ns_unittest.mm
+++ b/chrome/services/mac_notifications/mac_notification_service_ns_unittest.mm
@@ -5,8 +5,10 @@
 #import <Foundation/NSUserNotification.h>
 
 #include "base/run_loop.h"
+#include "base/strings/sys_string_conversions.h"
 #include "base/test/task_environment.h"
 #import "chrome/services/mac_notifications/mac_notification_service_ns.h"
+#include "chrome/services/mac_notifications/public/cpp/notification_constants_mac.h"
 #include "chrome/services/mac_notifications/public/mojom/mac_notifications.mojom.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -36,6 +38,9 @@
 }
 - (void)setDelegate:(id<NSUserNotificationCenterDelegate>)delegate {
 }
+- (NSArray*)deliveredNotifications {
+  return nil;
+}
 @end
 
 class MacNotificationServiceNSTest : public testing::Test {
@@ -72,6 +77,23 @@
         }]];
   }
 
+  base::scoped_nsobject<NSUserNotification> CreateNotification(
+      const std::string& notification_id,
+      const std::string& profile_id,
+      bool incognito) {
+    base::scoped_nsobject<NSUserNotification> toast(
+        [[NSUserNotification alloc] init]);
+    toast.get().userInfo = @{
+      notification_constants::
+      kNotificationId : base::SysUTF8ToNSString(notification_id),
+      notification_constants::
+      kNotificationProfileId : base::SysUTF8ToNSString(profile_id),
+      notification_constants::
+      kNotificationIncognito : [NSNumber numberWithBool:incognito],
+    };
+    return toast;
+  }
+
   base::test::TaskEnvironment task_environment_;
   MockNotificationActionHandler mock_handler_;
   mojo::Receiver<notifications::mojom::MacNotificationActionHandler>
@@ -82,6 +104,39 @@
   std::unique_ptr<MacNotificationServiceNS> service_;
 };
 
+TEST_F(MacNotificationServiceNSTest, CloseNotification) {
+  // Create 4 unique notifications and return them from deliveredNotifications.
+  base::scoped_nsobject<NSUserNotification> other1 =
+      CreateNotification("notificationId", "profileId", /*incognito=*/false);
+  base::scoped_nsobject<NSUserNotification> other2 =
+      CreateNotification("notificationId", "profileId2", /*incognito=*/true);
+  base::scoped_nsobject<NSUserNotification> other3 =
+      CreateNotification("notificationId2", "profileId", /*incognito=*/true);
+  base::scoped_nsobject<NSUserNotification> expected =
+      CreateNotification("notificationId", "profileId", /*incognito=*/true);
+  NSArray* notifications =
+      @[ other1.get(), other2.get(), other3.get(), expected.get() ];
+  [[[mock_notification_center_ expect] andReturn:notifications]
+      deliveredNotifications];
+
+  // Expect to close the expected notification.
+  base::RunLoop run_loop;
+  base::RepeatingClosure quit_closure = run_loop.QuitClosure();
+  [[[mock_notification_center_ expect] andDo:^(NSInvocation*) {
+    quit_closure.Run();
+  }] removeDeliveredNotification:expected.get()];
+
+  auto profile_identifier = notifications::mojom::ProfileIdentifier::New(
+      "profileId", /*incognito=*/true);
+  auto notification_identifier =
+      notifications::mojom::NotificationIdentifier::New(
+          "notificationId", std::move(profile_identifier));
+  service_remote_->CloseNotification(std::move(notification_identifier));
+
+  run_loop.Run();
+  [mock_notification_center_ verify];
+}
+
 TEST_F(MacNotificationServiceNSTest, CloseAllNotifications) {
   base::RunLoop run_loop;
   base::RepeatingClosure quit_closure = run_loop.QuitClosure();
diff --git a/chrome/services/mac_notifications/mac_notification_service_un.h b/chrome/services/mac_notifications/mac_notification_service_un.h
index 95b75866..49da39e 100644
--- a/chrome/services/mac_notifications/mac_notification_service_un.h
+++ b/chrome/services/mac_notifications/mac_notification_service_un.h
@@ -30,6 +30,8 @@
   ~MacNotificationServiceUN() override;
 
   // notifications::mojom::MacNotificationService:
+  void CloseNotification(
+      notifications::mojom::NotificationIdentifierPtr identifier) override;
   void CloseAllNotifications() override;
 
  private:
diff --git a/chrome/services/mac_notifications/mac_notification_service_un.mm b/chrome/services/mac_notifications/mac_notification_service_un.mm
index c563217f..4b322971 100644
--- a/chrome/services/mac_notifications/mac_notification_service_un.mm
+++ b/chrome/services/mac_notifications/mac_notification_service_un.mm
@@ -9,6 +9,8 @@
 
 #include <utility>
 
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/services/mac_notifications/public/cpp/notification_utils_mac.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 API_AVAILABLE(macosx(10.14))
@@ -37,6 +39,14 @@
   [notification_center_ setDelegate:nil];
 }
 
+void MacNotificationServiceUN::CloseNotification(
+    notifications::mojom::NotificationIdentifierPtr identifier) {
+  NSString* notification_id = base::SysUTF8ToNSString(DeriveMacNotificationId(
+      identifier->profile->incognito, identifier->profile->id, identifier->id));
+  [notification_center_
+      removeDeliveredNotificationsWithIdentifiers:@[ notification_id ]];
+}
+
 void MacNotificationServiceUN::CloseAllNotifications() {
   [notification_center_ removeAllDeliveredNotifications];
 }
diff --git a/chrome/services/mac_notifications/mac_notification_service_un_unittest.mm b/chrome/services/mac_notifications/mac_notification_service_un_unittest.mm
index 31c3f7bf..2480ff1a 100644
--- a/chrome/services/mac_notifications/mac_notification_service_un_unittest.mm
+++ b/chrome/services/mac_notifications/mac_notification_service_un_unittest.mm
@@ -83,6 +83,28 @@
   std::unique_ptr<MacNotificationServiceUN> service_;
 };
 
+TEST_F(MacNotificationServiceUNTest, CloseNotification) {
+  if (@available(macOS 10.14, *)) {
+    base::RunLoop run_loop;
+    base::RepeatingClosure quit_closure = run_loop.QuitClosure();
+
+    NSString* identifier = @"i|profileId|notificationId";
+    [[[mock_notification_center_ expect] andDo:^(NSInvocation*) {
+      quit_closure.Run();
+    }] removeDeliveredNotificationsWithIdentifiers:@[ identifier ]];
+
+    auto profile_identifier = notifications::mojom::ProfileIdentifier::New(
+        "profileId", /*incognito=*/true);
+    auto notification_identifier =
+        notifications::mojom::NotificationIdentifier::New(
+            "notificationId", std::move(profile_identifier));
+    service_remote_->CloseNotification(std::move(notification_identifier));
+
+    run_loop.Run();
+    [mock_notification_center_ verify];
+  }
+}
+
 TEST_F(MacNotificationServiceUNTest, CloseAllNotifications) {
   if (@available(macOS 10.14, *)) {
     base::RunLoop run_loop;
diff --git a/chrome/services/mac_notifications/public/mojom/mac_notifications.mojom b/chrome/services/mac_notifications/public/mojom/mac_notifications.mojom
index c43e5289..aef04ef 100644
--- a/chrome/services/mac_notifications/public/mojom/mac_notifications.mojom
+++ b/chrome/services/mac_notifications/public/mojom/mac_notifications.mojom
@@ -4,6 +4,28 @@
 
 module notifications.mojom;
 
+// Represents a profile being used to display notifications. A profile with a
+// given |id| might have another child profile with the same |id| but with
+// incognito set to true. This tuple uniquely identifies a profile.
+struct ProfileIdentifier {
+  // Identifies a profile. Usually the name of a profile but should be treated
+  // as an opaque string.
+  string id;
+  // Whether this profile is the incognito profile for |id|.
+  bool incognito;
+};
+
+// Represents a unique identifier for a notification. Notifications might have
+// the same |id| if they belong to different |profile|s but the tuple of both
+// uniquely identifies a notification.
+struct NotificationIdentifier {
+  // Notification id that might contain some developer controlled parts. Should
+  // always be treated as an opaque string.
+  string id;
+  // Profile for which this notification is being shown for.
+  ProfileIdentifier profile;
+};
+
 // Represents a notification and the action taken on it by the user.
 struct NotificationActionInfo {
   // TODO(knollr): Fill struct.
@@ -14,6 +36,9 @@
 interface MacNotificationService {
   // TODO(knollr): Add remaining methods to show / query notifications.
 
+  // Closes the notification identified by |identifier|.
+  CloseNotification(NotificationIdentifier identifier);
+
   // Closes all notifications shown by the helper app in the system notification
   // center for all profiles.
   CloseAllNotifications();
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 96a033e..10cd8e8 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1769,7 +1769,6 @@
       ]
 
       sources += [
-        "../browser/apps/app_service/media_access_browsertest.cc",
         "../browser/apps/app_service/notifications_browsertest.cc",
         "../browser/apps/app_service/web_apps_base_browsertest.cc",
         "../browser/apps/icon_standardizer_unittest.cc",
@@ -4484,6 +4483,7 @@
       "../browser/enterprise/connectors/connectors_manager_unittest.cc",
       "../browser/enterprise/connectors/connectors_service_unittest.cc",
       "../browser/enterprise/connectors/enterprise_connectors_policy_handler_unittest.cc",
+      "../browser/enterprise/connectors/file_system/rename_handler_unittest.cc",
       "../browser/enterprise/connectors/file_system/service_settings_unittest.cc",
       "../browser/enterprise/connectors/reporting/reporting_service_settings_unittest.cc",
       "../browser/enterprise/connectors/service_provider_config_unittest.cc",
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 2989744..eea99d1 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -389,6 +389,7 @@
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_reset_page_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_people_page_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_privacy_page_test.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_settings_menu_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/lock_screen_tests.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_printing_page_tests.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_search_page_test.m.js",
diff --git a/chrome/test/data/webui/js/cr/BUILD.gn b/chrome/test/data/webui/js/cr/BUILD.gn
index 61c9678..82f3ec9e 100644
--- a/chrome/test/data/webui/js/cr/BUILD.gn
+++ b/chrome/test/data/webui/js/cr/BUILD.gn
@@ -34,7 +34,5 @@
 }
 
 js_type_check("closure_compile_local") {
-  uses_js_modules = true
-
   deps = [ ":event_target_test.m" ]
 }
diff --git a/chrome/test/data/webui/js/cr/ui/BUILD.gn b/chrome/test/data/webui/js/cr/ui/BUILD.gn
index ebe1f3b..c0b4a17 100644
--- a/chrome/test/data/webui/js/cr/ui/BUILD.gn
+++ b/chrome/test/data/webui/js/cr/ui/BUILD.gn
@@ -157,8 +157,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
-
   deps = [
     ":array_data_model_test.m",
     ":command_test.m",
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index a87e43fa..504dacdb 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -112,6 +112,7 @@
     "os_privacy_page_test.js",
     "os_search_page_test.js",
     "os_settings_search_box_test.js",
+    "os_settings_menu_test.js",
     "parental_controls_page_test.js",
     "people_page_account_manager_test.js",
     "people_page_change_picture_test.js",
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_menu_test.js b/chrome/test/data/webui/settings/chromeos/os_settings_menu_test.js
index 0648a2b..02e9519 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_menu_test.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_menu_test.js
@@ -2,18 +2,30 @@
 // 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 {Router, routes, Route, pageVisibility} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// clang-format on
+
 /** @fileoverview Runs tests for the OS settings menu. */
 
 function setupRouter() {
-  const routes = {
+  const testRoutes = {
     BASIC: new settings.Route('/'),
     ADVANCED: new settings.Route('/advanced'),
   };
-  routes.BLUETOOTH = routes.BASIC.createSection('/bluetooth', 'bluetooth');
-  routes.RESET = routes.ADVANCED.createSection('/osReset', 'osReset');
+  testRoutes.BLUETOOTH =
+      testRoutes.BASIC.createSection('/bluetooth', 'bluetooth');
+  testRoutes.RESET = testRoutes.ADVANCED.createSection('/osReset', 'osReset');
 
-  settings.Router.resetInstanceForTesting(new settings.Router(routes));
-  settings.routes = routes;
+  settings.Router.resetInstanceForTesting(new settings.Router(testRoutes));
+
+  settings.routes.RESET = testRoutes.RESET;
+  settings.routes.BLUETOOTH = testRoutes.BLUETOOTH;
+  settings.routes.ADVANCED = testRoutes.ADVANCED;
+  settings.routes.BASIC = testRoutes.BASIC;
 }
 
 suite('OSSettingsMenu', function() {
@@ -82,17 +94,21 @@
     // the Advanced route, then ensure that the advanced menu expands.
     const params = new URLSearchParams('search=test');
     settings.Router.getInstance().navigateTo(settings.routes.RESET, params);
+    Polymer.dom.flush();
     assertTrue(settingsMenu.advancedOpened);
   });
 });
 
 suite('OSSettingsMenuReset', function() {
+  let settingsMenu = null;
+
   setup(function() {
     setupRouter();
     PolymerTest.clearBody();
     settings.Router.getInstance().navigateTo(settings.routes.RESET, '');
     settingsMenu = document.createElement('os-settings-menu');
     document.body.appendChild(settingsMenu);
+    Polymer.dom.flush();
   });
 
   teardown(function() {
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 42e2fd3b..d0df3ab 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
@@ -320,6 +320,7 @@
  ['OsLanguagesPageV2', 'os_languages_page_v2_tests.m.js'],
  ['OsSearchPage', 'os_search_page_test.m.js'],
  ['OsSettingsSearchBox', 'os_settings_search_box_test.m.js'],
+ ['OSSettingsMenu', 'os_settings_menu_test.m.js'],
  ['NearbyShareReceiveDialog', 'nearby_share_receive_dialog_tests.m.js'],
  ['ParentalControlsPage', 'parental_controls_page_test.m.js'],
  ['PeoplePage', 'os_people_page_test.m.js'],
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastApplication.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastApplication.java
index 1a819fa..6ccbff6fc 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastApplication.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastApplication.java
@@ -29,8 +29,7 @@
     protected void attachBaseContext(Context base) {
         super.attachBaseContext(base);
         ContextUtils.initApplicationContext(this);
-        ResourceBundle.setAvailablePakLocales(
-                ProductConfig.COMPRESSED_LOCALES, ProductConfig.UNCOMPRESSED_LOCALES);
+        ResourceBundle.setAvailablePakLocales(ProductConfig.LOCALES);
         LibraryLoader.getInstance().setLinkerImplementation(
                 ProductConfig.USE_CHROMIUM_LINKER, ProductConfig.USE_MODERN_LINKER);
         LibraryLoader.getInstance().setLibraryProcessType(isBrowserProcess()
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 46c5e500..e93bfcc 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -357,9 +357,6 @@
       <message name="IDS_PRINT_MANAGEMENT_TITLE" desc="The title of the print management app.">
         Print jobs
       </message>
-      <message name="IDS_PRINT_MANAGEMENT_APP_NAME" desc="The app name of the print management app.">
-        Print Jobs
-      </message>
       <message name="IDS_PRINT_MANAGEMENT_CLEAR_ALL_HISTORY_BUTTON_TEXT" desc="The label for the button that clears the print job history.">
         Clear all history
       </message>
@@ -818,6 +815,9 @@
       <message name="IDS_DIAGNOSTICS_SESSION_LOG_TOAST_TEXT_FAILURE" desc="The text shown when the session log download fails.">
         Download failed
       </message>
+      <message name="IDS_DIAGNOSTICS_SELECT_DIALOG_TITLE" desc="The text used as the title of the select dialog used for choosing the folder to save the session log to.">
+        Select a folder to save to
+      </message>
 
       <!-- Quick Answers -->
       <message name="IDS_QUICK_ANSWERS_DEFINITION_TITLE_TEXT" desc="The title text format string used for Quick Answers definition result card. The first placeholder contains the source query text and the second placeholder contains the phonetics.">
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_SELECT_DIALOG_TITLE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_SELECT_DIALOG_TITLE.png.sha1
new file mode 100644
index 0000000..ba4992dc
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_SELECT_DIALOG_TITLE.png.sha1
@@ -0,0 +1 @@
+499dccba6f568535a06cab27239999fa1b3c07be
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PRINT_MANAGEMENT_APP_NAME.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PRINT_MANAGEMENT_APP_NAME.png.sha1
deleted file mode 100644
index edc51a1..0000000
--- a/chromeos/chromeos_strings_grd/IDS_PRINT_MANAGEMENT_APP_NAME.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-f62f08e78643f5865a9093e03383088d9715fa91
\ No newline at end of file
diff --git a/chromeos/components/diagnostics_ui/backend/BUILD.gn b/chromeos/components/diagnostics_ui/backend/BUILD.gn
index e39c782..e8c6cb1d 100644
--- a/chromeos/components/diagnostics_ui/backend/BUILD.gn
+++ b/chromeos/components/diagnostics_ui/backend/BUILD.gn
@@ -38,6 +38,7 @@
     "//chromeos/dbus/power:power_manager_proto",
     "//chromeos/services/cros_healthd/public/cpp",
     "//chromeos/services/cros_healthd/public/mojom",
+    "//chromeos/strings/",
     "//content/public/browser",
     "//services/data_decoder/public/cpp",
     "//ui/base",
diff --git a/chromeos/components/diagnostics_ui/backend/session_log_handler.cc b/chromeos/components/diagnostics_ui/backend/session_log_handler.cc
index 7f4426e..5971fae3 100644
--- a/chromeos/components/diagnostics_ui/backend/session_log_handler.cc
+++ b/chromeos/components/diagnostics_ui/backend/session_log_handler.cc
@@ -7,12 +7,13 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/strings/strcat.h"
-#include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "chromeos/components/diagnostics_ui/backend/routine_log.h"
 #include "chromeos/components/diagnostics_ui/backend/telemetry_log.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
 #include "content/public/browser/web_contents.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/shell_dialogs/select_file_policy.h"
 
@@ -107,10 +108,9 @@
 
   select_file_dialog_ = ui::SelectFileDialog::Create(
       this, select_file_policy_creator_.Run(web_contents));
-  // TODO(michaelcheco): Add string for dialog title.
   select_file_dialog_->SelectFile(
       ui::SelectFileDialog::SELECT_SAVEAS_FILE,
-      /*title=*/base::ASCIIToUTF16(""),
+      /*title=*/l10n_util::GetStringUTF16(IDS_DIAGNOSTICS_SELECT_DIALOG_TITLE),
       /*default_path=*/base::FilePath(kDefaultSessionLogFileName),
       /*file_types=*/nullptr,
       /*file_type_index=*/0,
diff --git a/chromeos/components/diagnostics_ui/resources/data_point.html b/chromeos/components/diagnostics_ui/resources/data_point.html
index ba0df5b8..94d514a9 100644
--- a/chromeos/components/diagnostics_ui/resources/data_point.html
+++ b/chromeos/components/diagnostics_ui/resources/data_point.html
@@ -14,7 +14,7 @@
 
   #infoIcon {
     bottom: 1px;
-    fill:  var(--google-grey-700);
+    fill: var(--google-grey-700);
     height: 20px;
     left: 4px;
     width: 20px;
@@ -31,11 +31,15 @@
 </style>
 <div class="data-point">
   <div class="header">
-    <span>[[header]]</span>
+    <span id="headerText" aria-hidden="true"> [[header]]</span>
     <iron-icon slot="icon" icon="cr:info-outline" id="infoIcon"
-      hidden$="[[!tooltipText]]">
+        hidden$="[[!tooltipText]]">
     </iron-icon>
-    <paper-tooltip for="infoIcon">[[tooltipText]]</paper-tooltip>
+    <paper-tooltip for="infoIcon" aria-hidden="true">
+      <span id="tooltipText">[[tooltipText]]</span>
+    </paper-tooltip>
   </div>
-  <div class$="[[getValueClass_(warningState)]]">[[value]]</div>
-</div>
\ No newline at end of file
+  <div class$="[[getValueClass_(warningState)]]" aria-labelledby="headerText"
+      aria-describedby="tooltipText"> [[value]]
+  </div>
+</div>
diff --git a/chromeos/components/diagnostics_ui/resources/overview_card.html b/chromeos/components/diagnostics_ui/resources/overview_card.html
index 08ae8b0..caa2b19 100644
--- a/chromeos/components/diagnostics_ui/resources/overview_card.html
+++ b/chromeos/components/diagnostics_ui/resources/overview_card.html
@@ -12,7 +12,9 @@
 </style>
 <div id="overviewCardContainer">
   <div class="diagnostics-chip">
-    <span id="marketingName">[[systemInfo_.marketingName]]</span>
-    <span id="deviceInfo">[[deviceInfo_]]</span>
+    <span id="marketingName" aria-describedby="deviceInfo">
+      [[systemInfo_.marketingName]]
+    </span>
+    <span id="deviceInfo" aria-hidden="true">[[deviceInfo_]]</span>
   </div>
 </div>
diff --git a/chromeos/components/diagnostics_ui/resources/realtime_cpu_chart.html b/chromeos/components/diagnostics_ui/resources/realtime_cpu_chart.html
index 43a3f69..5887312 100644
--- a/chromeos/components/diagnostics_ui/resources/realtime_cpu_chart.html
+++ b/chromeos/components/diagnostics_ui/resources/realtime_cpu_chart.html
@@ -93,7 +93,7 @@
   }
 </style>
 
-<svg id="chart" width$="[[width_]]" height$="[[height_]]">
+<svg id="chart" width$="[[width_]]" height$="[[height_]]" aria-hidden="true">
   <g id="chartGroup">
     <defs>
       <!-- Define chart area and boundaries -->
@@ -121,13 +121,17 @@
 <div id="chart-legend">
   <div id="legend-user" class="legend-group">
     <div id="legend-bar" class="user"></div>
-    <label>[[i18n('cpuUsageUser')]]</label>
-    <span>[[getPercentageLabel_(user)]]</span>
+    <label id="userLabel">[[i18n('cpuUsageUser')]]</label>
+    <span id="userData" aria-labelledby="userLabel">
+      [[getPercentageLabel_(user)]]
+    </span>
   </div>
   <div class="divider"></div>
   <div id="legend-system" class="legend-group">
     <div id="legend-bar" class="system"></div>
-    <label>[[i18n('cpuUsageSystem')]]</label>
-    <span>[[getPercentageLabel_(system)]]</span>
+    <label id="systemLabel">[[i18n('cpuUsageSystem')]]</label>
+    <span id="systemData" aria-labelledby="systemLabel">
+      [[getPercentageLabel_(system)]]
+    </span>
   </div>
 </div>
diff --git a/chromeos/components/file_manager/resources/BUILD.gn b/chromeos/components/file_manager/resources/BUILD.gn
index e9b2402..6461891 100644
--- a/chromeos/components/file_manager/resources/BUILD.gn
+++ b/chromeos/components/file_manager/resources/BUILD.gn
@@ -16,7 +16,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":main_js" ]
 }
 
diff --git a/chromeos/components/phonehub/phone_hub_manager_impl.cc b/chromeos/components/phonehub/phone_hub_manager_impl.cc
index 96722782..a080258 100644
--- a/chromeos/components/phonehub/phone_hub_manager_impl.cc
+++ b/chromeos/components/phonehub/phone_hub_manager_impl.cc
@@ -41,8 +41,7 @@
     chromeos::secure_channel::SecureChannelClient* secure_channel_client,
     std::unique_ptr<BrowserTabsModelProvider> browser_tabs_model_provider,
     const base::RepeatingClosure& show_multidevice_setup_dialog_callback)
-    : user_action_recorder_(std::make_unique<UserActionRecorderImpl>()),
-      connection_manager_(
+    : connection_manager_(
           std::make_unique<ConnectionManagerImpl>(multidevice_setup_client,
                                                   device_sync_client,
                                                   secure_channel_client)),
@@ -52,6 +51,8 @@
           connection_manager_.get(),
           session_manager::SessionManager::Get(),
           chromeos::PowerManagerClient::Get())),
+      user_action_recorder_(std::make_unique<UserActionRecorderImpl>(
+          feature_status_provider_.get())),
       message_receiver_(
           std::make_unique<MessageReceiverImpl>(connection_manager_.get())),
       message_sender_(
@@ -193,9 +194,9 @@
   phone_model_.reset();
   message_sender_.reset();
   message_receiver_.reset();
+  user_action_recorder_.reset();
   feature_status_provider_.reset();
   connection_manager_.reset();
-  user_action_recorder_.reset();
 }
 
 }  // namespace phonehub
diff --git a/chromeos/components/phonehub/phone_hub_manager_impl.h b/chromeos/components/phonehub/phone_hub_manager_impl.h
index 95d45bd..7bac29e0 100644
--- a/chromeos/components/phonehub/phone_hub_manager_impl.h
+++ b/chromeos/components/phonehub/phone_hub_manager_impl.h
@@ -73,9 +73,9 @@
   // KeyedService:
   void Shutdown() override;
 
-  std::unique_ptr<UserActionRecorder> user_action_recorder_;
   std::unique_ptr<ConnectionManager> connection_manager_;
   std::unique_ptr<FeatureStatusProvider> feature_status_provider_;
+  std::unique_ptr<UserActionRecorder> user_action_recorder_;
   std::unique_ptr<MessageReceiver> message_receiver_;
   std::unique_ptr<MessageSender> message_sender_;
   std::unique_ptr<MutablePhoneModel> phone_model_;
diff --git a/chromeos/components/phonehub/user_action_recorder_impl.cc b/chromeos/components/phonehub/user_action_recorder_impl.cc
index d1d3ea1..03cd0852 100644
--- a/chromeos/components/phonehub/user_action_recorder_impl.cc
+++ b/chromeos/components/phonehub/user_action_recorder_impl.cc
@@ -5,16 +5,23 @@
 #include "chromeos/components/phonehub/user_action_recorder_impl.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "chromeos/components/phonehub/feature_status.h"
+#include "chromeos/components/phonehub/feature_status_provider.h"
 
 namespace chromeos {
 namespace phonehub {
 
-UserActionRecorderImpl::UserActionRecorderImpl() = default;
+UserActionRecorderImpl::UserActionRecorderImpl(
+    FeatureStatusProvider* feature_status_provider)
+    : feature_status_provider_(feature_status_provider) {}
 
 UserActionRecorderImpl::~UserActionRecorderImpl() = default;
 
 void UserActionRecorderImpl::RecordUiOpened() {
-  HandleUserAction(UserAction::kUiOpened);
+  if (feature_status_provider_->GetStatus() ==
+      FeatureStatus::kEnabledAndConnected) {
+    HandleUserAction(UserAction::kUiOpened);
+  }
 }
 
 void UserActionRecorderImpl::RecordTetherConnectionAttempt() {
diff --git a/chromeos/components/phonehub/user_action_recorder_impl.h b/chromeos/components/phonehub/user_action_recorder_impl.h
index 8846e64d..10245f5 100644
--- a/chromeos/components/phonehub/user_action_recorder_impl.h
+++ b/chromeos/components/phonehub/user_action_recorder_impl.h
@@ -14,16 +14,21 @@
 namespace chromeos {
 namespace phonehub {
 
+class FeatureStatusProvider;
+
 // UserActionRecorder implementation which generates engagement metrics for
 // Phone Hub.
 class UserActionRecorderImpl : public UserActionRecorder {
  public:
-  UserActionRecorderImpl();
+  explicit UserActionRecorderImpl(
+      FeatureStatusProvider* feature_status_provider);
   ~UserActionRecorderImpl() override;
 
  private:
   friend class UserActionRecorderImplTest;
-  FRIEND_TEST_ALL_PREFIXES(UserActionRecorderImplTest, Enabled_RecordActions);
+  FRIEND_TEST_ALL_PREFIXES(UserActionRecorderImplTest, RecordActions);
+  FRIEND_TEST_ALL_PREFIXES(UserActionRecorderImplTest,
+                           UiOpenedOnlyRecordedWhenConnected);
 
   // Types of user actions; numerical value should not be reused or reordered
   // since this enum is used in metrics.
@@ -48,6 +53,8 @@
   void RecordNotificationReplyAttempt() override;
 
   void HandleUserAction(UserAction action);
+
+  FeatureStatusProvider* feature_status_provider_;
 };
 
 }  // namespace phonehub
diff --git a/chromeos/components/phonehub/user_action_recorder_impl_unittest.cc b/chromeos/components/phonehub/user_action_recorder_impl_unittest.cc
index f875490..03ad89e 100644
--- a/chromeos/components/phonehub/user_action_recorder_impl_unittest.cc
+++ b/chromeos/components/phonehub/user_action_recorder_impl_unittest.cc
@@ -7,6 +7,8 @@
 #include <memory>
 
 #include "base/test/metrics/histogram_tester.h"
+#include "chromeos/components/phonehub/fake_feature_status_provider.h"
+#include "chromeos/components/phonehub/feature_status.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
@@ -17,17 +19,20 @@
 
 class UserActionRecorderImplTest : public testing::Test {
  protected:
-  UserActionRecorderImplTest() = default;
+  UserActionRecorderImplTest()
+      : fake_feature_status_provider_(FeatureStatus::kEnabledAndConnected),
+        recorder_(&fake_feature_status_provider_) {}
   UserActionRecorderImplTest(const UserActionRecorderImplTest&) = delete;
   UserActionRecorderImplTest& operator=(const UserActionRecorderImplTest&) =
       delete;
   ~UserActionRecorderImplTest() override = default;
 
+  FakeFeatureStatusProvider fake_feature_status_provider_;
   UserActionRecorderImpl recorder_;
   base::HistogramTester histogram_tester_;
 };
 
-TEST_F(UserActionRecorderImplTest, Enabled_RecordActions) {
+TEST_F(UserActionRecorderImplTest, RecordActions) {
   recorder_.RecordUiOpened();
   recorder_.RecordTetherConnectionAttempt();
   recorder_.RecordDndAttempt();
@@ -64,5 +69,29 @@
       /*expected_count=*/1);
 }
 
+TEST_F(UserActionRecorderImplTest, UiOpenedOnlyRecordedWhenConnected) {
+  // Should record a metric when enabled and connected.
+  recorder_.RecordUiOpened();
+  histogram_tester_.ExpectBucketCount(
+      kCompletedActionMetricName, UserActionRecorderImpl::UserAction::kUiOpened,
+      /*expected_count=*/1);
+
+  // Change to another status; opening the UI should not record a metric.
+  fake_feature_status_provider_.SetStatus(
+      FeatureStatus::kUnavailableBluetoothOff);
+  recorder_.RecordUiOpened();
+  histogram_tester_.ExpectBucketCount(
+      kCompletedActionMetricName, UserActionRecorderImpl::UserAction::kUiOpened,
+      /*expected_count=*/1);
+
+  // Change back to enabled and connected; opening the UI should record a
+  // metric.
+  fake_feature_status_provider_.SetStatus(FeatureStatus::kEnabledAndConnected);
+  recorder_.RecordUiOpened();
+  histogram_tester_.ExpectBucketCount(
+      kCompletedActionMetricName, UserActionRecorderImpl::UserAction::kUiOpened,
+      /*expected_count=*/2);
+}
+
 }  // namespace phonehub
 }  // namespace chromeos
diff --git a/chromeos/components/scanning/resources/ready_to_scan.svg b/chromeos/components/scanning/resources/ready_to_scan.svg
new file mode 100644
index 0000000..b922399b
--- /dev/null
+++ b/chromeos/components/scanning/resources/ready_to_scan.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><g id="Layer_2" data-name="Layer 2"><g id="Ready_to_scan" data-name="Ready to scan"><rect width="200" height="200" fill="#fff"/><line x1="3.4" y1="194.7" x2="198" y2="194.7" fill="#d2e3fc" stroke="#d2e3fc" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/><path d="M68.5,125.4a11.2,11.2,0,0,1,6.4-4.7c6.9-1.7,13.3,5.8,24.5,14.2,1.2.9,11.5,8.6,21.3,13,17.9,8.3,45.2,10.8,56.4-2.6a22.2,22.2,0,0,0,4.2-8" fill="none" stroke="#4285f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M87.4,135.7a5.9,5.9,0,0,0-5.4-1.3,2.6,2.6,0,0,0-1.6.9,4.1,4.1,0,0,0-.6,4.3" fill="none" stroke="#d2e3fc" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M100.1,106.2a4.4,4.4,0,0,0,.7,3.3,3.2,3.2,0,0,0,2.7,1.6,4.1,4.1,0,0,0,3.2-1.7" fill="none" stroke="#d2e3fc" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M2.4,124.7c9,2.6,18.4,5.2,28.2,7.6,7.2,1.8,14.1,3.4,20.9,4.8,0,0,2.6.6,17.5,2.5,11.3,1.4,16.4,1.8,18.2-1a4.9,4.9,0,0,0,.2-4.7c-.7-1.6-2.4-2.9-11.4-4.8s-11.2-1.9-11.9-3.8.9-3.6,2.1-4.9" fill="none" stroke="#4285f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M65.4,92.5c-2.5-2.5-6.5-1.7-9.4-.8S48.8,94.5,45.3,96l-8.7,4c-4.5,2.1-12.1,2.8-25.7-3" fill="none" stroke="#4285f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M66.2,120.4c1.3-.9,9.9-6.7,15.6-9.2a11.4,11.4,0,0,1,4.1-1.1,16.3,16.3,0,0,1,6,1.2c3.5,1.2,5.5,2.7,9.2,3.6s5,.6,6.5-1.4-.6-4.5-3-5.5l-1.7-.8-4.1-2.4c-2.8-1.9-4.3-3.3-7.6-4.3a24.1,24.1,0,0,0-4.3-1,28.4,28.4,0,0,0-9.2.8,41,41,0,0,0-9.7,3.6" fill="none" stroke="#4285f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M62.3,99.9a84.7,84.7,0,0,1,12.1-4.7,20.8,20.8,0,0,1,9.3-.1c3.1.6,7.1,3,8.8,5.7" fill="none" stroke="#4285f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M74.4,95.2a9.8,9.8,0,0,0-7.2-2.5,28.4,28.4,0,0,0-8.5,2.4l-2.8,1.3" fill="none" stroke="#4285f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M85.1,110.2c1.8.4,3.2,1.7,4.6,2.8a12.4,12.4,0,0,0,4.6,2.6,5.5,5.5,0,0,0,5-.8" fill="none" stroke="#4285f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M28,102.5c-2.1,8.9-4.3,17.8-6.4,26.7l-8.4-1.6,6.6-26.8Z" fill="#4285f4" stroke="#4285f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.36"/><circle cx="23.4" cy="110.8" r="7.3" fill="#fff" stroke="#4285f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.36"/><polyline points="25.5 109 22.7 110.9 26.4 111.8" fill="none" stroke="#4285f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.36"/><rect x="35" y="155.3" width="158.2" height="36.98" rx="5.2" transform="matrix(1, 0, 0, 1, -0.36, 0.24)" fill="none" stroke="#4285f4" stroke-miterlimit="10" stroke-width="2"/><rect x="92.1" y="74.6" width="157.7" height="6.37" rx="1.4" transform="translate(197.4 -108.2) rotate(73.6)" fill="#d2e3fc"/><line x1="35" y1="180.9" x2="193.2" y2="180.6" fill="#4285f4" stroke="#4285f4" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/><circle cx="178.7" cy="168.8" r="3.1" fill="#34a853"/><path d="M66.1,49.2h0a4,4,0,0,0,4.8,3.2l9.5-1.9a4.2,4.2,0,0,0,3.2-4.9h0a4.1,4.1,0,0,0-4.9-3.2l-9.4,2A4,4,0,0,0,66.1,49.2Z" fill="#ea4335"/><rect x="156" y="123.2" width="14.9" height="14.8" rx="7.4" transform="translate(-42.1 91.5) rotate(-27.8)" fill="#d2e3fc"/><path d="M44.1,50.1,46,45.5a.6.6,0,0,1,.6-.5l4.9-.7a1.1,1.1,0,0,1,.8.3l3,3.9a.8.8,0,0,1,.1.8l-1.8,4.6a1,1,0,0,1-.6.5l-4.9.7a1.1,1.1,0,0,1-.8-.3l-3-3.9A.8.8,0,0,1,44.1,50.1Z" fill="#fbbc05"/><rect x="101.3" y="39.2" width="25.1" height="24.9" rx="12.5" transform="translate(-11 59.1) rotate(-27.8)" fill="none" stroke="#f882ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M161.4,98.8a10.9,10.9,0,0,0-2.7-4h0a6.8,6.8,0,0,0-2.1-1.7l-2.1-1.7h0a7.9,7.9,0,0,1-3-5.1,17,17,0,0,1-.4-2.2,13.7,13.7,0,0,0-1.3-4.9l-.7.8-.8.7a14.3,14.3,0,0,1,.9,3.7l.3,2.3a11.2,11.2,0,0,0,3.7,6.3l2.2,1.7h.1a8.4,8.4,0,0,1,1.8,1.5,8.7,8.7,0,0,1,2.2,3.4,9.5,9.5,0,0,1-.2,7.9,9.8,9.8,0,0,1-6,5.3,10,10,0,0,1-12-5,8.9,8.9,0,0,1-1.1-4.8,8.5,8.5,0,0,1,.3-2.3h0l.4-2.7a10.3,10.3,0,0,0-1.9-7.6l-.7-.9-1.6-2.3-.2-.4a15.2,15.2,0,0,1-2.1.3,3.9,3.9,0,0,0,.6,1.1,22,22,0,0,0,1.7,2.5l.7.9a7.9,7.9,0,0,1,1.5,6.1l-.3,2.6a8.4,8.4,0,0,0-.4,2.7,11.9,11.9,0,0,0,12,12.4,11.5,11.5,0,0,0,3.7-.6,11.8,11.8,0,0,0,7.2-6.3A12.3,12.3,0,0,0,161.4,98.8Z" fill="#4285f4"/><path d="M134.2,80.7a8.4,8.4,0,0,1,4.9-5.2l.5-.2a8.8,8.8,0,0,1,8.5,1.7,6.9,6.9,0,0,1,1.7,2.2,21.9,21.9,0,0,0-.5-28.9,1.6,1.6,0,0,0-2.5,0L119.2,79.7a1.9,1.9,0,0,0,.2,2.7,22.5,22.5,0,0,0,15,4.6A9.1,9.1,0,0,1,134.2,80.7Z" fill="#34a853"/><path d="M142.4,76.9a8.3,8.3,0,0,0-2.2.3h-.3a6.7,6.7,0,0,0-3.8,4.1,6.5,6.5,0,0,0,.4,5.3,21.6,21.6,0,0,0,11.8-6,5.1,5.1,0,0,0-1.5-2.2A6.5,6.5,0,0,0,142.4,76.9Z" fill="#34a853"/></g></g></svg>
\ No newline at end of file
diff --git a/chromeos/components/scanning/resources/scan_preview.html b/chromeos/components/scanning/resources/scan_preview.html
index 7889a03..9a7b426 100644
--- a/chromeos/components/scanning/resources/scan_preview.html
+++ b/chromeos/components/scanning/resources/scan_preview.html
@@ -1,13 +1,24 @@
 <style include="scanning-shared scanning-fonts">
+  #helpDiv {
+    display: flex;
+    flex-direction: column;
+    margin: auto;
+  }
+
+  #readyToScanImg {
+    margin: var(--ready-to-scan-image-margin-top) auto 32px;
+    width: 50%;
+  }
+
   #helperText {
     color: var(--scanning-helper-text-color);
     font-family: var(--scanning-helper-text-font-family);
     font-size: var(--scanning-helper-text-font-size);
     font-weight: var(--scanning-regular-font-weight);
     line-height: var(--scanning-helper-text-line-height);
-    margin: auto;
+    margin: 0 auto var(--helper-text-margin-bottom);
     text-align: center;
-    width: 60%;
+    width: 65%;
   }
 
   #helpOrProgress {
@@ -89,8 +100,13 @@
     tabindex="0">
   <div id="helpOrProgress" class="preview-item" hidden$="[[showScannedImages_]]"
       aria-hidden="true">
-    <div id="helperText" hidden$="[[!showHelperText_]]">
-      [[i18n('scanPreviewHelperText')]]
+    <div id="helpDiv" hidden$="[[!showHelperText_]]">
+      <img id="readyToScanImg" src="ready_to_scan.svg"
+          alt="[[i18n('scanPreviewHelperText')]]">
+      </img>
+      <span id="helperText">
+        [[i18n('scanPreviewHelperText')]]
+      </span>
     </div>
     <div id="scanProgress" hidden$="[[!showScanProgress_]]">
       <span id="progressText">[[progressTextString_]]</span>
diff --git a/chromeos/components/scanning/resources/scanning_app_resources.grd b/chromeos/components/scanning/resources/scanning_app_resources.grd
index ccae2e2b..2fdfa48 100644
--- a/chromeos/components/scanning/resources/scanning_app_resources.grd
+++ b/chromeos/components/scanning/resources/scanning_app_resources.grd
@@ -49,6 +49,7 @@
       <include name="IDR_SCANNING_APP_ICON_256" file="scanning_app_icon_256.png" type="BINDATA" />
       <include name="IDR_SCANNING_APP_SCANNERS_LOADING_SVG" file="scanners_loading.svg" type="BINDATA" />
       <include name="IDR_SCANNING_APP_NO_SCANNERS_SVG" file="no_scanners.svg" type="BINDATA" />
+      <include name="IDR_SCANNING_APP_READY_TO_SCAN_SVG" file="ready_to_scan.svg" type="BINDATA" />
     </includes>
 
     <structures>
diff --git a/chromeos/components/scanning/resources/scanning_shared_css.html b/chromeos/components/scanning/resources/scanning_shared_css.html
index 0056d306..a72b183 100644
--- a/chromeos/components/scanning/resources/scanning_shared_css.html
+++ b/chromeos/components/scanning/resources/scanning_shared_css.html
@@ -34,10 +34,12 @@
     @media (min-width: 600px) {
       :host {
         --container-width: 600px;
+        --helper-text-margin-bottom: 0;
         --left-panel-margin-inline-end: 10px;
         --left-panel-margin-inline-start: 10px;
         --left-panel-width: 200px;
         --panel-container-margin-top: 20px;
+        --ready-to-scan-image-margin-top: 0;
         --right-panel-margin-inline-end: 10px;
         --right-panel-margin-inline-start: 0;
         --right-panel-padding-inline-end: 8px;
@@ -48,10 +50,12 @@
     @media (min-width: 768px) {
       :host {
         --container-width: 768px;
+        --helper-text-margin-bottom: 56px;
         --left-panel-margin-inline-end: 32px;
         --left-panel-margin-inline-start: 32px;
         --left-panel-width: 288px;
         --panel-container-margin-top: 20px;
+        --ready-to-scan-image-margin-top: 32px;
         --right-panel-margin-inline-end: 32px;
         --right-panel-margin-inline-start: 0;
         --right-panel-padding-inline-end: 16px;
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 8a70a705..75b1ce9 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -480,8 +480,10 @@
   if (id != autofill_query_id_)
     return;
 
+  ClearPreviewedForm();
+
   query_node_autofill_state_ = element_.GetAutofillState();
-  form_util::PreviewForm(form, element_);
+  previewed_elements_ = form_util::PreviewForm(form, element_);
 
   GetAutofillDriver()->DidPreviewAutofillFormData();
 }
@@ -512,8 +514,9 @@
   if (password_autofill_agent_->DidClearAutofillSelection(element_))
     return;
 
-  form_util::ClearPreviewedFormWithElement(element_,
-                                           query_node_autofill_state_);
+  form_util::ClearPreviewedElements(previewed_elements_, element_,
+                                    query_node_autofill_state_);
+  previewed_elements_ = {};
 }
 
 void AutofillAgent::FillFieldWithValue(const base::string16& value) {
diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h
index 18e88d37..dae44e9b 100644
--- a/components/autofill/content/renderer/autofill_agent.h
+++ b/components/autofill/content/renderer/autofill_agent.h
@@ -302,6 +302,9 @@
   // The element corresponding to the last request sent for form field Autofill.
   blink::WebFormControlElement element_;
 
+  // The elements that currently are being previewed.
+  std::vector<blink::WebFormControlElement> previewed_elements_;
+
   // The form element currently requesting an interactive autocomplete.
   blink::WebFormElement in_flight_request_form_;
 
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index b38f67e..d6da084c 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -990,7 +990,7 @@
                          bool, /* is_initiating_element */
                          blink::WebFormControlElement*);
 
-void ForEachMatchingFormFieldCommon(
+std::vector<WebFormControlElement> ForEachMatchingFormFieldCommon(
     std::vector<WebFormControlElement>* control_elements,
     const WebElement& initiating_element,
     const FormData& data,
@@ -1000,6 +1000,9 @@
     const Callback& callback) {
   DCHECK(control_elements);
 
+  std::vector<WebFormControlElement> matching_fields;
+  matching_fields.reserve(control_elements->size());
+
   const bool num_elements_matches_num_fields =
       control_elements->size() == data.fields.size();
   UMA_HISTOGRAM_BOOLEAN("Autofill.NumElementsMatchesNumFields",
@@ -1014,7 +1017,7 @@
     // restrictions are applied.
     //
     // TODO(crbug/847221): Add a UKM to capture these events.
-    return;
+    return matching_fields;
   }
 
   // The intended behaviour is:
@@ -1063,6 +1066,7 @@
       if (!is_preview && element->Focused())
         initially_focused_element = element;
 
+      matching_fields.push_back(*element);
       callback(data.fields[i], is_initiating_element, element);
       continue;
     }
@@ -1106,7 +1110,7 @@
   // If there is no other field to be autofilled, sending the blur event and
   // then the focus event for the initiating element does not make sense.
   if (autofillable_elements_index.empty())
-    return;
+    return matching_fields;
 
   // A blur event is emitted for the focused element if it is the initiating
   // element before all other elements are autofilled.
@@ -1114,50 +1118,58 @@
     initially_focused_element->DispatchBlurEvent();
 
   // Autofill the non-initiating elements.
-  for (const auto& index : autofillable_elements_index)
+  for (const auto& index : autofillable_elements_index) {
+    matching_fields.push_back((*control_elements)[index]);
     callback(data.fields[index], false, &(*control_elements)[index]);
+  }
 
   // A focus event is emitted for the initiating element after autofilling is
   // completed. It is not intended to work for the preview filling.
   if (initially_focused_element)
     initially_focused_element->DispatchFocusEvent();
+
+  return matching_fields;
 }
 
 // For each autofillable field in |data| that matches a field in the |form|,
 // the |callback| is invoked with the corresponding |form| field data.
-void ForEachMatchingFormField(const WebFormElement& form_element,
-                              const WebElement& initiating_element,
-                              const FormData& data,
-                              FieldFilterMask filters,
-                              bool force_override,
-                              bool is_preview,
-                              const Callback& callback) {
+std::vector<WebFormControlElement> ForEachMatchingFormField(
+    const WebFormElement& form_element,
+    const WebElement& initiating_element,
+    const FormData& data,
+    FieldFilterMask filters,
+    bool force_override,
+    bool is_preview,
+    const Callback& callback) {
   std::vector<WebFormControlElement> control_elements =
       ExtractAutofillableElementsInForm(form_element);
-  ForEachMatchingFormFieldCommon(&control_elements, initiating_element, data,
-                                 filters, force_override, is_preview, callback);
+  return ForEachMatchingFormFieldCommon(&control_elements, initiating_element,
+                                        data, filters, force_override,
+                                        is_preview, callback);
 }
 
 // For each autofillable field in |data| that matches a field in the set of
 // unowned autofillable form fields, the |callback| is invoked with the
 // corresponding |data| field.
-void ForEachMatchingUnownedFormField(const WebElement& initiating_element,
-                                     const FormData& data,
-                                     FieldFilterMask filters,
-                                     bool force_override,
-                                     bool is_preview,
-                                     const Callback& callback) {
+std::vector<WebFormControlElement> ForEachMatchingUnownedFormField(
+    const WebElement& initiating_element,
+    const FormData& data,
+    FieldFilterMask filters,
+    bool force_override,
+    bool is_preview,
+    const Callback& callback) {
   if (initiating_element.IsNull())
-    return;
+    return {};
 
   std::vector<WebFormControlElement> control_elements =
       GetUnownedAutofillableFormFieldElements(
           initiating_element.GetDocument().All(), nullptr);
   if (!IsElementInControlElementSet(initiating_element, control_elements))
-    return;
+    return {};
 
-  ForEachMatchingFormFieldCommon(&control_elements, initiating_element, data,
-                                 filters, force_override, is_preview, callback);
+  return ForEachMatchingFormFieldCommon(&control_elements, initiating_element,
+                                        data, filters, force_override,
+                                        is_preview, callback);
 }
 
 // Sets the |field|'s value to the value in |data|, and specifies the section
@@ -2073,85 +2085,66 @@
       element, field_data_manager, form_util::EXTRACT_NONE, form, field);
 }
 
-void FillForm(const FormData& form, const WebFormControlElement& element) {
+std::vector<WebFormControlElement> FillForm(
+    const FormData& form,
+    const WebFormControlElement& element) {
   WebFormElement form_element = element.Form();
   if (form_element.IsNull()) {
-    ForEachMatchingUnownedFormField(element, form,
+    return ForEachMatchingUnownedFormField(element, form,
+                                           FILTER_ALL_NON_EDITABLE_ELEMENTS,
+                                           false, /* dont force override */
+                                           false, /* not a preview filling */
+                                           &FillFormField);
+  } else {
+    return ForEachMatchingFormField(form_element, element, form,
                                     FILTER_ALL_NON_EDITABLE_ELEMENTS,
                                     false, /* dont force override */
                                     false, /* not a preview filling */
                                     &FillFormField);
-    return;
   }
-
-  ForEachMatchingFormField(form_element, element, form,
-                           FILTER_ALL_NON_EDITABLE_ELEMENTS,
-                           false, /* dont force override */
-                           false, /* not a preview filling */
-                           &FillFormField);
 }
 
-void PreviewForm(const FormData& form, const WebFormControlElement& element) {
+std::vector<WebFormControlElement> PreviewForm(
+    const FormData& form,
+    const WebFormControlElement& element) {
   WebFormElement form_element = element.Form();
   if (form_element.IsNull()) {
-    ForEachMatchingUnownedFormField(element, form,
+    return ForEachMatchingUnownedFormField(element, form,
+                                           FILTER_ALL_NON_EDITABLE_ELEMENTS,
+                                           false, /* dont force override */
+                                           true,  /* preview filling */
+                                           &PreviewFormField);
+  } else {
+    return ForEachMatchingFormField(form_element, element, form,
                                     FILTER_ALL_NON_EDITABLE_ELEMENTS,
                                     false, /* dont force override */
                                     true,  /* preview filling */
                                     &PreviewFormField);
-    return;
   }
-
-  ForEachMatchingFormField(form_element, element, form,
-                           FILTER_ALL_NON_EDITABLE_ELEMENTS,
-                           false, /* dont force override */
-                           true,  /* preview filling */
-                           &PreviewFormField);
 }
 
-bool ClearPreviewedFormWithElement(const WebFormControlElement& element,
-                                   blink::WebAutofillState old_autofill_state) {
-  WebFormElement form_element = element.Form();
-  std::vector<WebFormControlElement> control_elements;
-  if (form_element.IsNull()) {
-    control_elements = GetUnownedAutofillableFormFieldElements(
-        element.GetDocument().All(), nullptr);
-    if (!IsElementInControlElementSet(element, control_elements))
-      return false;
-  } else {
-    control_elements = ExtractAutofillableElementsInForm(form_element);
-  }
-
-  for (size_t i = 0; i < control_elements.size(); ++i) {
-    // There might be unrelated elements in this form which have already been
-    // auto-filled.  For example, the user might have already filled the address
-    // part of a form and now be dealing with the credit card section.  We only
-    // want to reset the auto-filled status for fields that were previewed.
-    WebFormControlElement control_element = control_elements[i];
-
-    // Only text input, textarea and select elements can be previewed.
-    WebInputElement* input_element = ToWebInputElement(&control_element);
-    if (!IsTextInput(input_element) && !IsMonthInput(input_element) &&
-        !IsTextAreaElement(control_element) &&
-        !IsSelectElement(control_element))
+void ClearPreviewedElements(
+    std::vector<blink::WebFormControlElement>& previewed_elements,
+    const WebFormControlElement& initiating_element,
+    blink::WebAutofillState old_autofill_state) {
+  for (WebFormControlElement& control_element : previewed_elements) {
+    if (control_element.IsNull())
       continue;
 
     // Only clear previewed fields.
     if (control_element.GetAutofillState() != WebAutofillState::kPreviewed)
       continue;
 
-    if ((IsTextInput(input_element) || IsMonthInput(input_element) ||
-         IsTextAreaElement(control_element) ||
-         IsSelectElement(control_element)) &&
-        control_element.SuggestedValue().IsEmpty())
+    if (control_element.SuggestedValue().IsEmpty())
       continue;
 
     // Clear the suggested value. For the initiating node, also restore the
     // original value.
+    WebInputElement* input_element = ToWebInputElement(&control_element);
     if (IsTextInput(input_element) || IsMonthInput(input_element) ||
         IsTextAreaElement(control_element)) {
       control_element.SetSuggestedValue(WebString());
-      bool is_initiating_node = (element == control_element);
+      bool is_initiating_node = (initiating_element == control_element);
       if (is_initiating_node) {
         // Clearing the suggested value in the focused node (above) can cause
         // selection to be lost. We force selection range to restore the text
@@ -2159,17 +2152,14 @@
         int length = control_element.Value().length();
         control_element.SetSelectionRange(length, length);
         control_element.SetAutofillState(old_autofill_state);
-
       } else {
         control_element.SetAutofillState(WebAutofillState::kNotFilled);
       }
-    } else if (IsSelectElement(control_element)) {
+    } else {
       control_element.SetSuggestedValue(WebString());
       control_element.SetAutofillState(WebAutofillState::kNotFilled);
     }
   }
-
-  return true;
 }
 
 bool IsWebpageEmpty(const blink::WebLocalFrame* frame) {
diff --git a/components/autofill/content/renderer/form_autofill_util.h b/components/autofill/content/renderer/form_autofill_util.h
index 2dbb27e..a8b0a8b 100644
--- a/components/autofill/content/renderer/form_autofill_util.h
+++ b/components/autofill/content/renderer/form_autofill_util.h
@@ -249,21 +249,24 @@
     FormFieldData* field);
 
 // Fills the form represented by |form|.  |element| is the input element that
-// initiated the auto-fill process.
-void FillForm(const FormData& form,
-              const blink::WebFormControlElement& element);
+// initiated the auto-fill process. Returns the filled fields.
+std::vector<blink::WebFormControlElement> FillForm(
+    const FormData& form,
+    const blink::WebFormControlElement& element);
 
-// Previews the form represented by |form|.  |element| is the input element that
-// initiated the preview process.
-void PreviewForm(const FormData& form,
-                 const blink::WebFormControlElement& element);
+// Previews the form represented by |form|. |element| is the input element that
+// initiated the preview process. Returns the previewed fields.
+std::vector<blink::WebFormControlElement> PreviewForm(
+    const FormData& form,
+    const blink::WebFormControlElement& element);
 
-// Clears the placeholder values and the auto-filled background for any fields
-// in the form containing |node| that have been previewed.  Resets the
-// autofilled state of |node| to |was_autofilled|.  Returns false if the form is
-// not found.
-bool ClearPreviewedFormWithElement(const blink::WebFormControlElement& element,
-                                   blink::WebAutofillState old_autofill_state);
+// Clears the suggested values in |control_elements|. The state of
+// |initiating_element| is set to |old_autofill_state|; all other fields are set
+// to kNotFilled.
+void ClearPreviewedElements(
+    std::vector<blink::WebFormControlElement>& control_elements,
+    const blink::WebFormControlElement& initiating_element,
+    blink::WebAutofillState old_autofill_state);
 
 // Checks if the webpage is empty.
 // This kind of webpage is considered as empty:
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index 0dd96da..1f2ff9e 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -304,7 +304,8 @@
   // Returns the language state, if available.
   virtual const translate::LanguageState* GetLanguageState() = 0;
 
-  // Returns the translate driver, if available.
+  // Returns the translate driver, if available, which is used to observe the
+  // page language for language-dependent heuristics.
   virtual translate::TranslateDriver* GetTranslateDriver() = 0;
 
   // Retrieves the country code of the user from Chrome variation service.
diff --git a/components/autofill/core/browser/autofill_metrics.cc b/components/autofill/core/browser/autofill_metrics.cc
index f741427..a24b7d4 100644
--- a/components/autofill/core/browser/autofill_metrics.cc
+++ b/components/autofill/core/browser/autofill_metrics.cc
@@ -218,8 +218,86 @@
         case UNKNOWN_TYPE:
           group = GROUP_UNKNOWN_TYPE;
           break;
-        default:
-          NOTREACHED() << field_type << " has no group assigned (ambiguous)";
+        case NO_SERVER_DATA:
+        case EMPTY_TYPE:
+        case NAME_FIRST:
+        case NAME_MIDDLE:
+        case NAME_LAST:
+        case NAME_MIDDLE_INITIAL:
+        case NAME_FULL:
+        case NAME_SUFFIX:
+        case EMAIL_ADDRESS:
+        case PHONE_HOME_NUMBER:
+        case PHONE_HOME_CITY_CODE:
+        case PHONE_HOME_COUNTRY_CODE:
+        case PHONE_HOME_CITY_AND_NUMBER:
+        case PHONE_HOME_WHOLE_NUMBER:
+        case PHONE_FAX_NUMBER:
+        case PHONE_FAX_CITY_CODE:
+        case PHONE_FAX_COUNTRY_CODE:
+        case PHONE_FAX_CITY_AND_NUMBER:
+        case PHONE_FAX_WHOLE_NUMBER:
+        case ADDRESS_BILLING_LINE1:
+        case ADDRESS_BILLING_LINE2:
+        case ADDRESS_BILLING_APT_NUM:
+        case ADDRESS_BILLING_CITY:
+        case ADDRESS_BILLING_STATE:
+        case ADDRESS_BILLING_ZIP:
+        case ADDRESS_BILLING_COUNTRY:
+        case CREDIT_CARD_NAME_FULL:
+        case CREDIT_CARD_NUMBER:
+        case CREDIT_CARD_EXP_MONTH:
+        case CREDIT_CARD_EXP_2_DIGIT_YEAR:
+        case CREDIT_CARD_EXP_4_DIGIT_YEAR:
+        case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR:
+        case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR:
+        case CREDIT_CARD_TYPE:
+        case CREDIT_CARD_VERIFICATION_CODE:
+        case COMPANY_NAME:
+        case FIELD_WITH_DEFAULT_VALUE:
+        case PHONE_BILLING_NUMBER:
+        case PHONE_BILLING_CITY_CODE:
+        case PHONE_BILLING_COUNTRY_CODE:
+        case PHONE_BILLING_CITY_AND_NUMBER:
+        case PHONE_BILLING_WHOLE_NUMBER:
+        case NAME_BILLING_FIRST:
+        case NAME_BILLING_MIDDLE:
+        case NAME_BILLING_LAST:
+        case NAME_BILLING_MIDDLE_INITIAL:
+        case NAME_BILLING_FULL:
+        case NAME_BILLING_SUFFIX:
+        case MERCHANT_EMAIL_SIGNUP:
+        case MERCHANT_PROMO_CODE:
+        case PASSWORD:
+        case ACCOUNT_CREATION_PASSWORD:
+        case ADDRESS_BILLING_STREET_ADDRESS:
+        case ADDRESS_BILLING_SORTING_CODE:
+        case ADDRESS_BILLING_DEPENDENT_LOCALITY:
+        case ADDRESS_BILLING_LINE3:
+        case NOT_ACCOUNT_CREATION_PASSWORD:
+        case USERNAME:
+        case USERNAME_AND_EMAIL_ADDRESS:
+        case NEW_PASSWORD:
+        case PROBABLY_NEW_PASSWORD:
+        case NOT_NEW_PASSWORD:
+        case CREDIT_CARD_NAME_FIRST:
+        case CREDIT_CARD_NAME_LAST:
+        case PHONE_HOME_EXTENSION:
+        case CONFIRMATION_PASSWORD:
+        case AMBIGUOUS_TYPE:
+        case SEARCH_TERM:
+        case PRICE:
+        case NOT_PASSWORD:
+        case SINGLE_USERNAME:
+        case NOT_USERNAME:
+        case UPI_VPA:
+        case NAME_LAST_FIRST:
+        case NAME_LAST_CONJUNCTION:
+        case NAME_LAST_SECOND:
+        case NAME_HONORIFIC_PREFIX:
+        case NAME_FULL_WITH_HONORIFIC_PREFIX:
+        case MAX_VALID_FIELD_TYPE:
+          NOTREACHED() << field_type << " type is not in that group.";
           group = GROUP_AMBIGUOUS;
           break;
       }
diff --git a/components/autofill_assistant/browser/onboarding_result.h b/components/autofill_assistant/browser/onboarding_result.h
index 2db2c29..c5eee0a9 100644
--- a/components/autofill_assistant/browser/onboarding_result.h
+++ b/components/autofill_assistant/browser/onboarding_result.h
@@ -10,7 +10,7 @@
 namespace autofill_assistant {
 
 // GENERATED_JAVA_ENUM_PACKAGE: (
-// org.chromium.chrome.browser.autofill_assistant)
+// org.chromium.chrome.browser.autofill_assistant.onboarding)
 // GENERATED_JAVA_CLASS_NAME_OVERRIDE: AssistantOnboardingResult
 enum class OnboardingResult {
   // The onboarding was dismissed. No explicit choice was made.
diff --git a/components/browser_ui/settings/android/BUILD.gn b/components/browser_ui/settings/android/BUILD.gn
index 6547dc5..59166280 100644
--- a/components/browser_ui/settings/android/BUILD.gn
+++ b/components/browser_ui/settings/android/BUILD.gn
@@ -19,6 +19,7 @@
     "widget/java/src/org/chromium/components/browser_ui/settings/ChromeSwitchPreference.java",
     "widget/java/src/org/chromium/components/browser_ui/settings/ExpandablePreferenceGroup.java",
     "widget/java/src/org/chromium/components/browser_ui/settings/LearnMorePreference.java",
+    "widget/java/src/org/chromium/components/browser_ui/settings/LongSummaryTextMessagePreference.java",
     "widget/java/src/org/chromium/components/browser_ui/settings/SpinnerPreference.java",
     "widget/java/src/org/chromium/components/browser_ui/settings/TextMessagePreference.java",
   ]
diff --git a/components/browser_ui/settings/android/widget/java/src/org/chromium/components/browser_ui/settings/LongSummaryTextMessagePreference.java b/components/browser_ui/settings/android/widget/java/src/org/chromium/components/browser_ui/settings/LongSummaryTextMessagePreference.java
new file mode 100644
index 0000000..08c077b
--- /dev/null
+++ b/components/browser_ui/settings/android/widget/java/src/org/chromium/components/browser_ui/settings/LongSummaryTextMessagePreference.java
@@ -0,0 +1,31 @@
+// 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.
+
+package org.chromium.components.browser_ui.settings;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * A custom version of a TextMessagePreference that allows for very long summary texts.
+ */
+public class LongSummaryTextMessagePreference extends TextMessagePreference {
+    /**
+     * Constructor for inflating from XML.
+     */
+    public LongSummaryTextMessagePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
+        summaryView.setMaxLines(100);
+    }
+}
diff --git a/components/download/internal/common/download_item_impl_unittest.cc b/components/download/internal/common/download_item_impl_unittest.cc
index 503e0ac..2405ec4 100644
--- a/components/download/internal/common/download_item_impl_unittest.cc
+++ b/components/download/internal/common/download_item_impl_unittest.cc
@@ -69,7 +69,8 @@
 
 class MockDownloadItemRenameHandler : public DownloadItemRenameHandler {
  public:
-  MockDownloadItemRenameHandler() : DownloadItemRenameHandler(nullptr) {}
+  explicit MockDownloadItemRenameHandler(DownloadItem* item)
+      : DownloadItemRenameHandler(item) {}
   ~MockDownloadItemRenameHandler() override = default;
 
   MOCK_METHOD1(Start, void(Callback));
@@ -2808,9 +2809,11 @@
 
   // Create a rename handler and make sure the delegate returns it.
   DownloadItemRenameHandler::Callback callback;
-  auto rename_handler = std::make_unique<MockDownloadItemRenameHandler>();
+  auto rename_handler = std::make_unique<MockDownloadItemRenameHandler>(item);
   MockDownloadItemRenameHandler* rename_handler_ptr = rename_handler.get();
 
+  ASSERT_EQ(item, rename_handler->download_item());
+
   EXPECT_CALL(*rename_handler, Start(_)).WillOnce(MoveArg<0>(&callback));
   EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload_(item, _))
       .WillOnce(Return(true));
diff --git a/components/download/internal/common/download_item_rename_handler.cc b/components/download/internal/common/download_item_rename_handler.cc
index d241df8..53b3277 100644
--- a/components/download/internal/common/download_item_rename_handler.cc
+++ b/components/download/internal/common/download_item_rename_handler.cc
@@ -10,7 +10,8 @@
 namespace download {
 
 DownloadItemRenameHandler::DownloadItemRenameHandler(
-    DownloadItem* download_item) {}
+    DownloadItem* download_item)
+    : download_item_(download_item) {}
 
 DownloadItemRenameHandler::~DownloadItemRenameHandler() = default;
 
diff --git a/components/download/public/common/download_item_rename_handler.h b/components/download/public/common/download_item_rename_handler.h
index a55e7febd..63d1244 100644
--- a/components/download/public/common/download_item_rename_handler.h
+++ b/components/download/public/common/download_item_rename_handler.h
@@ -21,6 +21,8 @@
 // DownloadItemImpl attempts to retrieve the object from its delegate, and
 // if valid will call the Start() method instead of using
 // DownloadFile::RenameAndAnnotate().
+//
+// Instances of DownloadItemRenameHandler are owned by DownloadItem.
 class COMPONENTS_DOWNLOAD_EXPORT DownloadItemRenameHandler {
  public:
   using Callback = base::OnceCallback<void(DownloadInterruptReason reason,
@@ -29,6 +31,8 @@
   explicit DownloadItemRenameHandler(DownloadItem* download_item);
   virtual ~DownloadItemRenameHandler();
 
+  DownloadItem* download_item() { return download_item_; }
+
   // Starts the process of renaming the file and invokes |callback| when
   // done.
   virtual void Start(Callback callback);
@@ -38,6 +42,9 @@
 
   // Shows the download in the context of its container.
   virtual void ShowDownloadInContext();
+
+ private:
+  DownloadItem* download_item_;
 };
 
 }  // namespace download
diff --git a/components/exo/permission.cc b/components/exo/permission.cc
index 49eee29..f353918 100644
--- a/components/exo/permission.cc
+++ b/components/exo/permission.cc
@@ -6,12 +6,22 @@
 
 #include "base/time/time.h"
 
+DEFINE_UI_CLASS_PROPERTY_TYPE(exo::Permission*)
+
 namespace exo {
 
+// Permission object allowing this window to activate itself.
+DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(exo::Permission, kPermissionKey, nullptr)
+
+Permission::Permission(Permission::Capability capability)
+    : capability_(capability), expiry_(base::Time::Max()) {}
+
 Permission::Permission(Permission::Capability capability,
                        base::TimeDelta timeout)
     : capability_(capability), expiry_(base::Time::Now() + timeout) {}
 
+Permission::~Permission() = default;
+
 void Permission::Revoke() {
   // Revoke the permission by setting its expiry to be in the past.
   expiry_ = {};
diff --git a/components/exo/permission.h b/components/exo/permission.h
index fdf0ab6..a6e69a3 100644
--- a/components/exo/permission.h
+++ b/components/exo/permission.h
@@ -6,15 +6,20 @@
 #define COMPONENTS_EXO_PERMISSION_H_
 
 #include "base/time/time.h"
+#include "ui/base/class_property.h"
 
 namespace exo {
 
+// An aura::Window property that adds a capability to a window.
 class Permission {
  public:
   enum class Capability {
     kActivate,
   };
 
+  // Creates a permission with a |capability| that never expires.
+  explicit Permission(Capability capability);
+
   // Create a permission with the given |capability| until |timeout| elapses.
   Permission(Capability capability, base::TimeDelta timeout);
 
@@ -24,7 +29,7 @@
   Permission& operator=(const Permission& other) = delete;
   Permission& operator=(Permission&& other) = delete;
 
-  virtual ~Permission() = default;
+  virtual ~Permission();
 
   // Prevent this permission from returning true on subsequent Check()s.
   void Revoke();
@@ -39,6 +44,8 @@
   base::Time expiry_;
 };
 
+extern const ui::ClassProperty<Permission*>* const kPermissionKey;
+
 }  // namespace exo
 
 #endif  // COMPONENTS_EXO_PERMISSION_H_
diff --git a/components/exo/shell_surface_unittest.cc b/components/exo/shell_surface_unittest.cc
index dde83c0..9372ba68 100644
--- a/components/exo/shell_surface_unittest.cc
+++ b/components/exo/shell_surface_unittest.cc
@@ -391,24 +391,61 @@
   EXPECT_FALSE(HasPermissionToActivate(window));
 
   // Can grant permission.
-  std::unique_ptr<exo::Permission> permission =
-      GrantPermissionToActivate(window, base::TimeDelta::FromDays(1));
+  GrantPermissionToActivate(window, base::TimeDelta::FromDays(1));
+  exo::Permission* permission = window->GetProperty(kPermissionKey);
   EXPECT_TRUE(permission->Check(Permission::Capability::kActivate));
   EXPECT_TRUE(HasPermissionToActivate(window));
 
-  // Overriding the permission revokes the previous one.
-  std::unique_ptr<exo::Permission> permission2 =
-      GrantPermissionToActivate(window, base::TimeDelta::FromDays(2));
-  EXPECT_FALSE(permission->Check(Permission::Capability::kActivate));
-  EXPECT_TRUE(permission2->Check(Permission::Capability::kActivate));
-
-  // The old permission no longer affects the window
-  permission.reset();
-  EXPECT_TRUE(HasPermissionToActivate(window));
-
-  // Deleting the permission revokes.
-  permission2.reset();
+  // Can revoke permission.
+  RevokePermissionToActivate(window);
   EXPECT_FALSE(HasPermissionToActivate(window));
+
+  // Can grant permission again.
+  GrantPermissionToActivate(window, base::TimeDelta::FromDays(2));
+  exo::Permission* permission2 = window->GetProperty(kPermissionKey);
+  EXPECT_TRUE(permission2->Check(Permission::Capability::kActivate));
+  EXPECT_TRUE(HasPermissionToActivate(window));
+}
+
+TEST_F(ShellSurfaceTest, WidgetActivation) {
+  gfx::Size buffer_size(64, 64);
+  auto buffer1 = std::make_unique<Buffer>(
+      exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
+  auto surface1 = std::make_unique<Surface>();
+  auto shell_surface1 = std::make_unique<ShellSurface>(surface1.get());
+  surface1->Attach(buffer1.get());
+  surface1->Commit();
+
+  // The window is active.
+  views::Widget* widget1 = shell_surface1->GetWidget();
+  EXPECT_TRUE(widget1->IsActive());
+
+  // Create a second window.
+  auto buffer2 = std::make_unique<Buffer>(
+      exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
+  auto surface2 = std::make_unique<Surface>();
+  auto shell_surface2 = std::make_unique<ShellSurface>(surface2.get());
+  surface2->Attach(buffer2.get());
+  surface2->Commit();
+
+  // Now the second window is active.
+  views::Widget* widget2 = shell_surface2->GetWidget();
+  EXPECT_FALSE(widget1->IsActive());
+  EXPECT_TRUE(widget2->IsActive());
+
+  // Grant permission to activate the first window.
+  GrantPermissionToActivate(widget1->GetNativeWindow(),
+                            base::TimeDelta::FromDays(1));
+
+  // The first window can activate itself.
+  surface1->RequestActivation();
+  EXPECT_TRUE(widget1->IsActive());
+  EXPECT_FALSE(widget2->IsActive());
+
+  // The second window cannot activate itself.
+  surface2->RequestActivation();
+  EXPECT_TRUE(widget1->IsActive());
+  EXPECT_FALSE(widget2->IsActive());
 }
 
 TEST_F(ShellSurfaceTest, EmulateOverrideRedirect) {
diff --git a/components/exo/shell_surface_util.cc b/components/exo/shell_surface_util.cc
index 313ef1ee6..c2ad802 100644
--- a/components/exo/shell_surface_util.cc
+++ b/components/exo/shell_surface_util.cc
@@ -26,8 +26,6 @@
 #include "chromeos/ui/base/window_properties.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-DEFINE_UI_CLASS_PROPERTY_TYPE(exo::Permission*)
-
 namespace exo {
 
 namespace {
@@ -45,9 +43,6 @@
 // Accessibility Id set by the client.
 DEFINE_UI_CLASS_PROPERTY_KEY(int32_t, kClientAccessibilityIdKey, -1)
 
-// Permission object allowing this window to activate itself.
-DEFINE_UI_CLASS_PROPERTY_KEY(exo::Permission*, kPermissionKey, nullptr)
-
 // Returns true if the component for a located event should be taken care of
 // by the window system.
 bool ShouldHTComponentBlocked(int component) {
@@ -221,36 +216,17 @@
   }
 }
 
-namespace {
+void GrantPermissionToActivate(aura::Window* window, base::TimeDelta timeout) {
+  // Activation is the only permission, so just set the property. The window
+  // owns the Permission object.
+  window->SetProperty(
+      kPermissionKey,
+      new Permission(Permission::Capability::kActivate, timeout));
+}
 
-// An activation-permission object whose lifetime is tied to a window property.
-class ScopedWindowActivationPermission : public Permission {
- public:
-  ScopedWindowActivationPermission(aura::Window* window,
-                                   base::TimeDelta timeout)
-      : Permission(Permission::Capability::kActivate, timeout),
-        window_(window) {
-    Permission* other = window_->GetProperty(kPermissionKey);
-    if (other) {
-      other->Revoke();
-    }
-    window_->SetProperty(kPermissionKey, reinterpret_cast<Permission*>(this));
-  }
-
-  ~ScopedWindowActivationPermission() override {
-    if (window_->GetProperty(kPermissionKey) == this)
-      window_->ClearProperty(kPermissionKey);
-  }
-
- private:
-  aura::Window* window_;
-};
-
-}  // namespace
-
-std::unique_ptr<Permission> GrantPermissionToActivate(aura::Window* window,
-                                                      base::TimeDelta timeout) {
-  return std::make_unique<ScopedWindowActivationPermission>(window, timeout);
+void RevokePermissionToActivate(aura::Window* window) {
+  // Activation is the only permission, so just clear the property.
+  window->ClearProperty(kPermissionKey);
 }
 
 bool HasPermissionToActivate(aura::Window* window) {
diff --git a/components/exo/shell_surface_util.h b/components/exo/shell_surface_util.h
index 765bc0e..7f491ee 100644
--- a/components/exo/shell_surface_util.h
+++ b/components/exo/shell_surface_util.h
@@ -29,7 +29,6 @@
 
 namespace exo {
 
-class Permission;
 class Surface;
 class ShellSurfaceBase;
 
@@ -75,12 +74,12 @@
 // requested grab.
 Surface* GetTargetSurfaceForLocatedEvent(const ui::LocatedEvent* event);
 
-// Allow the |window| to activate itself for the diration of |timeout|. Returns
-// the permission object, where deleting the object ammounts to Revoke()ing the
-// permission.
-std::unique_ptr<exo::Permission> GrantPermissionToActivate(
-    aura::Window* window,
-    base::TimeDelta timeout);
+// Allows the |window| to activate itself for the duration of |timeout|. Revokes
+// any existing permission.
+void GrantPermissionToActivate(aura::Window* window, base::TimeDelta timeout);
+
+// Revokes the permission for |window| to activate itself.
+void RevokePermissionToActivate(aura::Window* window);
 
 // Returns true if the |window| has permission to activate itself.
 bool HasPermissionToActivate(aura::Window* window);
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index 1abcabc3..23a6552 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -454,7 +454,8 @@
 
     /** http://crbug.com/464669 : Disallow firing external intent from background tab. */
     private boolean blockExternalNavFromBackgroundTab(ExternalNavigationParams params) {
-        if (params.isBackgroundTabNavigation()) {
+        if (params.isBackgroundTabNavigation()
+                && !params.areIntentLaunchesAllowedInBackgroundTabs()) {
             if (DEBUG) Log.i(TAG, "Navigation in background tab");
             return true;
         }
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java
index fe172dad..5c22f60 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java
@@ -39,6 +39,9 @@
     /** Whether this navigation happens in background tab. */
     private final boolean mIsBackgroundTabNavigation;
 
+    /** Whether intent launches are allowed in background tabs. */
+    private final boolean mIntentLaunchesAllowedInBackgroundTabs;
+
     /** Whether this navigation happens in main frame. */
     private final boolean mIsMainFrame;
 
@@ -70,8 +73,9 @@
     private ExternalNavigationParams(String url, boolean isIncognito, String referrerUrl,
             int pageTransition, boolean isRedirect, boolean appMustBeInForeground,
             RedirectHandler redirectHandler, boolean openInNewTab,
-            boolean isBackgroundTabNavigation, boolean isMainFrame, String nativeClientPackageName,
-            boolean hasUserGesture, boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent,
+            boolean isBackgroundTabNavigation, boolean intentLaunchesAllowedInBackgroundTabs,
+            boolean isMainFrame, String nativeClientPackageName, boolean hasUserGesture,
+            boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent,
             boolean isRendererInitiated, @Nullable Origin initiatorOrigin) {
         mUrl = url;
         mIsIncognito = isIncognito;
@@ -82,6 +86,7 @@
         mRedirectHandler = redirectHandler;
         mOpenInNewTab = openInNewTab;
         mIsBackgroundTabNavigation = isBackgroundTabNavigation;
+        mIntentLaunchesAllowedInBackgroundTabs = intentLaunchesAllowedInBackgroundTabs;
         mIsMainFrame = isMainFrame;
         mNativeClientPackageName = nativeClientPackageName;
         mHasUserGesture = hasUserGesture;
@@ -139,6 +144,11 @@
         return mIsBackgroundTabNavigation;
     }
 
+    /** @return Whether intent launches are allowed in background tabs. */
+    public boolean areIntentLaunchesAllowedInBackgroundTabs() {
+        return mIntentLaunchesAllowedInBackgroundTabs;
+    }
+
     /** @return Whether this navigation happens in main frame. */
     public boolean isMainFrame() {
         return mIsMainFrame;
@@ -209,6 +219,9 @@
         /** Whether this navigation happens in background tab. */
         private boolean mIsBackgroundTabNavigation;
 
+        /** Whether intent launches are allowed in background tabs. */
+        private boolean mIntentLaunchesAllowedInBackgroundTabs;
+
         /** Whether this navigation happens in main frame. */
         private boolean mIsMainFrame;
 
@@ -275,6 +288,12 @@
             return this;
         }
 
+        /** Sets whether intent launches are allowed in background tabs. */
+        public Builder setIntentLaunchesAllowedInBackgroundTabs(boolean v) {
+            mIntentLaunchesAllowedInBackgroundTabs = v;
+            return this;
+        }
+
         /** Sets whether this navigation happens in main frame. */
         public Builder setIsMainFrame(boolean v) {
             mIsMainFrame = v;
@@ -322,9 +341,10 @@
         public ExternalNavigationParams build() {
             return new ExternalNavigationParams(mUrl, mIsIncognito, mReferrerUrl, mPageTransition,
                     mIsRedirect, mApplicationMustBeInForeground, mRedirectHandler, mOpenInNewTab,
-                    mIsBackgroundTabNavigation, mIsMainFrame, mNativeClientPackageName,
-                    mHasUserGesture, mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent,
-                    mIsRendererInitiated, mInitiatorOrigin);
+                    mIsBackgroundTabNavigation, mIntentLaunchesAllowedInBackgroundTabs,
+                    mIsMainFrame, mNativeClientPackageName, mHasUserGesture,
+                    mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent, mIsRendererInitiated,
+                    mInitiatorOrigin);
         }
     }
 }
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java
index ba8390b2..a5b9c4b 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java
@@ -39,6 +39,10 @@
     /* Returns whether whether the tab associated with this client is currently hidden. */
     boolean isHidden();
 
+    /* Returns whether intent launching from hidden tabs is allowed for the navigation specified
+     * by |params|. */
+    boolean areIntentLaunchesAllowedInHiddenTabsForNavigation(NavigationParams params);
+
     /* Returns the Activity associated with this client. */
     Activity getActivity();
 
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
index b36ce00..c876393 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
@@ -180,6 +180,8 @@
                 .setRedirectHandler(redirectHandler)
                 .setOpenInNewTab(shouldCloseTab)
                 .setIsBackgroundTabNavigation(mClient.isHidden() && !isInitialTabLaunchInBackground)
+                .setIntentLaunchesAllowedInBackgroundTabs(
+                        mClient.areIntentLaunchesAllowedInHiddenTabsForNavigation(navigationParams))
                 .setIsMainFrame(navigationParams.isMainFrame)
                 .setHasUserGesture(navigationParams.hasUserGesture)
                 .setShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent(
diff --git a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
index b56186e..474be155 100644
--- a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
+++ b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
@@ -1306,6 +1306,18 @@
 
     @Test
     @SmallTest
+    public void testBackgroundTabNavigationWithIntentLaunchesInBackgroundTabsAllowed() {
+        mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
+
+        checkUrl(YOUTUBE_URL)
+                .withIsBackgroundTabNavigation(true)
+                .withAllowIntentLaunchesInBackgroundTabs(true)
+                .expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
+                        START_OTHER_ACTIVITY);
+    }
+
+    @Test
+    @SmallTest
     public void testPdfDownloadHappensInChrome() {
         mDelegate.add(new IntentActivity(CALENDAR_URL, "calendar"));
 
@@ -2351,6 +2363,7 @@
         private boolean mIsRedirect;
         private boolean mChromeAppInForegroundRequired = true;
         private boolean mIsBackgroundTabNavigation;
+        private boolean mIntentLaunchesAllowedInBackgroundTabs;
         private boolean mHasUserGesture;
         private RedirectHandler mRedirectHandler;
         private boolean mIsRendererInitiated;
@@ -2397,6 +2410,12 @@
             return this;
         }
 
+        public ExternalNavigationTestParams withAllowIntentLaunchesInBackgroundTabs(
+                boolean allowIntentLaunchesInBackgroundTabs) {
+            mIntentLaunchesAllowedInBackgroundTabs = allowIntentLaunchesInBackgroundTabs;
+            return this;
+        }
+
         public ExternalNavigationTestParams withRedirectHandler(RedirectHandler handler) {
             mRedirectHandler = handler;
             return this;
@@ -2439,6 +2458,8 @@
                             .setApplicationMustBeInForeground(mChromeAppInForegroundRequired)
                             .setRedirectHandler(mRedirectHandler)
                             .setIsBackgroundTabNavigation(mIsBackgroundTabNavigation)
+                            .setIntentLaunchesAllowedInBackgroundTabs(
+                                    mIntentLaunchesAllowedInBackgroundTabs)
                             .setIsMainFrame(mIsMainFrame)
                             .setNativeClientPackageName(mDelegate.getReferrerWebappPackageName())
                             .setHasUserGesture(mHasUserGesture)
diff --git a/components/lookalikes/core/features.cc b/components/lookalikes/core/features.cc
index dc362a9..ef79bef 100644
--- a/components/lookalikes/core/features.cc
+++ b/components/lookalikes/core/features.cc
@@ -13,5 +13,8 @@
 const base::Feature kLookalikeInterstitialForPunycode{
     "LookalikeInterstitialForPunycode", base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kLookalikeDigitalAssetLinks{
+    "LookalikeDigitalAssetLinks", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace lookalikes
diff --git a/components/lookalikes/core/features.h b/components/lookalikes/core/features.h
index 453e1146..459ecd1 100644
--- a/components/lookalikes/core/features.h
+++ b/components/lookalikes/core/features.h
@@ -19,6 +19,10 @@
 COMPONENT_EXPORT(LOOKALIKES_FEATURES)
 extern const base::Feature kLookalikeInterstitialForPunycode;
 
+// This feature enables Digital Asset Link validations for lookalikes.
+COMPONENT_EXPORT(LOOKALIKES_FEATURES)
+extern const base::Feature kLookalikeDigitalAssetLinks;
+
 }  // namespace features
 }  // namespace lookalikes
 
diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc
index aa85b72..53ff6b48 100644
--- a/components/metrics/metrics_service.cc
+++ b/components/metrics/metrics_service.cc
@@ -459,6 +459,8 @@
 // Initialization methods
 
 void MetricsService::InitializeMetricsState() {
+  SCOPED_UMA_HISTOGRAM_SHORT_TIMER("UMA.MetricsService.Initialize.Time");
+
   const int64_t buildtime = MetricsLog::GetBuildTime();
   const std::string version = client_->GetVersionString();
 
diff --git a/components/no_state_prefetch/browser/no_state_prefetch_contents.cc b/components/no_state_prefetch/browser/no_state_prefetch_contents.cc
index 1255d73..2ec5b1c 100644
--- a/components/no_state_prefetch/browser/no_state_prefetch_contents.cc
+++ b/components/no_state_prefetch/browser/no_state_prefetch_contents.cc
@@ -24,8 +24,6 @@
 #include "content/public/browser/browser_task_traits.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/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
@@ -42,7 +40,7 @@
 
 using content::BrowserThread;
 using content::OpenURLParams;
-using content::RenderViewHost;
+using content::RenderFrameHost;
 using content::SessionStorageNamespace;
 using content::WebContents;
 
@@ -204,19 +202,15 @@
 
   // Set the size of the prerender WebContents.
   no_state_prefetch_contents_->Resize(bounds_);
+  no_state_prefetch_contents_->WasHidden();
 
   // TODO(davidben): This logic assumes each prerender has at most one
   // process. https://crbug.com/440544
   no_state_prefetch_manager()->AddPrerenderProcessHost(
-      GetRenderViewHost()->GetProcess());
+      GetMainFrame()->GetProcess());
 
   NotifyPrefetchStart();
 
-  // Register to inform new RenderViews that we're prerendering.
-  notification_registrar_.Add(
-      this, content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
-      content::Source<WebContents>(no_state_prefetch_contents_.get()));
-
   content::NavigationController::LoadURLParams load_url_params(prerender_url_);
   load_url_params.referrer = referrer_;
   load_url_params.initiator_origin = initiator_origin_;
@@ -268,40 +262,6 @@
   observer_list_.RemoveObserver(observer);
 }
 
-void NoStatePrefetchContents::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  switch (type) {
-    case content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED: {
-      if (no_state_prefetch_contents_) {
-        DCHECK_EQ(content::Source<WebContents>(source).ptr(),
-                  no_state_prefetch_contents_.get());
-
-        content::Details<RenderViewHost> new_render_view_host(details);
-        OnRenderViewHostCreated(new_render_view_host.ptr());
-
-        // Make sure the size of the RenderViewHost has been passed to the new
-        // RenderView.  Otherwise, the size may not be sent until the
-        // RenderViewReady event makes it from the render process to the UI
-        // thread of the browser process.  When the RenderView receives its
-        // size, is also sets itself to be visible, which would then break the
-        // visibility API.
-        new_render_view_host->GetWidget()->SynchronizeVisualProperties();
-        no_state_prefetch_contents_->WasHidden();
-      }
-      break;
-    }
-
-    default:
-      NOTREACHED() << "Unexpected notification sent.";
-      break;
-  }
-}
-
-void NoStatePrefetchContents::OnRenderViewHostCreated(
-    RenderViewHost* new_render_view_host) {}
-
 std::unique_ptr<WebContents> NoStatePrefetchContents::CreateWebContents(
     SessionStorageNamespace* session_storage_namespace) {
   // TODO(ajwong): Remove the temporary map once prerendering is aware of
@@ -475,11 +435,11 @@
 
 void NoStatePrefetchContents::DestroyWhenUsingTooManyResources() {
   if (process_pid_ == base::kNullProcessId) {
-    RenderViewHost* rvh = GetRenderViewHost();
-    if (!rvh)
+    RenderFrameHost* rfh = GetMainFrame();
+    if (!rfh)
       return;
 
-    content::RenderProcessHost* rph = rvh->GetProcess();
+    content::RenderProcessHost* rph = rfh->GetProcess();
     if (!rph)
       return;
 
@@ -534,9 +494,9 @@
   return std::move(no_state_prefetch_contents_);
 }
 
-RenderViewHost* NoStatePrefetchContents::GetRenderViewHost() {
+RenderFrameHost* NoStatePrefetchContents::GetMainFrame() {
   return no_state_prefetch_contents_
-             ? no_state_prefetch_contents_->GetMainFrame()->GetRenderViewHost()
+             ? no_state_prefetch_contents_->GetMainFrame()
              : nullptr;
 }
 
diff --git a/components/no_state_prefetch/browser/no_state_prefetch_contents.h b/components/no_state_prefetch/browser/no_state_prefetch_contents.h
index 9ef5cc31..0695ac9 100644
--- a/components/no_state_prefetch/browser/no_state_prefetch_contents.h
+++ b/components/no_state_prefetch/browser/no_state_prefetch_contents.h
@@ -22,8 +22,6 @@
 #include "components/no_state_prefetch/common/prerender_canceler.mojom.h"
 #include "components/no_state_prefetch/common/prerender_final_status.h"
 #include "components/no_state_prefetch/common/prerender_origin.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/referrer.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
@@ -49,8 +47,7 @@
 
 class NoStatePrefetchManager;
 
-class NoStatePrefetchContents : public content::NotificationObserver,
-                                public content::WebContentsObserver,
+class NoStatePrefetchContents : public content::WebContentsObserver,
                                 public prerender::mojom::PrerenderCanceler {
  public:
   // NoStatePrefetchContents::Create uses the currently registered Factory to
@@ -123,7 +120,7 @@
   // it if not.
   void DestroyWhenUsingTooManyResources();
 
-  content::RenderViewHost* GetRenderViewHost();
+  content::RenderFrameHost* GetMainFrame();
 
   NoStatePrefetchManager* no_state_prefetch_manager() {
     return no_state_prefetch_manager_;
@@ -159,11 +156,6 @@
 
   void RenderProcessGone(base::TerminationStatus status) override;
 
-  // content::NotificationObserver
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
-
   // Checks that a URL may be prerendered, for one of the many redirections. If
   // the URL can not be prerendered - for example, it's an ftp URL - |this| will
   // be destroyed and false is returned. Otherwise, true is returned.
@@ -225,11 +217,6 @@
   void NotifyPrefetchStopLoading();
   void NotifyPrefetchStop();
 
-  // Called whenever a RenderViewHost is created for prerendering.  Only called
-  // once the RenderViewHost has a RenderView and RenderWidgetHostView.
-  virtual void OnRenderViewHostCreated(
-      content::RenderViewHost* new_render_view_host);
-
   std::unique_ptr<content::WebContents> CreateWebContents(
       content::SessionStorageNamespace* session_storage_namespace);
 
@@ -287,8 +274,6 @@
   // The browser context being used
   content::BrowserContext* browser_context_;
 
-  content::NotificationRegistrar notification_registrar_;
-
   // A vector of URLs that this prerendered page matches against.
   // This array can contain more than element as a result of redirects,
   // such as HTTP redirects or javascript redirects.
diff --git a/components/onc/docs/onc_spec.md b/components/onc/docs/onc_spec.md
index 92fb91b..31f0ad46 100644
--- a/components/onc/docs/onc_spec.md
+++ b/components/onc/docs/onc_spec.md
@@ -2081,11 +2081,186 @@
 }
 ```
 
+## Chrome internal format
 
-## Standalone editor
+Internally, Chrome uses a base::Value dictionary format to represent ONC
+configurations. We refer to two types of dictionaries within the code:
 
-The source code for a Chrome packaged app to generate ONC configuration can
-be found here: https://chromium.googlesource.com/chromiumos/platform/spigots/
+* **ONC Dictionary** A dictionary of key-value pairs similar to the
+    dictionaries under "NetworkConfigurations" described above.
+
+* **Managed ONC Dictionary** A dictionary of key-value pairs where the values
+    are dictionaries containing values from policies and settings described
+    below.
+
+Configurations may combine policy and settings configurations.
+
+### Merge rules
+
+If a policy configuration exists, the following rules apply:
+
+* **Enforced** (default): The policy value is always the effective value.
+    * *Note*: User policy supersedes Device policy.
+    * *Note*: If a property is not provided by policy it is treated as enforced
+      and the default value is applied.
+* **Recommended**: If a property appears in the [Recommended](#Recommended-Values) section,
+    it is considered 'Recommended'. If a UserSetting or SharedSetting value
+    exists, it can be selected as the Effective value.
+
+### Dictionary format
+
+Managed ONC dictionaries contain the keys described under
+[Network Configuration](#Network-Configuration), however the values are
+dictionaries that may include the following properties:
+
+* **Active**: For properties that are translated from the configuration
+    manager (e.g. Shill), the 'active' value currently in use by the
+    configuration manager.
+* **Effective**: The effective source for the property: UserPolicy, DevicePolicy,
+    UserSetting or SharedSetting.
+* **UserPolicy**: The value provided by the user policy if any.
+* **DevicePolicy**: The value provided by the device policy if any.
+* **UserSetting**: The value set by the logged in user. Only provided if
+    UserEditable is true (i.e. no policy affects the property or the
+    policy provided value is recommended only).
+* **SharedSetting**: The value set for all users of the device. Only provided if
+    DeviceEditiable is true (i.e. no policy affects the property or the
+    policy provided value is recommended only).
+* **UserEditable**: True if a UserPolicy exists and allows the property to be
+    edited (i.e. is a recommended value). Defaults to False.
+* **DeviceEditable**: True if a DevicePolicy exists and allows the property to be
+    edited (i.e. is a recommended value). Defaults to False.
+
+### Examples
+
+Property with User policy enforced value.
+```
+  "Priority": {
+    "Active": 3,
+    "Effective": "UserPolicy",
+    "UserEditable": false,
+    "UserPolicy": 3,
+  },
+```
+
+Property with Device policy recommended value and no setting value.
+```
+  "Priority": {
+    "Active": 2,
+    "DeviceEditable": true,
+    "DevicePolicy": 2,
+    "Effective": "DevicePolicy",
+  },
+```
+
+Property with Device policy recommended value and a shared setting value.
+```
+  "Priority": {
+    "Active": 1,
+    "DeviceEditable": true,
+    "DevicePolicy": 2,
+    "Effective": "SharedSetting",
+    "SharedSetting": 1,
+  },
+```
+
+Property with Device policy and User policy recommended values and a user
+setting selected as the effective setting.
+```
+  "Priority": {
+    "Active": 1
+    "DeviceEditable": true,
+    "DevicePolicy": 2,
+    "Effective": "UserSetting",
+    "UserEditable": true,
+    "UserPolicy": 3,
+    "UserSetting": 1
+  },
+```
+
+## Mojo format
+
+Chrome provides a mojo API for ONC properties:
+https://source.chromium.org/chromium/chromium/src/+/master:chromeos/services/network_config/public/mojom/cros_network_config.mojom
+
+The mojo API uses a simplified structure for managed properties based on the
+following assumptions:
+
+* Settings UI and other clients are only interested in the active value and
+  the source of the active / effective value.
+* Preserving non policy settings is only interesting if they are the active
+  value. i.e. if a policy value is enforced or a recommended value is used, it
+  is not necessary to preserve any other settings values.
+
+In this simplified format, a descriptive enum is used to describe the effective
+policy source and whether it is enforced or recommended.
+
+The conversion code can be found in cros_network_config.cc:GetManagedDictionary
+https://source.chromium.org/chromium/chromium/src/+/master:chromeos/services/network_config/cros_network_config.cc
+
+```
+enum PolicySource {
+  // The property is not controlled by policy.
+  kNone,
+  // The property value came from a user policy and is enforced.
+  kUserPolicyEnforced,
+  // The property value came from a device policy and is enforced.
+  kDevicePolicyEnforced,
+  // The property value came from a user policy and is recommended.
+  kUserPolicyRecommended,
+  // The property value came from a device policy and is recommended.
+  kDevicePolicyRecommended,
+  // The property value came from an extension.
+  kActiveExtension,
+};
+
+struct ManagedString {
+  string active_value;
+  PolicySource policy_source = kNone;
+  string? policy_value;
+};
+```
+
+### Examples
+
+Property with User policy enforced value.
+```
+  Priority: {
+    activeValue: 3,
+    policySource: kUserPolicyEnforced,
+    policyValue: 3
+  },
+```
+
+Property with Device policy recommended value and no setting value.
+```
+  Priority: {
+    activeValue: 2,
+    policySource: kDevicePolicyRecommended,
+    policyValue: 2
+  },
+```
+
+Property with Device policy recommended value and a setting value.
+```
+  Priority: {
+    activeValue: 1,
+    policySource: DevicePolicyRecommended,
+    policyValue: 2
+  },
+```
+
+Property with Device policy and User policy recommended values and a user
+setting selected as the effective setting. *Note*: The User policy overrides
+the Device policy and is all that is represented here. The device configuration
+is persisted in the device policy itself.
+```
+  Priority: {
+    activeValue: 1,
+    policySource: kUserPolicyRecommended,
+    policyValue: 3
+  },
+```
 
 ## Internationalization and Localization
 
@@ -2110,8 +2285,3 @@
 they should be stored in a location that is encrypted. Users can also opt in
 these cases to not save their user credentials in the config file and will
 instead be prompted when they are needed.
-
-## Authors
-
-* pneubeck@chromium.org
-* stevenjb@chromium.org
diff --git a/components/optimization_guide/content/renderer/page_text_agent.cc b/components/optimization_guide/content/renderer/page_text_agent.cc
index bcbc147f..dd3025da 100644
--- a/components/optimization_guide/content/renderer/page_text_agent.cc
+++ b/components/optimization_guide/content/renderer/page_text_agent.cc
@@ -29,7 +29,8 @@
 
 }  // namespace
 
-PageTextAgent::PageTextAgent(content::RenderFrame* frame) {
+PageTextAgent::PageTextAgent(content::RenderFrame* frame)
+    : content::RenderFrameObserverTracker<PageTextAgent>(frame) {
   if (!frame) {
     // For unittesting.
     return;
diff --git a/components/optimization_guide/content/renderer/page_text_agent.h b/components/optimization_guide/content/renderer/page_text_agent.h
index 5ff3071e..250aa9d 100644
--- a/components/optimization_guide/content/renderer/page_text_agent.h
+++ b/components/optimization_guide/content/renderer/page_text_agent.h
@@ -14,6 +14,7 @@
 #include "base/optional.h"
 #include "base/strings/string16.h"
 #include "components/optimization_guide/content/mojom/page_text_service.mojom.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/web/web_meaningful_layout.h"
@@ -28,7 +29,9 @@
 // mojom::PageTextService. It currently supports requesting and getting text
 // dumps during |content::RenderFrameObserver::DidMeaningfulLayout|, but more
 // events will be added in the future.
-class PageTextAgent : public mojom::PageTextService {
+class PageTextAgent
+    : public mojom::PageTextService,
+      public content::RenderFrameObserverTracker<PageTextAgent> {
  public:
   explicit PageTextAgent(content::RenderFrame* frame);
   ~PageTextAgent() override;
@@ -41,6 +44,9 @@
   MaybeRequestTextDumpOnLayoutEvent(blink::WebMeaningfulLayout event,
                                     uint64_t* max_size);
 
+  // Bind to mojo pipes. Public for testing.
+  void Bind(mojo::PendingAssociatedReceiver<mojom::PageTextService> receiver);
+
   // mojom::PageTextService:
   void RequestPageTextDump(
       mojom::PageTextDumpRequestPtr request,
@@ -50,9 +56,6 @@
   PageTextAgent& operator=(const PageTextAgent&) = delete;
 
  private:
-  // Bind to mojo pipes.
-  void Bind(mojo::PendingAssociatedReceiver<mojom::PageTextService> receiver);
-
   // Called when the text dump is done and it can be sent to |consumer|.
   void OnPageTextDump(mojo::PendingRemote<mojom::PageTextConsumer> consumer,
                       const base::string16& content);
diff --git a/components/payments/content/BUILD.gn b/components/payments/content/BUILD.gn
index e111e59..fa171bb 100644
--- a/components/payments/content/BUILD.gn
+++ b/components/payments/content/BUILD.gn
@@ -28,6 +28,9 @@
     "payment_app_service.h",
     "payment_app_service_factory.cc",
     "payment_app_service_factory.h",
+    "payment_credential_enrollment_model.cc",
+    "payment_credential_enrollment_model.h",
+    "payment_credential_enrollment_view.h",
     "payment_details_converter.cc",
     "payment_details_converter.h",
     "payment_event_response_util.cc",
@@ -47,6 +50,8 @@
     "secure_payment_confirmation_app.h",
     "secure_payment_confirmation_app_factory.cc",
     "secure_payment_confirmation_app_factory.h",
+    "secure_payment_confirmation_model.cc",
+    "secure_payment_confirmation_model.h",
     "secure_payment_confirmation_view.h",
     "service_worker_payment_app.cc",
     "service_worker_payment_app.h",
@@ -94,13 +99,18 @@
   }
 
   if (is_android) {
-    sources += [ "secure_payment_confirmation_view_stub.cc" ]
+    sources += [
+      "payment_credential_enrollment_view_stub.cc",
+      "secure_payment_confirmation_view_stub.cc",
+    ]
   } else {
     sources += [
       "content_payment_request_delegate.cc",
       "content_payment_request_delegate.h",
       "payment_credential.cc",
       "payment_credential.h",
+      "payment_credential_enrollment_controller.cc",
+      "payment_credential_enrollment_controller.h",
       "payment_request.cc",
       "payment_request.h",
       "payment_request_dialog.h",
@@ -114,8 +124,6 @@
       "payment_response_helper.h",
       "secure_payment_confirmation_controller.cc",
       "secure_payment_confirmation_controller.h",
-      "secure_payment_confirmation_model.cc",
-      "secure_payment_confirmation_model.h",
     ]
   }
 }
@@ -187,7 +195,9 @@
     "android_app_communication_unittest.cc",
     "android_payment_app_factory_unittest.cc",
     "android_payment_app_unittest.cc",
+    "payment_credential_enrollment_model_unittest.cc",
     "payment_method_manifest_table_unittest.cc",
+    "secure_payment_confirmation_model_unittest.cc",
     "service_worker_payment_app_finder_unittest.cc",
     "web_app_manifest_section_table_unittest.cc",
   ]
@@ -200,7 +210,6 @@
       "payment_request_state_unittest.cc",
       "payment_response_helper_unittest.cc",
       "secure_payment_confirmation_app_unittest.cc",
-      "secure_payment_confirmation_model_unittest.cc",
       "service_worker_payment_app_unittest.cc",
       "test_content_payment_request_delegate.cc",
       "test_content_payment_request_delegate.h",
diff --git a/components/payments/content/payment_credential_enrollment_controller.cc b/components/payments/content/payment_credential_enrollment_controller.cc
new file mode 100644
index 0000000..c0d97d5
--- /dev/null
+++ b/components/payments/content/payment_credential_enrollment_controller.cc
@@ -0,0 +1,84 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/payments/content/payment_credential_enrollment_controller.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
+#include "build/build_config.h"
+#include "components/payments/content/payment_credential_enrollment_model.h"
+
+namespace payments {
+
+PaymentCredentialEnrollmentController::PaymentCredentialEnrollmentController(
+    content::WebContents* web_contents,
+    AcceptCallback accept_callback,
+    CancelCallback cancel_callback)
+    : content::WebContentsObserver(web_contents),
+      accept_callback_(std::move(accept_callback)),
+      cancel_callback_(std::move(cancel_callback)) {}
+
+PaymentCredentialEnrollmentController::
+    ~PaymentCredentialEnrollmentController() = default;
+
+void PaymentCredentialEnrollmentController::ShowDialog() {
+#if defined(OS_ANDROID)
+  NOTREACHED();
+#endif  // OS_ANDROID
+  DCHECK(!view_);
+
+  model_.set_progress_bar_visible(false);
+
+  // TODO(crbug.com/1176368): Set dialog strings on the model.
+
+  view_ =
+      PaymentCredentialEnrollmentView::Create(/*payment_ui_observer=*/nullptr);
+  view_->ShowDialog(
+      web_contents(), model_.GetWeakPtr(),
+      base::BindOnce(&PaymentCredentialEnrollmentController::OnConfirm,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&PaymentCredentialEnrollmentController::OnCancel,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void PaymentCredentialEnrollmentController::ShowProcessingSpinner() {
+  if (!view_)
+    return;
+
+  model_.set_progress_bar_visible(true);
+  model_.set_accept_button_enabled(false);
+  model_.set_cancel_button_enabled(false);
+  view_->OnModelUpdated();
+}
+
+void PaymentCredentialEnrollmentController::CloseDialog() {
+  if (view_)
+    view_->HideDialog();
+}
+
+void PaymentCredentialEnrollmentController::OnCancel() {
+  CloseDialog();
+
+  std::move(cancel_callback_).Run();
+}
+
+void PaymentCredentialEnrollmentController::OnConfirm() {
+  DCHECK(web_contents());
+
+  ShowProcessingSpinner();
+
+  // This will trigger WebAuthn with OS-level UI (if any) on top of the |view_|
+  // with its animated processing spinner. For example, on Linux, there's no
+  // OS-level UI, while on MacOS, there's an OS-level prompt for the Touch ID
+  // that shows on top of Chrome.
+  std::move(accept_callback_).Run();
+}
+
+base::WeakPtr<PaymentCredentialEnrollmentController>
+PaymentCredentialEnrollmentController::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+}  // namespace payments
diff --git a/components/payments/content/payment_credential_enrollment_controller.h b/components/payments/content/payment_credential_enrollment_controller.h
new file mode 100644
index 0000000..7d230ab
--- /dev/null
+++ b/components/payments/content/payment_credential_enrollment_controller.h
@@ -0,0 +1,68 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_ENROLLMENT_CONTROLLER_H_
+#define COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_ENROLLMENT_CONTROLLER_H_
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "components/payments/content/payment_credential_enrollment_model.h"
+#include "components/payments/content/payment_credential_enrollment_view.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace payments {
+
+// Controls the user interface in the secure payment confirmation flow.
+class PaymentCredentialEnrollmentController
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<
+          PaymentCredentialEnrollmentController> {
+ public:
+  using AcceptCallback = base::OnceCallback<void()>;
+  using CancelCallback = base::OnceCallback<void()>;
+
+  PaymentCredentialEnrollmentController(content::WebContents* web_contents,
+                                        AcceptCallback accept_callback,
+                                        CancelCallback cancel_callback);
+  ~PaymentCredentialEnrollmentController() override;
+
+  PaymentCredentialEnrollmentController(
+      const PaymentCredentialEnrollmentController& other) = delete;
+  PaymentCredentialEnrollmentController& operator=(
+      const PaymentCredentialEnrollmentController& other) = delete;
+
+  void ShowDialog();
+  void CloseDialog();
+  void ShowProcessingSpinner();
+
+  // Dialog callbacks.
+  void OnCancel();
+  void OnConfirm();
+
+  base::WeakPtr<PaymentCredentialEnrollmentController> GetWeakPtr();
+
+ private:
+  friend class content::WebContentsUserData<
+      PaymentCredentialEnrollmentController>;
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+  AcceptCallback accept_callback_;
+  CancelCallback cancel_callback_;
+
+  PaymentCredentialEnrollmentModel model_;
+
+  // On desktop, the PaymentCredentialEnrollmentView object is memory managed by
+  // the views:: machinery. It is deleted when the window is closed and
+  // views::DialogDelegateView::DeleteDelegate() is called by its corresponding
+  // views::Widget.
+  base::WeakPtr<PaymentCredentialEnrollmentView> view_;
+
+  base::WeakPtrFactory<PaymentCredentialEnrollmentController> weak_ptr_factory_{
+      this};
+};
+
+}  // namespace payments
+
+#endif  // COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_ENROLLMENT_CONTROLLER_H_
diff --git a/components/payments/content/payment_credential_enrollment_model.cc b/components/payments/content/payment_credential_enrollment_model.cc
new file mode 100644
index 0000000..e83c6f3
--- /dev/null
+++ b/components/payments/content/payment_credential_enrollment_model.cc
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/payments/content/payment_credential_enrollment_model.h"
+
+namespace payments {
+
+PaymentCredentialEnrollmentModel::PaymentCredentialEnrollmentModel() = default;
+
+PaymentCredentialEnrollmentModel::~PaymentCredentialEnrollmentModel() = default;
+
+base::WeakPtr<PaymentCredentialEnrollmentModel>
+PaymentCredentialEnrollmentModel::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+}  // namespace payments
diff --git a/components/payments/content/payment_credential_enrollment_model.h b/components/payments/content/payment_credential_enrollment_model.h
new file mode 100644
index 0000000..ac4493b
--- /dev/null
+++ b/components/payments/content/payment_credential_enrollment_model.h
@@ -0,0 +1,104 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_ENROLLMENT_MODEL_H_
+#define COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_ENROLLMENT_MODEL_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+
+namespace payments {
+
+// The data model for PaymentCredentialEnrollmentView. Owned by the
+// PaymentCredentialEnrollmentController.
+class PaymentCredentialEnrollmentModel {
+ public:
+  PaymentCredentialEnrollmentModel();
+  ~PaymentCredentialEnrollmentModel();
+
+  PaymentCredentialEnrollmentModel(
+      const PaymentCredentialEnrollmentModel& other) = delete;
+  PaymentCredentialEnrollmentModel& operator=(
+      const PaymentCredentialEnrollmentModel& other) = delete;
+
+  // Title, e.g. "Faster card verification in Chrome using Touch ID"
+  const base::string16& title() const { return title_; }
+  void set_title(const base::string16& title) { title_ = title; }
+
+  // Description.
+  const base::string16& description() const { return description_; }
+  void set_description(const base::string16& description) {
+    description_ = description;
+  }
+
+  // Label for the accept button, e.g. "Use Touch ID".
+  const base::string16& accept_button_label() const {
+    return accept_button_label_;
+  }
+  void set_accept_button_label(const base::string16& accept_button_label) {
+    accept_button_label_ = accept_button_label;
+  }
+
+  // Label for the cancel button, e.g. "Cancel".
+  const base::string16& cancel_button_label() const {
+    return cancel_button_label_;
+  }
+  void set_cancel_button_label(const base::string16& cancel_button_label) {
+    cancel_button_label_ = cancel_button_label;
+  }
+
+  // Progress bar visibility.
+  bool progress_bar_visible() const { return progress_bar_visible_; }
+  void set_progress_bar_visible(bool progress_bar_visible) {
+    progress_bar_visible_ = progress_bar_visible;
+  }
+
+  // Accept button enabled state.
+  bool accept_button_enabled() const { return accept_button_enabled_; }
+  void set_accept_button_enabled(bool accept_button_enabled) {
+    accept_button_enabled_ = accept_button_enabled;
+  }
+
+  // Accept button visibility.
+  bool accept_button_visible() const { return accept_button_visible_; }
+  void set_accept_button_visible(bool accept_button_visible) {
+    accept_button_visible_ = accept_button_visible;
+  }
+
+  // Cancel button enabled state.
+  bool cancel_button_enabled() const { return cancel_button_enabled_; }
+  void set_cancel_button_enabled(bool cancel_button_enabled) {
+    cancel_button_enabled_ = cancel_button_enabled;
+  }
+
+  // Cancel button visibility.
+  bool cancel_button_visible() const { return cancel_button_visible_; }
+  void set_cancel_button_visible(bool cancel_button_visible) {
+    cancel_button_visible_ = cancel_button_visible;
+  }
+
+  base::WeakPtr<PaymentCredentialEnrollmentModel> GetWeakPtr();
+
+ private:
+  base::string16 title_;
+  base::string16 description_;
+
+  base::string16 accept_button_label_;
+  base::string16 cancel_button_label_;
+
+  bool progress_bar_visible_ = false;
+
+  bool accept_button_enabled_ = true;
+  bool accept_button_visible_ = true;
+
+  bool cancel_button_enabled_ = true;
+  bool cancel_button_visible_ = true;
+
+  base::WeakPtrFactory<PaymentCredentialEnrollmentModel> weak_ptr_factory_{
+      this};
+};
+
+}  // namespace payments
+
+#endif  // COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_ENROLLMENT_MODEL_H_
diff --git a/components/payments/content/payment_credential_enrollment_model_unittest.cc b/components/payments/content/payment_credential_enrollment_model_unittest.cc
new file mode 100644
index 0000000..eb37b7f
--- /dev/null
+++ b/components/payments/content/payment_credential_enrollment_model_unittest.cc
@@ -0,0 +1,56 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/payments/content/payment_credential_enrollment_model.h"
+
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace payments {
+
+TEST(PaymentCredentialEnrollmentModelTest, SmokeTest) {
+  PaymentCredentialEnrollmentModel model;
+
+  base::string16 title(
+      base::UTF8ToUTF16("Use Touch ID to verify and complete your purchase?"));
+  base::string16 description(base::UTF8ToUTF16(
+      "Save payment information to this device and skip bank verification next "
+      "time when you use Touch ID to verify your payment with Visa ••••4444."));
+  base::string16 accept_button_label(base::UTF8ToUTF16("Use Touch ID"));
+  base::string16 cancel_button_label(base::UTF8ToUTF16("No thanks"));
+
+  model.set_title(title);
+  EXPECT_EQ(title, model.title());
+
+  model.set_description(description);
+  EXPECT_EQ(description, model.description());
+
+  model.set_accept_button_label(accept_button_label);
+  EXPECT_EQ(accept_button_label, model.accept_button_label());
+
+  model.set_cancel_button_label(cancel_button_label);
+  EXPECT_EQ(cancel_button_label, model.cancel_button_label());
+
+  // Default values for visibility and enabled states
+  EXPECT_FALSE(model.progress_bar_visible());
+  EXPECT_TRUE(model.accept_button_enabled());
+  EXPECT_TRUE(model.accept_button_visible());
+  EXPECT_TRUE(model.cancel_button_enabled());
+  EXPECT_TRUE(model.cancel_button_visible());
+
+  model.set_progress_bar_visible(true);
+  model.set_accept_button_enabled(false);
+  model.set_accept_button_visible(false);
+  model.set_cancel_button_enabled(false);
+  model.set_cancel_button_visible(false);
+
+  EXPECT_TRUE(model.progress_bar_visible());
+  EXPECT_FALSE(model.accept_button_enabled());
+  EXPECT_FALSE(model.accept_button_visible());
+  EXPECT_FALSE(model.cancel_button_enabled());
+  EXPECT_FALSE(model.cancel_button_visible());
+}
+
+}  // namespace payments
diff --git a/components/payments/content/payment_credential_enrollment_view.h b/components/payments/content/payment_credential_enrollment_view.h
new file mode 100644
index 0000000..ee966a1
--- /dev/null
+++ b/components/payments/content/payment_credential_enrollment_view.h
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_ENROLLMENT_VIEW_H_
+#define COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_ENROLLMENT_VIEW_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace payments {
+
+class PaymentCredentialEnrollmentModel;
+class PaymentUIObserver;
+
+// Draws the user interface in the payment credential enrollment flow. Owned by
+// the SecurePaymentConfirmationController.
+class PaymentCredentialEnrollmentView {
+ public:
+  using AcceptCallback = base::OnceCallback<void()>;
+  using CancelCallback = base::OnceCallback<void()>;
+
+  static base::WeakPtr<PaymentCredentialEnrollmentView> Create(
+      const PaymentUIObserver* payment_ui_observer);
+
+  virtual ~PaymentCredentialEnrollmentView() = 0;
+
+  virtual void ShowDialog(content::WebContents* web_contents,
+                          base::WeakPtr<PaymentCredentialEnrollmentModel> model,
+                          AcceptCallback accept_callback,
+                          CancelCallback cancel_callback) = 0;
+  virtual void OnModelUpdated() = 0;
+  virtual void HideDialog() = 0;
+
+ protected:
+  PaymentCredentialEnrollmentView();
+
+  base::WeakPtr<PaymentCredentialEnrollmentModel> model_;
+};
+
+}  // namespace payments
+
+#endif  // COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_ENROLLMENT_VIEW_H_
diff --git a/components/payments/content/payment_credential_enrollment_view_stub.cc b/components/payments/content/payment_credential_enrollment_view_stub.cc
new file mode 100644
index 0000000..39234f9
--- /dev/null
+++ b/components/payments/content/payment_credential_enrollment_view_stub.cc
@@ -0,0 +1,19 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/payments/content/payment_credential_enrollment_view.h"
+
+namespace payments {
+
+// static
+base::WeakPtr<PaymentCredentialEnrollmentView>
+PaymentCredentialEnrollmentView::Create(
+    const PaymentUIObserver* payment_ui_observer) {
+  return nullptr;
+}
+
+PaymentCredentialEnrollmentView::PaymentCredentialEnrollmentView() = default;
+PaymentCredentialEnrollmentView::~PaymentCredentialEnrollmentView() = default;
+
+}  // namespace payments
diff --git a/components/performance_manager/v8_memory/web_memory_aggregator_unittest.cc b/components/performance_manager/v8_memory/web_memory_aggregator_unittest.cc
index cf917b4..6dfe4338 100644
--- a/components/performance_manager/v8_memory/web_memory_aggregator_unittest.cc
+++ b/components/performance_manager/v8_memory/web_memory_aggregator_unittest.cc
@@ -73,11 +73,8 @@
   return expected_measurement;
 }
 
-// Abuse Mojo's trace integration to serialize a measurement to sorted JSON for
-// string comparison. This gives failure messages that include the full
-// measurement in JSON format and is easier than comparing every field of
-// nested Mojo messages individually.
-std::string MeasurementToJSON(
+// Clone and sort the measurement for easier comparison.
+mojom::WebMemoryMeasurementPtr NormalizeMeasurement(
     const mojom::WebMemoryMeasurementPtr& measurement) {
   // Sort all arrays.
   auto canonical_measurement = measurement->Clone();
@@ -88,10 +85,7 @@
   std::sort(canonical_measurement->breakdown.begin(),
             canonical_measurement->breakdown.end());
 
-  // Convert to JSON string.
-  base::trace_event::TracedValueJSON json_value;
-  canonical_measurement->AsValueInto(&json_value);
-  return json_value.ToJSON();
+  return canonical_measurement;
 }
 
 }  // namespace
@@ -169,8 +163,8 @@
                                 AttributionScope::kWindow,
                                 /*expected_url=*/"", attribute, attribute),
     });
-    EXPECT_EQ(MeasurementToJSON(measurement),
-              MeasurementToJSON(expected_result));
+    EXPECT_EQ(NormalizeMeasurement(measurement),
+              NormalizeMeasurement(expected_result));
   }
 }
 
@@ -184,7 +178,8 @@
   });
   WebMemoryAggregator aggregator(main_frame);
   auto result = aggregator.AggregateMeasureMemoryResult();
-  EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+  EXPECT_EQ(NormalizeMeasurement(result),
+            NormalizeMeasurement(expected_result));
 }
 
 TEST_F(WebMemoryAggregatorTest, AggregateSingleSiteMultiFrame) {
@@ -203,7 +198,8 @@
                               "redirect.html?target=iframe.html"),
   });
   auto result = aggregator.AggregateMeasureMemoryResult();
-  EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+  EXPECT_EQ(NormalizeMeasurement(result),
+            NormalizeMeasurement(expected_result));
 }
 
 TEST_F(WebMemoryAggregatorTest, AggregateCrossOrigin) {
@@ -241,7 +237,8 @@
                               "https://foo.com/iframe1"),
   });
   auto result = aggregator.AggregateMeasureMemoryResult();
-  EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+  EXPECT_EQ(NormalizeMeasurement(result),
+            NormalizeMeasurement(expected_result));
   worker->RemoveClientFrame(child_frame);
 }
 
@@ -324,7 +321,8 @@
                               "https://example.com/empty_frame"),
   });
   auto result = aggregator.AggregateMeasureMemoryResult();
-  EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+  EXPECT_EQ(NormalizeMeasurement(result),
+            NormalizeMeasurement(expected_result));
 }
 
 TEST_F(WebMemoryAggregatorTest, AggregateSameOriginAboutBlank) {
@@ -338,7 +336,8 @@
   });
   WebMemoryAggregator aggregator(main_frame);
   auto result = aggregator.AggregateMeasureMemoryResult();
-  EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+  EXPECT_EQ(NormalizeMeasurement(result),
+            NormalizeMeasurement(expected_result));
 }
 
 TEST_F(WebMemoryAggregatorTest, SkipCrossOriginAboutBlank) {
@@ -355,7 +354,8 @@
   });
   WebMemoryAggregator aggregator(main_frame);
   auto result = aggregator.AggregateMeasureMemoryResult();
-  EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+  EXPECT_EQ(NormalizeMeasurement(result),
+            NormalizeMeasurement(expected_result));
 }
 
 TEST_F(WebMemoryAggregatorTest, AggregateWindowOpener) {
@@ -391,7 +391,8 @@
                               base::nullopt, "example-id3"),
   });
   auto result = aggregator.AggregateMeasureMemoryResult();
-  EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+  EXPECT_EQ(NormalizeMeasurement(result),
+            NormalizeMeasurement(expected_result));
 
   {
     WebMemoryAggregator aggregator(cross_site_child);
@@ -404,8 +405,8 @@
             base::nullopt),
     });
     auto cross_site_result = aggregator.AggregateMeasureMemoryResult();
-    EXPECT_EQ(MeasurementToJSON(cross_site_result),
-              MeasurementToJSON(expected_cross_site_result));
+    EXPECT_EQ(NormalizeMeasurement(cross_site_result),
+              NormalizeMeasurement(expected_cross_site_result));
   }
 
   {
@@ -417,8 +418,8 @@
                                 base::nullopt, base::nullopt),
     });
     auto cross_site_result = aggregator.AggregateMeasureMemoryResult();
-    EXPECT_EQ(MeasurementToJSON(cross_site_result),
-              MeasurementToJSON(expected_cross_site_result));
+    EXPECT_EQ(NormalizeMeasurement(cross_site_result),
+              NormalizeMeasurement(expected_cross_site_result));
   }
 }
 
@@ -437,7 +438,8 @@
                               "https://example.com/"),
   });
   auto result = aggregator.AggregateMeasureMemoryResult();
-  EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+  EXPECT_EQ(NormalizeMeasurement(result),
+            NormalizeMeasurement(expected_result));
 }
 
 TEST_F(WebMemoryAggregatorTest, AggregateSameOriginWorker) {
@@ -464,7 +466,8 @@
                               "https://example.com/worker2", "example-id"),
   });
   auto result = aggregator.AggregateMeasureMemoryResult();
-  EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+  EXPECT_EQ(NormalizeMeasurement(result),
+            NormalizeMeasurement(expected_result));
   worker2->RemoveClientWorker(worker1);
   worker1->RemoveClientFrame(child_frame);
 }
@@ -489,7 +492,8 @@
                               base::nullopt, "example-id"),
   });
   auto result = aggregator.AggregateMeasureMemoryResult();
-  EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+  EXPECT_EQ(NormalizeMeasurement(result),
+            NormalizeMeasurement(expected_result));
   worker2->RemoveClientWorker(worker1);
   worker1->RemoveClientFrame(child_frame);
 }
@@ -531,7 +535,8 @@
                                 "https://a.com/popup2"),
     });
     auto result = aggregator.AggregateMeasureMemoryResult();
-    EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+    EXPECT_EQ(NormalizeMeasurement(result),
+              NormalizeMeasurement(expected_result));
   }
 
   {
@@ -551,7 +556,8 @@
                                 base::nullopt, "c_com_iframe2"),
     });
     auto result = aggregator.AggregateMeasureMemoryResult();
-    EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+    EXPECT_EQ(NormalizeMeasurement(result),
+              NormalizeMeasurement(expected_result));
   }
 
   {
@@ -565,7 +571,8 @@
                                 "https://c.com/iframe2"),
     });
     auto result = aggregator.AggregateMeasureMemoryResult();
-    EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+    EXPECT_EQ(NormalizeMeasurement(result),
+              NormalizeMeasurement(expected_result));
   }
 }
 
@@ -582,7 +589,8 @@
                                 base::nullopt, "b_com_iframe"),
     });
     auto result = aggregator.AggregateMeasureMemoryResult();
-    EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+    EXPECT_EQ(NormalizeMeasurement(result),
+              NormalizeMeasurement(expected_result));
   }
 
   {
@@ -594,7 +602,8 @@
                                 "https://b.com/iframe"),
     });
     auto result = aggregator.AggregateMeasureMemoryResult();
-    EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
+    EXPECT_EQ(NormalizeMeasurement(result),
+              NormalizeMeasurement(expected_result));
   }
 }
 
diff --git a/components/policy/core/browser/android/policy_cache_updater_android.cc b/components/policy/core/browser/android/policy_cache_updater_android.cc
index 4e87b34..e5e13bc 100644
--- a/components/policy/core/browser/android/policy_cache_updater_android.cc
+++ b/components/policy/core/browser/android/policy_cache_updater_android.cc
@@ -32,6 +32,8 @@
     const ConfigurationPolicyHandlerList* handler_list)
     : policy_service_(policy_service), handler_list_(handler_list) {
   policy_service_->AddObserver(POLICY_DOMAIN_CHROME, this);
+  UpdateCache(
+      policy_service->GetPolicies(PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
 }
 
 PolicyCacheUpdater::~PolicyCacheUpdater() {
@@ -41,11 +43,16 @@
 void PolicyCacheUpdater::OnPolicyUpdated(const PolicyNamespace& ns,
                                          const PolicyMap& previous,
                                          const PolicyMap& current) {
+  UpdateCache(current);
+}
+
+void PolicyCacheUpdater::UpdateCache(const PolicyMap& current_policy_map) {
   PolicyMap policy_map;
-  policy_map.CopyFrom(current);
+  policy_map.CopyFrom(current_policy_map);
   PolicyErrorMap errors;
   std::set<std::string> future_policies;
-  handler_list_->ApplyPolicySettings(policy_map, /*prefs=*/nullptr, &errors,
+  handler_list_->ApplyPolicySettings(policy_map,
+                                     /*prefs=*/nullptr, &errors,
                                      /*deprecated_policies*/ nullptr,
                                      &future_policies);
   policy_map.EraseNonmatching(
diff --git a/components/policy/core/browser/android/policy_cache_updater_android.h b/components/policy/core/browser/android/policy_cache_updater_android.h
index 147951f..466d731 100644
--- a/components/policy/core/browser/android/policy_cache_updater_android.h
+++ b/components/policy/core/browser/android/policy_cache_updater_android.h
@@ -28,6 +28,8 @@
                        const PolicyMap& current) override;
 
  private:
+  void UpdateCache(const PolicyMap& current_policy_map);
+
   PolicyService* policy_service_;
   const ConfigurationPolicyHandlerList* handler_list_;
 };
diff --git a/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc b/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc
index a210f6c9..83d86c0c 100644
--- a/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc
+++ b/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc
@@ -179,5 +179,16 @@
   VerifyPolicyName(kPolicyName, /*has_value=*/true, kPolicyValue);
 }
 
+TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicUpdatedBeforeUpdaterCreated) {
+  policy_handler_list()->AddHandler(
+      std::make_unique<StubPolicyHandler>(kPolicyName, /*has_error=*/false));
+
+  SetPolicy(kPolicyName, kPolicyValue);
+  UpdatePolicy();
+  VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+  PolicyCacheUpdater updater(policy_service(), policy_handler_list());
+  VerifyPolicyName(kPolicyName, /*has_value=*/true, kPolicyValue);
+}
+
 }  // namespace android
 }  // namespace policy
diff --git a/components/power_scheduler/power_mode_voter.cc b/components/power_scheduler/power_mode_voter.cc
index 0a2a2c1e..cb9a164 100644
--- a/components/power_scheduler/power_mode_voter.cc
+++ b/components/power_scheduler/power_mode_voter.cc
@@ -13,6 +13,8 @@
 // static
 constexpr base::TimeDelta PowerModeVoter::kResponseTimeout;
 // static
+constexpr base::TimeDelta PowerModeVoter::kAnimationTimeout;
+// static
 constexpr base::TimeDelta PowerModeVoter::kLoadingTimeout;
 
 PowerModeVoter::~PowerModeVoter() {
diff --git a/components/power_scheduler/power_mode_voter.h b/components/power_scheduler/power_mode_voter.h
index 93461a5..f6c8792 100644
--- a/components/power_scheduler/power_mode_voter.h
+++ b/components/power_scheduler/power_mode_voter.h
@@ -31,6 +31,12 @@
   static constexpr base::TimeDelta kResponseTimeout =
       base::TimeDelta::FromMilliseconds(100);
 
+  // Animations often have brief idle periods where no frames are produced. This
+  // timeout is applied before resetting animation votes to avoid frequent vote
+  // reversals.
+  static constexpr base::TimeDelta kAnimationTimeout =
+      base::TimeDelta::FromMilliseconds(50);
+
   // Avoid getting stuck in loading stage forever. More than 99.9% of
   // navigations load (to largest contentful paint) in less than a minute.
   static constexpr base::TimeDelta kLoadingTimeout =
diff --git a/components/shared_highlighting/core/common/text_fragments_constants.cc b/components/shared_highlighting/core/common/text_fragments_constants.cc
index a088669..77ae680 100644
--- a/components/shared_highlighting/core/common/text_fragments_constants.cc
+++ b/components/shared_highlighting/core/common/text_fragments_constants.cc
@@ -16,6 +16,9 @@
 const char kFragmentSuffixKey[] = "suffix";
 
 // Light purple.
-const int kFragmentTextBackgroundColor = 0xFFE9D2FD;
+const int kFragmentTextBackgroundColorARGB = 0xFFE9D2FD;
+
+// Black.
+const int kFragmentTextForegroundColorARGB = 0xFF000000;
 
 }  // namespace shared_highlighting
diff --git a/components/shared_highlighting/core/common/text_fragments_constants.h b/components/shared_highlighting/core/common/text_fragments_constants.h
index b2de522..4f594c1 100644
--- a/components/shared_highlighting/core/common/text_fragments_constants.h
+++ b/components/shared_highlighting/core/common/text_fragments_constants.h
@@ -21,7 +21,10 @@
 extern const char kFragmentSuffixKey[];
 
 // Default highlight color stored as a hexadecimal number.
-extern const int kFragmentTextBackgroundColor;
+extern const int kFragmentTextBackgroundColorARGB;
+
+// Default text color stored as a hexadecimal number.
+extern const int kFragmentTextForegroundColorARGB;
 
 }  // namespace shared_highlighting
 
diff --git a/components/signin/public/base/account_consistency_method.cc b/components/signin/public/base/account_consistency_method.cc
index 403f237..f252bbb1b 100644
--- a/components/signin/public/base/account_consistency_method.cc
+++ b/components/signin/public/base/account_consistency_method.cc
@@ -23,4 +23,10 @@
     "MobileIdentityConsistency", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif
 
+#if defined(OS_ANDROID) || defined(OS_IOS)
+bool IsMobileIdentityConsistencyEnabled() {
+  return base::FeatureList::IsEnabled(kMobileIdentityConsistency);
+}
+#endif
+
 }  // namespace signin
diff --git a/components/signin/public/base/account_consistency_method.h b/components/signin/public/base/account_consistency_method.h
index 8a4d6e6..ecf120ac 100644
--- a/components/signin/public/base/account_consistency_method.h
+++ b/components/signin/public/base/account_consistency_method.h
@@ -21,6 +21,9 @@
 // This feature flag is used to run experiments of different variations
 // of MICE on Android.
 extern const base::Feature kMobileIdentityConsistencyVar;
+// Returns true if the flag |kMobileIdentityConsistency| is enabled for the
+// platform.
+bool IsMobileIdentityConsistencyEnabled();
 #endif
 
 enum class AccountConsistencyMethod : int {
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 85a1800..8985715 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -210,6 +210,7 @@
     "//cc/base",
     "//cc/paint",
     "//components/crash/core/common:crash_key",
+    "//components/power_scheduler",
     "//gpu/command_buffer/client:gles2_cmd_helper",
     "//gpu/command_buffer/client:gles2_implementation",
     "//gpu/command_buffer/client:raster",
diff --git a/components/viz/service/DEPS b/components/viz/service/DEPS
index fb16a09..00c1c1c 100644
--- a/components/viz/service/DEPS
+++ b/components/viz/service/DEPS
@@ -2,6 +2,7 @@
 
 include_rules = [
   "+cc",
+  "+components/power_scheduler",
   "-components/viz/common/features.h",
   "-components/viz/common/switches.h",
   "-components/viz/service",
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
index 8112c0ba..8d8f46d9 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -13,6 +13,9 @@
 #include "base/stl_util.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "components/power_scheduler/power_mode.h"
+#include "components/power_scheduler/power_mode_arbiter.h"
+#include "components/power_scheduler/power_mode_voter.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/quads/compositor_frame.h"
 #include "components/viz/common/resources/bitmap_allocation.h"
@@ -59,7 +62,10 @@
       frame_sink_id_(frame_sink_id),
       surface_resource_holder_(this),
       is_root_(is_root),
-      allow_copy_output_requests_(is_root) {
+      allow_copy_output_requests_(is_root),
+      animation_power_mode_voter_(
+          power_scheduler::PowerModeArbiter::GetInstance()->NewVoter(
+              "PowerModeVoter.Animation")) {
   // This may result in SetBeginFrameSource() being called.
   frame_sink_manager_->RegisterCompositorFrameSinkSupport(frame_sink_id_, this);
 }
@@ -755,10 +761,15 @@
     return;
 
   added_frame_observer_ = needs_begin_frame;
-  if (needs_begin_frame)
+  if (needs_begin_frame) {
     begin_frame_source_->AddObserver(this);
-  else
+    animation_power_mode_voter_->VoteFor(
+        power_scheduler::PowerMode::kAnimation);
+  } else {
     begin_frame_source_->RemoveObserver(this);
+    animation_power_mode_voter_->ResetVoteAfterTimeout(
+        power_scheduler::PowerModeVoter::kAnimationTimeout);
+  }
 }
 
 void CompositorFrameSinkSupport::AttachCaptureClient(
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
index 1bde6ba..d2c0bdd 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -15,6 +15,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/time/time.h"
+#include "components/power_scheduler/power_mode_voter.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/frame_timing_details_map.h"
 #include "components/viz/common/quads/compositor_frame.h"
@@ -362,6 +363,8 @@
   // single-page-app transitions.
   SurfaceAnimationManager surface_animation_manager_;
 
+  std::unique_ptr<power_scheduler::PowerModeVoter> animation_power_mode_voter_;
+
   base::WeakPtrFactory<CompositorFrameSinkSupport> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(CompositorFrameSinkSupport);
diff --git a/components/wifi/wifi_test.cc b/components/wifi/wifi_test.cc
index 396122d4..8bb0ac1 100644
--- a/components/wifi/wifi_test.cc
+++ b/components/wifi/wifi_test.cc
@@ -124,7 +124,7 @@
 
   if (parsed_command_line.GetArgs().size() == 1) {
 #if defined(OS_WIN)
-    network_guid = base::UTF16ToASCII(parsed_command_line.GetArgs()[0]);
+    network_guid = base::WideToASCII(parsed_command_line.GetArgs()[0]);
 #else
     network_guid = parsed_command_line.GetArgs()[0];
 #endif
diff --git a/content/browser/loader/cross_site_document_blocking_browsertest.cc b/content/browser/loader/cross_site_document_blocking_browsertest.cc
index 2c0fa39..a574d77 100644
--- a/content/browser/loader/cross_site_document_blocking_browsertest.cc
+++ b/content/browser/loader/cross_site_document_blocking_browsertest.cc
@@ -1412,13 +1412,23 @@
   EXPECT_EQ("<p>contents of the response</p>", response_body);
 }
 
+// Flaky on Mac.  https://crbug.com/1177286
+#if defined(OS_MAC)
+#define MAYBE_WithCORBProtectionSniffing DISABLED_WithCORBProtectionSniffing
+#define MAYBE_WithoutCORBProtectionSniffing \
+  DISABLED_WithoutCORBProtectionSniffing
+#else
+#define MAYBE_WithCORBProtectionSniffing WithCORBProtectionSniffing
+#define MAYBE_WithoutCORBProtectionSniffing WithoutCORBProtectionSniffing
+#endif
+
 INSTANTIATE_TEST_SUITE_P(
-    WithCORBProtectionSniffing,
+    MAYBE_WithCORBProtectionSniffing,
     CrossSiteDocumentBlockingTest,
     ::testing::Values(TestMode::kWithCORBProtectionSniffing));
 
 INSTANTIATE_TEST_SUITE_P(
-    WithoutCORBProtectionSniffing,
+    MAYBE_WithoutCORBProtectionSniffing,
     CrossSiteDocumentBlockingTest,
     ::testing::Values(TestMode::kWithoutCORBProtectionSniffing));
 
diff --git a/content/browser/prerender/prerender_browsertest.cc b/content/browser/prerender/prerender_browsertest.cc
index cf66e76..fc91ed3 100644
--- a/content/browser/prerender/prerender_browsertest.cc
+++ b/content/browser/prerender/prerender_browsertest.cc
@@ -2,7 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/callback_helpers.h"
 #include "base/macros.h"
+#include "base/optional.h"
+#include "base/run_loop.h"
 #include "base/synchronization/lock.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/thread_annotations.h"
@@ -29,14 +32,43 @@
 #include "net/test/embedded_test_server/http_request.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/browser_interface_broker.mojom.h"
+#include "url/gurl.h"
 
 namespace content {
 namespace {
 
-class PrerenderBrowserTest : public ContentBrowserTest {
+// TODO(https://crbug.com/1132746): There are two different ways of prerendering
+// the page: a dedicated WebContents instance or using a separate FrameTree
+// instance (MPArch). The MPArch code is still in its very early stages but will
+// eventually completely replace the WebContents approach. In the meantime we
+// should try to get all test to pass with both implementations.
+enum PrerenderBrowserTestType {
+  kWebContents,
+  kMPArch,
+};
+
+std::string ToString(
+    const testing::TestParamInfo<PrerenderBrowserTestType>& info) {
+  switch (info.param) {
+    case PrerenderBrowserTestType::kWebContents:
+      return "WebContents";
+    case PrerenderBrowserTestType::kMPArch:
+      return "MPArch";
+  }
+}
+
+class PrerenderBrowserTest
+    : public ContentBrowserTest,
+      public testing::WithParamInterface<PrerenderBrowserTestType> {
  public:
   PrerenderBrowserTest() {
-    feature_list_.InitAndEnableFeature(blink::features::kPrerender2);
+    std::map<std::string, std::string> parameters;
+    if (IsMPArchActive()) {
+      parameters["implementation"] = "mparch";
+    }
+
+    feature_list_.InitAndEnableFeatureWithParameters(
+        blink::features::kPrerender2, parameters);
   }
   ~PrerenderBrowserTest() override = default;
 
@@ -71,24 +103,22 @@
     return *storage_partition->GetPrerenderHostRegistry();
   }
 
+  void WaitForPrerenderLoadCompletion(const GURL& prerendering_url) {
+    PrerenderHost* host =
+        GetPrerenderHostRegistry().WaitForHostByUrlForTesting(prerendering_url);
+    host->WaitForLoadStopForTesting();
+  }
+
   // Adds <link rel=prerender> in the current main frame and waits until the
   // completion of prerendering.
-  // `final_response_url` is the final response URL of prerendering. This is
-  // necessary when it is different from `prerendering_url` due to redirection.
-  void AddPrerender(const GURL& prerendering_url,
-                    const GURL& final_response_url = GURL()) {
+  void AddPrerender(const GURL& prerendering_url) {
     DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    GURL expected_url = prerendering_url;
-    if (final_response_url.is_valid())
-      expected_url = final_response_url;
 
-    // Start watching new web contents to be created for prerendering.
-    content::TestNavigationObserver observer(expected_url);
-    observer.StartWatchingNewWebContents();
     // Add the link tag that will prerender the URL.
     EXPECT_TRUE(ExecJs(shell()->web_contents(),
                        JsReplace("add_prerender($1)", prerendering_url)));
-    observer.Wait();
+
+    WaitForPrerenderLoadCompletion(prerendering_url);
   }
 
   // Navigates to the URL and waits until the completion of navigation.
@@ -135,6 +165,17 @@
     return request_count_by_path_[url.PathForRequest()];
   }
 
+  bool IsActivationDisabled() const { return IsMPArchActive(); }
+
+  bool IsMPArchActive() const {
+    switch (GetParam()) {
+      case kWebContents:
+        return false;
+      case kMPArch:
+        return true;
+    }
+  }
+
   void TestRenderFrameHostPrerenderingState(const GURL& prerender_url) {
     const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
 
@@ -193,7 +234,12 @@
   base::Lock lock_;
 };
 
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, LinkRelPrerender) {
+INSTANTIATE_TEST_SUITE_P(All,
+                         PrerenderBrowserTest,
+                         testing::Values(kWebContents, kMPArch),
+                         ToString);
+
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, LinkRelPrerender) {
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html");
 
@@ -216,11 +262,16 @@
   // The prerender host should be consumed.
   EXPECT_EQ(registry.FindHostByUrlForTesting(kPrerenderingUrl), nullptr);
 
-  // Activating the prerendered page should not issue a request.
-  EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
+  if (IsActivationDisabled()) {
+    // Activation is disabled. The navigation should issue a request again.
+    EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2);
+  } else {
+    // Activating the prerendered page should not issue a request.
+    EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
+  }
 }
 
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, LinkRelPrerender_Multiple) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, LinkRelPrerender_Multiple) {
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   const GURL kPrerenderingUrl1 = GetUrl("/empty.html?1");
   const GURL kPrerenderingUrl2 = GetUrl("/empty.html?2");
@@ -251,29 +302,29 @@
   EXPECT_EQ(registry.FindHostByUrlForTesting(kPrerenderingUrl1), nullptr);
   EXPECT_EQ(registry.FindHostByUrlForTesting(kPrerenderingUrl2), nullptr);
 
-  // Activating the prerendered page should not issue a request.
-  EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 1);
-  EXPECT_EQ(GetRequestCount(kPrerenderingUrl2), 1);
+  if (IsActivationDisabled()) {
+    // Activation is disabled. The navigation should issue a request again.
+    EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 1);
+    EXPECT_EQ(GetRequestCount(kPrerenderingUrl2), 2);
+  } else {
+    // Activating the prerendered page should not issue a request.
+    EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 1);
+    EXPECT_EQ(GetRequestCount(kPrerenderingUrl2), 1);
+  }
 }
 
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, LinkRelPrerender_Duplicate) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, LinkRelPrerender_Duplicate) {
   const GURL kInitialUrl = GetUrl("/prerender/duplicate_prerenders.html");
   const GURL kPrerenderingUrl1 = GetUrl("/empty.html?1");
   const GURL kPrerenderingUrl2 = GetUrl("/empty.html?2");
 
-  // Start watching new web contents to be created for prerendering.
-  content::TestNavigationObserver navigation_observer1(kPrerenderingUrl1);
-  content::TestNavigationObserver navigation_observer2(kPrerenderingUrl2);
-  navigation_observer1.StartWatchingNewWebContents();
-  navigation_observer2.StartWatchingNewWebContents();
-
   // Navigate to a page that initiates prerendering for `kPrerenderingUrl1`
   // twice. The second prerendering request should be ignored.
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
 
   // Wait until the completion of prerendering.
-  navigation_observer1.Wait();
-  navigation_observer2.Wait();
+  WaitForPrerenderLoadCompletion(kPrerenderingUrl1);
+  WaitForPrerenderLoadCompletion(kPrerenderingUrl2);
 
   // Requests should be issued once per prerendering URL.
   EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 1);
@@ -293,12 +344,18 @@
   EXPECT_EQ(registry.FindHostByUrlForTesting(kPrerenderingUrl1), nullptr);
   EXPECT_EQ(registry.FindHostByUrlForTesting(kPrerenderingUrl2), nullptr);
 
-  // Activating the prerendered page should not issue a request.
-  EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 1);
-  EXPECT_EQ(GetRequestCount(kPrerenderingUrl2), 1);
+  if (IsActivationDisabled()) {
+    // Activation is disabled. The navigation should issue a request again.
+    EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 2);
+    EXPECT_EQ(GetRequestCount(kPrerenderingUrl2), 1);
+  } else {
+    // Activating the prerendered page should not issue a request.
+    EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 1);
+    EXPECT_EQ(GetRequestCount(kPrerenderingUrl2), 1);
+  }
 }
 
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SameOriginRedirection) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, SameOriginRedirection) {
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -307,7 +364,7 @@
   const GURL kRedirectedUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl =
       GetUrl("/server-redirect?" + kRedirectedUrl.spec());
-  AddPrerender(kPrerenderingUrl, kRedirectedUrl);
+  AddPrerender(kPrerenderingUrl);
   EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
   EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
 
@@ -318,7 +375,7 @@
   EXPECT_FALSE(registry.FindHostByUrlForTesting(kRedirectedUrl));
 }
 
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CrossOriginRedirection) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, CrossOriginRedirection) {
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -327,7 +384,7 @@
   const GURL kRedirectedUrl = GetCrossOriginUrl("/empty.html");
   const GURL kPrerenderingUrl =
       GetUrl("/server-redirect?" + kRedirectedUrl.spec());
-  AddPrerender(kPrerenderingUrl, kRedirectedUrl);
+  AddPrerender(kPrerenderingUrl);
   EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
   EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
 
@@ -344,7 +401,7 @@
 // URL.
 
 // Makes sure that activation on navigation for an iframes doesn't happen.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_iFrame) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, Activation_iFrame) {
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -369,7 +426,7 @@
 }
 
 // Makes sure that activation on navigation for a pop-up window doesn't happen.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_PopUpWindow) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, Activation_PopUpWindow) {
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -395,7 +452,7 @@
 
 // Makes sure that activation on navigation for a page that has a pop-up window
 // doesn't happen.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_PageWithPopUpWindow) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, Activation_PageWithPopUpWindow) {
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -426,7 +483,11 @@
 }
 
 // Tests that back-forward history is preserved after activation.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, HistoryAfterActivation) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, HistoryAfterActivation) {
+  // This test is only meaningful with activation.
+  if (IsActivationDisabled())
+    return;
+
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html");
 
@@ -447,13 +508,13 @@
 
 // Tests that all RenderFrameHostImpls in the prerendering page know the
 // prerendering state.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderIframe) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, PrerenderIframe) {
   TestRenderFrameHostPrerenderingState(GetUrl("/page_with_iframe.html"));
 }
 
 // Blank <iframe> is a special case. Tests that the blank iframe knows the
 // prerendering state as well.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderBlankIframe) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, PrerenderBlankIframe) {
   TestRenderFrameHostPrerenderingState(GetUrl("/page_with_blank_iframe.html"));
 }
 
@@ -515,7 +576,7 @@
 
 // Tests that binding requests are handled according to MojoBinderPolicyMap
 // during prerendering.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MojoCapabilityControl) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, MojoCapabilityControl) {
   MojoCapabilityControlTestContentBrowserClient test_browser_client;
   auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client);
 
@@ -563,6 +624,12 @@
   // Verify that BrowserInterfaceBrokerImpl executes kGrant binders immediately.
   EXPECT_EQ(test_browser_client.GetGrantReceiverSetSize(), frames.size());
 
+  // The rest of this test is only meaningful with activation.
+  if (IsActivationDisabled()) {
+    SetBrowserClientForTesting(old_browser_client);
+    return;
+  }
+
   // Activate the prerendered page.
   NavigateWithLocation(kPrerenderingUrl);
   EXPECT_EQ(test_browser_client.GetDeferReceiverSetSize(), frames.size());
@@ -572,7 +639,7 @@
 
 // Tests that mojo capability control will cancel prerendering if the main frame
 // receives a request for a kCancel interface.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest,
                        MojoCapabilityControl_CancelMainFrame) {
   MojoCapabilityControlTestContentBrowserClient test_browser_client;
   auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client);
@@ -608,7 +675,7 @@
 
 // Tests that mojo capability control will cancel prerendering if child frames
 // receive a request for a kCancel interface.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest,
                        MojoCapabilityControl_CancelIframe) {
   MojoCapabilityControlTestContentBrowserClient test_browser_client;
   auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client);
@@ -657,7 +724,7 @@
 // - Tests for feature-specific code methodology restrictions ==================
 
 // Tests that window.open() in a prerendering page fails.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, FeatureRestriction_WindowOpen) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, FeatureRestriction_WindowOpen) {
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -670,12 +737,12 @@
   PrerenderHost* prerender_host =
       registry.FindHostByUrlForTesting(kPrerenderingUrl);
   ASSERT_TRUE(prerender_host);
-  WebContents* prerender_contents = WebContents::FromRenderFrameHost(
-      prerender_host->GetPrerenderedMainFrameHostForTesting());
+  RenderFrameHostImpl* prerender_frame =
+      prerender_host->GetPrerenderedMainFrameHostForTesting();
 
   // Attempt to open a window in the prerendered page. This should fail.
   const GURL kWindowOpenUrl = GetUrl("/empty.html");
-  EXPECT_EQ("FAILED", EvalJs(prerender_contents,
+  EXPECT_EQ("FAILED", EvalJs(prerender_frame,
                              JsReplace("open_window($1)", kWindowOpenUrl)));
   EXPECT_EQ(GetRequestCount(kWindowOpenUrl), 0);
 
@@ -687,7 +754,7 @@
 // the prerendering state.
 // TODO(https://crbug.com/1166470): This can be a tentative behavior. We may
 // stop exposing the clients.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest,
                        FeatureRestriction_ClientsMatchAll) {
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/prerender/clients_matchall.html");
@@ -714,7 +781,7 @@
 // - Tests for Mojo capability control methodology restrictions ================
 
 // Tests that prerendering pages can access cookies.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CookieAccess) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, CookieAccess) {
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html");
   // Navigate to an initial page.
@@ -749,7 +816,7 @@
 }
 
 // Tests that prerendering pages can access local storage.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, LocalStorageAccess) {
+IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, LocalStorageAccess) {
   const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html");
   // Navigate to an initial page.
diff --git a/content/browser/prerender/prerender_host.cc b/content/browser/prerender/prerender_host.cc
index 1501af95..e4926fb 100644
--- a/content/browser/prerender/prerender_host.cc
+++ b/content/browser/prerender/prerender_host.cc
@@ -4,30 +4,260 @@
 
 #include "content/browser/prerender/prerender_host.h"
 
+#include "base/callback_forward.h"
 #include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/run_loop.h"
 #include "base/trace_event/common/trace_event_common.h"
 #include "base/trace_event/trace_conversion_helper.h"
+#include "content/browser/renderer_host/frame_tree.h"
+#include "content/browser/renderer_host/frame_tree_node.h"
+#include "content/browser/renderer_host/navigation_controller_impl.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/back_forward_cache.h"
+#include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "third_party/blink/public/common/features.h"
 
 namespace content {
 
+class PrerenderHost::PageHolderInterface {
+ public:
+  PageHolderInterface() = default;
+  virtual ~PageHolderInterface() = default;
+  virtual NavigationController& GetNavigationController() = 0;
+  virtual RenderFrameHostImpl* GetMainFrame() = 0;
+  virtual WebContents* GetWebContents() = 0;
+  virtual bool Activate(RenderFrameHostImpl& current_render_frame_host) = 0;
+  virtual void WaitForLoadCompletionForTesting() = 0;  // IN-TEST
+};
+
+class PrerenderHost::MPArchPageHolder
+    : public PrerenderHost::PageHolderInterface,
+      public FrameTree::Delegate,
+      public NavigationControllerDelegate {
+ public:
+  explicit MPArchPageHolder(WebContentsImpl& web_contents)
+      : web_contents_(web_contents),
+        frame_tree_(web_contents.GetBrowserContext(),
+                    this,
+                    this,
+                    &web_contents,
+                    &web_contents,
+                    &web_contents,
+                    &web_contents,
+                    &web_contents) {
+    frame_tree_.set_is_prerendering(true);
+    frame_tree_.Init(
+        SiteInstance::Create(web_contents.GetBrowserContext()).get(),
+        /*renderer_initiated_creation=*/false,
+        /*main_frame_name=*/"");
+
+    // TODO(https://crbug.com/1164280): This should be moved to FrameTree::Init
+    web_contents_.NotifySwappedFromRenderManager(
+        /*old_frame=*/nullptr,
+        frame_tree_.root()->render_manager()->current_frame_host(),
+        /*is_main_frame=*/true);
+  }
+
+  // TODO(https://crbug.com/1176148): Mostly copied from ~WebContentsImpl. Move
+  // to ~FrameTree or some common place.
+  ~MPArchPageHolder() override {
+    for (FrameTreeNode* node : frame_tree_.Nodes()) {
+      node->render_manager()->ClearRFHsPendingShutdown();
+      // TODO(https://crbug.com/1164280): Ban WebUI instance in Prerender pages.
+      node->render_manager()->ClearWebUIInstances();
+    }
+
+    GetMainFrame()->ResetChildren();
+    RenderFrameHostManager* root = frame_tree_.root()->render_manager();
+
+    root->ResetProxyHosts();
+
+    GetNavigationController().GetBackForwardCache().Shutdown();
+
+    root->current_frame_host()->RenderFrameDeleted();
+    root->current_frame_host()->ResetNavigationRequests();
+
+    frame_tree_.root()->ResetNavigationRequest(true);
+    if (root->speculative_frame_host()) {
+      root->speculative_frame_host()->DeleteRenderFrame(
+          mojom::FrameDeleteIntention::kSpeculativeMainFrameForShutdown);
+      root->speculative_frame_host()->RenderFrameDeleted();
+      root->speculative_frame_host()->ResetNavigationRequests();
+    }
+
+    web_contents_.OnFrameTreeNodeDestroyed(frame_tree_.root());
+    web_contents_.RenderViewDeleted(
+        root->current_frame_host()->render_view_host());
+  }
+
+  // FrameTree::Delegate
+
+  // TODO(https://crbug.com/1164280): Correctly handle load events. Ignored for
+  // now as it confuses WebContentsObserver instances because they can not
+  // distinguish between the different FrameTrees.
+
+  void DidStartLoading(FrameTreeNode* frame_tree_node,
+                       bool to_different_document) override {}
+
+  void DidStopLoading() override {
+    load_completed_ = true;
+    if (on_stopped_loading_for_tests_) {
+      std::move(on_stopped_loading_for_tests_).Run();
+    }
+  }
+
+  void DidChangeLoadProgress() override {}
+  bool IsHidden() override { return true; }
+
+  // NavigationControllerDelegate
+  void NotifyNavigationStateChanged(InvalidateTypes changed_flags) override {}
+  void Stop() override { frame_tree_.StopLoading(); }
+  bool IsBeingDestroyed() override { return false; }
+  void NotifyBeforeFormRepostWarningShow() override {}
+  void NotifyNavigationEntryCommitted(
+      const LoadCommittedDetails& load_details) override {}
+  void NotifyNavigationEntryChanged(
+      const EntryChangedDetails& change_details) override {}
+  void NotifyNavigationListPruned(
+      const PrunedDetails& pruned_details) override {}
+  void NotifyNavigationEntriesDeleted() override {}
+  void ActivateAndShowRepostFormWarningDialog() override {}
+  bool ShouldPreserveAbortedURLs() override { return false; }
+  WebContents* GetWebContents() override { return &web_contents_; }
+  void UpdateOverridingUserAgent() override {}
+
+  // PageHolder
+  NavigationControllerImpl& GetNavigationController() override {
+    return frame_tree_.controller();
+  }
+
+  RenderFrameHostImpl* GetMainFrame() override {
+    return frame_tree_.root()->current_frame_host();
+  }
+
+  bool Activate(RenderFrameHostImpl& current_render_frame_host) override {
+    NOTREACHED();
+    return false;
+  }
+
+  // TODO(https://crbug.com/1164280): Once we dispatch load events for prerender
+  // pages this method will no longer be needed and should go away.
+  void WaitForLoadCompletionForTesting() override {
+    if (load_completed_)
+      return;
+
+    base::RunLoop loop;
+    on_stopped_loading_for_tests_ = loop.QuitClosure();
+    loop.Run();
+  }
+
+ private:
+  // WebContents where this prerenderer is embedded.
+  WebContentsImpl& web_contents_;
+  FrameTree frame_tree_;
+  base::OnceClosure on_stopped_loading_for_tests_;
+  bool load_completed_ = false;
+};
+
+class PrerenderHost::WebContentsPageHolder
+    : public PrerenderHost::PageHolderInterface {
+ public:
+  explicit WebContentsPageHolder(BrowserContext* browser_context) {
+    // Create a new WebContents for prerendering.
+    WebContents::CreateParams web_contents_params(browser_context);
+    // TODO(https://crbug.com/1132746): Set up other fields of
+    // `web_contents_params` as well, and add tests for them.
+    web_contents_ = WebContents::Create(web_contents_params);
+    static_cast<WebContentsImpl*>(web_contents_.get())
+        ->GetFrameTree()
+        ->set_is_prerendering(true);
+  }
+
+  ~WebContentsPageHolder() override = default;
+
+  // PageHolder
+  NavigationController& GetNavigationController() override {
+    return web_contents_->GetController();
+  }
+
+  RenderFrameHostImpl* GetMainFrame() override {
+    return static_cast<RenderFrameHostImpl*>(web_contents_->GetMainFrame());
+  }
+
+  WebContents* GetWebContents() override { return web_contents_.get(); }
+
+  bool Activate(RenderFrameHostImpl& current_render_frame_host) override {
+    auto* current_web_contents =
+        WebContents::FromRenderFrameHost(&current_render_frame_host);
+    if (!current_web_contents)
+      return false;
+
+    // Merge browsing history.
+    web_contents_->GetController().CopyStateFromAndPrune(
+        &current_web_contents->GetController(), /*replace_entry=*/false);
+
+    // Activate the prerendered contents.
+    WebContentsDelegate* delegate = current_web_contents->GetDelegate();
+    DCHECK(delegate);
+    DCHECK(web_contents_);
+    DCHECK(GetMainFrame()->frame_tree()->is_prerendering());
+    GetMainFrame()->frame_tree()->set_is_prerendering(false);
+    GetMainFrame()->OnPrerenderedPageActivated();
+    // Tentatively use Portal's activation function.
+    // TODO(https://crbug.com/1132746): Replace this with the MPArch.
+    std::unique_ptr<WebContents> predecessor_web_contents =
+        delegate->ActivatePortalWebContents(current_web_contents,
+                                            std::move(web_contents_));
+
+    // Stop loading on the predecessor WebContents.
+    predecessor_web_contents->Stop();
+    return true;
+  }
+
+  void WaitForLoadCompletionForTesting() override {
+    if (!web_contents_ || !static_cast<WebContentsImpl*>(web_contents_.get())
+                               ->GetFrameTree()
+                               ->IsLoading()) {
+      return;
+    }
+
+    base::RunLoop loop;
+    LoadStopObserver observer(web_contents_.get(), loop.QuitClosure());
+    loop.Run();
+  }
+
+ private:
+  class LoadStopObserver : public WebContentsObserver {
+   public:
+    LoadStopObserver(WebContents* web_contents,
+                     base::OnceClosure on_stopped_loading)
+        : WebContentsObserver(web_contents),
+          on_stopped_loading_(std::move(on_stopped_loading)) {}
+
+    void DidStopLoading() override {
+      std::move(on_stopped_loading_).Run();
+      Observe(nullptr);
+    }
+
+   private:
+    base::OnceClosure on_stopped_loading_;
+  };
+
+  std::unique_ptr<WebContents> web_contents_;
+};
+
 PrerenderHost::PrerenderHost(blink::mojom::PrerenderAttributesPtr attributes,
                              const url::Origin& initiator_origin,
-                             BrowserContext& browser_context)
+                             WebContentsImpl& web_contents)
     : attributes_(std::move(attributes)), initiator_origin_(initiator_origin) {
   DCHECK(blink::features::IsPrerender2Enabled());
-  // Create a new WebContents for prerendering.
-  WebContents::CreateParams web_contents_params(&browser_context);
-  // TODO(https://crbug.com/1132746): Set up other fields of
-  // `web_contents_params` as well, and add tests for them.
-  prerendered_contents_ = WebContents::Create(web_contents_params);
-  frame_tree_node_id_ = GetPrerenderedFrameTree()->root()->frame_tree_node_id();
-  GetPrerenderedFrameTree()->set_is_prerendering(true);
+  CreatePageHolder(web_contents);
 }
 
 // TODO(https://crbug.com/1132746): Abort ongoing prerendering and notify the
@@ -42,17 +272,20 @@
 // for example.
 void PrerenderHost::StartPrerendering() {
   TRACE_EVENT0("navigation", "PrerenderHost::StartPrerendering");
+
   // Observe events about the prerendering contents.
-  Observe(prerendered_contents_.get());
+  Observe(page_holder_->GetWebContents());
 
   // Start prerendering navigation.
   NavigationController::LoadURLParams load_url_params(attributes_->url);
   load_url_params.initiator_origin = initiator_origin_;
   // TODO(https://crbug.com/1132746): Set up other fields of `load_url_params`
   // as well, and add tests for them.
-  prerendered_contents_->GetController().LoadURLWithParams(load_url_params);
+  page_holder_->GetNavigationController().LoadURLWithParams(load_url_params);
 }
 
+// TODO(https://crbug.com/1170277): Does not work with MPArch as we get
+// navigation events for all FrameTrees.
 void PrerenderHost::DidFinishNavigation(NavigationHandle* navigation_handle) {
   // The prerendered contents are considered ready for activation when it
   // reaches DidFinishNavigation.
@@ -72,49 +305,46 @@
   DCHECK(is_ready_for_activation_);
   is_ready_for_activation_ = false;
 
-  auto* current_web_contents =
-      WebContents::FromRenderFrameHost(&current_render_frame_host);
-  if (!current_web_contents)
-    return false;
-
-  // Merge browsing history.
-  prerendered_contents_->GetController().CopyStateFromAndPrune(
-      &current_web_contents->GetController(), /*replace_entry=*/false);
-
-  // Activate the prerendered contents.
-  WebContentsDelegate* delegate = current_web_contents->GetDelegate();
-  DCHECK(delegate);
-  DCHECK(prerendered_contents_);
-  DCHECK(GetPrerenderedFrameTree()->is_prerendering());
-  GetPrerenderedFrameTree()->set_is_prerendering(false);
-  static_cast<RenderFrameHostImpl*>(prerendered_contents_->GetMainFrame())
-      ->OnPrerenderedPageActivated();
-  // Tentatively use Portal's activation function.
-  // TODO(https://crbug.com/1132746): Replace this with the MPArch.
-  std::unique_ptr<WebContents> predecessor_web_contents =
-      delegate->ActivatePortalWebContents(current_web_contents,
-                                          std::move(prerendered_contents_));
-
-  // Stop loading on the predecessor WebContents.
-  predecessor_web_contents->Stop();
-
   // TODO(https://crbug.com/1142658): Notify renderer processes that the
   // contents get activated.
 
-  RecordFinalStatus(FinalStatus::kActivated);
-  return true;
+  bool success = page_holder_->Activate(current_render_frame_host);
+
+  // TODO(https://crbug.com/1126305): Record the final status for activation
+  // failure.
+  if (success)
+    RecordFinalStatus(FinalStatus::kActivated);
+
+  return success;
 }
 
 RenderFrameHostImpl* PrerenderHost::GetPrerenderedMainFrameHostForTesting() {
-  DCHECK(prerendered_contents_);
-  return static_cast<RenderFrameHostImpl*>(
-      prerendered_contents_->GetMainFrame());
+  return page_holder_->GetMainFrame();
+}
+
+void PrerenderHost::CreatePageHolder(WebContentsImpl& web_contents) {
+  switch (blink::features::kPrerender2ImplementationParam.Get()) {
+    case blink::features::Prerender2Implementation::kWebContents: {
+      page_holder_ = std::make_unique<WebContentsPageHolder>(
+          web_contents.GetBrowserContext());
+      break;
+    }
+    case blink::features::Prerender2Implementation::kMPArch: {
+      page_holder_ = std::make_unique<MPArchPageHolder>(web_contents);
+      break;
+    }
+  }
+
+  frame_tree_node_id_ = page_holder_->GetMainFrame()->GetFrameTreeNodeId();
+}
+
+void PrerenderHost::WaitForLoadStopForTesting() {
+  page_holder_->WaitForLoadCompletionForTesting();  // IN-TEST
 }
 
 FrameTree* PrerenderHost::GetPrerenderedFrameTree() {
-  DCHECK(prerendered_contents_);
-  return static_cast<WebContentsImpl*>(prerendered_contents_.get())
-      ->GetFrameTree();
+  DCHECK(page_holder_);
+  return page_holder_->GetMainFrame()->frame_tree();
 }
 
 void PrerenderHost::RecordFinalStatus(FinalStatus status) {
diff --git a/content/browser/prerender/prerender_host.h b/content/browser/prerender/prerender_host.h
index eb3f209..3ab55986 100644
--- a/content/browser/prerender/prerender_host.h
+++ b/content/browser/prerender/prerender_host.h
@@ -17,7 +17,8 @@
 
 namespace content {
 
-class BrowserContext;
+class FrameTree;
+class NavigationController;
 class RenderFrameHostImpl;
 class WebContents;
 class FrameTree;
@@ -30,9 +31,12 @@
 // created for browser-initiated prerendering (this code path is not implemented
 // yet). This is owned by PrerenderHostRegistry.
 //
-// TODO(https://crbug.com/1132746): Stop creating a new WebContents and instead
-// use the MPArch.
-class CONTENT_EXPORT PrerenderHost final : public WebContentsObserver {
+// TODO(https://crbug.com/1132746): This class has two different ways of
+// prerendering the page: a dedicated WebContents instance or using a separate
+// FrameTree instance (MPArch). You can choose one or the other via the feature
+// parameter "implementation". The MPArch code is still in its very early stages
+// but will eventually completely replace the WebContents approach.
+class CONTENT_EXPORT PrerenderHost : public WebContentsObserver {
  public:
   // These values are persisted to logs. Entries should not be renumbered and
   // numeric values should never be reused.
@@ -44,7 +48,7 @@
 
   PrerenderHost(blink::mojom::PrerenderAttributesPtr attributes,
                 const url::Origin& initiator_origin,
-                BrowserContext& browser_context);
+                WebContentsImpl& web_contents);
   ~PrerenderHost() override;
 
   PrerenderHost(const PrerenderHost&) = delete;
@@ -69,6 +73,9 @@
   // ActivatePrerenderedContents().
   RenderFrameHostImpl* GetPrerenderedMainFrameHostForTesting();
 
+  // Waits until the page load finishes.
+  void WaitForLoadStopForTesting();
+
   const GURL& GetInitialUrl() const;
 
   int frame_tree_node_id() const { return frame_tree_node_id_; }
@@ -76,21 +83,28 @@
   bool is_ready_for_activation() const { return is_ready_for_activation_; }
 
  private:
+  // There are two implementations of this interface. One holds the page in a
+  // WebContents, and one holds it in a FrameTree (for MPArch).
+  // TODO(https://crbug.com/1170277): Remove once MPArch is the only
+  // implementation.
+  class PageHolderInterface;
+  class MPArchPageHolder;
+  class WebContentsPageHolder;
+
   void RecordFinalStatus(FinalStatus status);
 
   // Returns the frame tree associated with |prerendered_contents_|;
   FrameTree* GetPrerenderedFrameTree();
 
+  void CreatePageHolder(WebContentsImpl& web_contents);
+
+  NavigationController& GetNavigationController();
+
   const blink::mojom::PrerenderAttributesPtr attributes_;
   const GlobalFrameRoutingId initiator_render_frame_host_id_;
   const url::Origin initiator_origin_;
 
-  // WebContents for prerendering.
-  // TODO(https://crbug.com/1132746): Stop owning a new WebContents and instead
-  // use the new MPArch mechanism.
-  std::unique_ptr<WebContents> prerendered_contents_;
-
-  // Indicates if `prerendered_contents_` is ready for activation.
+  // Indicates if `page_holder_` is ready for activation.
   bool is_ready_for_activation_ = false;
 
   // The ID of the root node of the frame tree for the prerendered page `this`
@@ -99,6 +113,8 @@
   int frame_tree_node_id_ = RenderFrameHost::kNoFrameTreeNodeId;
 
   base::Optional<FinalStatus> final_status_;
+
+  std::unique_ptr<PageHolderInterface> page_holder_;
 };
 
 }  // namespace content
diff --git a/content/browser/prerender/prerender_host_registry.cc b/content/browser/prerender/prerender_host_registry.cc
index 1a4383e..c4169b5 100644
--- a/content/browser/prerender/prerender_host_registry.cc
+++ b/content/browser/prerender/prerender_host_registry.cc
@@ -6,6 +6,7 @@
 
 #include "base/check.h"
 #include "base/feature_list.h"
+#include "base/run_loop.h"
 #include "base/stl_util.h"
 #include "base/trace_event/common/trace_event_common.h"
 #include "base/trace_event/trace_conversion_helper.h"
@@ -16,8 +17,7 @@
 
 namespace content {
 
-PrerenderHostRegistry::PrerenderHostRegistry(BrowserContext& browser_context)
-    : browser_context_(browser_context) {
+PrerenderHostRegistry::PrerenderHostRegistry() {
   DCHECK(blink::features::IsPrerender2Enabled());
 }
 
@@ -25,22 +25,22 @@
 
 int PrerenderHostRegistry::CreateAndStartHost(
     blink::mojom::PrerenderAttributesPtr attributes,
+    WebContentsImpl& web_contents,
     const url::Origin& initiator_origin) {
   DCHECK(attributes);
 
   // Ignore prerendering requests for the same URL.
   const GURL prerendering_url = attributes->url;
   TRACE_EVENT2("navigation", "PrerenderHostRegistry::CreateAndStartHost",
-               "Prerender Attributes",
-               base::trace_event::ToTracedValue(*attributes),
-               "initiator_origin", initiator_origin.GetURL().spec());
+               "attributes", attributes, "initiator_origin",
+               initiator_origin.GetURL().spec());
 
   auto found = frame_tree_node_id_by_url_.find(prerendering_url);
   if (found != frame_tree_node_id_by_url_.end())
     return found->second;
 
   auto prerender_host = std::make_unique<PrerenderHost>(
-      std::move(attributes), initiator_origin, browser_context_);
+      std::move(attributes), initiator_origin, web_contents);
   const int frame_tree_node_id = prerender_host->frame_tree_node_id();
 
   // Start prerendering before adding the host to `frame_tree_node_id_by_url_`
@@ -65,6 +65,10 @@
                         frame_tree_node_id));
 
   frame_tree_node_id_by_url_[prerendering_url] = frame_tree_node_id;
+
+  if (on_host_created_)
+    std::move(on_host_created_).Run();
+
   return frame_tree_node_id;
 }
 
@@ -140,4 +144,16 @@
   return host_iter->second.get();
 }
 
+PrerenderHost* PrerenderHostRegistry::WaitForHostByUrlForTesting(
+    const GURL& prerendering_url) {
+  PrerenderHost* host = FindHostByUrlForTesting(prerendering_url);  // IN-TEST
+  while (!host) {
+    base::RunLoop loop;
+    on_host_created_ = loop.QuitClosure();
+    loop.Run();
+    host = FindHostByUrlForTesting(prerendering_url);  // IN-TEST
+  }
+  return host;
+}
+
 }  // namespace content
diff --git a/content/browser/prerender/prerender_host_registry.h b/content/browser/prerender/prerender_host_registry.h
index 9e8b9c0..9b0e1ec 100644
--- a/content/browser/prerender/prerender_host_registry.h
+++ b/content/browser/prerender/prerender_host_registry.h
@@ -7,6 +7,7 @@
 
 #include <map>
 
+#include "base/callback_forward.h"
 #include "content/common/content_export.h"
 #include "third_party/blink/public/mojom/prerender/prerender.mojom.h"
 #include "url/gurl.h"
@@ -14,9 +15,9 @@
 
 namespace content {
 
-class BrowserContext;
 class FrameTreeNode;
 class PrerenderHost;
+class WebContentsImpl;
 
 // Prerender2:
 // PrerenderHostRegistry manages running prerender hosts and provides the host
@@ -24,7 +25,7 @@
 // owned by StoragePartitionImpl.
 class CONTENT_EXPORT PrerenderHostRegistry {
  public:
-  explicit PrerenderHostRegistry(BrowserContext& browser_context);
+  PrerenderHostRegistry();
   ~PrerenderHostRegistry();
 
   PrerenderHostRegistry(const PrerenderHostRegistry&) = delete;
@@ -35,6 +36,7 @@
   // Creates and starts a host. Returns the root frame tree node id of the
   // prerendered page, which can be used as the id of the host.
   int CreateAndStartHost(blink::mojom::PrerenderAttributesPtr attributes,
+                         WebContentsImpl& web_contents,
                          const url::Origin& initiator_origin);
 
   // Destroys the host registered for `frame_tree_node_id`.
@@ -58,16 +60,17 @@
   // doesn't match any prerender host.
   PrerenderHost* FindHostByUrlForTesting(const GURL& prerendering_url);
 
- private:
-  // This outlives `this` because PrerenderHostRegistry is owned by
-  // StoragePartitionImpl, which in turn is owned by BrowserContext.
-  BrowserContext& browser_context_;
+  // Waits until a prerender host for `prerendering_url` is created and returns
+  // a pointer to it.
+  PrerenderHost* WaitForHostByUrlForTesting(const GURL& prerendering_url);
 
+ private:
   // TODO(https://crbug.com/1132746): Expire prerendered contents if they are
   // not used for a while.
   std::map<int, std::unique_ptr<PrerenderHost>>
       prerender_host_by_frame_tree_node_id_;
   std::map<GURL, int> frame_tree_node_id_by_url_;
+  base::OnceClosure on_host_created_;
 };
 
 }  // namespace content
diff --git a/content/browser/prerender/prerender_host_registry_unittest.cc b/content/browser/prerender/prerender_host_registry_unittest.cc
index be9b745..98187d03 100644
--- a/content/browser/prerender/prerender_host_registry_unittest.cc
+++ b/content/browser/prerender/prerender_host_registry_unittest.cc
@@ -71,8 +71,9 @@
   attributes->url = kPrerenderingUrl;
 
   PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
-  const int prerender_frame_tree_node_id = registry->CreateAndStartHost(
-      std::move(attributes), render_frame_host->GetLastCommittedOrigin());
+  const int prerender_frame_tree_node_id =
+      registry->CreateAndStartHost(std::move(attributes), *web_contents,
+                                   render_frame_host->GetLastCommittedOrigin());
   ASSERT_NE(prerender_frame_tree_node_id, kNoFrameTreeNodeId);
   PrerenderHost* prerender_host =
       registry->FindHostByUrlForTesting(kPrerenderingUrl);
@@ -101,15 +102,17 @@
   attributes2->url = kPrerenderingUrl;
 
   PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
-  const int frame_tree_node_id1 = registry->CreateAndStartHost(
-      std::move(attributes1), render_frame_host->GetLastCommittedOrigin());
+  const int frame_tree_node_id1 =
+      registry->CreateAndStartHost(std::move(attributes1), *web_contents,
+                                   render_frame_host->GetLastCommittedOrigin());
   PrerenderHost* prerender_host1 =
       registry->FindHostByUrlForTesting(kPrerenderingUrl);
 
   // Start the prerender host for the same URL. This second host should be
   // ignored, and the first host should still be findable.
-  const int frame_tree_node_id2 = registry->CreateAndStartHost(
-      std::move(attributes2), render_frame_host->GetLastCommittedOrigin());
+  const int frame_tree_node_id2 =
+      registry->CreateAndStartHost(std::move(attributes2), *web_contents,
+                                   render_frame_host->GetLastCommittedOrigin());
   EXPECT_EQ(frame_tree_node_id1, frame_tree_node_id2);
   EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl),
             prerender_host1);
@@ -138,10 +141,12 @@
   attributes2->url = kPrerenderingUrl2;
 
   PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
-  const int frame_tree_node_id1 = registry->CreateAndStartHost(
-      std::move(attributes1), render_frame_host->GetLastCommittedOrigin());
-  const int frame_tree_node_id2 = registry->CreateAndStartHost(
-      std::move(attributes2), render_frame_host->GetLastCommittedOrigin());
+  const int frame_tree_node_id1 =
+      registry->CreateAndStartHost(std::move(attributes1), *web_contents,
+                                   render_frame_host->GetLastCommittedOrigin());
+  const int frame_tree_node_id2 =
+      registry->CreateAndStartHost(std::move(attributes2), *web_contents,
+                                   render_frame_host->GetLastCommittedOrigin());
   EXPECT_NE(frame_tree_node_id1, frame_tree_node_id2);
   PrerenderHost* prerender_host1 =
       registry->FindHostByUrlForTesting(kPrerenderingUrl1);
@@ -178,8 +183,9 @@
   attributes->url = kPrerenderingUrl;
 
   PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
-  const int prerender_frame_tree_node_id = registry->CreateAndStartHost(
-      std::move(attributes), render_frame_host->GetLastCommittedOrigin());
+  const int prerender_frame_tree_node_id =
+      registry->CreateAndStartHost(std::move(attributes), *web_contents,
+                                   render_frame_host->GetLastCommittedOrigin());
   ASSERT_NE(prerender_frame_tree_node_id, kNoFrameTreeNodeId);
   PrerenderHost* prerender_host =
       registry->FindHostByUrlForTesting(kPrerenderingUrl);
@@ -203,8 +209,9 @@
   attributes->url = kPrerenderingUrl;
 
   PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
-  const int prerender_frame_tree_node_id = registry->CreateAndStartHost(
-      std::move(attributes), render_frame_host->GetLastCommittedOrigin());
+  const int prerender_frame_tree_node_id =
+      registry->CreateAndStartHost(std::move(attributes), *web_contents,
+                                   render_frame_host->GetLastCommittedOrigin());
   EXPECT_NE(registry->FindHostByUrlForTesting(kPrerenderingUrl), nullptr);
 
   registry->AbandonHost(prerender_frame_tree_node_id);
diff --git a/content/browser/prerender/prerender_host_unittest.cc b/content/browser/prerender/prerender_host_unittest.cc
index cdb76e4..594ee129 100644
--- a/content/browser/prerender/prerender_host_unittest.cc
+++ b/content/browser/prerender/prerender_host_unittest.cc
@@ -53,8 +53,6 @@
     return web_contents;
   }
 
-  BrowserContext& browser_context() { return *browser_context_; }
-
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 
@@ -74,7 +72,7 @@
   attributes->url = kPrerenderingUrl;
   auto prerender_host = std::make_unique<PrerenderHost>(
       std::move(attributes), initiator_rfh->GetLastCommittedOrigin(),
-      browser_context());
+      *web_contents);
 
   // Start the prerendering navigation.
   prerender_host->StartPrerendering();
@@ -110,7 +108,7 @@
   attributes->url = kPrerenderingUrl;
   auto prerender_host = std::make_unique<PrerenderHost>(
       std::move(attributes), initiator_rfh->GetLastCommittedOrigin(),
-      browser_context());
+      *web_contents);
 
   // Start the prerendering navigation, but don't activate it.
   prerender_host->StartPrerendering();
diff --git a/content/browser/prerender/prerender_processor.cc b/content/browser/prerender/prerender_processor.cc
index 7f9a213..cc3b02b 100644
--- a/content/browser/prerender/prerender_processor.cc
+++ b/content/browser/prerender/prerender_processor.cc
@@ -8,6 +8,7 @@
 #include "content/browser/prerender/prerender_host.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/storage_partition_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "third_party/blink/public/common/features.h"
@@ -63,8 +64,14 @@
   // TODO(https://crbug.com/1138711, https://crbug.com/1138723): Abort if the
   // initiator frame is not the main frame (i.e., iframe or pop-up window).
 
+  auto* web_contents = static_cast<WebContentsImpl*>(
+      WebContents::FromRenderFrameHost(&initiator_render_frame_host_));
+
+  if (!web_contents)
+    return;
+
   prerender_frame_tree_node_id_ = GetPrerenderHostRegistry().CreateAndStartHost(
-      std::move(attributes), initiator_origin_);
+      std::move(attributes), *web_contents, initiator_origin_);
 }
 
 void PrerenderProcessor::Cancel() {
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 7bd4202..348f2e1 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -117,6 +117,7 @@
 #include "services/network/public/mojom/web_sandbox_flags.mojom.h"
 #include "third_party/blink/public/common/blob/blob_utils.h"
 #include "third_party/blink/public/common/client_hints/client_hints.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/net/ip_address_space_util.h"
 #include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
@@ -1605,9 +1606,21 @@
         prerender_host_registry->FindHostToActivate(common_params_->url,
                                                     *frame_tree_node_);
 
-    // If `prerender_host_` exists, this navigation will activate the
-    // prerendered page on navigation commit.
-    prerender_host_ = std::move(prerender_host);
+    switch (blink::features::kPrerender2ImplementationParam.Get()) {
+      case blink::features::Prerender2Implementation::kWebContents:
+        // If `prerender_host_` exists, this navigation will activate the
+        // prerendered page on navigation commit.
+        prerender_host_ = std::move(prerender_host);
+        break;
+      // TODO(https://crbug.com/1170277): Remove once activation support is
+      // added to MPArch
+      case blink::features::Prerender2Implementation::kMPArch:
+        // The feature param disallows activation of the prerendered page for
+        // testing. Destroy `prerender_host` to dispose of the prerendered
+        // page.
+        prerender_host.reset();
+        break;
+    }
   }
 
   WillStartRequest();
diff --git a/content/browser/resources/histograms/BUILD.gn b/content/browser/resources/histograms/BUILD.gn
index b5fab5c..b828c810 100644
--- a/content/browser/resources/histograms/BUILD.gn
+++ b/content/browser/resources/histograms/BUILD.gn
@@ -5,7 +5,6 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":histograms_internals" ]
 }
 
diff --git a/content/browser/resources/media/stats_rates_calculator.js b/content/browser/resources/media/stats_rates_calculator.js
index 7b6cd63..4bcee41 100644
--- a/content/browser/resources/media/stats_rates_calculator.js
+++ b/content/browser/resources/media/stats_rates_calculator.js
@@ -496,7 +496,10 @@
               'framesDecoded', 'interFrameDelay'),
           totalSamplesReceived:
               new RateCalculator('totalSamplesReceived', 'timestamp'),
-          concealedSamples: new RateCalculator('concealedSamples', 'timestamp'),
+          concealedSamples: [
+            new RateCalculator('concealedSamples', 'timestamp'),
+            new RateCalculator('concealedSamples', 'totalSamplesReceived'),
+          ],
           silentConcealedSamples:
               new RateCalculator('silentConcealedSamples', 'timestamp'),
           insertedSamplesForDeceleration:
diff --git a/content/browser/resources/process/BUILD.gn b/content/browser/resources/process/BUILD.gn
index 8871fe6..b13c34d6 100644
--- a/content/browser/resources/process/BUILD.gn
+++ b/content/browser/resources/process/BUILD.gn
@@ -6,7 +6,6 @@
 
 js_type_check("closure_compile") {
   deps = [ ":process_internals" ]
-  uses_js_modules = true
   closure_flags =
       default_closure_args + mojom_js_args + [
         "js_module_root=" + rebase_path(".", root_build_dir),
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 887ec06..d8ea9a4 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -1339,8 +1339,7 @@
   font_access_manager_ = std::make_unique<FontAccessManagerImpl>();
 
   if (blink::features::IsPrerender2Enabled()) {
-    prerender_host_registry_ =
-        std::make_unique<PrerenderHostRegistry>(*browser_context_);
+    prerender_host_registry_ = std::make_unique<PrerenderHostRegistry>();
   }
 }
 
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 2e461e4a..ee11868 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3538,7 +3538,7 @@
     SessionStorageNamespace* session_storage_namespace) {
   TRACE_EVENT2("browser,content,navigation", "WebContentsImpl::CreateNewWindow",
                "opener", base::trace_event::ToTracedValue(opener), "params",
-               base::trace_event::ToTracedValue(params));
+               params);
   DCHECK(opener);
 
   int render_process_id = opener->GetProcess()->GetID();
@@ -6787,16 +6787,10 @@
   OPTIONAL_TRACE_EVENT1("content", "WebContentsImpl::RenderViewCreated",
                         "render_view_host",
                         static_cast<void*>(render_view_host));
-  if (delegate_)
+  if (delegate_) {
     view_->SetOverscrollControllerEnabled(CanOverscrollContent());
-
-  NotificationService::current()->Notify(
-      NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
-      Source<WebContents>(this),
-      Details<RenderViewHost>(render_view_host));
-
-  if (delegate_)
     RenderFrameDevToolsAgentHost::WebContentsCreated(this);
+  }
 }
 
 void WebContentsImpl::RenderViewReady(RenderViewHost* rvh) {
@@ -7560,11 +7554,17 @@
       "content", "WebContentsImpl::NotifySwappedFromRenderManager",
       "old_render_frame_host", base::trace_event::ToTracedValue(old_frame),
       "new_render_frame_host", base::trace_event::ToTracedValue(new_frame));
-  if (is_main_frame) {
+
+  FrameTree* frame_tree =
+      static_cast<RenderFrameHostImpl*>(new_frame)->frame_tree();
+
+  // Only fire RenderViewHostChanged if it is related to our FrameTree, as
+  // observers can not deal with events coming from non-primary FrameTree.
+  // TODO(https://crbug.com/1168562): Update observers to deal with the events,
+  // and fire events for all frame trees.
+  if (is_main_frame && IsPrimaryFrameTree(*frame_tree)) {
     // The |new_frame| and its various compadres are already swapped into place
     // for the WebContentsImpl when this method is called.
-    FrameTree* frame_tree =
-        static_cast<RenderFrameHostImpl*>(new_frame)->frame_tree();
     DCHECK_EQ(frame_tree->root()->current_frame_host(), new_frame);
     DCHECK_EQ(frame_tree->root()->render_manager()->GetRenderWidgetHostView(),
               new_frame->GetView());
@@ -7602,6 +7602,15 @@
 void WebContentsImpl::NotifyMainFrameSwappedFromRenderManager(
     RenderFrameHost* old_frame,
     RenderFrameHost* new_frame) {
+  // Only fire RenderViewHostChanged if it is
+  // related to our FrameTree, as observers cannot deal with events coming
+  // from non-primary FrameTree.
+  // TODO(https://crbug.com/1168562): Update observers to deal with the events,
+  // and fire events for all frame trees.
+  if (!IsPrimaryFrameTree(
+          *static_cast<RenderFrameHostImpl*>(new_frame)->frame_tree())) {
+    return;
+  }
   NotifyViewSwapped(old_frame ? old_frame->GetRenderViewHost() : nullptr,
                     new_frame->GetRenderViewHost());
 }
@@ -8662,4 +8671,8 @@
   }
 }
 
+bool WebContentsImpl::IsPrimaryFrameTree(const FrameTree& frame_tree) const {
+  return !frame_tree.is_prerendering();
+}
+
 }  // namespace content
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index a861fc3..c9f4ba6 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -1695,6 +1695,10 @@
   void SetSlowWebPreferences(const base::CommandLine& command_line,
                              blink::web_pref::WebPreferences* prefs);
 
+  // Checks whether the given FrameTree is the primary one (the one whose URL is
+  // shown in the address bar), as opposed to one in for example a Prerender.
+  bool IsPrimaryFrameTree(const FrameTree& frame_tree) const;
+
   // Data for core operation ---------------------------------------------------
 
   // Delegate for notifying our owner about stuff. Not owned by us.
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 7433b45..1052c62fa 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -9,7 +9,7 @@
 // declarations instead of including more headers. If that is infeasible, adjust
 // the limit. For more info, see
 // https://chromium.googlesource.com/chromium/src/+/HEAD/docs/wmax_tokens.md
-#pragma clang max_tokens_here 840000
+#pragma clang max_tokens_here 850000
 
 #include <utility>
 
diff --git a/content/public/browser/notification_types.h b/content/public/browser/notification_types.h
index 4d6fba1f..65d81cfb 100644
--- a/content/public/browser/notification_types.h
+++ b/content/public/browser/notification_types.h
@@ -95,13 +95,6 @@
   // TODO(https://crbug.com/1174767): Remove.
   NOTIFICATION_WEB_CONTENTS_DESTROYED,
 
-  // A RenderViewHost was created for a WebContents. The source is the
-  // associated WebContents, and the details is the RenderViewHost
-  // pointer.
-  // DEPRECATED: Use WebContentsObserver::RenderViewCreated()
-  // TODO(https://crbug.com/1174768): Remove.
-  NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
-
   // Indicates that a RenderProcessHost was created and its handle is now
   // available. The source will be the RenderProcessHost that corresponds to
   // the process.
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 15aa2b0e..0e4db7d 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -763,7 +763,7 @@
 // An experimental replacement for the `User-Agent` header, defined in
 // https://tools.ietf.org/html/draft-west-ua-client-hints.
 const base::Feature kUserAgentClientHint{"UserAgentClientHint",
-                                         base::FEATURE_DISABLED_BY_DEFAULT};
+                                         base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables comparing browser and renderer's DidCommitProvisionalLoadParams in
 // RenderFrameHostImpl::VerifyThatBrowserAndRendererCalculatedDidCommitParamsMatch.
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 02e86c7..9e66d5d 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -827,7 +827,6 @@
 # NOTE: Building this target serves as a compile test for type-checking of
 # WebUI JS that consumes generated Mojom JS bindings.
 js_type_check("web_ui_mojo_test_js_type_check") {
-  uses_js_modules = true
   deps = [ ":web_ui_mojo_test_js" ]
   closure_flags = default_closure_args + mojom_js_args
 }
diff --git a/device/vr/openxr/openxr_api_wrapper.cc b/device/vr/openxr/openxr_api_wrapper.cc
index 69d8559..1ebca25 100644
--- a/device/vr/openxr/openxr_api_wrapper.cc
+++ b/device/vr/openxr/openxr_api_wrapper.cc
@@ -76,15 +76,14 @@
   origin_from_eye_views_.clear();
   head_from_eye_views_.clear();
   layer_projection_views_.clear();
+  input_helper_.reset();
 }
 
 bool OpenXrApiWrapper::Initialize(XrInstance instance) {
   Reset();
+
   session_running_ = false;
   pending_frame_ = false;
-  // Set to min so that the first call to EnsureEventPolling is guaranteed to
-  // call ProcessEvents, which will update this variable from there on.
-  last_process_events_time_ = base::TimeTicks::Min();
 
   DCHECK(instance != XR_NULL_HANDLE);
   instance_ = instance;
@@ -134,9 +133,6 @@
   Reset();
   session_running_ = false;
   pending_frame_ = false;
-
-  // Set to max so events are no longer polled in the EnsureEventPolling loop.
-  last_process_events_time_ = base::TimeTicks::Max();
 }
 
 bool OpenXrApiWrapper::HasInstance() const {
@@ -281,11 +277,15 @@
 // objects that may have been created before the failure.
 XrResult OpenXrApiWrapper::InitSession(
     const Microsoft::WRL::ComPtr<ID3D11Device>& d3d_device,
-    std::unique_ptr<OpenXRInputHelper>* input_helper,
-    const OpenXrExtensionHelper& extension_helper) {
+    const OpenXrExtensionHelper& extension_helper,
+    const SessionEndedCallback& on_session_ended_callback,
+    const VisibilityChangedCallback& visibility_changed_callback) {
   DCHECK(d3d_device.Get());
   DCHECK(IsInitialized());
 
+  on_session_ended_callback_ = std::move(on_session_ended_callback);
+  visibility_changed_callback_ = std::move(visibility_changed_callback);
+
   RETURN_IF_XR_FAILED(CreateSession(d3d_device));
   RETURN_IF_XR_FAILED(CreateSwapchain());
   RETURN_IF_XR_FAILED(
@@ -303,7 +303,9 @@
         CreateSpace(XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT, &unbounded_space_));
   }
 
-  RETURN_IF_XR_FAILED(CreateGamepadHelper(input_helper, extension_helper));
+  RETURN_IF_XR_FAILED(OpenXRInputHelper::CreateOpenXRInputHelper(
+      instance_, system_, extension_helper, session_, local_space_,
+      &input_helper_));
 
   // Since the objects in these arrays are used on every frame,
   // we don't want to create and destroy these objects every frame,
@@ -317,7 +319,7 @@
   DCHECK(HasColorSwapChain());
   DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL));
   DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_VIEW));
-  DCHECK(input_helper);
+  DCHECK(input_helper_);
 
   EnsureEventPolling();
 
@@ -421,17 +423,6 @@
   return xrCreateReferenceSpace(session_, &space_create_info, space);
 }
 
-XrResult OpenXrApiWrapper::CreateGamepadHelper(
-    std::unique_ptr<OpenXRInputHelper>* input_helper,
-    const OpenXrExtensionHelper& extension_helper) {
-  DCHECK(HasSession());
-  DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL));
-
-  return OpenXRInputHelper::CreateOpenXRInputHelper(instance_, system_,
-                                                    extension_helper, session_,
-                                                    local_space_, input_helper);
-}
-
 XrResult OpenXrApiWrapper::BeginSession() {
   DCHECK(HasSession());
 
@@ -649,6 +640,12 @@
   *right = head_from_eye_views_[1];
 }
 
+std::vector<mojom::XRInputSourceStatePtr> OpenXrApiWrapper::GetInputState(
+    bool hand_input_enabled) {
+  return input_helper_->GetInputState(hand_input_enabled,
+                                      GetPredictedDisplayTime());
+}
+
 XrResult OpenXrApiWrapper::GetLuid(
     LUID* luid,
     const OpenXrExtensionHelper& extension_helper) const {
@@ -675,11 +672,8 @@
   // aren't being requested, this timer loop ensures OpenXR events are
   // occasionally polled while OpenXR is active.
   if (IsInitialized()) {
-    if (base::TimeTicks::Now() - last_process_events_time_ >
-        kTimeBetweenPollingEvents) {
-      if (XR_FAILED(ProcessEvents())) {
-        DCHECK(!session_running_);
-      }
+    if (XR_FAILED(ProcessEvents())) {
+      DCHECK(!session_running_);
     }
 
     // Verify that OpenXR is still active after processing events.
@@ -750,8 +744,8 @@
                XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED) {
       XrEventDataInteractionProfileChanged* interaction_profile_changed =
           reinterpret_cast<XrEventDataInteractionProfileChanged*>(&event_data);
-      DCHECK(interaction_profile_changed->session == session_);
-      interaction_profile_changed_callback_.Run(&xr_result);
+      DCHECK_EQ(interaction_profile_changed->session, session_);
+      xr_result = input_helper_->OnInteractionProfileChanged();
     }
 
     if (XR_FAILED(xr_result)) {
@@ -763,8 +757,6 @@
     xr_result = xrPollEvent(instance_, &event_data);
   }
 
-  last_process_events_time_ = base::TimeTicks::Now();
-
   if (XR_FAILED(xr_result))
     Uninitialize();
   return xr_result;
@@ -874,24 +866,6 @@
   return true;
 }
 
-void OpenXrApiWrapper::RegisterInteractionProfileChangeCallback(
-    const base::RepeatingCallback<void(XrResult*)>&
-        interaction_profile_callback) {
-  interaction_profile_changed_callback_ =
-      std::move(interaction_profile_callback);
-}
-
-void OpenXrApiWrapper::RegisterVisibilityChangeCallback(
-    const base::RepeatingCallback<void(mojom::XRVisibilityState)>&
-        visibility_changed_callback) {
-  visibility_changed_callback_ = std::move(visibility_changed_callback);
-}
-
-void OpenXrApiWrapper::RegisterOnSessionEndedCallback(
-    const base::RepeatingCallback<void()>& on_session_ended_callback) {
-  on_session_ended_callback_ = std::move(on_session_ended_callback);
-}
-
 VRTestHook* OpenXrApiWrapper::test_hook_ = nullptr;
 ServiceTestHook* OpenXrApiWrapper::service_test_hook_ = nullptr;
 void OpenXrApiWrapper::SetTestHook(VRTestHook* hook) {
diff --git a/device/vr/openxr/openxr_api_wrapper.h b/device/vr/openxr/openxr_api_wrapper.h
index 3d56471..9f80a822 100644
--- a/device/vr/openxr/openxr_api_wrapper.h
+++ b/device/vr/openxr/openxr_api_wrapper.h
@@ -35,6 +35,10 @@
 class VRTestHook;
 class ServiceTestHook;
 
+using SessionEndedCallback = base::RepeatingCallback<void()>;
+using VisibilityChangedCallback =
+    base::RepeatingCallback<void(mojom::XRVisibilityState)>;
+
 class OpenXrApiWrapper {
  public:
   OpenXrApiWrapper();
@@ -47,9 +51,11 @@
 
   bool UpdateAndGetSessionEnded();
 
-  XrResult InitSession(const Microsoft::WRL::ComPtr<ID3D11Device>& d3d_device,
-                       std::unique_ptr<OpenXRInputHelper>* input_helper,
-                       const OpenXrExtensionHelper& extension_helper);
+  XrResult InitSession(
+      const Microsoft::WRL::ComPtr<ID3D11Device>& d3d_device,
+      const OpenXrExtensionHelper& extension_helper,
+      const SessionEndedCallback& on_session_ended_callback,
+      const VisibilityChangedCallback& visibility_changed_callback);
 
   XrSpace GetReferenceSpace(device::mojom::XRReferenceSpaceType type) const;
 
@@ -62,6 +68,8 @@
                        base::Optional<gfx::Point3F>* position,
                        bool* emulated_position) const;
   void GetHeadFromEyes(XrView* left, XrView* right) const;
+  std::vector<mojom::XRInputSourceStatePtr> GetInputState(
+      bool hand_input_enabled);
 
   gfx::Size GetViewSize() const;
   XrTime GetPredictedDisplayTime() const;
@@ -69,14 +77,6 @@
                    const OpenXrExtensionHelper& extension_helper) const;
   bool GetStageParameters(XrExtent2Df* stage_bounds,
                           gfx::Transform* local_from_stage);
-  void RegisterInteractionProfileChangeCallback(
-      const base::RepeatingCallback<void(XrResult*)>&
-          interaction_profile_callback);
-  void RegisterVisibilityChangeCallback(
-      const base::RepeatingCallback<void(mojom::XRVisibilityState)>&
-          visibility_changed_callback);
-  void RegisterOnSessionEndedCallback(
-      const base::RepeatingCallback<void()>& on_session_ended_callback);
 
   device::mojom::XREnvironmentBlendMode PickEnvironmentBlendModeForSession(
       device::mojom::XRSessionMode session_mode);
@@ -102,8 +102,6 @@
       const Microsoft::WRL::ComPtr<ID3D11Device>& d3d_device);
   XrResult CreateSwapchain();
   XrResult CreateSpace(XrReferenceSpaceType type, XrSpace* space);
-  XrResult CreateGamepadHelper(std::unique_ptr<OpenXRInputHelper>* input_helper,
-                               const OpenXrExtensionHelper& extension_helper);
 
   XrResult BeginSession();
   XrResult UpdateProjectionLayers();
@@ -127,18 +125,16 @@
   // It is not considered running after creation but before xrBeginSession.
   bool session_running_;
   bool pending_frame_;
-  base::TimeTicks last_process_events_time_;
 
-  base::RepeatingCallback<void(XrResult*)>
-      interaction_profile_changed_callback_;
-  base::RepeatingCallback<void(mojom::XRVisibilityState)>
-      visibility_changed_callback_;
-  base::RepeatingCallback<void()> on_session_ended_callback_;
+  VisibilityChangedCallback visibility_changed_callback_;
+  SessionEndedCallback on_session_ended_callback_;
 
   // Testing objects
   static VRTestHook* test_hook_;
   static ServiceTestHook* service_test_hook_;
 
+  std::unique_ptr<OpenXRInputHelper> input_helper_;
+
   // OpenXR objects
 
   // These objects are valid on successful initialization.
diff --git a/device/vr/openxr/openxr_input_helper.cc b/device/vr/openxr/openxr_input_helper.cc
index ac03f59..48c8e04 100644
--- a/device/vr/openxr/openxr_input_helper.cc
+++ b/device/vr/openxr/openxr_input_helper.cc
@@ -222,17 +222,11 @@
   return input_states;
 }
 
-void OpenXRInputHelper::OnInteractionProfileChanged(XrResult* xr_result) {
+XrResult OpenXRInputHelper::OnInteractionProfileChanged() {
   for (OpenXrControllerState& controller_state : controller_states_) {
-    *xr_result = controller_state.controller.UpdateInteractionProfile();
-    if (XR_FAILED(*xr_result)) {
-      return;
-    }
+    RETURN_IF_XR_FAILED(controller_state.controller.UpdateInteractionProfile());
   }
-}
-
-base::WeakPtr<OpenXRInputHelper> OpenXRInputHelper::GetWeakPtr() {
-  return weak_ptr_factory_.GetWeakPtr();
+  return XR_SUCCESS;
 }
 
 base::Optional<Gamepad> OpenXRInputHelper ::GetWebXRGamepad(
diff --git a/device/vr/openxr/openxr_input_helper.h b/device/vr/openxr/openxr_input_helper.h
index 62bf430..b8be503 100644
--- a/device/vr/openxr/openxr_input_helper.h
+++ b/device/vr/openxr/openxr_input_helper.h
@@ -35,9 +35,7 @@
       bool hand_input_enabled,
       XrTime predicted_display_time);
 
-  void OnInteractionProfileChanged(XrResult* xr_result);
-
-  base::WeakPtr<OpenXRInputHelper> GetWeakPtr();
+  XrResult OnInteractionProfileChanged();
 
  private:
   base::Optional<Gamepad> GetWebXRGamepad(const OpenXrController& controller);
@@ -65,9 +63,6 @@
 
   std::unique_ptr<OpenXRPathHelper> path_helper_;
 
-  // This must be the last member
-  base::WeakPtrFactory<OpenXRInputHelper> weak_ptr_factory_{this};
-
   DISALLOW_COPY_AND_ASSIGN(OpenXRInputHelper);
 };
 
diff --git a/device/vr/openxr/openxr_render_loop.cc b/device/vr/openxr/openxr_render_loop.cc
index 737de2f9..47070861 100644
--- a/device/vr/openxr/openxr_render_loop.cc
+++ b/device/vr/openxr/openxr_render_loop.cc
@@ -63,8 +63,7 @@
   frame_data->time_delta =
       base::TimeDelta::FromNanoseconds(openxr_->GetPredictedDisplayTime());
 
-  frame_data->input_state = input_helper_->GetInputState(
-      hand_input_enabled, openxr_->GetPredictedDisplayTime());
+  frame_data->input_state = openxr_->GetInputState(hand_input_enabled);
 
   frame_data->pose = mojom::VRPose::New();
 
@@ -117,7 +116,6 @@
     StartRuntimeCallback start_runtime_callback) {
   DCHECK(instance_ != XR_NULL_HANDLE);
   DCHECK(!openxr_);
-  DCHECK(!input_helper_);
   DCHECK(!current_display_info_);
 
   // The new wrapper object is stored in a temporary variable instead of
@@ -129,13 +127,20 @@
   if (!openxr)
     return std::move(start_runtime_callback).Run(false);
 
+  SessionEndedCallback on_session_ended_callback = base::BindRepeating(
+      &OpenXrRenderLoop::ExitPresent, weak_ptr_factory_.GetWeakPtr());
+  VisibilityChangedCallback on_visibility_state_changed = base::BindRepeating(
+      &OpenXrRenderLoop::SetVisibilityState, weak_ptr_factory_.GetWeakPtr());
+
   texture_helper_.SetUseBGRA(true);
   LUID luid;
   if (XR_FAILED(openxr->GetLuid(&luid, extension_helper_)) ||
       !texture_helper_.SetAdapterLUID(luid) ||
       !texture_helper_.EnsureInitialized() ||
-      XR_FAILED(openxr->InitSession(texture_helper_.GetDevice(), &input_helper_,
-                                    extension_helper_))) {
+      XR_FAILED(openxr->InitSession(texture_helper_.GetDevice(),
+                                    extension_helper_,
+                                    std::move(on_session_ended_callback),
+                                    std::move(on_visibility_state_changed)))) {
     texture_helper_.Reset();
     return std::move(start_runtime_callback).Run(false);
   }
@@ -145,13 +150,6 @@
   openxr_ = std::move(openxr);
   texture_helper_.SetDefaultSize(openxr_->GetViewSize());
 
-  openxr_->RegisterInteractionProfileChangeCallback(
-      base::BindRepeating(&OpenXRInputHelper::OnInteractionProfileChanged,
-                          input_helper_->GetWeakPtr()));
-  openxr_->RegisterVisibilityChangeCallback(base::BindRepeating(
-      &OpenXrRenderLoop::SetVisibilityState, weak_ptr_factory_.GetWeakPtr()));
-  openxr_->RegisterOnSessionEndedCallback(base::BindRepeating(
-      &OpenXrRenderLoop::ExitPresent, weak_ptr_factory_.GetWeakPtr()));
   InitializeDisplayInfo();
 
   StartContextProviderIfNeeded(std::move(start_runtime_callback));
@@ -162,7 +160,6 @@
   // first, input_helper_destructor will try to call the actual openxr runtime
   // rather than the mock in tests.
   DisposeActiveAnchorCallbacks();
-  input_helper_.reset();
   openxr_ = nullptr;
   current_display_info_ = nullptr;
   texture_helper_.Reset();
diff --git a/device/vr/openxr/openxr_render_loop.h b/device/vr/openxr/openxr_render_loop.h
index b59d295..691f2ce36 100644
--- a/device/vr/openxr/openxr_render_loop.h
+++ b/device/vr/openxr/openxr_render_loop.h
@@ -31,7 +31,6 @@
 namespace device {
 
 class OpenXrApiWrapper;
-class OpenXRInputHelper;
 
 class OpenXrRenderLoop : public XRCompositorCommon,
                          public mojom::XREnvironmentIntegrationProvider,
@@ -139,7 +138,6 @@
   const OpenXrExtensionHelper& extension_helper_;
 
   std::unique_ptr<OpenXrApiWrapper> openxr_;
-  std::unique_ptr<OpenXRInputHelper> input_helper_;
 
   std::vector<CreateAnchorRequest> create_anchor_requests_;
 
diff --git a/docs/webui_in_chrome.md b/docs/webui_in_chrome.md
index bc26be9..f24a9c5 100644
--- a/docs/webui_in_chrome.md
+++ b/docs/webui_in_chrome.md
@@ -72,7 +72,6 @@
 }
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":hello_world" ]
 }
 ```
diff --git a/extensions/common/manifest_handlers/web_accessible_resources_info.cc b/extensions/common/manifest_handlers/web_accessible_resources_info.cc
index c3e7572..20c66cf 100644
--- a/extensions/common/manifest_handlers/web_accessible_resources_info.cc
+++ b/extensions/common/manifest_handlers/web_accessible_resources_info.cc
@@ -36,16 +36,10 @@
       WebAccessibleResourcesManifestKeys::kWebAccessibleResources));
 }  // namespace
 
-base::Optional<URLPattern> GetPatternOrError(std::string relative_path,
-                                             const Extension& extension,
-                                             base::string16* error) {
+URLPattern GetPattern(std::string relative_path, const Extension& extension) {
   URLPattern pattern(URLPattern::SCHEME_EXTENSION);
-  if (pattern.Parse(extension.url().spec()) !=
-      URLPattern::ParseResult::kSuccess) {
-    *error = ErrorUtils::FormatErrorMessageUTF16(
-        errors::kInvalidURLPatternError, extension.url().spec());
-    return base::nullopt;
-  }
+  URLPattern::ParseResult result = pattern.Parse(extension.url().spec());
+  DCHECK_EQ(URLPattern::ParseResult::kSuccess, result);
   while (relative_path[0] == '/')
     relative_path = relative_path.substr(1, relative_path.length() - 1);
   pattern.SetPath(pattern.path() + relative_path);
@@ -64,15 +58,10 @@
   auto info = std::make_unique<WebAccessibleResourcesInfo>();
   URLPatternSet resource_set;
 
-  size_t i = 0;
   for (std::string& web_accessible_resource :
        manifest_keys.web_accessible_resources) {
-    auto pattern =
-        GetPatternOrError(std::move(web_accessible_resource), extension, error);
-    if (!pattern.has_value())
-      return nullptr;
-    resource_set.AddPattern(pattern.value());
-    ++i;
+    resource_set.AddPattern(
+        GetPattern(std::move(web_accessible_resource), extension));
   }
 
   // In extensions where only a resource list is provided (as is the case in
@@ -118,11 +107,7 @@
     // Prepare each key of the web accessible resources.
     URLPatternSet resource_set;
     for (std::string& resource : web_accessible_resource.resources) {
-      auto pattern = GetPatternOrError(std::move(resource), extension, error);
-      if (!pattern.has_value()) {
-        return nullptr;
-      }
-      resource_set.AddPattern(pattern.value());
+      resource_set.AddPattern(GetPattern(std::move(resource), extension));
     }
     URLPatternSet match_set;
     if (web_accessible_resource.matches) {
diff --git a/gpu/vulkan/init/gr_vk_memory_allocator_impl.cc b/gpu/vulkan/init/gr_vk_memory_allocator_impl.cc
index 3d06516..065ba6b 100644
--- a/gpu/vulkan/init/gr_vk_memory_allocator_impl.cc
+++ b/gpu/vulkan/init/gr_vk_memory_allocator_impl.cc
@@ -20,22 +20,8 @@
 
 class GrVkMemoryAllocatorImpl : public GrVkMemoryAllocator {
  public:
-  explicit GrVkMemoryAllocatorImpl(const VkPhysicalDeviceProperties& properties,
-                                   VmaAllocator allocator)
-      : allocator_(allocator) {
-    // On mobile GPUs we avoid using cached cpu memory. The memory is shared
-    // between the gpu and cpu and there probably isn't any win keeping a cached
-    // copy local on the CPU. We have seen examples on ARM where coherent
-    // non-cached memory writes are faster on the cpu than using cached
-    // non-coherent memory. Additionally we don't do a lot of read and writes to
-    // cpu memory in between GPU usues. Our uses are mostly write on CPU then
-    // read on GPU.
-    const auto& vendor_id = properties.vendorID;
-    if (kVendorQualcomm == vendor_id || kVendorARM == vendor_id ||
-        kVendorImagination == vendor_id) {
-      prefer_cached_memory_ = false;
-    }
-  }
+  explicit GrVkMemoryAllocatorImpl(VmaAllocator allocator)
+      : allocator_(allocator) {}
   ~GrVkMemoryAllocatorImpl() override = default;
 
   GrVkMemoryAllocatorImpl(const GrVkMemoryAllocatorImpl&) = delete;
@@ -94,22 +80,18 @@
         info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
         info.preferredFlags = 0;
         break;
-      case BufferUsage::kCpuOnly:
+      case BufferUsage::kCpuWritesGpuReads:
         info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
                              VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
-        info.preferredFlags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
-        break;
-      case BufferUsage::kCpuWritesGpuReads:
-        info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
-        if (prefer_cached_memory_)
-          info.requiredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
-
         info.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
         break;
-      case BufferUsage::kGpuWritesCpuReads:
+      case BufferUsage::kTransfersFromCpuToGpu:
+        info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+                             VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+        break;
+      case BufferUsage::kTransfersFromGpuToCpu:
         info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
-        info.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
-                              VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
+        info.preferredFlags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
         break;
     }
 
@@ -130,15 +112,6 @@
     VmaAllocation allocation;
     VkResult result = vma::AllocateMemoryForBuffer(allocator_, buffer, &info,
                                                    &allocation, nullptr);
-    if (VK_SUCCESS != result) {
-      if (usage == BufferUsage::kCpuWritesGpuReads) {
-        // We try again but this time drop the requirement for cached
-        info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
-        result = vma::AllocateMemoryForBuffer(allocator_, buffer, &info,
-                                              &allocation, nullptr);
-      }
-    }
-
     if (VK_SUCCESS == result)
       *backend_memory = reinterpret_cast<GrVkBackendMemory>(allocation);
 
@@ -227,16 +200,13 @@
   }
 
   const VmaAllocator allocator_;
-  bool prefer_cached_memory_ = true;
 };
 
 }  // namespace
 
 sk_sp<GrVkMemoryAllocator> CreateGrVkMemoryAllocator(
     VulkanDeviceQueue* device_queue) {
-  return sk_make_sp<GrVkMemoryAllocatorImpl>(
-      device_queue->vk_physical_device_properties(),
-      device_queue->vma_allocator());
+  return sk_make_sp<GrVkMemoryAllocatorImpl>(device_queue->vma_allocator());
 }
 
 }  // namespace gpu
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index 9ee8905e..aaaa16ac 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -46100,6 +46100,9 @@
       properties: "{\"$build/code_coverage\":{\"use_clang_coverage\":true},\"$build/goma\":{\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\",\"use_luci_auth\":true},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"$recipe_engine/isolated\":{\"server\":\"https://isolateserver.appspot.com\"},\"builder_group\":\"tryserver.chromium.mac\",\"recipe\":\"chromium_trybot\"}"
       execution_timeout_secs: 14400
       expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
       caches {
         name: "win_toolchain"
         path: "win_toolchain"
diff --git a/infra/config/main.star b/infra/config/main.star
index 0924c00..82acf94 100755
--- a/infra/config/main.star
+++ b/infra/config/main.star
@@ -10,7 +10,7 @@
 load("//project.star", "settings")
 
 lucicfg.check_version(
-    min = "1.21.5",
+    min = "1.22.1",
     message = "Update depot_tools",
 )
 
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index ddea92a..8b55809 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -1312,6 +1312,7 @@
     main_list_view = "try",
     os = os.MAC_DEFAULT,
     tryjob = try_.job(),
+    grace_period = 2 * time.minute,
 )
 
 try_.chromium_mac_builder(
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index 98c44ea..95ee153 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -256,6 +256,7 @@
     "//ios/chrome/browser/screenshot",
     "//ios/chrome/browser/search_engines",
     "//ios/chrome/browser/search_engines:extension_search_engine_data_updater",
+    "//ios/chrome/browser/sessions:scene_util",
     "//ios/chrome/browser/share_extension",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/snapshots",
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index dfba32f..794cbd3 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -81,6 +81,7 @@
 #import "ios/chrome/browser/search_engines/extension_search_engine_data_updater.h"
 #include "ios/chrome/browser/search_engines/search_engines_util.h"
 #include "ios/chrome/browser/search_engines/template_url_service_factory.h"
+#import "ios/chrome/browser/sessions/scene_util.h"
 #import "ios/chrome/browser/share_extension/share_extension_service.h"
 #import "ios/chrome/browser/share_extension/share_extension_service_factory.h"
 #include "ios/chrome/browser/signin/authentication_service_delegate.h"
@@ -486,10 +487,13 @@
   // browser state.
   BOOL needRestoration = NO;
   if (isPostCrashLaunch) {
-    NSSet<NSString*>* sessions =
-        base::ios::IsMultiwindowSupported()
-            ? [[PreviousSessionInfo sharedInstance] connectedSceneSessionsIDs]
-            : [NSSet setWithArray:@[ @"" ]];
+    NSSet<NSString*>* sessions = nil;
+    if (@available(ios 13, *)) {
+      sessions =
+          [[PreviousSessionInfo sharedInstance] connectedSceneSessionsIDs];
+    } else {
+      sessions = [NSSet setWithObjects:SessionIdentifierForScene(nil), nil];
+    }
 
     needRestoration = [CrashRestoreHelper moveAsideSessions:sessions
                                             forBrowserState:chromeBrowserState];
diff --git a/ios/chrome/app/strings/ios_chromium_strings.grd b/ios/chrome/app/strings/ios_chromium_strings.grd
index af01519..634f979 100644
--- a/ios/chrome/app/strings/ios_chromium_strings.grd
+++ b/ios/chrome/app/strings/ios_chromium_strings.grd
@@ -200,7 +200,7 @@
         Sign out of Chromium?
       </message>
      <message name="IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE" desc="The information text on the disconnect footer describing signout for non-managed accounts [Length: 400em].">
-      By signing out, your bookmarks, history, passwords, and other Chromium data will no longer be synced to your Google Account.
+        When you sign out, Chromium won't sync any new data to your Google Account. Data previously synced stays in the account.
       </message>
       <message name="IDS_IOS_ENTERPRISE_SIGNED_OUT_SUBTEXT" desc="Text displayed in an alert when the user is signed out due to browser sign-in becoming disabled by policy. [iOS only]">
         You can still see all your bookmarks, history, passwords and other settings on this device. If you make changes, they won't sync to your account.
@@ -328,11 +328,11 @@
       <message name="IDS_IOS_SIGN_IN_TO_CHROME_SETTING_TITLE" desc="The title for the setting item to Sign in to Chromium [iOS only]">
         Sign in to Chromium
       </message>
-      <message name="IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT" desc="The signout dialog title. This dialog is used when the user have a managed account (other than gmail account). Related to IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT [iOS only].">
-        Clear your Chromium data from this device?
+      <message name="IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_ACCOUNT" desc="The signout dialog title. This dialog is used when the user have a gmail account and sync is turned on [iOS only].">
+        Choose whether to clear your Chromium data from this device or keep it?
       </message>
-      <message name="IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT" desc="The signout dialog message. This dialog is used when the user have a managed account (other than gmail account). Related to IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT [iOS only].">
-        Because your account is managed by <ph name="HOSTED_DOMAIN">$1<ex>google.com</ex></ph>, your Chromium data will be deleted from this device when signing out. Your synced data will remain in your Google Account.
+      <message name="IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_MANAGED_ACCOUNT" desc="The signout dialog title. This dialog is used when the user have a managed account (other than gmail account) [iOS only].">
+        Your account is managed by <ph name="HOSTED_DOMAIN">$1<ex>google.com</ex></ph>, so your Chromium data will be cleared from this device.
       </message>
       <message name="IDS_IOS_TAB_SWITCHER_NO_TABS_TO_SYNC_PROMO" desc="The title of the paragraph IDS_OPEN_TABS_NO_SESSION_INSTRUCTIONS_IOS. The paragraph explains that tabs opened on other devices where the user is signed in will appear here, in the Tab Switcher. [iOS only]">
         Use Chromium Everywhere
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE.png.sha1
index 734f2f8..a887b0e 100644
--- a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE.png.sha1
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE.png.sha1
@@ -1 +1 @@
-e7fd44224def205c0e32a24d7d1b44ba1f720322
\ No newline at end of file
+28d7896784e8f89b8517adf245c1098c7c5e6a8c
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT.png.sha1
deleted file mode 100644
index f7604d6..0000000
--- a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e724b449ea5d830f1c7baf29d9a199237fca2b41
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT.png.sha1
deleted file mode 100644
index f7604d6..0000000
--- a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e724b449ea5d830f1c7baf29d9a199237fca2b41
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_ACCOUNT.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_ACCOUNT.png.sha1
new file mode 100644
index 0000000..ce8f9ad
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_ACCOUNT.png.sha1
@@ -0,0 +1 @@
+121bb43bc0639201d3e42c14a3bf03f0245fc717
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_MANAGED_ACCOUNT.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_MANAGED_ACCOUNT.png.sha1
new file mode 100644
index 0000000..70b76fa
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_MANAGED_ACCOUNT.png.sha1
@@ -0,0 +1 @@
+782db7cd5c3c3d4b4b8a0301a37b0909f5f3f8b6
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings.grd b/ios/chrome/app/strings/ios_google_chrome_strings.grd
index 91edde4..5e55a4d 100644
--- a/ios/chrome/app/strings/ios_google_chrome_strings.grd
+++ b/ios/chrome/app/strings/ios_google_chrome_strings.grd
@@ -200,7 +200,7 @@
         Sign out of Chrome?
       </message>
      <message name="IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE" desc="The information text on the disconnect footer describing signout for non-managed accounts [Length: 400em].">
-      By signing out, your bookmarks, history, passwords, and other Chrome data will no longer be synced to your Google Account.
+       When you sign out, Chrome won't sync any new data to your Google Account. Data previously synced stays in the account.
       </message>
       <message name="IDS_IOS_ENTERPRISE_SIGNED_OUT_SUBTEXT" desc="Text displayed in an alert when the user is signed out due to browser sign-in becoming disabled by policy. [iOS only]">
         You can still see all your bookmarks, history, passwords and other settings on this device. If you make changes, they won't sync to your Google Account.
@@ -328,11 +328,11 @@
       <message name="IDS_IOS_SIGN_IN_TO_CHROME_SETTING_TITLE" desc="The title for the setting item to Sign in to Chrome [iOS only]">
         Sign in to Chrome
       </message>
-      <message name="IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT" desc="The signout dialog title. This dialog is used when the user have a managed account (other than gmail account). Related to IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT [iOS only].">
-        Clear your Chrome data from this device?
+      <message name="IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_ACCOUNT" desc="The signout dialog title. This dialog is used when the user have a gmail account and sync is turned on [iOS only].">
+        Choose whether to clear your Chrome data from this device or keep it?
       </message>
-      <message name="IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT" desc="The signout dialog message. This dialog is used when the user have a managed account (other than gmail account). Related to IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT [iOS only].">
-        Because your account is managed by <ph name="HOSTED_DOMAIN">$1<ex>google.com</ex></ph>, your Chrome data will be deleted from this device when signing out. Your synced data will remain in your Google Account.
+      <message name="IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_MANAGED_ACCOUNT" desc="The signout dialog title. This dialog is used when the user have a managed account (other than gmail account) [iOS only].">
+        Your account is managed by <ph name="HOSTED_DOMAIN">$1<ex>google.com</ex></ph>, so your Chrome data will be cleared from this device.
       </message>
       <message name="IDS_IOS_TAB_SWITCHER_NO_TABS_TO_SYNC_PROMO" desc="The title of the paragraph IDS_OPEN_TABS_NO_SESSION_INSTRUCTIONS_IOS. The paragraph explains that tabs opened on other devices where the user is signed in will appear here, in the Tab Switcher. [iOS only]">
         Use Chrome Everywhere
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE.png.sha1
index 734f2f8..4855973 100644
--- a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE.png.sha1
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE.png.sha1
@@ -1 +1 @@
-e7fd44224def205c0e32a24d7d1b44ba1f720322
\ No newline at end of file
+a88920b5cc0738f62584fa3ab66334083b35e4bf
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT.png.sha1
deleted file mode 100644
index f7604d6..0000000
--- a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e724b449ea5d830f1c7baf29d9a199237fca2b41
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT.png.sha1
deleted file mode 100644
index f7604d6..0000000
--- a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e724b449ea5d830f1c7baf29d9a199237fca2b41
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_ACCOUNT.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_ACCOUNT.png.sha1
new file mode 100644
index 0000000..9f7fee98
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_ACCOUNT.png.sha1
@@ -0,0 +1 @@
+9791c920a124f11dbb6fc148f3a7e66a78823838
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_MANAGED_ACCOUNT.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_MANAGED_ACCOUNT.png.sha1
new file mode 100644
index 0000000..29dfec3f
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_MANAGED_ACCOUNT.png.sha1
@@ -0,0 +1 @@
+379acf579d224e62de617d5f1ace905ac55fd151
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 29b4305..4805148 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -691,9 +691,6 @@
       <message name="IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE" desc="The title of the continue button on the disconnect dialog [Length: 20em].">
         Sign Out
       </message>
-      <message name="IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE_MICE" desc="The title of the continue button on the disconnect dialog [Length: 20em].">
-        Sign Out…
-      </message>
       <message name="IDS_IOS_DISCONNECT_DIALOG_CONTINUE_AND_CLEAR_MOBILE" desc="The title of the continue and clear browsing data button on the disconnect dialog [iOS only].">
         Sign Out and Clear Data from this Device
       </message>
@@ -1352,6 +1349,9 @@
       <message name="IDS_IOS_OPTIONS_PRIVACY_GOOGLE_SERVICES_FOOTER" desc="Footer to invite the user to open the Sync and Google Services settings.">
         For more settings that relate to privacy, security, and data collection, see <ph name="BEGIN_LINK">BEGIN_LINK</ph>Sync and Google Services<ph name="END_LINK">END_LINK</ph>.
       </message>
+      <message name="IDS_IOS_PRIVACY_GOOGLE_SERVICES_FOOTER" desc="Footer to invite the user to open the Google Services settings.">
+        For more settings that relate to privacy, security, and data collection, see <ph name="BEGIN_LINK">BEGIN_LINK</ph>Google Services<ph name="END_LINK">END_LINK</ph>.
+      </message>
       <message name="IDS_IOS_OPTIONS_REPORT_AN_ISSUE" desc="Title for the option on Settings page to report an issue. [Length: 20em] [iOS only]">
         Report an Issue
       </message>
@@ -1896,6 +1896,9 @@
       <message name="IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED_INFO" desc="String in the popover for the Safe Browsing check to direct the user to the Sync and Google Services page if Safe Browsing is disabled">
         To turn on Safe Browsing, open <ph name="BEGIN_LINK">BEGIN_LINK</ph>Sync and Google Services<ph name="END_LINK">END_LINK</ph> and tap Safe Browsing.
       </message>
+      <message name="IDS_IOS_SETTINGS_SAFETY_CHECK_OPEN_SAFE_BROWSING_INFO" desc="String in the popover for the Safe Browsing check to direct the user to the Google Services page if Safe Browsing is disabled">
+        To turn on Safe Browsing, open <ph name="BEGIN_LINK">BEGIN_LINK</ph>Google Services<ph name="END_LINK">END_LINK</ph> and tap Safe Browsing.
+      </message>
       <message name="IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_TITLE" desc="Title for the updates element of safety check" meaning="Row title to display the user's Chrome update status, and access upates [CHAR_LIMIT=20]">
         Updates
       </message>
@@ -2155,16 +2158,10 @@
       Signed in as <ph name="USER">$1<ex>Jane Doe</ex></ph>
       </message>
       <message name="IDS_IOS_SIGNOUT_DIALOG_CLEAR_DATA_BUTTON" desc="This button signs the user out and clear all the data from the user device. The data is still remain in their Google account. Related to IDS_IOS_SIGNOUT_DIALOG_TITLE [iOS only].">
-        Clear from this Device
+        Clear Data
       </message>
       <message name="IDS_IOS_SIGNOUT_DIALOG_KEEP_DATA_BUTTON" desc="This button signs the user out and all its data stays on the device. Related to IDS_IOS_SIGNOUT_DIALOG_TITLE [iOS only].">
-        Keep on this Device
-      </message>
-      <message name="IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_SYNC" desc="The signout dialog message. This dialog is used when the user have a gmail account and sync is turned on. Related to IDS_IOS_SIGNOUT_DIALOG_TITLE [iOS only].">
-        Your synced data will remain in your Google Account.
-      </message>
-      <message name="IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNC" desc="The signout dialog title. This dialog is used when the user have a gmail account and sync is turned on. Related to IDS_IOS_SIGNOUT_DIALOG_MESSAGE [iOS only].">
-        What would you like to do with your bookmarks, history and other data on this device?
+        Keep Data
       </message>
       <message name="IDS_IOS_SIGNOUT_DIALOG_TITLE_WITHOUT_SYNC" desc="The signout dialog title. This dialog is used when the user have a gmail account and sync is turned on. Related to IDS_IOS_SIGNOUT_DIALOG_MESSAGE [iOS only].">
         Sign Out?
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE_MICE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE_MICE.png.sha1
deleted file mode 100644
index 734f2f8..0000000
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE_MICE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e7fd44224def205c0e32a24d7d1b44ba1f720322
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PRIVACY_GOOGLE_SERVICES_FOOTER.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PRIVACY_GOOGLE_SERVICES_FOOTER.png.sha1
new file mode 100644
index 0000000..9a53da0
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PRIVACY_GOOGLE_SERVICES_FOOTER.png.sha1
@@ -0,0 +1 @@
+314c102fbe23176ecf888f1f6ddf58575d3138de
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SETTINGS_SAFETY_CHECK_OPEN_SAFE_BROWSING_INFO.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SETTINGS_SAFETY_CHECK_OPEN_SAFE_BROWSING_INFO.png.sha1
new file mode 100644
index 0000000..2a9625d
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SETTINGS_SAFETY_CHECK_OPEN_SAFE_BROWSING_INFO.png.sha1
@@ -0,0 +1 @@
+191c3cdee08172f164c5c0317fc12ea5be9ed7a1
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_CLEAR_DATA_BUTTON.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_CLEAR_DATA_BUTTON.png.sha1
index bdfbcfbe..ce8f9ad 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_CLEAR_DATA_BUTTON.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_CLEAR_DATA_BUTTON.png.sha1
@@ -1 +1 @@
-875c95fb6efb766f12d51f4574ed92a4b4db574a
\ No newline at end of file
+121bb43bc0639201d3e42c14a3bf03f0245fc717
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_KEEP_DATA_BUTTON.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_KEEP_DATA_BUTTON.png.sha1
index bdfbcfbe..ce8f9ad 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_KEEP_DATA_BUTTON.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_KEEP_DATA_BUTTON.png.sha1
@@ -1 +1 @@
-875c95fb6efb766f12d51f4574ed92a4b4db574a
\ No newline at end of file
+121bb43bc0639201d3e42c14a3bf03f0245fc717
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_SYNC.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_SYNC.png.sha1
deleted file mode 100644
index bdfbcfbe..0000000
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_SYNC.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-875c95fb6efb766f12d51f4574ed92a4b4db574a
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNC.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNC.png.sha1
deleted file mode 100644
index bdfbcfbe..0000000
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNC.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-875c95fb6efb766f12d51f4574ed92a4b4db574a
\ No newline at end of file
diff --git a/ios/chrome/browser/crash_report/crash_restore_helper.mm b/ios/chrome/browser/crash_report/crash_restore_helper.mm
index dc54510..4024028 100644
--- a/ios/chrome/browser/crash_report/crash_restore_helper.mm
+++ b/ios/chrome/browser/crash_report/crash_restore_helper.mm
@@ -332,11 +332,6 @@
 
 + (NSString*)backupPathForSessionID:(NSString*)sessionID
                           directory:(const base::FilePath&)directory {
-  // TODO(crbug.com/1165798): remove when the sessionID is guaranteed to
-  // always be an non-empty string.
-  if (!sessionID.length)
-    return PathAsNSString(directory.Append(kSessionBackupFileName));
-
   return PathAsNSString(directory.Append(kSessionBackupDirectory)
                             .Append(base::SysNSStringToUTF8(sessionID))
                             .Append(kSessionBackupFileName));
@@ -344,8 +339,6 @@
 
 + (NSArray<NSString*>*)backedupSessionIDsForBrowserState:
     (ChromeBrowserState*)browserState {
-  if (!base::ios::IsMultiwindowSupported())
-    return @[ @"" ];
   const base::FilePath backupDirectory =
       browserState->GetStatePath().Append(kSessionBackupDirectory);
   return [[NSFileManager defaultManager]
diff --git a/ios/chrome/browser/crash_report/crash_restore_helper_unittest.mm b/ios/chrome/browser/crash_report/crash_restore_helper_unittest.mm
index 9d80362..fc8f0e0 100644
--- a/ios/chrome/browser/crash_report/crash_restore_helper_unittest.mm
+++ b/ios/chrome/browser/crash_report/crash_restore_helper_unittest.mm
@@ -116,17 +116,6 @@
   CrashRestoreHelper* helper_;
 };
 
-// Tests that moving session work correctly when multiple windows are not
-// supported.
-TEST_F(CrashRestoreHelperTest, MoveAsideSingleSession) {
-  ASSERT_TRUE(CreateSession(nil));
-  [CrashRestoreHelper moveAsideSessions:[NSSet setWithArray:@[ @"" ]]
-                        forBrowserState:chrome_browser_state_.get()];
-  EXPECT_TRUE(IsSessionErased(nil));
-  EXPECT_EQ(YES,
-            CheckAndDeleteSessionBackedUp(nil, chrome_browser_state_.get()));
-}
-
 // Tests that moving session work correctly when multiple windows are supported.
 TEST_F(CrashRestoreHelperTest, MoveAsideMultipleSessions) {
   NSSet<NSString*>* session_ids =
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index b4aaa7c..5778776 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -592,7 +592,8 @@
     {"ios-shared-highlighting-color-change",
      flag_descriptions::kIOSSharedHighlightingColorChangeName,
      flag_descriptions::kIOSSharedHighlightingColorChangeDescription,
-     flags_ui::kOsIos, FEATURE_VALUE_TYPE(kIOSSharedHighlightingColorChange)},
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(web::features::kIOSSharedHighlightingColorChange)},
     {"ios-persist-crash-restore-infobar",
      flag_descriptions::kIOSPersistCrashRestoreName,
      flag_descriptions::kIOSPersistCrashRestoreDescription, flags_ui::kOsIos,
diff --git a/ios/chrome/browser/safe_browsing/BUILD.gn b/ios/chrome/browser/safe_browsing/BUILD.gn
index 8fe701d..2cb1290 100644
--- a/ios/chrome/browser/safe_browsing/BUILD.gn
+++ b/ios/chrome/browser/safe_browsing/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//ios/build/config.gni")
 import("//ios/features.gni")
+import("//ios/web/js_compile.gni")
 
 source_set("safe_browsing") {
   sources = [
@@ -11,6 +12,8 @@
     "chrome_password_protection_service.mm",
     "chrome_password_protection_service_factory.h",
     "chrome_password_protection_service_factory.mm",
+    "password_protection_java_script_feature.h",
+    "password_protection_java_script_feature.mm",
     "pending_unsafe_resource_storage.h",
     "pending_unsafe_resource_storage.mm",
     "real_time_url_lookup_service_factory.h",
@@ -35,6 +38,7 @@
   ]
 
   deps = [
+    ":password_protection_js",
     ":util",
     "//base",
     "//build:branding_buildflags",
@@ -79,6 +83,7 @@
     "//ios/web/common:user_agent",
     "//ios/web/public",
     "//ios/web/public/init",
+    "//ios/web/public/js_messaging",
     "//mojo/public/cpp/bindings",
     "//net",
     "//services/network:network_service",
@@ -90,6 +95,13 @@
   configs += [ "//build/config/compiler:enable_arc" ]
 }
 
+js_compile_bundle("password_protection_js") {
+  visibility = [ ":safe_browsing" ]
+  closure_entry_point = "__crWeb.passwordProtection"
+
+  sources = [ "resources/password_protection.js" ]
+}
+
 source_set("test_support") {
   testonly = true
   sources = [
diff --git a/ios/chrome/browser/safe_browsing/README.md b/ios/chrome/browser/safe_browsing/README.md
new file mode 100644
index 0000000..bcc9d7d8
--- /dev/null
+++ b/ios/chrome/browser/safe_browsing/README.md
@@ -0,0 +1,18 @@
+## PasswordProtectionJavaScriptFeature
+
+Password Protection (also known as "PhishGuard") is a feature that warns a user
+when they type one of their saved passwords on an unrelated site that is not
+known to be safe.
+
+In order to detect these events, this feature needs to detect key presses and
+text paste events, so that entered text can be compared to saved passwords.
+
+Embedders of content/ can detect such events using a
+RenderWidgetHost::InputEventObserver. Since WKWebView doesn't provide an
+equivalent API for its embedders, these events can only be detected by
+injecting JavaScript into each page. PasswordProtectionJavaScriptFeature
+implements this JavaScript logic.
+
+To ensure that a malicious page cannot interfere with this detection logic,
+this JavaScript code is injected into an isolated world, where it is not
+visible to page script.
diff --git a/ios/chrome/browser/safe_browsing/password_protection_java_script_feature.h b/ios/chrome/browser/safe_browsing/password_protection_java_script_feature.h
new file mode 100644
index 0000000..1118060
--- /dev/null
+++ b/ios/chrome/browser/safe_browsing/password_protection_java_script_feature.h
@@ -0,0 +1,27 @@
+// 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 IOS_CHROME_BROWSER_SAFE_BROWSING_PASSWORD_PROTECTION_JAVA_SCRIPT_FEATURE_H_
+#define IOS_CHROME_BROWSER_SAFE_BROWSING_PASSWORD_PROTECTION_JAVA_SCRIPT_FEATURE_H_
+
+#include "ios/web/public/js_messaging/java_script_feature.h"
+
+// A JavaScriptFeature that detects key presses and paste actions in the web
+// content area.
+class PasswordProtectionJavaScriptFeature : public web::JavaScriptFeature {
+ public:
+  PasswordProtectionJavaScriptFeature();
+  ~PasswordProtectionJavaScriptFeature() override;
+
+  // This feature holds no state, so only a single static instance is ever
+  // needed.
+  static PasswordProtectionJavaScriptFeature* GetInstance();
+
+  // JavaScriptFeature:
+  base::Optional<std::string> GetScriptMessageHandlerName() const override;
+  void ScriptMessageReceived(web::BrowserState* browser_state,
+                             WKScriptMessage* message) override;
+};
+
+#endif  // IOS_CHROME_BROWSER_SAFE_BROWSING_PASSWORD_PROTECTION_JAVA_SCRIPT_FEATURE_H_
diff --git a/ios/chrome/browser/safe_browsing/password_protection_java_script_feature.mm b/ios/chrome/browser/safe_browsing/password_protection_java_script_feature.mm
new file mode 100644
index 0000000..ea4c46d
--- /dev/null
+++ b/ios/chrome/browser/safe_browsing/password_protection_java_script_feature.mm
@@ -0,0 +1,94 @@
+// 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.
+
+#import "ios/chrome/browser/safe_browsing/password_protection_java_script_feature.h"
+
+#import <WebKit/WebKit.h>
+
+#import "base/ios/ios_util.h"
+#include "base/logging.h"
+#import "base/strings/sys_string_conversions.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+const char kScriptFilename[] = "password_protection_js";
+
+const char kTextEnteredHandlerName[] = "PasswordProtectionTextEntered";
+
+// Values for the "eventType" field in messages received by this feature's
+// script message handler.
+const char kPasteEventType[] = "TextPasted";
+const char kKeyPressedEventType[] = "KeyPressed";
+}  // namespace
+
+PasswordProtectionJavaScriptFeature::PasswordProtectionJavaScriptFeature()
+    : JavaScriptFeature(ContentWorld::kAnyContentWorld,
+                        {FeatureScript::CreateWithFilename(
+                            kScriptFilename,
+                            FeatureScript::InjectionTime::kDocumentStart,
+                            FeatureScript::TargetFrames::kAllFrames,
+                            FeatureScript::ReinjectionBehavior::
+                                kReinjectOnDocumentRecreation)},
+                        {}) {
+  // This feature depends on JavaScript isolated worlds for security, so must
+  // only be used on iOS 14+.
+  DCHECK(base::ios::IsRunningOnIOS14OrLater());
+}
+
+PasswordProtectionJavaScriptFeature::~PasswordProtectionJavaScriptFeature() =
+    default;
+
+// static
+PasswordProtectionJavaScriptFeature*
+PasswordProtectionJavaScriptFeature::GetInstance() {
+  static std::unique_ptr<PasswordProtectionJavaScriptFeature> feature = nullptr;
+  if (!feature) {
+    feature = std::make_unique<PasswordProtectionJavaScriptFeature>();
+  }
+  return feature.get();
+}
+
+base::Optional<std::string>
+PasswordProtectionJavaScriptFeature::GetScriptMessageHandlerName() const {
+  return kTextEnteredHandlerName;
+}
+
+void PasswordProtectionJavaScriptFeature::ScriptMessageReceived(
+    web::BrowserState* browser_state,
+    WKScriptMessage* message) {
+  // Verify that the message is well-formed before using it.
+  if (![message.body isKindOfClass:[NSDictionary class]])
+    return;
+
+  NSString* eventType = message.body[@"eventType"];
+  if (!eventType || ![eventType isKindOfClass:[NSString class]] ||
+      ![eventType length]) {
+    return;
+  }
+
+  NSString* text = message.body[@"text"];
+  if (!text || ![text isKindOfClass:[NSString class]] || ![text length]) {
+    return;
+  }
+
+  std::string event_type_str = base::SysNSStringToUTF8(eventType);
+  std::string text_str = base::SysNSStringToUTF8(text);
+
+  if (event_type_str == kKeyPressedEventType) {
+    // A keypress event should consist of a single character. A longer string
+    // means the message isn't well-formed, so might be coming from a
+    // compromised WebProcess.
+    if (text_str.size() > 1)
+      return;
+
+    // TODO(crbug.com/1147970): Forward the entered key to
+    // PasswordReuseDetectionMananger.
+  } else if (event_type_str == kPasteEventType) {
+    // TODO(crbug.com/1147970): Forward the pasted text to
+    // PasswordReuseDetectionMananger.
+  }
+}
diff --git a/ios/chrome/browser/safe_browsing/resources/password_protection.js b/ios/chrome/browser/safe_browsing/resources/password_protection.js
new file mode 100644
index 0000000..726fab7
--- /dev/null
+++ b/ios/chrome/browser/safe_browsing/resources/password_protection.js
@@ -0,0 +1,44 @@
+// 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.
+
+goog.provide('__crWeb.passwordProtection');
+
+/*
+* @fileoverview Adds listeners that forward keypress and paste events to the
+* browser. The browser uses this information to detect and warn the user about
+* situations where the user enters one of their saved passwords on a
+* possibly-unsafe site
+*/
+
+/* Beginning of anonymous object. */
+(function() {
+
+/**
+ * Listens for keypress events and forwards the entered key to the browser.
+ */
+function onKeypressEvent(event) {
+  // Only forward events where the entered key has length 1, to avoid forwarding
+  // special keys like "Enter".
+  if (event.isTrusted && event.key.length == 1) {
+    window.webkit.messageHandlers['PasswordProtectionTextEntered'].postMessage(
+        {eventType: 'KeyPressed', text: event.key});
+  }
+}
+
+/**
+ * Listens for paste events and forwards the pasted text to the browser.
+ */
+function onPasteEvent(event) {
+  if (event.isTrusted) {
+    var text = event.clipboardData.getData('text');
+    window.webkit.messageHandlers['PasswordProtectionTextEntered'].postMessage(
+        {eventType: 'TextPasted', text: text});
+  }
+}
+
+// Events are first dispatched to the window object, in the capture phase of
+// JavaScript event dispatch, so listen for them there.
+window.addEventListener('keypress', onKeypressEvent, true);
+window.addEventListener('paste', onPasteEvent, true);
+}());  // End of anonymous object
diff --git a/ios/chrome/browser/sessions/BUILD.gn b/ios/chrome/browser/sessions/BUILD.gn
index 433f1d5f..2f24f58 100644
--- a/ios/chrome/browser/sessions/BUILD.gn
+++ b/ios/chrome/browser/sessions/BUILD.gn
@@ -16,6 +16,7 @@
   ]
   public_deps = [ "//components/sessions" ]
   deps = [
+    ":scene_util",
     ":serialisation",
     "//base",
     "//components/keyed_service/core",
@@ -36,6 +37,19 @@
   frameworks = [ "UIKit.framework" ]
 }
 
+source_set("scene_util") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "scene_util.h",
+    "scene_util.mm",
+  ]
+  deps = [ "//base" ]
+  frameworks = [
+    "Foundation.framework",
+    "UIKit.framework",
+  ]
+}
+
 source_set("restoration_observer") {
   sources = [ "session_restoration_observer.h" ]
   deps = [ "//base" ]
@@ -73,6 +87,7 @@
     "session_service_ios.mm",
   ]
   deps = [
+    ":scene_util",
     ":serialisation",
     "//base",
     "//ios/chrome/browser/web_state_list",
@@ -120,6 +135,7 @@
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
   sources = [
+    "scene_util_unittest.mm",
     "session_restoration_browser_agent_unittest.mm",
     "session_service_ios_unittest.mm",
     "session_window_ios_unittest.mm",
@@ -128,6 +144,7 @@
     ":resources_unit_tests",
     ":restoration_agent",
     ":restoration_observer",
+    ":scene_util",
     ":serialisation",
     ":session_service",
     ":sessions",
diff --git a/ios/chrome/browser/sessions/scene_util.h b/ios/chrome/browser/sessions/scene_util.h
new file mode 100644
index 0000000..71af9e9
--- /dev/null
+++ b/ios/chrome/browser/sessions/scene_util.h
@@ -0,0 +1,44 @@
+// 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 IOS_CHROME_BROWSER_SESSIONS_SCENE_UTIL_H_
+#define IOS_CHROME_BROWSER_SESSIONS_SCENE_UTIL_H_
+
+#import <Foundation/Foundation.h>
+
+#include "base/files/file_path.h"
+#include "base/strings/string_piece.h"
+
+// Name of the file storing the list of tabs.
+extern const base::FilePath::CharType kSessionFileName[];
+
+// Name of the directory containing the tab snapshots.
+extern const base::FilePath::CharType kSnapshotsDirectoryName[];
+
+// Returns the path for the directory relative to the BrowserState |directory|
+// that contains the scene session specific storage. All values returned by
+// |SessionPathForDirectory| will be below the path returned by this function.
+base::FilePath SessionsDirectoryForDirectory(const base::FilePath& directory);
+
+// Returns the path for file or directory named |name| associated with a scene
+// identified by |session_identifier| and located relative to the BrowserState
+// |directory|.
+base::FilePath SessionPathForDirectory(const base::FilePath& directory,
+                                       NSString* session_identifier,
+                                       base::StringPiece name);
+
+// Migrates the list of tabs and snapshots for a BrowserState's |directory|
+// to the given |session_identifier|. The |previous_session_identifier| if
+// non-nil is used as a possible previous session for the case of migration
+// between devices (in case of backup restoration).
+void MigrateSessionStorageForDirectory(const base::FilePath& directory,
+                                       NSString* session_identifier,
+                                       NSString* previous_session_identifier);
+
+// Returns the identifier to use for the session for |scene|. Note that |scene|
+// is a UIScene* but passed as a id to allow calling this function even if the
+// application is build with a deployment target older than iOS 13.
+NSString* SessionIdentifierForScene(id scene);
+
+#endif  // IOS_CHROME_BROWSER_SESSIONS_SCENE_UTIL_H_
diff --git a/ios/chrome/browser/sessions/scene_util.mm b/ios/chrome/browser/sessions/scene_util.mm
new file mode 100644
index 0000000..8e5f7169
--- /dev/null
+++ b/ios/chrome/browser/sessions/scene_util.mm
@@ -0,0 +1,208 @@
+// 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.
+
+#import "ios/chrome/browser/sessions/scene_util.h"
+
+#import <UIKit/UIKit.h>
+
+#include "base/ios/ios_util.h"
+#include "base/mac/foundation_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/sys_string_conversions.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+// Enumeration used to represent the type of session migration that took place.
+// The enumerator must not be removed or their order changed as the histogram
+// IOS.SessionMigration is recording them.
+enum class SessionMigration {
+  kNoMigration = 0,
+  kMigrationFailed = 1,
+  kMigrationNoSessionToMigrate = 2,
+  kMigrationPreMWToMultiScenes = 3,
+  kMigrationPreMWToSingleScene = 4,
+  kMigrationSingleSceneToMultiScenes = 5,
+  kMigrationMultiScenesToSingleScene = 6,
+  kMaxValue = kMigrationMultiScenesToSingleScene,
+};
+
+// Directory containing session files.
+const base::FilePath::CharType kSessions[] = FILE_PATH_LITERAL("Sessions");
+
+// Unique identifier used by device that do not support multiple scenes.
+NSString* const kSyntheticSessionIdentifier = @"{SyntheticIdentifier}";
+
+// Converts a base::FilePath to a NSString*.
+NSString* PathToNSString(const base::FilePath& file_path) {
+  return base::SysUTF8ToNSString(file_path.AsUTF8Unsafe());
+}
+
+SessionMigration MigrateSessionStorageForDirectoryImpl(
+    const base::FilePath& directory,
+    NSString* session_identifier,
+    NSArray<NSString*>* previous_identifiers) {
+  DCHECK(session_identifier.length != 0);
+
+  NSFileManager* file_manager = [NSFileManager defaultManager];
+
+  const std::string session = base::SysNSStringToUTF8(session_identifier);
+  NSString* session_directory =
+      PathToNSString(directory.Append(kSessions).Append(session));
+
+  // If the new directory already exists, then there is no need to perform
+  // any migration.
+  if ([file_manager fileExistsAtPath:session_directory])
+    return SessionMigration::kNoMigration;
+
+  // List of files to use to identify the previous session directory (and also
+  // to migrate to the new path).
+  const base::FilePath::CharType* kCandidateNames[] = {kSessionFileName,
+                                                       kSnapshotsDirectoryName};
+
+  // Try to identify the previous session directory. This is done by iterating
+  // over the possible previous session identifier, and looking for the files
+  // that are known to store the list of tabs or their snapshots. As soon as
+  // one of the file is found, consider the identifier found.
+  NSString* previous_session_identifier = nil;
+  for (NSString* identifier in previous_identifiers) {
+    for (const base::FilePath::CharType* name : kCandidateNames) {
+      NSString* path =
+          PathToNSString(SessionPathForDirectory(directory, identifier, name));
+      if ([file_manager fileExistsAtPath:path]) {
+        previous_session_identifier = identifier;
+        break;
+      }
+    }
+    if (previous_session_identifier)
+      break;
+  }
+
+  // Create the new directory used to store the session, aborting if the
+  // creation failed (since the migration won't be possible then).
+  NSError* error = nil;
+  if (![file_manager createDirectoryAtPath:session_directory
+               withIntermediateDirectories:YES
+                                attributes:nil
+                                     error:&error]) {
+    return SessionMigration::kMigrationFailed;
+  }
+
+  // If no identifier was found, then abort the migration. This is done after
+  // creating the new directory to avoid unnecessarily trying to perform the
+  // migration again on next startup.
+  if (!previous_session_identifier)
+    return SessionMigration::kMigrationNoSessionToMigrate;
+
+  // Migrate all the individual files (only attempt the migration if the file
+  // exist). Errors are logged but do not abort the migration.
+  for (const base::FilePath::CharType* name : kCandidateNames) {
+    NSString* path = PathToNSString(
+        SessionPathForDirectory(directory, previous_session_identifier, name));
+    if (![file_manager fileExistsAtPath:path])
+      continue;
+
+    NSString* destination = PathToNSString(
+        SessionPathForDirectory(directory, session_identifier, name));
+    if (![file_manager moveItemAtPath:path toPath:destination error:&error]) {
+      return SessionMigration::kMigrationFailed;
+    }
+  }
+
+  // If the previous session identifier was not empty, then it was located in
+  // the Sessions sub-directory of the BrowserState directory. As its content
+  // has been migrated, the directory itself should be empty, so delete it.
+  if (previous_session_identifier.length != 0) {
+    NSString* previous_session_directory =
+        PathToNSString(directory.Append(kSessions).Append(
+            base::SysNSStringToUTF8(previous_session_identifier)));
+
+    if (![file_manager removeItemAtPath:previous_session_directory
+                                  error:&error]) {
+      return SessionMigration::kMigrationFailed;
+    }
+
+    return [previous_session_identifier isEqual:kSyntheticSessionIdentifier]
+               ? SessionMigration::kMigrationSingleSceneToMultiScenes
+               : SessionMigration::kMigrationMultiScenesToSingleScene;
+  }
+
+  return base::ios::IsMultipleScenesSupported()
+             ? SessionMigration::kMigrationPreMWToMultiScenes
+             : SessionMigration::kMigrationPreMWToSingleScene;
+}
+
+}
+
+const base::FilePath::CharType kSessionFileName[] =
+    FILE_PATH_LITERAL("session.plist");
+
+const base::FilePath::CharType kSnapshotsDirectoryName[] =
+    FILE_PATH_LITERAL("Snapshots");
+
+base::FilePath SessionsDirectoryForDirectory(const base::FilePath& directory) {
+  return directory.Append(kSessions);
+}
+
+base::FilePath SessionPathForDirectory(const base::FilePath& directory,
+                                       NSString* session_identifier,
+                                       base::StringPiece name) {
+  // This is to support migration from old version of Chrome or old devices
+  // that were not using multi-window API. Remove once all user have migrated
+  // and there is no need to restore their old sessions.
+  if (!session_identifier.length)
+    return directory.Append(name);
+
+  const std::string session = base::SysNSStringToUTF8(session_identifier);
+  return directory.Append(kSessions).Append(session).Append(name);
+}
+
+void MigrateSessionStorageForDirectory(const base::FilePath& directory,
+                                       NSString* session_identifier,
+                                       NSString* previous_session_identifier) {
+  DCHECK(session_identifier.length != 0);
+  NSMutableArray<NSString*>* previous_identifiers = [NSMutableArray array];
+
+  // Support migrating from Chrome M-{87-89} on a device that does not support
+  // multiple scenes (where the session identifier was not constant). See
+  // crbug.com/1165798 for information on why this was a problem.
+  if (previous_session_identifier.length != 0)
+    [previous_identifiers addObject:previous_session_identifier];
+
+  // Support migrating from a device not supporting multiple scenes to one that
+  // does (e.g. migrating after upgrading an iPad to iOS 13+, restoring an
+  // iPhone backup on an iPad, ...).
+  if (base::ios::IsMultipleScenesSupported())
+    [previous_identifiers addObject:kSyntheticSessionIdentifier];
+
+  // Support migrating from Chrome pre M-87 which did not support multi-window
+  // API and thus did not use session identifier in the path to the files used
+  // to store the list of tabs or the snapshots.
+  [previous_identifiers addObject:@""];
+
+  const SessionMigration session_migration_status =
+      MigrateSessionStorageForDirectoryImpl(directory, session_identifier,
+                                            previous_identifiers);
+
+  UMA_HISTOGRAM_ENUMERATION("IOS.SessionMigration", session_migration_status,
+                            SessionMigration::kMaxValue);
+}
+
+NSString* SessionIdentifierForScene(id maybe_scene) {
+  if (@available(ios 13, *)) {
+    DCHECK(maybe_scene);
+    UIScene* scene = base::mac::ObjCCastStrict<UIScene>(maybe_scene);
+    if (base::ios::IsMultipleScenesSupported()) {
+      NSString* identifier = scene.session.persistentIdentifier;
+      DCHECK(identifier.length != 0);
+      DCHECK(![kSyntheticSessionIdentifier isEqual:identifier]);
+      return identifier;
+    }
+  }
+
+  return kSyntheticSessionIdentifier;
+}
diff --git a/ios/chrome/browser/sessions/scene_util_unittest.mm b/ios/chrome/browser/sessions/scene_util_unittest.mm
new file mode 100644
index 0000000..040760a
--- /dev/null
+++ b/ios/chrome/browser/sessions/scene_util_unittest.mm
@@ -0,0 +1,236 @@
+// 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.
+
+#import "ios/chrome/browser/sessions/scene_util.h"
+
+#import <UIKit/UIKit.h>
+
+#include <algorithm>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/ios/ios_util.h"
+#include "base/time/time.h"
+#import "testing/gtest_mac.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+// Constants used in tests to construct path names.
+const base::FilePath::CharType kRoot[] = FILE_PATH_LITERAL("root");
+const char kName[] = "filename";
+
+// Returns a newly mocked UIScene with |identifier| as session persistent
+// identifier when running on iOS 13+ or nil otherwise.
+id MockedSceneWithIdentifier(NSString* identifier) {
+  if (@available(ios 13, *)) {
+    id session = OCMClassMock([UISceneSession class]);
+    OCMStub([session persistentIdentifier]).andReturn(identifier);
+
+    id scene = OCMClassMock([UIScene class]);
+    OCMStub([scene session]).andReturn(session);
+    return scene;
+  }
+
+  return nil;
+}
+
+// Creates a temporary directory with |filenames| files below. The file names
+// may contain path separator in which case the whole directory structure will
+// be created. The file themselves will be created empty with default access.
+base::ScopedTempDir CreateScopedTempDirWithContent(
+    std::vector<base::StringPiece> filenames) {
+  base::ScopedTempDir temp_directory;
+  if (!temp_directory.CreateUniqueTempDir())
+    return temp_directory;
+
+  for (const base::StringPiece filename : filenames) {
+    base::FilePath path = temp_directory.GetPath().Append(filename);
+    if (!base::CreateDirectory(path.DirName()))
+      return base::ScopedTempDir();
+
+    if (!base::WriteFile(path, ""))
+      return base::ScopedTempDir();
+  }
+
+  return temp_directory;
+}
+
+// Returns the list of files recursively found below |directory|. The path will
+// be relative to |directory|.
+std::vector<base::FilePath> GetDirectoryContent(
+    const base::FilePath& directory) {
+  base::FileEnumerator enumerator(directory, /*recursive=*/true,
+                                  base::FileEnumerator::FILES);
+
+  std::vector<base::FilePath> filenames;
+  while (true) {
+    const base::FilePath filename = enumerator.Next();
+    if (filename.empty())
+      break;
+
+    base::FilePath relative_filename;
+    if (!directory.AppendRelativePath(filename, &relative_filename))
+      return std::vector<base::FilePath>();
+
+    filenames.push_back(relative_filename);
+  }
+
+  if (enumerator.GetError() != base::File::FILE_OK)
+    return std::vector<base::FilePath>();
+
+  std::sort(filenames.begin(), filenames.end());
+  return filenames;
+}
+
+}
+
+class SceneUtilTest : public PlatformTest {};
+
+TEST_F(SceneUtilTest, SessionsDirectoryForDirectory) {
+  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("root/Sessions")),
+            SessionsDirectoryForDirectory(base::FilePath(kRoot)));
+}
+
+TEST_F(SceneUtilTest, SessionPathForDirectory) {
+  EXPECT_EQ(base::FilePath("root/filename"),
+            SessionPathForDirectory(base::FilePath(kRoot), nil, kName));
+
+  EXPECT_EQ(base::FilePath("root/filename"),
+            SessionPathForDirectory(base::FilePath(kRoot), @"", kName));
+
+  EXPECT_EQ(
+      base::FilePath("root/Sessions/session-id/filename"),
+      SessionPathForDirectory(base::FilePath(kRoot), @"session-id", kName));
+}
+
+TEST_F(SceneUtilTest, SessionIdentifierForScene) {
+  NSString* identifier = [[NSUUID UUID] UUIDString];
+  id scene = MockedSceneWithIdentifier(identifier);
+
+  NSString* expected = @"{SyntheticIdentifier}";
+  if (@available(ios 13, *)) {
+    if (base::ios::IsMultipleScenesSupported())
+      expected = identifier;
+  }
+
+  EXPECT_NSEQ(expected, SessionIdentifierForScene(scene));
+}
+
+TEST_F(SceneUtilTest, MigrateSessionStorageForDirectory_SessionExists) {
+  base::ScopedTempDir temp_directory = CreateScopedTempDirWithContent({
+      "Sessions/session-id/Snapshots/1.png",
+      "Sessions/session-id/Snapshots/2.png",
+      "Sessions/session-id/session.plist",
+  });
+
+  ASSERT_TRUE(temp_directory.IsValid());
+
+  const base::FilePath directory = temp_directory.GetPath();
+  MigrateSessionStorageForDirectory(directory, @"session-id", nil);
+
+  const std::vector<base::FilePath> expected = {
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/Snapshots/1.png")),
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/Snapshots/2.png")),
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/session.plist")),
+  };
+
+  EXPECT_EQ(expected, GetDirectoryContent(directory));
+}
+
+TEST_F(SceneUtilTest, MigrateSessionStorageForDirectory_FromM86OrOlder) {
+  base::ScopedTempDir temp_directory = CreateScopedTempDirWithContent({
+      "Snapshots/1.png",
+      "Snapshots/2.png",
+      "session.plist",
+  });
+
+  ASSERT_TRUE(temp_directory.IsValid());
+
+  const base::FilePath directory = temp_directory.GetPath();
+  MigrateSessionStorageForDirectory(directory, @"session-id", nil);
+
+  const std::vector<base::FilePath> expected = {
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/Snapshots/1.png")),
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/Snapshots/2.png")),
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/session.plist")),
+  };
+
+  EXPECT_EQ(expected, GetDirectoryContent(directory));
+}
+
+TEST_F(SceneUtilTest, MigrateSessionStorageForDirectory_ToMultiWindow) {
+  // The test can only be run on a device that supports multiple scenes.
+  if (!base::ios::IsMultipleScenesSupported())
+    return;
+
+  base::ScopedTempDir temp_directory = CreateScopedTempDirWithContent({
+      "Sessions/{SyntheticIdentifier}/Snapshots/1.png",
+      "Sessions/{SyntheticIdentifier}/Snapshots/2.png",
+      "Sessions/{SyntheticIdentifier}/session.plist",
+  });
+
+  ASSERT_TRUE(temp_directory.IsValid());
+
+  const base::FilePath directory = temp_directory.GetPath();
+  MigrateSessionStorageForDirectory(directory, @"session-id", nil);
+
+  const std::vector<base::FilePath> expected = {
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/Snapshots/1.png")),
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/Snapshots/2.png")),
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/session.plist")),
+  };
+
+  EXPECT_EQ(expected, GetDirectoryContent(directory));
+}
+
+TEST_F(SceneUtilTest, MigrateSessionStorageForDirectory_FromMultiWindow) {
+  // The test can only be run on a device that does not support multiple scenes.
+  if (base::ios::IsMultipleScenesSupported())
+    return;
+
+  base::ScopedTempDir temp_directory = CreateScopedTempDirWithContent({
+      "Sessions/previous-id/Snapshots/1.png",
+      "Sessions/previous-id/Snapshots/2.png",
+      "Sessions/previous-id/session.plist",
+  });
+
+  ASSERT_TRUE(temp_directory.IsValid());
+
+  const base::FilePath directory = temp_directory.GetPath();
+  MigrateSessionStorageForDirectory(directory, @"session-id", @"previous-id");
+
+  const std::vector<base::FilePath> expected = {
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/Snapshots/1.png")),
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/Snapshots/2.png")),
+      base::FilePath(FILE_PATH_LITERAL("Sessions/session-id/session.plist")),
+  };
+
+  EXPECT_EQ(expected, GetDirectoryContent(directory));
+}
+
+TEST_F(SceneUtilTest, MigrateSessionStorageForDirectory_NothingToMigrate) {
+  base::ScopedTempDir temp_directory = CreateScopedTempDirWithContent({});
+  ASSERT_TRUE(temp_directory.IsValid());
+
+  const base::FilePath directory = temp_directory.GetPath();
+  const base::FilePath session_directory =
+      SessionsDirectoryForDirectory(directory).Append(
+          FILE_PATH_LITERAL("session-id"));
+
+  ASSERT_FALSE(base::DirectoryExists(session_directory));
+  MigrateSessionStorageForDirectory(directory, @"session-id", nil);
+
+  EXPECT_TRUE(base::DirectoryExists(session_directory));
+
+  const std::vector<base::FilePath> expected = {};
+  EXPECT_EQ(expected, GetDirectoryContent(directory));
+}
diff --git a/ios/chrome/browser/sessions/session_restoration_browser_agent.h b/ios/chrome/browser/sessions/session_restoration_browser_agent.h
index 8b1d2a8c..129536e 100644
--- a/ios/chrome/browser/sessions/session_restoration_browser_agent.h
+++ b/ios/chrome/browser/sessions/session_restoration_browser_agent.h
@@ -17,9 +17,6 @@
 #include "ios/web/public/web_state_observer.h"
 
 class AllWebStateObservationForwarder;
-namespace base {
-class FilePath;
-}
 class ChromeBrowserState;
 @class SessionWindowIOS;
 @class SessionIOSFactory;
@@ -45,13 +42,14 @@
 
   ~SessionRestorationBrowserAgent() override;
 
+  SessionRestorationBrowserAgent(const SessionRestorationBrowserAgent&) =
+      delete;
+  SessionRestorationBrowserAgent& operator=(
+      const SessionRestorationBrowserAgent&) = delete;
+
   // Set a session identification string that will be used to locate which
-  // session to restore. If this is unset (or an empty string), then the
-  // default session will be restored. This value is ignored when multi-window
-  // is not enabled, and the default session is always used.
-  // Setting this more than once on the same agent is probably a programming
-  // error.
-  void SetSessionID(const std::string& session_identifier);
+  // session to restore. Must be set before restoring/saving the session.
+  void SetSessionID(NSString* session_identifier);
 
   // Adds/Removes Observer to session restoration events.
   void AddObserver(SessionRestorationObserver* observer);
@@ -116,30 +114,24 @@
   void DidFinishNavigation(web::WebState* web_state,
                            web::NavigationContext* navigation_context) override;
 
-  // The path to use for all session storage reads and writes. If multi-window
-  // is enabled, the session ID for this agent is used to determine this path;
-  // otherwise or if |force_single_window| is true, the state path of the
-  // associated browser state will be returned.
-  base::FilePath GetSessionStoragePath(bool force_single_window = false);
-
   // The service object which handles the actual saving of sessions.
-  SessionServiceIOS* session_service_;
+  SessionServiceIOS* session_service_ = nullptr;
 
   // The list of web states to be saved.
-  WebStateList* web_state_list_;
+  WebStateList* web_state_list_ = nullptr;
 
   // The web usage enabler for the web state list being restored.
-  WebUsageEnablerBrowserAgent* web_enabler_;
+  WebUsageEnablerBrowserAgent* web_enabler_ = nullptr;
 
   base::ObserverList<SessionRestorationObserver, true> observers_;
 
-  ChromeBrowserState* browser_state_;
+  ChromeBrowserState* browser_state_ = nullptr;
 
   // Session Factory used to create session data for saving.
-  SessionIOSFactory* session_ios_factory_;
+  SessionIOSFactory* session_ios_factory_ = nullptr;
 
   // Session identifier for this agent.
-  std::string session_identifier_;
+  __strong NSString* session_identifier_ = nil;
 
   // True when session restoration is in progress.
   bool restoring_session_ = false;
diff --git a/ios/chrome/browser/sessions/session_restoration_browser_agent.mm b/ios/chrome/browser/sessions/session_restoration_browser_agent.mm
index dff4322..b9db230 100644
--- a/ios/chrome/browser/sessions/session_restoration_browser_agent.mm
+++ b/ios/chrome/browser/sessions/session_restoration_browser_agent.mm
@@ -35,11 +35,6 @@
 
 BROWSER_USER_DATA_KEY_IMPL(SessionRestorationBrowserAgent)
 
-namespace {
-const base::FilePath::CharType kSessionDirectory[] =
-    FILE_PATH_LITERAL("Sessions");
-}
-
 // static
 void SessionRestorationBrowserAgent::CreateForBrowser(
     Browser* browser,
@@ -75,7 +70,8 @@
 }
 
 void SessionRestorationBrowserAgent::SetSessionID(
-    const std::string& session_identifier) {
+    NSString* session_identifier) {
+  DCHECK(session_identifier.length != 0);
   session_identifier_ = session_identifier;
 }
 
@@ -173,16 +169,13 @@
 }
 
 bool SessionRestorationBrowserAgent::RestoreSession() {
+  DCHECK(session_identifier_.length != 0);
+
   PreviousSessionInfo* session_info = [PreviousSessionInfo sharedInstance];
   auto scoped_restore = [session_info startSessionRestoration];
 
-  NSString* session_id = (base::ios::IsMultiwindowSupported() &&
-                          session_info.isMultiWindowEnabledSession)
-                             ? base::SysUTF8ToNSString(session_identifier_)
-                             : nil;
-
   SessionIOS* session = [session_service_
-      loadSessionWithSessionID:session_id
+      loadSessionWithSessionID:session_identifier_
                      directory:browser_state_->GetStatePath()];
   SessionWindowIOS* session_window = nil;
 
@@ -199,11 +192,13 @@
 }
 
 void SessionRestorationBrowserAgent::SaveSession(bool immediately) {
+  DCHECK(session_identifier_.length != 0);
+
   if (!CanSaveSession())
     return;
 
   [session_service_ saveSession:session_ios_factory_
-                      sessionID:base::SysUTF8ToNSString(session_identifier_)
+                      sessionID:session_identifier_
                       directory:browser_state_->GetStatePath()
                     immediately:immediately];
 }
@@ -291,19 +286,6 @@
   SaveSession(/*immediately=*/false);
 }
 
-base::FilePath SessionRestorationBrowserAgent::GetSessionStoragePath(
-    bool force_single_window) {
-  base::FilePath path = browser_state_->GetStatePath();
-  if (!force_single_window && base::ios::IsMultiwindowSupported() &&
-      !session_identifier_.empty()) {
-    path = path.Append(kSessionDirectory)
-               .Append(session_identifier_)
-               .AsEndingWithSeparator();
-  }
-
-  return path;
-}
-
 // WebStateObserver methods
 void SessionRestorationBrowserAgent::DidFinishNavigation(
     web::WebState* web_state,
diff --git a/ios/chrome/browser/sessions/session_restoration_browser_agent_unittest.mm b/ios/chrome/browser/sessions/session_restoration_browser_agent_unittest.mm
index 97dcbd3..8a43fd3 100644
--- a/ios/chrome/browser/sessions/session_restoration_browser_agent_unittest.mm
+++ b/ios/chrome/browser/sessions/session_restoration_browser_agent_unittest.mm
@@ -86,10 +86,12 @@
         WebUsageEnablerBrowserAgent::FromBrowser(browser_.get());
     web_usage_enabler_->SetWebUsageEnabled(false);
 
+    session_identifier_ = [[NSUUID UUID] UUIDString];
     SessionRestorationBrowserAgent::CreateForBrowser(browser_.get(),
                                                      test_session_service_);
     session_restoration_agent_ =
         SessionRestorationBrowserAgent::FromBrowser(browser_.get());
+    session_restoration_agent_->SetSessionID(session_identifier_);
   }
 
   ~SessionRestorationBrowserAgentTest() override = default;
@@ -101,6 +103,8 @@
     PlatformTest::TearDown();
   }
 
+  NSString* session_id() { return session_identifier_; }
+
  protected:
   // Creates a session window with |sessions_count| and mark the
   // |selected_index| entry as selected.
@@ -146,6 +150,7 @@
   std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
   std::unique_ptr<Browser> browser_;
 
+  __strong NSString* session_identifier_ = nil;
   TestSessionService* test_session_service_;
   WebUsageEnablerBrowserAgent* web_usage_enabler_;
   SessionRestorationBrowserAgent* session_restoration_agent_;
@@ -225,7 +230,8 @@
   // Restore, expect that there are no sessions.
   const base::FilePath& state_path = chrome_browser_state_->GetStatePath();
   SessionIOS* session =
-      [test_session_service_ loadSessionWithSessionID:nil directory:state_path];
+      [test_session_service_ loadSessionWithSessionID:session_id()
+                                            directory:state_path];
   ASSERT_EQ(1u, session.sessionWindows.count);
   SessionWindowIOS* session_window = session.sessionWindows[0];
   session_restoration_agent_->RestoreSessionWindow(session_window);
@@ -254,7 +260,8 @@
 
   const base::FilePath& state_path = chrome_browser_state_->GetStatePath();
   SessionIOS* session =
-      [test_session_service_ loadSessionWithSessionID:nil directory:state_path];
+      [test_session_service_ loadSessionWithSessionID:session_id()
+                                            directory:state_path];
   ASSERT_EQ(1u, session.sessionWindows.count);
   SessionWindowIOS* session_window = session.sessionWindows[0];
 
diff --git a/ios/chrome/browser/sessions/session_service_ios.mm b/ios/chrome/browser/sessions/session_service_ios.mm
index e5882c6..59e253f 100644
--- a/ios/chrome/browser/sessions/session_service_ios.mm
+++ b/ios/chrome/browser/sessions/session_service_ios.mm
@@ -22,6 +22,7 @@
 #include "base/task/thread_pool.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "base/time/time.h"
+#import "ios/chrome/browser/sessions/scene_util.h"
 #import "ios/chrome/browser/sessions/session_ios.h"
 #import "ios/chrome/browser/sessions/session_ios_factory.h"
 #import "ios/chrome/browser/sessions/session_window_ios.h"
@@ -45,20 +46,6 @@
 namespace {
 const NSTimeInterval kSaveDelay = 2.5;     // Value taken from Desktop Chrome.
 NSString* const kRootObjectKey = @"root";  // Key for the root object.
-
-// The directory name inside BrowserStatedirectory which contain all sessions
-// directories.
-const base::FilePath::CharType kSessionDirectory[] =
-    FILE_PATH_LITERAL("Sessions");
-
-// The session file name on disk.
-const base::FilePath::CharType kSessionFileName[] =
-    FILE_PATH_LITERAL("session.plist");
-
-// Convert |path| to NSString.
-NSString* PathAsNSString(const base::FilePath& path) {
-  return base::SysUTF8ToNSString(path.AsUTF8Unsafe());
-}
 }
 
 @implementation NSKeyedUnarchiver (CrLegacySessionCompatibility)
@@ -186,9 +173,10 @@
 
 - (void)deleteAllSessionFilesInDirectory:(const base::FilePath&)directory
                               completion:(base::OnceClosure)callback {
-  const base::FilePath sessionDirectory = directory.Append(kSessionDirectory);
+  NSString* sessionsDirectory = base::SysUTF8ToNSString(
+      SessionsDirectoryForDirectory(directory).AsUTF8Unsafe());
   NSArray<NSString*>* allSessionIDs = [[NSFileManager defaultManager]
-      contentsOfDirectoryAtPath:PathAsNSString(sessionDirectory)
+      contentsOfDirectoryAtPath:sessionsDirectory
                           error:nil];
 
   // If there were no session ids, then scenes are not supported fall back to
@@ -216,14 +204,10 @@
 
 + (NSString*)sessionPathForSessionID:(NSString*)sessionID
                            directory:(const base::FilePath&)directory {
-  // TODO(crbug.com/1165798): remove when the sessionID is guaranteed to
-  // always be an non-empty string.
-  if (!sessionID.length)
-    return PathAsNSString(directory.Append(kSessionFileName));
-
-  return PathAsNSString(directory.Append(kSessionDirectory)
-                            .Append(base::SysNSStringToUTF8(sessionID))
-                            .Append(kSessionFileName));
+  DCHECK(sessionID.length != 0);
+  return base::SysUTF8ToNSString(
+      SessionPathForDirectory(directory, sessionID, kSessionFileName)
+          .AsUTF8Unsafe());
 }
 
 #pragma mark - Private methods
diff --git a/ios/chrome/browser/sessions/session_service_ios_unittest.mm b/ios/chrome/browser/sessions/session_service_ios_unittest.mm
index 4713631..4817825 100644
--- a/ios/chrome/browser/sessions/session_service_ios_unittest.mm
+++ b/ios/chrome/browser/sessions/session_service_ios_unittest.mm
@@ -46,8 +46,7 @@
   void SetUp() override {
     PlatformTest::SetUp();
     ASSERT_TRUE(scoped_temp_directory_.CreateUniqueTempDir());
-    directory_ =
-        scoped_temp_directory_.GetPath().Append(FILE_PATH_LITERAL("Sessions"));
+    directory_ = scoped_temp_directory_.GetPath();
 
     scoped_refptr<base::SequencedTaskRunner> task_runner =
         base::ThreadTaskRunnerHandle::Get();
@@ -110,13 +109,6 @@
 
 TEST_F(SessionServiceTest, SessionPathForDirectory) {
   const base::FilePath root(FILE_PATH_LITERAL("root"));
-
-  EXPECT_NSEQ(@"root/session.plist",
-              [SessionServiceIOS sessionPathForSessionID:nil directory:root]);
-
-  EXPECT_NSEQ(@"root/session.plist",
-              [SessionServiceIOS sessionPathForSessionID:@"" directory:root]);
-
   EXPECT_NSEQ(@"root/Sessions/session-id/session.plist",
               [SessionServiceIOS sessionPathForSessionID:@"session-id"
                                                directory:root]);
@@ -126,8 +118,10 @@
   std::unique_ptr<WebStateList> web_state_list = CreateWebStateList(0);
   SessionIOSFactory* factory =
       [[SessionIOSFactory alloc] initWithWebStateList:web_state_list.get()];
+
+  NSString* session_id = [[NSUUID UUID] UUIDString];
   [session_service() saveSession:factory
-                       sessionID:nil
+                       sessionID:session_id
                        directory:directory()
                      immediately:YES];
 
@@ -151,8 +145,9 @@
   SessionIOSFactory* factory =
       [[SessionIOSFactory alloc] initWithWebStateList:web_state_list.get()];
 
+  NSString* session_id = [[NSUUID UUID] UUIDString];
   [session_service() saveSession:factory
-                       sessionID:nil
+                       sessionID:session_id
                        directory:directory()
                      immediately:YES];
 
@@ -167,20 +162,23 @@
 }
 
 TEST_F(SessionServiceTest, LoadSessionFromDirectoryNoFile) {
+  NSString* session_id = [[NSUUID UUID] UUIDString];
   SessionIOS* session =
-      [session_service() loadSessionWithSessionID:nil directory:directory()];
+      [session_service() loadSessionWithSessionID:session_id
+                                        directory:directory()];
   EXPECT_TRUE(session == nil);
 }
 
 // Tests that the session service doesn't retain the SessionIOSFactory, and that
-// savesession will be no-op if the factory is destroyed earlier.
+// SaveSession will be no-op if the factory is destroyed earlier.
 TEST_F(SessionServiceTest, SaveExpiredSession) {
   std::unique_ptr<WebStateList> web_state_list = CreateWebStateList(2);
   SessionIOSFactory* factory =
       [[SessionIOSFactory alloc] initWithWebStateList:web_state_list.get()];
 
+  NSString* session_id = [[NSUUID UUID] UUIDString];
   [session_service() saveSession:factory
-                       sessionID:nil
+                       sessionID:session_id
                        directory:directory()
                      immediately:NO];
   [factory disconnect];
@@ -191,7 +189,8 @@
   base::RunLoop().RunUntilIdle();
 
   SessionIOS* session =
-      [session_service() loadSessionWithSessionID:nil directory:directory()];
+      [session_service() loadSessionWithSessionID:session_id
+                                        directory:directory()];
   EXPECT_FALSE(session);
 }
 
@@ -200,8 +199,9 @@
   SessionIOSFactory* factory =
       [[SessionIOSFactory alloc] initWithWebStateList:web_state_list.get()];
 
+  NSString* session_id = [[NSUUID UUID] UUIDString];
   [session_service() saveSession:factory
-                       sessionID:nil
+                       sessionID:session_id
                        directory:directory()
                      immediately:YES];
 
@@ -211,7 +211,8 @@
   base::RunLoop().RunUntilIdle();
 
   SessionIOS* session =
-      [session_service() loadSessionWithSessionID:nil directory:directory()];
+      [session_service() loadSessionWithSessionID:session_id
+                                        directory:directory()];
   EXPECT_EQ(1u, session.sessionWindows.count);
   EXPECT_EQ(2u, session.sessionWindows[0].sessions.count);
   EXPECT_EQ(0u, session.sessionWindows[0].selectedIndex);
@@ -222,8 +223,9 @@
   SessionIOSFactory* factory =
       [[SessionIOSFactory alloc] initWithWebStateList:web_state_list.get()];
 
+  NSString* session_id = [[NSUUID UUID] UUIDString];
   [session_service() saveSession:factory
-                       sessionID:nil
+                       sessionID:session_id
                        directory:directory()
                      immediately:YES];
 
@@ -233,7 +235,8 @@
   base::RunLoop().RunUntilIdle();
 
   NSString* session_path =
-      [SessionServiceIOS sessionPathForSessionID:nil directory:directory()];
+      [SessionServiceIOS sessionPathForSessionID:session_id
+                                       directory:directory()];
   NSString* renamed_path = [session_path stringByAppendingPathExtension:@"bak"];
   ASSERT_NSNE(session_path, renamed_path);
 
diff --git a/ios/chrome/browser/signin/authentication_service.mm b/ios/chrome/browser/signin/authentication_service.mm
index b0efa6c..9cef33b 100644
--- a/ios/chrome/browser/signin/authentication_service.mm
+++ b/ios/chrome/browser/signin/authentication_service.mm
@@ -13,6 +13,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
+#import "components/signin/ios/browser/features.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/device_accounts_synchronizer.h"
 #import "components/signin/public/identity_manager/primary_account_mutator.h"
@@ -374,7 +375,17 @@
       signout_source, signin_metrics::SignoutDelete::IGNORE_METRIC);
   crash_keys::SetCurrentlySignedIn(false);
   cached_mdm_infos_.clear();
-  if (force_clear_browsing_data || is_managed) {
+  bool clear_browsing_data;
+  if (base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS)) {
+    // With kSimplifySignOutIOS feature, browsing data for managed account needs
+    // to be cleared only if sync has started at least once.
+    clear_browsing_data =
+        force_clear_browsing_data ||
+        (is_managed && sync_setup_service_->IsFirstSetupComplete());
+  } else {
+    clear_browsing_data = force_clear_browsing_data || is_managed;
+  }
+  if (clear_browsing_data) {
     delegate_->ClearBrowsingData(completion);
   } else if (completion) {
     completion();
diff --git a/ios/chrome/browser/snapshots/BUILD.gn b/ios/chrome/browser/snapshots/BUILD.gn
index db6853e..7db266d 100644
--- a/ios/chrome/browser/snapshots/BUILD.gn
+++ b/ios/chrome/browser/snapshots/BUILD.gn
@@ -28,6 +28,7 @@
     "//base",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/main:public",
+    "//ios/chrome/browser/sessions:scene_util",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/web:tab_id_tab_helper",
diff --git a/ios/chrome/browser/snapshots/snapshot_browser_agent.h b/ios/chrome/browser/snapshots/snapshot_browser_agent.h
index 09adf24..925057c0 100644
--- a/ios/chrome/browser/snapshots/snapshot_browser_agent.h
+++ b/ios/chrome/browser/snapshots/snapshot_browser_agent.h
@@ -12,9 +12,6 @@
 #import "ios/chrome/browser/main/browser_user_data.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer.h"
 
-namespace base {
-class FilePath;
-}
 @class SnapshotCache;
 
 // Associates a SnapshotCache to a Browser.
@@ -25,10 +22,13 @@
   SnapshotBrowserAgent();
   ~SnapshotBrowserAgent() override;
 
+  SnapshotBrowserAgent(const SnapshotBrowserAgent&) = delete;
+  SnapshotBrowserAgent& operator=(const SnapshotBrowserAgent&) = delete;
+
   // Set a session identification string that will be used to locate the
   // snapshots directory. Setting this more than once on the same agent is
   // probably a programming error.
-  void SetSessionID(const std::string& session_identifier);
+  void SetSessionID(NSString* session_identifier);
 
   // Maintains the snapshots storage including purging unused images and
   // performing any necessary migrations.
@@ -69,17 +69,12 @@
   // Purges the snapshots folder of unused snapshots.
   void PurgeUnusedSnapshots();
 
-  // Returns the storage folder for snapshots.
-  base::FilePath GetStoragePath();
-
   // Returns the Tab IDs of all the WebStates in the Browser.
   NSSet<NSString*>* GetTabIDs();
 
-  Browser* browser_;               // unowned.
-  SnapshotCache* snapshot_cache_;  // strong, owned.
+  __strong SnapshotCache* snapshot_cache_;
 
-  // Session identifier for this agent.
-  std::string session_identifier_;
+  Browser* browser_ = nullptr;
 };
 
 #endif  // IOS_CHROME_BROWSER_SNAPSHOTS_SNAPSHOT_BROWSER_AGENT_H_
diff --git a/ios/chrome/browser/snapshots/snapshot_browser_agent.mm b/ios/chrome/browser/snapshots/snapshot_browser_agent.mm
index 783cc8ab..16635eb6 100644
--- a/ios/chrome/browser/snapshots/snapshot_browser_agent.mm
+++ b/ios/chrome/browser/snapshots/snapshot_browser_agent.mm
@@ -8,7 +8,9 @@
 #include "base/files/file_path.h"
 #import "base/ios/ios_util.h"
 #include "base/path_service.h"
+#include "base/strings/sys_string_conversions.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/sessions/scene_util.h"
 #import "ios/chrome/browser/snapshots/snapshot_cache.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
 #import "ios/chrome/browser/web/tab_id_tab_helper.h"
@@ -22,10 +24,6 @@
 
 const base::FilePath::CharType kLegacyBaseDirectory[] =
     FILE_PATH_LITERAL("Chromium");
-const base::FilePath::CharType kSessionsDirectory[] =
-    FILE_PATH_LITERAL("Sessions");
-const base::FilePath::CharType kSnapshotsDirectory[] =
-    FILE_PATH_LITERAL("Snapshots");
 
 }  // namespace
 
@@ -86,13 +84,13 @@
   }
 }
 
-void SnapshotBrowserAgent::SetSessionID(const std::string& session_identifier) {
-  // It's probably incorrect to set this more than once.
-  DCHECK(session_identifier_.empty() ||
-         session_identifier_ == session_identifier);
-  session_identifier_ = session_identifier;
-  snapshot_cache_ =
-      [[SnapshotCache alloc] initWithStoragePath:GetStoragePath()];
+void SnapshotBrowserAgent::SetSessionID(NSString* session_identifier) {
+  // It is incorrect to call this method twice.
+  DCHECK(!snapshot_cache_);
+  const base::FilePath storage_path =
+      SessionPathForDirectory(browser_->GetBrowserState()->GetStatePath(),
+                              session_identifier, kSnapshotsDirectoryName);
+  snapshot_cache_ = [[SnapshotCache alloc] initWithStoragePath:storage_path];
 }
 
 void SnapshotBrowserAgent::PerformStorageMaintenance() {
@@ -108,8 +106,8 @@
   DCHECK(snapshot_cache_);
   base::FilePath legacy_directory;
   DCHECK(base::PathService::Get(base::DIR_CACHE, &legacy_directory));
-  legacy_directory =
-      legacy_directory.Append(kLegacyBaseDirectory).Append(kSnapshotsDirectory);
+  legacy_directory = legacy_directory.Append(kLegacyBaseDirectory)
+                         .Append(kSnapshotsDirectoryName);
   // The legacy directory is deleted in migration, and migration is NO-OP if
   // directory does not exist.
   [snapshot_cache_ migrateSnapshotsWithIDs:GetTabIDs()
@@ -126,21 +124,6 @@
   [snapshot_cache_ purgeCacheOlderThan:one_minute_ago keeping:snapshot_ids];
 }
 
-base::FilePath SnapshotBrowserAgent::GetStoragePath() {
-  // TODO(crbug.com/1117317): This method should only need to append the
-  // snapshots folder to a base path that already includes the browser state
-  // path, sessions directory, and the session identifier.
-  base::FilePath path = browser_->GetBrowserState()->GetStatePath();
-  if (base::ios::IsSceneStartupSupported() && !session_identifier_.empty()) {
-    path = path.Append(kSessionsDirectory)
-               .Append(session_identifier_)
-               .Append(kSnapshotsDirectory);
-  } else {
-    path = path.Append(kSnapshotsDirectory);
-  }
-  return path;
-}
-
 NSSet<NSString*>* SnapshotBrowserAgent::GetTabIDs() {
   WebStateList* web_state_list = browser_->GetWebStateList();
   NSMutableSet<NSString*>* tab_ids =
diff --git a/ios/chrome/browser/snapshots/snapshot_browser_agent_unittest.mm b/ios/chrome/browser/snapshots/snapshot_browser_agent_unittest.mm
index e51b1778..5b73238 100644
--- a/ios/chrome/browser/snapshots/snapshot_browser_agent_unittest.mm
+++ b/ios/chrome/browser/snapshots/snapshot_browser_agent_unittest.mm
@@ -32,7 +32,7 @@
       SnapshotBrowserAgent::FromBrowser(browser_.get());
   EXPECT_NE(nullptr, agent);
   EXPECT_EQ(nil, agent->snapshot_cache());
-  agent->SetSessionID(base::SysNSStringToUTF8([[NSUUID UUID] UUIDString]));
+  agent->SetSessionID([[NSUUID UUID] UUIDString]);
   EXPECT_NE(nil, agent->snapshot_cache());
 }
 
diff --git a/ios/chrome/browser/tabs/tab_model_unittest.mm b/ios/chrome/browser/tabs/tab_model_unittest.mm
index f9b5d59..867fb60b 100644
--- a/ios/chrome/browser/tabs/tab_model_unittest.mm
+++ b/ios/chrome/browser/tabs/tab_model_unittest.mm
@@ -60,6 +60,8 @@
     // wanted.
     SessionRestorationBrowserAgent::CreateForBrowser(browser_.get(),
                                                      session_service_);
+    SessionRestorationBrowserAgent::FromBrowser(browser_.get())
+        ->SetSessionID([[NSUUID UUID] UUIDString]);
     SetTabModel(CreateTabModel(nil));
   }
 
diff --git a/ios/chrome/browser/test/perf_test_with_bvc_ios.mm b/ios/chrome/browser/test/perf_test_with_bvc_ios.mm
index cd02cf9e..6ac059e 100644
--- a/ios/chrome/browser/test/perf_test_with_bvc_ios.mm
+++ b/ios/chrome/browser/test/perf_test_with_bvc_ios.mm
@@ -95,10 +95,13 @@
   ios::AutocompleteClassifierFactory::GetForBrowserState(
       chrome_browser_state_.get());
 
+  // Generates a random session identifier.
+  NSString* session_id = [[NSUUID UUID] UUIDString];
+
   // Use the session to create a window which will contain the tabs.
   const base::FilePath& state_path = chrome_browser_state_->GetStatePath();
   SessionIOS* session =
-      [[SessionServiceIOS sharedService] loadSessionWithSessionID:nil
+      [[SessionServiceIOS sharedService] loadSessionWithSessionID:session_id
                                                         directory:state_path];
   DCHECK_EQ(session.sessionWindows.count, 1u);
 
diff --git a/ios/chrome/browser/ui/authentication/authentication_ui_util.mm b/ios/chrome/browser/ui/authentication/authentication_ui_util.mm
index ab728cb..bbbaf7c 100644
--- a/ios/chrome/browser/ui/authentication/authentication_ui_util.mm
+++ b/ios/chrome/browser/ui/authentication/authentication_ui_util.mm
@@ -59,7 +59,6 @@
       AuthenticationServiceFactory::GetForBrowserState(
           browser->GetBrowserState());
   NSString* title = nil;
-  NSString* message = nil;
   SyncSetupService* sync_setup_service =
       SyncSetupServiceFactory::GetForBrowserState(browser->GetBrowserState());
   BOOL sync_enabled = sync_setup_service->IsFirstSetupComplete();
@@ -74,23 +73,21 @@
                      : SignedInUserStateWithNoneManagedAccountAndNotSyncing;
   }
   switch (signed_in_user_state) {
-    case SignedInUserStateWithManagedAccountAndSyncing:
-    case SignedInUserStateWithManagedAccountAndNotSyncing: {
-      title = l10n_util::GetNSString(
-          IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_MANAGED_ACCOUNT);
+    case SignedInUserStateWithManagedAccountAndSyncing: {
       base::string16 hosted_domain = HostedDomainForPrimaryAccount(browser);
-      message = l10n_util::GetNSStringF(
-          IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT, hosted_domain);
+      title = l10n_util::GetNSStringF(
+          IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_MANAGED_ACCOUNT,
+          hosted_domain);
       break;
     }
     case SignedInUserStateWithNonManagedAccountAndSyncing: {
-      title = l10n_util::GetNSString(IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNC);
-      message =
-          l10n_util::GetNSString(IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_SYNC);
+      title = l10n_util::GetNSString(
+          IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_ACCOUNT);
       break;
     }
+    case SignedInUserStateWithManagedAccountAndNotSyncing:
     case SignedInUserStateWithNoneManagedAccountAndNotSyncing: {
-      title = l10n_util::GetNSString(IDS_IOS_SIGNOUT_DIALOG_TITLE_WITHOUT_SYNC);
+      // No title.
       break;
     }
   }
@@ -98,12 +95,11 @@
       [[ActionSheetCoordinator alloc] initWithBaseViewController:view_controller
                                                          browser:browser
                                                            title:title
-                                                         message:message
+                                                         message:nil
                                                             rect:view.frame
                                                             view:view];
   switch (signed_in_user_state) {
-    case SignedInUserStateWithManagedAccountAndSyncing:
-    case SignedInUserStateWithManagedAccountAndNotSyncing: {
+    case SignedInUserStateWithManagedAccountAndSyncing: {
       NSString* const clear_from_this_device =
           l10n_util::GetNSString(IDS_IOS_SIGNOUT_DIALOG_CLEAR_DATA_BUTTON);
       [alertCoordinator
@@ -115,6 +111,18 @@
                      style:UIAlertActionStyleDestructive];
       break;
     }
+    case SignedInUserStateWithManagedAccountAndNotSyncing: {
+      NSString* const clear_from_this_device =
+          l10n_util::GetNSString(IDS_IOS_SIGNOUT_DIALOG_SIGN_OUT_BUTTON);
+      [alertCoordinator
+          addItemWithTitle:clear_from_this_device
+                    action:^{
+                      signout_completion(
+                          SignoutActionSheetCoordinatorResultKeepOnDevice);
+                    }
+                     style:UIAlertActionStyleDestructive];
+      break;
+    }
     case SignedInUserStateWithNonManagedAccountAndSyncing: {
       NSString* const clear_from_this_device =
           l10n_util::GetNSString(IDS_IOS_SIGNOUT_DIALOG_CLEAR_DATA_BUTTON);
diff --git a/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm b/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm
index b022d99..122d324 100644
--- a/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm
+++ b/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm
@@ -16,18 +16,11 @@
 #endif
 
 namespace {
-// Padding used between the image and text.
-const CGFloat kHorizontalPaddingBetweenImageAndText = 10;
 
 // Padding used between the text and error icon.
 const CGFloat kHorizontalPaddingBetweenTextAndError = 5;
 
-// Image fixed horizontal size.
-const CGFloat kHorizontalImageFixedSize = 40;
-
-// Error icon fixed horizontal size.
-const CGFloat kHorizontalErrorIconFixedSize = 25;
-}
+}  // namespace
 
 @implementation TableViewAccountItem
 
@@ -117,7 +110,7 @@
   _imageView.layer.masksToBounds = YES;
   _imageView.contentMode = UIViewContentModeScaleAspectFit;
   // Creates the image rounded corners.
-  _imageView.layer.cornerRadius = kHorizontalImageFixedSize / 2.0f;
+  _imageView.layer.cornerRadius = kTableViewIconImageSize / 2.0f;
   [contentView addSubview:_imageView];
 
   _errorIcon = [[UIImageView alloc] init];
@@ -152,7 +145,7 @@
   _textLeadingAnchorConstraint = [_textLabel.leadingAnchor
       constraintEqualToAnchor:_imageView.trailingAnchor];
   _errorIconWidthConstraint = [_errorIcon.widthAnchor
-      constraintEqualToConstant:kHorizontalErrorIconFixedSize];
+      constraintEqualToConstant:kTableViewIconImageSize];
   [NSLayoutConstraint activateConstraints:@[
     // Set leading anchors.
     [_imageView.leadingAnchor
@@ -162,8 +155,7 @@
         constraintEqualToAnchor:_textLabel.leadingAnchor],
 
     // Fix image widths.
-    [_imageView.widthAnchor
-        constraintEqualToConstant:kHorizontalImageFixedSize],
+    [_imageView.widthAnchor constraintEqualToConstant:kTableViewIconImageSize],
     [_imageView.heightAnchor constraintEqualToAnchor:_imageView.widthAnchor],
     _errorIconWidthConstraint,
 
@@ -198,7 +190,7 @@
     // Set trailing anchors.
     [_errorIcon.trailingAnchor
         constraintEqualToAnchor:contentView.trailingAnchor
-                       constant:-kHorizontalPaddingBetweenImageAndText],
+                       constant:-kTableViewOneLabelCellVerticalSpacing],
     [_detailTextLabel.trailingAnchor
         constraintEqualToAnchor:_errorIcon.leadingAnchor
                        constant:-kHorizontalPaddingBetweenTextAndError],
@@ -227,13 +219,13 @@
   // Adjust the leading margin depending on existence of image.
   if (_imageView.image) {
     _textLeadingAnchorConstraint.constant =
-        kHorizontalPaddingBetweenImageAndText;
+        kTableViewOneLabelCellVerticalSpacing;
   } else {
     _textLeadingAnchorConstraint.constant = 0;
   }
 
   if (_errorIcon.image) {
-    _errorIconWidthConstraint.constant = kHorizontalErrorIconFixedSize;
+    _errorIconWidthConstraint.constant = kTableViewIconImageSize;
   } else {
     _errorIconWidthConstraint.constant = 0;
   }
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
index 38206911..6330aec 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
@@ -2260,6 +2260,11 @@
 #pragma mark - UITableViewDelegate
 
 - (CGFloat)tableView:(UITableView*)tableView
+    heightForHeaderInSection:(NSInteger)section {
+  return ChromeTableViewHeightForHeaderInSection(section);
+}
+
+- (CGFloat)tableView:(UITableView*)tableView
     heightForRowAtIndexPath:(NSIndexPath*)indexPath {
   return UITableViewAutomaticDimension;
 }
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
index da1aef4..17a6146 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
@@ -121,6 +121,8 @@
 
     SessionRestorationBrowserAgent::CreateForBrowser(
         browser_.get(), [[TestSessionService alloc] init]);
+    SessionRestorationBrowserAgent::FromBrowser(browser_.get())
+        ->SetSessionID([[NSUUID UUID] UUIDString]);
 
     SceneStateBrowserAgent::CreateForBrowser(browser_.get(), scene_state_);
 
diff --git a/ios/chrome/browser/ui/main/BUILD.gn b/ios/chrome/browser/ui/main/BUILD.gn
index 08d8515f..b13e8fbe4 100644
--- a/ios/chrome/browser/ui/main/BUILD.gn
+++ b/ios/chrome/browser/ui/main/BUILD.gn
@@ -112,6 +112,7 @@
     "//ios/chrome/browser/policy",
     "//ios/chrome/browser/policy:policy_util",
     "//ios/chrome/browser/screenshot",
+    "//ios/chrome/browser/sessions:scene_util",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/ui:feature_flags",
@@ -195,6 +196,7 @@
     "//ios/chrome/browser/reading_list",
     "//ios/chrome/browser/sessions",
     "//ios/chrome/browser/sessions:restoration_agent",
+    "//ios/chrome/browser/sessions:scene_util",
     "//ios/chrome/browser/sessions:serialisation",
     "//ios/chrome/browser/sessions:session_service",
     "//ios/chrome/browser/snapshots",
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler.mm b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
index 89ee44e..bf95ad5a 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
@@ -16,6 +16,7 @@
 #import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/main/browser_list.h"
 #import "ios/chrome/browser/main/browser_list_factory.h"
+#import "ios/chrome/browser/sessions/scene_util.h"
 #import "ios/chrome/browser/sessions/session_restoration_browser_agent.h"
 #import "ios/chrome/browser/snapshots/snapshot_browser_agent.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
@@ -406,53 +407,31 @@
 
 - (void)setSessionIDForBrowser:(Browser*)browser
                 restoreSession:(BOOL)restoreSession {
+  // The location were the session and snapshots are stored can change due to
+  // multiple factors, such as upgrading Chrome or iOS from a version that did
+  // not support multiple windows to one that does (e.g. Chrome M86 or earlier
+  // to M87, iOS 12.x to iOS 13.0+), or upgrading Chrome from M87-M89 to M90+,
+  // or restoring an iPhone backup to an iPad.
+  //
+  // As the migration code is relatively quick when there is nothing to do, it
+  // is always attempted (will result in one directory lookup). Trying to check
+  // if the migration has to be done can be quite tricky, as both permanent and
+  // off-the-record BrowserState need to be independently migrated, migration
+  // also needs to happen on device that do support multiple scenes, ...
+  //
+  // Once the migration has been performed, the function will be a no-op, so it
+  // is safe to call it multiple time for the same BrowserState.
+  MigrateSessionStorageForDirectory(
+      browser->GetBrowserState()->GetStatePath(), _sceneState.sceneSessionID,
+      _sceneState.appState.previousSingleWindowSessionID);
+
   SnapshotBrowserAgent::FromBrowser(browser)->SetSessionID(
-      base::SysNSStringToUTF8(_sceneState.sceneSessionID));
+      _sceneState.sceneSessionID);
 
   SessionRestorationBrowserAgent* restorationAgent =
       SessionRestorationBrowserAgent::FromBrowser(browser);
 
-  // crbug.com/1153606: when the app is distributed with multi-window enabled,
-  // the "swipe gesture" is sometimes interpreted as a "close window" gesture
-  // by iOS (reproduce easily if the user launch app, swipe it away in a loop).
-  // On the next start after iOS has decided the gesture is "close window", the
-  // session identifier will be reset to a different value.
-  //
-  // For device that support multiple windows (recent iPads running iOS 13+),
-  // the user has the option to restore recently closed windows (presented by
-  // iOS when user ask to see all windows). For other devices however they have
-  // no option to re-open the closed window and lose their data.
-  //
-  // To workaround this behaviour, the session identifier is saved, and on each
-  // run, if the device does not support multi-window, compared to the current
-  // session identifier. If there is a mismatch, load the session using the old
-  // identifier (which is used to construct path to Chrome's session data), and
-  // immediately save it with the new identifier.
-  //
-  // TODO(crbug.com/1165798): clean up this by using fixed identifier when the
-  // device do no support multi-windows.
-  if (!base::ios::IsMultipleScenesSupported() && restoreSession) {
-    NSString* previousSessionID =
-        _sceneState.appState.previousSingleWindowSessionID;
-    if (previousSessionID &&
-        ![_sceneState.sceneSessionID isEqualToString:previousSessionID]) {
-      restorationAgent->SetSessionID(
-          base::SysNSStringToUTF8(previousSessionID));
-      restorationAgent->RestoreSession();
-
-      restorationAgent->SetSessionID(
-          base::SysNSStringToUTF8(_sceneState.sceneSessionID));
-      restorationAgent->SaveSession(true);
-
-      // Fallback to the normal codepath. It will set the session identifier
-      // in the SessionRestorationBrowserAgent. Since the session has been
-      // loaded already, skip this step by setting |restoreSession| to NO.
-      restoreSession = NO;
-    }
-  }
-
-  restorationAgent->SetSessionID(
-      base::SysNSStringToUTF8(_sceneState.sceneSessionID));
+  restorationAgent->SetSessionID(_sceneState.sceneSessionID);
   if (restoreSession)
     restorationAgent->RestoreSession();
 }
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm b/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm
index c6d675f4..bc7cca1 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm
@@ -20,6 +20,7 @@
 #import "ios/testing/scoped_block_swizzler.h"
 #include "ios/web/public/test/web_task_environment.h"
 #include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -32,6 +33,16 @@
   BrowserViewWranglerTest()
       : scene_state_([[SceneState alloc] initWithAppState:nil]),
         test_session_service_([[TestSessionService alloc] init]) {
+    if (@available(ios 13, *)) {
+      NSString* session_id = [[NSUUID UUID] UUIDString];
+      id scene_session = OCMClassMock([UISceneSession class]);
+      OCMStub([scene_session persistentIdentifier]).andReturn(session_id);
+
+      id scene = OCMClassMock([UIScene class]);
+      OCMStub([scene session]).andReturn(scene);
+      scene_state_.scene = scene;
+    }
+
     TestChromeBrowserState::Builder test_cbs_builder;
     test_cbs_builder.AddTestingFactory(
         SendTabToSelfSyncServiceFactory::GetInstance(),
diff --git a/ios/chrome/browser/ui/main/scene_state.mm b/ios/chrome/browser/ui/main/scene_state.mm
index a7e73b7..9cf108a1 100644
--- a/ios/chrome/browser/ui/main/scene_state.mm
+++ b/ios/chrome/browser/ui/main/scene_state.mm
@@ -12,6 +12,7 @@
 #include "base/strings/sys_string_conversions.h"
 #import "ios/chrome/app/application_delegate/app_state.h"
 #import "ios/chrome/app/chrome_overlay_window.h"
+#import "ios/chrome/browser/sessions/scene_util.h"
 #import "ios/chrome/browser/ui/main/scene_controller.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -128,13 +129,11 @@
 }
 
 - (NSString*)sceneSessionID {
-  NSString* sessionID = nil;
-  if (@available(ios 13, *)) {
-    if (base::ios::IsMultiwindowSupported()) {
-      sessionID = _scene.session.persistentIdentifier;
-    }
-  }
-  return sessionID;
+  id maybe_scene = nil;
+  if (@available(ios 13, *))
+    maybe_scene = _scene;
+
+  return SessionIdentifierForScene(maybe_scene);
 }
 
 - (void)setActivationLevel:(SceneActivationLevel)newLevel {
diff --git a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
index 3c17e9b..3476e668 100644
--- a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
@@ -345,13 +345,11 @@
 - (TableViewItem*)signOutItem {
   TableViewTextItem* item =
       [[TableViewTextItem alloc] initWithType:ItemTypeSignOut];
+  item.text =
+      l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE);
   if (base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS)) {
-    item.text = l10n_util::GetNSString(
-        IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE_MICE);
     item.textColor = [UIColor colorNamed:kRedColor];
   } else {
-    item.text = l10n_util::GetNSString(
-        IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE);
     item.textColor = [UIColor colorNamed:kBlueColor];
   }
   item.accessibilityTraits |= UIAccessibilityTraitButton;
diff --git a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
index 1f383822..4863253c 100644
--- a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
@@ -13,6 +13,7 @@
 #import "components/prefs/ios/pref_observer_bridge.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
+#include "components/signin/public/base/account_consistency_method.h"
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
@@ -201,7 +202,11 @@
       [[TableViewLinkHeaderFooterItem alloc]
           initWithType:ItemTypePrivacyFooter];
   showPrivacyFooterItem.text =
-      l10n_util::GetNSString(IDS_IOS_OPTIONS_PRIVACY_GOOGLE_SERVICES_FOOTER);
+      signin::IsMobileIdentityConsistencyEnabled()
+          ? l10n_util::GetNSString(IDS_IOS_PRIVACY_GOOGLE_SERVICES_FOOTER)
+          : l10n_util::GetNSString(
+                IDS_IOS_OPTIONS_PRIVACY_GOOGLE_SERVICES_FOOTER);
+
   showPrivacyFooterItem.linkURL = GURL(kGoogleServicesSettingsURL);
 
   return showPrivacyFooterItem;
diff --git a/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.mm b/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.mm
index 5673e17..0488e8c 100644
--- a/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.mm
+++ b/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.mm
@@ -19,6 +19,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/safety_check/safety_check.h"
+#include "components/signin/public/base/account_consistency_method.h"
 #include "components/version_info/version_info.h"
 #include "ios/chrome/browser/application_context.h"
 #import "ios/chrome/browser/omaha/omaha_service.h"
@@ -466,8 +467,12 @@
     case PasswordItemType:
       return [self passwordCheckErrorInfo];
     case SafeBrowsingItemType: {
-      NSString* message = l10n_util::GetNSString(
-          IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED_INFO);
+      NSString* message =
+          signin::IsMobileIdentityConsistencyEnabled()
+              ? l10n_util::GetNSString(
+                    IDS_IOS_SETTINGS_SAFETY_CHECK_OPEN_SAFE_BROWSING_INFO)
+              : l10n_util::GetNSString(
+                    IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED_INFO);
       GURL safeBrowsingURL(
           base::SysNSStringToUTF8(kSafeBrowsingSafetyCheckStringURL));
       return [self attributedStringWithText:message link:safeBrowsingURL];
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
index 86e9823..ab2fda381 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
@@ -648,6 +648,7 @@
       collectionViewUpdatesCompletion:completion];
 
   [self updateVisibleCellZIndex];
+  [self updateVisibleCellIdentifiers];
 }
 
 - (void)removeItemWithID:(NSString*)removedItemID
@@ -679,6 +680,7 @@
       collectionViewUpdatesCompletion:completion];
 
   [self updateVisibleCellZIndex];
+  [self updateVisibleCellIdentifiers];
 }
 
 - (void)selectItemWithID:(NSString*)selectedItemID {
@@ -735,6 +737,7 @@
       collectionViewUpdatesCompletion:completion];
 
   [self updateVisibleCellZIndex];
+  [self updateVisibleCellIdentifiers];
 }
 
 #pragma mark - LayoutSwitcher
@@ -966,4 +969,16 @@
   }
 }
 
+// Update visible cells identifier, following a reorg of cells.
+- (void)updateVisibleCellIdentifiers {
+  for (NSIndexPath* indexPath in self.collectionView
+           .indexPathsForVisibleItems) {
+    UICollectionViewCell* cell =
+        [self.collectionView cellForItemAtIndexPath:indexPath];
+    NSUInteger itemIndex = base::checked_cast<NSUInteger>(indexPath.item);
+    cell.accessibilityIdentifier = [NSString
+        stringWithFormat:@"%@%ld", kGridCellIdentifierPrefix, itemIndex];
+  }
+}
+
 @end
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_unittest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_unittest.mm
index bb2d255..10cdf4b 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_unittest.mm
@@ -69,7 +69,7 @@
 void AddAgentsToBrowser(Browser* browser, SceneState* scene_state) {
   SnapshotBrowserAgent::CreateForBrowser(browser);
   SnapshotBrowserAgent::FromBrowser(browser)->SetSessionID(
-      base::SysNSStringToUTF8([[NSUUID UUID] UUIDString]));
+      [[NSUUID UUID] UUIDString]);
   SceneStateBrowserAgent::CreateForBrowser(browser, scene_state);
 }
 
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 9117cb2..4fac8034 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
@@ -24,17 +24,23 @@
 #endif
 
 using chrome_test_util::TabGridOtherDevicesPanelButton;
-using chrome_test_util::LongPressAndDragToEdge;
+using chrome_test_util::LongPressCellAndDragToEdge;
+using chrome_test_util::LongPressCellAndDragToOffsetOf;
+using chrome_test_util::TapAtOffsetOf;
+using chrome_test_util::WindowWithNumber;
 
 namespace {
 char kURL1[] = "http://firstURL";
 char kURL2[] = "http://secondURL";
 char kURL3[] = "http://thirdURL";
+char kURL4[] = "http://fourthURL";
 char kTitle1[] = "Page 1";
 char kTitle2[] = "Page 2";
+char kTitle4[] = "Page 4";
 char kResponse1[] = "Test Page 1 content";
 char kResponse2[] = "Test Page 2 content";
 char kResponse3[] = "Test Page 3 content";
+char kResponse4[] = "Test Page 4 content";
 
 // Matcher for the 'Close All' confirmation button.
 id<GREYMatcher> CloseAllTabsConfirmationWithNumberOfTabs(
@@ -50,12 +56,14 @@
 NSString* IdentifierForCellAtIndex(unsigned int index) {
   return [NSString stringWithFormat:@"%@%u", kGridCellIdentifierPrefix, index];
 }
+
 }  // namespace
 
 @interface TabGridTestCase : WebHttpServerChromeTestCase {
   GURL _URL1;
   GURL _URL2;
   GURL _URL3;
+  GURL _URL4;
 }
 @end
 
@@ -67,6 +75,7 @@
   _URL1 = web::test::HttpServer::MakeUrl(kURL1);
   _URL2 = web::test::HttpServer::MakeUrl(kURL2);
   _URL3 = web::test::HttpServer::MakeUrl(kURL3);
+  _URL4 = web::test::HttpServer::MakeUrl(kURL4);
 
   std::map<GURL, std::string> responses;
   const char kPageFormat[] = "<head><title>%s</title></head><body>%s</body>";
@@ -74,6 +83,7 @@
   responses[_URL2] = base::StringPrintf(kPageFormat, kTitle2, kResponse2);
   // Page 3 does not have <title> tag, so URL will be its title.
   responses[_URL3] = kResponse3;
+  responses[_URL4] = base::StringPrintf(kPageFormat, kTitle4, kResponse4);
   web::test::SetUpSimpleHttpServer(responses);
 }
 
@@ -339,7 +349,9 @@
 
   GREYWaitForAppToIdle(@"App failed to idle");
 
-  LongPressAndDragToEdge(IdentifierForCellAtIndex(0), kGREYContentEdgeRight);
+  GREYAssert(LongPressCellAndDragToEdge(IdentifierForCellAtIndex(0),
+                                        kGREYContentEdgeRight, 0),
+             @"Failed to DND cell");
 
   GREYWaitForAppToIdle(@"App failed to idle");
 
@@ -353,22 +365,20 @@
                              inWindowWithNumber:1];
 
   // Navigate back on second window to check the navigation stack is intact.
-  [EarlGrey setRootMatcherForSubsequentInteractions:chrome_test_util::
-                                                        WindowWithNumber(1)];
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
       performAction:grey_tap()];
   [ChromeEarlGrey waitForWebStateContainingText:kResponse1
                              inWindowWithNumber:1];
 
-  [EarlGrey setRootMatcherForSubsequentInteractions:chrome_test_util::
-                                                        WindowWithNumber(0)];
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
       performAction:grey_tap()];
 }
 
 // Tests that dragging a tab grid incognito item to the edge opens a new window
 // and that the tab is properly transferred, incuding navigation stack.
-// TODO(crbug.com/1176180): re-eable when it is fixed.
+// TODO(crbug.com/1176180): re-enable this test when it is fixed.
 - (void)DISABLED_testIncognitoDragAndDropAtEdgeToCreateNewWindow {
   if (![ChromeEarlGrey areMultipleWindowsSupported])
     EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
@@ -391,7 +401,9 @@
 
   GREYWaitForAppToIdle(@"App failed to idle");
 
-  LongPressAndDragToEdge(IdentifierForCellAtIndex(0), kGREYContentEdgeRight);
+  GREYAssert(LongPressCellAndDragToEdge(IdentifierForCellAtIndex(0),
+                                        kGREYContentEdgeRight, 0),
+             @"Failed to DND cell");
 
   GREYWaitForAppToIdle(@"App failed to idle");
 
@@ -405,15 +417,350 @@
                              inWindowWithNumber:1];
 
   // Navigate back on second window to check the navigation stack is intact.
-  [EarlGrey setRootMatcherForSubsequentInteractions:chrome_test_util::
-                                                        WindowWithNumber(1)];
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
       performAction:grey_tap()];
   [ChromeEarlGrey waitForWebStateContainingText:kResponse1
                              inWindowWithNumber:1];
 
-  [EarlGrey setRootMatcherForSubsequentInteractions:chrome_test_util::
-                                                        WindowWithNumber(0)];
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
+      performAction:grey_tap()];
+}
+
+// Tests dragging tab grid item between windows.
+- (void)testDragAndDropBetweenWindows {
+  if (![ChromeEarlGrey areMultipleWindowsSupported])
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
+
+  // Setup first window with tabs 1 and 2.
+  [ChromeEarlGrey loadURL:_URL1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
+
+  [ChromeEarlGrey openNewTab];
+  [ChromeEarlGrey loadURL:_URL2];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];
+
+  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:0];
+
+  // Open second window.
+  [ChromeEarlGrey openNewWindow];
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
+
+  // Setup second window with tabs 3 and 4.
+  [ChromeEarlGrey loadURL:_URL3 inWindowWithNumber:1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
+                             inWindowWithNumber:1];
+
+  [ChromeEarlGrey openNewTabInWindowWithNumber:1];
+  [ChromeEarlGrey loadURL:_URL4 inWindowWithNumber:1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse4
+                             inWindowWithNumber:1];
+
+  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:1];
+
+  // Open tab grid in both window.
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
+      performAction:grey_tap()];
+
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
+      performAction:grey_tap()];
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  // DnD first tab of left window to left edge of first tab in second window.
+  // Note: move to left half of the destination tile, to avoid unwanted
+  // scrolling that would happen closer to the left edge.
+  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(0), 0,
+                                            IdentifierForCellAtIndex(0), 1,
+                                            CGVectorMake(0.4, 0.5)),
+             @"Failed to DND cell on cell");
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForMainTabCount:3 inWindowWithNumber:1];
+
+  // Move third cell of second window as second cell in first window.
+  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(2), 1,
+                                            IdentifierForCellAtIndex(0), 0,
+                                            CGVectorMake(1.0, 0.5)),
+             @"Failed to DND cell on cell");
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:1];
+
+  // Check content and order of tabs.
+  [self fromGridCheckTabAtIndex:0 inWindowNumber:0 containsText:kResponse2];
+  [self fromGridCheckTabAtIndex:1 inWindowNumber:0 containsText:kResponse4];
+  [self fromGridCheckTabAtIndex:0 inWindowNumber:1 containsText:kResponse1];
+  [self fromGridCheckTabAtIndex:1 inWindowNumber:1 containsText:kResponse3];
+
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
+      performAction:grey_tap()];
+
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
+      performAction:grey_tap()];
+}
+
+// Tests dragging tab grid item between windows.
+// TODO(crbug.com/1176669): re-enable this test when it is fixed.
+- (void)DISABLED_testDragAndDropIncognitoBetweenWindows {
+  if (![ChromeEarlGrey areMultipleWindowsSupported])
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
+
+  // Setup first window with one incognito tab.
+  [ChromeEarlGrey closeAllNormalTabs];
+  [ChromeEarlGrey openNewIncognitoTab];
+  [ChromeEarlGrey loadURL:_URL1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
+
+  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];
+
+  // Open second window with main ntp.
+  [ChromeEarlGrey openNewWindow];
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
+
+  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];
+
+  // Open tab grid in both window.
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
+      performAction:grey_tap()];
+
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
+      performAction:grey_tap()];
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  // Try DnDing first incognito tab of left window to main tab panel on right
+  // window.
+  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(0), 0,
+                                            IdentifierForCellAtIndex(0), 1,
+                                            CGVectorMake(1.0, 0.5)),
+             @"Failed to DND cell on cell");
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  // It should fail and both windows should still have only one tab.
+  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];
+
+  // Move second window to incognito tab panel.
+  // Note: until reported bug is fixed in EarlGrey, grey_tap() doesn't always
+  // work in second window, because it fails the visibility check.
+  GREYAssert(TapAtOffsetOf(kTabGridIncognitoTabsPageButtonIdentifier, 1,
+                           CGVectorMake(0.5, 0.5)),
+             @"Failed to tap incognito panel button");
+
+  // Try again to move tabs.
+  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(0), 0, nil,
+                                            1, CGVectorMake(0.5, 0.5)),
+             @"Failed to DND cell on window");
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  // Check that it worked and there are 2 incgnito tabs in second window.
+  [ChromeEarlGrey waitForIncognitoTabCount:0 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:1];
+
+  // Cleanup.
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
+      performAction:grey_tap()];
+
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
+      performAction:grey_tap()];
+}
+
+// Tests dragging tab grid item as URL between windows.
+- (void)testDragAndDropURLBetweenWindows {
+  if (![ChromeEarlGrey areMultipleWindowsSupported])
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
+
+  // Setup first window with tabs 1 and 2.
+  [ChromeEarlGrey loadURL:_URL1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
+
+  [ChromeEarlGrey openNewTab];
+  [ChromeEarlGrey loadURL:_URL2];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];
+
+  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:0];
+
+  // Open second window.
+  [ChromeEarlGrey openNewWindow];
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
+
+  // Setup second window with tab 3.
+  [ChromeEarlGrey loadURL:_URL3 inWindowWithNumber:1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
+                             inWindowWithNumber:1];
+
+  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];
+
+  // Open tab grid in first window.
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
+      performAction:grey_tap()];
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  // DnD first tab of left window to second window.
+  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(0), 0, nil,
+                                            1, CGVectorMake(0.5, 0.5)),
+             @"Failed to DND cell on window");
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  // Tabs should not have changed.
+  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];
+
+  // Second window should show URL1
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse1
+                             inWindowWithNumber:1];
+
+  // Navigate back to check the navigation stack is intact.
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
+      performAction:grey_tap()];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
+                             inWindowWithNumber:1];
+
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
+      performAction:grey_tap()];
+}
+
+// Tests dragging tab grid incognito item as URL to a main windows.
+- (void)testDragAndDropIncognitoURLInMainWindow {
+  if (![ChromeEarlGrey areMultipleWindowsSupported])
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
+
+  // Setup first window with one incognito tab 1.
+  [ChromeEarlGrey closeAllNormalTabs];
+  [ChromeEarlGrey openNewIncognitoTab];
+  [ChromeEarlGrey loadURL:_URL1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
+
+  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];
+
+  // Open second window.
+  [ChromeEarlGrey openNewWindow];
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
+
+  // Setup second window with tab 3.
+  [ChromeEarlGrey loadURL:_URL3 inWindowWithNumber:1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
+                             inWindowWithNumber:1];
+
+  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];
+
+  // Open incognito tab grid in first window.
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::
+                                          TabGridIncognitoTabsPanelButton()]
+      performAction:grey_tap()];
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  // DnD first tab of left window to second window.
+  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(0), 0, nil,
+                                            1, CGVectorMake(0.5, 0.5)),
+             @"Failed to DND cell on window");
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  // Tabs should not have changed.
+  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];
+
+  // Second window should show URL1
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse1
+                             inWindowWithNumber:1];
+
+  // Navigate back to check the navigation stack is intact.
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
+      performAction:grey_tap()];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
+                             inWindowWithNumber:1];
+
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
+      performAction:grey_tap()];
+}
+
+// Tests dragging tab grid main item as URL to an incognito windows.
+- (void)testDragAndDropMainURLInIncognitoWindow {
+  if (![ChromeEarlGrey areMultipleWindowsSupported])
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
+
+  // Setup first window with one incognito tab 1.
+  [ChromeEarlGrey closeAllNormalTabs];
+  [ChromeEarlGrey openNewIncognitoTab];
+  [ChromeEarlGrey loadURL:_URL1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
+
+  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];
+
+  // Open second window.
+  [ChromeEarlGrey openNewWindow];
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
+
+  // Setup second window with tab 3.
+  [ChromeEarlGrey loadURL:_URL3 inWindowWithNumber:1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
+                             inWindowWithNumber:1];
+
+  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];
+
+  // Open incognito tab grid in first window.
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
+      performAction:grey_tap()];
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  // DnD first tab of second window to first window.
+  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(0), 1, nil,
+                                            0, CGVectorMake(0.5, 0.5)),
+             @"Failed to DND cell on window");
+
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  // Tabs should not have changed.
+  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];
+  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];
+
+  // First window should show URL3
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
+                             inWindowWithNumber:0];
+
+  // Navigate back to check the navigation stack is intact.
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
+      performAction:grey_tap()];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse1
+                             inWindowWithNumber:0];
+
+  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
       performAction:grey_tap()];
 }
@@ -424,11 +771,9 @@
   [ChromeEarlGrey loadURL:_URL1];
   [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
 
-  [ChromeEarlGrey openNewTab];
   [ChromeEarlGrey loadURL:_URL2];
   [ChromeEarlGrey waitForWebStateContainingText:kResponse2];
 
-  [ChromeEarlGrey openNewTab];
   [ChromeEarlGrey loadURL:_URL3];
   [ChromeEarlGrey waitForWebStateContainingText:kResponse3];
 }
@@ -456,4 +801,19 @@
       atIndex:0] performAction:grey_longPress()];
 }
 
+// Checks if the content of the given tab in the given window matches given
+// text. This method exits the tab grid and re-enters it afterward.
+- (void)fromGridCheckTabAtIndex:(int)tabIndex
+                 inWindowNumber:(int)windowNumber
+                   containsText:(const char*)text {
+  [EarlGrey
+      setRootMatcherForSubsequentInteractions:WindowWithNumber(windowNumber)];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(
+                                          tabIndex)] performAction:grey_tap()];
+  [ChromeEarlGrey waitForWebStateContainingText:text
+                             inWindowWithNumber:windowNumber];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
+      performAction:grey_tap()];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator_unittest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator_unittest.mm
index d05653c..874e93f 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator_unittest.mm
@@ -252,7 +252,7 @@
     ClosingWebStateObserverBrowserAgent::CreateForBrowser(browser_.get());
     SnapshotBrowserAgent::CreateForBrowser(browser_.get());
     SnapshotBrowserAgent::FromBrowser(browser_.get())
-        ->SetSessionID(base::SysNSStringToUTF8([[NSUUID UUID] UUIDString]));
+        ->SetSessionID([[NSUUID UUID] UUIDString]);
 
     // Insert some web states.
     for (int i = 0; i < 3; i++) {
@@ -302,6 +302,8 @@
         [[TestSessionService alloc] init];
     SessionRestorationBrowserAgent::CreateForBrowser(browser_.get(),
                                                      test_session_service);
+    SessionRestorationBrowserAgent::FromBrowser(browser_.get())
+        ->SetSessionID([[NSUUID UUID] UUIDString]);
   }
 
  protected:
diff --git a/ios/chrome/browser/ui/thumb_strip/BUILD.gn b/ios/chrome/browser/ui/thumb_strip/BUILD.gn
index 8c547a9..e03d15f 100644
--- a/ios/chrome/browser/ui/thumb_strip/BUILD.gn
+++ b/ios/chrome/browser/ui/thumb_strip/BUILD.gn
@@ -14,6 +14,7 @@
   ]
   deps = [
     "//base",
+    "//ios/chrome/browser",
     "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/gestures",
diff --git a/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.mm b/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.mm
index d889033..0247dd0d 100644
--- a/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.mm
+++ b/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.mm
@@ -21,7 +21,7 @@
     2 * kGridLayoutLineSpacingCompactCompactLimitedWidth;
 }  // namespace
 
-@interface ThumbStripCoordinator ()
+@interface ThumbStripCoordinator () <ThumbStripNavigationConsumer>
 
 @property(nonatomic, strong) ThumbStripMediator* mediator;
 
@@ -39,6 +39,7 @@
             baseViewHeight:baseViewHeight];
 
   self.mediator = [[ThumbStripMediator alloc] init];
+  self.mediator.consumer = self;
   if (self.regularBrowser) {
     self.mediator.regularWebStateList = self.regularBrowser->GetWebStateList();
   }
@@ -61,4 +62,10 @@
       _incognitoBrowser ? _incognitoBrowser->GetWebStateList() : nullptr;
 }
 
+#pragma mark - ThumbStripNavigationConsumer
+
+- (void)navigationDidStart {
+  [self.panHandler setNextState:ViewRevealState::Hidden animated:YES];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.h b/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.h
index bd67907..c98c4f4 100644
--- a/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.h
+++ b/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.h
@@ -10,10 +10,19 @@
 @protocol CRWWebViewScrollViewProxyObserver;
 class WebStateList;
 
+// Protocol for the thumb strip mediator to inform others about navigation
+// changes.
+@protocol ThumbStripNavigationConsumer
+- (void)navigationDidStart;
+@end
+
 // Mediator for the thumb strip. Handles observing changes in the active web
 // state.
 @interface ThumbStripMediator : NSObject
 
+// Consumer for this mediator to inform about updates.
+@property(nonatomic, weak) id<ThumbStripNavigationConsumer> consumer;
+
 // The regular web state list to observe.
 @property(nonatomic, assign) WebStateList* regularWebStateList;
 // The incognito web state list to observe.
diff --git a/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.mm b/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.mm
index ccfea19..4a744a1 100644
--- a/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.mm
+++ b/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.mm
@@ -4,18 +4,25 @@
 
 #import "ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.h"
 
+#import "ios/chrome/browser/chrome_url_util.h"
+#import "ios/chrome/browser/web_state_list/active_web_state_observation_forwarder.h"
 #include "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
+#import "ios/web/public/navigation/navigation_context.h"
 #import "ios/web/public/ui/crw_web_view_proxy.h"
 #import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
 #import "ios/web/public/web_state.h"
+#import "ios/web/public/web_state_observer_bridge.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
-@interface ThumbStripMediator () <WebStateListObserving> {
+@interface ThumbStripMediator () <WebStateListObserving, CRWWebStateObserver> {
   std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;
+  std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
+  std::unique_ptr<ActiveWebStateObservationForwarder> _regularForwarder;
+  std::unique_ptr<ActiveWebStateObservationForwarder> _incognitoForwarder;
 }
 @end
 
@@ -24,11 +31,16 @@
 - (instancetype)init {
   if (self = [super init]) {
     _webStateListObserver = std::make_unique<WebStateListObserverBridge>(self);
+
+    // Set up the active web state observer.
+    _webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
   }
   return self;
 }
 
 - (void)dealloc {
+  _regularForwarder = nullptr;
+  _incognitoForwarder = nullptr;
   if (_regularWebStateList) {
     _regularWebStateList->RemoveObserver(_webStateListObserver.get());
   }
@@ -41,6 +53,7 @@
   if (_regularWebStateList) {
     _regularWebStateList->RemoveObserver(_webStateListObserver.get());
     [self removeObserverFromWebState:_regularWebStateList->GetActiveWebState()];
+    _regularForwarder = nullptr;
   }
 
   _regularWebStateList = regularWebStateList;
@@ -48,6 +61,8 @@
   if (_regularWebStateList) {
     _regularWebStateList->AddObserver(_webStateListObserver.get());
     [self addObserverToWebState:_regularWebStateList->GetActiveWebState()];
+    _regularForwarder = std::make_unique<ActiveWebStateObservationForwarder>(
+        _regularWebStateList, _webStateObserver.get());
   }
 }
 
@@ -56,6 +71,7 @@
     _incognitoWebStateList->RemoveObserver(_webStateListObserver.get());
     [self
         removeObserverFromWebState:_incognitoWebStateList->GetActiveWebState()];
+    _incognitoForwarder = nullptr;
   }
 
   _incognitoWebStateList = incognitoWebStateList;
@@ -63,6 +79,8 @@
   if (_incognitoWebStateList) {
     _incognitoWebStateList->AddObserver(_webStateListObserver.get());
     [self addObserverToWebState:_incognitoWebStateList->GetActiveWebState()];
+    _incognitoForwarder = std::make_unique<ActiveWebStateObservationForwarder>(
+        _incognitoWebStateList, _webStateObserver.get());
   }
 }
 
@@ -118,4 +136,17 @@
   [self addObserverToWebState:newWebState];
 }
 
+#pragma mark - CRWWebStateObserver
+
+- (void)webState:(web::WebState*)webState
+    didStartNavigation:(web::NavigationContext*)navigation {
+  // Don't alert the consumer if this navigation is the first navigation in
+  // a newly opened tab. That doesn't count.
+  if (IsURLNtp(webState->GetVisibleURL()) &&
+      webState->GetLastCommittedURL().is_empty()) {
+    return;
+  }
+  [self.consumer navigationDidStart];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/ui_feature_flags.cc b/ios/chrome/browser/ui/ui_feature_flags.cc
index d557571..6084faa 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.cc
+++ b/ios/chrome/browser/ui/ui_feature_flags.cc
@@ -61,9 +61,6 @@
     "DefaultBrowserFullscreenPromoExperiment",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kIOSSharedHighlightingColorChange{
-    "IOSSharedHighlightingColorChange", base::FEATURE_DISABLED_BY_DEFAULT};
-
 const base::Feature kIOSNewOmniboxImplementation{
     "kIOSNewOmniboxImplementation", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/ios/chrome/browser/ui/ui_feature_flags.h b/ios/chrome/browser/ui/ui_feature_flags.h
index d6cd7c5..7558508 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.h
+++ b/ios/chrome/browser/ui/ui_feature_flags.h
@@ -65,9 +65,6 @@
 // Feature flag that experiments with the default browser fullscreen promo UI.
 extern const base::Feature kDefaultBrowserFullscreenPromoExperiment;
 
-// Feature flag that enable Shared Highlighting color change in iOS.
-extern const base::Feature kIOSSharedHighlightingColorChange;
-
 // Feature flag that swaps the omnibox textfield implementation.
 extern const base::Feature kIOSNewOmniboxImplementation;
 
diff --git a/ios/chrome/browser/web/BUILD.gn b/ios/chrome/browser/web/BUILD.gn
index 22f0b71..2b531b3 100644
--- a/ios/chrome/browser/web/BUILD.gn
+++ b/ios/chrome/browser/web/BUILD.gn
@@ -257,6 +257,7 @@
     "//components/dom_distiller/core",
     "//components/google/core/common",
     "//components/infobars/core",
+    "//components/password_manager/core/common",
     "//components/payments/core",
     "//components/prefs",
     "//components/resources",
diff --git a/ios/chrome/browser/web/chrome_web_client.h b/ios/chrome/browser/web/chrome_web_client.h
index f1e1f7a..bcde31f 100644
--- a/ios/chrome/browser/web/chrome_web_client.h
+++ b/ios/chrome/browser/web/chrome_web_client.h
@@ -39,6 +39,8 @@
       std::vector<std::string>* additional_schemes) override;
   void PostBrowserURLRewriterCreation(
       web::BrowserURLRewriter* rewriter) override;
+  std::vector<web::JavaScriptFeature*> GetJavaScriptFeatures(
+      web::BrowserState* browser_state) const override;
   NSString* GetDocumentStartScriptForAllFrames(
       web::BrowserState* browser_state) const override;
   NSString* GetDocumentStartScriptForMainFrame(
diff --git a/ios/chrome/browser/web/chrome_web_client.mm b/ios/chrome/browser/web/chrome_web_client.mm
index eef81c2..bd581f0 100644
--- a/ios/chrome/browser/web/chrome_web_client.mm
+++ b/ios/chrome/browser/web/chrome_web_client.mm
@@ -14,6 +14,7 @@
 #include "base/strings/sys_string_conversions.h"
 #include "components/dom_distiller/core/url_constants.h"
 #include "components/google/core/common/google_util.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/version_info/version_info.h"
 #include "ios/chrome/browser/application_context.h"
@@ -23,6 +24,7 @@
 #include "ios/chrome/browser/chrome_url_constants.h"
 #include "ios/chrome/browser/ios_chrome_main_parts.h"
 #import "ios/chrome/browser/reading_list/offline_page_tab_helper.h"
+#import "ios/chrome/browser/safe_browsing/password_protection_java_script_feature.h"
 #import "ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.h"
 #import "ios/chrome/browser/safe_browsing/safe_browsing_error.h"
 #import "ios/chrome/browser/safe_browsing/safe_browsing_unsafe_resource_container.h"
@@ -280,6 +282,17 @@
     provider->AddProviderRewriters(rewriter);
 }
 
+std::vector<web::JavaScriptFeature*> ChromeWebClient::GetJavaScriptFeatures(
+    web::BrowserState* browser_state) const {
+  std::vector<web::JavaScriptFeature*> features;
+  if (base::FeatureList::IsEnabled(
+          password_manager::features::kPasswordReuseDetectionEnabled) &&
+      base::ios::IsRunningOnIOS14OrLater()) {
+    features.push_back(PasswordProtectionJavaScriptFeature::GetInstance());
+  }
+  return features;
+}
+
 NSString* ChromeWebClient::GetDocumentStartScriptForAllFrames(
     web::BrowserState* browser_state) const {
   return GetPageScript(@"chrome_bundle_all_frames");
diff --git a/ios/chrome/test/app/window_test_util.h b/ios/chrome/test/app/window_test_util.h
index 7c8de60..0253e92 100644
--- a/ios/chrome/test/app/window_test_util.h
+++ b/ios/chrome/test/app/window_test_util.h
@@ -25,6 +25,9 @@
 // Returns the number of incognito tabs, in window with given number.
 NSUInteger GetIncognitoTabCountForWindowWithNumber(int windowNumber);
 
+// Opens a new tab, in window with given number.
+void OpenNewTabInWindowWithNumber(int windowNumber);
+
 }  // namespace chrome_test_util
 
 #endif
diff --git a/ios/chrome/test/app/window_test_util.mm b/ios/chrome/test/app/window_test_util.mm
index 2e38fcf1..1ebc28c5 100644
--- a/ios/chrome/test/app/window_test_util.mm
+++ b/ios/chrome/test/app/window_test_util.mm
@@ -7,9 +7,16 @@
 #import <Foundation/Foundation.h>
 
 #import "ios/chrome/app/main_controller.h"
+#include "ios/chrome/browser/chrome_url_constants.h"
 #import "ios/chrome/browser/main/browser.h"
+#import "ios/chrome/browser/ui/commands/browser_commands.h"
+#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
 #import "ios/chrome/browser/ui/main/browser_interface_provider.h"
+#import "ios/chrome/browser/ui/main/scene_controller.h"
+#import "ios/chrome/browser/ui/main/scene_controller_testing.h"
 #import "ios/chrome/browser/ui/main/scene_state.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.h"
+#import "ios/chrome/browser/url_loading/url_loading_params.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
 
@@ -17,8 +24,7 @@
 
 namespace {
 
-id<BrowserInterfaceProvider> GetInterfaceProviderForWindowWithNumber(
-    int windowNumber) {
+SceneState* GetSceneStateForWindowWithNumber(int windowNumber) {
   NSArray<SceneState*>* connected_scenes =
       GetMainController().appState.connectedScenes;
   NSString* accessibilityIdentifier =
@@ -26,12 +32,17 @@
   for (SceneState* state in connected_scenes) {
     if ([state.window.accessibilityIdentifier
             isEqualToString:accessibilityIdentifier]) {
-      return state.interfaceProvider;
+      return state;
     }
   }
   return nil;
 }
 
+id<BrowserInterfaceProvider> GetInterfaceProviderForWindowWithNumber(
+    int windowNumber) {
+  return GetSceneStateForWindowWithNumber(windowNumber).interfaceProvider;
+}
+
 // Returns the browser for the current mode.
 Browser* GetCurrentBrowserForWindowWithNumber(int windowNumber) {
   return GetInterfaceProviderForWindowWithNumber(windowNumber)
@@ -65,4 +76,24 @@
       ->count();
 }
 
+void OpenNewTabInWindowWithNumber(int windowNumber) {
+  @autoreleasepool {  // Make sure that all internals are deallocated.
+    OpenNewTabCommand* command = [OpenNewTabCommand command];
+    SceneController* controller =
+        GetSceneStateForWindowWithNumber(windowNumber).controller;
+    if (controller.mainCoordinator.isTabGridActive) {
+      // The TabGrid is currently presented.
+      Browser* browser = GetCurrentBrowserForWindowWithNumber(windowNumber);
+      UrlLoadParams params = UrlLoadParams::InNewTab(GURL(kChromeUINewTabURL));
+      [controller addANewTabAndPresentBrowser:browser withURLLoadParams:params];
+      return;
+    }
+    id<ApplicationCommands, BrowserCommands> handler =
+        static_cast<id<ApplicationCommands, BrowserCommands>>(
+            GetCurrentBrowserForWindowWithNumber(windowNumber)
+                ->GetCommandDispatcher());
+    [handler openURLInNewTab:command];
+  }
+}
+
 }  // namespace chrome_test_util
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h
index 99ecbeec..0eb980aa 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -367,6 +367,10 @@
 // Opens a new window.
 - (void)openNewWindow;
 
+// Opens a new tab in window with given number and waits for the new tab
+// animation to complete within a timeout, or a GREYAssert is induced.
+- (void)openNewTabInWindowWithNumber:(int)windowNumber;
+
 // Closes the window with given number. Note that numbering doesn't change and
 // if a new window is to be added in a test, a renumbering might be needed.
 - (void)closeWindowWithNumber:(int)windowNumber;
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
index c3a47b2..106f7e32 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -874,6 +874,12 @@
   EG_TEST_HELPER_ASSERT_NO_ERROR([ChromeEarlGreyAppInterface openNewWindow]);
 }
 
+- (void)openNewTabInWindowWithNumber:(int)windowNumber {
+  [ChromeEarlGreyAppInterface openNewTabInWindowWithNumber:windowNumber];
+  [self waitForPageToFinishLoadingInWindowWithNumber:windowNumber];
+  GREYWaitForAppToIdle(@"App failed to idle");
+}
+
 - (void)closeWindowWithNumber:(int)windowNumber {
   [ChromeEarlGreyAppInterface closeWindowWithNumber:windowNumber];
 }
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
index 26ced42..5690203 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
@@ -187,6 +187,10 @@
 // Open a new window. Returns an error if multiwindow is not supported.
 + (NSError*)openNewWindow;
 
+// Opens a new tab in window with given number, and does not wait for animations
+// to complete.
++ (void)openNewTabInWindowWithNumber:(int)windowNumber;
+
 // Closes the window with given number.
 + (void)closeWindowWithNumber:(int)windowNumber;
 
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index 4e36e902..ff5ae4e5 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -375,6 +375,10 @@
       @"Multiwindow supported on iOS13+ only");
 }
 
++ (void)openNewTabInWindowWithNumber:(int)windowNumber {
+  chrome_test_util::OpenNewTabInWindowWithNumber(windowNumber);
+}
+
 + (void)changeWindowWithNumber:(int)windowNumber
                    toNewNumber:(int)newWindowNumber {
   NSArray<SceneState*>* connectedScenes =
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.h b/ios/chrome/test/earl_grey/chrome_matchers.h
index 15191ec..bce4ad88 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers.h
@@ -279,7 +279,7 @@
 
 // Returns matcher for the back button on a settings menu in given window
 // number.
-id<GREYMatcher> SettingsMenuBackButton(int windowNumber);
+id<GREYMatcher> SettingsMenuBackButton(int window_number);
 
 // Returns matcher for the Privacy cell on the main Settings screen.
 id<GREYMatcher> SettingsMenuPrivacyButton();
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.mm b/ios/chrome/test/earl_grey/chrome_matchers.mm
index f6c36122..ac0bc3f2 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers.mm
@@ -351,9 +351,9 @@
   return [ChromeMatchersAppInterface settingsMenuBackButton];
 }
 
-id<GREYMatcher> SettingsMenuBackButton(int windowNumber) {
+id<GREYMatcher> SettingsMenuBackButton(int window_number) {
   return [ChromeMatchersAppInterface
-      settingsMenuBackButtonInWindowWithNumber:windowNumber];
+      settingsMenuBackButtonInWindowWithNumber:window_number];
 }
 
 id<GREYMatcher> SettingsMenuPrivacyButton() {
diff --git a/ios/chrome/test/earl_grey/chrome_xcui_actions.h b/ios/chrome/test/earl_grey/chrome_xcui_actions.h
index 2429c3c..8add2b68 100644
--- a/ios/chrome/test/earl_grey/chrome_xcui_actions.h
+++ b/ios/chrome/test/earl_grey/chrome_xcui_actions.h
@@ -11,12 +11,33 @@
 
 namespace chrome_test_util {
 
-// Action (XCUI, hence local) to long press an item and drag it to the given
-// |edge| (which probably will trigger a new window) before dropping it.
-// Returns YES on success (finding the element with given
-// |accessibilityIdentifier|).
-BOOL LongPressAndDragToEdge(NSString* accessibilityIdentifier,
-                            GREYContentEdge edge);
+// Action (XCUI, hence local) to long press a cell item with
+// |accessibility_identifier| in |window_number| and drag it to the given |edge|
+// of the app screen (can trigger a new window) before dropping it. Returns YES
+// on success (finding the item).
+BOOL LongPressCellAndDragToEdge(NSString* accessibility_identifier,
+                                GREYContentEdge edge,
+                                int window_number);
+
+// Action (XCUI, hence local) to long press a cell item  with
+// |src_accessibility_identifier| in |src_window_number| and drag it to the
+// given normalized offset of the cell or window with
+// |dst_accessibility_identifier| in |dst_window_number| before dropping it. To
+// target a window, pass nil as |dst_accessibility_identifier|. Returns YES on
+// success (finding both items).
+BOOL LongPressCellAndDragToOffsetOf(NSString* src_accessibility_identifier,
+                                    int src_window_number,
+                                    NSString* dst_accessibility_identifier,
+                                    int dst_window_number,
+                                    CGVector dst_normalized_offset);
+
+// Action (XCUI, hence local) to tap item with |accessibility_identifier| in
+// |window_number|. Should only be used in second or third window, until a
+// (already requested) fix is made to EarlGrey to allow using grey_tap()
+// correctly on extra windows (right now it fails visibility check).
+BOOL TapAtOffsetOf(NSString* accessibility_identifier,
+                   int window_number,
+                   CGVector normalized_offset);
 
 }  // namespace chrome_test_util
 
diff --git a/ios/chrome/test/earl_grey/chrome_xcui_actions.mm b/ios/chrome/test/earl_grey/chrome_xcui_actions.mm
index 7adb71d..48ea079 100644
--- a/ios/chrome/test/earl_grey/chrome_xcui_actions.mm
+++ b/ios/chrome/test/earl_grey/chrome_xcui_actions.mm
@@ -11,43 +11,119 @@
 #error "This file requires ARC support."
 #endif
 
+namespace {
+
+// Returns a normalized vector for the given edge.
+CGVector GetNormalizedEdgeVector(GREYContentEdge edge) {
+  switch (edge) {
+    case kGREYContentEdgeLeft:
+      return CGVectorMake(0.0, 0.5);
+      break;
+    case kGREYContentEdgeRight:
+      return CGVectorMake(1.0, 0.5);
+      break;
+    case kGREYContentEdgeTop:
+      return CGVectorMake(0.5, 0.0);
+      break;
+    case kGREYContentEdgeBottom:
+      return CGVectorMake(0.5, 1.0);
+      break;
+    default:
+      return CGVectorMake(0.5, 0.5);
+  }
+}
+
+// Creates a query for the given |identifier| of given |type| in given
+// |window_number|.  If |identitifer| is nil, this will return a query for the
+// window with |window_number| itself.
+XCUIElementQuery* GetQueryMatchingIdentifierInWindow(XCUIApplication* app,
+                                                     NSString* identifier,
+                                                     int window_number,
+                                                     XCUIElementType type) {
+  NSString* window_id = [NSString stringWithFormat:@"%d", window_number];
+  if (identifier) {
+    // Check for matching descendants.
+    return [[[app.windows matchingIdentifier:window_id]
+        descendantsMatchingType:type] matchingIdentifier:identifier];
+  }
+  // Check for window itself.
+  return [app.windows matchingIdentifier:window_id];
+}
+
+}  // namespace
+
 namespace chrome_test_util {
 
-BOOL LongPressAndDragToEdge(NSString* accessibilityIdentifier,
-                            GREYContentEdge edge) {
+BOOL LongPressCellAndDragToEdge(NSString* accessibility_identifier,
+                                GREYContentEdge edge,
+                                int window_number) {
   XCUIApplication* app = [[XCUIApplication alloc] init];
-  XCUIElementQuery* query =
-      [[app.windows descendantsMatchingType:XCUIElementTypeAny]
-          matchingIdentifier:accessibilityIdentifier];
+  XCUIElementQuery* query = GetQueryMatchingIdentifierInWindow(
+      app, accessibility_identifier, window_number, XCUIElementTypeCell);
 
   if (query.count == 0)
     return NO;
-  XCUIElement* dragElement = [query elementBoundByIndex:0];
+  XCUIElement* drag_element = [query elementBoundByIndex:0];
 
-  CGVector edgeCenter;
-  switch (edge) {
-    case kGREYContentEdgeLeft:
-      edgeCenter = CGVectorMake(0.0, 0.5);
-      break;
-    case kGREYContentEdgeRight:
-      edgeCenter = CGVectorMake(1.0, 0.5);
-      break;
-    case kGREYContentEdgeTop:
-      edgeCenter = CGVectorMake(0.5, 0.0);
-      break;
-    case kGREYContentEdgeBottom:
-      edgeCenter = CGVectorMake(0.5, 1.0);
-      break;
-  }
+  XCUICoordinate* start_point =
+      [drag_element coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.5)];
+  XCUICoordinate* end_point =
+      [app coordinateWithNormalizedOffset:GetNormalizedEdgeVector(edge)];
 
-  XCUICoordinate* startPoint =
-      [dragElement coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.5)];
-  XCUICoordinate* endPoint = [app coordinateWithNormalizedOffset:edgeCenter];
+  [start_point pressForDuration:1.5
+           thenDragToCoordinate:end_point
+                   withVelocity:XCUIGestureVelocityDefault
+            thenHoldForDuration:1.0];
 
-  [startPoint pressForDuration:1.5
-          thenDragToCoordinate:endPoint
-                  withVelocity:XCUIGestureVelocityDefault
-           thenHoldForDuration:1.0];
+  return YES;
+}
+
+BOOL LongPressCellAndDragToOffsetOf(NSString* src_accessibility_identifier,
+                                    int src_window_number,
+                                    NSString* dst_accessibility_identifier,
+                                    int dst_window_number,
+                                    CGVector dst_normalized_offset) {
+  XCUIApplication* app = [[XCUIApplication alloc] init];
+  XCUIElementQuery* src_query = GetQueryMatchingIdentifierInWindow(
+      app, src_accessibility_identifier, src_window_number,
+      XCUIElementTypeCell);
+
+  XCUIElementQuery* dst_query = GetQueryMatchingIdentifierInWindow(
+      app, dst_accessibility_identifier, dst_window_number,
+      XCUIElementTypeCell);
+
+  if (src_query.count == 0 || dst_query.count == 0)
+    return NO;
+  XCUIElement* src_element = [src_query elementBoundByIndex:0];
+  XCUIElement* dst_element = [dst_query elementBoundByIndex:0];
+
+  XCUICoordinate* start_point =
+      [src_element coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.5)];
+  XCUICoordinate* end_point =
+      [dst_element coordinateWithNormalizedOffset:dst_normalized_offset];
+
+  [start_point pressForDuration:1.5
+           thenDragToCoordinate:end_point
+                   withVelocity:XCUIGestureVelocityDefault
+            thenHoldForDuration:1.0];
+
+  return YES;
+}
+
+BOOL TapAtOffsetOf(NSString* accessibility_identifier,
+                   int window_number,
+                   CGVector normalized_offset) {
+  XCUIApplication* app = [[XCUIApplication alloc] init];
+  XCUIElementQuery* query = GetQueryMatchingIdentifierInWindow(
+      app, accessibility_identifier, window_number, XCUIElementTypeAny);
+
+  if (query.count == 0)
+    return NO;
+
+  XCUIElement* element = [query elementBoundByIndex:0];
+  XCUICoordinate* tap_point =
+      [element coordinateWithNormalizedOffset:normalized_offset];
+  [tap_point tap];
 
   return YES;
 }
diff --git a/ios/third_party/earl_grey2/BUILD.gn b/ios/third_party/earl_grey2/BUILD.gn
index ae3a604..7b69c65 100644
--- a/ios/third_party/earl_grey2/BUILD.gn
+++ b/ios/third_party/earl_grey2/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/ios/ios_sdk.gni")
 import("//build/config/ios/rules.gni")
+import("//build/config/sanitizers/sanitizers.gni")
 
 config("config") {
   include_dirs = [
@@ -26,6 +27,7 @@
     "//ios/third_party/earl_grey2/src/CommonLib/Error",
     "//ios/third_party/earl_grey2/src/CommonLib/Event",
     "//ios/third_party/earl_grey2/src/CommonLib/Exceptions",
+    "//ios/third_party/earl_grey2/src/CommonLib/Interposer",
     "//ios/third_party/earl_grey2/src/CommonLib/Matcher",
     "//ios/third_party/earl_grey2/src/CommonLib/Provider",
     "//ios/third_party/earl_grey2/src/UILib",
@@ -124,6 +126,7 @@
     "src/CommonLib/GREYStopwatch.m",
     "src/CommonLib/GREYSwizzler.h",
     "src/CommonLib/GREYSwizzler.m",
+    "src/CommonLib/Interposer/GREYDispatchQueueInterposer.h",
     "src/CommonLib/Matcher/GREYBaseMatcher.h",
     "src/CommonLib/Matcher/GREYBaseMatcher.m",
     "src/CommonLib/Matcher/GREYDescription.h",
@@ -138,6 +141,12 @@
     "src/CommonLib/Provider/GREYProvider.h",
   ]
 
+  # Build in interposer only when using supported sanitizers. Sync with
+  # src/CommonLib/GREYDefines.h.
+  if (is_asan || is_tsan || is_ubsan || is_msan) {
+    sources += [ "src/CommonLib/Interposer/GREYDispatchQueueInterposer.m" ]
+  }
+
   deps = [
     "//ios/third_party/edo",
     "//ios/third_party/fishhook",
diff --git a/ios/web/common/features.h b/ios/web/common/features.h
index 1ab3fd75..0b8e377 100644
--- a/ios/web/common/features.h
+++ b/ios/web/common/features.h
@@ -86,6 +86,9 @@
 // for the web content is used.
 bool UseWebViewNativeContextMenuSystem();
 
+// Feature flag that enable Shared Highlighting color change in iOS.
+extern const base::Feature kIOSSharedHighlightingColorChange;
+
 }  // namespace features
 }  // namespace web
 
diff --git a/ios/web/common/features.mm b/ios/web/common/features.mm
index 22b0e7a9..aa19124 100644
--- a/ios/web/common/features.mm
+++ b/ios/web/common/features.mm
@@ -85,5 +85,8 @@
   return false;
 }
 
+const base::Feature kIOSSharedHighlightingColorChange{
+    "IOSSharedHighlightingColorChange", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace web
diff --git a/ios/web/text_fragments/crw_text_fragments_handler.mm b/ios/web/text_fragments/crw_text_fragments_handler.mm
index 3d1726dd..e0204d5 100644
--- a/ios/web/text_fragments/crw_text_fragments_handler.mm
+++ b/ios/web/text_fragments/crw_text_fragments_handler.mm
@@ -2,12 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <iomanip>
+
 #import "ios/web/text_fragments/crw_text_fragments_handler.h"
 
 #import "base/json/json_writer.h"
 #import "base/strings/string_util.h"
 #import "base/strings/utf_string_conversions.h"
 #import "components/shared_highlighting/core/common/shared_highlighting_metrics.h"
+#import "components/shared_highlighting/core/common/text_fragments_constants.h"
 #import "components/shared_highlighting/core/common/text_fragments_utils.h"
 #import "ios/web/common/features.h"
 #import "ios/web/public/js_messaging/web_frame.h"
@@ -29,6 +32,14 @@
 const double kMinSelectorCount = 0.0;
 const double kMaxSelectorCount = 200.0;
 
+// Returns a rgb hexadecimal color, suitable for processing in JavaScript
+std::string ToHexStringRGB(int color) {
+  std::stringstream sstream;
+  sstream << "'" << std::setfill('0') << std::setw(6) << std::hex
+          << (color & 0x00FFFFFF) << "'";
+  return sstream.str();
+}
+
 }  // namespace
 
 @interface CRWTextFragmentsHandler () {
@@ -100,10 +111,24 @@
   std::string fragmentParam;
   base::JSONWriter::Write(parsedFragments, &fragmentParam);
 
-  std::string script = base::ReplaceStringPlaceholders(
-      "__gCrWeb.textFragments.handleTextFragments($1, $2)",
-      {fragmentParam, /* scroll = */ "true"},
-      /* offsets= */ nil);
+  std::string script;
+  if (base::FeatureList::IsEnabled(
+          web::features::kIOSSharedHighlightingColorChange)) {
+    script = base::ReplaceStringPlaceholders(
+        "__gCrWeb.textFragments.handleTextFragments($1, $2, $3, $4)",
+        {fragmentParam,
+         /* scroll = */ "true",
+         /* backgroundColor = */
+         ToHexStringRGB(shared_highlighting::kFragmentTextBackgroundColorARGB),
+         /* foregroundColor = */
+         ToHexStringRGB(shared_highlighting::kFragmentTextForegroundColorARGB)},
+        /* offsets= */ nil);
+  } else {
+    script = base::ReplaceStringPlaceholders(
+        "__gCrWeb.textFragments.handleTextFragments($1, $2, $3, $4)",
+        {fragmentParam, /* scroll = */ "true", "null", "null"},
+        /* offsets= */ nil);
+  }
 
   self.webStateImpl->ExecuteJavaScript(base::UTF8ToUTF16(script));
 }
diff --git a/ios/web/text_fragments/crw_text_fragments_handler_unittest.mm b/ios/web/text_fragments/crw_text_fragments_handler_unittest.mm
index 37dd6ec5..b24d052a 100644
--- a/ios/web/text_fragments/crw_text_fragments_handler_unittest.mm
+++ b/ios/web/text_fragments/crw_text_fragments_handler_unittest.mm
@@ -8,6 +8,7 @@
 #import "base/test/metrics/histogram_tester.h"
 #import "base/test/scoped_feature_list.h"
 #import "components/shared_highlighting/core/common/shared_highlighting_metrics.h"
+#import "components/shared_highlighting/core/common/text_fragments_constants.h"
 #import "components/ukm/test_ukm_recorder.h"
 #import "ios/web/common/features.h"
 #import "ios/web/public/navigation/referrer.h"
@@ -37,7 +38,10 @@
     "https://chromium.org#idFrag:~:text=text%201&text=text%202";
 const char kScriptForValidFragmentsURL[] =
     "__gCrWeb.textFragments.handleTextFragments([{\"textStart\":\"text "
-    "1\"},{\"textStart\":\"text 2\"}], true)";
+    "1\"},{\"textStart\":\"text 2\"}], true, null, null)";
+const char kScriptForValidFragmentsColorChangeURL[] =
+    "__gCrWeb.textFragments.handleTextFragments([{\"textStart\":\"text "
+    "1\"},{\"textStart\":\"text 2\"}], true, 'e9d2fd', '000000')";
 
 const char kSingleFragmentURL[] = "https://chromium.org#:~:text=text";
 const char kTwoFragmentsURL[] =
@@ -98,14 +102,21 @@
     return CreateHandler(/*has_opener=*/false,
                          /*has_user_gesture=*/true,
                          /*is_same_document=*/false,
-                         /*feature_enabled=*/true);
+                         /*feature_enabled=*/true,
+                         /*feature_color_change=*/false);
   }
 
   CRWTextFragmentsHandler* CreateHandler(bool has_opener,
                                          bool has_user_gesture,
                                          bool is_same_document,
-                                         bool feature_enabled) {
-    if (feature_enabled) {
+                                         bool feature_enabled,
+                                         bool feature_color_change) {
+    if (feature_enabled && feature_color_change) {
+      feature_list_.InitWithFeatures(
+          {web::features::kScrollToTextIOS,
+           web::features::kIOSSharedHighlightingColorChange},
+          {});
+    } else if (feature_enabled) {
       feature_list_.InitAndEnableFeature(web::features::kScrollToTextIOS);
     } else {
       feature_list_.InitAndDisableFeature(web::features::kScrollToTextIOS);
@@ -184,13 +195,43 @@
   EXPECT_EQ("textFragments", web_state_->last_command_prefix());
 }
 
+// Tests that the handler will execute JavaScript with the default colors
+// if the IOSSharedHighlightingColorChange flag is enabled, if highlighting
+// is allowed and fragments are present.
+TEST_F(CRWTextFragmentsHandlerTest, ExecuteJavaScriptWithColorChange) {
+  base::HistogramTester histogram_tester;
+  SetLastURL(GURL(kValidFragmentsURL));
+
+  CRWTextFragmentsHandler* handler =
+      CreateHandler(/*has_opener=*/false,
+                    /*has_user_gesture=*/true,
+                    /*is_same_document=*/false,
+                    /*feature_enabled=*/true,
+                    /*feature_color_change=*/true);
+
+  // Set up expectation.
+  base::string16 expected_javascript =
+      base::UTF8ToUTF16(kScriptForValidFragmentsColorChangeURL);
+  EXPECT_CALL(*web_state_, ExecuteJavaScript(expected_javascript)).Times(1);
+
+  [handler processTextFragmentsWithContext:&context_
+                                  referrer:GetSearchEngineReferrer()];
+
+  // Verify that a command callback was added with the right prefix.
+  EXPECT_NE(web::WebState::ScriptCommandCallback(),
+            web_state_->last_callback());
+  EXPECT_EQ("textFragments", web_state_->last_command_prefix());
+}
+
 // Tests that the handler will not execute JavaScript if the scroll to text
 // feature is disabled.
 TEST_F(CRWTextFragmentsHandlerTest, FeatureDisabledFragmentsDisallowed) {
-  CRWTextFragmentsHandler* handler = CreateHandler(/*has_opener=*/false,
-                                                   /*has_user_gesture=*/true,
-                                                   /*is_same_document=*/false,
-                                                   /*feature_enabled=*/false);
+  CRWTextFragmentsHandler* handler =
+      CreateHandler(/*has_opener=*/false,
+                    /*has_user_gesture=*/true,
+                    /*is_same_document=*/false,
+                    /*feature_enabled=*/false,
+                    /*feature_color_change=*/false);
 
   EXPECT_CALL(*web_state_, ExecuteJavaScript(_)).Times(0);
   EXPECT_CALL(*web_state_, GetLastCommittedURL()).Times(0);
@@ -206,10 +247,12 @@
 // Tests that the handler will not execute JavaScript if the WebState has an
 // opener.
 TEST_F(CRWTextFragmentsHandlerTest, HasOpenerFragmentsDisallowed) {
-  CRWTextFragmentsHandler* handler = CreateHandler(/*has_opener=*/true,
-                                                   /*has_user_gesture=*/true,
-                                                   /*is_same_document=*/false,
-                                                   /*feature_enabled=*/true);
+  CRWTextFragmentsHandler* handler =
+      CreateHandler(/*has_opener=*/true,
+                    /*has_user_gesture=*/true,
+                    /*is_same_document=*/false,
+                    /*feature_enabled=*/true,
+                    /*feature_color_change=*/false);
 
   EXPECT_CALL(*web_state_, ExecuteJavaScript(_)).Times(0);
   EXPECT_CALL(*web_state_, GetLastCommittedURL()).Times(0);
@@ -221,10 +264,12 @@
 // Tests that the handler will not execute JavaScript if the WebState has no
 // user gesture.
 TEST_F(CRWTextFragmentsHandlerTest, NoGestureFragmentsDisallowed) {
-  CRWTextFragmentsHandler* handler = CreateHandler(/*has_opener=*/false,
-                                                   /*has_user_gesture=*/false,
-                                                   /*is_same_document=*/false,
-                                                   /*feature_enabled=*/true);
+  CRWTextFragmentsHandler* handler =
+      CreateHandler(/*has_opener=*/false,
+                    /*has_user_gesture=*/false,
+                    /*is_same_document=*/false,
+                    /*feature_enabled=*/true,
+                    /*feature_color_change=*/false);
 
   EXPECT_CALL(*web_state_, ExecuteJavaScript(_)).Times(0);
   EXPECT_CALL(*web_state_, GetLastCommittedURL()).Times(0);
@@ -236,10 +281,12 @@
 // Tests that the handler will not execute JavaScript if we navigated on the
 // same document.
 TEST_F(CRWTextFragmentsHandlerTest, SameDocumentFragmentsDisallowed) {
-  CRWTextFragmentsHandler* handler = CreateHandler(/*has_opener=*/false,
-                                                   /*has_user_gesture=*/true,
-                                                   /*is_same_document=*/true,
-                                                   /*feature_enabled=*/true);
+  CRWTextFragmentsHandler* handler =
+      CreateHandler(/*has_opener=*/false,
+                    /*has_user_gesture=*/true,
+                    /*is_same_document=*/true,
+                    /*feature_enabled=*/true,
+                    /*feature_color_change=*/false);
 
   EXPECT_CALL(*web_state_, ExecuteJavaScript(_)).Times(0);
   EXPECT_CALL(*web_state_, GetLastCommittedURL()).Times(0);
@@ -253,10 +300,12 @@
 TEST_F(CRWTextFragmentsHandlerTest, NoFragmentsNoJavaScript) {
   SetLastURL(GURL("https://www.chromium.org/"));
 
-  CRWTextFragmentsHandler* handler = CreateHandler(/*has_opener=*/false,
-                                                   /*has_user_gesture=*/true,
-                                                   /*is_same_document=*/false,
-                                                   /*feature_enabled=*/true);
+  CRWTextFragmentsHandler* handler =
+      CreateHandler(/*has_opener=*/false,
+                    /*has_user_gesture=*/true,
+                    /*is_same_document=*/false,
+                    /*feature_enabled=*/true,
+                    /*feature_color_change=*/false);
 
   EXPECT_CALL(*web_state_, ExecuteJavaScript(_)).Times(0);
 
diff --git a/ios/web/web_state/js/resources/text_fragments.js b/ios/web/web_state/js/resources/text_fragments.js
index 51c1ee0..9156425 100644
--- a/ios/web/web_state/js/resources/text_fragments.js
+++ b/ios/web/web_state/js/resources/text_fragments.js
@@ -19,16 +19,30 @@
   __gCrWeb['textFragments'] = {};
 
   /**
-   * Attempts to identify and highlight the given text fragments and,
-   * optionally, scroll them into view.
+   * Attempts to identify and highlight the given text fragments and
+   * optionally, scroll them into view, and apply default colors.
+   * @param {object[]} fragments - Text fragments to process
+   * @param {bool} scroll - scroll into view
+   * @param {string} backgroundColor - default text fragments background
+   *    color in hexadecimal value enabled by IOSSharedHighlightingColorChange
+   *    feature flag
+   * @param {string} foregroundColor - default text fragments foreground
+   *    color in hexadecimal value enabled by IOSSharedHighlightingColorChange
+   *    feature flag.
    */
-  __gCrWeb.textFragments.handleTextFragments = function(fragments, scroll) {
+  __gCrWeb.textFragments.handleTextFragments =
+      function(fragments, scroll, backgroundColor, foregroundColor) {
+    const markDefaultStyle = backgroundColor && foregroundColor ? {
+      backgroundColor: `#${backgroundColor}`,
+      color: `#${foregroundColor}`
+    } : null;
+
     if (document.readyState === "complete" ||
         document.readyState === "interactive") {
-      doHandleTextFragments(fragments, scroll);
+      doHandleTextFragments(fragments, scroll, markDefaultStyle);
     } else {
-      document.addEventListener("DOMContentLoaded", () => {
-        doHandleTextFragments(fragments, scroll);
+      document.addEventListener('DOMContentLoaded', () => {
+        doHandleTextFragments(fragments, scroll, markDefaultStyle);
       });
     }
   }
@@ -36,10 +50,12 @@
   /**
    * Does the actual work for handleTextFragments.
    */
-  const doHandleTextFragments = function(fragments, scroll) {
+  const doHandleTextFragments = function(fragments, scroll, markStyle) {
     const marks = [];
     let successCount = 0;
 
+    if (markStyle) utils.setDefaultTextFragmentsStyle(markStyle);
+
     for (const fragment of fragments) {
       // Process the fragments, then filter out any that evaluate to false.
       const foundRanges = utils.processTextFragmentDirective(fragment)
diff --git a/mojo/public/cpp/bindings/struct_ptr.h b/mojo/public/cpp/bindings/struct_ptr.h
index af192d3..07acd6e0a 100644
--- a/mojo/public/cpp/bindings/struct_ptr.h
+++ b/mojo/public/cpp/bindings/struct_ptr.h
@@ -15,6 +15,7 @@
 #include "base/optional.h"
 #include "mojo/public/cpp/bindings/lib/hash_util.h"
 #include "mojo/public/cpp/bindings/type_converter.h"
+#include "third_party/perfetto/include/perfetto/tracing/traced_value.h"
 
 namespace mojo {
 namespace internal {
@@ -108,6 +109,13 @@
 
   explicit operator bool() const { return !is_null(); }
 
+  // If T is serialisable into trace, StructPtr<T> is also serialisable.
+  template <class U = S>
+  perfetto::check_traced_value_support_t<U> WriteIntoTracedValue(
+      perfetto::TracedValue context) const {
+    perfetto::WriteIntoTracedValue(std::move(context), ptr_);
+  }
+
  private:
   friend class internal::StructPtrWTFHelper<Struct>;
   void Take(StructPtr* other) {
@@ -202,6 +210,17 @@
 
   explicit operator bool() const { return !is_null(); }
 
+  // If T is serialisable into trace, StructPtr<T> is also serialisable.
+  template <class U = S>
+  perfetto::check_traced_value_support_t<U> WriteIntoTracedValue(
+      perfetto::TracedValue context) const {
+    if (is_null()) {
+      std::move(context).WritePointer(nullptr);
+      return;
+    }
+    perfetto::WriteIntoTracedValue(std::move(context), value_);
+  }
+
  private:
   friend class internal::InlinedStructPtrWTFHelper<Struct>;
   void Take(InlinedStructPtr* other) {
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl
index 3d7f935..9b1c769 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl
@@ -44,11 +44,18 @@
 {%- macro enum_stream(enum) %}
 {%-   set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %}
 std::ostream& operator<<(std::ostream& os, {{enum_name}} value) {
+  return os << {{enum_name}}ToString(value);
+}
+{%- endmacro %}
+
+{%- macro enum_to_string(enum) %}
+{%-   set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %}
+std::string {{enum_name}}ToString({{enum_name}} value) {
 {%- if enum.fields %}
   switch(value) {
 {%-   for _, values in enum.fields|groupby('numeric_value') %}
     case {{enum_name}}::{{values[0].name}}:
-      return os << "{{enum_name}}::
+      return "{{enum_name}}::
 {%-     if values|length > 1 -%}
       {{'{'}}
 {%-     endif -%}
@@ -59,10 +66,10 @@
       ";
 {%-   endfor %}
     default:
-      return os << "Unknown {{enum_name}} value: " << static_cast<int32_t>(value);
+      return base::StringPrintf("Unknown {{enum_name}} value: %i", static_cast<int32_t>(value));
   }
 {%- else %}
-  return os << "Unknown {{enum_name}} value: " << static_cast<int32_t>(value);
+  return base::StringPrintf("Unknown {{enum_name}} value: %i", static_cast<int32_t>(value));
 {%- endif %}
 }
 {%- endmacro %}
@@ -146,3 +153,30 @@
 };
 }  // namespace WTF
 {%- endmacro %}
+
+{%- macro enum_trace_format_traits_decl(enum, export_attributes) -%}
+{%-   set enum_name = enum|get_qualified_name_for_kind(
+          flatten_nested_kind=True) %}
+namespace perfetto {
+
+template <>
+struct {{export_attribute}} TraceFormatTraits<{{enum_name}}> {
+ static void WriteIntoTracedValue(perfetto::TracedValue context, {{enum_name}} value);
+};
+
+} // namespace perfetto
+{%- endmacro %}
+
+{%- macro enum_trace_format_traits(enum) -%}
+{%-   set enum_name = enum|get_qualified_name_for_kind(
+          flatten_nested_kind=True) %}
+namespace perfetto {
+
+// static
+void TraceFormatTraits<{{enum_name}}>::WriteIntoTracedValue(
+   perfetto::TracedValue context, {{enum_name}} value) {
+  return std::move(context).WriteString({{enum_name}}ToString(value));
+}
+
+} // namespace perfetto
+{%- endmacro %}endmacro 
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl
index a177ebd..9f49b0a 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl
@@ -41,34 +41,14 @@
 {%-   if method_parameters -%}
   TRACE_EVENT{{trace_event_type}}1(
     "mojom", "{{method_name}}", "{{parameter_group}}",
-    [&](){
-      auto value = std::make_unique<base::trace_event::TracedValue>();
-      base::trace_event::TracedValue* raw_value = value.get();
+    [&](perfetto::TracedValue context){
+      auto dict = std::move(context).WriteDictionary();
 {%-     for param in method_parameters %}
-{%-       if dereference_parameters -%}
-{%-         set cpp_parameter_ptr = prefix + param.name %}
-{%-         set cpp_parameter_name = "(*%s)" % cpp_parameter_ptr %}
-      if ({{cpp_parameter_ptr}}) {
-{%-         for line in param.kind|write_input_param_for_tracing(
-                                     parameter_name=param.name,
-                                     cpp_parameter_name=cpp_parameter_name,
-                                     value='raw_value') %}
-        {{line}}
-{%-         endfor %}
-      } else {
-        raw_value->SetString("{{param.name}}", "nullptr");
-      }
-{%-       else %}  {#- if dereference_parameters #}
-{%-         for line in param.kind|write_input_param_for_tracing(
-                                     parameter_name=param.name,
-                                     cpp_parameter_name=prefix+param.name,
-                                     value='raw_value') %}
-      {{line}}
-{%-         endfor %}
-{%-       endif  %}  {#- if dereference_parameters #}
+      perfetto::WriteIntoTracedValueWithFallback(
+           dict.AddItem("{{param.name}}"), {{prefix+param.name}}, 
+                        "<value of type {{param.kind|cpp_wrapper_param_type}}>");
 {%-     endfor %}
-      return value;
-    }());
+   });
 {%-   else -%}  {#- if method_parameters -#}
   TRACE_EVENT{{trace_event_type}}0("mojom", "{{method_name}}");
 {%-   endif %}  {#- if method_parameters #}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl
index 2aeb29f..25448014 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl
@@ -7,10 +7,12 @@
 #include <utility>
 
 #include "base/stl_util.h"  // for base::size()
+#include "base/strings/stringprintf.h"
 #include "mojo/public/cpp/bindings/lib/validate_params.h"
 #include "mojo/public/cpp/bindings/lib/validation_context.h"
 #include "mojo/public/cpp/bindings/lib/validation_errors.h"
 #include "mojo/public/cpp/bindings/lib/validation_util.h"
+#include "third_party/perfetto/include/perfetto/tracing/traced_value.h"
 
 #include "{{module.path}}-params-data.h"
 
@@ -23,9 +25,10 @@
 {%- endfor %}
 
 {#--- Enums #}
-{%- from "enum_macros.tmpl" import enum_stream %}
+{%- from "enum_macros.tmpl" import enum_stream, enum_to_string %}
 {%- for enum in all_enums %}
 {%-   if not enum|is_native_only_kind %}
+{{enum_to_string(enum)}}
 {{enum_stream(enum)}}
 {%-   endif %}
 {%- endfor %}
@@ -61,3 +64,12 @@
 {%- for namespace in namespaces_as_array|reverse %}
 }  // namespace {{namespace}}
 {%- endfor %}
+
+{#--- TraceFormatTraits specialisation for enums #}
+{%- from "enum_macros.tmpl" import enum_trace_format_traits %}
+{%- for enum in all_enums %}
+{%-   if not enum|is_native_only_kind %}
+{{enum_trace_format_traits(enum)}}
+{%-   endif %}
+{%- endfor %}
+
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
index b8ee1d1..246fb61 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
@@ -51,6 +51,7 @@
 #include "mojo/public/cpp/bindings/lib/serialization.h"
 #include "mojo/public/cpp/bindings/map_data_view.h"
 #include "mojo/public/cpp/bindings/string_data_view.h"
+#include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
 #include "{{module.path}}-shared-internal.h"
 {%- for import in imports %}
 #include "{{import.path}}-shared.h"
@@ -206,4 +207,13 @@
 
 {{namespace_end()}}
 
+// Declare TraceFormatTraits for enums, which should be defined in ::perfetto
+// namespace.
+{%- from "enum_macros.tmpl" import enum_trace_format_traits_decl with context %}
+{%- for enum in all_enums %}
+{%-   if not enum|is_native_only_kind %}
+{{enum_trace_format_traits_decl(enum, export_attribute)}}
+{%-   endif %}
+{%- endfor %}
+
 #endif  // {{header_guard}}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
index 1f06d01..0df0a69 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
@@ -29,6 +29,7 @@
 #include "base/task/common/task_annotator.h"
 #include "base/trace_event/trace_conversion_helper.h"
 #include "base/trace_event/traced_value.h"
+#include "third_party/perfetto/include/perfetto/tracing/traced_value.h"
 #include "mojo/public/cpp/bindings/lib/generated_code_util.h"
 #include "mojo/public/cpp/bindings/lib/message_internal.h"
 #include "mojo/public/cpp/bindings/lib/serialization_util.h"
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
index e8d3d6a..65326e3 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
@@ -134,54 +134,8 @@
   {{type}} {{name}};
 {%- endfor %}
 
-  // Write this structure into |value|. The members are represented as a
-  // dictionary |member name|: |member value|. This method does not deal with
-  // the dictionary name. The caller is responsible for not calling
-  // |AsValueInto| when |value| expects array items (see Array/Dictionary
-  // methods of |base::trace_event::TracedValue|).
-  //
-  // |value| The TracedValue to be written into.
-  //
-  // Example uses:
-  //
-  //   // |my_var1, my_var2| are variables with defined |AsValueInto| method.
-  //   auto value = std::make_unique<base::trace_event::TracedValue>();
-  //   value->BeginDictionary("my_var1");
-  //   my_var1.AsValueInto(value.get());
-  //   value->EndDictionary();
-  //   value->BeginDictionary("my_var2");
-  //   my_var2.AsValueInto(value.get());
-  //   value->EndDictionary();
-  //
-  //   // |my_var| is a variable with defined |AsValueInto| method.
-  //   auto value = std::make_unique<base::trace_event::TracedValue>();
-  //   my_var.AsValueInto(value.get());
-  //   TRACE_EVENT1("test", "test", "my_var", std::move(value));
-  //
-  // Calling |AsValueInto| on two objects without opening and closing a
-  // dictionary for each object is theoretically possible, but strongly
-  // discouraged due to potential name collisions:
-  //
-  //   struct A {
-  //     int foo;
-  //   };
-  //   struct B {
-  //     int bar;
-  //   };
-  //   struct C {
-  //     int foo;
-  //     int bar;
-  //   };
-  //   auto value1 = std::make_unique<base::trace_event::TracedValue>();
-  //   auto value2 = std::make_unique<base::trace_event::TracedValue>();
-  //   A a;
-  //   B b;
-  //   C c;
-  //   // value1 and value2 will be filled with the same information:
-  //   a.AsValueInto(value1);
-  //   b.AsValueInto(value1);
-  //   c.AsValueInto(value2);
-  void AsValueInto(base::trace_event::TracedValue* value) const;
+  // Serialise this struct into a trace.
+  void WriteIntoTracedValue(perfetto::TracedValue context) const;
 
  private:
   static bool Validate(const void* data,
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
index 89ce35aa..b4ed7c63 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
@@ -32,14 +32,13 @@
 }
 {%- endif %}
 
-void {{struct.name}}::AsValueInto(base::trace_event::TracedValue* value) const {
+void {{struct.name}}::WriteIntoTracedValue(perfetto::TracedValue context) const {
+  auto dict = std::move(context).WriteDictionary();
 {%-  for field in struct.fields %}
-{%-    for line in field.kind|write_input_param_for_tracing(
-                                parameter_name=field.name,
-                                cpp_parameter_name='this->'+field.name,
-                                value='value') %}
-  {{line}}
-{%-    endfor -%}
+  perfetto::WriteIntoTracedValueWithFallback(
+    dict.AddItem(
+      "{{field.name}}"), {{'this->'+field.name}}, 
+      "<value of type {{field.kind|cpp_wrapper_param_type}}>");
 {%-  endfor %}
 }
 
diff --git a/mojo/public/tools/bindings/generators/cpp_tracing_support.py b/mojo/public/tools/bindings/generators/cpp_tracing_support.py
deleted file mode 100644
index fc4d8a0..0000000
--- a/mojo/public/tools/bindings/generators/cpp_tracing_support.py
+++ /dev/null
@@ -1,408 +0,0 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""Generates C++ source code to trace mojo method parameters."""
-
-from generators.cpp_util import IsNativeOnlyKind
-import mojom.generate.module as mojom
-from abc import ABCMeta
-from abc import abstractmethod
-
-
-class _OutputContext(object):
-  __metaclass__ = ABCMeta
-  """Represents the context in which |self.value| should be used.
-
-  This is a base class for _ArrayItem, _DictionaryItemWithLiteralKey, and
-  _DictionaryItemWithCopiedKey. The distinction between _ArrayItem and
-  _DictionaryItemWithLiteralKey/_DictionaryItemWithCopiedKey is that
-  _ArrayItem has no name. The distinction between
-  _DictionaryItemWithLiteralKey and _DictionaryItemWithCopiedKey is whether
-  the name is expected to be long lived or can be temporary.
-  """
-
-  def __init__(self, value):
-    self.value = value
-
-  @abstractmethod
-  def AddSingleValue(self, trace_type, parameter_value):
-    pass
-
-  @abstractmethod
-  def BeginContainer(self, container_type):
-    pass
-
-  def EndContainer(self, container_type):
-    """Return a line of C++ code to close a container on self.value.
-
-    Args:
-      container_type: {string} Either 'Dictionary' or 'Array'.
-
-    Returns:
-      A single line of C++ which closes a container on self.value.
-    """
-    return '%s->End%s();' % (self.value, container_type)
-
-  def AsValueInto(self, cpp_expression):
-    """Return a line of C++ code to trace a variable to self.value.
-    Most probably the user of this method wants to yield
-    |self.BeginContainer('Dictionary')| immediately before and
-    |self.EndContainer('Dictionary')| immediately after a call to
-    |AsValueInto| (see documentation of |cpp_expression::AsValueInto|).
-
-    Args:
-      cpp_expression: {string} The C++ expression on which |->AsValueInto|
-        will be called.
-
-    Returns:
-      A single line of C++ which calls |AsValueInto| on |cpp_expression|
-      with self.value as the parameter.
-    """
-    return '%s->AsValueInto(%s);' % (cpp_expression, self.value)
-
-  def TraceContainer(self, container_type, iterator_name, container_name,
-                     loop_body):
-    """Generate the C++ for-loop to trace the container |container_name|.
-
-    Args:
-      container_type: {string} Either 'Array' or 'Dictionary'.
-      iterator_name: {string} The iterator variable name to be used.
-      container_name: {string} The name of the variable holding the container.
-      loop_body: {iterable} Lines of C++ code that trace individual elements.
-
-    Yields:
-      Lines of C++ for-loop to trace the container.
-    """
-    yield self.BeginContainer(container_type)
-    yield 'for (const auto& %s : %s) {' % (iterator_name, container_name)
-    for line in loop_body:
-      yield '  ' + line
-    yield '}'
-    yield self.EndContainer(container_type)
-
-
-class _ArrayItem(_OutputContext):
-  """Represents a |TracedValue| which expects to receive an array item (of no
-  name). This means that |AddSingleValue| will return a call on self.value
-  which is an Append and |BeginContainer| will start a container with no
-  name.
-
-  Attributes:
-    value: {string} The name of the C++ variable of type
-      |base::trace_event::TracedValue*| this object represents.
-  """
-
-  def __init__(self, value):
-    super(_ArrayItem, self).__init__(value)
-
-  def AddSingleValue(self, trace_type, parameter_value):
-    """Return a line of C++ code that will append a single value to
-    |self.value|.
-
-    Args:
-      trace_type: {string} The type of the appended value. Can be one of:
-        'Integer', 'Double', 'Boolean', 'String'.
-      parameter_value: {string} The C++ expression to be passed as the
-        appended value.
-
-    Returns:
-      A single line of C++ which appends |parameter_value| to self.value.
-    """
-    return '%s->Append%s(%s);' % (self.value, trace_type, parameter_value)
-
-  def BeginContainer(self, container_type):
-    """Return a line of C++ code to open a container on self.value.
-
-    Args:
-      container_type: {string} Either 'Dictionary' or 'Array'.
-
-    Returns:
-      A single line of C++ which starts a container on self.value.
-    """
-    return '%s->Begin%s();' % (self.value, container_type)
-
-
-class _DictionaryItemWithLiteralKey(_OutputContext):
-  """Represents a |TracedValue| which expects to receive a dictionary item
-  (with a long lived name). This means that |AddSingleValue| will return a
-  call on self.value which is a Set and |BeginContainer| will start a
-  container with self.name used in a string literal.
-
-  |base::trace_event::TracedValue| has two sets of methods -- ones with copied
-  name and ones with long lived name. This class corresponds to using the long
-  lived name.
-
-  _DictionaryItemWithLiteralKey generates calls on
-  |base::trace_event::TracedValue| which do not copy the name (assume that
-  the used name is long lived. Thus self.name is used inside a "quoted" long
-  lived string.
-
-  Attributes:
-    value: {string} The name of the C++ variable of type
-      |base::trace_event::TracedValue*| this object represents.
-    name: {string} The name of the mojo variable that is currently being
-      traced. Used inside double-quotes in method calls.
-  """
-
-  def __init__(self, name, value):
-    super(_DictionaryItemWithLiteralKey, self).__init__(value)
-    self.name = name
-
-  def AddSingleValue(self, trace_type, parameter_value):
-    """Return a line of C++ code that will set a single value to |self.value|.
-    Uses |self.name| inside a "quoted" string.
-
-    Args:
-      trace_type: {string} The type of the set value. Can be one of:
-        'Integer', 'Double', 'Boolean', 'String', 'Value'.
-      parameter_value: {string} The C++ expression to be passed as the
-        set value.
-
-    Returns:
-      A single line of C++ that sets |parameter_value| of name |"self.name"|
-      on self.value.
-    """
-    return '%s->Set%s("%s", %s);' % (self.value, trace_type, self.name,
-                                     parameter_value)
-
-  def BeginContainer(self, container_type):
-    return '%s->Begin%s("%s");' % (self.value, container_type, self.name)
-
-
-class _DictionaryItemWithCopiedKey(_OutputContext):
-  """Represents a |TracedValue| which expects to receive a dictionary item
-  (with a name which is a temporary string). This means that
-  |AddSingleValue| will return a call on self.value which is an
-  SetXWithCopiedName and |BeginContainer| will start a container with
-  self.name used as a temporary string.
-
-  |base::trace_event::TracedValue| has two sets of methods -- ones with copied
-  name and ones with long lived name. This class corresponds to using the
-  copied name.
-
-  _DictionaryItemWithCopiedKey generates calls on
-  |base::trace_event::TracedValue| which do copy the name (assume that
-  self.name is a C++ expression evaluating to a temporary string).
-
-  Attributes:
-    value: {string} The name of the C++ variable of type
-      |base::trace_event::TracedValue*| this object represents.
-    name: {string} The name of the mojo variable that is currently being
-      traced. Used directly (not in double quotes) in method calls.
-  """
-
-  def __init__(self, name, value):
-    super(_DictionaryItemWithCopiedKey, self).__init__(value)
-    self.name = name
-
-  def AddSingleValue(self, trace_type, parameter_value):
-    """Return a line of C++ code that will set a single value to |self.value|.
-    Uses |self.name| directly as a parameter that is expected to be copied
-    (can be a temporary string).
-
-    Args:
-      trace_type: {string} The type of the set value. Can be one of:
-        'Integer', 'Double', 'Boolean', 'String', 'Value'.
-      parameter_value: {string} The C++ expression to be passed as the
-        set value.
-
-    Returns:
-      A single line of C++ that sets |parameter_value| of name |self.name|
-      on self.value (with copied name).
-    """
-    return '%s->Set%sWithCopiedName(%s, %s);' % (self.value, trace_type,
-                                                 self.name, parameter_value)
-
-  def BeginContainer(self, container_type):
-    return '%s->Begin%sWithCopiedName(%s);' % (self.value, container_type,
-                                               self.name)
-
-
-def _WriteInputParamForTracingImpl(generator, kind, cpp_parameter_name,
-                                   output_context):
-  """Generates lines of C++ to log a parameter into TracedValue
-  |output_context.value|. Use |output_context.name| if |output_context| is of
-  inhereted type from _OutputContext.
-
-  Args:
-    kind: {Kind} The kind of the parameter (corresponds to its C++ type).
-    cpp_parameter_name: {string} The actual C++ variable name corresponding to
-      the mojom parameter |parameter_name|. Can be a valid C++ expression
-      (e.g., dereferenced variable |"(*var)"|).
-    output_context: {_OutputContext} Represents the TracedValue* variable to be
-      written into. Possibly also holds the mojo parameter name corresponding to
-      |cpp_parameter_name|.
-
-  Yields:
-    {string} C++ lines of code that trace a |cpp_parameter_name| into
-      |output_context.value|.
-  """
-
-  def _WrapIfNullable(inner_lines):
-    """Check if kind is nullable if so yield code to check whether it has
-    value.
-
-    Args:
-      inner_lines: {function} Function taking single argument and returning
-        iterable. If kind is nullable, yield from this method with
-        |cpp_parameter_name+'.value()'| otherwise yield with the parameter
-        |cpp_parameter_name|.
-
-    Args from the surrounding method:
-      kind
-      cpp_parameter_name
-      output_context.AddSingleValue
-    """
-    if mojom.IsNullableKind(kind):
-      yield 'if (%s.has_value()) {' % cpp_parameter_name
-      for line in inner_lines(cpp_parameter_name + '.value()'):
-        yield '  ' + line
-      yield '} else {'
-      yield '  ' + output_context.AddSingleValue('String', '"base::nullopt"')
-      yield '}'
-    else:
-      # |yield from| is introduced in Python3.3.
-      for line in inner_lines(cpp_parameter_name):
-        yield line
-
-  if mojom.IsEnumKind(kind):
-    if generator._IsTypemappedKind(kind) or IsNativeOnlyKind(kind):
-      yield output_context.AddSingleValue(
-          'Integer', 'static_cast<int>(%s)' % cpp_parameter_name)
-    else:
-      yield output_context.AddSingleValue(
-          'String', 'base::trace_event::ValueToString(%s)' % cpp_parameter_name)
-    return
-  if mojom.IsStringKind(kind):
-    if generator.for_blink:
-      # WTF::String is nullable on its own.
-      yield output_context.AddSingleValue('String',
-                                          '%s.Utf8()' % cpp_parameter_name)
-      return
-    # The type might be base::Optional<std::string> or std::string.
-    for line in _WrapIfNullable(lambda cpp_parameter_name: [
-        output_context.AddSingleValue('String', cpp_parameter_name)
-    ]):
-      yield line
-    return
-  if kind == mojom.BOOL:
-    yield output_context.AddSingleValue('Boolean', cpp_parameter_name)
-    return
-  # TODO(crbug.com/1103623): Make TracedValue support int64_t, then move to
-  # mojom.IsIntegralKind.
-  if kind in [mojom.INT8, mojom.UINT8, mojom.INT16, mojom.UINT16, mojom.INT32]:
-    # Parameter is representable as 32bit int.
-    yield output_context.AddSingleValue('Integer', cpp_parameter_name)
-    return
-  if kind in [mojom.UINT32, mojom.INT64, mojom.UINT64]:
-    yield output_context.AddSingleValue(
-        'String', 'base::NumberToString(%s)' % cpp_parameter_name)
-    return
-  if mojom.IsFloatKind(kind) or mojom.IsDoubleKind(kind):
-    yield output_context.AddSingleValue('Double', cpp_parameter_name)
-    return
-  if (mojom.IsStructKind(kind) and not generator._IsTypemappedKind(kind)
-      and not IsNativeOnlyKind(kind)):
-    yield 'if (%s.is_null()) {' % cpp_parameter_name
-    yield '  ' + output_context.AddSingleValue('String', '"nullptr"')
-    yield '} else {'
-    yield '  ' + output_context.BeginContainer('Dictionary')
-    yield '  ' + output_context.AsValueInto(cpp_parameter_name)
-    yield '  ' + output_context.EndContainer('Dictionary')
-    yield '}'
-    return
-
-  if mojom.IsArrayKind(kind):
-    iterator_name = 'item'
-    loop_body = _WriteInputParamForTracingImpl(generator=generator,
-                                               kind=kind.kind,
-                                               cpp_parameter_name=iterator_name,
-                                               output_context=_ArrayItem(
-                                                   output_context.value))
-    loop_generator = lambda cpp_parameter_name: output_context.TraceContainer(
-        container_type='Array',
-        iterator_name=iterator_name,
-        container_name=cpp_parameter_name,
-        loop_body=loop_body)
-    # Array might be a nullable kind.
-    for line in _WrapIfNullable(loop_generator):
-      yield line
-    return
-
-  def _TraceEventToString(cpp_parameter_name=cpp_parameter_name, kind=kind):
-    return 'base::trace_event::ValueToString(%s, "<value of type %s>")' % (
-        cpp_parameter_name, generator._GetCppWrapperParamType(kind))
-
-  if mojom.IsMapKind(kind):
-    iterator_name = 'item'
-    if generator.for_blink:
-      # WTF::HashMap<,>
-      key_access = '.key'
-      value_access = '.value'
-    else:
-      # base::flat_map<,>
-      key_access = '.first'
-      value_access = '.second'
-    loop_body = _WriteInputParamForTracingImpl(
-        generator=generator,
-        kind=kind.value_kind,
-        cpp_parameter_name=iterator_name + value_access,
-        output_context=_DictionaryItemWithCopiedKey(
-            value=output_context.value,
-            name=_TraceEventToString(cpp_parameter_name=iterator_name +
-                                     key_access,
-                                     kind=kind.key_kind)))
-    loop_generator = lambda cpp_parameter_name: output_context.TraceContainer(
-        container_type="Dictionary",
-        iterator_name=iterator_name,
-        container_name=cpp_parameter_name,
-        loop_body=loop_body)
-    # Dictionary might be a nullable kind.
-    for line in _WrapIfNullable(loop_generator):
-      yield line
-    return
-  if (mojom.IsInterfaceRequestKind(kind)
-      or mojom.IsAssociatedInterfaceRequestKind(kind)):
-    yield output_context.AddSingleValue('Boolean',
-                                        cpp_parameter_name + '.is_pending()')
-    return
-  if (mojom.IsAnyHandleOrInterfaceKind(kind)
-      and not mojom.IsInterfaceKind(kind)):
-    yield output_context.AddSingleValue('Boolean',
-                                        cpp_parameter_name + '.is_valid()')
-    return
-  """ The case |mojom.IsInterfaceKind(kind)| is not covered.
-  |mojom.IsInterfaceKind(kind) == True| for the following types:
-  |mojo::InterfacePtrInfo|, |mojo::InterfacePtr|.
-    There is |mojo::InterfacePtrInfo::is_valid|,
-      but not |mojo::InterfacePtrInfo::is_bound|.
-    There is |mojo::InterfacePtr::is_bound|,
-      but not |mojo::InterfacePtr::is_valid|.
-
-  Both |mojo::InterfacePtrInfo| and |mojo::InterfacePtr| are deprecated.
-  """
-  yield output_context.AddSingleValue('String', _TraceEventToString())
-
-
-def WriteInputParamForTracing(generator, kind, parameter_name,
-                              cpp_parameter_name, value):
-  """Generates lines of C++ to log parameter |parameter_name| into TracedValue
-  |value|.
-
-  Args:
-    kind: {Kind} The kind of the parameter (corresponds to its C++ type).
-    cpp_parameter_name: {string} The actual C++ variable name corresponding to
-      the mojom parameter |parameter_name|. Can be a valid C++ expression
-      (e.g., dereferenced variable |"(*var)"|).
-    value: {string} The C++ |TracedValue*| variable name to be logged into.
-
-  Yields:
-    {string} C++ lines of code that trace |parameter_name| into |value|.
-  """
-  for line in _WriteInputParamForTracingImpl(
-      generator=generator,
-      kind=kind,
-      cpp_parameter_name=cpp_parameter_name,
-      output_context=_DictionaryItemWithLiteralKey(name=parameter_name,
-                                                   value=value)):
-    yield line
diff --git a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
index fc7728a..744e1a3 100644
--- a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
@@ -8,7 +8,6 @@
 import mojom.generate.module as mojom
 import mojom.generate.pack as pack
 from generators.cpp_util import IsNativeOnlyKind
-from generators.cpp_tracing_support import WriteInputParamForTracing
 from mojom.generate.template_expander import UseJinja, UseJinjaForImportedTemplate
 
 
@@ -367,7 +366,6 @@
         self._GetUnionTraitGetterReturnType,
         "cpp_wrapper_call_type": self._GetCppWrapperCallType,
         "cpp_wrapper_param_type": self._GetCppWrapperParamType,
-        "write_input_param_for_tracing": self._WriteInputParamForTracing,
         "cpp_wrapper_param_type_new": self._GetCppWrapperParamTypeNew,
         "cpp_wrapper_type": self._GetCppWrapperType,
         "cpp_enum_without_namespace": GetEnumNameWithoutNamespace,
@@ -614,28 +612,6 @@
         GetCppPodType(constant.kind), constant.name,
         self._ConstantValue(constant))
 
-  def _WriteInputParamForTracing(self, kind, parameter_name, cpp_parameter_name,
-                                 value):
-    """Generates lines of C++ to log parameter |parameter_name| into TracedValue
-    |value|.
-
-    Args:
-      kind: {Kind} The kind of the parameter (corresponds to its C++ type).
-      cpp_parameter_name: {string} The actual C++ variable name corresponding to
-        the mojom parameter |parameter_name|. Can be a valid C++ expression
-        (e.g., dereferenced variable |"(*var)"|).
-      value: {string} The C++ |TracedValue*| variable name to be logged into.
-
-    Yields:
-      {string} C++ lines of code that trace |parameter_name| into |value|.
-    """
-    for line in WriteInputParamForTracing(generator=self,
-                                          kind=kind,
-                                          parameter_name=parameter_name,
-                                          cpp_parameter_name=cpp_parameter_name,
-                                          value=value):
-      yield line
-
   def _GetCppWrapperType(self,
                          kind,
                          add_same_module_namespaces=False,
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
index 5a1a418..fafce06 100644
--- a/mojo/public/tools/bindings/mojom.gni
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -98,7 +98,6 @@
     mojom_parser_sources + [
       "$mojom_generator_root/generators/cpp_util.py",
       "$mojom_generator_root/generators/mojom_cpp_generator.py",
-      "$mojom_generator_root/generators/cpp_tracing_support.py",
       "$mojom_generator_root/generators/mojom_java_generator.py",
       "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
       "$mojom_generator_root/generators/mojom_js_generator.py",
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 1a624bf..d9430bc 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -637,7 +637,7 @@
   response_.auth_challenge = proxy_response.auth_challenge;
   response_.did_use_http_auth = proxy_response.did_use_http_auth;
 
-  if (response_.headers.get() && !ContentEncodingsValid()) {
+  if (!ContentEncodingsValid()) {
     DoCallback(ERR_CONTENT_DECODING_FAILED);
     return;
   }
@@ -1111,15 +1111,13 @@
 
   DCHECK(response_.headers.get());
 
-  if (response_.headers.get() && !ContentEncodingsValid())
+  if (!ContentEncodingsValid())
     return ERR_CONTENT_DECODING_FAILED;
 
   // On a 408 response from the server ("Request Timeout") on a stale socket,
   // retry the request for HTTP/1.1 but not HTTP/2 or QUIC because those
   // multiplex requests and have no need for 408.
-  // Headers can be NULL because of http://crbug.com/384554.
-  if (response_.headers.get() &&
-      response_.headers->response_code() == HTTP_REQUEST_TIMEOUT &&
+  if (response_.headers->response_code() == HTTP_REQUEST_TIMEOUT &&
       HttpResponseInfo::ConnectionInfoToCoarse(response_.connection_info) ==
           HttpResponseInfo::CONNECTION_INFO_COARSE_HTTP1 &&
       stream_->IsConnectionReused()) {
@@ -1149,7 +1147,7 @@
       return ERR_METHOD_NOT_SUPPORTED;
   }
 
-  if (can_send_early_data_ && response_.headers.get() &&
+  if (can_send_early_data_ &&
       response_.headers->response_code() == HTTP_TOO_EARLY) {
     return HandleIOError(ERR_EARLY_DATA_REJECTED);
   }
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index bfd9041..c0f8888 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -5871,8 +5871,7 @@
 
 // Check that we're correctly logging 0-rtt success when the handshake
 // concludes during a Read.
-// Disabled due to flake, see crbug.com/1057921 .
-TEST_F(SSLClientSocketZeroRTTTest, DISABLED_EarlyDataReasonReadServerHello) {
+TEST_F(SSLClientSocketZeroRTTTest, EarlyDataReasonReadServerHello) {
   const char kReasonHistogram[] = "Net.SSLHandshakeEarlyDataReason";
   ASSERT_TRUE(StartServer());
   ASSERT_TRUE(RunInitialConnection());
@@ -5889,6 +5888,10 @@
   EXPECT_GT(size, 0);
   EXPECT_EQ('1', buf->data()[size - 1]);
 
+  // 0-RTT metrics are logged on a PostTask, so if Read returns synchronously,
+  // it is possible the metrics haven't been picked up yet.
+  base::RunLoop().RunUntilIdle();
+
   SSLInfo ssl_info;
   ASSERT_TRUE(GetSSLInfo(&ssl_info));
   EXPECT_EQ(SSLInfo::HANDSHAKE_RESUME, ssl_info.handshake_type);
diff --git a/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.cc b/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.cc
index a6468df..70f04a85 100644
--- a/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.cc
+++ b/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.cc
@@ -13,18 +13,142 @@
 namespace mojo {
 
 // static
+viz::mojom::ResourceFormat
+EnumTraits<viz::mojom::ResourceFormat, viz::ResourceFormat>::ToMojom(
+    viz::ResourceFormat format) {
+  switch (format) {
+    case viz::ResourceFormat::RGBA_8888:
+      return viz::mojom::ResourceFormat::RGBA_8888;
+    case viz::ResourceFormat::RGBA_4444:
+      return viz::mojom::ResourceFormat::RGBA_4444;
+    case viz::ResourceFormat::BGRA_8888:
+      return viz::mojom::ResourceFormat::BGRA_8888;
+    case viz::ResourceFormat::ALPHA_8:
+      return viz::mojom::ResourceFormat::ALPHA_8;
+    case viz::ResourceFormat::LUMINANCE_8:
+      return viz::mojom::ResourceFormat::LUMINANCE_8;
+    case viz::ResourceFormat::RGB_565:
+      return viz::mojom::ResourceFormat::RGB_565;
+    case viz::ResourceFormat::BGR_565:
+      return viz::mojom::ResourceFormat::BGR_565;
+    case viz::ResourceFormat::ETC1:
+      return viz::mojom::ResourceFormat::ETC1;
+    case viz::ResourceFormat::RED_8:
+      return viz::mojom::ResourceFormat::RED_8;
+    case viz::ResourceFormat::RG_88:
+      return viz::mojom::ResourceFormat::RG_88;
+    case viz::ResourceFormat::LUMINANCE_F16:
+      return viz::mojom::ResourceFormat::LUMINANCE_F16;
+    case viz::ResourceFormat::RGBA_F16:
+      return viz::mojom::ResourceFormat::RGBA_F16;
+    case viz::ResourceFormat::R16_EXT:
+      return viz::mojom::ResourceFormat::R16_EXT;
+    case viz::ResourceFormat::RG16_EXT:
+      return viz::mojom::ResourceFormat::RG16_EXT;
+    case viz::ResourceFormat::RGBX_8888:
+      return viz::mojom::ResourceFormat::RGBX_8888;
+    case viz::ResourceFormat::BGRX_8888:
+      return viz::mojom::ResourceFormat::BGRX_8888;
+    case viz::ResourceFormat::RGBA_1010102:
+      return viz::mojom::ResourceFormat::RGBX_1010102;
+    case viz::ResourceFormat::BGRA_1010102:
+      return viz::mojom::ResourceFormat::BGRX_1010102;
+    case viz::ResourceFormat::YVU_420:
+      return viz::mojom::ResourceFormat::YVU_420;
+    case viz::ResourceFormat::YUV_420_BIPLANAR:
+      return viz::mojom::ResourceFormat::YUV_420_BIPLANAR;
+    case viz::ResourceFormat::P010:
+      return viz::mojom::ResourceFormat::P010;
+  }
+  NOTREACHED();
+  return viz::mojom::ResourceFormat::RGBA_8888;
+}
+
+// static
+bool EnumTraits<viz::mojom::ResourceFormat, viz::ResourceFormat>::FromMojom(
+    viz::mojom::ResourceFormat format,
+    viz::ResourceFormat* out) {
+  switch (format) {
+    case viz::mojom::ResourceFormat::RGBA_8888:
+      *out = viz::ResourceFormat::RGBA_8888;
+      return true;
+    case viz::mojom::ResourceFormat::RGBA_4444:
+      *out = viz::ResourceFormat::RGBA_4444;
+      return true;
+    case viz::mojom::ResourceFormat::BGRA_8888:
+      *out = viz::ResourceFormat::BGRA_8888;
+      return true;
+    case viz::mojom::ResourceFormat::ALPHA_8:
+      *out = viz::ResourceFormat::ALPHA_8;
+      return true;
+    case viz::mojom::ResourceFormat::LUMINANCE_8:
+      *out = viz::ResourceFormat::LUMINANCE_8;
+      return true;
+    case viz::mojom::ResourceFormat::RGB_565:
+      *out = viz::ResourceFormat::RGB_565;
+      return true;
+    case viz::mojom::ResourceFormat::BGR_565:
+      *out = viz::ResourceFormat::BGR_565;
+      return true;
+    case viz::mojom::ResourceFormat::ETC1:
+      *out = viz::ResourceFormat::ETC1;
+      return true;
+    case viz::mojom::ResourceFormat::RED_8:
+      *out = viz::ResourceFormat::RED_8;
+      return true;
+    case viz::mojom::ResourceFormat::RG_88:
+      *out = viz::ResourceFormat::RG_88;
+      return true;
+    case viz::mojom::ResourceFormat::LUMINANCE_F16:
+      *out = viz::ResourceFormat::LUMINANCE_F16;
+      return true;
+    case viz::mojom::ResourceFormat::RGBA_F16:
+      *out = viz::ResourceFormat::RGBA_F16;
+      return true;
+    case viz::mojom::ResourceFormat::R16_EXT:
+      *out = viz::ResourceFormat::R16_EXT;
+      return true;
+    case viz::mojom::ResourceFormat::RG16_EXT:
+      *out = viz::ResourceFormat::RG16_EXT;
+      return true;
+    case viz::mojom::ResourceFormat::RGBX_8888:
+      *out = viz::ResourceFormat::RGBX_8888;
+      return true;
+    case viz::mojom::ResourceFormat::BGRX_8888:
+      *out = viz::ResourceFormat::BGRX_8888;
+      return true;
+    case viz::mojom::ResourceFormat::RGBX_1010102:
+      *out = viz::ResourceFormat::RGBA_1010102;
+      return true;
+    case viz::mojom::ResourceFormat::BGRX_1010102:
+      *out = viz::ResourceFormat::BGRA_1010102;
+      return true;
+    case viz::mojom::ResourceFormat::YVU_420:
+      *out = viz::ResourceFormat::YVU_420;
+      return true;
+    case viz::mojom::ResourceFormat::YUV_420_BIPLANAR:
+      *out = viz::ResourceFormat::YUV_420_BIPLANAR;
+      return true;
+    case viz::mojom::ResourceFormat::P010:
+      *out = viz::ResourceFormat::P010;
+      return true;
+  }
+
+  return false;
+}
+
+// static
 bool StructTraits<viz::mojom::TransferableResourceDataView,
                   viz::TransferableResource>::
     Read(viz::mojom::TransferableResourceDataView data,
          viz::TransferableResource* out) {
-  if (!data.ReadSize(&out->size) ||
+  if (!data.ReadSize(&out->size) || !data.ReadFormat(&out->format) ||
       !data.ReadMailboxHolder(&out->mailbox_holder) ||
       !data.ReadColorSpace(&out->color_space) ||
       !data.ReadYcbcrInfo(&out->ycbcr_info)) {
     return false;
   }
   out->id = data.id();
-  out->format = static_cast<viz::ResourceFormat>(data.format());
   out->filter = data.filter();
   out->read_lock_fences_enabled = data.read_lock_fences_enabled();
   out->is_software = data.is_software();
diff --git a/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.h b/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.h
index e6a97174..8dc601fb 100644
--- a/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.h
+++ b/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.h
@@ -15,15 +15,22 @@
 namespace mojo {
 
 template <>
+struct EnumTraits<viz::mojom::ResourceFormat, viz::ResourceFormat> {
+  static viz::mojom::ResourceFormat ToMojom(viz::ResourceFormat type);
+
+  static bool FromMojom(viz::mojom::ResourceFormat input,
+                        viz::ResourceFormat* out);
+};
+
+template <>
 struct StructTraits<viz::mojom::TransferableResourceDataView,
                     viz::TransferableResource> {
   static uint32_t id(const viz::TransferableResource& resource) {
     return resource.id;
   }
 
-  static viz::mojom::ResourceFormat format(
-      const viz::TransferableResource& resource) {
-    return static_cast<viz::mojom::ResourceFormat>(resource.format);
+  static viz::ResourceFormat format(const viz::TransferableResource& resource) {
+    return resource.format;
   }
 
   static uint32_t filter(const viz::TransferableResource& resource) {
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index 8206d16e..0059007 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -228,8 +228,6 @@
 
 #define SK_SUPPORT_LEGACY_CONVEXITY_DIRECTION_CHANGE
 
-#define SK_USE_LEGACY_VK_ALLOCATOR_USAGE_NAMES
-
 // Remove once we have imported the functionality we need into chrome
 // SkDrawLooper is unsupported in skia
 #define SK_SUPPORT_LEGACY_DRAWLOOPER
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 9a446fe..bc030d8 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -199,11 +199,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.176"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.177"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.176",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.177",
         "resultdb": {
           "enable": true
         },
@@ -213,7 +213,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.176"
+              "revision": "version:88.0.4324.177"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -278,11 +278,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.48"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.49"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.48",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.49",
         "resultdb": {
           "enable": true
         },
@@ -292,7 +292,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.48"
+              "revision": "version:89.0.4389.49"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -436,11 +436,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.176"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.177"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.176",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.177",
         "resultdb": {
           "enable": true
         },
@@ -450,7 +450,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.176"
+              "revision": "version:88.0.4324.177"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -515,11 +515,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.48"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.49"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.48",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.49",
         "resultdb": {
           "enable": true
         },
@@ -529,7 +529,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.48"
+              "revision": "version:89.0.4389.49"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -677,11 +677,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.176"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.177"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.176",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.177",
         "resultdb": {
           "enable": true
         },
@@ -691,7 +691,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.176"
+              "revision": "version:88.0.4324.177"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -756,11 +756,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.48"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.49"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.48",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.49",
         "resultdb": {
           "enable": true
         },
@@ -770,7 +770,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.48"
+              "revision": "version:89.0.4389.49"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -914,11 +914,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.176"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.177"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.176",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.177",
         "resultdb": {
           "enable": true
         },
@@ -928,7 +928,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.176"
+              "revision": "version:88.0.4324.177"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -993,11 +993,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.48"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.49"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.48",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.49",
         "resultdb": {
           "enable": true
         },
@@ -1007,7 +1007,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.48"
+              "revision": "version:89.0.4389.49"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1218,11 +1218,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.176"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.177"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.176",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.177",
         "resultdb": {
           "enable": true
         },
@@ -1232,7 +1232,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.176"
+              "revision": "version:88.0.4324.177"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1297,11 +1297,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.48"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.49"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.48",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.49",
         "resultdb": {
           "enable": true
         },
@@ -1311,7 +1311,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.48"
+              "revision": "version:89.0.4389.49"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1455,11 +1455,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.176"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.177"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.176",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.177",
         "resultdb": {
           "enable": true
         },
@@ -1469,7 +1469,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.176"
+              "revision": "version:88.0.4324.177"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1534,11 +1534,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.48"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.49"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.48",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.49",
         "resultdb": {
           "enable": true
         },
@@ -1548,7 +1548,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.48"
+              "revision": "version:89.0.4389.49"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1759,11 +1759,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.176"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.177"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.176",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 88.0.4324.177",
         "resultdb": {
           "enable": true
         },
@@ -1773,7 +1773,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.176"
+              "revision": "version:88.0.4324.177"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1838,11 +1838,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.48"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.49"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.48",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.49",
         "resultdb": {
           "enable": true
         },
@@ -1852,7 +1852,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.48"
+              "revision": "version:89.0.4389.49"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1996,11 +1996,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.176"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.177"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.176",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 88.0.4324.177",
         "resultdb": {
           "enable": true
         },
@@ -2010,7 +2010,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.176"
+              "revision": "version:88.0.4324.177"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -2075,11 +2075,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.48"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.49"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.48",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.49",
         "resultdb": {
           "enable": true
         },
@@ -2089,7 +2089,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.48"
+              "revision": "version:89.0.4389.49"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index c3eff50..6cf92db 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -320,13 +320,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=89',
     ],
-    'identifier': 'Implementation Library Skew Tests For 89.0.4389.48',
+    'identifier': 'Implementation Library Skew Tests For 89.0.4389.49',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M89',
-          'revision': 'version:89.0.4389.48',
+          'revision': 'version:89.0.4389.49',
         }
       ],
     },
@@ -344,13 +344,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=88',
     ],
-    'identifier': 'Implementation Library Skew Tests For 88.0.4324.176',
+    'identifier': 'Implementation Library Skew Tests For 88.0.4324.177',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M88',
-          'revision': 'version:88.0.4324.176',
+          'revision': 'version:88.0.4324.177',
         }
       ],
     },
@@ -392,13 +392,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=89',
     ],
-    'identifier': 'Implementation Library Skew Tests For 89.0.4389.48',
+    'identifier': 'Implementation Library Skew Tests For 89.0.4389.49',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M89',
-          'revision': 'version:89.0.4389.48',
+          'revision': 'version:89.0.4389.49',
         }
       ],
     },
@@ -416,13 +416,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=88',
     ],
-    'identifier': 'Implementation Library Skew Tests For 88.0.4324.176',
+    'identifier': 'Implementation Library Skew Tests For 88.0.4324.177',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M88',
-          'revision': 'version:88.0.4324.176',
+          'revision': 'version:88.0.4324.177',
         }
       ],
     },
@@ -464,13 +464,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--client-version=89',
     ],
-    'identifier': 'Client Library Skew Tests For 89.0.4389.48',
+    'identifier': 'Client Library Skew Tests For 89.0.4389.49',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M89',
-          'revision': 'version:89.0.4389.48',
+          'revision': 'version:89.0.4389.49',
         }
       ],
     },
@@ -488,13 +488,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--client-version=88',
     ],
-    'identifier': 'Client Library Skew Tests For 88.0.4324.176',
+    'identifier': 'Client Library Skew Tests For 88.0.4324.177',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M88',
-          'revision': 'version:88.0.4324.176',
+          'revision': 'version:88.0.4324.177',
         }
       ],
     },
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 3bea498..7ba79a2b 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -157,6 +157,15 @@
 // Enable the prerender2. https://crbug.com/1126305.
 const base::Feature kPrerender2{"Prerender2",
                                 base::FEATURE_DISABLED_BY_DEFAULT};
+const base::FeatureParam<Prerender2Implementation>::Option
+    prerender2_implementation_options[] = {
+        {Prerender2Implementation::kWebContents, "webcontents"},
+        {Prerender2Implementation::kMPArch, "mparch"}};
+const base::FeatureParam<Prerender2Implementation>
+    kPrerender2ImplementationParam{&kPrerender2, "implementation",
+                                   Prerender2Implementation::kWebContents,
+                                   &prerender2_implementation_options};
+
 bool IsPrerender2Enabled() {
   return base::FeatureList::IsEnabled(blink::features::kPrerender2);
 }
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index ed498acc..aa813473 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -51,6 +51,10 @@
 
 // Prerender2:
 BLINK_COMMON_EXPORT extern const base::Feature kPrerender2;
+enum class Prerender2Implementation { kWebContents, kMPArch };
+BLINK_COMMON_EXPORT extern const base::FeatureParam<Prerender2Implementation>
+    kPrerender2ImplementationParam;
+
 // Returns true when Prerender2 feature is enabled.
 BLINK_COMMON_EXPORT bool IsPrerender2Enabled();
 
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_gc_for_context_dispose.cc b/third_party/blink/renderer/bindings/core/v8/v8_gc_for_context_dispose.cc
index 0b6066e..a64019b 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_gc_for_context_dispose.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_gc_for_context_dispose.cc
@@ -49,7 +49,7 @@
   v8::HeapStatistics v8_heap_statistics;
   blink::V8PerIsolateData::MainThreadIsolate()->GetHeapStatistics(
       &v8_heap_statistics);
-  usage = v8_heap_statistics.total_heap_size();
+  usage += v8_heap_statistics.total_heap_size();
   return usage;
 }
 #endif  // defined(OS_ANDROID)
diff --git a/third_party/blink/renderer/bindings/modules/v8/generated.gni b/third_party/blink/renderer/bindings/modules/v8/generated.gni
index a8592c3a..153a73b 100644
--- a/third_party/blink/renderer/bindings/modules/v8/generated.gni
+++ b/third_party/blink/renderer/bindings/modules/v8/generated.gni
@@ -97,8 +97,6 @@
   "$bindings_modules_v8_output_dir/rendering_context.h",
   "$bindings_modules_v8_output_dir/request_or_usv_string_or_request_or_usv_string_sequence.cc",
   "$bindings_modules_v8_output_dir/request_or_usv_string_or_request_or_usv_string_sequence.h",
-  "$bindings_modules_v8_output_dir/rtc_ice_candidate_init_or_rtc_ice_candidate.cc",
-  "$bindings_modules_v8_output_dir/rtc_ice_candidate_init_or_rtc_ice_candidate.h",
   "$bindings_modules_v8_output_dir/string_or_array_buffer_or_array_buffer_view_or_ndef_message_init.cc",
   "$bindings_modules_v8_output_dir/string_or_array_buffer_or_array_buffer_view_or_ndef_message_init.h",
   "$bindings_modules_v8_output_dir/string_or_canvas_gradient_or_canvas_pattern.cc",
diff --git a/third_party/blink/renderer/core/animation/animation.cc b/third_party/blink/renderer/core/animation/animation.cc
index 25752977a..5bededa4 100644
--- a/third_party/blink/renderer/core/animation/animation.cc
+++ b/third_party/blink/renderer/core/animation/animation.cc
@@ -53,6 +53,8 @@
 #include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
 #include "third_party/blink/renderer/core/css/style_change_reason.h"
+#include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h"
+#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
 #include "third_party/blink/renderer/core/events/animation_playback_event.h"
@@ -2596,6 +2598,24 @@
           WrapWeakPersistent(inline_style), WrapWeakPersistent(target)));
 }
 
+bool Animation::IsInDisplayLockedSubtree() {
+  Element* owning_element = OwningElement();
+  if (!owning_element || !GetDocument())
+    return false;
+
+  base::TimeTicks display_lock_update_timestamp =
+      GetDocument()->GetDisplayLockDocumentState().GetLockUpdateTimestamp();
+
+  if (last_display_lock_update_time_ < display_lock_update_timestamp) {
+    const Element* element =
+        DisplayLockUtilities::NearestLockedExclusiveAncestor(*owning_element);
+    is_in_display_locked_subtree_ = !!element;
+    last_display_lock_update_time_ = display_lock_update_timestamp;
+  }
+
+  return is_in_display_locked_subtree_;
+}
+
 void Animation::Trace(Visitor* visitor) const {
   visitor->Trace(content_);
   visitor->Trace(document_);
diff --git a/third_party/blink/renderer/core/animation/animation.h b/third_party/blink/renderer/core/animation/animation.h
index 31814ef..1a512b86 100644
--- a/third_party/blink/renderer/core/animation/animation.h
+++ b/third_party/blink/renderer/core/animation/animation.h
@@ -36,6 +36,7 @@
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/optional.h"
+#include "base/time/time.h"
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_property.h"
@@ -299,6 +300,8 @@
   // depends on computed values.
   virtual void FlushPendingUpdates() const {}
 
+  bool IsInDisplayLockedSubtree();
+
   void SetCanCompositeBGColorAnim() { can_composite_bgcolor_anim_ = true; }
   void ResetCanCompositeBGColorAnim() { can_composite_bgcolor_anim_ = false; }
   bool CanCompositeBGColorAnim() const { return can_composite_bgcolor_anim_; }
@@ -529,6 +532,12 @@
   // the future, we might need to extend this to be a fall back reasons.
   bool can_composite_bgcolor_anim_ = false;
 
+  // Animations with an owning element stop ticking if there is an active
+  // display lock on an ancestor element.  Cache the status to minimize the
+  // number of tree walks.
+  base::TimeTicks last_display_lock_update_time_ = base::TimeTicks();
+  bool is_in_display_locked_subtree_ = false;
+
   FRIEND_TEST_ALL_PREFIXES(AnimationAnimationTestCompositeAfterPaint,
                            NoCompositeWithoutCompositedElementId);
   FRIEND_TEST_ALL_PREFIXES(AnimationAnimationTestNoCompositing,
diff --git a/third_party/blink/renderer/core/animation/animation_test.cc b/third_party/blink/renderer/core/animation/animation_test.cc
index bd07c1a..5916636 100644
--- a/third_party/blink/renderer/core/animation/animation_test.cc
+++ b/third_party/blink/renderer/core/animation/animation_test.cc
@@ -2237,4 +2237,62 @@
             CompositorAnimations::kTimelineSourceHasInvalidCompositingState);
 }
 
+TEST_F(AnimationAnimationTestCompositing, ContentVisibleDisplayLockTest) {
+  animation->cancel();
+  RunDocumentLifecycle();
+
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .container {
+        content-visibility: auto;
+      }
+      @keyframes anim {
+        from { opacity: 0; }
+        to { opacity: 1; }
+      }
+      #target {
+        background-color: blue;
+        width: 50px;
+        height: 50px;
+        animation: anim 1s linear alternate infinite;
+      }
+    </style>
+    <div id="outer" class="container">
+      <div id="inner" class="container">
+        <div id ="target">
+        </div>
+      </div>
+    </div>
+  )HTML");
+
+  RunDocumentLifecycle();
+
+  Element* outer = GetElementById("outer");
+  Element* inner = GetElementById("inner");
+  Element* target = GetElementById("target");
+
+  ElementAnimations* element_animations = target->GetElementAnimations();
+  EXPECT_EQ(1u, element_animations->Animations().size());
+
+  Animation* animation = element_animations->Animations().begin()->key;
+  ASSERT_TRUE(!!animation);
+  EXPECT_FALSE(animation->IsInDisplayLockedSubtree());
+
+  inner->setAttribute(html_names::kStyleAttr, "content-visibility: hidden");
+  RunDocumentLifecycle();
+  EXPECT_TRUE(animation->IsInDisplayLockedSubtree());
+
+  inner->setAttribute(html_names::kStyleAttr, "content-visibility: visible");
+  RunDocumentLifecycle();
+  EXPECT_FALSE(animation->IsInDisplayLockedSubtree());
+
+  outer->setAttribute(html_names::kStyleAttr, "content-visibility: hidden");
+  RunDocumentLifecycle();
+  EXPECT_TRUE(animation->IsInDisplayLockedSubtree());
+
+  // Ensure that the animation has not been canceled even though display locked.
+  EXPECT_EQ(1u, target->GetElementAnimations()->Animations().size());
+  EXPECT_EQ(animation->playState(), "running");
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/css/css_animations_test.cc b/third_party/blink/renderer/core/animation/css/css_animations_test.cc
index 9e6ec66..4851900 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations_test.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animations_test.cc
@@ -265,7 +265,7 @@
     cc::KeyframeModel* keyframe_model = GetCompositorKeyframeForOpacity();
     base::TimeDelta iteration_time = CompositorIterationTime();
     const cc::FloatAnimationCurve* opacity_curve =
-        keyframe_model->curve()->ToFloatAnimationCurve();
+        cc::FloatAnimationCurve::ToFloatAnimationCurve(keyframe_model->curve());
     EXPECT_NEAR(expected_value, opacity_curve->GetValue(iteration_time),
                 kTolerance);
   }
diff --git a/third_party/blink/renderer/core/animation/css_paint_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_paint_interpolation_type.cc
index 61fdbd2..aff7ec4 100644
--- a/third_party/blink/renderer/core/animation/css_paint_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_paint_interpolation_type.cc
@@ -30,9 +30,9 @@
               StyleColor& result) {
   switch (property.PropertyID()) {
     case CSSPropertyID::kFill:
-      return GetColorFromPaint(style.SvgStyle().FillPaint(), result);
+      return GetColorFromPaint(style.FillPaint(), result);
     case CSSPropertyID::kStroke:
-      return GetColorFromPaint(style.SvgStyle().StrokePaint(), result);
+      return GetColorFromPaint(style.StrokePaint(), result);
     default:
       NOTREACHED();
       return false;
diff --git a/third_party/blink/renderer/core/css/counter_style.cc b/third_party/blink/renderer/core/css/counter_style.cc
index a42437c..6c9f967 100644
--- a/third_party/blink/renderer/core/css/counter_style.cc
+++ b/third_party/blink/renderer/core/css/counter_style.cc
@@ -26,64 +26,6 @@
 // limit string length at 120.
 const wtf_size_t kCounterLengthLimit = 120;
 
-CounterStyleSystem ToCounterStyleSystemEnum(const CSSValue* value) {
-  if (!value)
-    return CounterStyleSystem::kSymbolic;
-
-  CSSValueID system_keyword;
-  if (const auto* id = DynamicTo<CSSIdentifierValue>(value)) {
-    system_keyword = id->GetValueID();
-  } else {
-    // Either fixed or extends.
-    DCHECK(value->IsValuePair());
-    const CSSValuePair* pair = To<CSSValuePair>(value);
-    DCHECK(pair->First().IsIdentifierValue());
-    system_keyword = To<CSSIdentifierValue>(pair->First()).GetValueID();
-  }
-
-  switch (system_keyword) {
-    case CSSValueID::kCyclic:
-      return CounterStyleSystem::kCyclic;
-    case CSSValueID::kFixed:
-      return CounterStyleSystem::kFixed;
-    case CSSValueID::kSymbolic:
-      return CounterStyleSystem::kSymbolic;
-    case CSSValueID::kAlphabetic:
-      return CounterStyleSystem::kAlphabetic;
-    case CSSValueID::kNumeric:
-      return CounterStyleSystem::kNumeric;
-    case CSSValueID::kAdditive:
-      return CounterStyleSystem::kAdditive;
-    case CSSValueID::kInternalHebrew:
-      return CounterStyleSystem::kHebrew;
-    case CSSValueID::kInternalSimpChineseInformal:
-      return CounterStyleSystem::kSimpChineseInformal;
-    case CSSValueID::kInternalSimpChineseFormal:
-      return CounterStyleSystem::kSimpChineseFormal;
-    case CSSValueID::kInternalTradChineseInformal:
-      return CounterStyleSystem::kTradChineseInformal;
-    case CSSValueID::kInternalTradChineseFormal:
-      return CounterStyleSystem::kTradChineseFormal;
-    case CSSValueID::kInternalKoreanHangulFormal:
-      return CounterStyleSystem::kKoreanHangulFormal;
-    case CSSValueID::kInternalKoreanHanjaInformal:
-      return CounterStyleSystem::kKoreanHanjaInformal;
-    case CSSValueID::kInternalKoreanHanjaFormal:
-      return CounterStyleSystem::kKoreanHanjaFormal;
-    case CSSValueID::kInternalLowerArmenian:
-      return CounterStyleSystem::kLowerArmenian;
-    case CSSValueID::kInternalUpperArmenian:
-      return CounterStyleSystem::kUpperArmenian;
-    case CSSValueID::kInternalEthiopicNumeric:
-      return CounterStyleSystem::kEthiopicNumeric;
-    case CSSValueID::kExtends:
-      return CounterStyleSystem::kUnresolvedExtends;
-    default:
-      NOTREACHED();
-      return CounterStyleSystem::kSymbolic;
-  }
-}
-
 bool HasSymbols(CounterStyleSystem system) {
   switch (system) {
     case CounterStyleSystem::kCyclic:
@@ -109,38 +51,6 @@
   }
 }
 
-bool SymbolsAreValid(const StyleRuleCounterStyle& rule,
-                     CounterStyleSystem system) {
-  const CSSValueList* symbols = To<CSSValueList>(rule.GetSymbols());
-  const CSSValueList* additive_symbols =
-      To<CSSValueList>(rule.GetAdditiveSymbols());
-  switch (system) {
-    case CounterStyleSystem::kCyclic:
-    case CounterStyleSystem::kFixed:
-    case CounterStyleSystem::kSymbolic:
-      return symbols && symbols->length();
-    case CounterStyleSystem::kAlphabetic:
-    case CounterStyleSystem::kNumeric:
-      return symbols && symbols->length() > 1u;
-    case CounterStyleSystem::kAdditive:
-      return additive_symbols && additive_symbols->length();
-    case CounterStyleSystem::kUnresolvedExtends:
-      return !symbols && !additive_symbols;
-    case CounterStyleSystem::kHebrew:
-    case CounterStyleSystem::kSimpChineseInformal:
-    case CounterStyleSystem::kSimpChineseFormal:
-    case CounterStyleSystem::kTradChineseInformal:
-    case CounterStyleSystem::kTradChineseFormal:
-    case CounterStyleSystem::kKoreanHangulFormal:
-    case CounterStyleSystem::kKoreanHanjaInformal:
-    case CounterStyleSystem::kKoreanHanjaFormal:
-    case CounterStyleSystem::kLowerArmenian:
-    case CounterStyleSystem::kUpperArmenian:
-    case CounterStyleSystem::kEthiopicNumeric:
-      return true;
-  }
-}
-
 String SymbolToString(const CSSValue& value) {
   if (const CSSStringValue* string = DynamicTo<CSSStringValue>(value))
     return string->Value();
@@ -384,6 +294,66 @@
   return *decimal;
 }
 
+// static
+CounterStyleSystem CounterStyle::ToCounterStyleSystemEnum(
+    const CSSValue* value) {
+  if (!value)
+    return CounterStyleSystem::kSymbolic;
+
+  CSSValueID system_keyword;
+  if (const auto* id = DynamicTo<CSSIdentifierValue>(value)) {
+    system_keyword = id->GetValueID();
+  } else {
+    // Either fixed or extends.
+    DCHECK(value->IsValuePair());
+    const CSSValuePair* pair = To<CSSValuePair>(value);
+    DCHECK(pair->First().IsIdentifierValue());
+    system_keyword = To<CSSIdentifierValue>(pair->First()).GetValueID();
+  }
+
+  switch (system_keyword) {
+    case CSSValueID::kCyclic:
+      return CounterStyleSystem::kCyclic;
+    case CSSValueID::kFixed:
+      return CounterStyleSystem::kFixed;
+    case CSSValueID::kSymbolic:
+      return CounterStyleSystem::kSymbolic;
+    case CSSValueID::kAlphabetic:
+      return CounterStyleSystem::kAlphabetic;
+    case CSSValueID::kNumeric:
+      return CounterStyleSystem::kNumeric;
+    case CSSValueID::kAdditive:
+      return CounterStyleSystem::kAdditive;
+    case CSSValueID::kInternalHebrew:
+      return CounterStyleSystem::kHebrew;
+    case CSSValueID::kInternalSimpChineseInformal:
+      return CounterStyleSystem::kSimpChineseInformal;
+    case CSSValueID::kInternalSimpChineseFormal:
+      return CounterStyleSystem::kSimpChineseFormal;
+    case CSSValueID::kInternalTradChineseInformal:
+      return CounterStyleSystem::kTradChineseInformal;
+    case CSSValueID::kInternalTradChineseFormal:
+      return CounterStyleSystem::kTradChineseFormal;
+    case CSSValueID::kInternalKoreanHangulFormal:
+      return CounterStyleSystem::kKoreanHangulFormal;
+    case CSSValueID::kInternalKoreanHanjaInformal:
+      return CounterStyleSystem::kKoreanHanjaInformal;
+    case CSSValueID::kInternalKoreanHanjaFormal:
+      return CounterStyleSystem::kKoreanHanjaFormal;
+    case CSSValueID::kInternalLowerArmenian:
+      return CounterStyleSystem::kLowerArmenian;
+    case CSSValueID::kInternalUpperArmenian:
+      return CounterStyleSystem::kUpperArmenian;
+    case CSSValueID::kInternalEthiopicNumeric:
+      return CounterStyleSystem::kEthiopicNumeric;
+    case CSSValueID::kExtends:
+      return CounterStyleSystem::kUnresolvedExtends;
+    default:
+      NOTREACHED();
+      return CounterStyleSystem::kSymbolic;
+  }
+}
+
 CounterStyle::~CounterStyle() = default;
 
 AtomicString CounterStyle::GetName() const {
@@ -392,8 +362,7 @@
 
 // static
 CounterStyle* CounterStyle::Create(const StyleRuleCounterStyle& rule) {
-  CounterStyleSystem system = ToCounterStyleSystemEnum(rule.GetSystem());
-  if (!SymbolsAreValid(rule, system))
+  if (!rule.HasValidSymbols())
     return nullptr;
 
   return MakeGarbageCollected<CounterStyle>(rule);
diff --git a/third_party/blink/renderer/core/css/counter_style.h b/third_party/blink/renderer/core/css/counter_style.h
index 3058e1ab..7dc26c957 100644
--- a/third_party/blink/renderer/core/css/counter_style.h
+++ b/third_party/blink/renderer/core/css/counter_style.h
@@ -12,6 +12,7 @@
 namespace blink {
 
 class StyleRuleCounterStyle;
+class CSSValue;
 
 enum class CounterStyleSystem {
   kCyclic,
@@ -39,6 +40,8 @@
  public:
   static CounterStyle& GetDecimal();
 
+  static CounterStyleSystem ToCounterStyleSystemEnum(const CSSValue* value);
+
   // Returns nullptr if the @counter-style rule is invalid.
   static CounterStyle* Create(const StyleRuleCounterStyle&);
 
diff --git a/third_party/blink/renderer/core/css/css_counter_style_rule.cc b/third_party/blink/renderer/core/css/css_counter_style_rule.cc
index 366180df..32bdf8c1 100644
--- a/third_party/blink/renderer/core/css/css_counter_style_rule.cc
+++ b/third_party/blink/renderer/core/css/css_counter_style_rule.cc
@@ -7,6 +7,7 @@
 #include "third_party/blink/renderer/core/css/parser/at_rule_descriptor_parser.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
 #include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
+#include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/css/style_rule_counter_style.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -236,8 +237,26 @@
     document->GetStyleEngine().MarkCounterStylesNeedUpdate();
 }
 
-void CSSCounterStyleRule::setName(const String&) {
-  // TODO(crbug.com/687225): Implement
+void CSSCounterStyleRule::setName(const ExecutionContext* execution_context,
+                                  const String& text) {
+  CSSStyleSheet* style_sheet = parentStyleSheet();
+  auto& context = *MakeGarbageCollected<CSSParserContext>(
+      ParserContext(execution_context->GetSecureContextMode()), style_sheet);
+  CSSTokenizer tokenizer(text);
+  auto tokens = tokenizer.TokenizeToEOF();
+  CSSParserTokenRange token_range(tokens);
+  AtomicString name =
+      css_parsing_utils::ConsumeCounterStyleNameInPrelude(token_range, context);
+  if (!name || name == counter_style_rule_->GetName())
+    return;
+
+  // Changing name may affect cascade result, which requires re-collecting all
+  // the rules and re-constructing the CounterStyleMap to handle.
+  CSSStyleSheet::RuleMutationScope rule_mutation_scope(this);
+
+  counter_style_rule_->SetName(name);
+  if (Document* document = style_sheet->OwnerDocument())
+    document->GetStyleEngine().MarkCounterStylesNeedUpdate();
 }
 
 void CSSCounterStyleRule::setSystem(const ExecutionContext* execution_context,
diff --git a/third_party/blink/renderer/core/css/css_counter_style_rule.h b/third_party/blink/renderer/core/css/css_counter_style_rule.h
index 524ef61..6e8620d 100644
--- a/third_party/blink/renderer/core/css/css_counter_style_rule.h
+++ b/third_party/blink/renderer/core/css/css_counter_style_rule.h
@@ -36,7 +36,7 @@
   String speakAs() const;
   String fallback() const;
 
-  void setName(const String&);
+  void setName(const ExecutionContext*, const String&);
   void setSystem(const ExecutionContext*, const String&);
   void setSymbols(const ExecutionContext*, const String&);
   void setAdditiveSymbols(const ExecutionContext*, const String&);
diff --git a/third_party/blink/renderer/core/css/css_counter_style_rule.idl b/third_party/blink/renderer/core/css/css_counter_style_rule.idl
index c8147ae6..88fcfc1f 100644
--- a/third_party/blink/renderer/core/css/css_counter_style_rule.idl
+++ b/third_party/blink/renderer/core/css/css_counter_style_rule.idl
@@ -5,7 +5,7 @@
 // https://drafts.csswg.org/css-counter-styles-3/#the-csscounterstylerule-interface
 [Exposed=Window, RuntimeEnabled=CSSAtRuleCounterStyle]
 interface CSSCounterStyleRule : CSSRule {
-  attribute CSSOMString name;
+  [SetterCallWith=ExecutionContext] attribute CSSOMString name;
   [SetterCallWith=ExecutionContext] attribute CSSOMString system;
   [SetterCallWith=ExecutionContext] attribute CSSOMString symbols;
   [SetterCallWith=ExecutionContext] attribute CSSOMString additiveSymbols;
diff --git a/third_party/blink/renderer/core/css/css_image_set_value.cc b/third_party/blink/renderer/core/css/css_image_set_value.cc
index 89ac0c7..b3bd010 100644
--- a/third_party/blink/renderer/core/css/css_image_set_value.cc
+++ b/third_party/blink/renderer/core/css/css_image_set_value.cc
@@ -39,6 +39,7 @@
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/weborigin/referrer.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 namespace blink {
@@ -54,23 +55,19 @@
   wtf_size_t length = this->length();
   wtf_size_t i = 0;
   while (i < length) {
-    const auto& image_value = To<CSSImageValue>(Item(i));
-    String image_url = image_value.Url();
+    wtf_size_t image_index = i;
 
     ++i;
     SECURITY_DCHECK(i < length);
-    const CSSValue& scale_factor_value = Item(i);
-    float scale_factor =
-        To<CSSPrimitiveValue>(scale_factor_value).GetFloatValue();
+    const auto& scale_factor_value = To<CSSPrimitiveValue>(Item(i));
 
     ImageWithScale image;
-    image.image_url = image_url;
-    image.referrer.referrer = image_value.GetReferrer().referrer;
-    image.referrer.referrer_policy = image_value.GetReferrer().referrer_policy;
-    image.scale_factor = scale_factor;
+    image.index = image_index;
+    image.scale_factor = scale_factor_value.GetFloatValue();
 
     // Only set for the first image as all images in a set should have identical
     // is_ad_related bits.
+    const auto& image_value = To<CSSImageValue>(Item(image_index));
     if (!images_in_set_.size())
       is_ad_related_ = image_value.GetIsAdRelated();
     DCHECK_EQ(is_ad_related_, image_value.GetIsAdRelated());
@@ -119,11 +116,12 @@
     // Page::PageScaleFactor(), LocalFrame::PageZoomFactor(), and any CSS
     // transforms. https://bugs.webkit.org/show_bug.cgi?id=81698
     ImageWithScale image = BestImageForScaleFactor(device_scale_factor);
-    ResourceRequest resource_request(document.CompleteURL(image.image_url));
+    const auto& image_value = To<CSSImageValue>(Item(image.index));
+    ResourceRequest resource_request(image_value.Url());
     resource_request.SetReferrerPolicy(
         ReferrerUtils::MojoReferrerPolicyResolveDefault(
-            image.referrer.referrer_policy));
-    resource_request.SetReferrerString(image.referrer.referrer);
+            image_value.GetReferrer().referrer_policy));
+    resource_request.SetReferrerString(image_value.GetReferrer().referrer);
     if (is_ad_related_)
       resource_request.SetIsAdResource();
     ResourceLoaderOptions options(
@@ -131,7 +129,7 @@
     options.initiator_info.name = parser_mode_ == kUASheetMode
                                       ? fetch_initiator_type_names::kUacss
                                       : fetch_initiator_type_names::kCSS;
-    options.initiator_info.referrer = image.referrer.referrer;
+    options.initiator_info.referrer = image_value.GetReferrer().referrer;
     FetchParameters params(std::move(resource_request), options);
 
     if (cross_origin != kCrossOriginAttributeNotSet) {
diff --git a/third_party/blink/renderer/core/css/css_image_set_value.h b/third_party/blink/renderer/core/css/css_image_set_value.h
index 0b474dc..c4cf2f0 100644
--- a/third_party/blink/renderer/core/css/css_image_set_value.h
+++ b/third_party/blink/renderer/core/css/css_image_set_value.h
@@ -30,7 +30,6 @@
 #include "third_party/blink/renderer/core/css/parser/css_parser_mode.h"
 #include "third_party/blink/renderer/platform/loader/fetch/cross_origin_attribute_value.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
-#include "third_party/blink/renderer/platform/weborigin/referrer.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
@@ -54,13 +53,6 @@
 
   String CustomCSSText() const;
 
-  struct ImageWithScale {
-    DISALLOW_NEW();
-    String image_url;
-    Referrer referrer;
-    float scale_factor;
-  };
-
   CSSImageSetValue* ValueWithURLsMadeAbsolute();
 
   bool HasFailedOrCanceledSubresources() const;
@@ -68,6 +60,12 @@
   void TraceAfterDispatch(blink::Visitor*) const;
 
  protected:
+  struct ImageWithScale {
+    DISALLOW_NEW();
+    wtf_size_t index;
+    float scale_factor;
+  };
+
   ImageWithScale BestImageForScaleFactor(float scale_factor);
 
  private:
diff --git a/third_party/blink/renderer/core/css/css_property_equality.cc b/third_party/blink/renderer/core/css/css_property_equality.cc
index 603a603..0a7c4be 100644
--- a/third_party/blink/renderer/core/css/css_property_equality.cc
+++ b/third_party/blink/renderer/core/css/css_property_equality.cc
@@ -138,13 +138,10 @@
     case CSSPropertyID::kColor:
       return a.GetColor() == b.GetColor() &&
              a.InternalVisitedColor() == b.InternalVisitedColor();
-    case CSSPropertyID::kFill: {
-      const SVGComputedStyle& a_svg = a.SvgStyle();
-      const SVGComputedStyle& b_svg = b.SvgStyle();
-      return a_svg.FillPaint().EqualTypeOrColor(b_svg.FillPaint()) &&
-             a_svg.InternalVisitedFillPaint().EqualTypeOrColor(
-                 b_svg.InternalVisitedFillPaint());
-    }
+    case CSSPropertyID::kFill:
+      return a.FillPaint().EqualTypeOrColor(b.FillPaint()) &&
+             a.InternalVisitedFillPaint().EqualTypeOrColor(
+                 b.InternalVisitedFillPaint());
     case CSSPropertyID::kFillOpacity:
       return a.FillOpacity() == b.FillOpacity();
     case CSSPropertyID::kFlexBasis:
@@ -249,13 +246,10 @@
       return a.StopColor() == b.StopColor();
     case CSSPropertyID::kStopOpacity:
       return a.StopOpacity() == b.StopOpacity();
-    case CSSPropertyID::kStroke: {
-      const SVGComputedStyle& a_svg = a.SvgStyle();
-      const SVGComputedStyle& b_svg = b.SvgStyle();
-      return a_svg.StrokePaint().EqualTypeOrColor(b_svg.StrokePaint()) &&
-             a_svg.InternalVisitedStrokePaint().EqualTypeOrColor(
-                 b_svg.InternalVisitedStrokePaint());
-    }
+    case CSSPropertyID::kStroke:
+      return a.StrokePaint().EqualTypeOrColor(b.StrokePaint()) &&
+             a.InternalVisitedStrokePaint().EqualTypeOrColor(
+                 b.InternalVisitedStrokePaint());
     case CSSPropertyID::kStrokeDasharray:
       return a.StrokeDashArray() == b.StrokeDashArray();
     case CSSPropertyID::kStrokeDashoffset:
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
index 68f0665b..167f96e 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
@@ -942,26 +942,11 @@
     return nullptr;
   CSSParserTokenStream::BlockGuard guard(stream);
 
-  const CSSParserToken& name_token = prelude.ConsumeIncludingWhitespace();
-  if (!prelude.AtEnd())
+  AtomicString name = css_parsing_utils::ConsumeCounterStyleNameInPrelude(
+      prelude, *GetContext());
+  if (!name)
     return nullptr;
 
-  if (name_token.GetType() != kIdentToken ||
-      !css_parsing_utils::IsCustomIdent<CSSValueID::kNone>(name_token.Id()))
-    return nullptr;
-
-  if (GetContext()->Mode() != kUASheetMode) {
-    if (name_token.Id() == CSSValueID::kDecimal ||
-        name_token.Id() == CSSValueID::kDisc)
-      return nullptr;
-  }
-
-  AtomicString name(name_token.Value().ToString());
-  if (css_parsing_utils::ShouldLowerCaseCounterStyleNameOnParse(
-          name, *GetContext())) {
-    name = name.LowerASCII();
-  }
-
   if (observer_) {
     observer_->StartRuleHeader(StyleRule::kCounterStyle, prelude_offset_start);
     observer_->EndRuleHeader(prelude_offset_end);
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index bc42ba23..6181f7c 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -4910,5 +4910,27 @@
   return MakeGarbageCollected<CSSCustomIdentValue>(name);
 }
 
+AtomicString ConsumeCounterStyleNameInPrelude(CSSParserTokenRange& prelude,
+                                              const CSSParserContext& context) {
+  const CSSParserToken& name_token = prelude.ConsumeIncludingWhitespace();
+  if (!prelude.AtEnd())
+    return g_null_atom;
+
+  if (name_token.GetType() != kIdentToken ||
+      !IsCustomIdent<CSSValueID::kNone>(name_token.Id()))
+    return g_null_atom;
+
+  if (context.Mode() != kUASheetMode) {
+    if (name_token.Id() == CSSValueID::kDecimal ||
+        name_token.Id() == CSSValueID::kDisc)
+      return g_null_atom;
+  }
+
+  AtomicString name(name_token.Value().ToString());
+  if (ShouldLowerCaseCounterStyleNameOnParse(name, context))
+    name = name.LowerASCII();
+  return name;
+}
+
 }  // namespace css_parsing_utils
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
index dbd898fd..ac372d9 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
@@ -452,6 +452,8 @@
 // https://drafts.csswg.org/css-counter-styles-3/#typedef-counter-style-name
 CSSCustomIdentValue* ConsumeCounterStyleName(CSSParserTokenRange&,
                                              const CSSParserContext&);
+AtomicString ConsumeCounterStyleNameInPrelude(CSSParserTokenRange&,
+                                              const CSSParserContext&);
 
 // When parsing a counter style name, it should be ASCII lowercased if it's an
 // ASCII case-insensitive match of any predefined counter style name.
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index f7821e93..e7b1ef7b 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -2677,18 +2677,18 @@
 
 const CSSValue* Fill::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
-    const SVGComputedStyle& svg_style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return ComputedStyleUtils::ValueForSVGPaint(svg_style.FillPaint(), style);
+  return ComputedStyleUtils::ValueForSVGPaint(style.FillPaint(), style);
 }
 
 const blink::Color Fill::ColorIncludingFallback(
     bool visited_link,
     const ComputedStyle& style) const {
   DCHECK(!visited_link);
-  DCHECK(style.SvgStyle().FillPaint().HasColor());
-  StyleColor fill_color = style.SvgStyle().FillPaint().GetColor();
+  DCHECK(style.FillPaint().HasColor());
+  const StyleColor& fill_color = style.FillPaint().GetColor();
   if (style.ShouldForceColor(fill_color))
     return style.GetInternalForcedCurrentColor();
   return fill_color.Resolve(style.GetCurrentColor(), style.UsedColorScheme());
@@ -2702,11 +2702,11 @@
 }
 
 const CSSValue* FillOpacity::CSSValueFromComputedStyleInternal(
-    const ComputedStyle&,
-    const SVGComputedStyle& svg_style,
+    const ComputedStyle& style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return CSSNumericLiteralValue::Create(svg_style.FillOpacity(),
+  return CSSNumericLiteralValue::Create(style.FillOpacity(),
                                         CSSPrimitiveValue::UnitType::kNumber);
 }
 
@@ -3825,7 +3825,7 @@
     bool visited_link,
     const ComputedStyle& style) const {
   DCHECK(visited_link);
-  const SVGPaint& paint = style.SvgStyle().InternalVisitedFillPaint();
+  const SVGPaint& paint = style.InternalVisitedFillPaint();
 
   // FIXME: This code doesn't support the uri component of the visited link
   // paint, https://bugs.webkit.org/show_bug.cgi?id=70006
@@ -3833,7 +3833,7 @@
     return To<Longhand>(GetCSSPropertyFill())
         .ColorIncludingFallback(false, style);
   }
-  StyleColor visited_fill_color = paint.GetColor();
+  const StyleColor& visited_fill_color = paint.GetColor();
   if (style.ShouldForceColor(visited_fill_color))
     return style.GetInternalForcedVisitedCurrentColor();
   return visited_fill_color.Resolve(style.GetInternalVisitedCurrentColor(),
@@ -3890,7 +3890,7 @@
     bool visited_link,
     const ComputedStyle& style) const {
   DCHECK(visited_link);
-  const SVGPaint& paint = style.SvgStyle().InternalVisitedStrokePaint();
+  const SVGPaint& paint = style.InternalVisitedStrokePaint();
 
   // FIXME: This code doesn't support the uri component of the visited link
   // paint, https://bugs.webkit.org/show_bug.cgi?id=70006
@@ -3898,7 +3898,7 @@
     return To<Longhand>(GetCSSPropertyStroke())
         .ColorIncludingFallback(false, style);
   }
-  StyleColor visited_stroke_color = paint.GetColor();
+  const StyleColor& visited_stroke_color = paint.GetColor();
   if (style.ShouldForceColor(visited_stroke_color))
     return style.GetInternalForcedVisitedCurrentColor();
   return visited_stroke_color.Resolve(style.GetInternalVisitedCurrentColor(),
@@ -4664,10 +4664,10 @@
 
 const CSSValue* MarkerEnd::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
-    const SVGComputedStyle& svg_style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return ComputedStyleUtils::ValueForSVGResource(svg_style.MarkerEndResource());
+  return ComputedStyleUtils::ValueForSVGResource(style.MarkerEndResource());
 }
 
 const CSSValue* MarkerMid::ParseSingleValue(
@@ -4681,10 +4681,10 @@
 
 const CSSValue* MarkerMid::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
-    const SVGComputedStyle& svg_style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return ComputedStyleUtils::ValueForSVGResource(svg_style.MarkerMidResource());
+  return ComputedStyleUtils::ValueForSVGResource(style.MarkerMidResource());
 }
 
 const CSSValue* MarkerStart::ParseSingleValue(
@@ -4698,11 +4698,10 @@
 
 const CSSValue* MarkerStart::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
-    const SVGComputedStyle& svg_style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return ComputedStyleUtils::ValueForSVGResource(
-      svg_style.MarkerStartResource());
+  return ComputedStyleUtils::ValueForSVGResource(style.MarkerStartResource());
 }
 
 const CSSValue* Mask::ParseSingleValue(CSSParserTokenRange& range,
@@ -6552,18 +6551,18 @@
 
 const CSSValue* Stroke::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
-    const SVGComputedStyle& svg_style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return ComputedStyleUtils::ValueForSVGPaint(svg_style.StrokePaint(), style);
+  return ComputedStyleUtils::ValueForSVGPaint(style.StrokePaint(), style);
 }
 
 const blink::Color Stroke::ColorIncludingFallback(
     bool visited_link,
     const ComputedStyle& style) const {
   DCHECK(!visited_link);
-  DCHECK(style.SvgStyle().StrokePaint().HasColor());
-  StyleColor stroke_color = style.SvgStyle().StrokePaint().GetColor();
+  DCHECK(style.StrokePaint().HasColor());
+  const StyleColor& stroke_color = style.StrokePaint().GetColor();
   if (style.ShouldForceColor(stroke_color))
     return style.GetInternalForcedCurrentColor();
   return stroke_color.Resolve(style.GetCurrentColor(), style.UsedColorScheme());
@@ -6592,11 +6591,11 @@
 
 const CSSValue* StrokeDasharray::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
-    const SVGComputedStyle& svg_style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
   return ComputedStyleUtils::StrokeDashArrayToCSSValueList(
-      *svg_style.StrokeDashArray(), style);
+      *style.StrokeDashArray(), style);
 }
 
 const CSSValue* StrokeDashoffset::ParseSingleValue(
@@ -6620,18 +6619,18 @@
 
 const CSSValue* StrokeLinecap::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
-    const SVGComputedStyle& svg_style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return CSSIdentifierValue::Create(svg_style.CapStyle());
+  return CSSIdentifierValue::Create(style.CapStyle());
 }
 
 const CSSValue* StrokeLinejoin::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
-    const SVGComputedStyle& svg_style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return CSSIdentifierValue::Create(svg_style.JoinStyle());
+  return CSSIdentifierValue::Create(style.JoinStyle());
 }
 
 const CSSValue* StrokeMiterlimit::ParseSingleValue(
@@ -6643,11 +6642,11 @@
 }
 
 const CSSValue* StrokeMiterlimit::CSSValueFromComputedStyleInternal(
-    const ComputedStyle&,
-    const SVGComputedStyle& svg_style,
+    const ComputedStyle& style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return CSSNumericLiteralValue::Create(svg_style.StrokeMiterLimit(),
+  return CSSNumericLiteralValue::Create(style.StrokeMiterLimit(),
                                         CSSPrimitiveValue::UnitType::kNumber);
 }
 
@@ -6659,11 +6658,11 @@
 }
 
 const CSSValue* StrokeOpacity::CSSValueFromComputedStyleInternal(
-    const ComputedStyle&,
-    const SVGComputedStyle& svg_style,
+    const ComputedStyle& style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return CSSNumericLiteralValue::Create(svg_style.StrokeOpacity(),
+  return CSSNumericLiteralValue::Create(style.StrokeOpacity(),
                                         CSSPrimitiveValue::UnitType::kNumber);
 }
 
@@ -6679,12 +6678,12 @@
 
 const CSSValue* StrokeWidth::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
-    const SVGComputedStyle& svg_style,
+    const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
   // We store the unzoomed stroke-width value using ConvertUnzoomedLength().
   // Don't apply zoom here either.
-  return CSSValue::Create(svg_style.StrokeWidth().length(), 1);
+  return CSSValue::Create(style.StrokeWidth().length(), 1);
 }
 
 const CSSValue* ContentVisibility::CSSValueFromComputedStyleInternal(
diff --git a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
index 7b09b6c..2d82393 100644
--- a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
@@ -2065,15 +2065,15 @@
 
 const CSSValue* Marker::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
-    const SVGComputedStyle& svg_style,
+    const SVGComputedStyle&,
     const LayoutObject* layout_object,
     bool allow_visited_style) const {
   const CSSValue* marker_start =
-      ComputedStyleUtils::ValueForSVGResource(svg_style.MarkerStartResource());
-  if (*marker_start == *ComputedStyleUtils::ValueForSVGResource(
-                           svg_style.MarkerMidResource()) &&
-      *marker_start == *ComputedStyleUtils::ValueForSVGResource(
-                           svg_style.MarkerEndResource())) {
+      ComputedStyleUtils::ValueForSVGResource(style.MarkerStartResource());
+  if (*marker_start ==
+          *ComputedStyleUtils::ValueForSVGResource(style.MarkerMidResource()) &&
+      *marker_start ==
+          *ComputedStyleUtils::ValueForSVGResource(style.MarkerEndResource())) {
     return marker_start;
   }
   return nullptr;
diff --git a/third_party/blink/renderer/core/css/style_rule_counter_style.cc b/third_party/blink/renderer/core/css/style_rule_counter_style.cc
index 957c29d2..b4e87fa 100644
--- a/third_party/blink/renderer/core/css/style_rule_counter_style.cc
+++ b/third_party/blink/renderer/core/css/style_rule_counter_style.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/css/style_rule_counter_style.h"
 
+#include "third_party/blink/renderer/core/css/counter_style.h"
 #include "third_party/blink/renderer/core/css/css_counter_style_rule.h"
 #include "third_party/blink/renderer/core/css/css_value_list.h"
 
@@ -32,6 +33,84 @@
 
 StyleRuleCounterStyle::~StyleRuleCounterStyle() = default;
 
+bool StyleRuleCounterStyle::HasValidSymbols() const {
+  CounterStyleSystem system =
+      CounterStyle::ToCounterStyleSystemEnum(GetSystem());
+  const auto* symbols = To<CSSValueList>(GetSymbols());
+  const auto* additive_symbols = To<CSSValueList>(GetAdditiveSymbols());
+  switch (system) {
+    case CounterStyleSystem::kCyclic:
+    case CounterStyleSystem::kFixed:
+    case CounterStyleSystem::kSymbolic:
+      return symbols && symbols->length();
+    case CounterStyleSystem::kAlphabetic:
+    case CounterStyleSystem::kNumeric:
+      return symbols && symbols->length() > 1u;
+    case CounterStyleSystem::kAdditive:
+      return additive_symbols && additive_symbols->length();
+    case CounterStyleSystem::kUnresolvedExtends:
+      return !symbols && !additive_symbols;
+    case CounterStyleSystem::kHebrew:
+    case CounterStyleSystem::kSimpChineseInformal:
+    case CounterStyleSystem::kSimpChineseFormal:
+    case CounterStyleSystem::kTradChineseInformal:
+    case CounterStyleSystem::kTradChineseFormal:
+    case CounterStyleSystem::kKoreanHangulFormal:
+    case CounterStyleSystem::kKoreanHanjaInformal:
+    case CounterStyleSystem::kKoreanHanjaFormal:
+    case CounterStyleSystem::kLowerArmenian:
+    case CounterStyleSystem::kUpperArmenian:
+    case CounterStyleSystem::kEthiopicNumeric:
+      return true;
+  }
+}
+
+bool StyleRuleCounterStyle::SetSystem(const CSSValue* system) {
+  CounterStyleSystem old_system =
+      CounterStyle::ToCounterStyleSystemEnum(system_);
+  CounterStyleSystem new_system =
+      CounterStyle::ToCounterStyleSystemEnum(system);
+
+  // If the attribute being set is system, and the new value would change the
+  // algorithm used, do nothing and abort these steps.
+  if (old_system != new_system)
+    return false;
+
+  // Except 'fixed' and 'extends', other systems have nothing to modify.
+  if (new_system != CounterStyleSystem::kFixed &&
+      new_system != CounterStyleSystem::kUnresolvedExtends)
+    return false;
+
+  system_ = system;
+  DCHECK(HasValidSymbols());
+
+  ++version_;
+  return true;
+}
+
+bool StyleRuleCounterStyle::SetSymbols(const CSSValue* symbols) {
+  const CSSValue* original_symbols = symbols_;
+  symbols_ = symbols;
+  if (!HasValidSymbols()) {
+    symbols_ = original_symbols;
+    return false;
+  }
+  ++version_;
+  return true;
+}
+
+bool StyleRuleCounterStyle::SetAdditiveSymbols(
+    const CSSValue* additive_symbols) {
+  const CSSValue* original_additive_symbols = additive_symbols_;
+  additive_symbols_ = additive_symbols;
+  if (!HasValidSymbols()) {
+    additive_symbols_ = original_additive_symbols;
+    return false;
+  }
+  ++version_;
+  return true;
+}
+
 void StyleRuleCounterStyle::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(system_);
   visitor->Trace(negative_);
diff --git a/third_party/blink/renderer/core/css/style_rule_counter_style.h b/third_party/blink/renderer/core/css/style_rule_counter_style.h
index e45bfa3bd..89d490be 100644
--- a/third_party/blink/renderer/core/css/style_rule_counter_style.h
+++ b/third_party/blink/renderer/core/css/style_rule_counter_style.h
@@ -17,6 +17,11 @@
 
   int GetVersion() const { return version_; }
 
+  // Different 'system' values have different requirements on 'symbols' and
+  // 'additive-symbols'. Returns true if the requirement is met.
+  // https://drafts.csswg.org/css-counter-styles-3/#counter-style-symbols
+  bool HasValidSymbols() const;
+
   AtomicString GetName() const { return name_; }
   const CSSValue* GetSystem() const { return system_; }
   const CSSValue* GetNegative() const { return negative_; }
@@ -29,15 +34,13 @@
   const CSSValue* GetAdditiveSymbols() const { return additive_symbols_; }
   const CSSValue* GetSpeakAs() const { return speak_as_; }
 
+  void SetName(const AtomicString& name) {
+    name_ = name;
+    ++version_;
+  }
+
   // Returns false if the setter fails due to invalid new value.
-  bool SetName(const AtomicString& name) {
-    // TODO(crbug.com/687225): Implement.
-    return false;
-  }
-  bool SetSystem(const CSSValue* system) {
-    // TODO(crbug.com/687225): Implement.
-    return false;
-  }
+  bool SetSystem(const CSSValue* system);
   bool SetNegative(const CSSValue* negative) {
     negative_ = negative;
     ++version_;
@@ -68,14 +71,8 @@
     ++version_;
     return true;
   }
-  bool SetSymbols(const CSSValue* symbols) {
-    // TODO(crbug.com/687225): Implement.
-    return false;
-  }
-  bool SetAdditiveSymbols(const CSSValue* additive_symbols) {
-    // TODO(crbug.com/687225): Implement.
-    return false;
-  }
+  bool SetSymbols(const CSSValue* symbols);
+  bool SetAdditiveSymbols(const CSSValue* additive_symbols);
   bool SetSpeakAs(const CSSValue* speak_as) {
     speak_as_ = speak_as;
     ++version_;
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_document_state.cc b/third_party/blink/renderer/core/display_lock/display_lock_document_state.cc
index 27b45fba..9aa3952d 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_document_state.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_document_state.cc
@@ -44,11 +44,13 @@
                     "LockedDisplayLockCount", TRACE_ID_LOCAL(this),
                     locked_display_lock_count_);
   ++locked_display_lock_count_;
+  last_lock_update_timestamp_ = base::TimeTicks::Now();
 }
 
 void DisplayLockDocumentState::RemoveLockedDisplayLock() {
   DCHECK(locked_display_lock_count_);
   --locked_display_lock_count_;
+  last_lock_update_timestamp_ = base::TimeTicks::Now();
   TRACE_COUNTER_ID1(TRACE_DISABLED_BY_DEFAULT("blink.debug.display_lock"),
                     "LockedDisplayLockCount", TRACE_ID_LOCAL(this),
                     locked_display_lock_count_);
@@ -71,6 +73,10 @@
   return display_lock_blocking_all_activation_count_;
 }
 
+base::TimeTicks DisplayLockDocumentState::GetLockUpdateTimestamp() {
+  return last_lock_update_timestamp_;
+}
+
 void DisplayLockDocumentState::RegisterDisplayLockActivationObservation(
     Element* element) {
   EnsureIntersectionObserver().observe(element);
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_document_state.h b/third_party/blink/renderer/core/display_lock/display_lock_document_state.h
index ccdd5bec..9a2e4c99 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_document_state.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_document_state.h
@@ -128,6 +128,8 @@
 
   void NotifyPrintingOrPreviewChanged();
 
+  base::TimeTicks GetLockUpdateTimestamp();
+
  private:
   IntersectionObserver& EnsureIntersectionObserver();
 
@@ -154,6 +156,8 @@
   HeapVector<ForcedNodeInfo> forced_node_info_;
 
   bool printing_ = false;
+
+  base::TimeTicks last_lock_update_timestamp_ = base::TimeTicks();
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index a1995d0..a123efb 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -281,7 +281,13 @@
 bool DefinitelyNewFormattingContext(const Node& node,
                                     const ComputedStyle& style) {
   auto display = style.Display();
-  if (display == EDisplay::kInline || display == EDisplay::kContents)
+  if (display == EDisplay::kInline || display == EDisplay::kContents ||
+      display == EDisplay::kTableRowGroup ||
+      display == EDisplay::kTableHeaderGroup ||
+      display == EDisplay::kTableFooterGroup ||
+      display == EDisplay::kTableRow ||
+      display == EDisplay::kTableColumnGroup ||
+      display == EDisplay::kTableColumn)
     return false;
   // ::marker may establish a formatting context but still have some dependency
   // on the originating list item, so return false.
@@ -4239,12 +4245,15 @@
 
 void Element::ForceLegacyLayoutInFormattingContext(
     const ComputedStyle& new_style) {
-  // TableNG requires that table elements are either all NG, or all Legacy.
-  bool needs_traverse_to_table =
-      RuntimeEnabledFeatures::LayoutNGTableEnabled() &&
-      new_style.IsDisplayTableType();
-  bool found_bfc = DefinitelyNewFormattingContext(*this, new_style);
-  for (Element* ancestor = this; !found_bfc || needs_traverse_to_table;) {
+  bool found_fc = DefinitelyNewFormattingContext(*this, new_style);
+  if (found_fc && RuntimeEnabledFeatures::LayoutNGTableEnabled()) {
+    // TableNG requires that table elements are either all NG, or all Legacy. We
+    // need to mark all the way up to the containing table.
+    found_fc = new_style.Display() != EDisplay::kTableCell &&
+               new_style.Display() != EDisplay::kTableCaption;
+  }
+
+  for (Element* ancestor = this; !found_fc;) {
     ancestor =
         DynamicTo<Element>(LayoutTreeBuilderTraversal::Parent(*ancestor));
     if (!ancestor || ancestor->ShouldForceLegacyLayout())
@@ -4252,16 +4261,7 @@
     const ComputedStyle* style = ancestor->GetComputedStyle();
     if (style->Display() == EDisplay::kNone)
       break;
-    found_bfc = found_bfc || DefinitelyNewFormattingContext(*ancestor, *style);
-    if (found_bfc && !needs_traverse_to_table) {
-      needs_traverse_to_table =
-          RuntimeEnabledFeatures::LayoutNGTableEnabled() &&
-          style->IsDisplayTableType();
-    }
-    if (needs_traverse_to_table) {
-      if (style->IsDisplayTableBox())
-        needs_traverse_to_table = false;
-    }
+    found_fc = found_fc || DefinitelyNewFormattingContext(*ancestor, *style);
     ancestor->SetShouldForceLegacyLayoutForChild(true);
     ancestor->SetNeedsReattachLayoutTree();
   }
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
index d720aac..de2e1e9 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
@@ -921,7 +921,8 @@
   return SecurityOrigin::Create(url)->ToString();
 }
 
-static void GatherSecurityPolicyViolationEventData(
+namespace {
+std::unique_ptr<SourceLocation> GatherSecurityPolicyViolationEventData(
     SecurityPolicyViolationEventInit* init,
     ContentSecurityPolicyDelegate* delegate,
     const String& directive_text,
@@ -1050,7 +1051,10 @@
   }
   if (!sample.IsEmpty())
     init->setSample(sample.ToString());
+
+  return source_location;
 }
+}  // namespace
 
 void ContentSecurityPolicy::ReportViolation(
     const String& directive_text,
@@ -1095,7 +1099,9 @@
           ? &context_frame->DomWindow()->GetContentSecurityPolicyDelegate()
           : delegate_.Get();
   DCHECK(relevant_delegate);
-  GatherSecurityPolicyViolationEventData(
+  // Let GatherSecurityPolicyViolationEventData decide which source location to
+  // report.
+  source_location = GatherSecurityPolicyViolationEventData(
       violation_data, relevant_delegate, directive_text, effective_type,
       blocked_url, header, redirect_status, header_type, violation_type,
       std::move(source_location), source, source_prefix);
@@ -1118,7 +1124,8 @@
     delegate_->DispatchViolationEvent(*violation_data, element);
 
   ReportContentSecurityPolicyIssue(*violation_data, header_type, violation_type,
-                                   context_frame, element);
+                                   context_frame, element,
+                                   source_location.get());
 }
 
 void ContentSecurityPolicy::PostViolationReport(
@@ -1418,7 +1425,8 @@
     ContentSecurityPolicyType header_type,
     ContentSecurityPolicyViolationType violation_type,
     LocalFrame* frame_ancestor,
-    Element* element) {
+    Element* element,
+    SourceLocation* source_location) {
   auto cspDetails = mojom::blink::ContentSecurityPolicyIssueDetails::New();
   cspDetails->is_report_only =
       header_type == ContentSecurityPolicyType::kReport;
@@ -1436,12 +1444,15 @@
     cspDetails->frame_ancestor = std::move(affected_frame);
   }
   if (violation_data.sourceFile() && violation_data.lineNumber()) {
-    // TODO(chromium:1158782): Try to get a scriptId here as well.
     auto affected_location = mojom::blink::AffectedLocation::New();
     affected_location->url = violation_data.sourceFile();
     // The frontend expects 0-based line numbers.
     affected_location->line = violation_data.lineNumber() - 1;
     affected_location->column = violation_data.columnNumber();
+    if (source_location) {
+      affected_location->script_id =
+          String::Number(source_location->ScriptId());
+    }
     cspDetails->affected_location = std::move(affected_location);
   }
   if (element) {
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.h b/third_party/blink/renderer/core/frame/csp/content_security_policy.h
index f49ebe3..7a83678 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.h
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.h
@@ -518,8 +518,9 @@
       const blink::SecurityPolicyViolationEventInit& violation_data,
       network::mojom::ContentSecurityPolicyType header_type,
       ContentSecurityPolicyViolationType violation_type,
-      LocalFrame* = nullptr,
-      Element* = nullptr);
+      LocalFrame*,
+      Element*,
+      SourceLocation*);
 
   Member<ContentSecurityPolicyDelegate> delegate_;
   bool override_inline_style_allowed_ = false;
diff --git a/third_party/blink/renderer/core/html/forms/html_input_element.cc b/third_party/blink/renderer/core/html/forms/html_input_element.cc
index 57f58951..e30897b 100644
--- a/third_party/blink/renderer/core/html/forms/html_input_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_input_element.cc
@@ -1126,8 +1126,12 @@
 }
 
 void HTMLInputElement::SetSuggestedValue(const String& value) {
-  if (!input_type_->CanSetSuggestedValue())
+  if (!input_type_->CanSetSuggestedValue()) {
+    // Clear the suggested value because it may have been set when
+    // `input_type_->CanSetSuggestedValue()` was true.
+    TextControlElement::SetSuggestedValue(String());
     return;
+  }
   needs_to_update_view_value_ = true;
   TextControlElement::SetSuggestedValue(SanitizeValue(value));
   SetNeedsStyleRecalc(
diff --git a/third_party/blink/renderer/core/inspector/inspector_highlight.cc b/third_party/blink/renderer/core/inspector/inspector_highlight.cc
index 9bc2c2ce..9f34bed 100644
--- a/third_party/blink/renderer/core/inspector/inspector_highlight.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_highlight.cc
@@ -1061,12 +1061,12 @@
       protocol::DictionaryValue::create();
 
   LayoutObject* layout_object = node->GetLayoutObject();
+  bool is_horizontal = IsHorizontalFlex(layout_object->Parent());
   Length base_size = Length::Auto();
 
   const Length& flex_basis = layout_object->StyleRef().FlexBasis();
-  const Length& size = IsHorizontalFlex(layout_object->Parent())
-                           ? layout_object->StyleRef().Width()
-                           : layout_object->StyleRef().Height();
+  const Length& size = is_horizontal ? layout_object->StyleRef().Width()
+                                     : layout_object->StyleRef().Height();
 
   if (flex_basis.IsFixed()) {
     base_size = flex_basis;
@@ -1077,6 +1077,7 @@
   // For now, we only care about the cases where we can know the base size.
   if (base_size.IsSpecified()) {
     flex_info->setDouble("baseSize", base_size.Pixels() * scale);
+    flex_info->setBoolean("isHorizontalFlow", is_horizontal);
 
     flex_info->setValue(
         "flexItemHighlightConfig",
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 3502fb3..c427ec4 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -488,7 +488,8 @@
 
 void LayoutObject::RemoveChild(LayoutObject* old_child) {
   NOT_DESTROYED();
-  DCHECK(IsAllowedToModifyLayoutTreeStructure(GetDocument()));
+  DCHECK(IsAllowedToModifyLayoutTreeStructure(GetDocument()) ||
+         IsLayoutNGObjectForCanvasFormattedText());
 
   LayoutObjectChildList* children = VirtualChildren();
   DCHECK(children);
@@ -2219,8 +2220,8 @@
         // underlines). MathML elements are not skipped either as some of them
         // do special painting (e.g. fraction bar).
         (IsText() && !IsBR() && To<LayoutText>(this)->HasInlineFragments()) ||
-        (IsSVG() && StyleRef().SvgStyle().IsFillColorCurrentColor()) ||
-        (IsSVG() && StyleRef().SvgStyle().IsStrokeColorCurrentColor()) ||
+        (IsSVG() && StyleRef().IsFillColorCurrentColor()) ||
+        (IsSVG() && StyleRef().IsStrokeColorCurrentColor()) ||
         IsListMarkerForNormalContent() || IsDetailsMarker() || IsMathML())
       diff.SetNeedsPaintInvalidation();
   }
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 78f0d538..9d09cd6 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -612,6 +612,18 @@
            (!IsInline() || IsAtomicInlineLevel()) && !IsRubyText() &&
            (!IsTablePart() || IsTableCaption()) && !IsTable();
   }
+  inline bool ShouldApplyInlineSizeContainment() const {
+    NOT_DESTROYED();
+    return (StyleRef().ContainsInlineSize() || StyleRef().ContainsSize()) &&
+           (!IsInline() || IsAtomicInlineLevel()) && !IsRubyText() &&
+           (!IsTablePart() || IsTableCaption()) && !IsTable();
+  }
+  inline bool ShouldApplyBlockSizeContainment() const {
+    NOT_DESTROYED();
+    return (StyleRef().ContainsBlockSize() || StyleRef().ContainsSize()) &&
+           (!IsInline() || IsAtomicInlineLevel()) && !IsRubyText() &&
+           (!IsTablePart() || IsTableCaption()) && !IsTable();
+  }
   inline bool ShouldApplyStyleContainment() const {
     NOT_DESTROYED();
     return StyleRef().ContainsStyle();
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 c3e495fb..c9f3062 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
@@ -106,7 +106,7 @@
   // Note that in size containment, we have to consider sizing as if we have no
   // contents, with the conjecture being that legend is part of the contents.
   // Thus, only do this adjustment if we do not contain size.
-  if (!Node().ShouldApplySizeContainment()) {
+  if (!Node().ShouldApplyBlockSizeContainment()) {
     // Similar to how we add the consumed block size to the intrinsic
     // block size when calculating border_box_size_.block_size, we also need to
     // do so when the fieldset is adjusted to encompass the legend.
@@ -174,7 +174,7 @@
       LayoutLegend(legend);
     // The legend may eat from the available content box block size. Calculate
     // the minimum block size needed to encompass the legend.
-    if (!Node().ShouldApplySizeContainment() &&
+    if (!Node().ShouldApplyBlockSizeContainment() &&
         !IsResumingLayout(content_break_token.get())) {
       minimum_border_box_block_size_ =
           intrinsic_block_size_ + padding_.BlockSum() + borders_.block_end;
@@ -386,8 +386,8 @@
     const MinMaxSizesInput& input) const {
   MinMaxSizesResult result;
 
-  bool apply_size_containment = Node().ShouldApplySizeContainment();
-  if (apply_size_containment) {
+  bool has_inline_size_containment = Node().ShouldApplyInlineSizeContainment();
+  if (has_inline_size_containment) {
     // Size containment does not consider the legend for sizing.
     base::Optional<MinMaxSizesResult> result_without_children =
         CalculateMinMaxSizesIgnoringChildren(Node(), BorderScrollbarPadding());
@@ -406,7 +406,7 @@
   result.sizes += ComputePadding(ConstraintSpace(), Style()).InlineSum();
 
   // Size containment does not consider the content for sizing.
-  if (!apply_size_containment) {
+  if (!has_inline_size_containment) {
     if (NGBlockNode content = Node().GetFieldsetContent()) {
       MinMaxSizesResult content_result =
           ComputeMinAndMaxContentContribution(Style(), content, input);
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc b/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc
index 53cc85860..0d82846 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc
@@ -176,6 +176,7 @@
       *computed_block_size = default_block_size;
   }
 
+  // TODO(mstensho): Update for contain:inline-size / contain:block-size.
   if (ShouldApplySizeContainment()) {
     if (!*computed_inline_size)
       *computed_inline_size = LayoutUnit();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h b/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h
index 1c8f335b..d508396 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h
@@ -241,6 +241,16 @@
   bool ShouldApplySizeContainment() const {
     return box_->ShouldApplySizeContainment();
   }
+  // Return true if we should apply at least inline-size containment
+  // (i.e. "contain" is "size" or "inline-size").
+  bool ShouldApplyInlineSizeContainment() const {
+    return box_->ShouldApplyInlineSizeContainment();
+  }
+  // Return true if we should apply at least block-size containment
+  // (i.e. "contain" is "size" or "block-size").
+  bool ShouldApplyBlockSizeContainment() const {
+    return box_->ShouldApplyBlockSizeContainment();
+  }
 
   bool IsContainerForContainerQueries() const {
     return box_->IsContainerForContainerQueries();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
index 6d4ceae5..8d6af8d 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
@@ -1518,7 +1518,7 @@
 
   // If we have size containment, we ignore child contributions to intrinsic
   // sizing.
-  if (node.ShouldApplySizeContainment())
+  if (node.ShouldApplyBlockSizeContainment())
     return border_scrollbar_padding.BlockSum();
   return current_intrinsic_block_size;
 }
@@ -1550,7 +1550,7 @@
 
   // Size contained elements don't consider children for intrinsic sizing.
   // Also, if we don't have children, we can determine the size immediately.
-  if (node.ShouldApplySizeContainment() || !node.FirstChild()) {
+  if (node.ShouldApplyInlineSizeContainment() || !node.FirstChild()) {
     return MinMaxSizesResult{sizes,
                              /* depends_on_percentage_block_size */ false};
   }
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_ellipse.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_ellipse.cc
index 310ca71..fd41379 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_ellipse.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_ellipse.cc
@@ -136,8 +136,7 @@
 
 bool LayoutSVGEllipse::HasContinuousStroke() const {
   NOT_DESTROYED();
-  const SVGComputedStyle& svg_style = StyleRef().SvgStyle();
-  return svg_style.StrokeDashArray()->data.IsEmpty();
+  return !StyleRef().HasDashArray();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_path.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_path.cc
index 753af9d..50755df 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_path.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_path.cc
@@ -82,18 +82,18 @@
   NOT_DESTROYED();
   marker_positions_.clear();
 
-  const SVGComputedStyle& svg_style = StyleRef().SvgStyle();
-  if (!svg_style.HasMarkers())
+  const ComputedStyle& style = StyleRef();
+  if (!style.HasMarkers())
     return;
   SVGElementResourceClient* client = SVGResources::GetClient(*this);
   if (!client)
     return;
   auto* marker_start = GetSVGResourceAsType<LayoutSVGResourceMarker>(
-      *client, svg_style.MarkerStartResource());
+      *client, style.MarkerStartResource());
   auto* marker_mid = GetSVGResourceAsType<LayoutSVGResourceMarker>(
-      *client, svg_style.MarkerMidResource());
+      *client, style.MarkerMidResource());
   auto* marker_end = GetSVGResourceAsType<LayoutSVGResourceMarker>(
-      *client, svg_style.MarkerEndResource());
+      *client, style.MarkerEndResource());
   if (!(marker_start || marker_mid || marker_end))
     return;
 
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_rect.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_rect.cc
index fa04315..2d31b55 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_rect.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_rect.cc
@@ -123,7 +123,7 @@
 // Returns true if the stroke is continuous and definitely uses miter joins.
 bool LayoutSVGRect::DefinitelyHasSimpleStroke() const {
   NOT_DESTROYED();
-  const SVGComputedStyle& svg_style = StyleRef().SvgStyle();
+  const ComputedStyle& style = StyleRef();
 
   // The four angles of a rect are 90 degrees. Using the formula at:
   // http://www.w3.org/TR/SVG/painting.html#StrokeMiterlimitProperty
@@ -141,9 +141,8 @@
   // miterlimits, the join style used might not be correct (e.g. a miterlimit
   // of 1.4142135 should result in bevel joins, but may be drawn using miter
   // joins).
-  return svg_style.StrokeDashArray()->data.IsEmpty() &&
-         svg_style.JoinStyle() == kMiterJoin &&
-         svg_style.StrokeMiterLimit() >= 1.5;
+  return !style.HasDashArray() && style.JoinStyle() == kMiterJoin &&
+         style.StrokeMiterLimit() >= 1.5;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.cc
index 7be59302..1cb3a737 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.cc
@@ -103,18 +103,17 @@
             DynamicTo<ReferenceFilterOperation>(*operation))
       resources.push_back(reference_operation->Resource());
   }
-  const SVGComputedStyle& svg_style = style.SvgStyle();
   if (auto* masker = style.MaskerResource())
     resources.push_back(masker->Resource());
-  if (auto* marker = svg_style.MarkerStartResource())
+  if (auto* marker = style.MarkerStartResource())
     resources.push_back(marker->Resource());
-  if (auto* marker = svg_style.MarkerMidResource())
+  if (auto* marker = style.MarkerMidResource())
     resources.push_back(marker->Resource());
-  if (auto* marker = svg_style.MarkerEndResource())
+  if (auto* marker = style.MarkerEndResource())
     resources.push_back(marker->Resource());
-  if (auto* paint_resource = svg_style.FillPaint().Resource())
+  if (auto* paint_resource = style.FillPaint().Resource())
     resources.push_back(paint_resource->Resource());
-  if (auto* paint_resource = svg_style.StrokePaint().Resource())
+  if (auto* paint_resource = style.StrokePaint().Resource())
     resources.push_back(paint_resource->Resource());
   return resources;
 }
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc
index 671dc58..4e3d2e5 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc
@@ -76,10 +76,9 @@
   // couple of additional properties that *won't* cause a layout, but are
   // significant enough to require invalidating the cache.
   if (!diff.NeedsFullLayout() && old_style && stroke_path_cache_) {
-    const auto& old_svg_style = old_style->SvgStyle();
-    const auto& svg_style = StyleRef().SvgStyle();
-    if (old_svg_style.StrokeDashOffset() != svg_style.StrokeDashOffset() ||
-        *old_svg_style.StrokeDashArray() != *svg_style.StrokeDashArray()) {
+    const ComputedStyle& style = StyleRef();
+    if (old_style->StrokeDashOffset() != style.StrokeDashOffset() ||
+        *old_style->StrokeDashArray() != *style.StrokeDashArray()) {
       stroke_path_cache_.reset();
     }
   }
@@ -113,7 +112,7 @@
 
 float LayoutSVGShape::DashScaleFactor() const {
   NOT_DESTROYED();
-  if (StyleRef().SvgStyle().StrokeDashArray()->data.IsEmpty())
+  if (!StyleRef().HasDashArray())
     return 1;
   return To<SVGGeometryElement>(*GetElement()).PathLengthScaleFactor();
 }
@@ -135,11 +134,11 @@
 
 namespace {
 
-bool HasMiterJoinStyle(const SVGComputedStyle& svg_style) {
-  return svg_style.JoinStyle() == kMiterJoin;
+bool HasMiterJoinStyle(const ComputedStyle& style) {
+  return style.JoinStyle() == kMiterJoin;
 }
-bool HasSquareCapStyle(const SVGComputedStyle& svg_style) {
-  return svg_style.CapStyle() == kSquareCap;
+bool HasSquareCapStyle(const ComputedStyle& style) {
+  return style.CapStyle() == kSquareCap;
 }
 
 }  // namespace
@@ -159,14 +158,14 @@
 
   float delta = stroke_width / 2;
   if (geometry_class_ != kSimple) {
-    const SVGComputedStyle& svg_style = StyleRef().SvgStyle();
-    if (geometry_class_ != kNoMiters && HasMiterJoinStyle(svg_style)) {
-      const float miter = svg_style.StrokeMiterLimit();
-      if (miter < M_SQRT2 && HasSquareCapStyle(svg_style))
+    const ComputedStyle& style = StyleRef();
+    if (geometry_class_ != kNoMiters && HasMiterJoinStyle(style)) {
+      const float miter = style.StrokeMiterLimit();
+      if (miter < M_SQRT2 && HasSquareCapStyle(style))
         delta *= M_SQRT2;
       else
         delta *= std::max(miter, 1.0f);
-    } else if (HasSquareCapStyle(svg_style)) {
+    } else if (HasSquareCapStyle(style)) {
       delta *= M_SQRT2;
     }
   }
@@ -176,7 +175,7 @@
 
 FloatRect LayoutSVGShape::HitTestStrokeBoundingBox() const {
   NOT_DESTROYED();
-  if (StyleRef().SvgStyle().HasStroke())
+  if (StyleRef().HasStroke())
     return stroke_bounding_box_;
   return ApproximateStrokeBoundingBox(fill_bounding_box_);
 }
@@ -246,8 +245,7 @@
   if (!fill_bounding_box_.Contains(location.TransformedPoint()))
     return false;
 
-  if (requires_fill &&
-      !HasPaintServer(*this, StyleRef().SvgStyle().FillPaint()))
+  if (requires_fill && !HasPaintServer(*this, StyleRef().FillPaint()))
     return false;
 
   return ShapeDependentFillContains(location, fill_rule);
@@ -257,14 +255,14 @@
                                     bool requires_stroke) {
   NOT_DESTROYED();
   // "A zero value causes no stroke to be painted."
-  if (StyleRef().SvgStyle().StrokeWidth().IsZero())
+  if (StyleRef().StrokeWidth().IsZero())
     return false;
 
   if (requires_stroke) {
     if (!StrokeBoundingBox().Contains(location.TransformedPoint()))
       return false;
 
-    if (!HasPaintServer(*this, StyleRef().SvgStyle().StrokePaint()))
+    if (!HasPaintServer(*this, StyleRef().StrokePaint()))
       return false;
   } else {
     if (!HitTestStrokeBoundingBox().Contains(location.TransformedPoint()))
@@ -424,16 +422,14 @@
 
   // TODO(chrishtr): support rect-based intersections in the cases below.
   const ComputedStyle& style = StyleRef();
-  const SVGComputedStyle& svg_style = style.SvgStyle();
   if (hit_rules.can_hit_stroke &&
-      (svg_style.HasStroke() || !hit_rules.require_stroke) &&
+      (style.HasStroke() || !hit_rules.require_stroke) &&
       StrokeContains(local_location, hit_rules.require_stroke))
     return true;
   WindRule fill_rule = style.FillRule();
   if (request.SvgClipContent())
     fill_rule = style.ClipRule();
-  if (hit_rules.can_hit_fill &&
-      (svg_style.HasFill() || !hit_rules.require_fill) &&
+  if (hit_rules.can_hit_fill && (style.HasFill() || !hit_rules.require_fill) &&
       FillContains(local_location, hit_rules.require_fill, fill_rule))
     return true;
   return false;
@@ -441,7 +437,7 @@
 
 FloatRect LayoutSVGShape::CalculateStrokeBoundingBox() const {
   NOT_DESTROYED();
-  if (!StyleRef().SvgStyle().HasStroke() || IsShapeEmpty())
+  if (!StyleRef().HasStroke() || IsShapeEmpty())
     return fill_bounding_box_;
   if (HasNonScalingStroke())
     return CalculateNonScalingStrokeBoundingBox();
@@ -451,7 +447,7 @@
 FloatRect LayoutSVGShape::CalculateNonScalingStrokeBoundingBox() const {
   NOT_DESTROYED();
   DCHECK(path_);
-  DCHECK(StyleRef().SvgStyle().HasStroke());
+  DCHECK(StyleRef().HasStroke());
   DCHECK(HasNonScalingStroke());
 
   FloatRect stroke_bounding_box = fill_bounding_box_;
@@ -470,7 +466,7 @@
 float LayoutSVGShape::StrokeWidth() const {
   NOT_DESTROYED();
   SVGLengthContext length_context(GetElement());
-  return length_context.ValueForLength(StyleRef().SvgStyle().StrokeWidth());
+  return length_context.ValueForLength(StyleRef().StrokeWidth());
 }
 
 float LayoutSVGShape::StrokeWidthForMarkerUnits() const {
@@ -499,8 +495,9 @@
 RasterEffectOutset LayoutSVGShape::VisualRectOutsetForRasterEffects() const {
   NOT_DESTROYED();
   // Account for raster expansions due to SVG stroke hairline raster effects.
-  if (StyleRef().SvgStyle().HasVisibleStroke()) {
-    if (StyleRef().SvgStyle().CapStyle() != kButtCap)
+  const ComputedStyle& style = StyleRef();
+  if (style.HasVisibleStroke()) {
+    if (style.CapStyle() != kButtCap)
       return RasterEffectOutset::kWholePixel;
     return RasterEffectOutset::kHalfPixel;
   }
diff --git a/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.cc b/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.cc
index c6d0e3a..7c21b485 100644
--- a/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.cc
+++ b/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.cc
@@ -313,9 +313,9 @@
     return false;
   if (hit_rules.can_hit_bounding_box ||
       (hit_rules.can_hit_stroke &&
-       (style.SvgStyle().HasStroke() || !hit_rules.require_stroke)) ||
+       (style.HasStroke() || !hit_rules.require_stroke)) ||
       (hit_rules.can_hit_fill &&
-       (style.SvgStyle().HasFill() || !hit_rules.require_fill))) {
+       (style.HasFill() || !hit_rules.require_fill))) {
     // Currently SVGInlineTextBox doesn't flip in blocks direction.
     PhysicalRect rect{PhysicalOffset(Location()), PhysicalSize(Size())};
     rect.Move(accumulated_offset);
diff --git a/third_party/blink/renderer/core/layout/svg/svg_content_container.cc b/third_party/blink/renderer/core/layout/svg/svg_content_container.cc
index 9decb97f..642e615 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_content_container.cc
+++ b/third_party/blink/renderer/core/layout/svg/svg_content_container.cc
@@ -19,15 +19,15 @@
   SVGElementResourceClient* client = SVGResources::GetClient(layout_object);
   if (!client)
     return;
-  const SVGComputedStyle& svg_style = layout_object.StyleRef().SvgStyle();
+  const ComputedStyle& style = layout_object.StyleRef();
   if (auto* marker = GetSVGResourceAsType<LayoutSVGResourceMarker>(
-          *client, svg_style.MarkerStartResource()))
+          *client, style.MarkerStartResource()))
     marker->LayoutIfNeeded();
   if (auto* marker = GetSVGResourceAsType<LayoutSVGResourceMarker>(
-          *client, svg_style.MarkerMidResource()))
+          *client, style.MarkerMidResource()))
     marker->LayoutIfNeeded();
   if (auto* marker = GetSVGResourceAsType<LayoutSVGResourceMarker>(
-          *client, svg_style.MarkerEndResource()))
+          *client, style.MarkerEndResource()))
     marker->LayoutIfNeeded();
 }
 
diff --git a/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc b/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc
index a91cc9fc5..55a9985 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc
+++ b/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc
@@ -279,13 +279,13 @@
     const FloatRect& text_bounds) {
   DCHECK(layout_object.IsSVGText() || layout_object.IsSVGInline());
   FloatRect bounds = text_bounds;
-  const SVGComputedStyle& svg_style = layout_object.StyleRef().SvgStyle();
-  if (svg_style.HasStroke()) {
+  const ComputedStyle& style = layout_object.StyleRef();
+  if (style.HasStroke()) {
     SVGLengthContext length_context(To<SVGElement>(layout_object.GetNode()));
     // TODO(fs): This approximation doesn't appear to be conservative enough
     // since while text (usually?) won't have caps it could have joins and thus
     // miters.
-    bounds.Inflate(length_context.ValueForLength(svg_style.StrokeWidth()));
+    bounds.Inflate(length_context.ValueForLength(style.StrokeWidth()));
   }
   return bounds;
 }
@@ -336,19 +336,16 @@
   DCHECK(object.GetNode());
   DCHECK(object.GetNode()->IsSVGElement());
 
-  const SVGComputedStyle& svg_style = style.SvgStyle();
-
   SVGLengthContext length_context(To<SVGElement>(object.GetNode()));
-  stroke_data.SetThickness(
-      length_context.ValueForLength(svg_style.StrokeWidth()));
-  stroke_data.SetLineCap(svg_style.CapStyle());
-  stroke_data.SetLineJoin(svg_style.JoinStyle());
-  stroke_data.SetMiterLimit(svg_style.StrokeMiterLimit());
+  stroke_data.SetThickness(length_context.ValueForLength(style.StrokeWidth()));
+  stroke_data.SetLineCap(style.CapStyle());
+  stroke_data.SetLineJoin(style.JoinStyle());
+  stroke_data.SetMiterLimit(style.StrokeMiterLimit());
 
   DashArray dash_array =
-      ResolveSVGDashArray(*svg_style.StrokeDashArray(), style, length_context);
+      ResolveSVGDashArray(*style.StrokeDashArray(), style, length_context);
   float dash_offset =
-      length_context.ValueForLength(svg_style.StrokeDashOffset(), style);
+      length_context.ValueForLength(style.StrokeDashOffset(), style);
   // Apply scaling from 'pathLength'.
   if (dash_scale_factor != 1) {
     DCHECK_GE(dash_scale_factor, 0);
diff --git a/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc b/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc
index d2e64cc..31a851d 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc
+++ b/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc
@@ -270,7 +270,6 @@
 
 static void WriteStyle(WTF::TextStream& ts, const LayoutObject& object) {
   const ComputedStyle& style = object.StyleRef();
-  const SVGComputedStyle& svg_style = style.SvgStyle();
 
   if (!object.LocalSVGTransform().IsIdentity())
     WriteNameValuePair(ts, "transform", object.LocalSVGTransform());
@@ -280,23 +279,22 @@
   WriteIfNotDefault(ts, "opacity", style.Opacity(),
                     ComputedStyleInitialValues::InitialOpacity());
   if (object.IsSVGShape()) {
-    if (WriteSVGPaint(ts, object, svg_style.StrokePaint(),
-                      GetCSSPropertyStroke(), "stroke")) {
+    if (WriteSVGPaint(ts, object, style.StrokePaint(), GetCSSPropertyStroke(),
+                      "stroke")) {
       const LayoutSVGShape& shape = static_cast<const LayoutSVGShape&>(object);
       DCHECK(shape.GetElement());
       SVGLengthContext length_context(shape.GetElement());
       double dash_offset =
-          length_context.ValueForLength(svg_style.StrokeDashOffset(), style);
-      double stroke_width =
-          length_context.ValueForLength(svg_style.StrokeWidth());
+          length_context.ValueForLength(style.StrokeDashOffset(), style);
+      double stroke_width = length_context.ValueForLength(style.StrokeWidth());
       DashArray dash_array = SVGLayoutSupport::ResolveSVGDashArray(
-          *svg_style.StrokeDashArray(), style, length_context);
+          *style.StrokeDashArray(), style, length_context);
 
-      WriteIfNotDefault(ts, "opacity", svg_style.StrokeOpacity(), 1.0f);
+      WriteIfNotDefault(ts, "opacity", style.StrokeOpacity(), 1.0f);
       WriteIfNotDefault(ts, "stroke width", stroke_width, 1.0);
-      WriteIfNotDefault(ts, "miter limit", svg_style.StrokeMiterLimit(), 4.0f);
-      WriteIfNotDefault(ts, "line cap", svg_style.CapStyle(), kButtCap);
-      WriteIfNotDefault(ts, "line join", svg_style.JoinStyle(), kMiterJoin);
+      WriteIfNotDefault(ts, "miter limit", style.StrokeMiterLimit(), 4.0f);
+      WriteIfNotDefault(ts, "line cap", style.CapStyle(), kButtCap);
+      WriteIfNotDefault(ts, "line join", style.JoinStyle(), kMiterJoin);
       WriteIfNotDefault(ts, "dash offset", dash_offset, 0.0);
       if (!dash_array.IsEmpty())
         WriteNameValuePair(ts, "dash array", dash_array);
@@ -304,9 +302,9 @@
       ts << "}]";
     }
 
-    if (WriteSVGPaint(ts, object, svg_style.FillPaint(), GetCSSPropertyFill(),
+    if (WriteSVGPaint(ts, object, style.FillPaint(), GetCSSPropertyFill(),
                       "fill")) {
-      WriteIfNotDefault(ts, "opacity", svg_style.FillOpacity(), 1.0f);
+      WriteIfNotDefault(ts, "opacity", style.FillOpacity(), 1.0f);
       WriteIfNotDefault(ts, "fill rule", style.FillRule(), RULE_NONZERO);
       ts << "}]";
     }
@@ -314,11 +312,11 @@
   }
 
   TreeScope& tree_scope = object.GetDocument();
-  WriteSVGResourceIfNotNull(ts, "start marker", svg_style.MarkerStartResource(),
+  WriteSVGResourceIfNotNull(ts, "start marker", style.MarkerStartResource(),
                             tree_scope);
-  WriteSVGResourceIfNotNull(ts, "middle marker", svg_style.MarkerMidResource(),
+  WriteSVGResourceIfNotNull(ts, "middle marker", style.MarkerMidResource(),
                             tree_scope);
-  WriteSVGResourceIfNotNull(ts, "end marker", svg_style.MarkerEndResource(),
+  WriteSVGResourceIfNotNull(ts, "end marker", style.MarkerEndResource(),
                             tree_scope);
 }
 
diff --git a/third_party/blink/renderer/core/layout/svg/svg_resources.cc b/third_party/blink/renderer/core/layout/svg/svg_resources.cc
index feb1e540..31f880f 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_resources.cc
+++ b/third_party/blink/renderer/core/layout/svg/svg_resources.cc
@@ -110,10 +110,9 @@
                                 const ComputedStyle* old_style,
                                 const ComputedStyle& style) {
   const bool had_client = element.GetSVGResourceClient();
-  const SVGComputedStyle& svg_style = style.SvgStyle();
-  if (StyleSVGResource* paint_resource = svg_style.FillPaint().Resource())
+  if (StyleSVGResource* paint_resource = style.FillPaint().Resource())
     paint_resource->AddClient(element.EnsureSVGResourceClient());
-  if (StyleSVGResource* paint_resource = svg_style.StrokePaint().Resource())
+  if (StyleSVGResource* paint_resource = style.StrokePaint().Resource())
     paint_resource->AddClient(element.EnsureSVGResourceClient());
   if (had_client)
     ClearPaints(element, old_style);
@@ -126,10 +125,9 @@
   SVGResourceClient* client = element.GetSVGResourceClient();
   if (!client)
     return;
-  const SVGComputedStyle& old_svg_style = style->SvgStyle();
-  if (StyleSVGResource* paint_resource = old_svg_style.FillPaint().Resource())
+  if (StyleSVGResource* paint_resource = style->FillPaint().Resource())
     paint_resource->RemoveClient(*client);
-  if (StyleSVGResource* paint_resource = old_svg_style.StrokePaint().Resource())
+  if (StyleSVGResource* paint_resource = style->StrokePaint().Resource())
     paint_resource->RemoveClient(*client);
 }
 
@@ -137,12 +135,11 @@
                                  const ComputedStyle* old_style,
                                  const ComputedStyle& style) {
   const bool had_client = element.GetSVGResourceClient();
-  const SVGComputedStyle& svg_style = style.SvgStyle();
-  if (StyleSVGResource* marker_resource = svg_style.MarkerStartResource())
+  if (StyleSVGResource* marker_resource = style.MarkerStartResource())
     marker_resource->AddClient(element.EnsureSVGResourceClient());
-  if (StyleSVGResource* marker_resource = svg_style.MarkerMidResource())
+  if (StyleSVGResource* marker_resource = style.MarkerMidResource())
     marker_resource->AddClient(element.EnsureSVGResourceClient());
-  if (StyleSVGResource* marker_resource = svg_style.MarkerEndResource())
+  if (StyleSVGResource* marker_resource = style.MarkerEndResource())
     marker_resource->AddClient(element.EnsureSVGResourceClient());
   if (had_client)
     ClearMarkers(element, old_style);
@@ -155,12 +152,11 @@
   SVGResourceClient* client = element.GetSVGResourceClient();
   if (!client)
     return;
-  const SVGComputedStyle& old_svg_style = style->SvgStyle();
-  if (StyleSVGResource* marker_resource = old_svg_style.MarkerStartResource())
+  if (StyleSVGResource* marker_resource = style->MarkerStartResource())
     marker_resource->RemoveClient(*client);
-  if (StyleSVGResource* marker_resource = old_svg_style.MarkerMidResource())
+  if (StyleSVGResource* marker_resource = style->MarkerMidResource())
     marker_resource->RemoveClient(*client);
-  if (StyleSVGResource* marker_resource = old_svg_style.MarkerEndResource())
+  if (StyleSVGResource* marker_resource = style->MarkerEndResource())
     marker_resource->RemoveClient(*client);
 }
 
@@ -241,9 +237,8 @@
     return;
   }
 
-  const SVGComputedStyle& svg_style = style.SvgStyle();
-  if (ContainsResource(svg_style.FillPaint().Resource(), resource) ||
-      ContainsResource(svg_style.StrokePaint().Resource(), resource)) {
+  if (ContainsResource(style.FillPaint().Resource(), resource) ||
+      ContainsResource(style.StrokePaint().Resource(), resource)) {
     // Since LayoutSVGInlineTexts don't have SVGResources (they use their
     // parent's), they will not be notified of changes to paint servers. So
     // if the client is one that could have a LayoutSVGInlineText use a
@@ -254,9 +249,9 @@
   }
 
   bool needs_layout = false;
-  if (ContainsResource(svg_style.MarkerStartResource(), resource) ||
-      ContainsResource(svg_style.MarkerMidResource(), resource) ||
-      ContainsResource(svg_style.MarkerEndResource(), resource)) {
+  if (ContainsResource(style.MarkerStartResource(), resource) ||
+      ContainsResource(style.MarkerMidResource(), resource) ||
+      ContainsResource(style.MarkerEndResource(), resource)) {
     needs_layout = true;
     layout_object->SetNeedsBoundariesUpdate();
   }
@@ -396,14 +391,14 @@
   if (!client)
     return;
   bool needs_invalidation = false;
-  const SVGComputedStyle& svg_style = object_.StyleRef().SvgStyle();
+  const ComputedStyle& style = object_.StyleRef();
   if (auto* fill = GetSVGResourceAsType<LayoutSVGResourcePaintServer>(
-          *client, svg_style.FillPaint().Resource())) {
+          *client, style.FillPaint().Resource())) {
     fill->RemoveClientFromCache(*client);
     needs_invalidation = true;
   }
   if (auto* stroke = GetSVGResourceAsType<LayoutSVGResourcePaintServer>(
-          *client, svg_style.StrokePaint().Resource())) {
+          *client, style.StrokePaint().Resource())) {
     stroke->RemoveClientFromCache(*client);
     needs_invalidation = true;
   }
diff --git a/third_party/blink/renderer/core/paint/highlight_painting_utils.cc b/third_party/blink/renderer/core/paint/highlight_painting_utils.cc
index 03bc89e..762a9a6 100644
--- a/third_party/blink/renderer/core/paint/highlight_painting_utils.cc
+++ b/third_party/blink/renderer/core/paint/highlight_painting_utils.cc
@@ -108,7 +108,7 @@
                        style.UsedColorScheme());
     case kPseudoIdTargetText:
       if (RuntimeEnabledFeatures::TextFragmentColorChangeEnabled())
-        return Color(shared_highlighting::kFragmentTextBackgroundColor);
+        return Color(shared_highlighting::kFragmentTextBackgroundColorARGB);
 
       return LayoutTheme::GetTheme().PlatformTextSearchHighlightColor(
           false /* active match */, document.InForcedColorsMode(),
diff --git a/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc b/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc
index 888f677c..bf5b747 100644
--- a/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc
@@ -121,10 +121,9 @@
     const PaintInfo& paint_info,
     LayoutObject& parent_layout_object) {
   const ComputedStyle& style = parent_layout_object.StyleRef();
-  const SVGComputedStyle& svg_style = style.SvgStyle();
 
-  bool has_fill = svg_style.HasFill();
-  bool has_visible_stroke = svg_style.HasVisibleStroke();
+  bool has_fill = style.HasFill();
+  bool has_visible_stroke = style.HasVisibleStroke();
 
   const ComputedStyle* selection_style = &style;
   bool should_paint_selection = ShouldPaintSelection(paint_info);
@@ -132,12 +131,10 @@
     selection_style =
         parent_layout_object.GetCachedPseudoElementStyle(kPseudoIdSelection);
     if (selection_style) {
-      const SVGComputedStyle& svg_selection_style = selection_style->SvgStyle();
-
       if (!has_fill)
-        has_fill = svg_selection_style.HasFill();
+        has_fill = selection_style->HasFill();
       if (!has_visible_stroke)
-        has_visible_stroke = svg_selection_style.HasVisibleStroke();
+        has_visible_stroke = selection_style->HasVisibleStroke();
     } else {
       selection_style = &style;
     }
@@ -342,12 +339,10 @@
       FloatRect(decoration_origin,
                 FloatSize(fragment.width, thickness / scaling_factor)));
 
-  const SVGComputedStyle& svg_decoration_style = decoration_style.SvgStyle();
-
   for (int i = 0; i < 3; i++) {
     switch (decoration_style.PaintOrderType(i)) {
       case PT_FILL:
-        if (svg_decoration_style.HasFill()) {
+        if (decoration_style.HasFill()) {
           PaintFlags fill_flags;
           if (!SVGObjectPainter(*decoration_layout_object)
                    .PreparePaint(paint_info, decoration_style, kApplyToFillMode,
@@ -358,7 +353,7 @@
         }
         break;
       case PT_STROKE:
-        if (svg_decoration_style.HasVisibleStroke()) {
+        if (decoration_style.HasVisibleStroke()) {
           PaintFlags stroke_flags;
           if (!SVGObjectPainter(*decoration_layout_object)
                    .PreparePaint(paint_info, decoration_style,
diff --git a/third_party/blink/renderer/core/paint/svg_object_painter.cc b/third_party/blink/renderer/core/paint/svg_object_painter.cc
index 4afa4ed..acd0c2d 100644
--- a/third_party/blink/renderer/core/paint/svg_object_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_object_painter.cc
@@ -71,11 +71,10 @@
   }
 
   const bool apply_to_fill = resource_mode == kApplyToFillMode;
-  const SVGComputedStyle& svg_style = style.SvgStyle();
   const SVGPaint& paint =
-      apply_to_fill ? svg_style.FillPaint() : svg_style.StrokePaint();
+      apply_to_fill ? style.FillPaint() : style.StrokePaint();
   const float alpha =
-      apply_to_fill ? svg_style.FillOpacity() : svg_style.StrokeOpacity();
+      apply_to_fill ? style.FillOpacity() : style.StrokeOpacity();
   if (paint.HasUrl()) {
     if (ApplyPaintResource(paint, additional_paint_server_transform, flags)) {
       flags.setColor(ScaleAlpha(SK_ColorBLACK, alpha));
diff --git a/third_party/blink/renderer/core/paint/svg_shape_painter.cc b/third_party/blink/renderer/core/paint/svg_shape_painter.cc
index dc3a1756..3f7bede 100644
--- a/third_party/blink/renderer/core/paint/svg_shape_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_shape_painter.cc
@@ -64,7 +64,6 @@
       SVGDrawingRecorder recorder(paint_info.context, layout_svg_shape_,
                                   paint_info.phase);
       const ComputedStyle& style = layout_svg_shape_.StyleRef();
-      const SVGComputedStyle& svg_style = style.SvgStyle();
 
       bool should_anti_alias = style.ShapeRendering() != SR_CRISPEDGES &&
                                style.ShapeRendering() != SR_OPTIMIZESPEED;
@@ -83,7 +82,7 @@
             break;
           }
           case PT_STROKE:
-            if (svg_style.HasVisibleStroke()) {
+            if (style.HasVisibleStroke()) {
               GraphicsContextStateSaver state_saver(paint_info.context, false);
               base::Optional<AffineTransform> non_scaling_transform;
 
@@ -169,7 +168,7 @@
 
 void SVGShapePainter::StrokeShape(GraphicsContext& context,
                                   const PaintFlags& flags) {
-  DCHECK(layout_svg_shape_.StyleRef().SvgStyle().HasVisibleStroke());
+  DCHECK(layout_svg_shape_.StyleRef().HasVisibleStroke());
 
   switch (layout_svg_shape_.GeometryCodePath()) {
     case kRectGeometryFastPath:
@@ -199,13 +198,13 @@
   if (!marker_positions || marker_positions->IsEmpty())
     return;
   SVGResourceClient* client = SVGResources::GetClient(layout_svg_shape_);
-  const SVGComputedStyle& svg_style = layout_svg_shape_.StyleRef().SvgStyle();
+  const ComputedStyle& style = layout_svg_shape_.StyleRef();
   auto* marker_start = GetSVGResourceAsType<LayoutSVGResourceMarker>(
-      *client, svg_style.MarkerStartResource());
+      *client, style.MarkerStartResource());
   auto* marker_mid = GetSVGResourceAsType<LayoutSVGResourceMarker>(
-      *client, svg_style.MarkerMidResource());
+      *client, style.MarkerMidResource());
   auto* marker_end = GetSVGResourceAsType<LayoutSVGResourceMarker>(
-      *client, svg_style.MarkerEndResource());
+      *client, style.MarkerEndResource());
   if (!marker_start && !marker_mid && !marker_end)
     return;
 
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index d4a11ff..79af248 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -1164,6 +1164,18 @@
   }
   WindRule FillRule() const { return SvgStyle().FillRule(); }
 
+  const SVGPaint& FillPaint() const { return SvgStyle().FillPaint(); }
+  const SVGPaint& InternalVisitedFillPaint() const {
+    return SvgStyle().InternalVisitedFillPaint();
+  }
+
+  // fill helpers
+  bool HasFill() const { return !FillPaint().IsNone(); }
+  bool IsFillColorCurrentColor() const {
+    return FillPaint().HasCurrentColor() ||
+           InternalVisitedFillPaint().HasCurrentColor();
+  }
+
   // fill-opacity
   float FillOpacity() const { return SvgStyle().FillOpacity(); }
   void SetFillOpacity(float f) { AccessSVGStyle().SetFillOpacity(f); }
@@ -1194,14 +1206,43 @@
   float StopOpacity() const { return SvgStyle().StopOpacity(); }
   void SetStopOpacity(float f) { AccessSVGStyle().SetStopOpacity(f); }
 
+  // marker-* helpers
+  StyleSVGResource* MarkerStartResource() const {
+    return SvgStyle().MarkerStartResource();
+  }
+  StyleSVGResource* MarkerMidResource() const {
+    return SvgStyle().MarkerMidResource();
+  }
+  StyleSVGResource* MarkerEndResource() const {
+    return SvgStyle().MarkerEndResource();
+  }
+  bool HasMarkers() const {
+    return MarkerStartResource() || MarkerMidResource() || MarkerEndResource();
+  }
+
   // paint-order helper
   EPaintOrder PaintOrder() const { return SvgStyle().PaintOrder(); }
   EPaintOrderType PaintOrderType(unsigned index) const;
 
   EShapeRendering ShapeRendering() const { return SvgStyle().ShapeRendering(); }
 
+  // stroke helpers
+  bool HasStroke() const { return !StrokePaint().IsNone(); }
+  bool HasVisibleStroke() const {
+    return HasStroke() && !StrokeWidth().IsZero();
+  }
+  bool IsStrokeColorCurrentColor() const {
+    return StrokePaint().HasCurrentColor() ||
+           InternalVisitedStrokePaint().HasCurrentColor();
+  }
+  const SVGPaint& StrokePaint() const { return SvgStyle().StrokePaint(); }
+  const SVGPaint& InternalVisitedStrokePaint() const {
+    return SvgStyle().InternalVisitedStrokePaint();
+  }
+
   // stroke-dasharray
   SVGDashArray* StrokeDashArray() const { return SvgStyle().StrokeDashArray(); }
+  bool HasDashArray() const { return !StrokeDashArray()->data.IsEmpty(); }
   void SetStrokeDashArray(scoped_refptr<SVGDashArray> array) {
     AccessSVGStyle().SetStrokeDashArray(std::move(array));
   }
@@ -1214,6 +1255,9 @@
     AccessSVGStyle().SetStrokeDashOffset(d);
   }
 
+  LineCap CapStyle() const { return SvgStyle().CapStyle(); }
+  LineJoin JoinStyle() const { return SvgStyle().JoinStyle(); }
+
   // stroke-miterlimit
   float StrokeMiterLimit() const { return SvgStyle().StrokeMiterLimit(); }
   void SetStrokeMiterLimit(float f) { AccessSVGStyle().SetStrokeMiterLimit(f); }
diff --git a/third_party/blink/renderer/core/style/svg_computed_style.h b/third_party/blink/renderer/core/style/svg_computed_style.h
index 319bda6..9bd174b 100644
--- a/third_party/blink/renderer/core/style/svg_computed_style.h
+++ b/third_party/blink/renderer/core/style/svg_computed_style.h
@@ -355,26 +355,6 @@
     return stroke->visited_link_paint;
   }
 
-  bool IsFillColorCurrentColor() const {
-    return FillPaint().HasCurrentColor() ||
-           InternalVisitedFillPaint().HasCurrentColor();
-  }
-
-  bool IsStrokeColorCurrentColor() const {
-    return StrokePaint().HasCurrentColor() ||
-           InternalVisitedStrokePaint().HasCurrentColor();
-  }
-
-  // convenience
-  bool HasMarkers() const {
-    return MarkerStartResource() || MarkerMidResource() || MarkerEndResource();
-  }
-  bool HasStroke() const { return !StrokePaint().IsNone(); }
-  bool HasVisibleStroke() const {
-    return HasStroke() && !StrokeWidth().IsZero();
-  }
-  bool HasFill() const { return !FillPaint().IsNone(); }
-
  protected:
   // inherit
   struct InheritedFlags {
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.cc
index 3af77edc..7e8e88a 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.cc
@@ -4,8 +4,17 @@
 
 #include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.h"
 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
+#include "third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h"
+#include "third_party/blink/renderer/core/paint/paint_info.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/platform/fonts/font_description.h"
+#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
 
 namespace blink {
 
@@ -22,7 +31,7 @@
   block_->SetIsLayoutNGObjectForCanvasFormattedText(true);
 }
 
-CanvasFormattedText::~CanvasFormattedText() {
+void CanvasFormattedText::Dispose() {
   AllowDestroyingLayoutObjectInFinalizerScope scope;
   if (block_)
     block_->Destroy();
@@ -51,4 +60,54 @@
   return run;
 }
 
+sk_sp<PaintRecord> CanvasFormattedText::PaintFormattedText(
+    Document& document,
+    const FontDescription& font,
+    double x,
+    double y,
+    double wrap_width,
+    FloatRect& bounds) {
+  LayoutBlockFlow* block = GetLayoutBlock(document, font);
+  NGBlockNode block_node(block);
+  NGInlineNode node(block);
+  // Call IsEmptyInline to force prepare layout.
+  if (node.IsEmptyInline())
+    return nullptr;
+
+  // TODO(sushraja) Once we add support for writing mode on the canvas formatted
+  // text, fix this to be not hardcoded horizontal top to bottom.
+  NGConstraintSpaceBuilder builder(
+      WritingMode::kHorizontalTb,
+      {WritingMode::kHorizontalTb, TextDirection::kLtr},
+      /* is_new_fc */ true);
+  LayoutUnit available_logical_width(wrap_width);
+  LogicalSize available_size = {available_logical_width, kIndefiniteSize};
+  builder.SetAvailableSize(available_size);
+  NGConstraintSpace space = builder.ToConstraintSpace();
+  scoped_refptr<const NGLayoutResult> block_results =
+      block_node.Layout(space, nullptr);
+  const auto& fragment =
+      To<NGPhysicalBoxFragment>(block_results->PhysicalFragment());
+  block->RecalcInlineChildrenVisualOverflow();
+  bounds = FloatRect(block->PhysicalVisualOverflowRect());
+
+  PaintController paint_controller(PaintController::Usage::kTransient);
+  paint_controller.UpdateCurrentPaintChunkProperties(nullptr,
+                                                     PropertyTreeState::Root());
+  GraphicsContext graphics_context(paint_controller);
+  PhysicalOffset physical_offset((LayoutUnit(x)), (LayoutUnit(y)));
+  NGBoxFragmentPainter box_fragment_painter(fragment);
+  PaintInfo paint_info(graphics_context, CullRect::Infinite(),
+                       PaintPhase::kForeground, kGlobalPaintNormalPhase,
+                       kPaintLayerPaintingRenderingClipPathAsMask |
+                           kPaintLayerPaintingRenderingResourceSubtree);
+  box_fragment_painter.PaintObject(paint_info, physical_offset);
+  paint_controller.CommitNewDisplayItems();
+  paint_controller.FinishCycle();
+  sk_sp<PaintRecord> recording =
+      paint_controller.GetPaintArtifact().GetPaintRecord(
+          PropertyTreeState::Root());
+  return recording;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.h
index ff2cde2..aae868a0 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.h
@@ -26,6 +26,7 @@
 
 class MODULES_EXPORT CanvasFormattedText final : public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
+  USING_PRE_FINALIZER(CanvasFormattedText, Dispose);
 
  public:
   static CanvasFormattedText* Create(ExecutionContext* execution_context) {
@@ -34,7 +35,6 @@
   }
 
   explicit CanvasFormattedText(Document*);
-  ~CanvasFormattedText() override;
   CanvasFormattedText(const CanvasFormattedText&) = delete;
   CanvasFormattedText& operator=(const CanvasFormattedText&) = delete;
 
@@ -63,17 +63,20 @@
     return text_runs_[index];
   }
 
-  CanvasFormattedTextRun* getRunInternal(unsigned index) const {
-    if (!CheckRunsIndexBound(index, nullptr))
-      return nullptr;
-    return text_runs_[index];
-  }
-
   CanvasFormattedTextRun* appendRun(CanvasFormattedTextRun* run);
 
   LayoutBlockFlow* GetLayoutBlock(Document& document,
                                   const FontDescription& defaultFont);
 
+  sk_sp<PaintRecord> PaintFormattedText(Document& document,
+                                        const FontDescription& font,
+                                        double x,
+                                        double y,
+                                        double wrap_width,
+                                        FloatRect& bounds);
+
+  void Dispose();
+
  private:
   HeapVector<Member<CanvasFormattedTextRun>> text_runs_;
   LayoutBlockFlow* block_;
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index 484b2c2..b3ef16c3 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -56,14 +56,7 @@
 #include "third_party/blink/renderer/core/layout/hit_test_canvas_result.h"
 #include "third_party/blink/renderer/core/layout/layout_box.h"
 #include "third_party/blink/renderer/core/layout/layout_theme.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
 #include "third_party/blink/renderer/core/origin_trials/origin_trials.h"
-#include "third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h"
-#include "third_party/blink/renderer/core/paint/paint_info.h"
 #include "third_party/blink/renderer/core/scroll/scroll_alignment.h"
 #include "third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h"
 #include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_style.h"
@@ -73,9 +66,7 @@
 #include "third_party/blink/renderer/platform/fonts/font_cache.h"
 #include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h"
 #include "third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h"
-#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
-#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h"
 #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
@@ -986,50 +977,16 @@
   if (!formatted_text)
     return;
 
-  LayoutBlockFlow* block = formatted_text->GetLayoutBlock(
-      canvas()->GetDocument(), GetState().GetFontDescription());
-  NGBlockNode block_node(block);
-  NGInlineNode node(block);
-  // Call IsEmptyInline to force prepare layout.
-  if (node.IsEmptyInline())
-    return;
+  if (!GetState().HasRealizedFont())
+    setFont(font());
 
-  // TODO(sushraja) Once we add support for writing mode on the canvas formatted
-  // text, fix this to be not hardcoded horizontal top to bottom.
-  NGConstraintSpaceBuilder builder(
-      WritingMode::kHorizontalTb,
-      {WritingMode::kHorizontalTb, TextDirection::kLtr},
-      /* is_new_fc */ true);
-  LayoutUnit available_logical_width(wrap_width);
-  LogicalSize available_size = {available_logical_width, kIndefiniteSize};
-  builder.SetAvailableSize(available_size);
-  NGConstraintSpace space = builder.ToConstraintSpace();
-  scoped_refptr<const NGLayoutResult> block_results =
-      block_node.Layout(space, nullptr);
-  const auto& fragment =
-      To<NGPhysicalBoxFragment>(block_results->PhysicalFragment());
-  block->RecalcInlineChildrenVisualOverflow();
-  PhysicalRect bounds = block->PhysicalVisualOverflowRect();
-
-  PaintController paint_controller(PaintController::Usage::kTransient);
-  paint_controller.UpdateCurrentPaintChunkProperties(nullptr,
-                                                     PropertyTreeState::Root());
-  GraphicsContext graphics_context(paint_controller);
-  PhysicalOffset physical_offset((LayoutUnit(x)), (LayoutUnit(y)));
-  NGBoxFragmentPainter box_fragment_painter(fragment);
-  PaintInfo paint_info(graphics_context, CullRect::Infinite(),
-                       PaintPhase::kForeground, kGlobalPaintNormalPhase,
-                       kPaintLayerPaintingRenderingClipPathAsMask |
-                           kPaintLayerPaintingRenderingResourceSubtree);
-  box_fragment_painter.PaintObject(paint_info, physical_offset);
-  paint_controller.CommitNewDisplayItems();
-  paint_controller.FinishCycle();
-  sk_sp<PaintRecord> recording =
-      paint_controller.GetPaintArtifact().GetPaintRecord(
-          PropertyTreeState::Root());
+  FloatRect bounds;
+  sk_sp<PaintRecord> recording = formatted_text->PaintFormattedText(
+      canvas()->GetDocument(), GetState().GetFontDescription(), x, y,
+      wrap_width, bounds);
   Draw([recording](cc::PaintCanvas* c, const PaintFlags* flags)  // draw lambda
        { c->drawPicture(recording); },
-       [](const SkIRect& rect) { return false; }, FloatRect(bounds),
+       [](const SkIRect& rect) { return false; }, bounds,
        CanvasRenderingContext2DState::PaintType::kFillPaintType,
        CanvasRenderingContext2DState::kNoImage);
 }
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
index 8e97582..130ee620 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
@@ -50,12 +50,12 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_void_function.h"
 #include "third_party/blink/renderer/bindings/modules/v8/media_stream_track_or_string.h"
-#include "third_party/blink/renderer/bindings/modules/v8/rtc_ice_candidate_init_or_rtc_ice_candidate.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_stream_track.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_answer_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_certificate.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_configuration.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_data_channel_init.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_ice_candidate_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_ice_server.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_offer_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_peer_connection_error_callback.h"
@@ -178,17 +178,10 @@
   return false;
 }
 
-bool IsIceCandidateMissingSdp(
-    const RTCIceCandidateInitOrRTCIceCandidate& candidate) {
-  if (candidate.IsRTCIceCandidateInit()) {
-    const RTCIceCandidateInit* ice_candidate_init =
-        candidate.GetAsRTCIceCandidateInit();
-    return ice_candidate_init->sdpMid().IsNull() &&
-           !ice_candidate_init->hasSdpMLineIndexNonNull();
-  }
-
-  DCHECK(candidate.IsRTCIceCandidate());
-  return false;
+bool IsIceCandidateMissingSdpMidAndMLineIndex(
+    const RTCIceCandidateInit* candidate) {
+  return (candidate->sdpMid().IsNull() &&
+          !candidate->hasSdpMLineIndexNonNull());
 }
 
 RTCOfferOptionsPlatform* ConvertToRTCOfferOptionsPlatform(
@@ -214,26 +207,18 @@
 
 RTCIceCandidatePlatform* ConvertToRTCIceCandidatePlatform(
     ExecutionContext* context,
-    const RTCIceCandidateInitOrRTCIceCandidate& candidate) {
-  DCHECK(!candidate.IsNull());
-  if (candidate.IsRTCIceCandidateInit()) {
-    const RTCIceCandidateInit* ice_candidate_init =
-        candidate.GetAsRTCIceCandidateInit();
-    // TODO(guidou): Change default value to -1. crbug.com/614958.
-    uint16_t sdp_m_line_index = 0;
-    if (ice_candidate_init->hasSdpMLineIndexNonNull()) {
-      sdp_m_line_index = ice_candidate_init->sdpMLineIndexNonNull();
-    } else {
-      UseCounter::Count(context,
-                        WebFeature::kRTCIceCandidateDefaultSdpMLineIndex);
-    }
-    return MakeGarbageCollected<RTCIceCandidatePlatform>(
-        ice_candidate_init->candidate(), ice_candidate_init->sdpMid(),
-        sdp_m_line_index, ice_candidate_init->usernameFragment());
+    const RTCIceCandidateInit* candidate) {
+  // TODO(guidou): Change default value to -1. crbug.com/614958.
+  uint16_t sdp_m_line_index = 0;
+  if (candidate->hasSdpMLineIndexNonNull()) {
+    sdp_m_line_index = candidate->sdpMLineIndexNonNull();
+  } else {
+    UseCounter::Count(context,
+                      WebFeature::kRTCIceCandidateDefaultSdpMLineIndex);
   }
-
-  DCHECK(candidate.IsRTCIceCandidate());
-  return candidate.GetAsRTCIceCandidate()->PlatformCandidate();
+  return MakeGarbageCollected<RTCIceCandidatePlatform>(
+      candidate->candidate(), candidate->sdpMid(), sdp_m_line_index,
+      candidate->usernameFragment());
 }
 
 enum SdpSemanticRequested {
@@ -1906,8 +1891,9 @@
 
 ScriptPromise RTCPeerConnection::addIceCandidate(
     ScriptState* script_state,
-    const RTCIceCandidateInitOrRTCIceCandidate& candidate,
+    const RTCIceCandidateInit* candidate,
     ExceptionState& exception_state) {
+  DCHECK(script_state->ContextIsValid());
   if (signaling_state_ ==
       webrtc::PeerConnectionInterface::SignalingState::kClosed) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
@@ -1915,23 +1901,23 @@
     return ScriptPromise();
   }
 
-  if (IsIceCandidateMissingSdp(candidate)) {
-    exception_state.ThrowTypeError(
-        "Candidate missing values for both sdpMid and sdpMLineIndex");
-    return ScriptPromise();
-  }
-
   RTCIceCandidatePlatform* platform_candidate =
       ConvertToRTCIceCandidatePlatform(ExecutionContext::From(script_state),
                                        candidate);
 
   // Temporary mitigation to avoid throwing an exception when candidate is
-  // empty.
+  // empty or nothing was passed.
   // TODO(crbug.com/978582): Remove this mitigation when the WebRTC layer
-  // handles the empty candidate field correctly.
+  // handles the empty candidate field or the null candidate correctly.
   if (platform_candidate->Candidate().IsEmpty())
     return ScriptPromise::CastUndefined(script_state);
 
+  if (IsIceCandidateMissingSdpMidAndMLineIndex(candidate)) {
+    exception_state.ThrowTypeError(
+        "Candidate missing values for both sdpMid and sdpMLineIndex");
+    return ScriptPromise();
+  }
+
   DisableBackForwardCache(GetExecutionContext());
 
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
@@ -1944,17 +1930,18 @@
 
 ScriptPromise RTCPeerConnection::addIceCandidate(
     ScriptState* script_state,
-    const RTCIceCandidateInitOrRTCIceCandidate& candidate,
+    const RTCIceCandidateInit* candidate,
     V8VoidFunction* success_callback,
     V8RTCPeerConnectionErrorCallback* error_callback,
     ExceptionState& exception_state) {
+  DCHECK(script_state->ContextIsValid());
   DCHECK(success_callback);
   DCHECK(error_callback);
 
   if (CallErrorCallbackIfSignalingStateClosed(signaling_state_, error_callback))
     return ScriptPromise::CastUndefined(script_state);
 
-  if (IsIceCandidateMissingSdp(candidate)) {
+  if (IsIceCandidateMissingSdpMidAndMLineIndex(candidate)) {
     exception_state.ThrowTypeError(
         "Candidate missing values for both sdpMid and sdpMLineIndex");
     return ScriptPromise();
@@ -1967,7 +1954,7 @@
   // Temporary mitigation to avoid throwing an exception when candidate is
   // empty.
   // TODO(crbug.com/978582): Remove this mitigation when the WebRTC layer
-  // handles the empty candidate field correctly.
+  // handles the empty candidate field or the null candidate correctly.
   if (platform_candidate->Candidate().IsEmpty())
     return ScriptPromise::CastUndefined(script_state);
 
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h
index 75cb17f..241dc8e 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h
@@ -68,7 +68,7 @@
 class RTCDTMFSender;
 class RTCDataChannel;
 class RTCDataChannelInit;
-class RTCIceCandidateInitOrRTCIceCandidate;
+class RTCIceCandidateInit;
 class RTCIceTransport;
 class RTCOfferOptions;
 class RTCPeerConnectionTest;
@@ -211,10 +211,10 @@
       ExceptionState&);
 
   ScriptPromise addIceCandidate(ScriptState*,
-                                const RTCIceCandidateInitOrRTCIceCandidate&,
+                                const RTCIceCandidateInit*,
                                 ExceptionState&);
   ScriptPromise addIceCandidate(ScriptState*,
-                                const RTCIceCandidateInitOrRTCIceCandidate&,
+                                const RTCIceCandidateInit*,
                                 V8VoidFunction*,
                                 V8RTCPeerConnectionErrorCallback*,
                                 ExceptionState&);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.idl b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.idl
index a977ad2..d88ca64 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.idl
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.idl
@@ -80,7 +80,7 @@
     readonly attribute RTCSessionDescription? remoteDescription;
     readonly attribute RTCSessionDescription? currentRemoteDescription;
     readonly attribute RTCSessionDescription? pendingRemoteDescription;
-    [CallWith=ScriptState, RaisesException, MeasureAs=RTCPeerConnectionAddIceCandidatePromise] Promise<void> addIceCandidate((RTCIceCandidateInit or RTCIceCandidate) candidate);
+    [CallWith=ScriptState, RaisesException, MeasureAs=RTCPeerConnectionAddIceCandidatePromise] Promise<void> addIceCandidate(optional RTCIceCandidateInit candidate = {});
     readonly attribute RTCSignalingState signalingState;
     readonly attribute RTCIceGatheringState iceGatheringState;
     readonly attribute RTCIceConnectionState iceConnectionState;
@@ -109,7 +109,7 @@
     // TODO(guidou): The failureCallback argument should be non-optional.
     // TODO(crbug.com/841185): |failureCallback| is not nullable in the spec.
     [CallWith=ScriptState] Promise<void> setRemoteDescription(RTCSessionDescriptionInit description, VoidFunction successCallback, optional RTCPeerConnectionErrorCallback? failureCallback);
-    [CallWith=ScriptState, RaisesException, MeasureAs=RTCPeerConnectionAddIceCandidateLegacy] Promise<void> addIceCandidate((RTCIceCandidateInit or RTCIceCandidate) candidate, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
+    [CallWith=ScriptState, RaisesException, MeasureAs=RTCPeerConnectionAddIceCandidateLegacy] Promise<void> addIceCandidate(RTCIceCandidateInit candidate, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
 
     // getStats() has a standardized version and a legacy non-standard version.
     //
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 9560ef3..c9a0c65e 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2072,7 +2072,7 @@
     },
     {
       name: "UserAgentClientHint",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "V8IdleTasks",
diff --git a/third_party/blink/renderer/platform/widget/compositing/android_webview/BUILD.gn b/third_party/blink/renderer/platform/widget/compositing/android_webview/BUILD.gn
index 1acdc30..1fec93f 100644
--- a/third_party/blink/renderer/platform/widget/compositing/android_webview/BUILD.gn
+++ b/third_party/blink/renderer/platform/widget/compositing/android_webview/BUILD.gn
@@ -11,6 +11,10 @@
     "synchronous_layer_tree_frame_sink.h",
   ]
 
-  # TODO(crbug.com/805739): This should not be depended on from the renderer.
-  deps = [ "//components/viz/service" ]
+  deps = [
+    "//components/power_scheduler",
+
+    # TODO(crbug.com/805739): This should not be depended on from the renderer.
+    "//components/viz/service",
+  ]
 }
diff --git a/third_party/blink/renderer/platform/widget/compositing/android_webview/DEPS b/third_party/blink/renderer/platform/widget/compositing/android_webview/DEPS
index 4d7eaf7..88159ae 100644
--- a/third_party/blink/renderer/platform/widget/compositing/android_webview/DEPS
+++ b/third_party/blink/renderer/platform/widget/compositing/android_webview/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
     "+base/cancelable_callback.h",
+    "+components/power_scheduler",
     "+components/viz/service/display",
     "+components/viz/service/display_embedder",
     "+components/viz/service/frame_sinks",
diff --git a/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc b/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc
index a5e27fd..c690561 100644
--- a/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc
@@ -16,6 +16,9 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "cc/trees/layer_tree_frame_sink_client.h"
+#include "components/power_scheduler/power_mode.h"
+#include "components/power_scheduler/power_mode_arbiter.h"
+#include "components/power_scheduler/power_mode_voter.h"
 #include "components/viz/common/display/renderer_settings.h"
 #include "components/viz/common/features.h"
 #include "components/viz/common/gpu/context_provider.h"
@@ -159,7 +162,10 @@
           features::IsUsingVizFrameSubmissionForWebView()),
       use_zero_copy_sw_draw_(
           Platform::Current()
-              ->IsZeroCopySynchronousSwDrawEnabledForAndroidWebView()) {
+              ->IsZeroCopySynchronousSwDrawEnabledForAndroidWebView()),
+      animation_power_mode_voter_(
+          power_scheduler::PowerModeArbiter::GetInstance()->NewVoter(
+              "PowerModeVoter.Animation")) {
   DCHECK(registry_);
   DETACH_FROM_THREAD(thread_checker_);
   memory_policy_.priority_cutoff_when_visible =
@@ -582,6 +588,18 @@
 
 void SynchronousLayerTreeFrameSink::OnNeedsBeginFrames(
     bool needs_begin_frames) {
+  if (needs_begin_frames_ != needs_begin_frames) {
+    if (needs_begin_frames) {
+      TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("cc,benchmark", "NeedsBeginFrames",
+                                        this);
+      animation_power_mode_voter_->VoteFor(
+          power_scheduler::PowerMode::kAnimation);
+    } else {
+      TRACE_EVENT_NESTABLE_ASYNC_END0("cc,benchmark", "NeedsBeginFrames", this);
+      animation_power_mode_voter_->ResetVoteAfterTimeout(
+          power_scheduler::PowerModeVoter::kAnimationTimeout);
+    }
+  }
   needs_begin_frames_ = needs_begin_frames;
   if (sync_client_) {
     sync_client_->SetNeedsBeginFrames(needs_begin_frames);
diff --git a/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.h b/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.h
index 774140db..7663ac29 100644
--- a/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.h
+++ b/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.h
@@ -19,6 +19,7 @@
 #include "base/threading/thread_checker.h"
 #include "cc/trees/layer_tree_frame_sink.h"
 #include "cc/trees/managed_memory_policy.h"
+#include "components/power_scheduler/power_mode_voter.h"
 #include "components/viz/common/display/renderer_settings.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/frame_timing_details_map.h"
@@ -222,6 +223,8 @@
   bool needs_begin_frames_ = false;
   const bool use_zero_copy_sw_draw_;
 
+  std::unique_ptr<power_scheduler::PowerModeVoter> animation_power_mode_voter_;
+
   DISALLOW_COPY_AND_ASSIGN(SynchronousLayerTreeFrameSink);
 };
 
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 4298cb40..1307d12 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -496,6 +496,9 @@
 ### http/tests/csslayout/
 crbug.com/591099 http/tests/csslayout/* [ Skip ]
 
+### virtual/container-queries/
+virtual/container-queries/* [ Skip ]
+
 ### virtual/controls-refresh-hc/
 virtual/controls-refresh-hc/* [ Skip ]
 
@@ -971,3 +974,8 @@
 crbug.com/6606 external/wpt/css/css-display/display-math-on-non-mathml-elements.html [ Failure ]
 crbug.com/6606 external/wpt/css/css-typed-om/the-stylepropertymap/properties/display.html [ Failure ]
 
+# CanvasFormattedText is supported only through layout NG
+crbug.com/1176933 fast/canvas/canvas-formattedtext-1.html [ Skip ]
+crbug.com/1176933 fast/canvas/canvas-formattedtext-2.html [ Skip ]
+crbug.com/1176933 virtual/gpu/fast/canvas/canvas-formattedtext-1.html [ Skip ]
+crbug.com/1176933 virtual/gpu/fast/canvas/canvas-formattedtext-2.html [ Skip ]
diff --git a/third_party/blink/web_tests/MSANExpectations b/third_party/blink/web_tests/MSANExpectations
index 7ac407f..7fa2606a 100644
--- a/third_party/blink/web_tests/MSANExpectations
+++ b/third_party/blink/web_tests/MSANExpectations
@@ -156,3 +156,9 @@
 
 # Flaky timeouts on MSan.
 crbug.com/1174822 [ Linux ] http/tests/mojo/bindings-lite-sw.https.html [ Pass Timeout ]
+
+# Sheriff 2021-02-11
+crbug.com/1177358 [ Linux ] fast/forms/form-invalid-url.html [ Pass Crash ]
+crbug.com/1177358 [ Linux ] http/tests/devtools/console/console-viewport-indices.js [ Pass Timeout ]
+crbug.com/1177358 [ Linux ] http/tests/devtools/console/console-search-reveals-messages.js [ Pass Timeout ]
+crbug.com/1177358 [ Linux ] http/tests/devtools/console/console-viewport-stick-to-bottom.js [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index fd7d975..0163c9df 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -176,6 +176,12 @@
 crbug.com/1081534 [ Mac ] virtual/oopr-canvas2d/* [ Skip ]
 # --- END OOP-R Canvas Tests
 
+# --- CanvasFormattedText tests ---
+# Fails on linux-rel even though actual and expected appear the same.
+crbug.com/1176933 [ Linux ] virtual/gpu/fast/canvas/canvas-formattedtext-2.html [ Skip ]
+
+# --- END CanvasFormattedText tests
+
 # Sheriff on 2020-09-03
 crbug.com/1124352 media/picture-in-picture/clear-after-request.html [ Crash Pass ]
 crbug.com/1124352 media/picture-in-picture/controls/picture-in-picture-button.html [ Crash Pass ]
@@ -1086,10 +1092,6 @@
 crbug.com/958381 fast/table/table-rowspan-height-distribution-in-rows-1.html [ Failure ]
 crbug.com/958381 fast/table/table-rowspan-height-distribution-in-rows-2.html [ Failure ]
 
-# Unknown cause, but they are virtual
-crbug.com/958381 virtual/layout_ng_block_frag/fragmentation/multi-line-cells-paginated.html [ Failure ]
-crbug.com/958381 virtual/layout_ng_block_frag/fragmentation/single-line-cells-paginated.html [ Failure ]
-
 # Mac failures - antialiasing of ref
 crbug.com/958381 [ Mac ] external/wpt/css/CSS2/tables/table-anonymous-objects-087.xht [ Failure ]
 crbug.com/958381 [ Mac ] external/wpt/css/CSS2/tables/table-anonymous-objects-088.xht [ Failure ]
@@ -1282,6 +1284,10 @@
 crbug.com/1145970 wpt_internal/css/css-conditional/container-queries/* [ Skip ]
 crbug.com/1146092 wpt_internal/css/css-contain/* [ Skip ]
 crbug.com/1145970 virtual/container-queries/* [ Pass ]
+crbug.com/1045599 virtual/container-queries/wpt_internal/css/css-contain/grid-block-size.html [ Failure ]
+crbug.com/1045599 virtual/container-queries/wpt_internal/css/css-contain/grid-inline-size.html [ Failure ]
+crbug.com/829028 virtual/container-queries/wpt_internal/css/css-contain/multicol-block-size.html [ Failure ]
+crbug.com/829028 virtual/container-queries/wpt_internal/css/css-contain/multicol-inline-size.html [ Failure ]
 
 # CSS Scrollbars
 crbug.com/891944 external/wpt/css/css-scrollbars/textarea-scrollbar-width-none.html [ Failure ]
@@ -4876,6 +4882,11 @@
 crbug.com/878878 [ Win ] virtual/threaded/fast/scroll-snap/snaps-after-scrollbar-scrolling-vertical.html [ Pass Timeout Failure ]
 crbug.com/878878 [ Win ] virtual/threaded/fast/scroll-snap/snaps-after-scrollbar-scrolling-horizontal.html [ Pass Timeout Failure ]
 
+crbug.com/1038354 [ Win ] fast/scroll-snap/snaps-after-scrollbar-scrolling-vertical.html [ Pass Timeout Failure ]
+crbug.com/1038354 [ Linux ] fast/scroll-snap/snaps-after-scrollbar-scrolling-vertical.html [ Pass Timeout Failure ]
+crbug.com/1038354 [ Win ] fast/scroll-snap/snaps-after-scrollbar-scrolling-horizontal.html [ Pass Timeout Failure ]
+crbug.com/1038354 [ Linux ] fast/scroll-snap/snaps-after-scrollbar-scrolling-horizontal.html [ Pass Timeout Failure ]
+
 crbug.com/878878 virtual/threaded/fast/scroll-snap/animate-fling-to-snap-points.html [ Failure Pass Timeout ]
 crbug.com/878878 virtual/threaded/fast/scroll-snap/snap-scrolls-visual-viewport.html [ Failure Pass ]
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-additive-symbols-setter-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-additive-symbols-setter-invalid.html
new file mode 100644
index 0000000..fd38255
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-additive-symbols-setter-invalid.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule additiveSymbols setter with invalid values</title>
+<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="cssom-additive-symbols-setter-ref.html">
+<style id="sheet">
+@counter-style foo {
+  system: additive;
+  additive-symbols: 2 C, 1 B, 0 A;
+}
+</style>
+
+<ol style="list-style-type: foo; list-style-position: inside" start=0>
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<script>
+// Force layout update before changing the rule
+document.body.offsetWidth;
+
+const sheet = document.getElementById('sheet');
+const foo_rule = sheet.sheet.rules[0];
+
+// Invalid values should be ignored
+foo_rule.additiveSymbols = '';
+foo_rule.additiveSymbols = 'A B C';
+foo_rule.additiveSymbols = '1 B, 2 C, 0 A';
+foo_rule.additiveSymbols = '2 C C, 1 B, 0 A';
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-additive-symbols-setter-ref.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-additive-symbols-setter-ref.html
new file mode 100644
index 0000000..a09788e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-additive-symbols-setter-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule additiveSymbols setter</title>
+
+<ol>
+  <div>A.</div>
+  <div>B.</div>
+  <div>C.</div>
+</ol>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-additive-symbols-setter.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-additive-symbols-setter.html
new file mode 100644
index 0000000..1ff6b42
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-additive-symbols-setter.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule additiveSymbols setter</title>
+<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="cssom-additive-symbols-setter-ref.html">
+<style id="sheet">
+@counter-style foo {
+  system: additive;
+  additive-symbols: 1 I, 0 O;
+}
+</style>
+
+<ol style="list-style-type: foo; list-style-position: inside" start=0>
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<script>
+// Force layout update before changing the rule
+document.body.offsetWidth;
+
+const sheet = document.getElementById('sheet');
+const foo_rule = sheet.sheet.rules[0];
+foo_rule.additiveSymbols = '2 C, 1 B, 0 A';
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-name-setter-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-name-setter-invalid.html
new file mode 100644
index 0000000..01edc41
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-name-setter-invalid.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule name setter with invalid values</title>
+<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="cssom-name-setter-ref.html">
+<style id="sheet">
+@counter-style foo {
+  system: fixed;
+  symbols: A B C;
+}
+
+@counter-style bar {
+  system: fixed;
+  symbols: X Y Z;
+}
+</style>
+
+<ol style="list-style-type: foo; list-style-position: inside">
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<ol style="list-style-type: bar; list-style-position: inside">
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<script>
+// Force layout update before changing the rule
+document.body.offsetWidth;
+
+const sheet = document.getElementById('sheet');
+const rule = sheet.sheet.rules[0];
+
+// Invalid values should be ignored
+rule.name = '';
+rule.name = '123';
+rule.name = 'initial';
+rule.name = 'inherit';
+rule.name = 'unset';
+rule.name = 'none';
+rule.name = 'disc';
+rule.name = 'decimal';
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-name-setter-ref.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-name-setter-ref.html
new file mode 100644
index 0000000..91251ad
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-name-setter-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule name setter</title>
+
+<ol>
+  <div>A.</div>
+  <div>B.</div>
+  <div>C.</div>
+</ol>
+
+<ol>
+  <div>X.</div>
+  <div>Y.</div>
+  <div>Z.</div>
+</ol>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-name-setter.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-name-setter.html
new file mode 100644
index 0000000..4cb926d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-name-setter.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule name setter</title>
+<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="cssom-name-setter-ref.html">
+<style id="sheet">
+@counter-style foo {
+  system: fixed;
+  symbols: A B C;
+}
+
+@counter-style bar {
+  system: fixed;
+  symbols: '1' '2' '3';
+}
+
+@counter-style foo {
+  system: fixed;
+  symbols: X Y Z;
+}
+</style>
+
+<ol style="list-style-type: foo; list-style-position: inside">
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<ol style="list-style-type: bar; list-style-position: inside">
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<script>
+// Force layout update before changing the rule
+document.body.offsetWidth;
+
+// Change the last counter style name from 'foo' to 'bar'
+const sheet = document.getElementById('sheet');
+const rule = sheet.sheet.rules[2];
+rule.name = 'bar';
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-symbols-setter-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-symbols-setter-invalid.html
new file mode 100644
index 0000000..3b40b0d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-symbols-setter-invalid.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule symbols setter with invalid values</title>
+<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="cssom-symbols-setter-ref.html">
+<style id="sheet">
+@counter-style foo {
+  system: alphabetic;
+  symbols: A B C;
+}
+</style>
+
+<ol style="list-style-type: foo; list-style-position: inside">
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<script>
+// Force layout update before changing the rule
+document.body.offsetWidth;
+
+const sheet = document.getElementById('sheet');
+const foo_rule = sheet.sheet.rules[0];
+
+// Invalid values should be ignored
+foo_rule.symbols = '';
+foo_rule.symbols = '1 2 *';
+foo_rule.symbols = 'A';
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-symbols-setter-ref.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-symbols-setter-ref.html
new file mode 100644
index 0000000..64967db
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-symbols-setter-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule symbols setter</title>
+
+<ol>
+  <div>A.</div>
+  <div>B.</div>
+  <div>C.</div>
+</ol>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-symbols-setter.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-symbols-setter.html
new file mode 100644
index 0000000..cd9f66d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-symbols-setter.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule symbols setter</title>
+<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="cssom-symbols-setter-ref.html">
+<style id="sheet">
+@counter-style foo {
+  system: cyclic;
+  symbols: X Y Z;
+}
+</style>
+
+<ol style="list-style-type: foo; list-style-position: inside">
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<script>
+// Force layout update before changing the rule
+document.body.offsetWidth;
+
+const sheet = document.getElementById('sheet');
+const foo_rule = sheet.sheet.rules[0];
+foo_rule.symbols = "A B C";
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-1.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-1.html
new file mode 100644
index 0000000..a616a60
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule system setter with 'fixed' system</title>
+<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="cssom-system-setter-ref.html">
+<style id="sheet">
+@counter-style foo {
+  system: fixed;
+  symbols: A B C;
+}
+</style>
+
+<ol style="list-style-type: foo; list-style-position: inside" start=0>
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<script>
+// Force layout update before changing the rule
+document.body.offsetWidth;
+
+const sheet = document.getElementById('sheet');
+const foo_rule = sheet.sheet.rules[0];
+foo_rule.system = "fixed 0";
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-2.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-2.html
new file mode 100644
index 0000000..f1cc65d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-2.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule system setter with 'extends' system</title>
+<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="cssom-system-setter-ref.html">
+<style id="sheet">
+@counter-style foo {
+  system: extends decimal;
+}
+</style>
+
+<ol style="list-style-type: foo; list-style-position: inside">
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<script>
+// Force layout update before changing the rule
+document.body.offsetWidth;
+
+const sheet = document.getElementById('sheet');
+const foo_rule = sheet.sheet.rules[0];
+foo_rule.system = "extends upper-alpha";
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-invalid.html
new file mode 100644
index 0000000..e56ec1a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-invalid.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule system setter with invalid values</title>
+<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="cssom-system-setter-ref.html">
+<style id="sheet">
+@counter-style foo {
+  system: fixed;
+  symbols: A B C;
+}
+</style>
+
+<ol style="list-style-type: foo; list-style-position: inside">
+  <li></li>
+  <li></li>
+  <li></li>
+</ol>
+
+<script>
+// Force layout update before changing the rule
+document.body.offsetWidth;
+
+const sheet = document.getElementById('sheet');
+const foo_rule = sheet.sheet.rules[0];
+
+// Values with syntax errors should be ignored
+foo_rule.system = '123';
+foo_rule.system = 'extends none';
+foo_rule.system = 'extends decimal decimal';
+
+// Values changing algorithm should be ignored
+foo_rule.system = 'numeric';
+foo_rule.system = 'extends lower-roman';
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-ref.html b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-ref.html
new file mode 100644
index 0000000..98bd994
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-counter-styles/cssom/cssom-system-setter-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>CSSCounterStyleRule system setter</title>
+
+<ol>
+  <div>A.</div>
+  <div>B.</div>
+  <div>C.</div>
+</ol>
+
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/canvas-aspect-ratio.html b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/canvas-aspect-ratio.html
index 91fdc6c8..816d84e 100644
--- a/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/canvas-aspect-ratio.html
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/canvas-aspect-ratio.html
@@ -2,6 +2,7 @@
 <title>Canvas width and height attributes are used as the surface size</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/aspect-ratio.js"></script>
 <style>
   canvas {
     width: 100%;
@@ -17,6 +18,10 @@
   assert_approx_equals(parseInt(getComputedStyle(img).width, 10) / parseInt(getComputedStyle(img).height, 10), expected, epsilon);
 }
 
+function test_computed_style(width, height, expected) {
+  test_computed_style_aspect_ratio("canvas", {width: width, height: height}, expected);
+}
+
 test(function() {
   canvas = document.getElementById("contained");
   assert_ratio(canvas, 2.5);
@@ -31,4 +36,21 @@
   // Canvases always use the aspect ratio from their surface size.
   assert_ratio(canvas, 2.5);
 }, "Canvas width and height attributes are used as the surface size");
+
+test(function() {
+  test_computed_style("10", "20", "auto 10 / 20");
+  test_computed_style("0", "1", "auto 0 / 1");
+  test_computed_style("1", "0", "auto 1 / 0");
+  test_computed_style("0", "0", "auto 0 / 0");
+  test_computed_style("0.5", "1.5", "auto 0.5 / 1.5");
+}, "Computed style");
+
+test(function() {
+  test_computed_style(null, null, "auto");
+  test_computed_style("10", null, "auto");
+  test_computed_style(null, "20", "auto");
+  test_computed_style("xx", "20", "auto");
+  test_computed_style("10%", "20", "auto");
+}, "Computed style for invalid ratios");
+
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html
index af4542e..79a9becc 100644
--- a/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html
@@ -2,6 +2,7 @@
 <title>Image width and height attributes are used to infer aspect-ratio</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/aspect-ratio.js"></script>
 <style>
   img {
     width: 100%;
@@ -22,6 +23,11 @@
   assert_approx_equals(parseFloat(getComputedStyle(img).width, 10) / parseFloat(getComputedStyle(img).height, 10),
                        expected, epsilon, description);
 }
+
+function test_computed_style(width, height, expected) {
+  test_computed_style_aspect_ratio("img", {width: width, height: height}, expected);
+}
+
 // Create and append a new image and immediately check the ratio.
 // This is not racy because the spec requires the user agent to queue a task:
 // https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data
@@ -58,4 +64,21 @@
   assert_not_equals(images[5].offsetHeight, 500, "Images with alt text should be inline and ignore the aspect ratio");
   assert_ratio(images[6], 133/106, "The original aspect ratio of blue.png");
 });
+
+test(function() {
+  test_computed_style("10", "20", "auto 10 / 20");
+  test_computed_style("0", "1", "auto 0 / 1");
+  test_computed_style("1", "0", "auto 1 / 0");
+  test_computed_style("0", "0", "auto 0 / 0");
+  test_computed_style("0.5", "1.5", "auto 0.5 / 1.5");
+}, "Computed style");
+
+test(function() {
+  test_computed_style(null, null, "auto");
+  test_computed_style("10", null, "auto");
+  test_computed_style(null, "20", "auto");
+  test_computed_style("xx", "20", "auto");
+  test_computed_style("10%", "20", "auto");
+}, "Computed style for invalid ratios");
+
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/resources/aspect-ratio.js b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/resources/aspect-ratio.js
new file mode 100644
index 0000000..53226c38
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/resources/aspect-ratio.js
@@ -0,0 +1,10 @@
+function test_computed_style_aspect_ratio(tag, attributes, expected) {
+  var elem = document.createElement(tag);
+  for (name in attributes) {
+    let val = attributes[name];
+    if (val !== null)
+      elem.setAttribute(name, val);
+  }
+  document.body.appendChild(elem);
+  assert_equals(getComputedStyle(elem).aspectRatio, expected);
+}
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/video-aspect-ratio.html b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/video-aspect-ratio.html
index c81b70d..bfa9108 100644
--- a/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/video-aspect-ratio.html
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/video-aspect-ratio.html
@@ -3,6 +3,7 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/common/media.js"></script>
+<script src="resources/aspect-ratio.js"></script>
 <style>
   video {
     width: 100%;
@@ -19,6 +20,10 @@
   assert_approx_equals(parseInt(getComputedStyle(img).width, 10) / parseInt(getComputedStyle(img).height, 10), expected, epsilon);
 }
 
+function test_computed_style(width, height, expected) {
+  test_computed_style_aspect_ratio("video", {width: width, height: height}, expected);
+}
+
 t.step(function() {
   var video = document.getElementById("contained");
   video.src = getVideoURI('/media/2x2-green');
@@ -44,4 +49,21 @@
     assert_ratio(video, 1);
   });
 }, "aspect ratio for regular video");
+
+test(function() {
+  test_computed_style("10", "20", "auto 10 / 20");
+  test_computed_style("0", "1", "auto 0 / 1");
+  test_computed_style("1", "0", "auto 1 / 0");
+  test_computed_style("0", "0", "auto 0 / 0");
+  test_computed_style("0.5", "1.5", "auto 0.5 / 1.5");
+}, "Computed style");
+
+test(function() {
+  test_computed_style(null, null, "auto");
+  test_computed_style("10", null, "auto");
+  test_computed_style(null, "20", "auto");
+  test_computed_style("xx", "20", "auto");
+  test_computed_style("10%", "20", "auto");
+}, "Computed style for invalid ratios");
+
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/CodingConventions.md b/third_party/blink/web_tests/external/wpt/resource-timing/CodingConventions.md
new file mode 100644
index 0000000..461cae42
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/CodingConventions.md
@@ -0,0 +1,59 @@
+For [Resource Timing][1] tests, we want to have a consistent and clear coding
+style. The goals of this style are to:
+*   Make it easier for new contributors to find their way around
+*   Help improve readability and maintainability
+*   Help us understand which parts of the spec are tested or not
+Lots of the following rules are arbitrary but the value is realized in
+consistency instead of adhering to the 'perfect' style.
+
+We want the test suite to be navigable. Developers should be able to easily
+find the file or test that is relevant to their work.
+*   Tests should be arranged in files according to which piece of the spec they
+    test
+*   Files should be named using a consistent pattern
+*   HTML files should include useful meta tags
+    *   `<title>` for controlling labels in results pages
+    *   `<link rel="help">` to point at the relevant piece of the spec
+
+We want the test suite to run consistently. Flaky tests are counterproductive.
+*   Prefer `promise_test` to `async_test`
+    *   Note that there’s [still potential for some concurrency][2]; use
+        `add_cleanup()` if needed
+
+We want the tests to be readable. Tests should be written in a modern style
+with recurring patterns.
+*   80 character line limits where we can
+*   Consistent use of anonymous functions
+    *   prefer
+        ```
+        fn(param => {
+            body();
+        });
+        ```
+
+        over
+
+        ```
+        fn(function(param) {
+            body();
+        });
+        ```
+
+*   Prefer `const` (or, if needed, `let`) to `var`
+*   Contain use of ‘.sub’ in filenames to known helper utilities where possible
+    *   E.g. prefer use of get-host-info.sub.js to `{{host}}` or `{{ports[0]}}`
+        expressions
+*   Avoid use of webperftestharness[extension].js as it’s a layer of cognitive
+    overhead between test content and test intent
+    *   Helper .js files are still encouraged where it makes sense but we want
+        to avoid a testing framework that is specific to Resource Timing (or
+        web performance APIs, in general).
+*   Prefer [`fetch_tests_from_window`][3] to collect test results from embedded
+    iframes instead of hand-rolled `postMessage` approaches
+*   Where possible, we want tests to be scalable - adding another test case
+    should be as simple as calling the tests with new parameters, rather than
+    copying an existing test and modifying it.
+
+[1]: https://www.w3.org/TR/resource-timing-2/
+[2]: https://web-platform-tests.org/writing-tests/testharness-api.html#promise-tests
+[3]: https://web-platform-tests.org/writing-tests/testharness-api.html#consolidating-tests-from-other-documents
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addIceCandidate-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addIceCandidate-expected.txt
index 74fd4978..a6fc09f 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addIceCandidate-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addIceCandidate-expected.txt
@@ -1,13 +1,17 @@
 This is a testharness.js-based test.
 FAIL Add ICE candidate before setting remote description should reject with InvalidStateError promise_rejects_dom: function "function() { throw e }" threw object "OperationError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Error processing ICE candidate" that is not a DOMException InvalidStateError: property "code" is equal to 0, expected 11
-FAIL addIceCandidate({"candidate":"","sdpMid":null,"sdpMLineIndex":null}) should work, and add a=end-of-candidates to both m-sections promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Candidate missing values for both sdpMid and sdpMLineIndex"
-FAIL addIceCandidate(undefined) should work, and add a=end-of-candidates to both m-sections promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Candidate missing values for both sdpMid and sdpMLineIndex"
-FAIL addIceCandidate(null) should work, and add a=end-of-candidates to both m-sections promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Candidate missing values for both sdpMid and sdpMLineIndex"
-FAIL addIceCandidate({}) should work, and add a=end-of-candidates to both m-sections promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Candidate missing values for both sdpMid and sdpMLineIndex"
-FAIL addIceCandidate({}) in stable should work, and add a=end-of-candidates to both m-sections promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Candidate missing values for both sdpMid and sdpMLineIndex"
+PASS addIceCandidate({"candidate":"","sdpMid":null,"sdpMLineIndex":null}) works
+FAIL addIceCandidate({"candidate":"","sdpMid":null,"sdpMLineIndex":null}) adds a=end-of-candidates to both m-sections assert_true: Expect candidate line to be found between media lines m=audio and m=video expected true got false
+PASS addIceCandidate(undefined) works
+FAIL addIceCandidate(undefined) adds a=end-of-candidates to both m-sections assert_true: Expect candidate line to be found between media lines m=audio and m=video expected true got false
+PASS addIceCandidate(null) works
+FAIL addIceCandidate(null) adds a=end-of-candidates to both m-sections assert_true: Expect candidate line to be found between media lines m=audio and m=video expected true got false
+PASS addIceCandidate({}) works
+FAIL addIceCandidate({}) adds a=end-of-candidates to both m-sections assert_true: Expect candidate line to be found between media lines m=audio and m=video expected true got false
+FAIL addIceCandidate({}) in stable should work, and add a=end-of-candidates to both m-sections assert_true: Expect candidate line to be found between media lines m=audio and m=video expected true got false
 FAIL addIceCandidate({usernameFragment: usernameFragment1, sdpMid: sdpMid1}) should work, and add a=end-of-candidates to the first m-section assert_true: Expect candidate line to be found between media lines m=audio and m=video expected true got false
 FAIL addIceCandidate({usernameFragment: usernameFragment2, sdpMLineIndex: 1}) should work, and add a=end-of-candidates to the first m-section assert_true: expected true got false
-FAIL addIceCandidate({usernameFragment: "no such ufrag"}) should not work promise_rejects_dom: function "function() { throw e }" threw object "TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Candidate missing values for both sdpMid and sdpMLineIndex" that is not a DOMException OperationError: property "code" is equal to undefined, expected 0
+FAIL addIceCandidate({usernameFragment: "no such ufrag"}) should not work assert_unreached: Should have rejected: undefined Reached unreachable code
 PASS Add ICE candidate after setting remote description should succeed
 PASS Add ICE candidate with RTCIceCandidate should succeed
 PASS Add candidate with only valid sdpMid should succeed
@@ -17,8 +21,9 @@
 PASS addIceCandidate with second sdpMid and sdpMLineIndex should add candidate to second media stream
 PASS Add candidate for first media stream with null usernameFragment should add candidate to first media stream
 PASS Adding multiple candidates should add candidates to their corresponding media stream
-FAIL Add with empty candidate string (end of candidate) should succeed assert_true: Expect candidate line to be found between media lines m=audio and m=video expected true got false
+FAIL Add with empty candidate string (end of candidates) should succeed assert_true: Expect candidate line to be found between media lines m=audio and m=video expected true got false
 PASS Add candidate with both sdpMid and sdpMLineIndex manually set to null should reject with TypeError
+PASS addIceCandidate with a candidate and neither sdpMid nor sdpMLineIndex should reject with TypeError
 PASS Add candidate with only valid candidate string should reject with TypeError
 PASS Add candidate with invalid candidate string and both sdpMid and sdpMLineIndex null should reject with TypeError
 PASS Add candidate with invalid sdpMid should reject with OperationError
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addIceCandidate.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addIceCandidate.html
index 5fcb6e86..d8e24d6 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addIceCandidate.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addIceCandidate.html
@@ -158,18 +158,28 @@
     null,
     // Members in the dictionary take their default values
     {}
-  ].forEach(init => promise_test(async t => {
-    const pc = new RTCPeerConnection();
+  ].forEach(init => {
+    promise_test(async t => {
+      const pc = new RTCPeerConnection();
 
-    t.add_cleanup(() => pc.close());
+      t.add_cleanup(() => pc.close());
 
-    await pc.setRemoteDescription(sessionDesc);
-    await pc.addIceCandidate(init);
-    assert_candidate_line_between(pc.remoteDescription.sdp,
-      mediaLine1, endOfCandidateLine, mediaLine2);
-    assert_candidate_line_after(pc.remoteDescription.sdp,
-      mediaLine2, endOfCandidateLine);
-  }, `addIceCandidate(${JSON.stringify(init)}) should work, and add a=end-of-candidates to both m-sections`));
+      await pc.setRemoteDescription(sessionDesc);
+      await pc.addIceCandidate(init);
+    }, `addIceCandidate(${JSON.stringify(init)}) works`);
+    promise_test(async t => {
+      const pc = new RTCPeerConnection();
+
+      t.add_cleanup(() => pc.close());
+
+      await pc.setRemoteDescription(sessionDesc);
+      await pc.addIceCandidate(init);
+      assert_candidate_line_between(pc.remoteDescription.sdp,
+        mediaLine1, endOfCandidateLine, mediaLine2);
+      assert_candidate_line_after(pc.remoteDescription.sdp,
+        mediaLine2, endOfCandidateLine);
+    }, `addIceCandidate(${JSON.stringify(init)}) adds a=end-of-candidates to both m-sections`);
+  });
 
   promise_test(async t => {
     const pc = new RTCPeerConnection();
@@ -418,7 +428,7 @@
       assert_candidate_line_between(pc.remoteDescription.sdp,
         mediaLine1, endOfCandidateLine, mediaLine2);
     });
-  }, 'Add with empty candidate string (end of candidate) should succeed');
+  }, 'Add with empty candidate string (end of candidates) should succeed');
 
   /*
     4.4.2.  addIceCandidate
@@ -440,6 +450,15 @@
         })));
   }, 'Add candidate with both sdpMid and sdpMLineIndex manually set to null should reject with TypeError');
 
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+
+    await pc.setRemoteDescription(sessionDesc);
+    promise_rejects_js(t, TypeError,
+      pc.addIceCandidate({candidate: candidateStr1}));
+  }, 'addIceCandidate with a candidate and neither sdpMid nor sdpMLineIndex should reject with TypeError');
+
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
@@ -609,5 +628,4 @@
           usernameFragment: usernameFragment1
         })));
   }, 'Add candidate with sdpMid belonging to different usernameFragment should reject with OperationError');
-
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc/idlharness.https.window-expected.txt
index b94b06a..7b9055e 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/webrtc/idlharness.https.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 496 tests; 480 PASS, 16 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 496 tests; 482 PASS, 14 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS Test driver for asyncInitCertificate
@@ -33,7 +33,7 @@
 PASS RTCPeerConnection interface: attribute remoteDescription
 PASS RTCPeerConnection interface: attribute currentRemoteDescription
 PASS RTCPeerConnection interface: attribute pendingRemoteDescription
-FAIL RTCPeerConnection interface: operation addIceCandidate(optional RTCIceCandidateInit) assert_equals: property has wrong .length expected 0 but got 1
+PASS RTCPeerConnection interface: operation addIceCandidate(optional RTCIceCandidateInit)
 PASS RTCPeerConnection interface: attribute signalingState
 PASS RTCPeerConnection interface: attribute iceGatheringState
 PASS RTCPeerConnection interface: attribute iceConnectionState
@@ -54,7 +54,7 @@
 PASS RTCPeerConnection interface: operation setLocalDescription(RTCLocalSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)
 PASS RTCPeerConnection interface: operation createAnswer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback)
 PASS RTCPeerConnection interface: operation setRemoteDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)
-FAIL RTCPeerConnection interface: operation addIceCandidate(RTCIceCandidateInit, VoidFunction, RTCPeerConnectionErrorCallback) assert_equals: property has wrong .length expected 0 but got 1
+PASS RTCPeerConnection interface: operation addIceCandidate(RTCIceCandidateInit, VoidFunction, RTCPeerConnectionErrorCallback)
 PASS RTCPeerConnection interface: operation generateCertificate(AlgorithmIdentifier)
 PASS RTCPeerConnection interface: operation getSenders()
 PASS RTCPeerConnection interface: operation getReceivers()
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-1-expected.html b/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-1-expected.html
new file mode 100644
index 0000000..6dc8375
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-1-expected.html
@@ -0,0 +1,24 @@
+<style> 
+    .container {
+      position:absolute;
+      top:0;
+      left:0;
+    }
+    b {
+        display:block;
+    }
+    canvas {
+        display:block;
+    }
+</style>
+<body>
+<DIV id="canvas-rendered" class="container">
+</DIV>
+<DIV id="div-rendered" class="container">
+</DIV>
+</body>
+<script type = "text/javascript" src = "resources\canvas-formattedtext-cases-1.js" charset="UTF-8"></script>
+<script type = "text/javascript" src = "resources\canvas-formattedtext.js" charset="UTF-8"></script>
+<script>
+    RenderWithDiv(document.getElementById("div-rendered"));
+</script>
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-1.html b/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-1.html
new file mode 100644
index 0000000..79102cc0
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-1.html
@@ -0,0 +1,25 @@
+<style> 
+    .container {
+      position:absolute;
+      top:0;
+      left:0;
+    }
+    b {
+        display:block;
+    }
+    canvas {
+        display:block;
+    }
+</style>
+<body>
+<DIV id="canvas-rendered" class="container">
+</DIV>
+<DIV id="div-rendered" class="container">
+</DIV>
+</body>
+<script type = "text/javascript" src = "resources\canvas-formattedtext-cases-1.js" charset="UTF-8"></script>
+<script type = "text/javascript" src = "resources\canvas-formattedtext.js" charset="UTF-8"></script>
+<script>
+    RenderWithCanvas(document.getElementById("canvas-rendered"));
+    //RenderWithDiv(document.getElementById("div-rendered"));
+</script>
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-2-expected.html b/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-2-expected.html
new file mode 100644
index 0000000..b8df10f
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-2-expected.html
@@ -0,0 +1,25 @@
+<style> 
+    body { font: arial;}
+    .container {
+      position:absolute;
+      top:0;
+      left:0;
+    }
+    b {
+        display:block;
+    }
+    canvas {
+        display:block;
+    }
+</style>
+<body>
+<DIV id="canvas-rendered" class="container">
+</DIV>
+<DIV id="div-rendered" class="container">
+</DIV>
+</body>
+<script type = "text/javascript" src = "resources\canvas-formattedtext-cases-2.js" charset="UTF-8"></script>
+<script type = "text/javascript" src = "resources\canvas-formattedtext.js" charset="UTF-8"></script>
+<script>
+    RenderWithDiv(document.getElementById("div-rendered"));
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-2.html b/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-2.html
new file mode 100644
index 0000000..128f43e
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/canvas-formattedtext-2.html
@@ -0,0 +1,26 @@
+<style> 
+    body { font: arial;}
+    .container {
+      position:absolute;
+      top:0;
+      left:0;
+    }
+    b {
+        display:block;
+    }
+    canvas {
+        display:block;
+    }
+</style>
+<body>
+<DIV id="canvas-rendered" class="container">
+</DIV>
+<DIV id="div-rendered" class="container">
+</DIV>
+</body>
+<script type = "text/javascript" src = "resources\canvas-formattedtext-cases-2.js" charset="UTF-8"></script>
+<script type = "text/javascript" src = "resources\canvas-formattedtext.js" charset="UTF-8"></script>
+<script>
+    RenderWithCanvas(document.getElementById("canvas-rendered"));
+    //RenderWithDiv(document.getElementById("div-rendered"));
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/resources/canvas-formattedtext-cases-1.js b/third_party/blink/web_tests/fast/canvas/resources/canvas-formattedtext-cases-1.js
new file mode 100644
index 0000000..73245e3
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/resources/canvas-formattedtext-cases-1.js
@@ -0,0 +1,42 @@
+var test_cases =
+[
+    {
+        "description": "English",
+        "text": "Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
+        "font_size": 30,
+        "width": 420,
+        "height": 145
+    },
+    {
+        "description": "English Hyphen",
+        "text": "Some Random unbreak-able text, you say !",
+        "font_size": 12,
+        "width": 120,
+        "height": 36
+    },
+    {
+        "description": "Thai Non WhiteSpace Break",
+        "text": "ทุกคนมีสิทธิทจะออกจากประเทศใด ๆ ไป",
+        "font_size": 36,
+        "width": 274,
+        "height": 90
+    },
+    {
+        "description": "Lao Non Whitespace breaks",
+        "text": "ບຸກຄົນແຕ່ລະຄົນມີສິດທີ່ຈະຈາກປະເທດໃດປະເທດໜື່ງໄປ",
+        "font_size": 24,
+        "width": 106
+    },
+    {
+        "description": "Amheric breaks at colons",
+        "text": "፪/፡ እያንዳንዱ፡ሰው፡ከማንኛውም፡አገር፡ሆነ፡ከራሱ፡",
+        "font_size": 24,
+        "width": 133
+    },
+    {
+        "description": "Kerning",
+        "text":"AVAVAVAVAAVAVAVAVAAVAVAVAVAAVAVAVAVA AV AVAVAVAVAAVAVAVAVA",
+        "font_size": 18,
+        "width": 434
+    }
+];
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/resources/canvas-formattedtext-cases-2.js b/third_party/blink/web_tests/fast/canvas/resources/canvas-formattedtext-cases-2.js
new file mode 100644
index 0000000..1e8a843
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/resources/canvas-formattedtext-cases-2.js
@@ -0,0 +1,35 @@
+var test_cases =
+[
+    {
+        "description": "Hebrew text",
+        "text": "בינלאומי!",
+        "font_size": 36,
+        "width": 274
+    },
+    {
+        "description": "Hebrew text - break inbetween",
+        "text": "Hello בינ לאומי!",
+        "font_size": 36,
+        "width": 204,
+        "height": 100
+    },
+    {
+        "description": "Arabic Simple",
+        "text": "يحق لكل",
+        "font_size": 42,
+        "width": 200
+    },
+    {
+        "description": "Arabic Bidi",
+        "text": "ذلك بلده كما يحق له Web design العودة إليه.",
+        "font_size": 42,
+        "width": 200,
+        "height": 200
+    },
+    {
+        "description": "Arabic Multiline",
+        "text": "2. يحق لكل فرد أن يغادر أية بلاد بما في",
+        "font_size": 26,
+        "width": 224
+    }
+];
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/resources/canvas-formattedtext.js b/third_party/blink/web_tests/fast/canvas/resources/canvas-formattedtext.js
new file mode 100644
index 0000000..beb8575
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/resources/canvas-formattedtext.js
@@ -0,0 +1,43 @@
+function RenderWithCanvas(parent)
+{
+    test_cases.forEach(async function(test_case) {
+        var heading = document.createElement("b");
+        heading.innerText = test_case.description;
+        parent.appendChild(heading);
+
+        var canvas = document.createElement("canvas");
+        canvas.setAttribute("width", test_case.width);
+        canvas.setAttribute("style", "border: 1px solid black");
+        canvas.setAttribute("height", test_case.height ? test_case.height : 60);
+        parent.appendChild(canvas);
+        var text = new CanvasFormattedText();
+        text.appendRun({text: test_case.text});
+        var context = canvas.getContext("2d", { alpha: false });
+        context.clearRect(0,0,test_case.width, canvas.height);
+        context.fillStyle = "#FFFFFF";
+        context.fillRect(0,0,test_case.width, canvas.height);
+        context.fillStyle = "#000000";
+        context.font = (test_case.font_size + "px Arial");
+        var y = (test_case.y === undefined ? 0 : test_case.y);
+        context.fillFormattedText(text, 0, y, canvas.width);
+    });
+}
+
+function RenderWithDiv(parent)
+{
+    test_cases.forEach(async function(test_case) {
+        var heading = document.createElement("b");
+        heading.innerText = test_case.description;
+        parent.appendChild(heading);
+
+        var div = document.createElement("div");
+        div.setAttribute("width", test_case.width);
+        var style = "border: 1px solid black; overflow:hidden;";
+        style += ("width:" + test_case.width + "px;");
+        style += ("height:" + (test_case.height ? test_case.height : 60)  + "px;");
+        style += ("font: "+ test_case.font_size + "px Arial" + " ");
+        div.setAttribute("style", style);
+        div.innerText = test_case.text;
+        parent.appendChild(div);
+    });
+}
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/forms/suggested-value-after-type-change.html b/third_party/blink/web_tests/fast/forms/suggested-value-after-type-change.html
new file mode 100644
index 0000000..5c7a3e47
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/suggested-value-after-type-change.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+<title>This test checks that setting the suggested value of an input element that cannot display a suggested value actually means to reset the suggested value (crbug/1174657)</title>
+<body>
+<input id="test" type="text">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+var input = document.getElementById('test');
+if (!window.internals) {
+  testFailed('This test requires internals object');
+} else {
+  test(() => {
+    internals.setSuggestedValue(input, 'suggested value');
+    assert_equals(input.value, '');
+    assert_equals(internals.suggestedValue(input), 'suggested value');
+
+    // Changing the type to "radio" won't change suggested value, despite
+    // radio buttons not being able to display any suggested value.
+    // So changing the type back to 'text' right away would keep the
+    // suggested value.
+    input.type = 'radio';
+    assert_equals(input.value, 'on');
+    assert_equals(internals.suggestedValue(input), 'suggested value');
+
+    // But changing the suggested value of a radio button clears the
+    // suggested value. Otherwise, selected SetSuggestedValue() calls
+    // could be turned into a no-op by changing the 'type' temporarily.
+    internals.setSuggestedValue(input, 'clear me');
+    assert_equals(input.value, 'on');
+    assert_equals(internals.suggestedValue(input), '');
+
+    // Changing the type back to "text" must not reinstate the old
+    // suggested value.
+    input.type = 'text';
+    assert_equals(input.value, '');
+    assert_equals(internals.suggestedValue(input), '');
+  }, 'Test that setting the suggested value of a radio button clears the old suggested value.');
+
+  // This loop tests several values for 'type'. Except for 'month' and
+  // 'text', they do not support suggested values and SetSuggestedValue()
+  // should hence clear the suggested value.
+  const testcases = [
+    // These do not support suggested values.
+    ['button', 'click-me', ''],
+    ['checkbox', 'check-me', ''],
+    ['color', '#996633', ''],
+    ['file', '/', ''],
+    ['image', 'nonsense', ''],
+    ['radio', 'select-me', ''],
+    ['range', '3', ''],
+    ['week', '2075-W33', ''],
+    ['date', '2075-05-01', ''],
+    ['datetime-local', '2075-05-01T19:30', ''],
+    // These do support suggested values.
+    ['month', '2075-02', '2075-02'],
+    ['text', 'blabla', 'blabla'],
+    ['tel', '3', '3'],
+  ];
+  for (const [type, suggestedValue, expectedValue] of testcases) {
+    test(() => {
+      input.type = 'text';
+      internals.setSuggestedValue(input, suggestedValue);
+      assert_equals(input.type, 'text');
+      assert_equals(internals.suggestedValue(input), suggestedValue);
+
+      input.type = type;
+      internals.setSuggestedValue(input, suggestedValue);
+      assert_equals(input.type, type);
+      assert_equals(internals.suggestedValue(input), expectedValue);
+
+      input.type = 'text';
+      assert_equals(input.type, 'text');
+      assert_equals(internals.suggestedValue(input), expectedValue);
+    }, 'Test for that setting the suggested value either clears or actually sets the suggested value for various types, here: "'+ type +'"');
+  }
+}
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-flex-item-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-flex-item-expected.txt
new file mode 100644
index 0000000..0565c51
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-flex-item-expected.txt
@@ -0,0 +1,549 @@
+This test verifies the base size returned when highlighting flex items.
+
+fixed-flex-basis{
+  "paths": [
+    {
+      "path": [
+        "M",
+        50,
+        50,
+        "L",
+        200,
+        50,
+        "L",
+        200,
+        200,
+        "L",
+        50,
+        200,
+        "Z"
+      ],
+      "fillColor": "rgba(255, 0, 0, 0)",
+      "outlineColor": "rgba(128, 0, 0, 0)",
+      "name": "content"
+    },
+    {
+      "path": [
+        "M",
+        50,
+        50,
+        "L",
+        200,
+        50,
+        "L",
+        200,
+        200,
+        "L",
+        50,
+        200,
+        "Z"
+      ],
+      "fillColor": "rgba(0, 255, 0, 0)",
+      "name": "padding"
+    },
+    {
+      "path": [
+        "M",
+        50,
+        50,
+        "L",
+        200,
+        50,
+        "L",
+        200,
+        200,
+        "L",
+        50,
+        200,
+        "Z"
+      ],
+      "fillColor": "rgba(0, 0, 255, 0)",
+      "name": "border"
+    },
+    {
+      "path": [
+        "M",
+        50,
+        50,
+        "L",
+        200,
+        50,
+        "L",
+        200,
+        200,
+        "L",
+        50,
+        200,
+        "Z"
+      ],
+      "fillColor": "rgba(255, 255, 255, 0)",
+      "name": "margin"
+    }
+  ],
+  "showRulers": true,
+  "showExtensionLines": true,
+  "showAccessibilityInfo": true,
+  "colorFormat": "hex",
+  "elementInfo": {
+    "tagName": "div",
+    "idValue": "fixed-flex-basis",
+    "nodeWidth": "150",
+    "nodeHeight": "150",
+    "isKeyboardFocusable": false,
+    "accessibleName": "",
+    "accessibleRole": "generic",
+    "layoutObjectName": "LayoutNGBlockFlow",
+    "showAccessibilityInfo": true
+  },
+  "flexItemInfo": [
+    {
+      "baseSize": 20,
+      "isHorizontalFlow": true,
+      "flexItemHighlightConfig": {
+        "baseSizeBox": {
+          "fillColor": "rgba(255, 0, 0, 0)",
+          "hatchColor": "rgba(255, 0, 0, 0)"
+        },
+        "baseSizeBorder": {
+          "color": "rgba(255, 0, 0, 0)",
+          "pattern": "solid"
+        },
+        "flexibilityArrow": {
+          "color": "rgba(255, 0, 0, 0)",
+          "pattern": "solid"
+        }
+      }
+    }
+  ]
+}
+zero-flex-basis{
+  "paths": [
+    {
+      "path": [
+        "M",
+        250,
+        50,
+        "L",
+        400,
+        50,
+        "L",
+        400,
+        200,
+        "L",
+        250,
+        200,
+        "Z"
+      ],
+      "fillColor": "rgba(255, 0, 0, 0)",
+      "outlineColor": "rgba(128, 0, 0, 0)",
+      "name": "content"
+    },
+    {
+      "path": [
+        "M",
+        250,
+        50,
+        "L",
+        400,
+        50,
+        "L",
+        400,
+        200,
+        "L",
+        250,
+        200,
+        "Z"
+      ],
+      "fillColor": "rgba(0, 255, 0, 0)",
+      "name": "padding"
+    },
+    {
+      "path": [
+        "M",
+        250,
+        50,
+        "L",
+        400,
+        50,
+        "L",
+        400,
+        200,
+        "L",
+        250,
+        200,
+        "Z"
+      ],
+      "fillColor": "rgba(0, 0, 255, 0)",
+      "name": "border"
+    },
+    {
+      "path": [
+        "M",
+        250,
+        50,
+        "L",
+        400,
+        50,
+        "L",
+        400,
+        200,
+        "L",
+        250,
+        200,
+        "Z"
+      ],
+      "fillColor": "rgba(255, 255, 255, 0)",
+      "name": "margin"
+    }
+  ],
+  "showRulers": true,
+  "showExtensionLines": true,
+  "showAccessibilityInfo": true,
+  "colorFormat": "hex",
+  "elementInfo": {
+    "tagName": "div",
+    "idValue": "zero-flex-basis",
+    "nodeWidth": "150",
+    "nodeHeight": "150",
+    "isKeyboardFocusable": false,
+    "accessibleName": "",
+    "accessibleRole": "generic",
+    "layoutObjectName": "LayoutNGBlockFlow",
+    "showAccessibilityInfo": true
+  },
+  "flexItemInfo": [
+    {}
+  ]
+}
+fixed-width{
+  "paths": [
+    {
+      "path": [
+        "M",
+        50,
+        250,
+        "L",
+        200,
+        250,
+        "L",
+        200,
+        400,
+        "L",
+        50,
+        400,
+        "Z"
+      ],
+      "fillColor": "rgba(255, 0, 0, 0)",
+      "outlineColor": "rgba(128, 0, 0, 0)",
+      "name": "content"
+    },
+    {
+      "path": [
+        "M",
+        50,
+        250,
+        "L",
+        200,
+        250,
+        "L",
+        200,
+        400,
+        "L",
+        50,
+        400,
+        "Z"
+      ],
+      "fillColor": "rgba(0, 255, 0, 0)",
+      "name": "padding"
+    },
+    {
+      "path": [
+        "M",
+        50,
+        250,
+        "L",
+        200,
+        250,
+        "L",
+        200,
+        400,
+        "L",
+        50,
+        400,
+        "Z"
+      ],
+      "fillColor": "rgba(0, 0, 255, 0)",
+      "name": "border"
+    },
+    {
+      "path": [
+        "M",
+        50,
+        250,
+        "L",
+        200,
+        250,
+        "L",
+        200,
+        400,
+        "L",
+        50,
+        400,
+        "Z"
+      ],
+      "fillColor": "rgba(255, 255, 255, 0)",
+      "name": "margin"
+    }
+  ],
+  "showRulers": true,
+  "showExtensionLines": true,
+  "showAccessibilityInfo": true,
+  "colorFormat": "hex",
+  "elementInfo": {
+    "tagName": "div",
+    "idValue": "fixed-width",
+    "nodeWidth": "150",
+    "nodeHeight": "150",
+    "isKeyboardFocusable": false,
+    "accessibleName": "",
+    "accessibleRole": "generic",
+    "layoutObjectName": "LayoutNGBlockFlow",
+    "showAccessibilityInfo": true
+  },
+  "flexItemInfo": [
+    {
+      "baseSize": 30,
+      "isHorizontalFlow": true,
+      "flexItemHighlightConfig": {
+        "baseSizeBox": {
+          "fillColor": "rgba(255, 0, 0, 0)",
+          "hatchColor": "rgba(255, 0, 0, 0)"
+        },
+        "baseSizeBorder": {
+          "color": "rgba(255, 0, 0, 0)",
+          "pattern": "solid"
+        },
+        "flexibilityArrow": {
+          "color": "rgba(255, 0, 0, 0)",
+          "pattern": "solid"
+        }
+      }
+    }
+  ]
+}
+missing-basis-width{
+  "paths": [
+    {
+      "path": [
+        "M",
+        250,
+        250,
+        "L",
+        400,
+        250,
+        "L",
+        400,
+        400,
+        "L",
+        250,
+        400,
+        "Z"
+      ],
+      "fillColor": "rgba(255, 0, 0, 0)",
+      "outlineColor": "rgba(128, 0, 0, 0)",
+      "name": "content"
+    },
+    {
+      "path": [
+        "M",
+        250,
+        250,
+        "L",
+        400,
+        250,
+        "L",
+        400,
+        400,
+        "L",
+        250,
+        400,
+        "Z"
+      ],
+      "fillColor": "rgba(0, 255, 0, 0)",
+      "name": "padding"
+    },
+    {
+      "path": [
+        "M",
+        250,
+        250,
+        "L",
+        400,
+        250,
+        "L",
+        400,
+        400,
+        "L",
+        250,
+        400,
+        "Z"
+      ],
+      "fillColor": "rgba(0, 0, 255, 0)",
+      "name": "border"
+    },
+    {
+      "path": [
+        "M",
+        250,
+        250,
+        "L",
+        400,
+        250,
+        "L",
+        400,
+        400,
+        "L",
+        250,
+        400,
+        "Z"
+      ],
+      "fillColor": "rgba(255, 255, 255, 0)",
+      "name": "margin"
+    }
+  ],
+  "showRulers": true,
+  "showExtensionLines": true,
+  "showAccessibilityInfo": true,
+  "colorFormat": "hex",
+  "elementInfo": {
+    "tagName": "div",
+    "idValue": "missing-basis-width",
+    "nodeWidth": "150",
+    "nodeHeight": "150",
+    "isKeyboardFocusable": false,
+    "accessibleName": "",
+    "accessibleRole": "generic",
+    "layoutObjectName": "LayoutNGBlockFlow",
+    "showAccessibilityInfo": true
+  },
+  "flexItemInfo": [
+    {}
+  ]
+}
+column-fixed-height{
+  "paths": [
+    {
+      "path": [
+        "M",
+        50,
+        450,
+        "L",
+        200,
+        450,
+        "L",
+        200,
+        600,
+        "L",
+        50,
+        600,
+        "Z"
+      ],
+      "fillColor": "rgba(255, 0, 0, 0)",
+      "outlineColor": "rgba(128, 0, 0, 0)",
+      "name": "content"
+    },
+    {
+      "path": [
+        "M",
+        50,
+        450,
+        "L",
+        200,
+        450,
+        "L",
+        200,
+        600,
+        "L",
+        50,
+        600,
+        "Z"
+      ],
+      "fillColor": "rgba(0, 255, 0, 0)",
+      "name": "padding"
+    },
+    {
+      "path": [
+        "M",
+        50,
+        450,
+        "L",
+        200,
+        450,
+        "L",
+        200,
+        600,
+        "L",
+        50,
+        600,
+        "Z"
+      ],
+      "fillColor": "rgba(0, 0, 255, 0)",
+      "name": "border"
+    },
+    {
+      "path": [
+        "M",
+        50,
+        450,
+        "L",
+        200,
+        450,
+        "L",
+        200,
+        600,
+        "L",
+        50,
+        600,
+        "Z"
+      ],
+      "fillColor": "rgba(255, 255, 255, 0)",
+      "name": "margin"
+    }
+  ],
+  "showRulers": true,
+  "showExtensionLines": true,
+  "showAccessibilityInfo": true,
+  "colorFormat": "hex",
+  "elementInfo": {
+    "tagName": "div",
+    "idValue": "column-fixed-height",
+    "nodeWidth": "150",
+    "nodeHeight": "150",
+    "isKeyboardFocusable": false,
+    "accessibleName": "",
+    "accessibleRole": "generic",
+    "layoutObjectName": "LayoutNGBlockFlow",
+    "showAccessibilityInfo": true
+  },
+  "flexItemInfo": [
+    {
+      "baseSize": 40,
+      "isHorizontalFlow": false,
+      "flexItemHighlightConfig": {
+        "baseSizeBox": {
+          "fillColor": "rgba(255, 0, 0, 0)",
+          "hatchColor": "rgba(255, 0, 0, 0)"
+        },
+        "baseSizeBorder": {
+          "color": "rgba(255, 0, 0, 0)",
+          "pattern": "solid"
+        },
+        "flexibilityArrow": {
+          "color": "rgba(255, 0, 0, 0)",
+          "pattern": "solid"
+        }
+      }
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-flex-item.js b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-flex-item.js
new file mode 100644
index 0000000..ab18256
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-flex-item.js
@@ -0,0 +1,48 @@
+// 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.
+
+(async function() {
+  TestRunner.addResult(`This test verifies the base size returned when highlighting flex items.\n`);
+  await TestRunner.loadModule('elements_test_runner');
+  await TestRunner.showPanel('elements');
+  await TestRunner.loadHTML(`
+      <style>
+      .container {
+        display: flex;
+        position: absolute;
+        width: 150px;
+        height: 150px;
+        outline: 1px dashed red;
+      }
+      </style>
+      <div class="container" style="top:50px;left:50px;">
+        <div id="fixed-flex-basis" style="flex:1 1 20px;">Defined flex-basis in px</div>
+      </div>
+      <div class="container" style="top:50px;left:250px;">
+        <div id="zero-flex-basis" style="flex:1;">flex-basis 0</div>
+      </div>
+      <div class="container" style="top:250px;left:50px;">
+        <div id="fixed-width" style="flex-grow:1;width:30px;">Defined width in px</div>
+      </div>
+      <div class="container" style="top:250px;left:250px;">
+        <div id="missing-basis-width" style="flex-grow:1;">No flex-basis or width</div>
+      </div>
+      <div class="container" style="flex-direction:column;top:450px;left:50px;">
+        <div id="column-fixed-height" style="flex-grow:1;height:40px;">Defined height in px</div>
+      </div>
+      <p id="description">This test verifies the base size returned when highlighting flex items.</p>
+    `);
+
+  function dumFlexHighlight(id) {
+    return new Promise(resolve => ElementsTestRunner.dumpInspectorHighlightJSON(id, resolve));
+  }
+
+  await dumFlexHighlight('fixed-flex-basis');
+  await dumFlexHighlight('zero-flex-basis');
+  await dumFlexHighlight('fixed-width');
+  await dumFlexHighlight('missing-basis-width');
+  await dumFlexHighlight('column-fixed-height');
+
+  TestRunner.completeTest();
+})();
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-svg-content-inside-iframe.js b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-svg-content-inside-iframe.js
index 8da18fe..56ad111 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-svg-content-inside-iframe.js
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-svg-content-inside-iframe.js
@@ -31,5 +31,5 @@
       </div>
     `);
 
-  ElementsTestRunner.dumpInspectorHighlightJSON('svg-rect', TestRunner.completeTest.bind(TestRunner), 'svg-iframe');
+  ElementsTestRunner.dumpInspectorHighlightJSON('svg-rect', TestRunner.completeTest.bind(TestRunner));
 })();
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-creation-eval-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-creation-eval-expected.txt
index c1a79a5..91966b8 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-creation-eval-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-creation-eval-expected.txt
@@ -10,6 +10,7 @@
                 sourceCodeLocation : {
                     columnNumber : 13
                     lineNumber : 5
+                    scriptId : <string>
                     url : https://devtools.test:8443/inspector-protocol/resources/content-security-policy-issue-eval.php
                 }
                 violatedDirective : script-src
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-report-only-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-report-only-expected.txt
index 5bf0a7398..2f1baf7 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-report-only-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-report-only-expected.txt
@@ -11,6 +11,7 @@
                 sourceCodeLocation : {
                     columnNumber : 0
                     lineNumber : 2
+                    scriptId : 0
                     url : https://devtools.test:8443/inspector-protocol/resources/content-security-policy-issue-report-only.php
                 }
                 violatedDirective : style-src-elem
@@ -28,6 +29,7 @@
                 sourceCodeLocation : {
                     columnNumber : 13
                     lineNumber : 4
+                    scriptId : 4
                     url : https://devtools.test:8443/inspector-protocol/resources/content-security-policy-issue-report-only.php
                 }
                 violatedDirective : script-src
@@ -45,6 +47,7 @@
                 sourceCodeLocation : {
                     columnNumber : 0
                     lineNumber : 5
+                    scriptId : 0
                     url : https://devtools.test:8443/inspector-protocol/resources/content-security-policy-issue-report-only.php
                 }
                 violatedDirective : style-src-attr
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-src-location-added-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-src-location-added-expected.txt
index 72a565d..6e6ecfc 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-src-location-added-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-src-location-added-expected.txt
@@ -11,6 +11,7 @@
                 sourceCodeLocation : {
                     columnNumber : 0
                     lineNumber : 2
+                    scriptId : <string>
                     url : https://devtools.test:8443/inspector-protocol/resources/content-security-policy-issue-with-src-location.php
                 }
                 violatedDirective : style-src-elem
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-trusted-types-policy-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-trusted-types-policy-expected.txt
index d8f291f..51498c8 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-trusted-types-policy-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-trusted-types-policy-expected.txt
@@ -10,6 +10,7 @@
                 sourceCodeLocation : {
                     columnNumber : 36
                     lineNumber : 4
+                    scriptId : <string>
                     url : https://devtools.test:8443/inspector-protocol/resources/content-security-policy-issue-trusted-types-policy.php
                 }
                 violatedDirective : trusted-types
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-trusted-types-sink-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-trusted-types-sink-expected.txt
index 27562f1..7823d1c 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-trusted-types-sink-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/content-security-policy-issue-trusted-types-sink-expected.txt
@@ -10,6 +10,7 @@
                 sourceCodeLocation : {
                     columnNumber : 55
                     lineNumber : 5
+                    scriptId : <string>
                     url : https://devtools.test:8443/inspector-protocol/resources/content-security-policy-issue-trusted-types-sink.php
                 }
                 violatedDirective : require-trusted-types-for
diff --git a/third_party/blink/web_tests/resources/testdriver-vendor.js b/third_party/blink/web_tests/resources/testdriver-vendor.js
index 6177dd4..080df45 100644
--- a/third_party/blink/web_tests/resources/testdriver-vendor.js
+++ b/third_party/blink/web_tests/resources/testdriver-vendor.js
@@ -132,6 +132,9 @@
           } else if (charCode == 0xE008) {
             eventSenderKeys = "ShiftLeft";
             modifierValue = "shiftKey";
+          } else if (charCode == 0xE006) {
+            eventSenderKeys = "Enter";
+            modifierValue = "enter";
           } else if (charCode >= 0xE000 && charCode <= 0xF8FF) {
             reject(new Error("No support for this code: U+" + charCode.toString(16)));
             return;
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
index d7c52ed2..fe83c47 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
@@ -52,6 +52,9 @@
 PASS window.cached_navigator_usb.ondisconnect is null
 PASS window.cached_navigator_userActivation.hasBeenActive is false
 PASS window.cached_navigator_userActivation.isActive is false
+PASS window.cached_navigator_userAgentData.brands[0].brand is ''
+PASS window.cached_navigator_userAgentData.brands[0].version is ''
+PASS window.cached_navigator_userAgentData.mobile is false
 PASS window.cached_navigator_xr.ondevicechange is null
 PASS window.cached_performance.onresourcetimingbufferfull is null
 PASS window.cached_performance_navigation.redirectCount is 0
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
index 463e454..4d60712 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
@@ -52,6 +52,9 @@
 PASS window.cached_navigator_usb.ondisconnect is null
 PASS window.cached_navigator_userActivation.hasBeenActive is false
 PASS window.cached_navigator_userActivation.isActive is false
+PASS window.cached_navigator_userAgentData.brands[0].brand is ''
+PASS window.cached_navigator_userAgentData.brands[0].version is ''
+PASS window.cached_navigator_userAgentData.mobile is false
 PASS window.cached_navigator_xr.ondevicechange is null
 PASS window.cached_performance.onresourcetimingbufferfull is null
 PASS window.cached_performance_navigation.redirectCount is 0
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
index 350b91e..655a22f 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
@@ -52,6 +52,9 @@
 PASS window.cached_navigator_usb.ondisconnect is null
 PASS window.cached_navigator_userActivation.hasBeenActive is false
 PASS window.cached_navigator_userActivation.isActive is false
+PASS window.cached_navigator_userAgentData.brands[0].brand is ''
+PASS window.cached_navigator_userAgentData.brands[0].version is ''
+PASS window.cached_navigator_userAgentData.mobile is false
 PASS window.cached_navigator_xr.ondevicechange is null
 PASS window.cached_performance.onresourcetimingbufferfull is null
 PASS window.cached_performance_navigation.redirectCount is 0
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
index 0e0a18e..3428cb8f 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
@@ -59,6 +59,9 @@
 PASS oldChildWindow.navigator.userActivation.hasBeenActive is newChildWindow.navigator.userActivation.hasBeenActive
 PASS oldChildWindow.navigator.userActivation.isActive is newChildWindow.navigator.userActivation.isActive
 PASS oldChildWindow.navigator.userAgent is newChildWindow.navigator.userAgent
+PASS oldChildWindow.navigator.userAgentData.brands[0].brand is newChildWindow.navigator.userAgentData.brands[0].brand
+PASS oldChildWindow.navigator.userAgentData.brands[0].version is newChildWindow.navigator.userAgentData.brands[0].version
+PASS oldChildWindow.navigator.userAgentData.mobile is newChildWindow.navigator.userAgentData.mobile
 PASS oldChildWindow.navigator.vendor is newChildWindow.navigator.vendor
 PASS oldChildWindow.navigator.vendorSub is newChildWindow.navigator.vendorSub
 PASS oldChildWindow.navigator.webdriver is newChildWindow.navigator.webdriver
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-get-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-get-expected.txt
index c0fe7221..bb9b551d 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-get-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-get-expected.txt
@@ -14,6 +14,8 @@
 HTTP_CONNECTION = keep-alive
 HTTP_HOST = localhost:8080
 HTTP_REFERER = http://127.0.0.1:8000/
+HTTP_SEC_CH_UA = "content_shell";v="999"
+HTTP_SEC_CH_UA_MOBILE = ?0
 HTTP_SEC_FETCH_DEST = iframe
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = cross-site
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-no-referrer-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-no-referrer-expected.txt
index 83f2240..b981167 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-no-referrer-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-no-referrer-expected.txt
@@ -15,6 +15,8 @@
 HTTP_CONNECTION = keep-alive
 HTTP_HOST = localhost:8080
 HTTP_ORIGIN = null
+HTTP_SEC_CH_UA = "content_shell";v="999"
+HTTP_SEC_CH_UA_MOBILE = ?0
 HTTP_SEC_FETCH_DEST = iframe
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = cross-site
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-post-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-post-expected.txt
index 7bae065f..c1d0c3f 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-post-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-targets-cross-site-frame-post-expected.txt
@@ -16,6 +16,8 @@
 HTTP_HOST = localhost:8080
 HTTP_ORIGIN = http://127.0.0.1:8000
 HTTP_REFERER = http://127.0.0.1:8000/
+HTTP_SEC_CH_UA = "content_shell";v="999"
+HTTP_SEC_CH_UA_MOBILE = ?0
 HTTP_SEC_FETCH_DEST = iframe
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = cross-site
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-with-enctype-targets-cross-site-frame-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-with-enctype-targets-cross-site-frame-expected.txt
index 7bae065f..c1d0c3f 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-with-enctype-targets-cross-site-frame-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/form-with-enctype-targets-cross-site-frame-expected.txt
@@ -16,6 +16,8 @@
 HTTP_HOST = localhost:8080
 HTTP_ORIGIN = http://127.0.0.1:8000
 HTTP_REFERER = http://127.0.0.1:8000/
+HTTP_SEC_CH_UA = "content_shell";v="999"
+HTTP_SEC_CH_UA_MOBILE = ?0
 HTTP_SEC_FETCH_DEST = iframe
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = cross-site
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-basic-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-basic-expected.txt
index 6753a6b..3eb0acec 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-basic-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-basic-expected.txt
@@ -12,6 +12,8 @@
 HTTP_HOST = 127.0.0.1:8000
 HTTP_ORIGIN = http://127.0.0.1:8000
 HTTP_REFERER = http://127.0.0.1:8000/navigation/resources/page-that-posts.html
+HTTP_SEC_CH_UA = "content_shell";v="999"
+HTTP_SEC_CH_UA_MOBILE = ?0
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-frames-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-frames-expected.txt
index 2f68b2d..282fdca 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-frames-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-frames-expected.txt
@@ -17,6 +17,8 @@
 HTTP_HOST = 127.0.0.1:8000
 HTTP_ORIGIN = http://127.0.0.1:8000
 HTTP_REFERER = http://127.0.0.1:8000/navigation/resources/page-that-posts.html
+HTTP_SEC_CH_UA = "content_shell";v="999"
+HTTP_SEC_CH_UA_MOBILE = ?0
 HTTP_SEC_FETCH_DEST = iframe
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-frames-goback1-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-frames-goback1-expected.txt
index 3cc4535..ebb9189 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-frames-goback1-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-frames-goback1-expected.txt
@@ -16,6 +16,8 @@
 HTTP_HOST = 127.0.0.1:8000
 HTTP_ORIGIN = http://127.0.0.1:8000
 HTTP_REFERER = http://127.0.0.1:8000/navigation/post-frames-goback1.html
+HTTP_SEC_CH_UA = "content_shell";v="999"
+HTTP_SEC_CH_UA_MOBILE = ?0
 HTTP_SEC_FETCH_DEST = iframe
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-goback1-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-goback1-expected.txt
index 44c870e..88edc282 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-goback1-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/post-goback1-expected.txt
@@ -12,6 +12,8 @@
 HTTP_HOST = 127.0.0.1:8000
 HTTP_ORIGIN = http://127.0.0.1:8000
 HTTP_REFERER = http://127.0.0.1:8000/navigation/resources/page-that-posts.html
+HTTP_SEC_CH_UA = "content_shell";v="999"
+HTTP_SEC_CH_UA_MOBILE = ?0
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/rename-subframe-goback-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/rename-subframe-goback-expected.txt
index 8ea7757..3b9b1f8 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/rename-subframe-goback-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/rename-subframe-goback-expected.txt
@@ -20,6 +20,8 @@
 HTTP_HOST = 127.0.0.1:8000
 HTTP_ORIGIN = http://127.0.0.1:8000
 HTTP_REFERER = http://127.0.0.1:8000/navigation/resources/page-that-posts.html
+HTTP_SEC_CH_UA = "content_shell";v="999"
+HTTP_SEC_CH_UA_MOBILE = ?0
 HTTP_SEC_FETCH_DEST = iframe
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index b9ec8c2..53aa482 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -792,6 +792,12 @@
     method enable
     method getState
     method setHeaderValue
+interface NavigatorUAData
+    attribute @@toStringTag
+    getter brands
+    getter mobile
+    method constructor
+    method getHighEntropyValues
 interface NetworkInformation : EventTarget
     attribute @@toStringTag
     getter downlink
@@ -2750,6 +2756,7 @@
     getter product
     getter storage
     getter userAgent
+    getter userAgentData
     method clearAppBadge
     method constructor
     method setAppBadge
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index e559f44..a700313 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -731,6 +731,12 @@
 [Worker]     method enable
 [Worker]     method getState
 [Worker]     method setHeaderValue
+[Worker] interface NavigatorUAData
+[Worker]     attribute @@toStringTag
+[Worker]     getter brands
+[Worker]     getter mobile
+[Worker]     method constructor
+[Worker]     method getHighEntropyValues
 [Worker] interface NetworkInformation : EventTarget
 [Worker]     attribute @@toStringTag
 [Worker]     getter downlink
@@ -2770,6 +2776,7 @@
 [Worker]     getter storage
 [Worker]     getter usb
 [Worker]     getter userAgent
+[Worker]     getter userAgentData
 [Worker]     method constructor
 [Worker] interface WritableStream
 [Worker]     attribute @@toStringTag
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 1fd0ec39..9cd5154 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -4629,6 +4629,7 @@
     getter usb
     getter userActivation
     getter userAgent
+    getter userAgentData
     getter vendor
     getter vendorSub
     getter wakeLock
@@ -4651,6 +4652,12 @@
     method unregisterProtocolHandler
     method vibrate
     method webkitGetUserMedia
+interface NavigatorUAData
+    attribute @@toStringTag
+    getter brands
+    getter mobile
+    method constructor
+    method getHighEntropyValues
 interface NetworkInformation : EventTarget
     attribute @@toStringTag
     getter downlink
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
index b095aa7..7d335c0 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -726,6 +726,12 @@
 [Worker]     method enable
 [Worker]     method getState
 [Worker]     method setHeaderValue
+[Worker] interface NavigatorUAData
+[Worker]     attribute @@toStringTag
+[Worker]     getter brands
+[Worker]     getter mobile
+[Worker]     method constructor
+[Worker]     method getHighEntropyValues
 [Worker] interface NetworkInformation : EventTarget
 [Worker]     attribute @@toStringTag
 [Worker]     getter downlink
@@ -2633,6 +2639,7 @@
 [Worker]     getter product
 [Worker]     getter storage
 [Worker]     getter userAgent
+[Worker]     getter userAgentData
 [Worker]     method constructor
 [Worker] interface WritableStream
 [Worker]     attribute @@toStringTag
diff --git a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-operations.https-expected.txt b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-operations.https-expected.txt
index 5a7c71f..3bfc83d 100644
--- a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-operations.https-expected.txt
+++ b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-operations.https-expected.txt
@@ -4,7 +4,7 @@
 FAIL createOffer must detect InvalidStateError synchronously when chain is empty (prerequisite) assert_equals: expected "InvalidStateError" but got "Error"
 FAIL createAnswer must detect InvalidStateError synchronously when chain is empty (prerequisite) assert_equals: promise rejected on same task expected "rejected" but got "pending"
 FAIL SLD(rollback) must detect InvalidStateError synchronously when chain is empty assert_equals: expected "InvalidStateError" but got "OperationError"
-FAIL addIceCandidate must detect InvalidStateError synchronously when chain is empty assert_equals: expected "InvalidStateError" but got "TypeError"
+FAIL addIceCandidate must detect InvalidStateError synchronously when chain is empty assert_equals: expected "InvalidStateError" but got "Error"
 FAIL replaceTrack must detect InvalidStateError synchronously when chain is empty and transceiver is stopped promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'."
 FAIL setParameters must detect InvalidStateError synchronously always when transceiver is stopped promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'."
 FAIL pc.getStats must detect InvalidAccessError synchronously always promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'."
diff --git a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/idlharness.https.window-expected.txt b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/idlharness.https.window-expected.txt
index 5a8c559..dadb188e 100644
--- a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/idlharness.https.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 496 tests; 412 PASS, 84 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 496 tests; 414 PASS, 82 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS Test driver for asyncInitCertificate
@@ -33,7 +33,7 @@
 PASS RTCPeerConnection interface: attribute remoteDescription
 PASS RTCPeerConnection interface: attribute currentRemoteDescription
 PASS RTCPeerConnection interface: attribute pendingRemoteDescription
-FAIL RTCPeerConnection interface: operation addIceCandidate(optional RTCIceCandidateInit) assert_equals: property has wrong .length expected 0 but got 1
+PASS RTCPeerConnection interface: operation addIceCandidate(optional RTCIceCandidateInit)
 PASS RTCPeerConnection interface: attribute signalingState
 PASS RTCPeerConnection interface: attribute iceGatheringState
 PASS RTCPeerConnection interface: attribute iceConnectionState
@@ -54,7 +54,7 @@
 PASS RTCPeerConnection interface: operation setLocalDescription(RTCLocalSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)
 PASS RTCPeerConnection interface: operation createAnswer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback)
 PASS RTCPeerConnection interface: operation setRemoteDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)
-FAIL RTCPeerConnection interface: operation addIceCandidate(RTCIceCandidateInit, VoidFunction, RTCPeerConnectionErrorCallback) assert_equals: property has wrong .length expected 0 but got 1
+PASS RTCPeerConnection interface: operation addIceCandidate(RTCIceCandidateInit, VoidFunction, RTCPeerConnectionErrorCallback)
 PASS RTCPeerConnection interface: operation generateCertificate(AlgorithmIdentifier)
 PASS RTCPeerConnection interface: operation getSenders()
 PASS RTCPeerConnection interface: operation getReceivers()
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-block-size-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-block-size-ref.html
new file mode 100644
index 0000000..c1e974ef
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-block-size-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<p>The fieldset below has block-size containment. It should be wide enough to
+  contain the blue legend line, but not make any block-size room for the hotpink
+  square.</p>
+<fieldset style="height:0; border:20px solid; width:fit-content;">
+  <legend>
+    <div style="width:200px; height:2px; background:blue;"></div>
+  </legend>
+  <div style="width:100px; height:100px; background:hotpink;"></div>
+</fieldset>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-block-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-block-size.html
new file mode 100644
index 0000000..1821c19
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-block-size.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<link rel="match" href="fieldset-block-size-ref.html">
+<p>The fieldset below has block-size containment. It should be wide enough to
+  contain the blue legend line, but not make any block-size room for the hotpink
+  square.</p>
+<fieldset style="contain:block-size; border:20px solid; width:fit-content;">
+  <legend>
+    <div style="width:200px; height:2px; background:blue;"></div>
+  </legend>
+  <div style="width:100px; height:100px; background:hotpink;"></div>
+</fieldset>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-inline-size-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-inline-size-ref.html
new file mode 100644
index 0000000..456af1ce
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-inline-size-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<link rel="match" href="fieldset-inline-size-ref.html">
+<p>The fieldset below has block-size containment. It should not make any
+  inline-size room for the blue legend line, but it should fit the hotpink
+  square in the block direction (but not in the inline direction, where it
+  should overflow the right border of the fieldset.</p>
+<fieldset style="border:20px solid; width:0; min-width:0;">
+  <legend>
+    <div style="width:200px; height:2px; background:blue;"></div>
+  </legend>
+  <div style="width:100px; height:100px; background:hotpink;"></div>
+</fieldset>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-inline-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-inline-size.html
new file mode 100644
index 0000000..10bb0bc
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/fieldset-inline-size.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<link rel="match" href="fieldset-inline-size-ref.html">
+<p>The fieldset below has block-size containment. It should not make any
+  inline-size room for the blue legend line, but it should fit the hotpink
+  square in the block direction (but not in the inline direction, where it
+  should overflow the right border of the fieldset.</p>
+<fieldset style="contain:inline-size; border:20px solid; width:fit-content;">
+  <legend>
+    <div style="width:200px; height:2px; background:blue;"></div>
+  </legend>
+  <div style="width:100px; height:100px; background:hotpink;"></div>
+</fieldset>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/flex-block-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/flex-block-size.html
new file mode 100644
index 0000000..29ebb9d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/flex-block-size.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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="display:flex; contain:block-size; width:fit-content; border:solid green; border-width:50px 0; background:red;">
+  <div style="width:100px; height:100px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/flex-inline-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/flex-inline-size.html
new file mode 100644
index 0000000..b0f6fac
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/flex-inline-size.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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="display:flex; contain:inline-size; width:fit-content; border:solid green; border-width:0 50px; background:red;">
+  <div style="width:100px; height:100px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/flexitem-block-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/flexitem-block-size.html
new file mode 100644
index 0000000..5954336f
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/flexitem-block-size.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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; background:red;">
+  <div style="display:flex; width:fit-content; flex-direction:column;">
+    <div style="contain:block-size; flex-basis:100px; background:green;">
+      <div style="width:100px; height:300px;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/flexitem-inline-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/flexitem-inline-size.html
new file mode 100644
index 0000000..737c3be
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/flexitem-inline-size.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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; background:red;">
+  <div style="display:flex;">
+    <div style="contain:inline-size; flex-basis:100px; background:green;">
+      <div style="width:300px; height:100px;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/grid-block-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/grid-block-size.html
new file mode 100644
index 0000000..eb343fd
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/grid-block-size.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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="display:grid; contain:block-size; width:fit-content; border:solid green; border-width:50px 0; background:red;">
+  <div style="width:100px; height:100px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/grid-inline-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/grid-inline-size.html
new file mode 100644
index 0000000..7099b1a
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/grid-inline-size.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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="display:grid; contain:inline-size; width:fit-content; border:solid green; border-width:0 50px; background:red;">
+  <div style="width:100px; height:100px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-block-size-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-block-size-ref.html
new file mode 100644
index 0000000..cc78d952
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-block-size-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<p>There should be blue square (legend), and a yellow square below it (fieldset
+  content).</p>
+<fieldset style="width:fit-content;">
+  <legend style="height:0; border:solid blue; border-width:50px 0; padding:0; background:red;">
+    <div style="width:100px; height:500px;"></div>
+  </legend>
+  <div style="height:100px; background:yellow;"></div>
+</fieldset>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-block-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-block-size.html
new file mode 100644
index 0000000..19bc0db
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-block-size.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<link rel="match" href="legend-block-size-ref.html">
+<p>There should be blue square (legend), and a yellow square below it (fieldset
+  content).</p>
+<fieldset style="width:fit-content;">
+  <legend style="contain:block-size; border:solid blue; border-width:50px 0; padding:0; background:red;">
+    <div style="width:100px; height:500px;"></div>
+  </legend>
+  <div style="height:100px; background:yellow;"></div>
+</fieldset>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-inline-size-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-inline-size-ref.html
new file mode 100644
index 0000000..ab79be1
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-inline-size-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<link rel="match" href="legend-inline-size-ref.html">
+<p>There should be blue square (legend), and a yellow square below it (fieldset
+  content).</p>
+<fieldset style="width:fit-content; min-width:0;">
+  <legend style="width:0; border:solid blue; border-width:0 50px; padding:0; background:red;">
+    <div style="width:500px; height:100px;"></div>
+  </legend>
+  <div style="height:100px; background:yellow;"></div>
+</fieldset>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-inline-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-inline-size.html
new file mode 100644
index 0000000..09fed74
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/legend-inline-size.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<link rel="match" href="legend-inline-size-ref.html">
+<p>There should be blue square (legend), and a yellow square below it (fieldset
+  content).</p>
+<fieldset style="width:fit-content; min-width:0;">
+  <legend style="contain:inline-size; border:solid blue; border-width:0 50px; padding:0; background:red;">
+    <div style="width:500px; height:100px;"></div>
+  </legend>
+  <div style="height:100px; background:yellow;"></div>
+</fieldset>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/multicol-block-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/multicol-block-size.html
new file mode 100644
index 0000000..9dfa8d6
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/multicol-block-size.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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="columns:4; column-gap:0; contain:block-size; width:fit-content; border:solid green; border-width:50px 0; background:red;">
+  <div style="width:25px; height:400px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/multicol-inline-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/multicol-inline-size.html
new file mode 100644
index 0000000..238be92e
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/multicol-inline-size.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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="columns:4; column-gap:0; contain:inline-size; width:fit-content; border:solid green; border-width:0 50px; background:red;">
+  <div style="width:25px; height:400px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/regular-container-block-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/regular-container-block-size.html
new file mode 100644
index 0000000..5c385bf
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/regular-container-block-size.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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="contain:block-size; width:fit-content; border:solid green; border-width:50px 0; background:red;">
+  <div style="width:100px; height:100px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/regular-container-inline-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/regular-container-inline-size.html
new file mode 100644
index 0000000..dd33236
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/regular-container-inline-size.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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="contain:inline-size; width:fit-content; border:solid green; border-width:0 50px; background:red;">
+  <div style="width:100px; height:100px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/table-block-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/table-block-size.html
new file mode 100644
index 0000000..57c6a99d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/table-block-size.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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>
+<!-- Note that size containment doesn't apply to tables:
+     https://www.w3.org/TR/css-contain-1/#containment-size -->
+<div style="width:100px; height:100px; background:red;">
+  <div style="display:table; contain:block-size; width:fit-content; background:green;">
+    <div style="width:100px; height:100px;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/table-inline-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/table-inline-size.html
new file mode 100644
index 0000000..69bc58e
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/table-inline-size.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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>
+<!-- Note that size containment doesn't apply to tables:
+     https://www.w3.org/TR/css-contain-1/#containment-size -->
+<div style="width:100px; height:100px; background:red;">
+  <div style="display:table; contain:inline-size; width:fit-content; background:green;">
+    <div style="width:100px; height:100px;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/vertical-rl-block-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/vertical-rl-block-size.html
new file mode 100644
index 0000000..aebe94d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/vertical-rl-block-size.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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="writing-mode:vertical-rl; contain:block-size; width:fit-content; border:solid green; border-width:0 50px; background:red;">
+  <div style="width:100px; height:100px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-contain/vertical-rl-inline-size.html b/third_party/blink/web_tests/wpt_internal/css/css-contain/vertical-rl-inline-size.html
new file mode 100644
index 0000000..78f0807d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-contain/vertical-rl-inline-size.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<!-- Merge these tests into upstream wpt test if/when added to the css-contain spec -->
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1031">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5796">
+<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="writing-mode:vertical-rl; contain:inline-size; width:fit-content; border:solid green; border-width:50px 0; background:red;">
+  <div style="width:100px; height:100px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/css/reference/ref-filled-green-100px-square.xht b/third_party/blink/web_tests/wpt_internal/css/reference/ref-filled-green-100px-square.xht
new file mode 100644
index 0000000..05a1379
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/reference/ref-filled-green-100px-square.xht
@@ -0,0 +1,19 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
+  <style type="text/css"><![CDATA[
+  div
+  {
+  background-color: green;
+  height: 100px;
+  width: 100px;
+  }
+  ]]></style>
+ </head>
+ <body>
+  <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+  <div></div>
+ </body>
+</html>
diff --git a/third_party/closure_compiler/closure_args.gni b/third_party/closure_compiler/closure_args.gni
index 04b9946c..4800c47 100644
--- a/third_party/closure_compiler/closure_args.gni
+++ b/third_party/closure_compiler/closure_args.gni
@@ -74,22 +74,22 @@
   "browser_resolver_prefix_replacements=\"chrome://resources/mojo/=/\"",
 ]
 
-polymer3_args = js_modules_args + [
-                  "browser_resolver_prefix_replacements=\"../polymer/polymer_bundled.min.js=../polymer/polymer_bundled.js\"",
-                  "browser_resolver_prefix_replacements=\"chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js=../../third_party/polymer/v3_0/components-chromium/polymer/polymer_bundled.js\"",
-                  "browser_resolver_prefix_replacements=\"//resources/polymer/v3_0/polymer/polymer_bundled.min.js=../../third_party/polymer/v3_0/components-chromium/polymer/polymer_bundled.js\"",
-                  "browser_resolver_prefix_replacements=\"chrome://resources/polymer/v3_0/=../../third_party/polymer/v3_0/components-chromium/\"",
-                  "browser_resolver_prefix_replacements=\"//resources/polymer/v3_0/=../../third_party/polymer/v3_0/components-chromium/\"",
+polymer3_args = [
+  "browser_resolver_prefix_replacements=\"../polymer/polymer_bundled.min.js=../polymer/polymer_bundled.js\"",
+  "browser_resolver_prefix_replacements=\"chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js=../../third_party/polymer/v3_0/components-chromium/polymer/polymer_bundled.js\"",
+  "browser_resolver_prefix_replacements=\"//resources/polymer/v3_0/polymer/polymer_bundled.min.js=../../third_party/polymer/v3_0/components-chromium/polymer/polymer_bundled.js\"",
+  "browser_resolver_prefix_replacements=\"chrome://resources/polymer/v3_0/=../../third_party/polymer/v3_0/components-chromium/\"",
+  "browser_resolver_prefix_replacements=\"//resources/polymer/v3_0/=../../third_party/polymer/v3_0/components-chromium/\"",
 
-                  "hide_warnings_for=externs.zip",
+  "hide_warnings_for=externs.zip",
 
-                  # TODO(crbug.com/1093048): Add the leading '../../' back to
-                  # the path once the CrOS chroot no longer uses symlinks when
-                  # building chrome.
-                  "hide_warnings_for=third_party/polymer/v3_0/components-chromium/",
+  # TODO(crbug.com/1093048): Add the leading '../../' back to
+  # the path once the CrOS chroot no longer uses symlinks when
+  # building chrome.
+  "hide_warnings_for=third_party/polymer/v3_0/components-chromium/",
 
-                  # Note: "2" is counter-intuitively the correct value to use for Polymer 3.
-                  "polymer_version=2",
-                ]
+  # Note: "2" is counter-intuitively the correct value to use for Polymer 3.
+  "polymer_version=2",
+]
 
 default_disabled_closure_args = [ "jscomp_off=duplicate" ]
diff --git a/third_party/closure_compiler/compile_js.gni b/third_party/closure_compiler/compile_js.gni
index 760ccd7..ae4d16f 100644
--- a/third_party/closure_compiler/compile_js.gni
+++ b/third_party/closure_compiler/compile_js.gni
@@ -173,7 +173,6 @@
                              "sources",
                              "testonly",
                              "uses_legacy_modules",
-                             "uses_js_modules",
                            ])
     args = [
       "--compiler",
@@ -224,25 +223,19 @@
       closure_flags = default_closure_args
     }
 
-    # Only one of |is_polymer3|, |uses_js_modules| and |uses_legacy_modules|
-    # should be true at any given time.
-    is_polymer3 = defined(is_polymer3) && is_polymer3
     uses_legacy_modules = defined(uses_legacy_modules) && uses_legacy_modules
-    uses_js_modules = defined(uses_js_modules) && uses_js_modules
-
-    if (is_polymer3) {
-      assert(!uses_js_modules && !uses_legacy_modules)
-      closure_flags += polymer3_args
-    }
-    if (uses_js_modules) {
-      assert(!uses_legacy_modules && !is_polymer3)
+    if (!uses_legacy_modules) {
       closure_flags += js_modules_args
-    }
-    if (uses_legacy_modules) {
-      assert(!uses_js_modules && !is_polymer3)
+    } else {
       closure_flags += legacy_modules_args
     }
 
+    is_polymer3 = defined(is_polymer3) && is_polymer3
+    assert(!is_polymer3 || !uses_legacy_modules)
+    if (is_polymer3) {
+      closure_flags += polymer3_args
+    }
+
     args += [ "--flags" ] + closure_flags
     args += [
       "--externs",
@@ -287,7 +280,6 @@
                                "deps",
                                "is_polymer3",
                                "testonly",
-                               "uses_js_modules",
                                "uses_legacy_modules",
                              ])
     }
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index 075462da..f40f972 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-10-4-155-gc8dede7b1
-Revision: c8dede7b1c632ee7f3032546856f2f92dc3bdc6c
+Version: VER-2-10-4-156-g54c5ad5c9
+Revision: 54c5ad5c9215feca69614565be7ec2030ee46cfb
 CPEPrefix: cpe:/a:freetype:freetype:2.10.4
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index caff711..12cc43c5 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -40153,6 +40153,16 @@
   <int value="7" label="Search Image"/>
 </enum>
 
+<enum name="IOSSessionMigration">
+  <int value="0" label="No Migration"/>
+  <int value="1" label="Migration Failed"/>
+  <int value="2" label="No Session To Migrate"/>
+  <int value="3" label="Migrated Pre-MW to Multi Scenes"/>
+  <int value="4" label="Migrated Pre-MW to Single Scene"/>
+  <int value="5" label="Migrated Single Scene to Multi Scenes"/>
+  <int value="6" label="Migrated Multi Scenes to Single Scene"/>
+</enum>
+
 <enum name="IOSShareAction">
   <int value="0" label="Unknown"/>
   <int value="1" label="Cancel"/>
@@ -41726,6 +41736,8 @@
   <int value="9" label="Open In Browser CCT Menu"/>
   <int value="10"
       label="External Search Intent (eg. Third Party Voice Search results)"/>
+  <int value="11"
+      label="Notification (eg. SW Notification, Media Controls, etc.)"/>
 </enum>
 
 <enum name="LauncherRankingItemType">
@@ -59906,7 +59918,7 @@
 </enum>
 
 <enum name="PhoneHubUserAction">
-  <int value="0" label="UI opened"/>
+  <int value="0" label="UI opened while connected"/>
   <int value="1" label="Tether connection attempted"/>
   <int value="2" label="Do Not Disturb toggled"/>
   <int value="3" label="Find My Device toggled"/>
@@ -72526,8 +72538,17 @@
   </summary>
   <int value="2" label="Pageload count"/>
   <int value="3" label="Renderer crash count"/>
-  <int value="5" label="Extensions renderer count"/>
+  <int value="4" label="Renderer hang count"/>
+  <int value="5" label="Extension renderer crash count"/>
+  <int value="6" label="Child process crash count"/>
+  <int value="15" label="Browser launch count"/>
   <int value="16" label="Browser crash count"/>
+  <int value="17" label="Incomplete shutdown count"/>
+  <int value="24" label="Renderer failed launch count"/>
+  <int value="25" label="Extension renderer failed launch count"/>
+  <int value="26" label="Renderer launch count"/>
+  <int value="27" label="Extension renderer launch count"/>
+  <int value="31" label="GPU process crash count"/>
 </enum>
 
 <enum name="StabilityPageLoadType">
diff --git a/tools/metrics/histograms/histograms_xml/ios/histograms.xml b/tools/metrics/histograms/histograms_xml/ios/histograms.xml
index a2fbbfa..0109196 100644
--- a/tools/metrics/histograms/histograms_xml/ios/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/ios/histograms.xml
@@ -760,6 +760,19 @@
   </summary>
 </histogram>
 
+<histogram name="IOS.SessionMigration" enum="IOSSessionMigration"
+    expires_after="2021-12-11">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>sdefresne@chromium.org</owner>
+  <summary>
+    Record whether the session (list of tabs, and their snapshots) was migrated
+    from one place to another. This migration can happen when the user update
+    Chrome, their device version of iOS or when they restore a backup. The event
+    is recorded for all Browser creation, so most of the event should be
+    &quot;No Migration&quot;.
+  </summary>
+</histogram>
+
 <histogram name="IOS.ShareExtension.ReceivedEntriesCount" units="files"
     expires_after="2021-12-11">
   <owner>javierrobles@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/mobile/histograms.xml b/tools/metrics/histograms/histograms_xml/mobile/histograms.xml
index 33e840e0..46fd76d7 100644
--- a/tools/metrics/histograms/histograms_xml/mobile/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/mobile/histograms.xml
@@ -173,7 +173,7 @@
 </histogram>
 
 <histogram base="true" name="Mobile.Messages.Badge.Tapped"
-    enum="MobileMessagesBadgeState" expires_after="2021-08-09">
+    enum="MobileMessagesBadgeState" expires_after="2022-05-23">
 <!-- Name completed by histogram_suffixes name="Mobile.Messages.Type" -->
 
   <owner>sczs@chromium.org</owner>
@@ -182,7 +182,7 @@
 </histogram>
 
 <histogram base="true" name="Mobile.Messages.Banner.Dismiss"
-    enum="MobileMessagesBannerDismissType" expires_after="2021-08-09">
+    enum="MobileMessagesBannerDismissType" expires_after="2022-05-23">
 <!-- Name completed by histogram_suffixes name="Mobile.Messages.Type" -->
 
   <owner>sczs@chromium.org</owner>
@@ -191,7 +191,7 @@
 </histogram>
 
 <histogram base="true" name="Mobile.Messages.Banner.Event"
-    enum="MobileMessagesBannerEvent" expires_after="2021-07-27">
+    enum="MobileMessagesBannerEvent" expires_after="2022-05-27">
 <!-- Name completed by histogram_suffixes name="Mobile.Messages.Type" -->
 
   <owner>sczs@chromium.org</owner>
@@ -200,7 +200,7 @@
 </histogram>
 
 <histogram name="Mobile.Messages.Banner.OnScreenTime" units="ms"
-    expires_after="2021-07-11">
+    expires_after="2022-05-11">
   <owner>sczs@chromium.org</owner>
   <owner>thegreenfrog@google.com</owner>
   <summary>
@@ -211,7 +211,7 @@
 </histogram>
 
 <histogram name="Mobile.Messages.ConcurrentPresented" units="infobars"
-    expires_after="2021-03-20">
+    expires_after="2022-03-20">
   <owner>sczs@chromium.org</owner>
   <owner>thegreenfrog@chromium.org</owner>
   <summary>
@@ -221,7 +221,7 @@
 </histogram>
 
 <histogram base="true" name="Mobile.Messages.Confirm.Accept.Time" units="ms"
-    expires_after="2021-07-27">
+    expires_after="2022-07-27">
 <!-- Name completed by histogram_suffixes name="Mobile.Messages.Confirm.Type" -->
 
   <owner>sczs@chromium.org</owner>
@@ -233,7 +233,7 @@
 </histogram>
 
 <histogram base="true" name="Mobile.Messages.Confirm.Event"
-    enum="MobileMessagesConfirmInfobarEvents" expires_after="2021-07-27">
+    enum="MobileMessagesConfirmInfobarEvents" expires_after="2022-07-27">
 <!-- Name completed by histogram_suffixes name="Mobile.Messages.Confirm.Type" -->
 
   <owner>sczs@chromium.org</owner>
@@ -245,7 +245,7 @@
 </histogram>
 
 <histogram base="true" name="Mobile.Messages.Modal.Event"
-    enum="MobileMessagesModalEvent" expires_after="2021-03-20">
+    enum="MobileMessagesModalEvent" expires_after="2022-03-20">
 <!-- Name completed by histogram_suffixes name="Mobile.Messages.Type" -->
 
   <owner>sczs@chromium.org</owner>
@@ -254,7 +254,7 @@
 </histogram>
 
 <histogram base="true" name="Mobile.Messages.OverflowRow.Tapped"
-    enum="MobileMessagesInfobarType" expires_after="2021-03-20">
+    enum="MobileMessagesInfobarType" expires_after="2022-03-20">
   <owner>sczs@chromium.org</owner>
   <owner>thegreenfrog@chromium.org</owner>
   <summary>Records a tap on an Infobar overflow menu row.</summary>
@@ -270,7 +270,7 @@
 </histogram>
 
 <histogram base="true" name="Mobile.Messages.Passwords.Modal.Event"
-    enum="MobileMessagesPasswordsModalEvent" expires_after="2021-03-20">
+    enum="MobileMessagesPasswordsModalEvent" expires_after="2022-03-20">
 <!-- Name completed by histogram_suffixes name="Mobile.Messages.Password.Type" -->
 
   <owner>sczs@chromium.org</owner>
@@ -279,7 +279,7 @@
 </histogram>
 
 <histogram base="true" name="Mobile.Messages.Passwords.Modal.Present"
-    enum="MobileMessagesPasswordsModalPresent" expires_after="2021-03-20">
+    enum="MobileMessagesPasswordsModalPresent" expires_after="2022-03-20">
 <!-- Name completed by histogram_suffixes name="Mobile.Messages.Password.Type" -->
 
   <owner>sczs@chromium.org</owner>
@@ -288,14 +288,14 @@
 </histogram>
 
 <histogram name="Mobile.Messages.Save.Card.Modal.Event"
-    enum="MobileMessagesSaveCardModalEvent" expires_after="2021-03-20">
+    enum="MobileMessagesSaveCardModalEvent" expires_after="2022-03-20">
   <owner>sczs@chromium.org</owner>
   <owner>thegreenfrog@chromium.org</owner>
   <summary>Records Save Card specific Infobar Modal events.</summary>
 </histogram>
 
 <histogram name="Mobile.Messages.Translate.Banner.Event"
-    enum="MobileMessagesTranslateBannerEvent" expires_after="2021-03-20">
+    enum="MobileMessagesTranslateBannerEvent" expires_after="2022-03-20">
   <owner>sczs@chromium.org</owner>
   <owner>thegreenfrog@chromium.org</owner>
   <summary>
@@ -305,7 +305,7 @@
 </histogram>
 
 <histogram name="Mobile.Messages.Translate.Modal.Event"
-    enum="MobileMessagesTranslateModalEvent" expires_after="2021-03-20">
+    enum="MobileMessagesTranslateModalEvent" expires_after="2022-03-20">
   <owner>sczs@chromium.org</owner>
   <owner>thegreenfrog@chromium.org</owner>
   <summary>
@@ -315,7 +315,7 @@
 </histogram>
 
 <histogram name="Mobile.Messages.Translate.Modal.Present"
-    enum="MobileMessagesTranslateModalPresent" expires_after="2021-03-20">
+    enum="MobileMessagesTranslateModalPresent" expires_after="2022-03-20">
   <owner>sczs@chromium.org</owner>
   <owner>thegreenfrog@chromium.org</owner>
   <summary>
@@ -514,7 +514,7 @@
 </histogram>
 
 <histogram name="Mobile.Translate.Unused.Count" units="units"
-    expires_after="2021-03-20">
+    expires_after="2022-03-20">
   <owner>sczs@chromium.org</owner>
   <owner>thegreenfrog@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index 49fda90..d778aa0 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -6152,6 +6152,23 @@
   </summary>
 </histogram>
 
+<histogram name="Graphics.Smoothness.Diagnostics.DiscardedDependentCount"
+    units="dependent reporters" expires_after="2022-02-01">
+  <owner>sadrul@chromium.org</owner>
+  <owner>behdadb@chromium.org</owner>
+  <summary>
+    Diagnostic metric to measure how many dependent reporters have been
+    discarded.
+
+    The reporters might be dependant on another reporter (when having partial
+    updates) but if the number of dependents go over a limit we would discard
+    them earlier. This metric count how many of such reporters been discarded
+    earlier than expected as a result of outstanding number of dependent
+    reporters. The metric will be reported at the end of each frame if there has
+    been any discarded dependent reporters.
+  </summary>
+</histogram>
+
 <histogram
     name="Graphics.Smoothness.Diagnostics.DroppedFrameAfterScrollStart.Frames"
     units="vsyncs" expires_after="2021-09-01">
diff --git a/tools/metrics/histograms/histograms_xml/uma/histograms.xml b/tools/metrics/histograms/histograms_xml/uma/histograms.xml
index 42472eb0..632550de 100644
--- a/tools/metrics/histograms/histograms_xml/uma/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/uma/histograms.xml
@@ -333,6 +333,13 @@
   </summary>
 </histogram>
 
+<histogram name="UMA.MetricsService.Initialize.Time" units="ms"
+    expires_after="2021-06-13">
+  <owner>asvitkine@chromium.org</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>Time taken by MetricsService::InitializeMetricsState().</summary>
+</histogram>
+
 <histogram name="UMA.NegativeSamples.Histogram" enum="HistogramNameHash"
     expires_after="2021-06-20">
   <owner>asvitkine@chromium.org</owner>
diff --git a/ui/android/java/src/org/chromium/ui/base/ResourceBundle.java b/ui/android/java/src/org/chromium/ui/base/ResourceBundle.java
index 1f166ef..3fc8f81b 100644
--- a/ui/android/java/src/org/chromium/ui/base/ResourceBundle.java
+++ b/ui/android/java/src/org/chromium/ui/base/ResourceBundle.java
@@ -27,8 +27,7 @@
 @JNINamespace("ui")
 public final class ResourceBundle {
     private static final String TAG = "ResourceBundle";
-    private static String[] sCompressedLocales;
-    private static String[] sUncompressedLocales;
+    private static String[] sAvailableLocales;
 
     private ResourceBundle() {}
 
@@ -37,29 +36,17 @@
      */
     @CalledByNative
     public static void setNoAvailableLocalePaks() {
-        assert sCompressedLocales == null && sUncompressedLocales == null;
-        sCompressedLocales = new String[] {};
-        sUncompressedLocales = new String[] {};
+        assert sAvailableLocales == null;
+        sAvailableLocales = new String[] {};
     }
 
     /**
-     * Sets the available compressed and uncompressed locale pak files.
-     * @param compressed Locales that have compressed pak files.
-     * @param uncompressed Locales that have uncompressed pak files.
+     * Sets the available locale pak files.
+     * @param uncompressed Locales that have pak files.
      */
-    public static void setAvailablePakLocales(String[] compressed, String[] uncompressed) {
-        assert sCompressedLocales == null && sUncompressedLocales == null;
-        sCompressedLocales = compressed;
-        sUncompressedLocales = uncompressed;
-    }
-
-    /**
-     * Return the array of locales that have compressed pak files. Do not modify the array.
-     * @return The locales that have compressed pak files.
-     */
-    public static String[] getAvailableCompressedPakLocales() {
-        assert sCompressedLocales != null;
-        return sCompressedLocales;
+    public static void setAvailablePakLocales(String[] locales) {
+        assert sAvailableLocales == null;
+        sAvailableLocales = locales;
     }
 
     /**
@@ -67,33 +54,28 @@
      * @return The correct locale list for this build.
      */
     public static String[] getAvailableLocales() {
-        assert sCompressedLocales != null;
-        assert sUncompressedLocales != null;
-        return sUncompressedLocales;
+        assert sAvailableLocales != null;
+        return sAvailableLocales;
     }
 
     /**
-     * Return the location of a locale-specific uncompress .pak file asset.
+     * Return the location of a locale-specific .pak file asset.
      *
      * @param locale Chromium locale name.
      * @param inBundle If true, return the path of the uncompressed .pak file
      *                 containing Chromium UI strings within app bundles. If
-     *                 false, return the path of the uncompressed WebView UI
-     *                 strings instead. Note that APK .pak files are stored
-     *                 compressed and handled differently.
+     *                 false, return the path of the WebView UI strings instead.
      * @param logError Logs if the file is not found.
-     * @return Asset path to uncompressed .pak file, or null if the locale is
-     *         not supported by this version of Chromium, or the file is
-     *         missing.
+     * @return Asset path to .pak file, or null if the locale is not supported.
      */
     @CalledByNative
     private static String getLocalePakResourcePath(
             String locale, boolean inBundle, boolean logError) {
-        if (sUncompressedLocales == null) {
+        if (sAvailableLocales == null) {
             // Locales may be null in unit tests.
             return null;
         }
-        if (Arrays.binarySearch(sUncompressedLocales, locale) < 0) {
+        if (Arrays.binarySearch(sAvailableLocales, locale) < 0) {
             // This locale is not supported by Chromium.
             return null;
         }
diff --git a/ui/file_manager/audio_player/js/BUILD.gn b/ui/file_manager/audio_player/js/BUILD.gn
index 5326c22..c47dae2 100644
--- a/ui/file_manager/audio_player/js/BUILD.gn
+++ b/ui/file_manager/audio_player/js/BUILD.gn
@@ -27,7 +27,6 @@
 }
 
 js_type_check("closure_compile_jsmodules") {
-  uses_js_modules = true
   deps = [
     ":background.m",
     ":error_util.m",
diff --git a/ui/file_manager/base/gn/js_test_gen_html.gni b/ui/file_manager/base/gn/js_test_gen_html.gni
index 4cb68bed..c601384 100644
--- a/ui/file_manager/base/gn/js_test_gen_html.gni
+++ b/ui/file_manager/base/gn/js_test_gen_html.gni
@@ -125,7 +125,6 @@
     type_check_deps += [ ":$type_check_target_name" ]
     js_type_check(type_check_target_name) {
       if (defined(invoker.js_module) && invoker.js_module) {
-        uses_js_modules = true
       } else if (defined(invoker.is_polymer3) && invoker.is_polymer3) {
         is_polymer3 = true
       } else {
diff --git a/ui/file_manager/base/js/BUILD.gn b/ui/file_manager/base/js/BUILD.gn
index 923f8bbd..c60eeb6 100644
--- a/ui/file_manager/base/js/BUILD.gn
+++ b/ui/file_manager/base/js/BUILD.gn
@@ -22,7 +22,6 @@
 }
 
 js_type_check("closure_compile_jsmodules") {
-  uses_js_modules = true
   deps = [
     ":app_util.m",
     ":error_counter.m",
@@ -54,7 +53,6 @@
 }
 
 js_type_check("test_support_type_check_jsmodules") {
-  uses_js_modules = true
   testonly = true
   deps = [
     ":mock_chrome.m",
diff --git a/ui/file_manager/base/tools/modules.py b/ui/file_manager/base/tools/modules.py
index d6d21480..8851aac 100755
--- a/ui/file_manager/base/tools/modules.py
+++ b/ui/file_manager/base/tools/modules.py
@@ -335,7 +335,6 @@
             return
         new_lines = '''\
 js_type_check("closure_compile_jsmodules") {
-  uses_js_modules = true
   deps = [
   ]
 }
diff --git a/ui/file_manager/file_manager/background/js/BUILD.gn b/ui/file_manager/file_manager/background/js/BUILD.gn
index d82be78..86bbc35 100644
--- a/ui/file_manager/file_manager/background/js/BUILD.gn
+++ b/ui/file_manager/file_manager/background/js/BUILD.gn
@@ -72,7 +72,6 @@
 }
 
 js_type_check("closure_compile_jsmodules") {
-  uses_js_modules = true
   deps = [
     ":app_window_wrapper.m",
     ":app_windows.m",
@@ -119,7 +118,6 @@
 
 js_type_check("test_support_modules_type_check") {
   testonly = true
-  uses_js_modules = true
   deps = [
     ":mock_crostini.m",
     ":mock_media_scanner.m",
diff --git a/ui/file_manager/file_manager/common/js/BUILD.gn b/ui/file_manager/file_manager/common/js/BUILD.gn
index e9d5d0a..57a9473 100644
--- a/ui/file_manager/file_manager/common/js/BUILD.gn
+++ b/ui/file_manager/file_manager/common/js/BUILD.gn
@@ -43,7 +43,6 @@
 }
 
 js_type_check("closure_compile_jsmodules") {
-  uses_js_modules = true
   deps = [
     ":async_util.m",
     ":file_operation_common.m",
@@ -77,7 +76,6 @@
 }
 
 js_type_check("test_support_modules_type_check") {
-  uses_js_modules = true
   testonly = true
   deps = [
     ":test_importer_common.m",
diff --git a/ui/file_manager/file_manager/cws_widget/BUILD.gn b/ui/file_manager/file_manager/cws_widget/BUILD.gn
index 80625efe..d2a1b7d 100644
--- a/ui/file_manager/file_manager/cws_widget/BUILD.gn
+++ b/ui/file_manager/file_manager/cws_widget/BUILD.gn
@@ -15,7 +15,6 @@
 }
 
 js_type_check("closure_compile_jsmodules") {
-  uses_js_modules = true
   deps = [
     ":app_installer.m",
     ":cws_webview_client.m",
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn b/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
index 0080c30..5205e2bb 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
@@ -21,7 +21,6 @@
 }
 
 js_type_check("closure_compile_jsmodules") {
-  uses_js_modules = true
   deps = [
     ":byte_reader.m",
     ":content_metadata_provider.m",
diff --git a/ui/file_manager/gallery/js/image_editor/BUILD.gn b/ui/file_manager/gallery/js/image_editor/BUILD.gn
index 654f473..e8523774 100644
--- a/ui/file_manager/gallery/js/image_editor/BUILD.gn
+++ b/ui/file_manager/gallery/js/image_editor/BUILD.gn
@@ -8,7 +8,6 @@
 import("//ui/webui/resources/tools/js_modulizer.gni")
 
 js_type_check("closure_compile_jsmodules") {
-  uses_js_modules = true
   deps = [
     ":exif_encoder.m",
     ":image_encoder.m",
diff --git a/ui/file_manager/image_loader/BUILD.gn b/ui/file_manager/image_loader/BUILD.gn
index 5edf778..41c22b23 100644
--- a/ui/file_manager/image_loader/BUILD.gn
+++ b/ui/file_manager/image_loader/BUILD.gn
@@ -9,7 +9,6 @@
 import("//ui/webui/resources/tools/js_modulizer.gni")
 
 js_type_check("closure_compile_jsmodules") {
-  uses_js_modules = true
   deps = [
     ":background.m",
     ":cache.m",
diff --git a/ui/ozone/platform/wayland/host/wayland_popup.cc b/ui/ozone/platform/wayland/host/wayland_popup.cc
index ed3e4d29..736b2388 100644
--- a/ui/ozone/platform/wayland/host/wayland_popup.cc
+++ b/ui/ozone/platform/wayland/host/wayland_popup.cc
@@ -4,11 +4,14 @@
 
 #include "ui/ozone/platform/wayland/host/wayland_popup.h"
 
+#include <aura-shell-client-protocol.h>
+
 #include "ui/ozone/platform/wayland/common/wayland_util.h"
 #include "ui/ozone/platform/wayland/host/shell_object_factory.h"
 #include "ui/ozone/platform/wayland/host/shell_popup_wrapper.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_zaura_shell.h"
 
 namespace ui {
 
@@ -33,9 +36,22 @@
   }
 
   parent_window()->set_child_window(this);
+  InitializeAuraShellSurface();
   return true;
 }
 
+void WaylandPopup::InitializeAuraShellSurface() {
+  DCHECK(shell_popup_);
+  if (!connection()->zaura_shell() || aura_surface_)
+    return;
+  aura_surface_.reset(zaura_shell_get_aura_surface(
+      connection()->zaura_shell()->wl_object(), root_surface()->surface()));
+  if (shadow_type_ == PlatformWindowShadowType::kDrop) {
+    zaura_surface_set_frame(aura_surface_.get(),
+                            ZAURA_SURFACE_FRAME_TYPE_SHADOW);
+  }
+}
+
 void WaylandPopup::Show(bool inactive) {
   if (shell_popup_)
     return;
@@ -137,6 +153,7 @@
   DCHECK(parent_window());
   root_surface()->SetBufferScale(parent_window()->buffer_scale(), false);
   set_ui_scale(parent_window()->ui_scale());
+  shadow_type_ = properties.shadow_type;
   return true;
 }
 
diff --git a/ui/ozone/platform/wayland/host/wayland_popup.h b/ui/ozone/platform/wayland/host/wayland_popup.h
index 1382f1d..38af4fa 100644
--- a/ui/ozone/platform/wayland/host/wayland_popup.h
+++ b/ui/ozone/platform/wayland/host/wayland_popup.h
@@ -34,6 +34,10 @@
   // Creates a popup window, which is visible as a menu window.
   bool CreateShellPopup();
 
+  // Initializes the aura-shell surface, in the case aura-shell EXO extension
+  // is available.
+  void InitializeAuraShellSurface();
+
   // Returns bounds with origin relative to parent window's origin.
   gfx::Rect AdjustPopupWindowPosition();
 
@@ -41,6 +45,10 @@
   // know anything about the version.
   std::unique_ptr<ShellPopupWrapper> shell_popup_;
 
+  wl::Object<zaura_surface> aura_surface_;
+
+  PlatformWindowShadowType shadow_type_ = PlatformWindowShadowType::kNone;
+
   DISALLOW_COPY_AND_ASSIGN(WaylandPopup);
 };
 
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
index 78fbf63..8f671fd 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
@@ -190,6 +190,14 @@
   return state_;
 }
 
+void WaylandToplevelWindow::Activate() {
+  // Only supported by compositors that support zaura_shell (e.g. exo).
+  // TODO(https://crbug.com/1175327): Use standard Wayland extensions, such as
+  // xdg-activation, when those are available.
+  if (aura_surface_)
+    zaura_surface_activate(aura_surface_.get());
+}
+
 void WaylandToplevelWindow::SizeConstraintsChanged() {
   // Size constraints only make sense for normal windows.
   if (!shell_toplevel_)
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
index b6039d52..c5a7dd8b 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
@@ -49,6 +49,7 @@
   void Minimize() override;
   void Restore() override;
   PlatformWindowState GetPlatformWindowState() const override;
+  void Activate() override;
   void SizeConstraintsChanged() override;
   std::string GetWindowUniqueId() const override;
   // SetUseNativeFrame and ShouldUseNativeFrame decide on
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index 90cc9fc..2c77e850 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -243,9 +243,7 @@
   return PlatformWindowState::kNormal;
 }
 
-void WaylandWindow::Activate() {
-  NOTIMPLEMENTED_LOG_ONCE();
-}
+void WaylandWindow::Activate() {}
 
 void WaylandWindow::Deactivate() {
   NOTIMPLEMENTED_LOG_ONCE();
diff --git a/ui/platform_window/platform_window_init_properties.h b/ui/platform_window/platform_window_init_properties.h
index faf1c2a85..df061f47 100644
--- a/ui/platform_window/platform_window_init_properties.h
+++ b/ui/platform_window/platform_window_init_properties.h
@@ -39,6 +39,12 @@
   kTranslucentWindow,
 };
 
+enum class PlatformWindowShadowType {
+  kDefault,
+  kNone,
+  kDrop,
+};
+
 class WorkspaceExtensionDelegate;
 
 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
@@ -83,6 +89,8 @@
 
   WorkspaceExtensionDelegate* workspace_extension_delegate = nullptr;
 
+  PlatformWindowShadowType shadow_type = PlatformWindowShadowType::kDefault;
+
 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
   bool prefer_dark_theme = false;
   gfx::ImageSkia* icon = nullptr;
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
index d5f85e7d..9063a3e 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
@@ -90,6 +90,20 @@
   return ui::PlatformWindowType::kPopup;
 }
 
+ui::PlatformWindowShadowType GetPlatformWindowShadowType(
+    Widget::InitParams::ShadowType shadow_type) {
+  switch (shadow_type) {
+    case Widget::InitParams::ShadowType::kDefault:
+      return ui::PlatformWindowShadowType::kDefault;
+    case Widget::InitParams::ShadowType::kNone:
+      return ui::PlatformWindowShadowType::kNone;
+    case Widget::InitParams::ShadowType::kDrop:
+      return ui::PlatformWindowShadowType::kDrop;
+  }
+  NOTREACHED();
+  return ui::PlatformWindowShadowType::kNone;
+}
+
 ui::PlatformWindowInitProperties ConvertWidgetInitParamsToInitProperties(
     const Widget::InitParams& params) {
   ui::PlatformWindowInitProperties properties;
@@ -103,6 +117,7 @@
   properties.remove_standard_frame = params.remove_standard_frame;
   properties.workspace = params.workspace;
   properties.opacity = GetPlatformWindowOpacity(params.opacity);
+  properties.shadow_type = GetPlatformWindowShadowType(params.shadow_type);
 
   if (params.parent && params.parent->GetHost())
     properties.parent_widget = params.parent->GetHost()->GetAcceleratedWidget();
diff --git a/ui/webui/resources/cr_components/chromeos/network_health/network_health_summary.html b/ui/webui/resources/cr_components/chromeos/network_health/network_health_summary.html
index 6c18eb2..52b7239 100644
--- a/ui/webui/resources/cr_components/chromeos/network_health/network_health_summary.html
+++ b/ui/webui/resources/cr_components/chromeos/network_health/network_health_summary.html
@@ -82,7 +82,7 @@
               [[i18n('OncIpv6Address')]]
             </div>
             <span class="network-attribute-value">
-              [[network.ipv6Addresses.join(', ')]]
+              [[joinAddresses_(network.ipv6Addresses)]]
             </span>
           </div>
         </template>
diff --git a/ui/webui/resources/cr_components/chromeos/network_health/network_health_summary.js b/ui/webui/resources/cr_components/chromeos/network_health/network_health_summary.js
index 8247aa3..2b8cfa1 100644
--- a/ui/webui/resources/cr_components/chromeos/network_health/network_health_summary.js
+++ b/ui/webui/resources/cr_components/chromeos/network_health/network_health_summary.js
@@ -157,4 +157,14 @@
   getNetworkUrl_(network) {
     return 'chrome://os-settings/networkDetail?guid=' + network.guid;
   },
+
+  /**
+   * Returns a concatenated list of strings.
+   * @private
+   * @param {!Array<string>} addresses
+   * @return {string}
+   */
+  joinAddresses_(addresses) {
+    return addresses.join(', ');
+  },
 });
diff --git a/ui/webui/resources/cr_components/omnibox/BUILD.gn b/ui/webui/resources/cr_components/omnibox/BUILD.gn
index b02454d..978d475 100644
--- a/ui/webui/resources/cr_components/omnibox/BUILD.gn
+++ b/ui/webui/resources/cr_components/omnibox/BUILD.gn
@@ -6,7 +6,6 @@
 import("//tools/polymer/html_to_js.gni")
 
 js_type_check("closure_compile") {
-  uses_js_modules = true
   deps = [ ":cr_autocomplete_match_list" ]
 }
 
diff --git a/ui/webui/resources/js/cr/BUILD.gn b/ui/webui/resources/js/cr/BUILD.gn
index 9a0df4f..577de97 100644
--- a/ui/webui/resources/js/cr/BUILD.gn
+++ b/ui/webui/resources/js/cr/BUILD.gn
@@ -14,7 +14,6 @@
 }
 
 js_type_check("closure_compile_modules") {
-  uses_js_modules = true
   deps = [
     ":event_target.m",
     ":ui.m",
diff --git a/weblayer/browser/autofill_client_impl.cc b/weblayer/browser/autofill_client_impl.cc
index b40572ee..9f39db2 100644
--- a/weblayer/browser/autofill_client_impl.cc
+++ b/weblayer/browser/autofill_client_impl.cc
@@ -8,6 +8,7 @@
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/ssl_status.h"
 #include "content/public/browser/web_contents.h"
+#include "weblayer/browser/translate_client_impl.h"
 
 namespace weblayer {
 
@@ -85,6 +86,11 @@
 }
 
 translate::TranslateDriver* AutofillClientImpl::GetTranslateDriver() {
+  // The TranslateDriver is used by AutofillHandler to observe the page language
+  // and run the type-prediction heuristics with language-dependent regexps.
+  auto* translate_client = TranslateClientImpl::FromWebContents(web_contents());
+  if (translate_client)
+    return translate_client->translate_driver();
   return nullptr;
 }
 
@@ -304,7 +310,8 @@
   NOTREACHED();
 }
 
-AutofillClientImpl::AutofillClientImpl(content::WebContents* web_contents) {}
+AutofillClientImpl::AutofillClientImpl(content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents) {}
 
 WEB_CONTENTS_USER_DATA_KEY_IMPL(AutofillClientImpl)
 
diff --git a/weblayer/browser/autofill_client_impl.h b/weblayer/browser/autofill_client_impl.h
index 8b69d24..d4fbe48 100644
--- a/weblayer/browser/autofill_client_impl.h
+++ b/weblayer/browser/autofill_client_impl.h
@@ -9,6 +9,7 @@
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "components/autofill/core/browser/autofill_client.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 
 namespace weblayer {
@@ -18,7 +19,8 @@
 // exercised within the WebLayer autofill flow.
 class AutofillClientImpl
     : public autofill::AutofillClient,
-      public content::WebContentsUserData<AutofillClientImpl> {
+      public content::WebContentsUserData<AutofillClientImpl>,
+      public content::WebContentsObserver {
  public:
   ~AutofillClientImpl() override;
 
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateClientImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateClientImpl.java
index b0f8b8f..1d6105d 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateClientImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateClientImpl.java
@@ -32,7 +32,6 @@
     private InterceptNavigationDelegateImpl mInterceptNavigationDelegate;
     private long mLastNavigationWithUserGestureTime = RedirectHandler.INVALID_TIME;
     private boolean mDestroyed;
-    private boolean mAllowIntentLaunchesInBackgroundForCurrentNavigation;
 
     InterceptNavigationDelegateClientImpl(TabImpl tab) {
         mTab = tab;
@@ -104,18 +103,19 @@
 
     @Override
     public boolean isHidden() {
-        // The tab is never considered hidden for the purpose of intent launching if the embedder
-        // has specified that intent launches should be allowed in the background on the current
-        // navigation.
-        // TODO(crbug.com/1162267): Pass this state into ExternalNavigationHandler so that
-        // it can consider this case explicitly and eliminate the need to have special-case
-        // logic in this method.
-        if (mAllowIntentLaunchesInBackgroundForCurrentNavigation) return false;
-
         return !mTab.isVisible();
     }
 
     @Override
+    public boolean areIntentLaunchesAllowedInHiddenTabsForNavigation(NavigationParams params) {
+        NavigationImpl navigation =
+                mTab.getNavigationControllerImpl().getNavigationImplFromId(params.navigationId);
+        if (navigation == null) return false;
+
+        return navigation.areIntentLaunchesAllowedInBackground();
+    }
+
+    @Override
     public Activity getActivity() {
         return ContextUtils.activityFromContext(mTab.getBrowser().getContext());
     }
@@ -145,19 +145,6 @@
         if (params.hasUserGesture || params.hasUserGestureCarryover) {
             mLastNavigationWithUserGestureTime = SystemClock.elapsedRealtime();
         }
-
-        NavigationImpl navigation =
-                mTab.getNavigationControllerImpl().getNavigationImplFromId(params.navigationId);
-        // As the navigation is ongoing at this point there should be a NavigationImpl instance for
-        // it.
-        assert navigation != null;
-
-        // Save the information of whether intent launches are allowed in the background for use
-        // later in the calculation of the decision for this navigation.
-        // TODO(crbug.com/1162267): Pass this state into ExternalNavigationHandler so that
-        // it can consider this case explicitly and eliminate the need to track this state here.
-        mAllowIntentLaunchesInBackgroundForCurrentNavigation =
-                navigation.areIntentLaunchesAllowedInBackground();
     }
 
     @Override
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
index 9852e1d..63b73a6 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
@@ -272,7 +272,7 @@
         SelectionPopupController.setMustUseWebContentsContext();
         SelectionPopupController.setShouldGetReadbackViewFromWindowAndroid();
 
-        ResourceBundle.setAvailablePakLocales(new String[] {}, ProductConfig.UNCOMPRESSED_LOCALES);
+        ResourceBundle.setAvailablePakLocales(ProductConfig.LOCALES);
         BundleUtils.setIsBundle(ProductConfig.IS_BUNDLE);
 
         setChildProcessCreationParams(appContext, packageInfo.packageName);