diff --git a/DEPS b/DEPS
index 87f8cd6..bb6c3250 100644
--- a/DEPS
+++ b/DEPS
@@ -209,7 +209,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'a65c295c1c9b65d796087aeaac26287ac59b1b1e',
+  'skia_revision': '42bee2d3d2a6c39d0c7956f137f4b42da7ee7552',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -225,7 +225,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '0ca03fb2907c0dce78f01f0e9774ed7c832a8883',
+  'swiftshader_revision': '2deb0df8fa34b2935bdae7a6e01560504bdad399',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -288,7 +288,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': '436469a9e3582bc20cdd9ee7a7ca89b46704e8f1',
+  'devtools_frontend_revision': '7d681c9b79e45ee3e2b8de0c8563ae30a882ab38',
   # 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.
@@ -548,7 +548,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' + '@' + '1bbcdeb54662bbb5695f81db9fe57ec368de2cd7',
+      'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + '19034e9e9f74113493ad4d69a70770aac64024e1',
       'condition': 'checkout_ios',
   },
 
@@ -966,7 +966,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '6b0a611c2c692684f94c0c3629f793feebd16b39',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '3ffca4bed79f3b5336a93193c571484df09b1004',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1349,7 +1349,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '9a1689eeaa281b21c5bcedcad5d54ddaeeb7889c',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '565fc165d0f867c9de129ead9b1766f42d2a1205',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1438,7 +1438,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': 'Hf11zqHzrfja2miAIic8j5jVjfs3rcuSFj8vUK-AVYAC'
+              'version': 'ZV7RwS9jPyaEWlNUpA8m6n3XwWC5ORRQMw8T_kUjsYEC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1457,7 +1457,7 @@
   },
 
   'src/third_party/re2/src':
-    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + 'bc423653fdf28618554da96e1532662d1e33eaca',
+    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + 'c47b5818a7cb7e282dadb1fc56f0d993a113294b',
 
   'src/third_party/r8': {
       'packages': [
@@ -1547,7 +1547,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '22ba62ffe79c3881581ab430368bf3764d9533eb',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@baa6900ec346d5c20ef3263198f011fb1bd8077a',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@108d5055b4a3068b1c71cb592a09698d4139c15b',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '732a76d9d3c70d6aa487216495eeb28518349c3a',
@@ -1635,7 +1635,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@cb232a24709c9be3cc91fa068b4aa4833d03031b',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@210b0f0b51851d02d93d3424947d578c8bbbd09d',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/renderer/aw_render_frame_ext.cc b/android_webview/renderer/aw_render_frame_ext.cc
index 66932ba..56c3a50 100644
--- a/android_webview/renderer/aw_render_frame_ext.cc
+++ b/android_webview/renderer/aw_render_frame_ext.cc
@@ -349,11 +349,10 @@
 }
 
 blink::WebView* AwRenderFrameExt::GetWebView() {
-  if (!render_frame() || !render_frame()->GetRenderView() ||
-      !render_frame()->GetRenderView()->GetWebView())
+  if (!render_frame())
     return nullptr;
 
-  return render_frame()->GetRenderView()->GetWebView();
+  return render_frame()->GetWebView();
 }
 
 blink::WebFrameWidget* AwRenderFrameExt::GetWebFrameWidget() {
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 4d8df504..36b8898 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -485,6 +485,8 @@
     "fast_ink/view_tree_host_widget.h",
     "focus_cycler.cc",
     "focus_cycler.h",
+    "frame/frame_context_menu_controller.cc",
+    "frame/frame_context_menu_controller.h",
     "frame/header_view.cc",
     "frame/non_client_frame_view_ash.cc",
     "frame/snap_controller_impl.cc",
diff --git a/ash/capture_mode/capture_mode_notification_view.cc b/ash/capture_mode/capture_mode_notification_view.cc
index 174237b1..75dbae4 100644
--- a/ash/capture_mode/capture_mode_notification_view.cc
+++ b/ash/capture_mode/capture_mode_notification_view.cc
@@ -89,7 +89,6 @@
           IDS_ASH_MULTIPASTE_SCREENSHOT_NOTIFICATION_NUDGE)));
   shortcut_label->SetBackgroundColor(background_color);
   shortcut_label->SetEnabledColor(text_icon_color);
-  ClipboardHistoryController::Get()->MarkScreenshotNotificationNudgeShown();
   return clipboard_shortcut_view;
 }
 
@@ -133,6 +132,9 @@
     layout->SetFlexForView(label, 1);
   }
 
+  // Notify the clipboard history of the created notification.
+  ClipboardHistoryController::Get()->OnScreenshotNotificationCreated();
+
   return banner_view;
 }
 
diff --git a/ash/clipboard/clipboard_history_controller_impl.cc b/ash/clipboard/clipboard_history_controller_impl.cc
index c9dcd18..c42d63c 100644
--- a/ash/clipboard/clipboard_history_controller_impl.cc
+++ b/ash/clipboard/clipboard_history_controller_impl.cc
@@ -287,8 +287,8 @@
   nudge_controller_->MarkNewFeatureBadgeShown();
 }
 
-void ClipboardHistoryControllerImpl::MarkScreenshotNotificationNudgeShown() {
-  nudge_controller_->MarkScreenshotNotificationNudgeShown();
+void ClipboardHistoryControllerImpl::OnScreenshotNotificationCreated() {
+  nudge_controller_->MarkScreenshotNotificationShown();
 }
 
 bool ClipboardHistoryControllerImpl::CanShowMenu() const {
diff --git a/ash/clipboard/clipboard_history_controller_impl.h b/ash/clipboard/clipboard_history_controller_impl.h
index 96bdc6a..ffe9dc4 100644
--- a/ash/clipboard/clipboard_history_controller_impl.h
+++ b/ash/clipboard/clipboard_history_controller_impl.h
@@ -109,7 +109,7 @@
   bool CanShowMenu() const override;
   bool ShouldShowNewFeatureBadge() const override;
   void MarkNewFeatureBadgeShown() override;
-  void MarkScreenshotNotificationNudgeShown() override;
+  void OnScreenshotNotificationCreated() override;
   std::unique_ptr<ScopedClipboardHistoryPause> CreateScopedPause() override;
   base::Value GetHistoryValues(
       const std::set<std::string>& item_id_filter) const override;
diff --git a/ash/clipboard/clipboard_nudge_controller.cc b/ash/clipboard/clipboard_nudge_controller.cc
index 7720390..8b61b3ae 100644
--- a/ash/clipboard/clipboard_nudge_controller.cc
+++ b/ash/clipboard/clipboard_nudge_controller.cc
@@ -173,7 +173,7 @@
   new_feature_last_shown_time_.ResetTime();
 }
 
-void ClipboardNudgeController::MarkScreenshotNotificationNudgeShown() {
+void ClipboardNudgeController::MarkScreenshotNotificationShown() {
   base::UmaHistogramBoolean(kScreenshotNotification_ShowCount, true);
   if (screenshot_notification_last_shown_time_.ShouldLogFeatureOpenTime()) {
     base::UmaHistogramExactLinear(kScreenshotNotification_OpenTime, kMaxSeconds,
diff --git a/ash/clipboard/clipboard_nudge_controller.h b/ash/clipboard/clipboard_nudge_controller.h
index e1e5b4a6..d4ab7dc 100644
--- a/ash/clipboard/clipboard_nudge_controller.h
+++ b/ash/clipboard/clipboard_nudge_controller.h
@@ -90,8 +90,8 @@
   // Increment the 'new' feature badge shown count.
   void MarkNewFeatureBadgeShown();
 
-  // Increment the screenshot notification nudge shown count.
-  void MarkScreenshotNotificationNudgeShown();
+  // Increment the screenshot notification shown count.
+  void MarkScreenshotNotificationShown();
 
   // ClipboardHistoryControllerImpl:
   void OnClipboardHistoryMenuShown(
diff --git a/ash/clipboard/clipboard_nudge_controller_unittest.cc b/ash/clipboard/clipboard_nudge_controller_unittest.cc
index 1f810f1b..e0ddafff 100644
--- a/ash/clipboard/clipboard_nudge_controller_unittest.cc
+++ b/ash/clipboard/clipboard_nudge_controller_unittest.cc
@@ -93,7 +93,7 @@
         nudge_controller_->MarkNewFeatureBadgeShown();
         return;
       case ClipboardNudgeType::kScreenshotNotificationNudge:
-        nudge_controller_->MarkScreenshotNotificationNudgeShown();
+        nudge_controller_->MarkScreenshotNotificationShown();
         return;
     }
   }
diff --git a/ash/frame/frame_context_menu_controller.cc b/ash/frame/frame_context_menu_controller.cc
new file mode 100644
index 0000000..653b4cc5
--- /dev/null
+++ b/ash/frame/frame_context_menu_controller.cc
@@ -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.
+
+#include "ash/frame/frame_context_menu_controller.h"
+
+#include "ash/public/cpp/move_to_desks_menu_delegate.h"
+#include "chromeos/ui/frame/move_to_desks_menu_model.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+FrameContextMenuController::FrameContextMenuController(views::Widget* frame,
+                                                       Delegate* delegate)
+    : frame_(frame), delegate_(delegate) {}
+
+FrameContextMenuController::~FrameContextMenuController() = default;
+
+void FrameContextMenuController::ShowContextMenuForViewImpl(
+    views::View* source,
+    const gfx::Point& point,
+    ui::MenuSourceType source_type) {
+  if (!MoveToDesksMenuDelegate::ShouldShowMoveToDesksMenu())
+    return;
+
+  DCHECK(delegate_);
+  if (!delegate_->ShouldShowContextMenu(source, point))
+    return;
+
+  if (!move_to_desks_menu_model_) {
+    move_to_desks_menu_model_ =
+        std::make_unique<chromeos::MoveToDesksMenuModel>(
+            std::make_unique<MoveToDesksMenuDelegate>(frame_),
+            /*add_title=*/true);
+  }
+
+  // Recreate the `menu_runner_` so the checked label of
+  // `move_to_desks_menu_model_` will be updated.
+  menu_runner_ = std::make_unique<views::MenuRunner>(
+      move_to_desks_menu_model_.get(), views::MenuRunner::CONTEXT_MENU);
+  menu_runner_->RunMenuAt(frame_, /*button_controller=*/nullptr,
+                          gfx::Rect(point, gfx::Size()),
+                          views::MenuAnchorPosition::kTopLeft, source_type);
+}
+
+}  // namespace ash
diff --git a/ash/frame/frame_context_menu_controller.h b/ash/frame/frame_context_menu_controller.h
new file mode 100644
index 0000000..06d5cccd
--- /dev/null
+++ b/ash/frame/frame_context_menu_controller.h
@@ -0,0 +1,66 @@
+// 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_FRAME_FRAME_CONTEXT_MENU_CONTROLLER_H_
+#define ASH_FRAME_FRAME_CONTEXT_MENU_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/views/context_menu_controller.h"
+
+namespace chromeos {
+class MoveToDesksMenuModel;
+}  // namespace chromeos
+
+namespace views {
+class View;
+class Widget;
+class MenuRunner;
+}  // namespace views
+
+namespace ash {
+
+// FrameContextMenuController is used to house the common code for displaying
+// the context menu of frames like `NonClientFrameViewAsh` and `WideFrameView`.
+class ASH_EXPORT FrameContextMenuController
+    : public views::ContextMenuController {
+ public:
+  class Delegate {
+   public:
+    // Given a `source` and a `screen_coords_point`, determine whether the
+    // context menu should be shown.
+    virtual bool ShouldShowContextMenu(
+        views::View* source,
+        const gfx::Point& screen_coords_point) = 0;
+
+   protected:
+    virtual ~Delegate() = default;
+  };
+
+  FrameContextMenuController(views::Widget* frame, Delegate* delegate);
+  FrameContextMenuController(const FrameContextMenuController&) = delete;
+  FrameContextMenuController& operator=(const FrameContextMenuController&) =
+      delete;
+  ~FrameContextMenuController() override;
+
+  // views::ContextMenuController:
+  void ShowContextMenuForViewImpl(views::View* source,
+                                  const gfx::Point& point,
+                                  ui::MenuSourceType source_type) override;
+
+ private:
+  // The widget that `this` controls the context menu for.
+  views::Widget* frame_;
+
+  // A delegate who is responsible for determining whether the context menu
+  // should be shown at a point.
+  Delegate* delegate_;
+
+  std::unique_ptr<chromeos::MoveToDesksMenuModel> move_to_desks_menu_model_;
+  std::unique_ptr<views::MenuRunner> menu_runner_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_FRAME_FRAME_CONTEXT_MENU_CONTROLLER_H_
diff --git a/ash/frame/non_client_frame_view_ash.cc b/ash/frame/non_client_frame_view_ash.cc
index b0e444f..5362cd9 100644
--- a/ash/frame/non_client_frame_view_ash.cc
+++ b/ash/frame/non_client_frame_view_ash.cc
@@ -220,7 +220,9 @@
 NonClientFrameViewAsh::NonClientFrameViewAsh(views::Widget* frame)
     : frame_(frame),
       header_view_(new HeaderView(frame, this)),
-      overlay_view_(new OverlayView(header_view_)) {
+      overlay_view_(new OverlayView(header_view_)),
+      frame_context_menu_controller_(
+          std::make_unique<FrameContextMenuController>(frame, this)) {
   DCHECK(frame_);
 
   header_view_->set_immersive_mode_changed_callback(base::BindRepeating(
@@ -250,7 +252,8 @@
 
   frame_window->SetProperty(kNonClientFrameViewAshKey, this);
 
-  header_view_->set_context_menu_controller(this);
+  header_view_->set_context_menu_controller(
+      frame_context_menu_controller_.get());
 }
 
 NonClientFrameViewAsh::~NonClientFrameViewAsh() = default;
@@ -365,47 +368,24 @@
   return gfx::Size(width, height);
 }
 
-void NonClientFrameViewAsh::ShowContextMenuForViewImpl(
-    View* source,
-    const gfx::Point& point,
-    ui::MenuSourceType source_type) {
-  if (!MoveToDesksMenuDelegate::ShouldShowMoveToDesksMenu())
-    return;
-
+bool NonClientFrameViewAsh::ShouldShowContextMenu(
+    views::View* source,
+    const gfx::Point& screen_coords_point) {
   if (header_view_->in_immersive_mode()) {
     // If the `header_view_` is in immersive mode, then a `NonClientHitTest`
     // will return HTCLIENT so manually check whether `point` lies inside
     // `header_view_`.
-    gfx::Point point_in_header_coords(point);
+    gfx::Point point_in_header_coords(screen_coords_point);
     views::View::ConvertPointToTarget(this, header_view_,
                                       &point_in_header_coords);
-    if (!header_view_->HitTestRect(
-            gfx::Rect(point_in_header_coords, gfx::Size(1, 1)))) {
-      return;
-    }
-  } else {
-    // Only show the context menu if `point` is in the caption area.
-    gfx::Point point_in_view_coords(point);
-    views::View::ConvertPointFromScreen(this, &point_in_view_coords);
-    if (NonClientHitTest(point_in_view_coords) != HTCAPTION)
-      return;
+    return header_view_->HitTestRect(
+        gfx::Rect(point_in_header_coords, gfx::Size(1, 1)));
   }
 
-  auto* widget = GetWidget();
-  if (!move_to_desks_menu_model_) {
-    move_to_desks_menu_model_ =
-        std::make_unique<chromeos::MoveToDesksMenuModel>(
-            std::make_unique<MoveToDesksMenuDelegate>(widget),
-            /*add_title=*/true);
-  }
-
-  // Recreate the `menu_runner_` so the checked label of
-  // `move_to_desks_menu_model_` will be updated.
-  menu_runner_ = std::make_unique<views::MenuRunner>(
-      move_to_desks_menu_model_.get(), views::MenuRunner::CONTEXT_MENU);
-  menu_runner_->RunMenuAt(widget, /*button_controller=*/nullptr,
-                          gfx::Rect(point, gfx::Size()),
-                          views::MenuAnchorPosition::kTopLeft, source_type);
+  // Only show the context menu if `screen_coords_point` is in the caption area.
+  gfx::Point point_in_view_coords(screen_coords_point);
+  views::View::ConvertPointFromScreen(this, &point_in_view_coords);
+  return NonClientHitTest(point_in_view_coords) == HTCAPTION;
 }
 
 void NonClientFrameViewAsh::SetShouldPaintHeader(bool paint) {
diff --git a/ash/frame/non_client_frame_view_ash.h b/ash/frame/non_client_frame_view_ash.h
index 2ff28550..8d89a25c 100644
--- a/ash/frame/non_client_frame_view_ash.h
+++ b/ash/frame/non_client_frame_view_ash.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ash/ash_export.h"
+#include "ash/frame/frame_context_menu_controller.h"
 #include "ash/frame/header_view.h"
 #include "ash/wm/overview/overview_observer.h"
 #include "base/macros.h"
@@ -15,19 +16,16 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/metadata/metadata_header_macros.h"
-#include "ui/views/context_menu_controller.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/window/non_client_view.h"
 
 namespace chromeos {
 class FrameCaptionButtonContainerView;
 class ImmersiveFullscreenController;
-class MoveToDesksMenuModel;
 }
 
 namespace views {
 class Widget;
-class MenuRunner;
 }
 
 namespace ash {
@@ -40,8 +38,9 @@
 // The window header overlay slides onscreen when the user hovers the mouse at
 // the top of the screen. See also views::CustomFrameView and
 // BrowserNonClientFrameViewAsh.
-class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView,
-                                         public views::ContextMenuController {
+class ASH_EXPORT NonClientFrameViewAsh
+    : public views::NonClientFrameView,
+      public FrameContextMenuController::Delegate {
  public:
   METADATA_HEADER(NonClientFrameViewAsh);
 
@@ -95,10 +94,9 @@
   gfx::Size GetMinimumSize() const override;
   gfx::Size GetMaximumSize() const override;
 
-  // views::ContextMenuController:
-  void ShowContextMenuForViewImpl(View* source,
-                                  const gfx::Point& point,
-                                  ui::MenuSourceType source_type) override;
+  // FrameContextMenuController::Delegate:
+  bool ShouldShowContextMenu(views::View* source,
+                             const gfx::Point& screen_coords_point) override;
 
   // If |paint| is false, we should not paint the header. Used for overview mode
   // with OnOverviewModeStarting() and OnOverviewModeEnded() to hide/show the
@@ -156,8 +154,7 @@
 
   std::unique_ptr<NonClientFrameViewAshImmersiveHelper> immersive_helper_;
 
-  std::unique_ptr<chromeos::MoveToDesksMenuModel> move_to_desks_menu_model_;
-  std::unique_ptr<views::MenuRunner> menu_runner_;
+  std::unique_ptr<FrameContextMenuController> frame_context_menu_controller_;
 
   base::CallbackListSubscription paint_as_active_subscription_ =
       frame_->RegisterPaintAsActiveChangedCallback(
diff --git a/ash/frame/wide_frame_view.cc b/ash/frame/wide_frame_view.cc
index d7f6997..886546d4 100644
--- a/ash/frame/wide_frame_view.cc
+++ b/ash/frame/wide_frame_view.cc
@@ -85,7 +85,10 @@
   header_view_->UpdateCaptionButtons();
 }
 
-WideFrameView::WideFrameView(views::Widget* target) : target_(target) {
+WideFrameView::WideFrameView(views::Widget* target)
+    : target_(target),
+      frame_context_menu_controller_(
+          std::make_unique<FrameContextMenuController>(target_, this)) {
   // WideFrameView is owned by its client, not by Views.
   SetOwnedByWidget(false);
   display::Screen::GetScreen()->AddObserver(this);
@@ -97,6 +100,8 @@
   header_view_ = new HeaderView(target, /*frame view=*/nullptr);
   AddChildView(header_view_);
   GetTargetHeaderView()->SetShouldPaintHeader(false);
+  header_view_->set_context_menu_controller(
+      frame_context_menu_controller_.get());
 
   views::Widget::InitParams params;
   params.type = views::Widget::InitParams::TYPE_POPUP;
@@ -221,6 +226,16 @@
   return header_view_->GetVisibleBoundsInScreen();
 }
 
+bool WideFrameView::ShouldShowContextMenu(
+    View* source,
+    const gfx::Point& screen_coords_point) {
+  gfx::Point point_in_header_coords(screen_coords_point);
+  views::View::ConvertPointToTarget(this, header_view_,
+                                    &point_in_header_coords);
+  return header_view_->HitTestRect(
+      gfx::Rect(point_in_header_coords, gfx::Size(1, 1)));
+}
+
 HeaderView* WideFrameView::GetTargetHeaderView() {
   auto* frame_view = static_cast<NonClientFrameViewAsh*>(
       target_->non_client_view()->frame_view());
diff --git a/ash/frame/wide_frame_view.h b/ash/frame/wide_frame_view.h
index cb4e52c..eb0c116 100644
--- a/ash/frame/wide_frame_view.h
+++ b/ash/frame/wide_frame_view.h
@@ -6,6 +6,7 @@
 #define ASH_FRAME_WIDE_FRAME_VIEW_H_
 
 #include "ash/ash_export.h"
+#include "ash/frame/frame_context_menu_controller.h"
 #include "ash/wm/overview/overview_observer.h"
 #include "chromeos/ui/frame/caption_buttons/caption_button_model.h"
 #include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_delegate.h"
@@ -38,7 +39,8 @@
     : public views::WidgetDelegateView,
       public aura::WindowObserver,
       public display::DisplayObserver,
-      public chromeos::ImmersiveFullscreenControllerDelegate {
+      public chromeos::ImmersiveFullscreenControllerDelegate,
+      public FrameContextMenuController::Delegate {
  public:
   explicit WideFrameView(views::Widget* target);
   ~WideFrameView() override;
@@ -75,6 +77,10 @@
   void SetVisibleFraction(double visible_fraction) override;
   std::vector<gfx::Rect> GetVisibleBoundsInScreen() const override;
 
+  // FrameContextMenuController::Delegate:
+  bool ShouldShowContextMenu(views::View* source,
+                             const gfx::Point& screen_coords_point) override;
+
   HeaderView* GetTargetHeaderView();
 
   // The target widget this frame will control.
@@ -84,6 +90,8 @@
 
   HeaderView* header_view_ = nullptr;
 
+  std::unique_ptr<FrameContextMenuController> frame_context_menu_controller_;
+
   // Called when |target_|'s "paint as active" state has changed.
   void PaintAsActiveChanged();
 
diff --git a/ash/public/cpp/clipboard_history_controller.h b/ash/public/cpp/clipboard_history_controller.h
index 09020be..3b3112a 100644
--- a/ash/public/cpp/clipboard_history_controller.h
+++ b/ash/public/cpp/clipboard_history_controller.h
@@ -69,8 +69,8 @@
   // Increment the 'new' feature badge shown count.
   virtual void MarkNewFeatureBadgeShown() = 0;
 
-  // Increment the screenshot notification nudge shown count.
-  virtual void MarkScreenshotNotificationNudgeShown() = 0;
+  // Notify the clipboard history that a screenshot notification was created.
+  virtual void OnScreenshotNotificationCreated() = 0;
 
   // Creates a ScopedClipboardHistoryPause, which pauses ClipboardHistory for
   // its lifetime.
diff --git a/ash/system/status_area_widget_unittest.cc b/ash/system/status_area_widget_unittest.cc
index e6c75f7..296256f 100644
--- a/ash/system/status_area_widget_unittest.cc
+++ b/ash/system/status_area_widget_unittest.cc
@@ -267,8 +267,6 @@
   void SetUp() override {
     // Initializing NetworkHandler before ash is more like production.
     AshTestBase::SetUp();
-    chromeos::CellularMetricsLogger::RegisterLocalStatePrefs(
-        local_state_.registry());
     chromeos::CellularESimProfileHandlerImpl::RegisterLocalStatePrefs(
         local_state_.registry());
     chromeos::NetworkMetadataStore::RegisterPrefs(profile_prefs_.registry());
diff --git a/ash/system/unified/unified_slider_bubble_controller.cc b/ash/system/unified/unified_slider_bubble_controller.cc
index 1384a8d..ef68a38 100644
--- a/ash/system/unified/unified_slider_bubble_controller.cc
+++ b/ash/system/unified/unified_slider_bubble_controller.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/unified/unified_slider_bubble_controller.h"
 
+#include "ash/constants/ash_features.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shelf/shelf.h"
@@ -99,6 +100,9 @@
 }
 
 void UnifiedSliderBubbleController::OnInputMuteChanged(bool mute_on) {
+  if (!features::IsMicMuteNotificationsEnabled())
+    return;
+
   ShowBubble(SLIDER_TYPE_MIC);
 }
 
diff --git a/build/README.md b/build/README.md
index f9dde97..26671250 100644
--- a/build/README.md
+++ b/build/README.md
@@ -11,7 +11,7 @@
 
 Changes to `//build` should be landed in the Chromium repo. They will then be
 replicated to the stand-alone [build repo](https://chromium.googlesource.com/chromium/src/build)
-by the [gsubtreed tool.](https://chromium.googlesource.com/infra/infra/+/master/infra/services/gsubtreed)
+by the [gsubtreed tool.](https://chromium.googlesource.com/infra/infra/+/main/infra/services/gsubtreed)
 Note: You can find all directories already  available through gsubtreed in the
 [list of all chromium repos](https://chromium.googlesource.com/).
 
diff --git a/build/android/adb_gdb b/build/android/adb_gdb
index 6de4273..0923210 100755
--- a/build/android/adb_gdb
+++ b/build/android/adb_gdb
@@ -986,7 +986,7 @@
   echo "GDB wrapper script: $SYM_GDB"
   echo "App executable: $SYM_EXE"
   echo "gdbinit: $SYM_INIT"
-  echo "Connect with vscode: https://chromium.googlesource.com/chromium/src/+/master/docs/vscode.md#Launch-Commands"
+  echo "Connect with vscode: https://chromium.googlesource.com/chromium/src/+/main/docs/vscode.md#Launch-Commands"
   echo "Showing gdbserver logs. Press Ctrl-C to disconnect."
   tail -f "$GDBSERVER_LOG"
 else
diff --git a/build/android/docs/build_config.md b/build/android/docs/build_config.md
index 8a301c8..06a4873c 100644
--- a/build/android/docs/build_config.md
+++ b/build/android/docs/build_config.md
@@ -163,6 +163,6 @@
 python tools/md_browser/md_browser.py -d /tmp /tmp/format.md
 ```
 
-[build/android/gyp/]: https://chromium.googlesource.com/chromium/src/build/+/master/android/gyp/
+[build/android/gyp/]: https://chromium.googlesource.com/chromium/src/build/+/main/android/gyp/
 [gn_write_build_config]: https://cs.chromium.org/chromium/src/build/config/android/internal_rules.gni?q=write_build_config&sq=package:chromium
-[write_build_config_py]: https://chromium.googlesource.com/chromium/src/build/+/master/android/gyp/write_build_config.py
+[write_build_config_py]: https://chromium.googlesource.com/chromium/src/build/+/main/android/gyp/write_build_config.py
diff --git a/build/android/docs/lint.md b/build/android/docs/lint.md
index 4ba13d7..67e2f8bf3 100644
--- a/build/android/docs/lint.md
+++ b/build/android/docs/lint.md
@@ -83,7 +83,7 @@
 that are too hard (or not possible) to suppress locally, and permanently
 ignoring warnings only for this target. To permanently ignore a warning for all
 targets, add the warning to the `_DISABLED_ALWAYS` list in
-[build/android/gyp/lint.py](https://source.chromium.org/chromium/chromium/src/+/master:build/android/gyp/lint.py).
+[build/android/gyp/lint.py](https://source.chromium.org/chromium/chromium/src/+/main:build/android/gyp/lint.py).
 Disabling globally makes lint a bit faster.
 
 The exception to the above rule is for warnings that affect multiple languages.
diff --git a/build/android/gyp/assert_static_initializers.py b/build/android/gyp/assert_static_initializers.py
index 31f2a77..a6f74ba 100755
--- a/build/android/gyp/assert_static_initializers.py
+++ b/build/android/gyp/assert_static_initializers.py
@@ -174,7 +174,7 @@
       print('    //tools/binary_size/diagnose_bloat.py')
       print()
       print('For more information:')
-      print('    https://chromium.googlesource.com/chromium/src/+/master/docs/'
+      print('    https://chromium.googlesource.com/chromium/src/+/main/docs/'
             'static_initializers.md')
     sys.exit(1)
 
diff --git a/build/android/gyp/lint.py b/build/android/gyp/lint.py
index 18891e1..87d45d6 100755
--- a/build/android/gyp/lint.py
+++ b/build/android/gyp/lint.py
@@ -23,7 +23,7 @@
 from util import manifest_utils
 from util import server_utils
 
-_LINT_MD_URL = 'https://chromium.googlesource.com/chromium/src/+/master/build/android/docs/lint.md'  # pylint: disable=line-too-long
+_LINT_MD_URL = 'https://chromium.googlesource.com/chromium/src/+/main/build/android/docs/lint.md'  # pylint: disable=line-too-long
 
 # These checks are not useful for chromium.
 _DISABLED_ALWAYS = [
diff --git a/build/android/gyp/proguard.py b/build/android/gyp/proguard.py
index bd1c499..6444f6b 100755
--- a/build/android/gyp/proguard.py
+++ b/build/android/gyp/proguard.py
@@ -518,7 +518,7 @@
           stderr += """
 You may need to update build configs to run FragmentActivityReplacer for
 additional targets. See
-https://chromium.googlesource.com/chromium/src.git/+/master/docs/ui/android/bytecode_rewriting.md.
+https://chromium.googlesource.com/chromium/src.git/+/main/docs/ui/android/bytecode_rewriting.md.
 """
       elif had_unfiltered_items:
         # Left only with empty headings. All indented items filtered out.
diff --git a/build/build_config.h b/build/build_config.h
index 0f1cd57..daf51ff 100644
--- a/build/build_config.h
+++ b/build/build_config.h
@@ -94,7 +94,7 @@
 #error Please add support for your platform in build/build_config.h
 #endif
 // NOTE: Adding a new port? Please follow
-// https://chromium.googlesource.com/chromium/src/+/master/docs/new_port_policy.md
+// https://chromium.googlesource.com/chromium/src/+/main/docs/new_port_policy.md
 
 #if defined(OS_MAC) || defined(OS_IOS)
 #define OS_APPLE 1
diff --git a/build/chromeos/test_runner.py b/build/chromeos/test_runner.py
index b5280a1b..50168b6 100755
--- a/build/chromeos/test_runner.py
+++ b/build/chromeos/test_runner.py
@@ -55,6 +55,7 @@
 LAB_DUT_HOSTNAME = 'variable_chromeos_device_hostname'
 
 SYSTEM_LOG_LOCATIONS = [
+    '/home/chronos/crash/',
     '/var/log/chrome/',
     '/var/log/messages',
     '/var/log/ui/',
@@ -174,7 +175,7 @@
     # Traps SIGTERM and kills all child processes of cros_run_test when it's
     # caught. This will allow us to capture logs from the device if a test hangs
     # and gets timeout-killed by swarming. See also:
-    # https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
+    # https://chromium.googlesource.com/infra/luci/luci-py/+/main/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
     test_proc = None
 
     def _kill_child_procs(trapped_signal, _):
@@ -922,7 +923,7 @@
   tast_test_parser = subparsers.add_parser(
       'tast',
       help='Runs a device-side set of Tast tests. For more details, see: '
-      'https://chromium.googlesource.com/chromiumos/platform/tast/+/master/docs/running_tests.md'
+      'https://chromium.googlesource.com/chromiumos/platform/tast/+/main/docs/running_tests.md'
   )
   tast_test_parser.set_defaults(func=device_test)
   tast_test_parser.add_argument(
diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn
index 0ef73ab..46bc476 100644
--- a/build/config/BUILDCONFIG.gn
+++ b/build/config/BUILDCONFIG.gn
@@ -159,7 +159,7 @@
   # When false, components will be linked statically.
   #
   # For more information see
-  # https://chromium.googlesource.com/chromium/src/+/master/docs/component_build.md
+  # https://chromium.googlesource.com/chromium/src/+/main/docs/component_build.md
   is_component_build = is_debug && current_os != "ios"
 }
 
diff --git a/build/config/chromeos/args.gni b/build/config/chromeos/args.gni
index 3be4f27..5ae62774 100644
--- a/build/config/chromeos/args.gni
+++ b/build/config/chromeos/args.gni
@@ -21,7 +21,7 @@
   # linux-chromeos, so some have compile-time asserts that intentionally fail
   # when this build flag is set. Build and run the tests for linux-chromeos
   # instead.
-  # https://chromium.googlesource.com/chromium/src/+/master/docs/chromeos_build_instructions.md
-  # https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md
+  # https://chromium.googlesource.com/chromium/src/+/main/docs/chromeos_build_instructions.md
+  # https://chromium.googlesource.com/chromiumos/docs/+/main/simple_chrome_workflow.md
   is_chromeos_device = false
 }
diff --git a/build/config/chromeos/rules.gni b/build/config/chromeos/rules.gni
index 7d793e2f..0ccbcb2 100644
--- a/build/config/chromeos/rules.gni
+++ b/build/config/chromeos/rules.gni
@@ -365,7 +365,7 @@
   if (!defined(tast_attr_expr) && !defined(tast_tests)) {
     # The following expression filters out all non-critical tests. See the link
     # below for more details:
-    # https://chromium.googlesource.com/chromiumos/platform/tast/+/master/docs/test_attributes.md
+    # https://chromium.googlesource.com/chromiumos/platform/tast/+/main/docs/test_attributes.md
     tast_attr_expr = "\"group:mainline\" && \"dep:chrome\""
 
     if (defined(enable_tast_informational_tests) &&
@@ -451,7 +451,7 @@
     # use it for testing. To support running lacros tast tests from Chromium CI,
     # a Var is added to support pointing the tast tests to use a specified
     # pre-deployed lacros-chrome. The location is decided by:
-    # https://source.chromium.org/chromium/chromium/src/+/master:third_party/chromite/scripts/deploy_chrome.py;l=80;drc=86f1234a4be8e9574442e076cdc835897f7bea61
+    # https://source.chromium.org/chromium/chromium/src/+/main:third_party/chromite/scripts/deploy_chrome.py;l=80;drc=86f1234a4be8e9574442e076cdc835897f7bea61
     tast_vars = [ "lacrosDeployedBinary=/usr/local/lacros-chrome" ]
 
     data_deps = [
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index c848dd7f..572d7a8a 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -105,7 +105,7 @@
   # Technology (CET). If Windows version and hardware supports the feature and
   # it's enabled by OS then additional validation of return address will be
   # performed as mitigation against Return-oriented programming (ROP).
-  # https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md#cet-shadow-stack
+  # https://chromium.googlesource.com/chromium/src/+/main/docs/design/sandbox.md#cet-shadow-stack
   enable_cet_shadow_stack = target_cpu == "x64"
 }
 
@@ -216,7 +216,7 @@
 # results independent of the checkout and build directory names, which
 # in turn is important for goma compile hit rate.
 # Setting this to true may make it harder to debug binaries on Linux, see
-# https://chromium.googlesource.com/chromium/src/+/master/docs/linux/debugging.md#Source-level-debug-with-fdebug_compilation_dir
+# https://chromium.googlesource.com/chromium/src/+/main/docs/linux/debugging.md#Source-level-debug-with-fdebug_compilation_dir
 # It's not clear if the crash server will correctly handle dSYMs with relative
 # paths, so we disable this feature for official benefit. The main benefit is
 # deterministic builds to reduce compile times, so this is less relevant for
diff --git a/build/fuchsia/binary_sizes.py b/build/fuchsia/binary_sizes.py
index da874bb..52d0599 100755
--- a/build/fuchsia/binary_sizes.py
+++ b/build/fuchsia/binary_sizes.py
@@ -96,7 +96,7 @@
   """Create test results data to write to JSON test results file.
 
   The JSON data format is defined in
-  https://chromium.googlesource.com/chromium/src/+/master/docs/testing/json_test_results_format.md
+  https://chromium.googlesource.com/chromium/src/+/main/docs/testing/json_test_results_format.md
   """
 
   results = {
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 16bde68..8862a80 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-4.20210518.3.1
+4.20210519.2.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 16bde68..8862a80 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-4.20210518.3.1
+4.20210519.2.1
diff --git a/build/install-build-deps.sh b/build/install-build-deps.sh
index 407915c..13076f3 100755
--- a/build/install-build-deps.sh
+++ b/build/install-build-deps.sh
@@ -5,7 +5,7 @@
 # found in the LICENSE file.
 
 # Script to install everything needed to build chromium (well, ideally, anyway)
-# See https://chromium.googlesource.com/chromium/src/+/master/docs/linux/build_instructions.md
+# See https://chromium.googlesource.com/chromium/src/+/main/docs/linux/build_instructions.md
 
 usage() {
   echo "Usage: $0 [--options]"
diff --git a/buildtools/clang_format/README.txt b/buildtools/clang_format/README.txt
index 29b446c..20e72c3 100644
--- a/buildtools/clang_format/README.txt
+++ b/buildtools/clang_format/README.txt
@@ -2,7 +2,7 @@
 downloaded from Google Storage by gclient runhooks for the current platform.
 
 For a walkthrough on how to maintain these binaries:
-  https://chromium.googlesource.com/chromium/src/+/master/docs/updating_clang_format_binaries.md
+  https://chromium.googlesource.com/chromium/src/+/main/docs/updating_clang_format_binaries.md
 
 To upload a file:
   python ~/depot_tools/upload_to_google_storage.py -b chromium-clang-format <FILENAME>
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 8d5a1965..baa0c8ab 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -918,6 +918,7 @@
     "//chrome/browser/payments/android:junit",
     "//chrome/browser/performance_hints/android:java",
     "//chrome/browser/policy/android:java",
+    "//chrome/browser/policy/android:junit",
     "//chrome/browser/preferences:java",
     "//chrome/browser/preferences:preferences_junit_tests",
     "//chrome/browser/profiles/android:java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 050b531..c2de7c1 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -752,6 +752,7 @@
   "java/src/org/chromium/chrome/browser/media/ui/ChromeMediaNotificationControllerDelegate.java",
   "java/src/org/chromium/chrome/browser/media/ui/ChromeMediaNotificationManager.java",
   "java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java",
+  "java/src/org/chromium/chrome/browser/messages/ChromeMessageAutodismissDurationProvider.java",
   "java/src/org/chromium/chrome/browser/messages/ChromeMessageQueueMediator.java",
   "java/src/org/chromium/chrome/browser/messages/MessageContainerCoordinator.java",
   "java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index bdf5977a..37e1a6f 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -129,6 +129,7 @@
   "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java",
   "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationThrottlerTest.java",
   "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTitleUpdatedTest.java",
+  "junit/src/org/chromium/chrome/browser/messages/ChromeMessageAutodismissDurationProviderTest.java",
   "junit/src/org/chromium/chrome/browser/messages/ChromeMessageQueueMediatorTest.java",
   "junit/src/org/chromium/chrome/browser/metrics/VariationsSessionTest.java",
   "junit/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsUnitTest.java",
diff --git a/chrome/android/features/autofill_assistant/OWNERS b/chrome/android/features/autofill_assistant/OWNERS
index 829eb83..ed6d5f0 100644
--- a/chrome/android/features/autofill_assistant/OWNERS
+++ b/chrome/android/features/autofill_assistant/OWNERS
@@ -1 +1 @@
-file://components/autofill_assistant/OWNERS
\ No newline at end of file
+file://components/autofill_assistant/OWNERS
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 3d27db66..5435c36 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
@@ -484,8 +484,6 @@
         // hasUnreadContent() changes.
         Callback<Boolean> callback = hasUnreadContent -> {
             headerModel.set(SectionHeaderProperties.UNREAD_CONTENT_KEY, hasUnreadContent);
-            headerModel.set(SectionHeaderProperties.HEADER_ACCESSIBILITY_TEXT_KEY,
-                    hasUnreadContent ? accessibilityTextUnreadContent : null);
         };
         callback.onResult(stream.hasUnreadContent().addObserver(callback));
 
diff --git a/chrome/android/java/res/layout/new_tab_page_section_tab.xml b/chrome/android/java/res/layout/new_tab_page_section_tab.xml
index 9166b6e6b..2ffbcde6 100644
--- a/chrome/android/java/res/layout/new_tab_page_section_tab.xml
+++ b/chrome/android/java/res/layout/new_tab_page_section_tab.xml
@@ -3,12 +3,33 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/text1"
-    android:gravity="center"
-    android:maxLines="2"
-    android:layout_width="wrap_content"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:drawablePadding="2dp"
-    android:textAppearance="@style/TextAppearance.Button.Text.Blue"
-    />
+    tools:ignore="UseCompoundDrawables"
+    android:gravity="center_vertical"
+    android:orientation="horizontal"
+    android:focusable="true" >
+
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_gravity="center_horizontal"
+        android:maxLines="2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Button.Text.Blue"
+        />
+
+    <ImageView
+        android:id="@+id/badge"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:contentDescription="@string/accessibility_ntp_following_unread_content"
+        android:src="@drawable/new_tab_section_header_content_circle"
+        android:paddingStart="2dp"
+        android:layout_gravity="top|end"
+        android:visibility="gone"
+        />
+
+</FrameLayout>
diff --git a/chrome/android/java/res_base/OWNERS b/chrome/android/java/res_base/OWNERS
index c737d6d5..8708130f 100644
--- a/chrome/android/java/res_base/OWNERS
+++ b/chrome/android/java/res_base/OWNERS
@@ -3,4 +3,4 @@
 # we don't want them.
 set noparent
 
-file://ui/android/java/res/LAYOUT_OWNERS
\ No newline at end of file
+file://ui/android/java/res/LAYOUT_OWNERS
diff --git a/chrome/android/java/res_chromium/OWNERS b/chrome/android/java/res_chromium/OWNERS
index c737d6d5..8708130f 100644
--- a/chrome/android/java/res_chromium/OWNERS
+++ b/chrome/android/java/res_chromium/OWNERS
@@ -3,4 +3,4 @@
 # we don't want them.
 set noparent
 
-file://ui/android/java/res/LAYOUT_OWNERS
\ No newline at end of file
+file://ui/android/java/res/LAYOUT_OWNERS
diff --git a/chrome/android/java/res_chromium_base/OWNERS b/chrome/android/java/res_chromium_base/OWNERS
index c737d6d5..8708130f 100644
--- a/chrome/android/java/res_chromium_base/OWNERS
+++ b/chrome/android/java/res_chromium_base/OWNERS
@@ -3,4 +3,4 @@
 # we don't want them.
 set noparent
 
-file://ui/android/java/res/LAYOUT_OWNERS
\ No newline at end of file
+file://ui/android/java/res/LAYOUT_OWNERS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/OWNERS
index c4d2e224..50a21bb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/OWNERS
@@ -1 +1 @@
-file://components/autofill/OWNERS
\ No newline at end of file
+file://components/autofill/OWNERS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/component_updater/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/component_updater/OWNERS
index 7c78796..3a43fc5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/component_updater/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/component_updater/OWNERS
@@ -1,3 +1,3 @@
 tiborg@chromium.org
 
-file://components/component_updater/OWNERS
\ No newline at end of file
+file://components/component_updater/OWNERS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/history/OWNERS
index 05d32ba..0fa757c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/history/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/history/OWNERS
@@ -1 +1 @@
-twellington@chromium.org
\ No newline at end of file
+twellington@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/homepage/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/homepage/OWNERS
index 588f95f..cc92f624 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/homepage/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/homepage/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/OWNERS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/messages/ChromeMessageAutodismissDurationProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/messages/ChromeMessageAutodismissDurationProvider.java
new file mode 100644
index 0000000..07c3a2e2
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/messages/ChromeMessageAutodismissDurationProvider.java
@@ -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.
+
+package org.chromium.chrome.browser.messages;
+
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT;
+
+import android.os.Build;
+import android.text.format.DateUtils;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
+import org.chromium.components.messages.MessageAutodismissDurationProvider;
+
+/**
+ * Implementation of {@link MessageAutodismissDurationProvider}.
+ */
+public class ChromeMessageAutodismissDurationProvider
+        implements MessageAutodismissDurationProvider {
+    private long mAutodismissDurationMs;
+    private long mAutodismissDurationWithA11yMs;
+    public ChromeMessageAutodismissDurationProvider() {
+        mAutodismissDurationMs = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                ChromeFeatureList.MESSAGES_FOR_ANDROID_INFRASTRUCTURE, "autodismiss_duration_ms",
+                10 * (int) DateUtils.SECOND_IN_MILLIS);
+
+        mAutodismissDurationWithA11yMs = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                ChromeFeatureList.MESSAGES_FOR_ANDROID_INFRASTRUCTURE,
+                "autodismiss_duration_with_a11y_ms", 30 * (int) DateUtils.SECOND_IN_MILLIS);
+    }
+
+    @Override
+    public long get(long customDuration) {
+        long nonA11yDuration = Math.max(mAutodismissDurationMs, customDuration);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+                && ChromeAccessibilityUtil.get().isAccessibilityEnabled()) {
+            return (long) ChromeAccessibilityUtil.get().getRecommendedTimeoutMillis(
+                    (int) nonA11yDuration,
+                    FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS | FLAG_CONTENT_TEXT);
+        }
+        return ChromeAccessibilityUtil.get().isAccessibilityEnabled()
+                ? Math.max(mAutodismissDurationWithA11yMs, nonA11yDuration)
+                : nonA11yDuration;
+    }
+
+    @VisibleForTesting
+    public void setDefaultAutodismissDurationMsForTesting(long duration) {
+        mAutodismissDurationMs = duration;
+    }
+
+    public void setDefaultAutodismissDurationWithA11yMsForTesting(long duration) {
+        mAutodismissDurationWithA11yMs = duration;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/messages/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/messages/OWNERS
index 12c1e52..3a88845 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/messages/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/messages/OWNERS
@@ -1 +1 @@
-file://components/messages/OWNERS
\ No newline at end of file
+file://components/messages/OWNERS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderProperties.java
index aa276e9..620bba2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderProperties.java
@@ -13,10 +13,6 @@
     /** The header text to be shown. */
     public static final PropertyModel.WritableObjectPropertyKey<String> HEADER_TEXT_KEY =
             new PropertyModel.WritableObjectPropertyKey<>();
-    /** The accessibility text for the header. */
-    public static final PropertyModel
-            .WritableObjectPropertyKey<String> HEADER_ACCESSIBILITY_TEXT_KEY =
-            new PropertyModel.WritableObjectPropertyKey<>();
     public static final PropertyModel.WritableBooleanPropertyKey UNREAD_CONTENT_KEY =
             new PropertyModel.WritableBooleanPropertyKey();
     public static final PropertyModel.WritableObjectPropertyKey<Runnable> ON_SELECT_CALLBACK_KEY =
@@ -24,8 +20,7 @@
 
     public static PropertyModel createSectionHeader(String headerText) {
         return new PropertyModel
-                .Builder(ON_SELECT_CALLBACK_KEY, HEADER_TEXT_KEY, HEADER_ACCESSIBILITY_TEXT_KEY,
-                        UNREAD_CONTENT_KEY)
+                .Builder(ON_SELECT_CALLBACK_KEY, HEADER_TEXT_KEY, UNREAD_CONTENT_KEY)
                 .with(HEADER_TEXT_KEY, headerText)
                 .with(UNREAD_CONTENT_KEY, false)
                 .build();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderView.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderView.java
index a6bd041..274ce29 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderView.java
@@ -10,10 +10,7 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
 import android.util.AttributeSet;
-import android.util.TypedValue;
 import android.view.TouchDelegate;
 import android.view.View;
 import android.widget.ImageView;
@@ -21,7 +18,6 @@
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
-import androidx.core.content.res.ResourcesCompat;
 
 import com.google.android.material.tabs.TabLayout;
 
@@ -46,6 +42,7 @@
  * manage the feed.
  */
 public class SectionHeaderView extends LinearLayout {
+    private static final String TAG = "SectionHeaderView";
     private static final int ANIMATION_DURATION_MS = 200;
 
     /** OnTabSelectedListener that delegates calls to the SectionHeadSelectedListener. */
@@ -165,19 +162,20 @@
      * Does nothing if index is invalid. Make sure to call addTab() beforehand.
      *
      * @param text Text to set the tab to.
-     * @param accessibilityText The optional content description for the header.
      * @param hasUnreadContent Whether there is unread content.
      * @param index Index of the tab to set.
      */
-    void setHeaderAt(
-            String text, @Nullable String accessibilityText, boolean hasUnreadContent, int index) {
+    void setHeaderAt(String text, boolean hasUnreadContent, int index) {
         TabLayout.Tab tab = getTabAt(index);
         if (tab != null) {
             tab.setText(text);
-            tab.setContentDescription(accessibilityText);
             TextView textView = (TextView) tab.getCustomView().findViewById(android.R.id.text1);
-            textView.setCompoundDrawablesWithIntrinsicBounds(
-                    null, null, hasUnreadContent ? makeUnreadContentIndicator() : null, null);
+            ImageView badgeView = tab.getCustomView().findViewById(R.id.badge);
+            if (hasUnreadContent) {
+                badgeView.setVisibility(View.VISIBLE);
+            } else {
+                badgeView.setVisibility(View.GONE);
+            }
         }
     }
 
@@ -186,15 +184,6 @@
         return mTabLayout != null ? mTabLayout.getTabAt(index) : null;
     }
 
-    private Drawable makeUnreadContentIndicator() {
-        // Use insets to shift the shape up.
-        final int fiveDpInPx = (int) TypedValue.applyDimension(
-                TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());
-        return new InsetDrawable(ResourcesCompat.getDrawable(getResources(),
-                                         R.drawable.new_tab_section_header_content_circle, null),
-                0, -fiveDpInPx, 0, fiveDpInPx);
-    }
-
     /**
      * @param index The index of the tab to set as active. Does nothing if index is invalid.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderViewBinder.java
index cfa92e6..648044cb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderViewBinder.java
@@ -77,8 +77,7 @@
             SectionHeaderView view, int index, int count, PropertyKey payload) {
         PropertyModel header = headers.get(0);
         if (payload == null || payload == SectionHeaderProperties.HEADER_TEXT_KEY
-                || payload == SectionHeaderProperties.UNREAD_CONTENT_KEY
-                || payload == SectionHeaderProperties.HEADER_ACCESSIBILITY_TEXT_KEY) {
+                || payload == SectionHeaderProperties.UNREAD_CONTENT_KEY) {
             // Only use 1st tab for legacy headerText;
             view.setHeaderText(header.get(SectionHeaderProperties.HEADER_TEXT_KEY));
 
@@ -88,7 +87,6 @@
                 boolean hasUnreadContent = tabModel.get(SectionHeaderProperties.UNREAD_CONTENT_KEY);
 
                 view.setHeaderAt(tabModel.get(SectionHeaderProperties.HEADER_TEXT_KEY),
-                        tabModel.get(SectionHeaderProperties.HEADER_ACCESSIBILITY_TEXT_KEY),
                         hasUnreadContent, i);
             }
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/paint_preview/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/paint_preview/OWNERS
index 124e026e..5736463 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/paint_preview/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/paint_preview/OWNERS
@@ -1 +1 @@
-file://components/paint_preview/OWNERS
\ No newline at end of file
+file://components/paint_preview/OWNERS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/payments/OWNERS
index 02824e0d..47d8657 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/OWNERS
@@ -2,4 +2,4 @@
 file://components/payments/OWNERS
 
 per-file *TypeConverter*.*=set noparent
-per-file *TypeConverter*.*=file://ipc/SECURITY_OWNERS
\ No newline at end of file
+per-file *TypeConverter*.*=file://ipc/SECURITY_OWNERS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/OWNERS
index c1e9864a9..78c3488 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/ntp/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/ntp/OWNERS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/OWNERS
index 125bc14..de7517a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/OWNERS
@@ -1 +1 @@
-file://chrome/browser/android/explore_sites/OWNERS
\ No newline at end of file
+file://chrome/browser/android/explore_sites/OWNERS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/OWNERS
index 125bc14..de7517a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/OWNERS
@@ -1 +1 @@
-file://chrome/browser/android/explore_sites/OWNERS
\ No newline at end of file
+file://chrome/browser/android/explore_sites/OWNERS
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 0a716aa4..7087355 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
@@ -4,15 +4,10 @@
 
 package org.chromium.chrome.browser.ui;
 
-import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
-import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
-import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT;
-
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -57,6 +52,7 @@
 import org.chromium.chrome.browser.lifecycle.DestroyObserver;
 import org.chromium.chrome.browser.lifecycle.InflationObserver;
 import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
+import org.chromium.chrome.browser.messages.ChromeMessageAutodismissDurationProvider;
 import org.chromium.chrome.browser.messages.ChromeMessageQueueMediator;
 import org.chromium.chrome.browser.messages.MessageContainerCoordinator;
 import org.chromium.chrome.browser.omnibox.OmniboxFocusReason;
@@ -90,7 +86,6 @@
 import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinatorFactory;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
-import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.chrome.browser.vr.VrModuleProvider;
 import org.chromium.chrome.features.start_surface.StartSurface;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
@@ -476,27 +471,9 @@
             MessageContainer container = mActivity.findViewById(R.id.message_container);
             mMessageContainerCoordinator =
                     new MessageContainerCoordinator(container, mBrowserControlsManager);
-            long autodismissDurationMs = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
-                    ChromeFeatureList.MESSAGES_FOR_ANDROID_INFRASTRUCTURE,
-                    "autodismiss_duration_ms", 10 * (int) DateUtils.SECOND_IN_MILLIS);
-
-            long autodismissDurationWithA11yMs = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
-                    ChromeFeatureList.MESSAGES_FOR_ANDROID_INFRASTRUCTURE,
-                    "autodismiss_duration_with_a11y_ms", 30 * (int) DateUtils.SECOND_IN_MILLIS);
-
-            Supplier<Long> autodismissDurationSupplier = () -> {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-                    return (long) ChromeAccessibilityUtil.get().getRecommendedTimeoutMillis(
-                            (int) autodismissDurationMs,
-                            FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS | FLAG_CONTENT_TEXT);
-                }
-                return ChromeAccessibilityUtil.get().isAccessibilityEnabled()
-                        ? autodismissDurationWithA11yMs
-                        : autodismissDurationMs;
-            };
             mMessageDispatcher = MessagesFactory.createMessageDispatcher(container,
                     mMessageContainerCoordinator::getMessageMaxTranslation,
-                    autodismissDurationSupplier,
+                    new ChromeMessageAutodismissDurationProvider(),
                     mActivity.getWindowAndroid()::startAnimationOverContent);
             mMessageQueueMediator = new ChromeMessageQueueMediator(mBrowserControlsManager,
                     mMessageContainerCoordinator, mActivity.getFullscreenManager(),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java
index de14be5..f926125 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java
@@ -22,12 +22,14 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.infobar.InfoBarContainer;
 import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.components.infobars.InfoBar;
 import org.chromium.components.safe_browsing.SafeBrowsingApiBridge;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
@@ -43,6 +45,7 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@DisableFeatures({ChromeFeatureList.MESSAGES_FOR_ANDROID_INFRASTRUCTURE})
 public class PopupTest {
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/OWNERS
index e716fed5..4f9c1ee4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/OWNERS
@@ -1,2 +1,2 @@
 file://chrome/android/java/src/org/chromium/chrome/browser/autofill/OWNERS
-per-file AutofillUpstreamTest.java=file://components/autofill/core/browser/payments/OWNERS
\ No newline at end of file
+per-file AutofillUpstreamTest.java=file://components/autofill/core/browser/payments/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/OWNERS
index c4d2e224..50a21bb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/OWNERS
@@ -1 +1 @@
-file://components/autofill/OWNERS
\ No newline at end of file
+file://components/autofill/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/OWNERS
index e7e9c00..50bb1fa7b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/bookmarks/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/bookmarks/OWNERS
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 041d4d3..2d40584a 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
@@ -514,6 +514,7 @@
      */
     @Test
     @SmallTest
+    @DisableFeatures({ChromeFeatureList.MESSAGES_FOR_ANDROID_INFRASTRUCTURE})
     public void testTabReparentingInfoBar() {
         LocationSettingsTestUtil.setSystemLocationSettingEnabled(true);
         mCustomTabActivityTestRule.startCustomTabActivityWithIntent(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/OWNERS
index 829eb83..ed6d5f0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/OWNERS
@@ -1 +1 @@
-file://components/autofill_assistant/OWNERS
\ No newline at end of file
+file://components/autofill_assistant/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/OWNERS
index 8a046e8..7a3c879c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
index b1c6bb7..9affe90 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
@@ -90,6 +90,7 @@
     private static final String TEST_URL = "https://test.com";
     private static final String FOO_URL = "https://foo.com";
     private static final long ACTIVITY_WAIT_LONG_MS = TimeUnit.SECONDS.toMillis(10);
+    private static final String TEST_ENROLLMENT_TOKEN = "enrollment-token";
 
     @Rule
     public MultiActivityTestRule mTestRule = new MultiActivityTestRule();
@@ -206,6 +207,13 @@
         setDeviceOwnedForMock();
     }
 
+    private void enableCloudManagementViaPolicy() {
+        setHasAppRestrictionForMock(true);
+        Bundle restrictions = new Bundle();
+        restrictions.putString("CloudManagementEnrollmentToken", TEST_ENROLLMENT_TOKEN);
+        AbstractAppRestrictionsProvider.setTestRestrictions(restrictions);
+    }
+
     private void launchCustomTabs(String url) {
         mContext.startActivity(CustomTabsTestUtils.createMinimalCustomTabIntent(mContext, url));
     }
@@ -682,6 +690,18 @@
         verifyUrlEquals(TEST_URL, waitAndGetUriFromChromeActivity(CustomTabActivity.class));
     }
 
+    @Test
+    @MediumTest
+    public void testCloudManagementDoesNotBlockFirstRun() throws Exception {
+        // Ensures FRE is not blocked if cloud management is enabled.
+        enableCloudManagementViaPolicy();
+
+        launchViewIntent(TEST_URL);
+        FirstRunActivity firstRunActivity = waitForActivity(FirstRunActivity.class);
+        clickThroughFirstRun(firstRunActivity, SearchEnginePromoType.DONT_SHOW);
+        verifyUrlEquals(TEST_URL, waitAndGetUriFromChromeActivity(ChromeTabbedActivity.class));
+    }
+
     private void clickButton(final Activity activity, final int id, final String message) {
         CriteriaHelper.pollUiThread(
                 () -> Criteria.checkThat(activity.findViewById(id), Matchers.notNullValue()));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/OWNERS
index 7e0354ab..f296ab80 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/identity_disc/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/identity_disc/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarTest.java
index 363a006..f9b7c21 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarTest.java
@@ -31,6 +31,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.WebContentsFactory;
 import org.chromium.chrome.browser.datareduction.DataReductionPromoUtils;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -42,6 +43,7 @@
 import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
 import org.chromium.chrome.test.util.InfoBarTestAnimationListener;
 import org.chromium.chrome.test.util.InfoBarUtil;
+import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.chrome.test.util.browser.LocationSettingsTestUtil;
 import org.chromium.components.infobars.InfoBar;
@@ -59,6 +61,7 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @Batch(Batch.PER_CLASS)
+@DisableFeatures({ChromeFeatureList.MESSAGES_FOR_ANDROID_INFRASTRUCTURE})
 public class InfoBarTest {
     @ClassRule
     public static ChromeTabbedActivityTestRule sActivityTestRule =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/OWNERS
index 64295110..aa5aa18a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/locale/OWNERS
\ No newline at end of file
+file://chrome/browser/locale/java/src/org/chromium/chrome/browser/locale/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/OWNERS
index 83754a0..2123302 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/notifications/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/notifications/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/paint_preview/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/paint_preview/OWNERS
index 124e026e..5736463 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/paint_preview/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/paint_preview/OWNERS
@@ -1 +1 @@
-file://components/paint_preview/OWNERS
\ No newline at end of file
+file://components/paint_preview/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/query_tiles/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/query_tiles/OWNERS
index bae272e..593e722 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/query_tiles/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/query_tiles/OWNERS
@@ -1 +1 @@
-file://components/query_tiles/OWNERS
\ No newline at end of file
+file://components/query_tiles/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/OWNERS
index d0dbd1eb..a19f350 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/searchwidget/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/searchwidget/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/OWNERS
index 5518d18b..4cc897be 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/toolbar/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/toolbar/OWNERS
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/OWNERS
index b946bf67..db55bc9 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/OWNERS
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/autofill/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/autofill/OWNERS
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/cookies/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/cookies/OWNERS
index 22c7c0e9..508ad91 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/cookies/OWNERS
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/cookies/OWNERS
@@ -1 +1 @@
-file://chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/cookies/OWNERS
\ No newline at end of file
+file://chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/cookies/OWNERS
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/cryptids/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/cryptids/OWNERS
index 1393b4b..fa962e4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/cryptids/OWNERS
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/cryptids/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/cryptids/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/cryptids/OWNERS
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/messages/ChromeMessageAutodismissDurationProviderTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/messages/ChromeMessageAutodismissDurationProviderTest.java
new file mode 100644
index 0000000..c418329
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/messages/ChromeMessageAutodismissDurationProviderTest.java
@@ -0,0 +1,73 @@
+// 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.messages;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.FeatureList;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
+
+import java.util.Map;
+
+/**
+ * Unit tests for {@link ChromeMessageAutodismissDurationProvider}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class ChromeMessageAutodismissDurationProviderTest {
+    @Before
+    public void setUp() {
+        FeatureList.setTestFeatures(
+                Map.of(ChromeFeatureList.MESSAGES_FOR_ANDROID_INFRASTRUCTURE, true));
+    }
+
+    @Test
+    public void testDefaultNonA11yDuration() {
+        ChromeAccessibilityUtil.get().setAccessibilityEnabledForTesting(false);
+        ChromeMessageAutodismissDurationProvider provider =
+                new ChromeMessageAutodismissDurationProvider();
+        provider.setDefaultAutodismissDurationMsForTesting(500);
+        provider.setDefaultAutodismissDurationWithA11yMsForTesting(1000);
+        Assert.assertEquals("Provider should return default non-a11y duration if a11y is off", 500,
+                provider.get(0));
+    }
+
+    @Test
+    public void testA11yDuration() {
+        ChromeAccessibilityUtil.get().setAccessibilityEnabledForTesting(true);
+        ChromeMessageAutodismissDurationProvider provider =
+                new ChromeMessageAutodismissDurationProvider();
+        provider.setDefaultAutodismissDurationMsForTesting(500);
+        provider.setDefaultAutodismissDurationWithA11yMsForTesting(1000);
+        Assert.assertEquals("Provider should return default a11y duration if a11y is on", 1000,
+                provider.get(0));
+    }
+
+    @Test
+    public void testCustomDuration() {
+        ChromeAccessibilityUtil.get().setAccessibilityEnabledForTesting(false);
+        ChromeMessageAutodismissDurationProvider provider =
+                new ChromeMessageAutodismissDurationProvider();
+        provider.setDefaultAutodismissDurationMsForTesting(500);
+        provider.setDefaultAutodismissDurationWithA11yMsForTesting(1000);
+        Assert.assertEquals("Provider should return custom non-a11y duration if a11y is off", 1500,
+                provider.get(1500));
+        Assert.assertEquals(
+                "Provider should return default non-a11y duration if custom duration is too short",
+                500, provider.get(250));
+        ChromeAccessibilityUtil.get().setAccessibilityEnabledForTesting(true);
+        Assert.assertEquals("Provider should return custom a11y duration if a11y is on", 1500,
+                provider.get(1500));
+        Assert.assertEquals(
+                "Provider should return default a11y duration if custom duration is too short",
+                1000, provider.get(250));
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/OWNERS
index 83754a0..2123302 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/OWNERS
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/notifications/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/notifications/OWNERS
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/partnerbookmarks/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/partnerbookmarks/OWNERS
index bb27b0f..77d93dd 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/partnerbookmarks/OWNERS
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/partnerbookmarks/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/partnerbookmarks/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/partnerbookmarks/OWNERS
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/preferences/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/preferences/OWNERS
index aca697b..e53de1b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/preferences/OWNERS
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/preferences/OWNERS
@@ -1 +1 @@
-file://chrome/browser/preferences/OWNERS
\ No newline at end of file
+file://chrome/browser/preferences/OWNERS
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/safe_browsing/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/safe_browsing/OWNERS
index d8a4750..b7f4917 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/safe_browsing/OWNERS
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/safe_browsing/OWNERS
@@ -1 +1 @@
-file://chrome/browser/safe_browsing/OWNERS
\ No newline at end of file
+file://chrome/browser/safe_browsing/OWNERS
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/site_settings/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/site_settings/OWNERS
index 72fb09a..051690a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/site_settings/OWNERS
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/site_settings/OWNERS
@@ -1 +1 @@
-file://components/browser_ui/site_settings/OWNERS
\ No newline at end of file
+file://components/browser_ui/site_settings/OWNERS
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/OWNERS
index f23716a..2a1a899 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/OWNERS
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/OWNERS
@@ -1 +1 @@
-file://chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/OWNERS
\ No newline at end of file
+file://chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/OWNERS
diff --git a/chrome/android/shared_preference_files/OWNERS b/chrome/android/shared_preference_files/OWNERS
index 1516dad..63d94f7 100644
--- a/chrome/android/shared_preference_files/OWNERS
+++ b/chrome/android/shared_preference_files/OWNERS
@@ -1,3 +1,3 @@
 bsheedy@chromium.org
 
-per-file *.json=*
\ No newline at end of file
+per-file *.json=*
diff --git a/chrome/android/webapk/libs/runtime_library/src/org/chromium/webapk/lib/runtime_library/OWNERS b/chrome/android/webapk/libs/runtime_library/src/org/chromium/webapk/lib/runtime_library/OWNERS
index 89442abe..8f094e0 100644
--- a/chrome/android/webapk/libs/runtime_library/src/org/chromium/webapk/lib/runtime_library/OWNERS
+++ b/chrome/android/webapk/libs/runtime_library/src/org/chromium/webapk/lib/runtime_library/OWNERS
@@ -1,2 +1,2 @@
 per-file *.aidl=set noparent
-per-file *.aidl=file://ipc/SECURITY_OWNERS
\ No newline at end of file
+per-file *.aidl=file://ipc/SECURITY_OWNERS
diff --git a/chrome/android/webapk/shell_apk/BUILD.gn b/chrome/android/webapk/shell_apk/BUILD.gn
index 1b05fc5..8e9f060 100644
--- a/chrome/android/webapk/shell_apk/BUILD.gn
+++ b/chrome/android/webapk/shell_apk/BUILD.gn
@@ -69,6 +69,7 @@
       "src/org/chromium/webapk/shell_apk/h2o/H2OMainActivity.java",
       "src/org/chromium/webapk/shell_apk/h2o/H2OOpaqueMainActivity.java",
       "src/org/chromium/webapk/shell_apk/h2o/H2OTransparentLauncherActivity.java",
+      "src/org/chromium/webapk/shell_apk/h2o/LaunchTrigger.java",
       "src/org/chromium/webapk/shell_apk/h2o/SplashActivity.java",
       "src/org/chromium/webapk/shell_apk/h2o/SplashContentProvider.java",
       "src/org/chromium/webapk/shell_apk/h2o/SplashUtils.java",
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/LaunchTrigger.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/LaunchTrigger.java
new file mode 100644
index 0000000..5f6f6b5d
--- /dev/null
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/LaunchTrigger.java
@@ -0,0 +1,58 @@
+// 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.webapk.shell_apk.h2o;
+
+/**
+ * Controls when to launch the WebAPK for {@link SplashActivity}.
+ *
+ * Executes the provided Runnable when all of {@link #onSplashScreenLaidOut}, {@link #onWillLaunch}
+ * and {@link #onHostBrowserSelected} have been called. The provided Runnable is only called once,
+ * but this can be reset by calling {@link #reset}.
+ */
+public class LaunchTrigger {
+    private final Runnable mCallback;
+
+    // Variables that determine whether we are ready to encode the splash screen.
+    private boolean mSplashScreenLaidOut;
+    private boolean mWillLaunch;
+    private boolean mHostBrowserSelected;
+    private boolean mLaunchingOrLaunched;
+
+    public LaunchTrigger(Runnable callback) {
+        mCallback = callback;
+    }
+
+    // Methods that could trigger the splash screen encoding.
+    public void onSplashScreenLaidOut() {
+        mSplashScreenLaidOut = true;
+        maybeTrigger();
+    }
+
+    public void onWillLaunch() {
+        mWillLaunch = true;
+        maybeTrigger();
+    }
+
+    public void onHostBrowserSelected() {
+        mHostBrowserSelected = true;
+        maybeTrigger();
+    }
+
+    public void reset() {
+        mLaunchingOrLaunched = false;
+        mHostBrowserSelected = false;
+        mWillLaunch = false;
+    }
+
+    private void maybeTrigger() {
+        if (!mHostBrowserSelected || !mSplashScreenLaidOut || !mWillLaunch
+                || mLaunchingOrLaunched) {
+            return;
+        }
+        mLaunchingOrLaunched = true;
+
+        mCallback.run();
+    }
+}
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/SplashActivity.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/SplashActivity.java
index a16ad1b..bbb6fbc 100644
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/SplashActivity.java
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/SplashActivity.java
@@ -19,6 +19,7 @@
 import android.view.ViewTreeObserver;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
 
 import org.chromium.components.webapk.lib.common.WebApkMetaDataKeys;
 import org.chromium.webapk.lib.common.WebApkMetaDataUtils;
@@ -35,11 +36,9 @@
 
 /** Displays splash screen. */
 public class SplashActivity extends Activity {
-    /** Whether {@link mSplashView} was laid out. */
-    private boolean mSplashViewLaidOut;
-
     /** Task to screenshot and encode splash. */
     @SuppressWarnings("NoAndroidAsyncTaskCheck")
+    @Nullable
     private android.os.AsyncTask mScreenshotSplashTask;
 
     @IntDef({ActivityResult.NONE, ActivityResult.CANCELED, ActivityResult.IGNORE})
@@ -53,8 +52,9 @@
     private View mSplashView;
     private HostBrowserLauncherParams mParams;
     private @ActivityResult int mResult;
-    private boolean mResumed;
-    private boolean mPendingLaunch;
+
+    private final LaunchTrigger mLaunchTrigger =
+            new LaunchTrigger(this::screenshotAndEncodeSplashInBackground);
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -75,7 +75,6 @@
             return;
         }
 
-        mPendingLaunch = true;
         selectHostBrowser(splashAddedToLayoutTimeMs);
     }
 
@@ -97,7 +96,7 @@
         // "singleTask".
         mResult = ActivityResult.IGNORE;
 
-        mPendingLaunch = true;
+        mLaunchTrigger.reset();
 
         selectHostBrowser(-1 /* splashShownTimeMs */);
     }
@@ -105,20 +104,16 @@
     @Override
     public void onResume() {
         super.onResume();
-        mResumed = true;
+
+        // If Activity#onActivityResult() will be called, it will be called prior to the
+        // activity being resumed.
         if (mResult == ActivityResult.CANCELED) {
             finish();
             return;
         }
 
         mResult = ActivityResult.NONE;
-        maybeScreenshotSplashAndLaunch();
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        mResumed = false;
+        mLaunchTrigger.onWillLaunch();
     }
 
     @Override
@@ -168,8 +163,7 @@
                         if (mSplashView.getWidth() == 0 || mSplashView.getHeight() == 0) return;
 
                         mSplashView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        mSplashViewLaidOut = true;
-                        maybeScreenshotSplashAndLaunch();
+                        mLaunchTrigger.onSplashScreenLaidOut();
                     }
                 });
         setContentView(mSplashView);
@@ -207,22 +201,7 @@
         }
 
         mParams = params;
-        maybeScreenshotSplashAndLaunch();
-    }
-
-    /**
-     * Screenshots {@link mSplashView} if:
-     * - host browser was selected
-     * AND
-     * - splash view was laid out
-     */
-    private void maybeScreenshotSplashAndLaunch() {
-        // If Activity#onActivityResult() will be called, it will be called prior to the
-        // activity being resumed.
-        if (mParams == null || !mSplashViewLaidOut || !mResumed || !mPendingLaunch) return;
-        mPendingLaunch = false;
-
-        screenshotAndEncodeSplashInBackground();
+        mLaunchTrigger.onHostBrowserSelected();
     }
 
     /**
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index d3fa11c..cc68e9d4 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1239,6 +1239,12 @@
   <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION4" desc="Part 4 of the explanation of the Privacy Sandbox, shown on the Privacy Sandbox Page. The 'Learn more' link navigates the user to a page with more details.">
     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. <ph name="BEGIN_LINK">&lt;a href="$1" target="_blank"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
   </message>
+  <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION1_PHASE2" desc="Part 1 of the phase 2 explanation of the Privacy Sandbox, shown on the Privacy Sandbox Page. The 'Privacy Sandbox' link navigates the user to a page with more details.">
+    <ph name="BEGIN_LINK">&lt;a href="$1" target="_blank"&gt;</ph>Privacy Sandbox<ph name="END_LINK">&lt;/a&gt;</ph> is an ongoing initiative to develop new technologies that will safeguard you from tracking mechanisms while preserving the open web.
+  </message>
+  <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION2_PHASE2" desc="Part 2 of the phase 2 explanation of the Privacy Sandbox, shown on the Privacy Sandbox Page. The 'Privacy Sandbox' link navigates the user to a page with more details.">
+   Privacy Sandbox trials are still in active development and are 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_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_TITLE" desc="Title of the description of the setting to enable or disable the Privacy Sandbox.">
     Privacy Sandbox trials
   </message>
@@ -1251,6 +1257,15 @@
   <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION3" desc="Part 3 of the explanation of the setting to enable or disable the Privacy Sandbox.">
     Advertisers can study the effectiveness of ads in a way that does not track you across sites.
   </message>
+  <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION1_PHASE2" desc="Part 1 of the phase 2 explanation of the setting to enable or disable the Privacy Sandbox.">
+    When on, 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.
+  </message>
+  <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION2_PHASE2" desc="Part 2 of the phase 2 explanation of the setting to enable or disable the Privacy Sandbox.">
+    Advertisers and publishers can use the FLoC technique, described later on this page.
+  </message>
+  <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION3_PHASE2" desc="Part 2 of the phase 2 explanation of the setting to enable or disable the Privacy Sandbox.">
+    Advertisers and publishers can study the effectiveness of ads in a way that does not track you across sites.
+  </message>
   <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_DETAILS" desc="Label of a button in the Privacy Sandbox page that leads the user to more details about the Privacy Sandbox.">
     Details
   </message>
@@ -1261,22 +1276,23 @@
     Trial features are off
   </message>
   <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_HEADING" desc="The heading of the card which displays the user's Federated Learning of Cohorts (FLoC) settings">
-    Federated Learning of Cohorts
+    FLoC
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_EXPLANATION" desc="Explanation of Federated Learning of Cohorts (FLoC) which appears at the top of the FLoC settings card">
-    FLoC provides a privacy-preserving mechanism for interest-based ad selection.
+  <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_EXPLANATION" desc="Explanation of Federated Learning of Cohorts (FLoC) which appears at the top of the FLoC settings card. The some regions link goes to a site indicating where FLoC is currently available">
+    When on and the status is active, Chrome uses your browsing history over 7 days to determine a group, or “cohort”, that you’re in. Advertisers can select ads for the group. Your browsing history is kept private on your device. This trial is active only in
+    <ph name="BEGIN_LINK">&lt;a href="$1" target="_blank"&gt;</ph>some regions<ph name="END_LINK">&lt;/a&gt;</ph>.
   </message>
   <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_STATUS" desc="The text used to label the user's current Federated Learning of Cohorts (FLoC) trial status">
     Status
   </message>
   <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_COHORT" desc="The text used to label the user's current Federated Learning of Cohorts (FLoC) cohort">
-    FLoC ID
+    Group number
   </message>
   <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_COHORT_NEXT_UPDATE" desc="The text used to label when the user's current Federated Learning of Cohorts (FLoC) cohort will next be updated">
     Next Update
   </message>
   <message name="IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_RESET_COHORT" desc="The text used to label the button which allows a user to reset their Federated Learning of Cohorts (FLoC) cohort identifier">
-    Reset ID
+    Reset group
   </message>
   <message name="IDS_SETTINGS_PRIVACY_SANDBOX_COOKIES_DIALOG" desc="Text of a dialog shown to users when they interacted with cookies to make them aware of the Privacy Sandbox.">
     Learn about and control new technologies that aim to replace third-party cookies
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION1_PHASE2.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION1_PHASE2.png.sha1
new file mode 100644
index 0000000..aa6d6f7
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION1_PHASE2.png.sha1
@@ -0,0 +1 @@
+26eb02bf641228865c1090d21689f8345b78730e
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION2_PHASE2.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION2_PHASE2.png.sha1
new file mode 100644
index 0000000..aa6d6f7
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION2_PHASE2.png.sha1
@@ -0,0 +1 @@
+26eb02bf641228865c1090d21689f8345b78730e
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_COHORT.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_COHORT.png.sha1
index 2616739..aa6d6f7 100644
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_COHORT.png.sha1
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_COHORT.png.sha1
@@ -1 +1 @@
-9de2c1031455bd0a067c985f091f8512ee8323ad
\ No newline at end of file
+26eb02bf641228865c1090d21689f8345b78730e
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_EXPLANATION.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_EXPLANATION.png.sha1
index 2616739..aa6d6f7 100644
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_EXPLANATION.png.sha1
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_EXPLANATION.png.sha1
@@ -1 +1 @@
-9de2c1031455bd0a067c985f091f8512ee8323ad
\ No newline at end of file
+26eb02bf641228865c1090d21689f8345b78730e
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_HEADING.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_HEADING.png.sha1
index 2616739..aa6d6f7 100644
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_HEADING.png.sha1
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_HEADING.png.sha1
@@ -1 +1 @@
-9de2c1031455bd0a067c985f091f8512ee8323ad
\ No newline at end of file
+26eb02bf641228865c1090d21689f8345b78730e
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_RESET_COHORT.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_RESET_COHORT.png.sha1
index 2616739..aa6d6f7 100644
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_RESET_COHORT.png.sha1
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_RESET_COHORT.png.sha1
@@ -1 +1 @@
-9de2c1031455bd0a067c985f091f8512ee8323ad
\ No newline at end of file
+26eb02bf641228865c1090d21689f8345b78730e
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION1_PHASE2.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION1_PHASE2.png.sha1
new file mode 100644
index 0000000..aa6d6f7
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION1_PHASE2.png.sha1
@@ -0,0 +1 @@
+26eb02bf641228865c1090d21689f8345b78730e
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION2_PHASE2.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION2_PHASE2.png.sha1
new file mode 100644
index 0000000..aa6d6f7
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION2_PHASE2.png.sha1
@@ -0,0 +1 @@
+26eb02bf641228865c1090d21689f8345b78730e
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION3_PHASE2.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION3_PHASE2.png.sha1
new file mode 100644
index 0000000..aa6d6f7
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION3_PHASE2.png.sha1
@@ -0,0 +1 @@
+26eb02bf641228865c1090d21689f8345b78730e
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 25d2bbb..82833c7 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3128,6 +3128,8 @@
       "permissions/permission_update_infobar_delegate_android.cc",
       "permissions/permission_update_infobar_delegate_android.h",
       "platform_util_android.cc",
+      "policy/android/cloud_management_shared_preferences.cc",
+      "policy/android/cloud_management_shared_preferences.h",
       "policy/browser_dm_token_storage_android.cc",
       "policy/browser_dm_token_storage_android.h",
       "policy/chrome_browser_cloud_management_controller_android.cc",
@@ -3571,6 +3573,7 @@
       "enterprise/connectors/analysis/analysis_service_settings.h",
       "enterprise/connectors/analysis/content_analysis_delegate.cc",
       "enterprise/connectors/analysis/content_analysis_delegate.h",
+      "enterprise/connectors/analysis/content_analysis_delegate_base.h",
       "enterprise/connectors/analysis/content_analysis_dialog.cc",
       "enterprise/connectors/analysis/content_analysis_dialog.h",
       "enterprise/connectors/common.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 67e7ad6..df2c8c33 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -700,6 +700,18 @@
      crosapi::browser_util::kLacrosStabilityMoreStable},
 };
 
+const char kLacrosSelectionInternalName[] = "lacros-selection";
+
+const FeatureEntry::Choice kLacrosSelectionChoices[] = {
+    {flags_ui::kGenericExperimentChoiceDefault, "", ""},
+    {flag_descriptions::kLacrosSelectionStatefulDescription,
+     crosapi::browser_util::kLacrosSelectionSwitch,
+     crosapi::browser_util::kLacrosSelectionStateful},
+    {flag_descriptions::kLacrosSelectionRootfsDescription,
+     crosapi::browser_util::kLacrosSelectionSwitch,
+     crosapi::browser_util::kLacrosSelectionRootfs},
+};
+
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -2970,6 +2982,9 @@
     {kLacrosStabilityInternalName, flag_descriptions::kLacrosStabilityName,
      flag_descriptions::kLacrosStabilityDescription, kOsCrOS,
      MULTI_VALUE_TYPE(kLacrosStabilityChoices)},
+    {kLacrosSelectionInternalName, flag_descriptions::kLacrosSelectionName,
+     flag_descriptions::kLacrosSelectionDescription, kOsCrOS,
+     MULTI_VALUE_TYPE(kLacrosSelectionChoices)},
     {kWebAppsCrosapiInternalName, flag_descriptions::kWebAppsCrosapiName,
      flag_descriptions::kWebAppsCrosapiDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(features::kWebAppsCrosapi)},
@@ -3032,6 +3047,14 @@
     {"wake-on-wifi-allowed", flag_descriptions::kWakeOnWifiAllowedName,
      flag_descriptions::kWakeOnWifiAllowedDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kWakeOnWifiAllowed)},
+    {"microphone-mute-notifications",
+     flag_descriptions::kMicrophoneMuteNotificationsName,
+     flag_descriptions::kMicrophoneMuteNotificationsDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kMicMuteNotifications)},
+    {"microphone-mute-switch-device",
+     flag_descriptions::kMicrophoneMuteSwitchDeviceName,
+     flag_descriptions::kMicrophoneMuteSwitchDeviceDescription, kOsCrOS,
+     SINGLE_VALUE_TYPE("enable-microphone-mute-switch-device")},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(OS_CHROMEOS)
@@ -3236,9 +3259,16 @@
     {"adaptive-button-in-top-toolbar",
      flag_descriptions::kAdaptiveButtonInTopToolbarName,
      flag_descriptions::kAdaptiveButtonInTopToolbarDescription, kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(features::kAdaptiveButtonInTopToolbar,
-                                    kAdaptiveButtonInTopToolbarVariations,
-                                    "AdaptiveButtonInTopToolbar")},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(
+         chrome::android::kAdaptiveButtonInTopToolbar,
+         kAdaptiveButtonInTopToolbarVariations,
+         "AdaptiveButtonInTopToolbar")},
+    {"adaptive-button-in-top-toolbar-customization",
+     flag_descriptions::kAdaptiveButtonInTopToolbarCustomizationName,
+     flag_descriptions::kAdaptiveButtonInTopToolbarCustomizationDescription,
+     kOsAndroid,
+     FEATURE_VALUE_TYPE(
+         chrome::android::kAdaptiveButtonInTopToolbarCustomization)},
     {"reader-mode-heuristics", flag_descriptions::kReaderModeHeuristicsName,
      flag_descriptions::kReaderModeHeuristicsDescription, kOsAndroid,
      MULTI_VALUE_TYPE(kReaderModeHeuristicsChoices)},
@@ -7231,6 +7261,10 @@
      FEATURE_VALUE_TYPE(features::kWebUIDownloadShelf)},
 #endif  // defined(TOOLKIT_VIEWS)
 
+    {"playback-speed-button", flag_descriptions::kPlaybackSpeedButtonName,
+     flag_descriptions::kPlaybackSpeedButtonDescription, kOsAll,
+     FEATURE_VALUE_TYPE(media::kPlaybackSpeedButton)},
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 22d1018..b909604 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -4754,26 +4754,16 @@
   EXPECT_EQ("PASSED", test_passed);
 }
 
-class PrivateNetworkAccessWebViewTest : public WebViewTest {
- public:
-  PrivateNetworkAccessWebViewTest() {
-    features_.InitAndEnableFeature(
-        features::kBlockInsecurePrivateNetworkRequests);
-  }
-
- private:
-  base::test::ScopedFeatureList features_;
-};
-
 // Verify that Private Network Access has the correct understanding of the
 // chrome-guest:// scheme, that should only ever be used as a SiteURL, and not
 // interfere with the local/private/public classification.
 // See https://crbug.com/1167698 for details.
+//
 // Note: This test is put in this file for convenience of reusing the entire
 // app testing infrastructure. Other similar tests that do not require that
 // infrastructure live in PrivateNetworkAccessBrowserTest.*
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWebViewTest,
-                       SpecialSchemeChromeGuest) {
+IN_PROC_BROWSER_TEST_F(WebViewTest,
+                       PrivateNetworkAccessSpecialSchemeChromeGuest) {
   LoadAppWithGuest("web_view/simple");
   content::WebContents* guest = GetGuestWebContents();
   ASSERT_TRUE(guest);
diff --git a/chrome/browser/ash/accessibility/dictation.cc b/chrome/browser/ash/accessibility/dictation.cc
index 40add489..da2a0fd 100644
--- a/chrome/browser/ash/accessibility/dictation.cc
+++ b/chrome/browser/ash/accessibility/dictation.cc
@@ -6,6 +6,7 @@
 
 #include "ash/components/audio/sounds.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/metrics/metrics_hashes.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
@@ -96,6 +97,10 @@
   }
   has_committed_text_ = false;
   std::string language = GetUserLanguage(profile_);
+  // Log the language used with CLD3LanguageCode.
+  base::UmaHistogramSparse("Accessibility.CrosDictation.Language",
+                           base::HashMetricName(language));
+
   if (switches::IsExperimentalAccessibilityDictationOfflineEnabled() &&
       OnDeviceSpeechRecognizer::IsOnDeviceSpeechRecognizerAvailable(language)) {
     // On-device recognition is behind a flag and then only available if
diff --git a/chrome/browser/ash/arc/notification/arc_management_transition_notification.cc b/chrome/browser/ash/arc/notification/arc_management_transition_notification.cc
index ed3468e7..2a6a889 100644
--- a/chrome/browser/ash/arc/notification/arc_management_transition_notification.cc
+++ b/chrome/browser/ash/arc/notification/arc_management_transition_notification.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
+#include "chromeos/ui/vector_icons/vector_icons.h"
 #include "components/account_id/account_id.h"
 #include "components/arc/arc_prefs.h"
 #include "components/prefs/pref_change_registrar.h"
@@ -82,6 +83,15 @@
   DISALLOW_COPY_AND_ASSIGN(NotificationDelegate);
 };
 
+const gfx::VectorIcon& GetNotificationIcon(
+    ArcSupervisionTransition transition) {
+  if (transition == ArcSupervisionTransition::UNMANAGED_TO_MANAGED) {
+    return chromeos::kEnterpriseIcon;
+  } else {
+    return kNotificationFamilyLinkIcon;
+  }
+}
+
 }  // namespace
 
 const char kManagementTransitionNotificationId[] =
@@ -106,7 +116,7 @@
           l10n_util::GetStringUTF16(IDS_ARC_CHILD_TRANSITION_MESSAGE),
           l10n_util::GetStringUTF16(IDS_ARC_NOTIFICATION_DISPLAY_SOURCE),
           GURL(), notifier_id, message_center::RichNotificationData(),
-          new NotificationDelegate(profile), kNotificationFamilyLinkIcon,
+          new NotificationDelegate(profile), GetNotificationIcon(transition),
           message_center::SystemNotificationWarningLevel::NORMAL);
   NotificationDisplayService::GetForProfile(profile)->Display(
       NotificationHandler::Type::TRANSIENT, *notification,
diff --git a/chrome/browser/ash/arc/notification/arc_management_transition_notification_unittest.cc b/chrome/browser/ash/arc/notification/arc_management_transition_notification_unittest.cc
index 569936e..e7dfe60 100644
--- a/chrome/browser/ash/arc/notification/arc_management_transition_notification_unittest.cc
+++ b/chrome/browser/ash/arc/notification/arc_management_transition_notification_unittest.cc
@@ -10,11 +10,13 @@
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_test.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/ui/vector_icons/vector_icons.h"
 #include "components/arc/arc_features.h"
 #include "components/arc/arc_prefs.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
@@ -26,9 +28,22 @@
 
 namespace arc {
 
+namespace {
+
+struct TransitionNotificationParams {
+  TransitionNotificationParams(ArcSupervisionTransition arc_transition,
+                               const gfx::VectorIcon* notification_icon)
+      : arc_transition(arc_transition), notification_icon(notification_icon) {}
+
+  ArcSupervisionTransition arc_transition;
+  const gfx::VectorIcon* notification_icon;
+};
+
+}  // namespace
+
 class ArcManagementTransitionNotificationTest
     : public testing::Test,
-      public testing::WithParamInterface<ArcSupervisionTransition> {
+      public testing::WithParamInterface<TransitionNotificationParams> {
  public:
   ArcManagementTransitionNotificationTest() = default;
   ~ArcManagementTransitionNotificationTest() override = default;
@@ -59,6 +74,14 @@
   }
   ArcAppTest* arc_app_test() { return &arc_app_test_; }
 
+  const gfx::VectorIcon* expected_notification_icon() {
+    return GetParam().notification_icon;
+  }
+
+  ArcSupervisionTransition arc_transition() {
+    return GetParam().arc_transition;
+  }
+
  private:
   std::unique_ptr<TestingProfile> profile_;
   std::unique_ptr<NotificationDisplayServiceTester> display_service_;
@@ -72,10 +95,16 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     ArcManagementTransitionNotificationTest,
-    ::testing::Values(ArcSupervisionTransition::NO_TRANSITION,
-                      ArcSupervisionTransition::CHILD_TO_REGULAR,
-                      ArcSupervisionTransition::REGULAR_TO_CHILD,
-                      ArcSupervisionTransition::UNMANAGED_TO_MANAGED));
+    ::testing::Values(
+        TransitionNotificationParams(ArcSupervisionTransition::NO_TRANSITION,
+                                     nullptr),
+        TransitionNotificationParams(ArcSupervisionTransition::CHILD_TO_REGULAR,
+                                     &kNotificationFamilyLinkIcon),
+        TransitionNotificationParams(ArcSupervisionTransition::REGULAR_TO_CHILD,
+                                     &kNotificationFamilyLinkIcon),
+        TransitionNotificationParams(
+            ArcSupervisionTransition::UNMANAGED_TO_MANAGED,
+            &chromeos::kEnterpriseIcon)));
 
 TEST_P(ArcManagementTransitionNotificationTest, BaseFlow) {
   ASSERT_TRUE(arc_app_test()->fake_apps().size());
@@ -85,7 +114,7 @@
       ArcAppTest::GetAppId(arc_app_test()->fake_apps()[0]);
 
   profile()->GetPrefs()->SetInteger(prefs::kArcSupervisionTransition,
-                                    static_cast<int>(GetParam()));
+                                    static_cast<int>(arc_transition()));
 
   // Attempt to launch ARC app triggers notification.
   LaunchApp(profile(), app_id, 0 /* event_flags */,
@@ -97,7 +126,7 @@
 
   // In case no management transition in progress notification is not
   // triggered.
-  if (GetParam() == ArcSupervisionTransition::NO_TRANSITION) {
+  if (arc_transition() == ArcSupervisionTransition::NO_TRANSITION) {
     EXPECT_FALSE(display_service()->GetNotification(
         kManagementTransitionNotificationId));
     // Last launch is set, indicating that launch attempt was not blocked.
@@ -105,8 +134,17 @@
     return;
   }
 
-  EXPECT_TRUE(
-      display_service()->GetNotification(kManagementTransitionNotificationId));
+  {
+    auto notification =
+        display_service()->GetNotification(kManagementTransitionNotificationId);
+
+    // Notification is shown.
+    ASSERT_TRUE(notification);
+    // Notification has expected icon.
+    EXPECT_EQ(&notification->vector_small_image(),
+              expected_notification_icon());
+  }
+
   // Last launch is not set, indicating that launch attempt was blocked.
   EXPECT_TRUE(app_info->last_launch_time.is_null());
 
@@ -120,7 +158,7 @@
   // Re-activate notification and check opt out. On opt-out notification is also
   // automatially dismissed.
   profile()->GetPrefs()->SetInteger(prefs::kArcSupervisionTransition,
-                                    static_cast<int>(GetParam()));
+                                    static_cast<int>(arc_transition()));
   ShowManagementTransitionNotification(profile());
   EXPECT_TRUE(
       display_service()->GetNotification(kManagementTransitionNotificationId));
diff --git a/chrome/browser/ash/crosapi/browser_loader.cc b/chrome/browser/ash/crosapi/browser_loader.cc
index 9136d71..cf828cfc 100644
--- a/chrome/browser/ash/crosapi/browser_loader.cc
+++ b/chrome/browser/ash/crosapi/browser_loader.cc
@@ -178,6 +178,22 @@
     return;
   }
 
+  // If the user has specified to force using stateful or rootfs lacros-chrome
+  // binary, force the selection.
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  if (cmdline->HasSwitch(browser_util::kLacrosSelectionSwitch)) {
+    auto value =
+        cmdline->GetSwitchValueASCII(browser_util::kLacrosSelectionSwitch);
+    if (value == browser_util::kLacrosSelectionRootfs) {
+      LoadRootfsLacros(std::move(callback));
+      return;
+    }
+    if (value == browser_util::kLacrosSelectionStateful) {
+      LoadStatefulLacros(std::move(callback));
+      return;
+    }
+  }
+
   // TODO(b/188473251): Remove this check once rootfs lacros-chrome is in.
   if (!base::PathExists(
           base::FilePath(kRootfsLacrosPath).Append(kLacrosImage))) {
diff --git a/chrome/browser/ash/crosapi/browser_util.cc b/chrome/browser/ash/crosapi/browser_util.cc
index 9c4ae3d..d72b006 100644
--- a/chrome/browser/ash/crosapi/browser_util.cc
+++ b/chrome/browser/ash/crosapi/browser_util.cc
@@ -275,6 +275,10 @@
 const char kLacrosStabilityLessStable[] = "less-stable";
 const char kLacrosStabilityMoreStable[] = "more-stable";
 
+const char kLacrosSelectionSwitch[] = "lacros-selection";
+const char kLacrosSelectionRootfs[] = "rootfs";
+const char kLacrosSelectionStateful[] = "stateful";
+
 const char kLaunchOnLoginPref[] = "lacros.launch_on_login";
 const char kClearUserDataDir1Pref[] = "lacros.clear_user_data_dir_1";
 const char kDataVerPref[] = "lacros.data_version";
diff --git a/chrome/browser/ash/crosapi/browser_util.h b/chrome/browser/ash/crosapi/browser_util.h
index 0b8ded60..01f1101 100644
--- a/chrome/browser/ash/crosapi/browser_util.h
+++ b/chrome/browser/ash/crosapi/browser_util.h
@@ -70,6 +70,12 @@
 extern const char kLacrosStabilityLessStable[];
 extern const char kLacrosStabilityMoreStable[];
 
+// A command-line switch that can also be set from chrome://flags that chooses
+// which selection of Lacros to use.
+extern const char kLacrosSelectionSwitch[];
+extern const char kLacrosSelectionRootfs[];
+extern const char kLacrosSelectionStateful[];
+
 // Boolean preference. Whether to launch lacros-chrome on login.
 extern const char kLaunchOnLoginPref[];
 
diff --git a/chrome/browser/ash/login/quick_unlock/fingerprint_storage.cc b/chrome/browser/ash/login/quick_unlock/fingerprint_storage.cc
index c57ca0735..68cf919 100644
--- a/chrome/browser/ash/login/quick_unlock/fingerprint_storage.cc
+++ b/chrome/browser/ash/login/quick_unlock/fingerprint_storage.cc
@@ -54,8 +54,6 @@
 // static
 void FingerprintStorage::RegisterProfilePrefs(PrefRegistrySimple* registry) {
   registry->RegisterIntegerPref(prefs::kQuickUnlockFingerprintRecord, 0);
-  feature_usage::FeatureUsageMetrics::RegisterPref(registry,
-                                                   kFingerprintUMAFeatureName);
 }
 
 FingerprintStorage::FingerprintStorage(Profile* profile) : profile_(profile) {
@@ -76,7 +74,7 @@
   fp_service_->AddFingerprintObserver(metrics_reporter_->GetRemote());
   feature_usage_metrics_service_ =
       std::make_unique<feature_usage::FeatureUsageMetrics>(
-          kFingerprintUMAFeatureName, profile_->GetPrefs(), this);
+          kFingerprintUMAFeatureName, this);
 }
 
 FingerprintStorage::~FingerprintStorage() {}
diff --git a/chrome/browser/ash/login/quick_unlock/quick_unlock_utils.cc b/chrome/browser/ash/login/quick_unlock/quick_unlock_utils.cc
index e6128bd..591c9a7e 100644
--- a/chrome/browser/ash/login/quick_unlock/quick_unlock_utils.cc
+++ b/chrome/browser/ash/login/quick_unlock/quick_unlock_utils.cc
@@ -153,6 +153,9 @@
 }
 
 bool IsFingerprintSupported() {
+  if (enable_for_testing_)
+    return true;
+
   const base::CommandLine* command_line =
       base::CommandLine::ForCurrentProcess();
   return base::FeatureList::IsEnabled(::features::kQuickUnlockFingerprint) &&
diff --git a/chrome/browser/autofill/autofill_interactive_uitest.cc b/chrome/browser/autofill/autofill_interactive_uitest.cc
index b325812..7a1e638 100644
--- a/chrome/browser/autofill/autofill_interactive_uitest.cc
+++ b/chrome/browser/autofill/autofill_interactive_uitest.cc
@@ -78,6 +78,7 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/switches.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/keycodes/dom_us_layout_data.h"
@@ -268,7 +269,9 @@
 class AutofillInteractiveTestBase : public AutofillUiTest {
  protected:
   AutofillInteractiveTestBase()
-      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
+    feature_list_.InitAndEnableFeature(blink::features::kAutofillShadowDOM);
+  }
 
  public:
   AutofillInteractiveTestBase(const AutofillInteractiveTestBase&) = delete;
@@ -703,7 +706,10 @@
 
   void TriggerFormFill(const std::string& field_name) {
     FocusFieldByName(field_name);
+    TriggerFormFillAlreadyFocused();
+  }
 
+  void TriggerFormFillAlreadyFocused() {
     // Start filling the first name field with "M" and wait for the popup to be
     // shown.
     SendKeyToPageAndWait(ui::DomKey::FromCharacter('M'), ui::DomCode::US_M,
@@ -770,6 +776,8 @@
 
   // The response to return for queries to |kTestUrlPath|
   std::string test_url_content_;
+
+  base::test::ScopedFeatureList feature_list_;
 };
 
 const char AutofillInteractiveTestBase::kTestUrlPath[] =
@@ -3509,6 +3517,61 @@
   ExpectFieldValue("phone", "15125551234");
 }
 
+IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, ShadowDOM) {
+  CreateTestProfile();
+
+  GURL url =
+      embedded_test_server()->GetURL("a.com", "/autofill/shadowdom.html");
+  ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
+
+  bool result = false;
+  ASSERT_TRUE(
+      content::ExecuteScriptAndExtractBool(GetWebContents(),
+                                           R"( function onFocusHandler(e) {
+              e.target.removeEventListener(e.type, arguments.callee);
+              domAutomationController.send(true);
+            }
+            if (document.readyState === 'complete') {
+              var target = getNameElement();
+              target.addEventListener('focus', onFocusHandler);
+              target.focus();
+            } else {
+              domAutomationController.send(false);
+            })",
+                                           &result));
+  ASSERT_TRUE(result);
+  TriggerFormFillAlreadyFocused();
+
+  std::string name;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+      GetWebContents(), "window.domAutomationController.send(getName())",
+      &name));
+  EXPECT_EQ("Milton C. Waddams", name) << " for field name";
+
+  std::string address;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+      GetWebContents(), "window.domAutomationController.send(getAddress())",
+      &address));
+  EXPECT_EQ("4120 Freidrich Lane", address) << " for field address";
+
+  std::string city;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+      GetWebContents(), "window.domAutomationController.send(getCity())",
+      &city));
+  EXPECT_EQ("Austin", city) << " for field city";
+
+  std::string state;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+      GetWebContents(), "window.domAutomationController.send(getState())",
+      &state));
+  EXPECT_EQ("TX", state) << " for field state";
+
+  std::string zip;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+      GetWebContents(), "window.domAutomationController.send(getZip())", &zip));
+  EXPECT_EQ("78744", zip) << " for field zip";
+}
+
 INSTANTIATE_TEST_SUITE_P(All,
                          AutofillDynamicFormReplacementInteractiveTest,
                          testing::Bool());
diff --git a/chrome/browser/browser_switcher/browser_switcher_sitelist.cc b/chrome/browser/browser_switcher/browser_switcher_sitelist.cc
index 8891a89..87513fe 100644
--- a/chrome/browser/browser_switcher/browser_switcher_sitelist.cc
+++ b/chrome/browser/browser_switcher/browser_switcher_sitelist.cc
@@ -36,35 +36,16 @@
   base::StringPiece spec_without_port;
 };
 
-// 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) {
+// 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) {
                     return base::ToLowerASCII(a) == base::ToLowerASCII(b);
                   });
-  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;
+  return found != input.end();
 }
 
 // Checks if the omitted prefix for a non-fully specific prefix is one of the
@@ -101,7 +82,7 @@
     return false;
   }
   // Compare hosts and ports, case-insensitive.
-  return MatchesHostNameAtEnd(url.host_and_port, pattern);
+  return StringContainsInsensitiveASCII(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 360691c..ffc559c 100644
--- a/chrome/browser/browser_switcher/browser_switcher_sitelist_unittest.cc
+++ b/chrome/browser/browser_switcher/browser_switcher_sitelist_unittest.cc
@@ -122,11 +122,12 @@
   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/")));
-  EXPECT_FALSE(ShouldSwitch(GURL("http://notexample.com/")));
-  EXPECT_FALSE(ShouldSwitch(GURL("http://example.com.invalid.com/")));
+
+  // For backwards compatibility, this should also match, even if it's not the
+  // same host.
+  EXPECT_TRUE(ShouldSwitch(GURL("https://notexample.com/")));
 }
 
 TEST_F(BrowserSwitcherSitelistTest, ShouldRedirectHostNotLowerCase) {
@@ -197,7 +198,6 @@
   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,9 +205,6 @@
   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/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 0f32339..2cfa5e7 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -192,6 +192,7 @@
 #include "chromeos/components/camera_app_ui/camera_app_ui.h"
 #include "chromeos/components/connectivity_diagnostics/connectivity_diagnostics_ui.h"
 #include "chromeos/components/diagnostics_ui/diagnostics_ui.h"
+#include "chromeos/components/diagnostics_ui/mojom/input_data_provider.mojom.h"
 #include "chromeos/components/diagnostics_ui/mojom/system_data_provider.mojom.h"
 #include "chromeos/components/diagnostics_ui/mojom/system_routine_controller.mojom.h"
 #include "chromeos/components/eche_app_ui/eche_app_ui.h"
@@ -806,6 +807,10 @@
       chromeos::NetworkUI, chromeos::ConnectivityDiagnosticsUI>(map);
 
   RegisterWebUIControllerInterfaceBinder<
+      chromeos::diagnostics::mojom::InputDataProvider,
+      chromeos::DiagnosticsDialogUI>(map);
+
+  RegisterWebUIControllerInterfaceBinder<
       chromeos::diagnostics::mojom::SystemDataProvider,
       chromeos::DiagnosticsDialogUI>(map);
 
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index c528491..cf7f295 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -184,6 +184,7 @@
 #if defined(OS_ANDROID)
 #include "chrome/browser/flags/android/chrome_feature_list.h"
 #include "chrome/browser/metrics/thread_watcher_android.h"
+#include "chrome/browser/share/share_history.h"
 #include "chrome/browser/ui/page_info/chrome_page_info_client.h"
 #include "ui/base/resource/resource_bundle_android.h"
 #else
@@ -1567,6 +1568,11 @@
 // http://crbug.com/179143
 #if !defined(OS_ANDROID)
   // Start watching for a hang.
+  //
+  // TODO(b/184937096): Remove the below call and remove the function
+  // MetricsService::LogNeedForCleanShutdown() once this is moved earlier. It
+  // is being kept here for the time being for the control group of the
+  // extended Variations Safe Mode experiment.
   browser_process_->metrics_service()->LogNeedForCleanShutdown();
 #endif  // !defined(OS_ANDROID)
 
@@ -1765,6 +1771,9 @@
 
 void ChromeBrowserMainParts::OnFirstIdle() {
   startup_metric_utils::RecordBrowserMainLoopFirstIdle(base::TimeTicks::Now());
+#if defined(OS_ANDROID)
+  sharing::ShareHistory::CreateForProfile(profile_);
+#endif
 }
 
 void ChromeBrowserMainParts::PostMainMessageLoopRun() {
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 94b60aa..9eff32e6 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -4046,7 +4046,6 @@
     "input_method/emoji_suggester_unittest.cc",
     "input_method/fake_suggestion_handler.cc",
     "input_method/fake_suggestion_handler.h",
-    "input_method/grammar_manager_unittest.cc",
     "input_method/grammar_service_client_unittest.cc",
     "input_method/input_method_configuration_unittest.cc",
     "input_method/input_method_engine_unittest.cc",
diff --git a/chrome/browser/chromeos/input_method/grammar_manager.cc b/chrome/browser/chromeos/input_method/grammar_manager.cc
index 2dbd9970..838a0fc 100644
--- a/chrome/browser/chromeos/input_method/grammar_manager.cc
+++ b/chrome/browser/chromeos/input_method/grammar_manager.cc
@@ -19,10 +19,7 @@
 
 }  // namespace
 
-GrammarManager::GrammarManager(
-    Profile* profile,
-    std::unique_ptr<GrammarServiceClient> grammar_client)
-    : profile_(profile), grammar_client_(std::move(grammar_client)) {}
+GrammarManager::GrammarManager() {}
 
 GrammarManager::~GrammarManager() = default;
 
@@ -62,31 +59,8 @@
     return;
   }
 
-  ui::IMEInputContextHandlerInterface* input_context =
-      ui::IMEBridge::Get()->GetInputContextHandler();
-  if (!input_context)
-    return;
-
-  input_context->ClearGrammarFragments(gfx::Range(0, text.size()));
-
-  grammar_client_->RequestTextCheck(
-      profile_, text,
-      base::BindOnce(&GrammarManager::OnGrammarCheckDone,
-                     base::Unretained(this), text));
-}
-
-void GrammarManager::OnGrammarCheckDone(
-    const std::u16string& text,
-    bool success,
-    const std::vector<ui::GrammarFragment>& results) const {
-  if (!success || text != last_text_ || results.empty())
-    return;
-  ui::IMEInputContextHandlerInterface* input_context =
-      ui::IMEBridge::Get()->GetInputContextHandler();
-  if (!input_context)
-    return;
-
-  input_context->AddGrammarFragments(results);
+  // TODO(crbug/1132699): implement this method.
+  NOTIMPLEMENTED_LOG_ONCE();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/grammar_manager.h b/chrome/browser/chromeos/input_method/grammar_manager.h
index 183dde2d..573a9be 100644
--- a/chrome/browser/chromeos/input_method/grammar_manager.h
+++ b/chrome/browser/chromeos/input_method/grammar_manager.h
@@ -8,8 +8,6 @@
 #include <string>
 
 #include "base/timer/timer.h"
-#include "chrome/browser/chromeos/input_method/grammar_service_client.h"
-#include "chrome/browser/profiles/profile.h"
 #include "ui/events/event.h"
 
 namespace chromeos {
@@ -19,8 +17,7 @@
 // accept or dismiss the suggestions.
 class GrammarManager {
  public:
-  explicit GrammarManager(Profile* profile,
-                          std::unique_ptr<GrammarServiceClient> grammar_client);
+  GrammarManager();
   GrammarManager(const GrammarManager&) = delete;
   GrammarManager& operator=(const GrammarManager&) = delete;
   ~GrammarManager();
@@ -44,13 +41,6 @@
  private:
   void Check(const std::u16string& text);
 
-  void OnGrammarCheckDone(
-      const std::u16string& text,
-      bool success,
-      const std::vector<ui::GrammarFragment>& results) const;
-
-  Profile* profile_;
-  std::unique_ptr<GrammarServiceClient> grammar_client_;
   int context_id_ = 0;
   std::u16string last_text_;
   base::OneShotTimer delay_timer_;
diff --git a/chrome/browser/chromeos/input_method/grammar_manager_unittest.cc b/chrome/browser/chromeos/input_method/grammar_manager_unittest.cc
deleted file mode 100644
index 2924412..0000000
--- a/chrome/browser/chromeos/input_method/grammar_manager_unittest.cc
+++ /dev/null
@@ -1,123 +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 "chrome/browser/chromeos/input_method/grammar_manager.h"
-
-#include "chrome/browser/chromeos/input_method/grammar_service_client.h"
-#include "chrome/test/base/testing_profile.h"
-#include "chromeos/services/machine_learning/public/cpp/fake_service_connection.h"
-#include "components/spellcheck/common/spellcheck_result.h"
-#include "content/public/test/browser_task_environment.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/ime/chromeos/ime_bridge.h"
-#include "ui/base/ime/chromeos/mock_ime_input_context_handler.h"
-
-namespace chromeos {
-namespace {
-
-class TestGrammarServiceClient : public GrammarServiceClient {
- public:
-  TestGrammarServiceClient() {}
-  ~TestGrammarServiceClient() override = default;
-
-  bool RequestTextCheck(Profile* profile,
-                        const std::u16string& text,
-                        TextCheckCompleteCallback callback) const override {
-    std::vector<ui::GrammarFragment> grammar_results;
-    for (int i = 0; i < text.size(); i++) {
-      if (text.substr(i, 5) == u"error") {
-        grammar_results.emplace_back(gfx::Range(i, i + 5), "correct");
-      }
-    }
-    std::move(callback).Run(true, grammar_results);
-    return true;
-  }
-};
-
-class GrammarManagerTest : public testing::Test {
- protected:
-  void SetUp() override {
-    profile_ = std::make_unique<TestingProfile>();
-    machine_learning::ServiceConnection::UseFakeServiceConnectionForTesting(
-        &fake_service_connection_);
-    machine_learning::ServiceConnection::GetInstance()->Initialize();
-  }
-
-  content::BrowserTaskEnvironment task_environment_{
-      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-
-  std::unique_ptr<TestingProfile> profile_;
-  machine_learning::FakeServiceConnectionImpl fake_service_connection_;
-};
-
-TEST_F(GrammarManagerTest, HandlesSingleGrammarCheckResult) {
-  ui::IMEBridge::Initialize();
-  ui::MockIMEInputContextHandler mock_ime_input_context_handler;
-  ui::IMEBridge::Get()->SetInputContextHandler(&mock_ime_input_context_handler);
-
-  GrammarManager manager(profile_.get(),
-                         std::make_unique<TestGrammarServiceClient>());
-
-  manager.OnFocus(1);
-  manager.OnSurroundingTextChanged(u"There is error.", 0, 0);
-  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
-
-  auto grammar_fragments =
-      mock_ime_input_context_handler.get_grammar_fragments();
-  EXPECT_EQ(grammar_fragments.size(), 1);
-  EXPECT_EQ(grammar_fragments[0].range, gfx::Range(9, 14));
-  EXPECT_EQ(grammar_fragments[0].suggestion, "correct");
-}
-
-TEST_F(GrammarManagerTest, HandlesMultipleGrammarCheckResults) {
-  ui::IMEBridge::Initialize();
-  ui::MockIMEInputContextHandler mock_ime_input_context_handler;
-  ui::IMEBridge::Get()->SetInputContextHandler(&mock_ime_input_context_handler);
-
-  GrammarManager manager(profile_.get(),
-                         std::make_unique<TestGrammarServiceClient>());
-
-  manager.OnFocus(1);
-  manager.OnSurroundingTextChanged(u"There is error error.", 0, 0);
-  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
-
-  auto grammar_fragments =
-      mock_ime_input_context_handler.get_grammar_fragments();
-  EXPECT_EQ(grammar_fragments.size(), 2);
-  EXPECT_EQ(grammar_fragments[0].range, gfx::Range(9, 14));
-  EXPECT_EQ(grammar_fragments[0].suggestion, "correct");
-  EXPECT_EQ(grammar_fragments[1].range, gfx::Range(15, 20));
-  EXPECT_EQ(grammar_fragments[0].suggestion, "correct");
-}
-
-TEST_F(GrammarManagerTest, ClearsPreviousMarkersUponGettingNewResults) {
-  ui::IMEBridge::Initialize();
-  ui::MockIMEInputContextHandler mock_ime_input_context_handler;
-  ui::IMEBridge::Get()->SetInputContextHandler(&mock_ime_input_context_handler);
-
-  GrammarManager manager(profile_.get(),
-                         std::make_unique<TestGrammarServiceClient>());
-
-  manager.OnFocus(1);
-  manager.OnSurroundingTextChanged(u"There is error.", 0, 0);
-  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
-
-  auto grammar_fragments =
-      mock_ime_input_context_handler.get_grammar_fragments();
-  EXPECT_EQ(grammar_fragments.size(), 1);
-  EXPECT_EQ(grammar_fragments[0].range, gfx::Range(9, 14));
-  EXPECT_EQ(grammar_fragments[0].suggestion, "correct");
-
-  manager.OnSurroundingTextChanged(u"There is a new error.", 0, 0);
-  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
-
-  auto updated_grammar_fragments =
-      mock_ime_input_context_handler.get_grammar_fragments();
-  EXPECT_EQ(updated_grammar_fragments.size(), 1);
-  EXPECT_EQ(updated_grammar_fragments[0].range, gfx::Range(15, 20));
-  EXPECT_EQ(updated_grammar_fragments[0].suggestion, "correct");
-}
-
-}  // namespace
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/grammar_service_client.cc b/chrome/browser/chromeos/input_method/grammar_service_client.cc
index 96204a3..b5f5197 100644
--- a/chrome/browser/chromeos/input_method/grammar_service_client.cc
+++ b/chrome/browser/chromeos/input_method/grammar_service_client.cc
@@ -10,8 +10,8 @@
 #include "chromeos/services/machine_learning/public/cpp/service_connection.h"
 #include "components/prefs/pref_service.h"
 #include "components/spellcheck/browser/pref_names.h"
+#include "components/spellcheck/common/spellcheck_result.h"
 #include "components/user_prefs/user_prefs.h"
-#include "ui/gfx/range/range.h"
 
 namespace chromeos {
 namespace {
@@ -121,7 +121,7 @@
       !result->candidates.empty()) {
     const auto& top_candidate = result->candidates.front();
     if (!top_candidate->text.empty() && !top_candidate->fragments.empty()) {
-      std::vector<ui::GrammarFragment> grammar_results;
+      std::vector<SpellCheckResult> grammar_results;
       for (const auto& fragment : top_candidate->fragments) {
         uint32_t start;
         uint32_t end;
@@ -138,8 +138,9 @@
           // Compute the offsets in string16.
           std::vector<size_t> offsets = {start, end};
           base::UTF8ToUTF16AndAdjustOffsets(query_text, &offsets);
-          grammar_results.emplace_back(gfx::Range(offsets[0], offsets[1]),
-                                       fragment->replacement);
+          grammar_results.emplace_back(
+              SpellCheckResult::GRAMMAR, offsets[0], offsets[1] - offsets[0],
+              base::UTF8ToUTF16(fragment->replacement));
         }
       }
       std::move(callback).Run(true, grammar_results);
diff --git a/chrome/browser/chromeos/input_method/grammar_service_client.h b/chrome/browser/chromeos/input_method/grammar_service_client.h
index 9257592..02fc63c2 100644
--- a/chrome/browser/chromeos/input_method/grammar_service_client.h
+++ b/chrome/browser/chromeos/input_method/grammar_service_client.h
@@ -12,7 +12,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chromeos/services/machine_learning/public/mojom/grammar_checker.mojom.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "ui/base/ime/grammar_fragment.h"
+
+struct SpellCheckResult;
 
 namespace chromeos {
 
@@ -46,17 +47,17 @@
 class GrammarServiceClient {
  public:
   GrammarServiceClient();
-  virtual ~GrammarServiceClient();
+  ~GrammarServiceClient();
 
   using TextCheckCompleteCallback = base::OnceCallback<void(
       bool /* success */,
-      const std::vector<ui::GrammarFragment>& /* results */)>;
+      const std::vector<SpellCheckResult>& /* results */)>;
 
   // Sends grammar check request to ML service, parses the reponse
   // and calls a provided callback method.
-  virtual bool RequestTextCheck(Profile* profile,
-                                const std::u16string& text,
-                                TextCheckCompleteCallback callback) const;
+  bool RequestTextCheck(Profile* profile,
+                        const std::u16string& text,
+                        TextCheckCompleteCallback callback) const;
 
  private:
   // Parse the result returned from grammar check service.
diff --git a/chrome/browser/chromeos/input_method/grammar_service_client_unittest.cc b/chrome/browser/chromeos/input_method/grammar_service_client_unittest.cc
index d359e4aa..3ca1047 100644
--- a/chrome/browser/chromeos/input_method/grammar_service_client_unittest.cc
+++ b/chrome/browser/chromeos/input_method/grammar_service_client_unittest.cc
@@ -14,10 +14,9 @@
 #include "chromeos/services/machine_learning/public/mojom/grammar_checker.mojom.h"
 #include "components/prefs/pref_service.h"
 #include "components/spellcheck/browser/pref_names.h"
+#include "components/spellcheck/common/spellcheck_result.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/ime/grammar_fragment.h"
-#include "ui/gfx/range/range.h"
 
 namespace chromeos {
 namespace {
@@ -47,7 +46,7 @@
   client.RequestTextCheck(
       profile.get(), u"cat",
       base::BindOnce(
-          [](bool success, const std::vector<ui::GrammarFragment>& results) {
+          [](bool success, const std::vector<SpellCheckResult>& results) {
             EXPECT_FALSE(success);
             EXPECT_TRUE(results.empty());
           }));
@@ -89,11 +88,14 @@
   client.RequestTextCheck(
       profile.get(), u"fake input",
       base::BindOnce(
-          [](bool success, const std::vector<ui::GrammarFragment>& results) {
+          [](bool success, const std::vector<SpellCheckResult>& results) {
             EXPECT_TRUE(success);
             ASSERT_EQ(results.size(), 1U);
-            EXPECT_EQ(results[0].range, gfx::Range(3, 8));
-            EXPECT_EQ(results[0].suggestion, "fake replacement");
+            EXPECT_EQ(results[0].decoration, SpellCheckResult::GRAMMAR);
+            EXPECT_EQ(results[0].location, 3);
+            EXPECT_EQ(results[0].length, 5);
+            ASSERT_EQ(results[0].replacements.size(), 1U);
+            EXPECT_EQ(results[0].replacements[0], u"fake replacement");
           }));
 
   base::RunLoop().RunUntilIdle();
@@ -139,13 +141,16 @@
   client.RequestTextCheck(
       profile.get(), long_text,
       base::BindOnce(
-          [](bool success, const std::vector<ui::GrammarFragment>& results) {
+          [](bool success, const std::vector<SpellCheckResult>& results) {
             EXPECT_TRUE(success);
             ASSERT_EQ(results.size(), 1U);
+            EXPECT_EQ(results[0].decoration, SpellCheckResult::GRAMMAR);
             // The fake grammar check result is set to return offset=3, so here
             // getting the location as 203 verifies the trimming.
-            EXPECT_EQ(results[0].range, gfx::Range(203, 208));
-            EXPECT_EQ(results[0].suggestion, "fake replacement");
+            EXPECT_EQ(results[0].location, 203);
+            EXPECT_EQ(results[0].length, 5);
+            ASSERT_EQ(results[0].replacements.size(), 1U);
+            EXPECT_EQ(results[0].replacements[0], u"fake replacement");
           }));
 
   base::RunLoop().RunUntilIdle();
diff --git a/chrome/browser/chromeos/input_method/native_input_method_engine.cc b/chrome/browser/chromeos/input_method/native_input_method_engine.cc
index c1ca86d3..6554862 100644
--- a/chrome/browser/chromeos/input_method/native_input_method_engine.cc
+++ b/chrome/browser/chromeos/input_method/native_input_method_engine.cc
@@ -15,7 +15,6 @@
 #include "base/strings/utf_offset_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/chromeos/input_method/autocorrect_manager.h"
-#include "chrome/browser/chromeos/input_method/grammar_service_client.h"
 #include "chrome/browser/chromeos/input_method/suggestions_service_client.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
@@ -189,9 +188,7 @@
       std::make_unique<chromeos::NativeInputMethodEngine::ImeObserver>(
           profile->GetPrefs(), std::move(observer),
           std::move(assistive_suggester), std::move(autocorrect_manager),
-          std::move(suggestions_collector),
-          std::make_unique<GrammarManager>(
-              profile, std::make_unique<GrammarServiceClient>()));
+          std::move(suggestions_collector), std::make_unique<GrammarManager>());
   InputMethodEngine::Initialize(std::move(native_observer), extension_id,
                                 profile);
 }
diff --git a/chrome/browser/chromeos/input_method/native_input_method_engine_unittest.cc b/chrome/browser/chromeos/input_method/native_input_method_engine_unittest.cc
index 427124e0..c941473 100644
--- a/chrome/browser/chromeos/input_method/native_input_method_engine_unittest.cc
+++ b/chrome/browser/chromeos/input_method/native_input_method_engine_unittest.cc
@@ -13,7 +13,6 @@
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/services/ime/mock_input_channel.h"
 #include "chromeos/services/ime/public/mojom/input_engine.mojom.h"
-#include "chromeos/services/machine_learning/public/cpp/fake_service_connection.h"
 #include "components/ukm/content/source_url_recorder.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/web_contents.h"
@@ -120,10 +119,6 @@
     // Needed by NativeInputMethodEngine for the virtual keyboard.
     keyboard_controller_client_test_helper_ =
         ChromeKeyboardControllerClientTestHelper::InitializeWithFake();
-
-    machine_learning::ServiceConnection::UseFakeServiceConnectionForTesting(
-        &fake_service_connection_);
-    machine_learning::ServiceConnection::GetInstance()->Initialize();
   }
 
  private:
@@ -131,7 +126,6 @@
   base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<ChromeKeyboardControllerClientTestHelper>
       keyboard_controller_client_test_helper_;
-  machine_learning::FakeServiceConnectionImpl fake_service_connection_;
 };
 
 TEST_F(NativeInputMethodEngineTest, DoesNotLaunchImeServiceIfAutocorrectIsOff) {
@@ -385,10 +379,6 @@
     // Needed by NativeInputMethodEngine for the virtual keyboard.
     keyboard_controller_client_test_helper_ =
         ChromeKeyboardControllerClientTestHelper::InitializeWithFake();
-
-    machine_learning::ServiceConnection::UseFakeServiceConnectionForTesting(
-        &fake_service_connection_);
-    machine_learning::ServiceConnection::GetInstance()->Initialize();
   }
 
   std::unique_ptr<content::BrowserContext> CreateBrowserContext() override {
@@ -399,7 +389,6 @@
   base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<ChromeKeyboardControllerClientTestHelper>
       keyboard_controller_client_test_helper_;
-  machine_learning::FakeServiceConnectionImpl fake_service_connection_;
 };
 
 TEST_F(NativeInputMethodEngineWithRenderViewHostTest, RecordUkmAddsUkmEntry) {
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry.cc b/chrome/browser/custom_handlers/protocol_handler_registry.cc
index 041ab36b..70c38fd 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry.cc
@@ -25,7 +25,6 @@
 #include "components/user_prefs/user_prefs.h"
 #include "content/public/browser/child_process_security_policy.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/security/protocol_handler_security_level.h"
 
 using content::BrowserThread;
 using content::ChildProcessSecurityPolicy;
@@ -118,30 +117,6 @@
       ->StartCheckIsDefault(std::move(callback));
 }
 
-void ProtocolHandlerRegistry::Delegate::RegisterAppProtocolsWithOS(
-    const base::FilePath& app_profile_path,
-    const shell_integration::AppProtocolMap app_protocols,
-    base::OnceCallback<void(bool)> registration_complete_callback) {
-  shell_integration::AddAppProtocolClients(
-      app_protocols, app_profile_path,
-      std::move(registration_complete_callback));
-}
-
-void ProtocolHandlerRegistry::Delegate::DeregisterAppProtocolsWithOS(
-    const base::FilePath& app_profile_path,
-    const std::vector<std::string>& app_protocols) {
-  shell_integration::RemoveAppProtocolClients(app_protocols, app_profile_path);
-}
-
-void ProtocolHandlerRegistry::Delegate::CheckAppIsDefaultClientWithOS(
-    const std::string& app_id,
-    const base::FilePath& app_profile_path,
-    const std::string& protocol,
-    base::OnceCallback<void(bool)> check_complete_callback) {
-  shell_integration::CheckAppIsProtocolClient(
-      app_id, protocol, app_profile_path, std::move(check_complete_callback));
-}
-
 // ProtocolHandlerRegistry -----------------------------------------------------
 
 ProtocolHandlerRegistry::ProtocolHandlerRegistry(
@@ -193,50 +168,6 @@
   NotifyChanged();
 }
 
-void ProtocolHandlerRegistry::RegisterAppProtocolHandlers(
-    const std::string& app_id,
-    const std::vector<apps::ProtocolHandlerInfo>& handler_infos) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  std::vector<std::string> app_protocols;
-
-  for (const auto& handler_info : handler_infos) {
-    ProtocolHandler handler = ProtocolHandler::CreateWebAppProtocolHandler(
-        handler_info.protocol, handler_info.url, app_id);
-    if (!handler.IsValid() || !RegisterProtocolHandler(handler, USER))
-      continue;
-
-    app_protocols.push_back(handler.protocol());
-
-    ProtocolHandler default_handler = GetHandlerFor(handler.protocol());
-    if (default_handler.IsEmpty()) {
-      SetDefaultImpl(handler);
-    }
-  }
-
-  UpdateAppProtocolsWithOS(app_protocols);
-
-  Save();
-  NotifyChanged();
-}
-
-void ProtocolHandlerRegistry::DeregisterAppProtocolHandlers(
-    const std::string& app_id,
-    const std::vector<apps::ProtocolHandlerInfo>& handler_infos) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  std::vector<ProtocolHandler> handlers;
-  for (const auto& handler_info : handler_infos) {
-    ProtocolHandler handler = ProtocolHandler::CreateWebAppProtocolHandler(
-        handler_info.protocol, handler_info.url, app_id);
-    if (HandlerExists(handler, &protocol_handlers_)) {
-      handlers.push_back(handler);
-    }
-  }
-
-  RemoveHandlers(handlers);
-}
-
 bool ProtocolHandlerRegistry::AttemptReplace(const ProtocolHandler& handler) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   ProtocolHandler old_default = GetHandlerFor(handler.protocol());
@@ -331,15 +262,8 @@
     for (ProtocolHandlerMap::const_iterator p = default_handlers_.begin();
          p != default_handlers_.end(); ++p) {
       const std::string& protocol = p->second.protocol();
-      if (p->second.web_app_id()) {
-        std::string app_id = p->second.web_app_id().value();
-        delegate_->CheckAppIsDefaultClientWithOS(
-            app_id, context_->GetPath(), protocol,
-            GetAppProtocolWorkerCallback({{protocol, app_id}}));
-      } else {
-        delegate_->CheckDefaultClientWithOS(
-            protocol, GetDefaultWebClientCallback(protocol));
-      }
+      delegate_->CheckDefaultClientWithOS(
+          protocol, GetDefaultWebClientCallback(protocol));
     }
   }
 }
@@ -520,82 +444,37 @@
 }
 
 void ProtocolHandlerRegistry::RemoveHandler(const ProtocolHandler& handler) {
-  RemoveHandlers({handler});
-}
+  if (IsIgnored(handler)) {
+    RemoveIgnoredHandler(handler);
+    return;
+  }
 
-void ProtocolHandlerRegistry::RemoveHandlers(
-    const std::vector<ProtocolHandler>& handlers) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  std::vector<ProtocolHandler> erased_handlers;
-  std::vector<std::string> app_protocols_to_remove_from_os;
-
-  for (const auto& handler : handlers) {
-    if (IsIgnored(handler)) {
-      RemoveIgnoredHandler(handler);
-      continue;
-    }
-
-    ProtocolHandlerList& existing_handlers =
-        protocol_handlers_[handler.protocol()];
-    bool erase_success = false;
-    if (HandlerExists(handler, existing_handlers) &&
-        HandlerExists(handler, &user_protocol_handlers_)) {
-      EraseHandler(handler, &user_protocol_handlers_);
-      erase_success = true;
-      if (!HandlerExists(handler, &policy_protocol_handlers_))
-        EraseHandler(handler, &protocol_handlers_);
-    }
-
-    if (!erase_success)
-      continue;
-    else
-      erased_handlers.push_back(handler);
+  ProtocolHandlerList& handlers = protocol_handlers_[handler.protocol()];
+  bool erase_success = false;
+  if (HandlerExists(handler, handlers) &&
+      HandlerExists(handler, &user_protocol_handlers_)) {
+    EraseHandler(handler, &user_protocol_handlers_);
+    erase_success = true;
+    if (!HandlerExists(handler, &policy_protocol_handlers_))
+      EraseHandler(handler, &protocol_handlers_);
   }
-
-  std::vector<ProtocolHandler> updated_default_handlers;
-
-  // Choose new defaults for erased handlers that were default handlers.
-  for (const auto& erased_handler : erased_handlers) {
-    ProtocolHandlerList& existing_handlers =
-        protocol_handlers_[erased_handler.protocol()];
-    ProtocolHandler default_handler = GetHandlerFor(erased_handler.protocol());
-    if (default_handler == erased_handler || erased_handler.web_app_id()) {
-      // Removing the default handler for a protocol requires updating the
-      // default registration. If the default handler is a web app handler, its
-      // removal may require updating the the default registration as the
-      // protocol may no longer require disambiguation (i.e.: if a single app is
-      // left as a handler for a protocol, it should be registered with the OS
-      // instead).
-      if (!existing_handlers.empty()) {
-        updated_default_handlers.push_back(existing_handlers[0]);
-      } else {
-        default_handlers_.erase(erased_handler.protocol());
-      }
-    }
-
-    // TODO(1132105): Implement DeregisterExternalHandler so that websites can
-    // be unregistered with the OS. Alternatively, expand
-    // DeregisterAppProtocolsWithOS's scope to work with non-app protocols.
-    if (!IsHandledProtocol(erased_handler.protocol())) {
-      delegate_->DeregisterExternalHandler(erased_handler.protocol());
-    }
-
-    if (GetHandlerFor(erased_handler.protocol()).IsEmpty() &&
-        erased_handler.web_app_id()) {
-      // Web app protocols are removed from the OS when the protocol no longer
-      // has a default handler.
-      app_protocols_to_remove_from_os.push_back(erased_handler.protocol());
+  auto q = default_handlers_.find(handler.protocol());
+  if (erase_success && q != default_handlers_.end() && q->second == handler) {
+    // Make the new top handler in the list the default.
+    if (!handlers.empty()) {
+      // NOTE We pass a copy because SetDefault() modifies handlers.
+      SetDefault(ProtocolHandler(handlers[0]));
+    } else {
+      default_handlers_.erase(q);
     }
   }
 
-  SetDefaults(updated_default_handlers);
-  if (!app_protocols_to_remove_from_os.empty())
-    delegate_->DeregisterAppProtocolsWithOS(context_->GetPath(),
-                                            app_protocols_to_remove_from_os);
-
+  if (erase_success && !IsHandledProtocol(handler.protocol())) {
+    delegate_->DeregisterExternalHandler(handler.protocol());
+  }
   Save();
-  if (!erased_handlers.empty())
+  if (erase_success)
     NotifyChanged();
 }
 
@@ -714,43 +593,18 @@
 }
 
 void ProtocolHandlerRegistry::SetDefault(const ProtocolHandler& handler) {
-  SetDefaults({handler});
-}
-
-void ProtocolHandlerRegistry::SetDefaults(
-    const std::vector<ProtocolHandler>& handlers) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  for (const auto& handler : handlers) {
-    SetDefaultImpl(handler);
-  }
-
-  if (is_loading_)
-    return;
-
-  // Separate site handlers from web app handlers so that web app handlers can
-  // be registered to the OS in a batch operation.
-  std::vector<std::string> app_protocols;
-  for (const auto& handler : handlers) {
-    if (handler.web_app_id()) {
-      app_protocols.push_back(handler.protocol());
-    } else {
-      delegate_->RegisterWithOSAsDefaultClient(
-          handler.protocol(), GetDefaultWebClientCallback(handler.protocol()));
-    }
-  }
-
-  UpdateAppProtocolsWithOS(app_protocols);
-
-  Save();
-  NotifyChanged();
-}
-
-void ProtocolHandlerRegistry::SetDefaultImpl(const ProtocolHandler& handler) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   const std::string& protocol = handler.protocol();
+  ProtocolHandlerMap::const_iterator p = default_handlers_.find(protocol);
+  // If we're not loading, and we are setting a default for a new protocol,
+  // register with the OS.
+  if (!is_loading_ && p == default_handlers_.end())
+    delegate_->RegisterWithOSAsDefaultClient(
+        protocol, GetDefaultWebClientCallback(protocol));
   default_handlers_.erase(protocol);
   default_handlers_.insert(std::make_pair(protocol, handler));
+
   PromoteHandler(handler);
 }
 
@@ -913,30 +767,6 @@
   list->erase(std::find(list->begin(), list->end(), handler));
 }
 
-void ProtocolHandlerRegistry::UpdateAppProtocolsWithOS(
-    const std::vector<std::string>& protocols) {
-  if (protocols.empty())
-    return;
-
-  shell_integration::AppProtocolMap app_protocols;
-
-  for (const auto& protocol : protocols) {
-    ProtocolHandler default_handler = GetHandlerFor(protocol);
-    ProtocolHandlerList handlers = protocol_handlers_[protocol];
-    // If other handlers exist for the protocol, the protocol will be
-    // registered for disambiguation (i.e.: the browser will be registered
-    // with the OS as the handler instead of the app).
-    if (handlers.size() == 1 && handlers[0] == default_handler)
-      app_protocols[protocol] = default_handler.web_app_id();
-    else
-      app_protocols[protocol] = absl::nullopt;
-  }
-
-  delegate_->RegisterAppProtocolsWithOS(
-      context_->GetPath(), app_protocols,
-      GetAppProtocolWorkerCallback(app_protocols));
-}
-
 void ProtocolHandlerRegistry::OnSetAsDefaultProtocolClientFinished(
     const std::string& protocol,
     shell_integration::DefaultWebClientState state) {
@@ -963,21 +793,3 @@
       &ProtocolHandlerRegistry::OnSetAsDefaultProtocolClientFinished,
       weak_ptr_factory_.GetWeakPtr(), protocol);
 }
-
-void ProtocolHandlerRegistry::OnAppProtocolOperationFinished(
-    const shell_integration::AppProtocolMap& app_protocols,
-    bool registration_success) {
-  // Clear if the protocol could not be registered
-  if (ShouldRemoveHandlersNotInOS() && !registration_success) {
-    for (const auto& app_protocol_pair : app_protocols)
-      ClearDefault(app_protocol_pair.first);
-  }
-}
-
-base::OnceCallback<void(bool)>
-ProtocolHandlerRegistry::GetAppProtocolWorkerCallback(
-    const shell_integration::AppProtocolMap& app_protocols) {
-  return base::BindOnce(
-      &ProtocolHandlerRegistry::OnAppProtocolOperationFinished,
-      weak_ptr_factory_.GetWeakPtr(), app_protocols);
-}
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry.h b/chrome/browser/custom_handlers/protocol_handler_registry.h
index ab76f62..ab449ca 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry.h
+++ b/chrome/browser/custom_handlers/protocol_handler_registry.h
@@ -19,7 +19,6 @@
 #include "chrome/browser/shell_integration.h"
 #include "chrome/common/custom_handlers/protocol_handler.h"
 #include "components/keyed_service/core/keyed_service.h"
-#include "components/services/app_service/public/cpp/protocol_handler_info.h"
 #include "content/public/browser/browser_thread.h"
 
 namespace user_prefs {
@@ -42,9 +41,9 @@
   typedef std::vector<ProtocolHandler> ProtocolHandlerList;
   typedef std::map<std::string, ProtocolHandlerList> ProtocolHandlerMultiMap;
 
-  // |Delegate| provides an interface for interacting asynchronously with the
-  // underlying OS for the purposes of registering Chrome and web apps as
-  // default handlers for specific protocols.
+  // |Delegate| provides an interface for interacting asynchronously
+  // with the underlying OS for the purposes of registering Chrome
+  // as the default handler for specific protocols.
   class Delegate {
    public:
     virtual ~Delegate();
@@ -57,18 +56,6 @@
     virtual void CheckDefaultClientWithOS(
         const std::string& protocol,
         shell_integration::DefaultWebClientWorkerCallback callback);
-    virtual void RegisterAppProtocolsWithOS(
-        const base::FilePath& app_profile_path,
-        const shell_integration::AppProtocolMap app_protocols,
-        base::OnceCallback<void(bool)> registration_complete_callback);
-    virtual void DeregisterAppProtocolsWithOS(
-        const base::FilePath& app_profile_path,
-        const std::vector<std::string>& app_protocols);
-    virtual void CheckAppIsDefaultClientWithOS(
-        const std::string& app_id,
-        const base::FilePath& app_profile_path,
-        const std::string& protocol,
-        base::OnceCallback<void(bool)> check_complete_callback);
   };
 
   class Observer : public base::CheckedObserver {
@@ -100,18 +87,6 @@
   // given protocol handler again.
   void OnIgnoreRegisterProtocolHandler(const ProtocolHandler& handler);
 
-  // Registers each web app protocol handler as a non default handler if another
-  // handler exists for its scheme or as a default handler for uncontested
-  // schemes.
-  void RegisterAppProtocolHandlers(
-      const std::string& app_id,
-      const std::vector<apps::ProtocolHandlerInfo>& handler_infos);
-
-  // Removes each web app protocol handler from the registry and OS.
-  void DeregisterAppProtocolHandlers(
-      const std::string& app_id,
-      const std::vector<apps::ProtocolHandlerInfo>& handler_infos);
-
   // Removes all handlers that have the same origin and protocol as the given
   // one and installs the given handler. Returns true if any protocol handlers
   // were replaced.
@@ -188,9 +163,6 @@
   // Removes the given protocol handler from the registry.
   void RemoveHandler(const ProtocolHandler& handler);
 
-  // Removes multiple protocol handlers from the registry.
-  void RemoveHandlers(const std::vector<ProtocolHandler>& handlers);
-
   // Remove the default handler for the given protocol.
   void RemoveDefaultHandler(const std::string& scheme);
 
@@ -252,13 +224,6 @@
   // Makes this ProtocolHandler the default handler for its protocol.
   void SetDefault(const ProtocolHandler& handler);
 
-  // Makes these ProtocolHandler the default handlers for their protocols and
-  // registers the handlers with the OS.
-  void SetDefaults(const std::vector<ProtocolHandler>& handlers);
-
-  // Makes a handler the default for its protocol inside the registry.
-  void SetDefaultImpl(const ProtocolHandler& handler);
-
   // Insert the given ProtocolHandler into the registry.
   void InsertHandler(const ProtocolHandler& handler);
 
@@ -319,13 +284,6 @@
   // Erases the handler that is guaranteed to exist from the list.
   void EraseHandler(const ProtocolHandler& handler, ProtocolHandlerList* list);
 
-  // Updates the OS registration for each protocol based on the registry state
-  // of corresponding app handlers. For each protocol, the resulting OS
-  // registration may be the associated app, the browser (for disambiguation),
-  // or no registration if the app handler is not present in the registry.
-  // |protocols| is expected to contain a list of unique protocols.
-  void UpdateAppProtocolsWithOS(const std::vector<std::string>& protocols);
-
   // Called with the default state when the default protocol client worker is
   // done.
   void OnSetAsDefaultProtocolClientFinished(
@@ -336,16 +294,6 @@
   shell_integration::DefaultWebClientWorkerCallback GetDefaultWebClientCallback(
       const std::string& protocol);
 
-  // Called with success flag when an asynchronous app protocols operation is
-  // complete.
-  void OnAppProtocolOperationFinished(
-      const shell_integration::AppProtocolMap& app_protocols,
-      bool operation_success);
-
-  // Gets the callback for AppProtocolWorkerCallback.
-  base::OnceCallback<void(bool)> GetAppProtocolWorkerCallback(
-      const shell_integration::AppProtocolMap& protocols);
-
   // Map from protocols (strings) to protocol handlers.
   ProtocolHandlerMultiMap protocol_handlers_;
 
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc b/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
index 88e88e1..a23c107f 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/scoped_observation.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
-#include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chrome/common/custom_handlers/protocol_handler.h"
@@ -29,7 +28,6 @@
 #include "content/public/test/test_renderer_host.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/security/protocol_handler_security_level.h"
-#include "url/gurl.h"
 
 using content::BrowserThread;
 
@@ -92,43 +90,9 @@
         os_registered_protocols_.end();
   }
 
-  void RegisterAppProtocolsWithOS(
-      const base::FilePath& app_profile_path,
-      const shell_integration::AppProtocolMap app_protocols,
-      base::OnceCallback<void(bool)> registration_complete_callback) override {
-    // Do as-if the registration has to run on another sequence and post back
-    // the result with a task to the current thread.
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(registration_complete_callback),
-                                  !force_os_failure_));
-
-    if (!force_os_failure_) {
-      for (const auto& protocol_pair : app_protocols) {
-        os_registered_app_protocols_[protocol_pair.first] =
-            protocol_pair.second;
-      }
-    }
-  }
-
-  void DeregisterAppProtocolsWithOS(
-      const base::FilePath& app_profile_path,
-      const std::vector<std::string>& app_protocols) override {
-    for (const auto& protocol : app_protocols) {
-      os_registered_app_protocols_.erase(protocol);
-    }
-  }
-
-  bool IsFakeAppHandlerRegisteredWithOS(const std::string& protocol,
-                                        absl::optional<std::string> app_id) {
-    auto handler_iter = os_registered_app_protocols_.find(protocol);
-    return handler_iter != os_registered_app_protocols_.end() &&
-           handler_iter->second == app_id;
-  }
-
   void Reset() {
     registered_protocols_.clear();
     os_registered_protocols_.clear();
-    os_registered_app_protocols_.clear();
     force_os_failure_ = false;
   }
 
@@ -139,7 +103,6 @@
  private:
   std::set<std::string> registered_protocols_;
   std::set<std::string> os_registered_protocols_;
-  shell_integration::AppProtocolMap os_registered_app_protocols_;
   bool force_os_failure_;
 };
 
@@ -194,21 +157,6 @@
   DISALLOW_COPY_AND_ASSIGN(QueryProtocolHandlerOnChange);
 };
 
-apps::ProtocolHandlerInfo GetProtocolHandlerInfo(const std::string& protocol,
-                                                 const GURL& url) {
-  apps::ProtocolHandlerInfo handler_info;
-  handler_info.protocol = protocol;
-  handler_info.url = url;
-  return handler_info;
-}
-
-ProtocolHandler GetProtocolHandler(
-    const apps::ProtocolHandlerInfo& handler_info,
-    const std::string& app_id) {
-  return ProtocolHandler::CreateWebAppProtocolHandler(handler_info.protocol,
-                                                      handler_info.url, app_id);
-}
-
 }  // namespace
 
 class ProtocolHandlerRegistryTest : public testing::Test {
@@ -395,7 +343,6 @@
       ProtocolHandler::CreateProtocolHandler(value.get());
   EXPECT_EQ("news", recreated.protocol());
   EXPECT_EQ(GURL("https://example.com"), recreated.url());
-  EXPECT_EQ("app_id", recreated.web_app_id());
   EXPECT_EQ(now, recreated.last_modified());
 }
 
@@ -1288,162 +1235,3 @@
       "ext+foo", https_handler_url,
       blink::ProtocolHandlerSecurityLevel::kExtensionFeatures));
 }
-
-TEST_F(ProtocolHandlerRegistryTest, TestRegisterWebAppHandlers) {
-  EXPECT_FALSE(delegate()->IsExternalHandlerRegistered("mailto"));
-  EXPECT_FALSE(delegate()->IsExternalHandlerRegistered("web+test"));
-
-  apps::ProtocolHandlerInfo ph1_info =
-      GetProtocolHandlerInfo("mailto", GURL("https://test1.com/%s"));
-  ProtocolHandler ph1 = GetProtocolHandler(ph1_info, "app_id1");
-
-  apps::ProtocolHandlerInfo ph2_info =
-      GetProtocolHandlerInfo("web+test", GURL("https://test2.com/%s"));
-  ProtocolHandler ph2 = GetProtocolHandler(ph2_info, "app_id1");
-
-  registry()->RegisterAppProtocolHandlers("app_id1", {ph1_info, ph2_info});
-
-  // After registration, both handlers should be externally registered with the
-  // OS and registered as defaults in the ProtocolHandlerRegistsry.
-  EXPECT_TRUE(delegate()->IsExternalHandlerRegistered("mailto"));
-  EXPECT_TRUE(delegate()->IsExternalHandlerRegistered("web+test"));
-
-  EXPECT_TRUE(registry()->IsRegistered(ph1));
-  EXPECT_TRUE(registry()->IsDefault(ph1));
-  EXPECT_TRUE(delegate()->IsFakeAppHandlerRegisteredWithOS(ph1.protocol(),
-                                                           ph1.web_app_id()));
-
-  EXPECT_TRUE(registry()->IsRegistered(ph2));
-  EXPECT_TRUE(registry()->IsDefault(ph2));
-  EXPECT_TRUE(delegate()->IsFakeAppHandlerRegisteredWithOS(ph2.protocol(),
-                                                           ph2.web_app_id()));
-}
-
-TEST_F(ProtocolHandlerRegistryTest,
-       TestDuplicateSchemeAppHandlersAreNonDefault) {
-  ProtocolHandler ph1 = ProtocolHandler::CreateProtocolHandler(
-      "mailto", GURL("https://test1.com/%s"));
-  registry()->OnAcceptRegisterProtocolHandler(ph1);
-
-  EXPECT_TRUE(registry()->IsRegistered(ph1));
-  EXPECT_TRUE(registry()->IsDefault(ph1));
-  EXPECT_TRUE(delegate()->IsFakeRegisteredWithOS(ph1.protocol()));
-
-  apps::ProtocolHandlerInfo ph2_info =
-      GetProtocolHandlerInfo("mailto", GURL("https://test2.com/%s"));
-  ProtocolHandler ph2 = GetProtocolHandler(ph2_info, "app_id1");
-
-  registry()->RegisterAppProtocolHandlers("app_id1", {ph2_info});
-
-  // Because ph2 was registered second and has the same scheme as ph1, it should
-  // be registered as non default with the ProtocolHandlerRegistry and not
-  // registered with the OS.
-  EXPECT_TRUE(registry()->IsRegistered(ph2));
-  EXPECT_FALSE(registry()->IsDefault(ph2));
-  EXPECT_FALSE(delegate()->IsFakeAppHandlerRegisteredWithOS(ph2.protocol(),
-                                                            ph2.web_app_id()));
-}
-
-TEST_F(ProtocolHandlerRegistryTest,
-       TestDuplicateSchemeAppHandlersAreRegisteredForDisambiguation) {
-  apps::ProtocolHandlerInfo ph1_info =
-      GetProtocolHandlerInfo("mailto", GURL("https://test1.com/%s"));
-  ProtocolHandler ph1 = GetProtocolHandler(ph1_info, "app_id1");
-
-  registry()->RegisterAppProtocolHandlers("app_id1", {ph1_info});
-  EXPECT_TRUE(
-      delegate()->IsFakeAppHandlerRegisteredWithOS(ph1.protocol(), "app_id1"));
-
-  apps::ProtocolHandlerInfo ph2_info =
-      GetProtocolHandlerInfo("mailto", GURL("https://test2.com/%s"));
-  ProtocolHandler ph2 = GetProtocolHandler(ph2_info, "app_id2");
-
-  registry()->RegisterAppProtocolHandlers("app_id2", {ph2_info});
-  // The protocol should be registered for disambiguation with a null app_id.
-  EXPECT_TRUE(delegate()->IsFakeAppHandlerRegisteredWithOS(ph2.protocol(),
-                                                           absl::nullopt));
-}
-
-TEST_F(ProtocolHandlerRegistryTest, TestRemoveWebAppHandlers) {
-  // Register a single handler for web+testing and multiple handlers for mailto.
-  apps::ProtocolHandlerInfo ph1_info =
-      GetProtocolHandlerInfo("mailto", GURL("https://test1.com/%s"));
-  ProtocolHandler ph1 = GetProtocolHandler(ph1_info, "app_id1");
-  apps::ProtocolHandlerInfo ph2_info =
-      GetProtocolHandlerInfo("web+testing", GURL("https://test1.com/%s"));
-  ProtocolHandler ph2 = GetProtocolHandler(ph2_info, "app_id1");
-  registry()->RegisterAppProtocolHandlers("app_id1", {ph1_info, ph2_info});
-
-  apps::ProtocolHandlerInfo ph3_info =
-      GetProtocolHandlerInfo("mailto", GURL("https://test2.com/%s"));
-  ProtocolHandler ph3 = GetProtocolHandler(ph3_info, "app_id2");
-  registry()->RegisterAppProtocolHandlers("app_id2", {ph3_info});
-
-  EXPECT_TRUE(
-      delegate()->IsFakeAppHandlerRegisteredWithOS("mailto", absl::nullopt));
-  EXPECT_TRUE(
-      delegate()->IsFakeAppHandlerRegisteredWithOS("web+testing", "app_id1"));
-
-  // Deregister a single handler for both web+testing and mailto, leaving no
-  // registered web+testing and handlers and one registered mailto handler.
-  registry()->DeregisterAppProtocolHandlers("app_id1", {ph1_info, ph2_info});
-
-  EXPECT_FALSE(registry()->IsRegistered(ph1));
-  EXPECT_FALSE(registry()->IsRegistered(ph2));
-  EXPECT_TRUE(registry()->IsRegistered(ph3));
-  EXPECT_TRUE(registry()->IsDefault(ph3));
-
-  // web+testing should be removed from the OS while mailto should remain
-  // registered with the OS since ph3 was promoted to default.
-  EXPECT_FALSE(
-      delegate()->IsFakeAppHandlerRegisteredWithOS("web+testing", "app_id1"));
-  EXPECT_FALSE(delegate()->IsExternalHandlerRegistered("web+testing"));
-  EXPECT_TRUE(
-      delegate()->IsFakeAppHandlerRegisteredWithOS("mailto", "app_id2"));
-  EXPECT_TRUE(delegate()->IsExternalHandlerRegistered("mailto"));
-}
-
-TEST_F(ProtocolHandlerRegistryTest,
-       TestRemovingNonDefaultHandlerUpdatesDefault) {
-  // Register multiple handlers for mailto and ensure the protocol is registered
-  // for disambiguation.
-  apps::ProtocolHandlerInfo ph1_info =
-      GetProtocolHandlerInfo("mailto", GURL("https://test1.com/%s"));
-  ProtocolHandler ph1 = GetProtocolHandler(ph1_info, "app_id1");
-  registry()->RegisterAppProtocolHandlers("app_id1", {ph1_info});
-
-  apps::ProtocolHandlerInfo ph2_info =
-      GetProtocolHandlerInfo("mailto", GURL("https://test2.com/%s"));
-  ProtocolHandler ph2 = GetProtocolHandler(ph2_info, "app_id2");
-  registry()->RegisterAppProtocolHandlers("app_id2", {ph2_info});
-
-  ASSERT_TRUE(
-      delegate()->IsFakeAppHandlerRegisteredWithOS("mailto", absl::nullopt));
-
-  // Remove the non default handler (ph2) and test that the protocol is handled
-  // by ph1 and no longer registered for disambiguation.
-  registry()->DeregisterAppProtocolHandlers("app_id2", {ph2_info});
-
-  EXPECT_TRUE(
-      delegate()->IsFakeAppHandlerRegisteredWithOS("mailto", "app_id1"));
-}
-
-TEST_F(ProtocolHandlerRegistryTest, TestRegisterHandlersWithOSFailure) {
-  delegate()->set_force_os_failure(true);
-
-  apps::ProtocolHandlerInfo ph1_info =
-      GetProtocolHandlerInfo("mailto", GURL("https://test1.com/%s"));
-  ProtocolHandler ph1 = GetProtocolHandler(ph1_info, "app_id1");
-  registry()->RegisterAppProtocolHandlers("app_id1", {ph1_info});
-  base::RunLoop().RunUntilIdle();
-  base::ThreadPoolInstance::Get()->FlushForTesting();
-
-  EXPECT_TRUE(delegate()->IsExternalHandlerRegistered(ph1.protocol()));
-  EXPECT_TRUE(registry()->IsRegistered(ph1));
-
-#if defined(OS_WIN) || defined(OS_MAC)
-  // Default handlers that failed OS registration should no longer be default.
-  // TODO(crbug.com/1019239): Investigate Linux assumptions.
-  EXPECT_FALSE(registry()->IsDefault(ph1));
-#endif
-}
diff --git a/chrome/browser/dom_distiller/tab_utils_browsertest.cc b/chrome/browser/dom_distiller/tab_utils_browsertest.cc
index a12d9e0..389a3bf 100644
--- a/chrome/browser/dom_distiller/tab_utils_browsertest.cc
+++ b/chrome/browser/dom_distiller/tab_utils_browsertest.cc
@@ -9,6 +9,7 @@
 #include "base/command_line.h"
 #include "base/run_loop.h"
 #include "base/scoped_observation.h"
+#include "base/strings/strcat.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
@@ -51,6 +52,7 @@
 #include "content/public/test/test_utils.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/request_handler_util.h"
+#include "services/network/public/cpp/network_switches.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
@@ -458,13 +460,20 @@
     if (!DistillerJavaScriptWorldIdIsSet()) {
       SetDistillerJavaScriptWorldId(content::ISOLATED_WORLD_ID_CONTENT_END);
     }
-    ASSERT_TRUE(https_server_->Start());
-    ASSERT_TRUE(https_server_expired_->Start());
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     command_line->AppendSwitch(switches::kEnableDomDistiller);
     command_line->AppendSwitch(switches::kAllowInsecureLocalhost);
+
+    // Distilled documents are placed in the `public` address space, whence they
+    // cannot load subresources from the `local` address space. See also:
+    // https://bit.ly/3v0MsaY. This prevents distilled documents from loading
+    // images from localhost. Instruct the browser to treat the HTTPS server as
+    // `public` to avoid this.
+    command_line->AppendSwitchASCII(
+        network::switches::kIpAddressSpaceOverrides,
+        base::StrCat({https_server_->host_port_pair().ToString(), "=public"}));
   }
 
   void CheckImageWidthById(content::WebContents* contents,
@@ -479,17 +488,24 @@
   DomDistillerTabUtilsBrowserTestInsecureContent() {
     feature_list_.InitWithFeatures({dom_distiller::kReaderMode},
                                    {blink::features::kMixedContentAutoupgrade});
-  }
 
-  void SetUpInProcessBrowserTestFixture() override {
     https_server_ = std::make_unique<net::EmbeddedTestServer>(
         net::EmbeddedTestServer::TYPE_HTTPS);
     https_server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir());
+
     https_server_expired_ = std::make_unique<net::EmbeddedTestServer>(
         net::EmbeddedTestServer::TYPE_HTTPS);
     https_server_expired_->SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
     https_server_expired_->ServeFilesFromSourceDirectory(
         GetChromeTestDataDir());
+
+    StartServers();
+  }
+
+  // Constructor helper: ASSERT_* macros can only be used in `void` functions.
+  void StartServers() {
+    ASSERT_TRUE(https_server_->Start());
+    ASSERT_TRUE(https_server_expired_->Start());
   }
 
   std::unique_ptr<net::EmbeddedTestServer> https_server_;
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
index b3098a5..ef07f3c 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
@@ -405,9 +405,9 @@
     if (should_warn) {
       text_warning_ = true;
       text_response_ = std::move(response);
-      UpdateFinalResult(FinalResult::WARNING);
+      UpdateFinalResult(ContentAnalysisDelegateBase::FinalResult::WARNING);
     } else {
-      UpdateFinalResult(FinalResult::FAILURE);
+      UpdateFinalResult(ContentAnalysisDelegateBase::FinalResult::FAILURE);
     }
   }
 
@@ -438,14 +438,15 @@
 
   if (!file_complies) {
     if (result == BinaryUploadService::Result::FILE_TOO_LARGE) {
-      UpdateFinalResult(FinalResult::LARGE_FILES);
+      UpdateFinalResult(ContentAnalysisDelegateBase::FinalResult::LARGE_FILES);
     } else if (result == BinaryUploadService::Result::FILE_ENCRYPTED) {
-      UpdateFinalResult(FinalResult::ENCRYPTED_FILES);
+      UpdateFinalResult(
+          ContentAnalysisDelegateBase::FinalResult::ENCRYPTED_FILES);
     } else if (should_warn) {
       file_warnings_[index] = std::move(response);
-      UpdateFinalResult(FinalResult::WARNING);
+      UpdateFinalResult(ContentAnalysisDelegateBase::FinalResult::WARNING);
     } else {
-      UpdateFinalResult(FinalResult::FAILURE);
+      UpdateFinalResult(ContentAnalysisDelegateBase::FinalResult::FAILURE);
     }
   }
 
@@ -597,7 +598,7 @@
 
   // If showing the warning message, wait before running the callback. The
   // callback will be called either in BypassWarnings or Cancel.
-  if (final_result_ != FinalResult::WARNING)
+  if (final_result_ != ContentAnalysisDelegateBase::FinalResult::WARNING)
     RunCallback();
 
   if (!UpdateDialog() && data_uploaded_) {
@@ -643,7 +644,8 @@
   UploadFileForDeepScanning(result, data_.paths[index], std::move(request));
 }
 
-void ContentAnalysisDelegate::UpdateFinalResult(FinalResult result) {
+void ContentAnalysisDelegate::UpdateFinalResult(
+    ContentAnalysisDelegateBase::FinalResult result) {
   if (result < final_result_)
     final_result_ = result;
 }
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h
index a6b257e..286f9737 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h
@@ -15,6 +15,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
+#include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_base.h"
 #include "chrome/browser/enterprise/connectors/common.h"
 #include "chrome/browser/enterprise/connectors/connectors_manager.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
@@ -59,7 +60,7 @@
 //     safe_browsing::ContentAnalysisDelegate::CreateForWebContents(
 //         contents, std::move(data), base::BindOnce(...));
 //   }
-class ContentAnalysisDelegate {
+class ContentAnalysisDelegate : public ContentAnalysisDelegateBase {
  public:
   // Used as an input to CreateForWebContents() to describe what data needs
   // deeper scanning.  Any members can be empty.
@@ -134,26 +135,6 @@
     std::string sha256;
   };
 
-  // Enum to identify which message to show once scanning is complete. Ordered
-  // by precedence for when multiple files have conflicting results.
-  // TODO(crbug.com/1055785): Refactor this to whatever solution is chosen.
-  enum class FinalResult {
-    // Show that an issue was found and that the upload is blocked.
-    FAILURE = 0,
-
-    // Show that files were not uploaded since they were too large.
-    LARGE_FILES = 1,
-
-    // Show that files were not uploaded since they were encrypted.
-    ENCRYPTED_FILES = 2,
-
-    // Show that DLP checks failed, but that the user can proceed if they want.
-    WARNING = 3,
-
-    // Show that no issue was found and that the user may proceed.
-    SUCCESS = 4,
-  };
-
   // Callback used with CreateForWebContents() that informs caller of verdict
   // of deep scans.
   using CompletionCallback =
@@ -169,17 +150,19 @@
 
   ContentAnalysisDelegate(const ContentAnalysisDelegate&) = delete;
   ContentAnalysisDelegate& operator=(const ContentAnalysisDelegate&) = delete;
-  virtual ~ContentAnalysisDelegate();
+  ~ContentAnalysisDelegate() override;
+
+  // ContentAnalysisDelegateBase:
 
   // Called when the user decides to bypass the verdict they obtained from DLP.
   // This will allow the upload of files marked as DLP warnings.
-  void BypassWarnings();
+  void BypassWarnings() override;
 
   // Called when the user decides to cancel the file upload. This will stop the
   // upload to Chrome since the scan wasn't allowed to complete. If |warning| is
   // true, it means the user clicked Cancel after getting a warning, meaning the
   // "CancelledByUser" metrics should not be recorded.
-  void Cancel(bool warning);
+  void Cancel(bool warning) override;
 
   // Returns true if the deep scanning feature is enabled in the upload
   // direction via enterprise policies.  If the appropriate enterprise policies
@@ -308,7 +291,7 @@
 
   // Updates |final_result_| following the precedence established by the
   // FinalResult enum.
-  void UpdateFinalResult(FinalResult message);
+  void UpdateFinalResult(ContentAnalysisDelegateBase::FinalResult message);
 
   // Returns the BinaryUploadService used to upload content for deep scanning.
   // Virtual to override in tests.
@@ -354,7 +337,8 @@
   safe_browsing::DeepScanAccessPoint access_point_;
 
   // Scanning result to be shown to the user once every request is done.
-  FinalResult final_result_ = FinalResult::SUCCESS;
+  ContentAnalysisDelegateBase::FinalResult final_result_ =
+      ContentAnalysisDelegateBase::FinalResult::SUCCESS;
 
   // Set to true at the end of UploadData to indicate requests have been made
   // for every file/text. This is read to ensure |this| isn't deleted too early.
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_base.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_base.h
new file mode 100644
index 0000000..c30ffd23
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_base.h
@@ -0,0 +1,43 @@
+// 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_ANALYSIS_CONTENT_ANALYSIS_DELEGATE_BASE_H_
+#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_ANALYSIS_CONTENT_ANALYSIS_DELEGATE_BASE_H_
+
+namespace enterprise_connectors {
+
+class ContentAnalysisDelegateBase {
+ public:
+  // Enum to identify which message to show once scanning is complete. Ordered
+  // by precedence for when multiple files have conflicting results.
+  enum class FinalResult {
+    // Show that an issue was found and that the upload is blocked.
+    FAILURE = 0,
+
+    // Show that files were not uploaded since they were too large.
+    LARGE_FILES = 1,
+
+    // Show that files were not uploaded since they were encrypted.
+    ENCRYPTED_FILES = 2,
+
+    // Show that DLP checks failed, but that the user can proceed if they want.
+    WARNING = 3,
+
+    // Show that no issue was found and that the user may proceed.
+    SUCCESS = 4,
+  };
+
+  virtual ~ContentAnalysisDelegateBase() = default;
+
+  // Called when the user decides to bypass the verdict they obtained from DLP.
+  virtual void BypassWarnings() = 0;
+
+  // Called when the user hits "cancel" on the dialog, typically cancelling a
+  // pending file transfer.
+  virtual void Cancel(bool warning) = 0;
+};
+
+}  // namespace enterprise_connectors
+
+#endif  // CHROME_BROWSER_ENTERPRISE_CONNECTORS_ANALYSIS_CONTENT_ANALYSIS_DELEGATE_BASE_H_
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc
index b709720..ed4f6bd 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc
@@ -1041,7 +1041,7 @@
   }
 
   void DialogUpdated(ContentAnalysisDialog* dialog,
-                     ContentAnalysisDelegate::FinalResult result) override {
+                     ContentAnalysisDelegateBase::FinalResult result) override {
     ASSERT_TRUE(file_scan_ && blocking_scan());
   }
 
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
index cde377a3..1dc37f92 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
@@ -204,7 +204,7 @@
 }
 
 ContentAnalysisDialog::ContentAnalysisDialog(
-    std::unique_ptr<ContentAnalysisDelegate> delegate,
+    std::unique_ptr<ContentAnalysisDelegateBase> delegate,
     content::WebContents* web_contents,
     safe_browsing::DeepScanAccessPoint access_point,
     int files_count)
@@ -366,7 +366,7 @@
 }
 
 void ContentAnalysisDialog::ShowResult(
-    ContentAnalysisDelegate::FinalResult result,
+    ContentAnalysisDelegateBase::FinalResult result,
     const std::u16string& custom_message,
     const GURL& learn_more_url) {
   DCHECK(is_pending());
@@ -375,15 +375,15 @@
   final_learn_more_url_ = learn_more_url;
 
   switch (final_result_) {
-    case ContentAnalysisDelegate::FinalResult::ENCRYPTED_FILES:
-    case ContentAnalysisDelegate::FinalResult::LARGE_FILES:
-    case ContentAnalysisDelegate::FinalResult::FAILURE:
+    case ContentAnalysisDelegateBase::FinalResult::ENCRYPTED_FILES:
+    case ContentAnalysisDelegateBase::FinalResult::LARGE_FILES:
+    case ContentAnalysisDelegateBase::FinalResult::FAILURE:
       dialog_status_ = DeepScanningDialogStatus::FAILURE;
       break;
-    case ContentAnalysisDelegate::FinalResult::SUCCESS:
+    case ContentAnalysisDelegateBase::FinalResult::SUCCESS:
       dialog_status_ = DeepScanningDialogStatus::SUCCESS;
       break;
-    case ContentAnalysisDelegate::FinalResult::WARNING:
+    case ContentAnalysisDelegateBase::FinalResult::WARNING:
       dialog_status_ = DeepScanningDialogStatus::WARNING;
       break;
   }
@@ -648,12 +648,13 @@
   if (has_custom_message())
     return GetCustomMessage();
 
-  if (final_result_ == ContentAnalysisDelegate::FinalResult::LARGE_FILES) {
+  if (final_result_ == ContentAnalysisDelegateBase::FinalResult::LARGE_FILES) {
     return l10n_util::GetPluralStringFUTF16(
         IDS_DEEP_SCANNING_DIALOG_LARGE_FILE_FAILURE_MESSAGE, files_count_);
   }
 
-  if (final_result_ == ContentAnalysisDelegate::FinalResult::ENCRYPTED_FILES) {
+  if (final_result_ ==
+      ContentAnalysisDelegateBase::FinalResult::ENCRYPTED_FILES) {
     return l10n_util::GetPluralStringFUTF16(
         IDS_DEEP_SCANNING_DIALOG_ENCRYPTED_FILE_FAILURE_MESSAGE, files_count_);
   }
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h
index 5ed3c7da..7fd5cba 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h
@@ -9,7 +9,7 @@
 
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
-#include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h"
+#include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_base.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "ui/views/animation/bounds_animator.h"
@@ -88,8 +88,9 @@
     // Called at the end of ContentAnalysisDialog::UpdateDialog. |result| is
     // the value that UpdatedDialog used to transition from the pending state to
     // the success/failure/warning state.
-    virtual void DialogUpdated(ContentAnalysisDialog* dialog,
-                               ContentAnalysisDelegate::FinalResult result) {}
+    virtual void DialogUpdated(
+        ContentAnalysisDialog* dialog,
+        ContentAnalysisDelegateBase::FinalResult result) {}
 
     // Called at the end of ContentAnalysisDialog's destructor. |dialog| is a
     // pointer to the ContentAnalysisDialog being destructed. It can be used
@@ -106,7 +107,7 @@
   static base::TimeDelta GetMinimumPendingDialogTime();
   static base::TimeDelta GetSuccessDialogTimeout();
 
-  ContentAnalysisDialog(std::unique_ptr<ContentAnalysisDelegate> delegate,
+  ContentAnalysisDialog(std::unique_ptr<ContentAnalysisDelegateBase> delegate,
                         content::WebContents* web_contents,
                         safe_browsing::DeepScanAccessPoint access_point,
                         int files_count);
@@ -124,7 +125,7 @@
 
   // Updates the dialog with the result, and simply delete it from memory if
   // nothing should be shown.
-  void ShowResult(ContentAnalysisDelegate::FinalResult result,
+  void ShowResult(ContentAnalysisDelegateBase::FinalResult result,
                   const std::u16string& custom_message,
                   const GURL& learn_more_url);
 
@@ -225,7 +226,7 @@
   // ensure the auto-closing success dialog handles focus correctly.
   void SuccessCallback();
 
-  std::unique_ptr<ContentAnalysisDelegate> delegate_;
+  std::unique_ptr<ContentAnalysisDelegateBase> delegate_;
 
   content::WebContents* web_contents_;
 
@@ -243,8 +244,8 @@
   DeepScanningDialogStatus dialog_status_ = DeepScanningDialogStatus::PENDING;
 
   // Used to show the appropriate message.
-  ContentAnalysisDelegate::FinalResult final_result_ =
-      ContentAnalysisDelegate::FinalResult::SUCCESS;
+  ContentAnalysisDelegateBase::FinalResult final_result_ =
+      ContentAnalysisDelegateBase::FinalResult::SUCCESS;
   std::u16string final_custom_message_;
   GURL final_learn_more_url_;
 
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc
index eef9b3c3..addef5d 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc
@@ -141,7 +141,7 @@
   }
 
   void DialogUpdated(ContentAnalysisDialog* dialog,
-                     ContentAnalysisDelegate::FinalResult result) override {
+                     ContentAnalysisDelegateBase::FinalResult result) override {
     DCHECK_EQ(dialog, dialog_);
     dialog_updated_timestamp_ = base::TimeTicks::Now();
 
@@ -154,7 +154,8 @@
 
     // The dialog can only be updated to the success or failure case.
     EXPECT_TRUE(dialog_->is_result());
-    bool is_success = result == ContentAnalysisDelegate::FinalResult::SUCCESS;
+    bool is_success =
+        result == ContentAnalysisDelegateBase::FinalResult::SUCCESS;
     EXPECT_EQ(dialog_->is_success(), is_success);
     EXPECT_EQ(dialog_->is_success(), expected_scan_result_);
 
@@ -301,7 +302,7 @@
   }
 
   void DialogUpdated(ContentAnalysisDialog* dialog,
-                     ContentAnalysisDelegate::FinalResult result) override {
+                     ContentAnalysisDelegateBase::FinalResult result) override {
     ASSERT_TRUE(dialog->is_warning());
 
     // The dialog's buttons should be Ok and Cancel.
@@ -376,7 +377,7 @@
   }
 
   void DialogUpdated(ContentAnalysisDialog* dialog,
-                     ContentAnalysisDelegate::FinalResult result) override {
+                     ContentAnalysisDelegateBase::FinalResult result) override {
     // The dialog shows the failure or success message for the appropriate
     // access point and scan type.
     std::u16string final_message = dialog->GetMessageForTesting()->GetText();
@@ -766,7 +767,7 @@
           base::TimeDelta::FromMilliseconds(0));
 
   ContentAnalysisDialog* dialog = CreateContentAnalysisDialog();
-  dialog->ShowResult(ContentAnalysisDelegate::FinalResult::WARNING, u"Test",
+  dialog->ShowResult(ContentAnalysisDelegateBase::FinalResult::WARNING, u"Test",
                      GURL("http://www.example.com"));
 
   EXPECT_EQ(dialog->GetMessageForTesting()->GetText(),
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
index f6dbac3..494afae 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
@@ -417,8 +417,6 @@
         base::Value("third_party_provider_extension_id"));
     profile_test()->AddService(kUser1ProfilePath, "stub_vpn2");
 
-    chromeos::CellularMetricsLogger::RegisterLocalStatePrefs(
-        local_state_.registry());
     chromeos::CellularESimProfileHandlerImpl::RegisterLocalStatePrefs(
         local_state_.registry());
     PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry());
diff --git a/chrome/browser/extensions/api/scripting/scripting_api.cc b/chrome/browser/extensions/api/scripting/scripting_api.cc
index e80615582..1231c819 100644
--- a/chrome/browser/extensions/api/scripting/scripting_api.cc
+++ b/chrome/browser/extensions/api/scripting/scripting_api.cc
@@ -8,6 +8,8 @@
 #include <utility>
 
 #include "base/check.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/extensions/tab_helper.h"
@@ -246,6 +248,9 @@
   }
 
   if (injection_.files) {
+    if (injection_.args)
+      return RespondNow(Error("'args' may not be used with file injections."));
+
     // JS files don't require localization.
     constexpr bool kRequiresLocalization = false;
     std::string error;
@@ -262,10 +267,24 @@
   DCHECK(injection_.func);
 
   // TODO(devlin): This (wrapping a function to create an IIFE) is pretty hacky,
-  // and won't work well when we support currying arguments. Add support to the
-  // ScriptExecutor to better support this case.
-  std::string code_to_execute =
-      base::StringPrintf("(%s)()", injection_.func->c_str());
+  // and along with the JSON-serialization of the arguments to curry in.
+  // Add support to the ScriptExecutor to better support this case.
+  std::string args_expression;
+  if (injection_.args) {
+    std::vector<std::string> string_args;
+    string_args.reserve(injection_.args->size());
+    for (const auto& arg : *injection_.args) {
+      DCHECK(arg);
+      std::string json;
+      if (!base::JSONWriter::Write(*arg, &json))
+        return RespondNow(Error("Unserializable argument passed."));
+      string_args.push_back(std::move(json));
+    }
+    args_expression = base::JoinString(string_args, ",");
+  }
+
+  std::string code_to_execute = base::StringPrintf(
+      "(%s)(%s)", injection_.func->c_str(), args_expression.c_str());
 
   std::string error;
   if (!Execute(std::move(code_to_execute), /*script_src=*/GURL(), &error))
diff --git a/chrome/browser/extensions/api/storage/settings_apitest.cc b/chrome/browser/extensions/api/storage/settings_apitest.cc
index 279c301..78cb945 100644
--- a/chrome/browser/extensions/api/storage/settings_apitest.cc
+++ b/chrome/browser/extensions/api/storage/settings_apitest.cc
@@ -33,15 +33,18 @@
 #include "components/sync/test/model/fake_sync_change_processor.h"
 #include "components/sync/test/model/sync_change_processor_wrapper_for_test.h"
 #include "components/sync/test/model/sync_error_factory_mock.h"
+#include "components/version_info/channel.h"
 #include "content/public/test/browser_test.h"
 #include "extensions/browser/api/storage/backend_task_runner.h"
 #include "extensions/browser/api/storage/storage_area_namespace.h"
 #include "extensions/browser/api/storage/storage_frontend.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/value_store/settings_namespace.h"
+#include "extensions/common/features/feature_channel.h"
 #include "extensions/common/value_builder.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
+#include "extensions/test/test_extension_dir.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace extensions {
@@ -228,7 +231,75 @@
   testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
 };
 
-IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, SimpleTest) {
+// A specialization of ExtensionSettingsApiTest that pretends it's running
+// on version_info::Channel::UNKNOWN.
+class ExtensionSettingsTrunkApiTest : public ExtensionSettingsApiTest {
+ public:
+  ExtensionSettingsTrunkApiTest() = default;
+  ~ExtensionSettingsTrunkApiTest() override = default;
+  ExtensionSettingsTrunkApiTest(const ExtensionSettingsTrunkApiTest& other) =
+      delete;
+  ExtensionSettingsTrunkApiTest& operator=(
+      const ExtensionSettingsTrunkApiTest& other) = delete;
+
+ private:
+  // TODO(crbug.com/1185226): Remove unknown channel when chrome.storage.session
+  // is released in stable.
+  ScopedCurrentChannel current_channel_{version_info::Channel::UNKNOWN};
+};
+
+// A specialization of ExtensionSettingsApiTest that pretends it's running
+// on version_info::Channel::DEV.
+class ExtensionSettingsDevApiTest : public ExtensionSettingsApiTest {
+ public:
+  ExtensionSettingsDevApiTest() = default;
+  ~ExtensionSettingsDevApiTest() override = default;
+  ExtensionSettingsDevApiTest(const ExtensionSettingsDevApiTest& other) =
+      delete;
+  ExtensionSettingsDevApiTest& operator=(
+      const ExtensionSettingsDevApiTest& other) = delete;
+
+ private:
+  // TODO(crbug.com/1185226): Remove dev channel when chrome.storage.session
+  // is released in stable.
+  ScopedCurrentChannel current_channel_{version_info::Channel::DEV};
+};
+
+// TODO(crbug.com/1185226): Remove test when chrome.storage.session
+// is released in stable.
+IN_PROC_BROWSER_TEST_F(ExtensionSettingsDevApiTest,
+                       SessionInUnsupportedChannel) {
+  constexpr char kManifest[] =
+      R"({
+           "name": "Unsupported channel for session",
+           "manifest_version": 3,
+           "version": "0.1",
+           "background": { "service_worker": "worker.js" },
+           "permissions": ["storage"]
+         })";
+
+  constexpr char kWorker[] =
+      R"(chrome.test.runTests([
+          function unsupported() {
+            chrome.test.assertEq(undefined, chrome.storage.session);
+            chrome.test.assertTrue(!!chrome.storage.local);
+            chrome.test.succeed();
+          },
+        ]);)";
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"), kWorker);
+
+  ResultCatcher catcher;
+  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+// TODO(crbug.com/1185226): Change parent class to `ExtensionSettingsApiTest`
+// when chrome.storage.session is released in stable.
+IN_PROC_BROWSER_TEST_F(ExtensionSettingsTrunkApiTest, SimpleTest) {
   ASSERT_TRUE(RunExtensionTest("settings/simple_test")) << message_;
 }
 
@@ -236,32 +307,50 @@
 // Note that only split-mode incognito is tested, because spanning mode
 // incognito looks the same as normal mode when the only API activity comes
 // from background pages.
-IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, SplitModeIncognito) {
+// TODO(crbug.com/1185226): Change parent class to `ExtensionSettingsApiTest`
+// when chrome.storage.session is released in stable.
+IN_PROC_BROWSER_TEST_F(ExtensionSettingsTrunkApiTest, SplitModeIncognito) {
   // We need 2 ResultCatchers because we'll be running the same test in both
   // regular and incognito mode.
-  ResultCatcher catcher, catcher_incognito;
+  ResultCatcher catcher;
+  ResultCatcher catcher_incognito;
   catcher.RestrictToBrowserContext(browser()->profile());
   catcher_incognito.RestrictToBrowserContext(
       browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true));
 
+  // Sync, local and managed follow the same storage flow (RunWithStorage),
+  // whereas session follows a separate flow (RunWithSession). For the purpose
+  // of this test we can just test sync and session.
+  StorageAreaNamespace storage_areas[2] = {StorageAreaNamespace::kSync,
+                                           StorageAreaNamespace::kSession};
   LoadAndReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertEmpty",
                             "assertEmpty", "split_incognito");
-  ReplyWhenSatisfied(StorageAreaNamespace::kSync, "noop", "setFoo");
-  ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertFoo", "assertFoo");
-  ReplyWhenSatisfied(StorageAreaNamespace::kSync, "clear", "noop");
-  ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertEmpty", "assertEmpty");
-  ReplyWhenSatisfied(StorageAreaNamespace::kSync, "setFoo", "noop");
-  ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertFoo", "assertFoo");
-  ReplyWhenSatisfied(StorageAreaNamespace::kSync, "noop", "removeFoo");
-  FinalReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertEmpty",
-                          "assertEmpty");
+  for (const StorageAreaNamespace& storage_area : storage_areas) {
+    ReplyWhenSatisfied(storage_area, "assertEmpty", "assertEmpty");
+    ReplyWhenSatisfied(storage_area, "noop", "setFoo");
+    // TODO(crbug.com/1185226): Move this condition accordingly as `session`
+    // SettingFunction's are implemented. Currently it skips the
+    // functions that `session` has not implemented yet. When all functions are
+    // implemented, FinalReplyWhenSatisfied() will be moved outside the loop.
+    if (storage_area == StorageAreaNamespace::kSession) {
+      FinalReplyWhenSatisfied(storage_area, "assertFoo", "assertFoo");
+      break;
+    }
+    ReplyWhenSatisfied(storage_area, "assertFoo", "assertFoo");
+    ReplyWhenSatisfied(storage_area, "clear", "noop");
+    ReplyWhenSatisfied(storage_area, "assertEmpty", "assertEmpty");
+    ReplyWhenSatisfied(storage_area, "setFoo", "noop");
+    ReplyWhenSatisfied(storage_area, "assertFoo", "assertFoo");
+    ReplyWhenSatisfied(storage_area, "noop", "removeFoo");
+    ReplyWhenSatisfied(storage_area, "assertEmpty", "assertEmpty");
+  }
 
   EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
   EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message();
 }
 
 IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest,
-    OnChangedNotificationsBetweenBackgroundPages) {
+                       OnChangedNotificationsBetweenBackgroundPages) {
   // We need 2 ResultCatchers because we'll be running the same test in both
   // regular and incognito mode.
   ResultCatcher catcher, catcher_incognito;
@@ -286,11 +375,14 @@
   EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message();
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest,
-    SyncAndLocalAreasAreSeparate) {
+// TODO(crbug.com/1185226): Change parent class to `ExtensionSettingsApiTest`
+// when chrome.storage.session is released in stable.
+IN_PROC_BROWSER_TEST_F(ExtensionSettingsTrunkApiTest,
+                       SyncLocalAndSessionAreasAreSeparate) {
   // We need 2 ResultCatchers because we'll be running the same test in both
   // regular and incognito mode.
-  ResultCatcher catcher, catcher_incognito;
+  ResultCatcher catcher;
+  ResultCatcher catcher_incognito;
   catcher.RestrictToBrowserContext(browser()->profile());
   catcher_incognito.RestrictToBrowserContext(
       browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true));
@@ -307,6 +399,10 @@
                      "assertEmpty");
   ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertNoNotifications",
                      "assertNoNotifications");
+  ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertEmpty",
+                     "assertEmpty");
+  // TODO(crbug.com/1185226): Assert no notifications when onChangedEvent
+  // implemented for 'session'.
 
   ReplyWhenSatisfied(StorageAreaNamespace::kSync, "clearNotifications",
                      "clearNotifications");
@@ -318,10 +414,28 @@
   ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertFoo", "assertFoo");
   ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertNoNotifications",
                      "assertNoNotifications");
+  ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertEmpty",
+                     "assertEmpty");
+  // TODO(crbug.com/1185226): Assert no notifications when onChangedEvent
+  // implemented for 'session'.
 
   ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "clearNotifications",
                      "clearNotifications");
 
+  ReplyWhenSatisfied(StorageAreaNamespace::kSession, "setFoo", "noop");
+  ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertFoo", "assertFoo");
+  // TODO(crbug.com/1185226): Assert add notification when onChangedEvent
+  // implemented for 'session'.
+  ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertFoo", "assertFoo");
+  ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertNoNotifications",
+                     "assertNoNotifications");
+  ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertFoo", "assertFoo");
+  ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertNoNotifications",
+                     "assertNoNotifications");
+
+  // TODO(crbug.com/1185226): Clear notifications when onChangedEvent
+  // implemented for 'session'.
+
   ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "noop", "removeFoo");
   ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertEmpty",
                      "assertEmpty");
@@ -331,6 +445,9 @@
   ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertFoo", "assertFoo");
   ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertNoNotifications",
                      "assertNoNotifications");
+  ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertFoo", "assertFoo");
+  // TODO(crbug.com/1185226): Assert no notifications when onChangedEvent
+  // implemented for 'session'.
 
   ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "clearNotifications",
                      "clearNotifications");
@@ -339,10 +456,14 @@
   ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertEmpty", "assertEmpty");
   ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertDeleteFooNotification",
                      "assertDeleteFooNotification");
+  ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertEmpty",
+                     "assertEmpty");
   ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertNoNotifications",
                      "assertNoNotifications");
-  FinalReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertEmpty",
-                          "assertEmpty");
+  FinalReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertFoo",
+                          "assertFoo");
+  // TODO(crbug.com/1185226): Assert no notifications when onChangedEvent
+  // implemented for 'session'.
 
   EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
   EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message();
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index c91d8c49..6b85853 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -336,8 +336,7 @@
 
   TabStripModel* target_tab_strip =
       ExtensionTabUtil::GetEditableTabStripModel(target_browser);
-  if (!target_tab_strip)
-    return -1;
+  DCHECK(target_tab_strip);
 
   // Clamp move location to the last position.
   // This is ">" because it can append to a new index position.
@@ -1646,8 +1645,7 @@
     if (has_callback()) {
       TabStripModel* tab_strip_model =
           ExtensionTabUtil::GetEditableTabStripModel(target_browser);
-      if (!tab_strip_model)
-        return false;
+      DCHECK(tab_strip_model);
       content::WebContents* web_contents =
           tab_strip_model->GetWebContentsAt(inserted_index);
 
@@ -2010,8 +2008,10 @@
 
   TabStripModel* tab_strip_model =
       ExtensionTabUtil::GetEditableTabStripModel(browser);
-  if (!tab_strip_model)
+  if (!tab_strip_model) {
+    *error = tabs_constants::kTabStripNotEditableError;
     return nullptr;
+  }
   WebContents* contents = tab_strip_model->GetActiveWebContents();
   if (!contents) {
     *error = "No active web contents to capture";
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index 24610c6..31a4951f 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -626,17 +626,24 @@
       << message_;
 }
 
-// Flaky on all platforms: https://crbug.com/1003661
-IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
-                       DISABLED_WebRequestExtraHeaders) {
-  CancelLoginDialog login_dialog_helper;
-
+IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestExtraHeaders) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   ASSERT_TRUE(
       RunExtensionTest("webrequest", {.page_url = "test_extra_headers.html"}))
       << message_;
 }
 
+// Flaky on all platforms: https://crbug.com/1003661
+IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
+                       DISABLED_WebRequestExtraHeaders_Auth) {
+  CancelLoginDialog login_dialog_helper;
+
+  ASSERT_TRUE(StartEmbeddedTestServer());
+  ASSERT_TRUE(RunExtensionTest("webrequest",
+                               {.page_url = "test_extra_headers_auth.html"}))
+      << message_;
+}
+
 IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
                        WebRequestCORSWithExtraHeaders) {
   ASSERT_TRUE(StartEmbeddedTestServer());
diff --git a/chrome/browser/extensions/chrome_content_verifier_delegate.cc b/chrome/browser/extensions/chrome_content_verifier_delegate.cc
index 564fd0f..70a9197c3 100644
--- a/chrome/browser/extensions/chrome_content_verifier_delegate.cc
+++ b/chrome/browser/extensions/chrome_content_verifier_delegate.cc
@@ -245,18 +245,19 @@
   }
 
   if (info.should_repair) {
-    if (pending_manager->IsPolicyReinstallForCorruptionExpected(extension_id))
+    if (pending_manager->IsReinstallForCorruptionExpected(extension_id))
       return;
     SYSLOG(WARNING) << "Corruption detected in policy extension "
                     << extension_id
                     << " installed at: " << extension->path().value()
                     << ", from webstore: " << info.is_from_webstore;
-    pending_manager->ExpectPolicyReinstallForCorruption(
-        extension_id, info.is_from_webstore
-                          ? PendingExtensionManager::PolicyReinstallReason::
-                                CORRUPTION_DETECTED_WEBSTORE
-                          : PendingExtensionManager::PolicyReinstallReason::
-                                CORRUPTION_DETECTED_NON_WEBSTORE);
+    pending_manager->ExpectReinstallForCorruption(
+        extension_id,
+        info.is_from_webstore ? PendingExtensionManager::PolicyReinstallReason::
+                                    CORRUPTION_DETECTED_WEBSTORE
+                              : PendingExtensionManager::PolicyReinstallReason::
+                                    CORRUPTION_DETECTED_NON_WEBSTORE,
+        extension->location());
     service->DisableExtension(extension_id, disable_reason::DISABLE_CORRUPTED);
     // Attempt to reinstall.
     policy_extension_reinstaller_->NotifyExtensionDisabledDueToCorruption();
diff --git a/chrome/browser/extensions/content_verifier_browsertest.cc b/chrome/browser/extensions/content_verifier_browsertest.cc
index 16f60f7..a83d1ec 100644
--- a/chrome/browser/extensions/content_verifier_browsertest.cc
+++ b/chrome/browser/extensions/content_verifier_browsertest.cc
@@ -11,6 +11,7 @@
 #include "base/files/file_util.h"
 #include "base/macros.h"
 #include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread_restrictions.h"
@@ -20,6 +21,7 @@
 #include "chrome/browser/extensions/chrome_content_verifier_delegate.h"
 #include "chrome/browser/extensions/content_verifier_test_utils.h"
 #include "chrome/browser/extensions/crx_installer.h"
+#include "chrome/browser/extensions/devtools_util.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/extension_management_test_util.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -54,6 +56,10 @@
 namespace {
 constexpr char kTenMegResourceExtensionId[] =
     "mibjhafkjlepkpbjleahhallgddpjgle";
+constexpr char kStoragePermissionExtensionId[] =
+    "dmabdbcjhngdcmkfmgiogpcpiniaoddk";
+constexpr char kStoragePermissionExtensionCrx[] =
+    "content_verifier/storage_permission.crx";
 
 class MockUpdateService : public UpdateService {
  public:
@@ -113,8 +119,8 @@
     OnUpdateCheck(params, std::move(callback));
   }
 
-  void OnUpdateCheck(const ExtensionUpdateCheckParams& params,
-                     base::OnceClosure callback) {
+  virtual void OnUpdateCheck(const ExtensionUpdateCheckParams& params,
+                             base::OnceClosure callback) {
     scoped_refptr<CrxInstaller> installer(
         CrxInstaller::CreateSilent(extension_service()));
     installer->set_install_source(ManifestLocation::kExternalPolicyDownload);
@@ -431,6 +437,135 @@
                   .GetByID(kTestExtensionId));
 }
 
+class UserInstalledContentVerifierTest : public ContentVerifierTest {
+ public:
+  void SetUpInProcessBrowserTestFixture() override {
+    ContentVerifierTest::SetUpInProcessBrowserTestFixture();
+
+    EXPECT_CALL(update_service_, StartUpdateCheck)
+        .WillRepeatedly(
+            Invoke(this, &UserInstalledContentVerifierTest::OnUpdateCheck));
+  }
+
+ protected:
+  void OnUpdateCheck(const ExtensionUpdateCheckParams& params,
+                     base::OnceClosure callback) override {
+    scoped_refptr<CrxInstaller> installer(
+        CrxInstaller::CreateSilent(extension_service()));
+    installer->set_install_source(ManifestLocation::kInternal);
+    installer->set_install_immediately(true);
+    installer->set_allow_silent_install(true);
+    installer->set_off_store_install_allow_reason(
+        CrxInstaller::OffStoreInstallAllowedInTest);
+    installer->set_installer_callback(
+        base::BindOnce(&ExtensionUpdateComplete, std::move(callback)));
+    installer->InstallCrx(
+        test_data_dir_.AppendASCII(kStoragePermissionExtensionCrx));
+  }
+
+  PendingExtensionManager* pending_extension_manager() {
+    return ExtensionSystem::Get(profile())
+        ->extension_service()
+        ->pending_extension_manager();
+  }
+};
+
+// Setup a corrupted extension by tampering with one of its source files in
+// PRE to verify that it is repaired at startup.
+IN_PROC_BROWSER_TEST_F(UserInstalledContentVerifierTest,
+                       PRE_UserInstalledCorruptedResourceOnStartup) {
+  auto verifier_observer = std::make_unique<VerifierObserver>();
+  InstallExtensionFromWebstore(
+      test_data_dir_.AppendASCII(kStoragePermissionExtensionCrx), 1);
+  verifier_observer->EnsureFetchCompleted(kStoragePermissionExtensionId);
+  verifier_observer.reset();
+  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
+  const Extension* extension =
+      registry->enabled_extensions().GetByID(kStoragePermissionExtensionId);
+  EXPECT_TRUE(extension);
+  const base::FilePath kResourcePath(FILE_PATH_LITERAL("background.js"));
+
+  EXPECT_EQ("Test", ExecuteScriptInBackgroundPage(
+                        kStoragePermissionExtensionId,
+                        R"(chrome.storage.local.set({key: "Test"}, () =>
+             domAutomationController.send("Test")))"));
+
+  EXPECT_EQ("Test", ExecuteScriptInBackgroundPage(
+                        kStoragePermissionExtensionId,
+                        R"(chrome.storage.local.get(['key'], ({key}) =>
+             domAutomationController.send(key)))"));
+  // Corrupt the extension
+  {
+    base::FilePath resource_path = extension->path().Append(kResourcePath);
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    // Temporarily disable extension, we don't want to tackle with resources of
+    // enabled one.
+    DisableExtension(kStoragePermissionExtensionId);
+    ASSERT_TRUE(base::WriteFile(resource_path, "// corrupted\n"));
+    EnableExtension(kStoragePermissionExtensionId);
+  }
+
+  TestExtensionRegistryObserver registry_observer(
+      registry, kStoragePermissionExtensionId);
+  ExtensionSystem* system = ExtensionSystem::Get(profile());
+  system->content_verifier()->VerifyFailedForTest(
+      kStoragePermissionExtensionId, ContentVerifyJob::HASH_MISMATCH);
+  EXPECT_TRUE(registry_observer.WaitForExtensionUnloaded());
+
+  // The extension should be disabled and not be in expected to be repaired yet.
+  EXPECT_FALSE(pending_extension_manager()->IsReinstallForCorruptionExpected(
+      kStoragePermissionExtensionId));
+  EXPECT_EQ(disable_reason::DISABLE_CORRUPTED,
+            ExtensionPrefs::Get(profile())->GetDisableReasons(
+                kStoragePermissionExtensionId));
+}
+
+// Now actually test what happens on the next startup after the PRE test above.
+IN_PROC_BROWSER_TEST_F(UserInstalledContentVerifierTest,
+                       UserInstalledCorruptedResourceOnStartup) {
+  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
+  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
+  int disable_reasons = prefs->GetDisableReasons(kStoragePermissionExtensionId);
+
+  // Depending on timing, the extension may have already been reinstalled
+  // between SetUpInProcessBrowserTestFixture and now (usually not during local
+  // testing on a developer machine, but sometimes on a heavily loaded system
+  // such as the build waterfall / trybots). If the reinstall didn't already
+  // happen, wait for it.
+  if (disable_reasons & disable_reason::DISABLE_CORRUPTED) {
+    EXPECT_TRUE(pending_extension_manager()->IsReinstallForCorruptionExpected(
+        kStoragePermissionExtensionId));
+    TestExtensionRegistryObserver registry_observer(
+        registry, kStoragePermissionExtensionId);
+    ASSERT_TRUE(registry_observer.WaitForExtensionInstalled());
+    disable_reasons = prefs->GetDisableReasons(kStoragePermissionExtensionId);
+  }
+  EXPECT_FALSE(pending_extension_manager()->IsReinstallForCorruptionExpected(
+      kStoragePermissionExtensionId));
+  EXPECT_EQ(disable_reason::DISABLE_NONE, disable_reasons);
+  const Extension* extension =
+      ExtensionRegistry::Get(profile())->enabled_extensions().GetByID(
+          kStoragePermissionExtensionId);
+  EXPECT_TRUE(extension);
+
+  {
+    const base::FilePath kResourcePath(FILE_PATH_LITERAL("background.js"));
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    base::FilePath resource_path = extension->path().Append(kResourcePath);
+    std::string contents;
+    ASSERT_TRUE(base::ReadFileToString(resource_path, &contents));
+    EXPECT_EQ(std::string::npos, contents.find("corrupted"));
+  }
+  // This ensures that the background page is loaded. There is a unload/load
+  // of the extension happening which crashes `ExtensionBackgroundPageWaiter`.
+  devtools_util::InspectBackgroundPage(extension, profile());
+  WaitForExtensionViewsToLoad();
+  EXPECT_EQ("Test", ExecuteScriptInBackgroundPage(
+                        kStoragePermissionExtensionId,
+                        R"(chrome.storage.local.get(['key'], ({key}) =>
+             domAutomationController.send(key)))"));
+}
+
 // Tests that verification failure during navigating to an extension resource
 // correctly disables the extension.
 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, VerificationFailureOnNavigate) {
diff --git a/chrome/browser/extensions/extension_install_prompt.cc b/chrome/browser/extensions/extension_install_prompt.cc
index 16cfe64..a58a264 100644
--- a/chrome/browser/extensions/extension_install_prompt.cc
+++ b/chrome/browser/extensions/extension_install_prompt.cc
@@ -667,7 +667,7 @@
   // a callback on the stack.
   auto cb = std::move(done_callback_);
   std::move(show_dialog_callback_)
-      .Run(show_params_.get(), std::move(cb), std::move(prompt_));
+      .Run(std::move(show_params_), std::move(cb), std::move(prompt_));
 }
 
 bool ExtensionInstallPrompt::AutoConfirmPromptIfEnabled() {
diff --git a/chrome/browser/extensions/extension_install_prompt.h b/chrome/browser/extensions/extension_install_prompt.h
index beb371d..1affafa6 100644
--- a/chrome/browser/extensions/extension_install_prompt.h
+++ b/chrome/browser/extensions/extension_install_prompt.h
@@ -255,11 +255,10 @@
 
   using DoneCallback = base::OnceCallback<void(Result result)>;
 
-  typedef base::RepeatingCallback<void(
-      ExtensionInstallPromptShowParams*,
+  using ShowDialogCallback = base::RepeatingCallback<void(
+      std::unique_ptr<ExtensionInstallPromptShowParams>,
       DoneCallback,
-      std::unique_ptr<ExtensionInstallPrompt::Prompt>)>
-      ShowDialogCallback;
+      std::unique_ptr<ExtensionInstallPrompt::Prompt>)>;
 
   // Callback to show the default extension install dialog.
   // The implementations of this function are platform-specific.
diff --git a/chrome/browser/extensions/extension_install_prompt_unittest.cc b/chrome/browser/extensions/extension_install_prompt_unittest.cc
index 5c48318..84f194a 100644
--- a/chrome/browser/extensions/extension_install_prompt_unittest.cc
+++ b/chrome/browser/extensions/extension_install_prompt_unittest.cc
@@ -45,7 +45,7 @@
 void VerifyPromptIconCallback(
     base::OnceClosure quit_closure,
     const SkBitmap& expected_bitmap,
-    ExtensionInstallPromptShowParams* params,
+    std::unique_ptr<ExtensionInstallPromptShowParams> params,
     ExtensionInstallPrompt::DoneCallback done_callback,
     std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt) {
   EXPECT_TRUE(gfx::BitmapsAreEqual(prompt->icon().AsBitmap(), expected_bitmap));
@@ -55,7 +55,7 @@
 void VerifyPromptPermissionsCallback(
     base::OnceClosure quit_closure,
     size_t regular_permissions_count,
-    ExtensionInstallPromptShowParams* params,
+    std::unique_ptr<ExtensionInstallPromptShowParams> params,
     ExtensionInstallPrompt::DoneCallback done_callback,
     std::unique_ptr<ExtensionInstallPrompt::Prompt> install_prompt) {
   ASSERT_TRUE(install_prompt.get());
@@ -66,7 +66,7 @@
 void VerifyPromptWithholdingUICallback(
     base::OnceClosure quit_closure,
     const bool should_display,
-    ExtensionInstallPromptShowParams* params,
+    std::unique_ptr<ExtensionInstallPromptShowParams> params,
     ExtensionInstallPrompt::DoneCallback done_callback,
     std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt) {
   EXPECT_EQ(should_display, prompt->ShouldDisplayWithholdingUI());
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 125d128..f5b2fdf 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -238,7 +238,7 @@
     // priority than |info.download_location|, and we aren't doing a
     // reinstall of a corrupt policy force-installed extension.
     ManifestLocation current = extension->location();
-    if (!pending_extension_manager_.IsPolicyReinstallForCorruptionExpected(
+    if (!pending_extension_manager_.IsReinstallForCorruptionExpected(
             info.extension_id) &&
         current == Manifest::GetHigherPriorityLocation(
                        current, info.download_location)) {
@@ -291,7 +291,7 @@
       // set of extensions. If the extension is corrupted, it should be
       // reinstalled, thus it should be added to the pending extensions for
       // installation.
-      if (!pending_extension_manager_.IsPolicyReinstallForCorruptionExpected(
+      if (!pending_extension_manager_.IsReinstallForCorruptionExpected(
               info.extension_id)) {
         return false;
       }
@@ -509,6 +509,12 @@
 
   // Must be called after extensions are loaded.
   allowlist_.Init();
+
+  // Check for updates especially for corrupted user installed extension from
+  // the webstore. This will do nothing if an extension update check was
+  // triggered before and is still running.
+  if (pending_extension_manager_.HasAnyReinstallForCorruption())
+    CheckForUpdatesSoon();
 }
 
 void ExtensionService::EnabledReloadableExtensions() {
@@ -1630,6 +1636,9 @@
 
     install_parameter = pending_extension_info->install_parameter();
     pending_extension_manager()->Remove(id);
+  } else if (pending_extension_manager()->IsReinstallForCorruptionExpected(
+                 extension->id())) {
+    pending_extension_manager()->Remove(id);
   } else {
     // We explicitly want to re-enable an uninstalled external
     // extension; if we're here, that means the user is manually
diff --git a/chrome/browser/extensions/external_install_error.cc b/chrome/browser/extensions/external_install_error.cc
index 3ab3f08..ab0fc50 100644
--- a/chrome/browser/extensions/external_install_error.cc
+++ b/chrome/browser/extensions/external_install_error.cc
@@ -399,11 +399,9 @@
   DCHECK(browser);
   content::WebContents* web_contents = NULL;
   web_contents = browser->tab_strip_model()->GetActiveWebContents();
-  install_ui_show_params_ =
-      std::make_unique<ExtensionInstallPromptShowParams>(web_contents);
   manager_->DidChangeInstallAlertVisibility(this, true);
   ExtensionInstallPrompt::GetDefaultShowDialogCallback().Run(
-      install_ui_show_params_.get(),
+      std::make_unique<ExtensionInstallPromptShowParams>(web_contents),
       base::BindOnce(&ExternalInstallError::OnInstallPromptDone,
                      weak_factory_.GetWeakPtr()),
       std::move(prompt_));
@@ -469,7 +467,7 @@
 }
 
 void ExternalInstallError::OnDialogReady(
-    ExtensionInstallPromptShowParams* show_params,
+    std::unique_ptr<ExtensionInstallPromptShowParams> show_params,
     ExtensionInstallPrompt::DoneCallback callback,
     std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt) {
   prompt_ = std::move(prompt);
diff --git a/chrome/browser/extensions/external_install_error.h b/chrome/browser/extensions/external_install_error.h
index d3d900f..dbb51ba 100644
--- a/chrome/browser/extensions/external_install_error.h
+++ b/chrome/browser/extensions/external_install_error.h
@@ -99,9 +99,10 @@
 
   // Called when the dialog has been successfully populated, and is ready to be
   // shown.
-  void OnDialogReady(ExtensionInstallPromptShowParams* show_params,
-                     ExtensionInstallPrompt::DoneCallback done_callback,
-                     std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt);
+  void OnDialogReady(
+      std::unique_ptr<ExtensionInstallPromptShowParams> show_params,
+      ExtensionInstallPrompt::DoneCallback done_callback,
+      std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt);
 
   // Removes the error.
   void RemoveError();
@@ -126,7 +127,6 @@
 
   // The UI for showing the error.
   std::unique_ptr<ExtensionInstallPrompt> install_ui_;
-  std::unique_ptr<ExtensionInstallPromptShowParams> install_ui_show_params_;
   std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt_;
 
   // The UI for the given error, which will take the form of either a menu
diff --git a/chrome/browser/extensions/installed_loader.cc b/chrome/browser/extensions/installed_loader.cc
index eb498c72..90dbbc0 100644
--- a/chrome/browser/extensions/installed_loader.cc
+++ b/chrome/browser/extensions/installed_loader.cc
@@ -48,6 +48,7 @@
 #include "extensions/common/manifest_handlers/background_info.h"
 #include "extensions/common/permissions/api_permission.h"
 #include "extensions/common/permissions/permissions_data.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 using content::BrowserThread;
 
@@ -228,19 +229,27 @@
         extension_prefs_->SetExtensionEnabled(extension->id());
     }
 
-    if ((disable_reasons & disable_reason::DISABLE_CORRUPTED) &&
-        policy->MustRemainEnabled(extension.get(), nullptr)) {
-      // This extension must have been disabled due to corruption on a
-      // previous run of chrome, and for some reason we weren't successful in
-      // auto-reinstalling it. So we want to notify the
-      // PendingExtensionManager that we'd still like to keep attempt to
-      // re-download and reinstall it whenever the ExtensionService checks for
-      // external updates.
+    if ((disable_reasons & disable_reason::DISABLE_CORRUPTED)) {
       PendingExtensionManager* pending_manager =
           extension_service_->pending_extension_manager();
-      pending_manager->ExpectPolicyReinstallForCorruption(
-          extension->id(), PendingExtensionManager::PolicyReinstallReason::
-                               CORRUPTION_DETECTED_IN_PRIOR_SESSION);
+      if (policy->MustRemainEnabled(extension.get(), nullptr)) {
+        // This extension must have been disabled due to corruption on a
+        // previous run of chrome, and for some reason we weren't successful in
+        // auto-reinstalling it. So we want to notify the
+        // PendingExtensionManager that we'd still like to keep attempt to
+        // re-download and reinstall it whenever the ExtensionService checks for
+        // external updates.
+        pending_manager->ExpectReinstallForCorruption(
+            extension->id(),
+            PendingExtensionManager::PolicyReinstallReason::
+                CORRUPTION_DETECTED_IN_PRIOR_SESSION,
+            extension->location());
+      } else if (extension->from_webstore()) {
+        // Non-policy extensions are repaired on startup. Add any corrupted
+        // user-installed extensions to the PendingExtensionManager as well.
+        pending_manager->ExpectReinstallForCorruption(
+            extension->id(), absl::nullopt, extension->location());
+      }
     }
   } else {
     // Extension is enabled. Check management policy to verify if it should
diff --git a/chrome/browser/extensions/omaha_attributes_handler.cc b/chrome/browser/extensions/omaha_attributes_handler.cc
index 700ed79..6696610 100644
--- a/chrome/browser/extensions/omaha_attributes_handler.cc
+++ b/chrome/browser/extensions/omaha_attributes_handler.cc
@@ -79,6 +79,10 @@
       extension_id, attributes,
       extensions_features::kDisablePolicyViolationExtensionsRemotely,
       BitMapBlocklistState::BLOCKLISTED_CWS_POLICY_VIOLATION);
+  HandleGreylistOmahaAttribute(
+      extension_id, attributes,
+      extensions_features::kDisablePotentiallyUwsExtensionsRemotely,
+      BitMapBlocklistState::BLOCKLISTED_POTENTIALLY_UNWANTED);
 }
 
 void OmahaAttributesHandler::ReportPolicyViolationUWSOmahaAttributes(
diff --git a/chrome/browser/extensions/omaha_attributes_handler_unittest.cc b/chrome/browser/extensions/omaha_attributes_handler_unittest.cc
index 3430657..ea1f4743 100644
--- a/chrome/browser/extensions/omaha_attributes_handler_unittest.cc
+++ b/chrome/browser/extensions/omaha_attributes_handler_unittest.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/extensions/blocklist_extension_prefs.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
+#include "extensions/browser/disable_reason.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/common/extension_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -27,8 +28,11 @@
 class OmahaAttributesHandlerUnitTest : public ExtensionServiceTestBase {
  public:
   OmahaAttributesHandlerUnitTest() {
-    feature_list_.InitAndEnableFeature(
-        extensions_features::kDisablePolicyViolationExtensionsRemotely);
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/
+        {extensions_features::kDisablePolicyViolationExtensionsRemotely,
+         extensions_features::kDisablePotentiallyUwsExtensionsRemotely},
+        /*disabled_features=*/{});
   }
 };
 
@@ -92,6 +96,109 @@
   EXPECT_FALSE(disabled_extensions.Contains(kTestExtensionId));
 }
 
+TEST_F(OmahaAttributesHandlerUnitTest, DisableRemotelyForPotentiallyUws) {
+  InitializeGoodInstalledExtensionService();
+  service()->Init();
+
+  const ExtensionSet& enabled_extensions = registry()->enabled_extensions();
+  const ExtensionSet& disabled_extensions = registry()->disabled_extensions();
+
+  EXPECT_TRUE(enabled_extensions.Contains(kTestExtensionId));
+
+  base::Value attributes(base::Value::Type::DICTIONARY);
+  attributes.SetBoolKey("_potentially_uws", true);
+  service()->PerformActionBasedOnOmahaAttributes(kTestExtensionId, attributes);
+
+  EXPECT_FALSE(enabled_extensions.Contains(kTestExtensionId));
+  EXPECT_TRUE(disabled_extensions.Contains(kTestExtensionId));
+
+  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
+  EXPECT_TRUE(blocklist_prefs::HasOmahaBlocklistState(
+      kTestExtensionId, BitMapBlocklistState::BLOCKLISTED_POTENTIALLY_UNWANTED,
+      prefs));
+  EXPECT_EQ(disable_reason::DISABLE_GREYLIST,
+            prefs->GetDisableReasons(kTestExtensionId));
+
+  // Remove extensions from greylist.
+  attributes.SetBoolKey("_potentially_uws", false);
+  service()->PerformActionBasedOnOmahaAttributes(kTestExtensionId, attributes);
+  EXPECT_FALSE(blocklist_prefs::HasOmahaBlocklistState(
+      kTestExtensionId, BitMapBlocklistState::BLOCKLISTED_POTENTIALLY_UNWANTED,
+      prefs));
+  EXPECT_EQ(disable_reason::DISABLE_NONE,
+            prefs->GetDisableReasons(kTestExtensionId));
+
+  EXPECT_FALSE(blocklist_prefs::HasOmahaBlocklistState(
+      kTestExtensionId, BitMapBlocklistState::BLOCKLISTED_POTENTIALLY_UNWANTED,
+      prefs));
+
+  // The extension is re-enabled.
+  EXPECT_TRUE(enabled_extensions.Contains(kTestExtensionId));
+  EXPECT_FALSE(disabled_extensions.Contains(kTestExtensionId));
+}
+
+TEST_F(OmahaAttributesHandlerUnitTest, MultipleGreylistStates) {
+  InitializeGoodInstalledExtensionService();
+  service()->Init();
+
+  const ExtensionSet& enabled_extensions = registry()->enabled_extensions();
+  const ExtensionSet& disabled_extensions = registry()->disabled_extensions();
+
+  EXPECT_TRUE(enabled_extensions.Contains(kTestExtensionId));
+
+  base::Value attributes(base::Value::Type::DICTIONARY);
+  attributes.SetBoolKey("_policy_violation", true);
+  service()->PerformActionBasedOnOmahaAttributes(kTestExtensionId, attributes);
+
+  EXPECT_FALSE(enabled_extensions.Contains(kTestExtensionId));
+  EXPECT_TRUE(disabled_extensions.Contains(kTestExtensionId));
+
+  // Now user enables kTestExtensionId.
+  service()->EnableExtension(kTestExtensionId);
+
+  EXPECT_TRUE(enabled_extensions.Contains(kTestExtensionId));
+  EXPECT_FALSE(disabled_extensions.Contains(kTestExtensionId));
+
+  // Another greylist state is added to Omaha attribute.
+  attributes.SetBoolKey("_potentially_uws", true);
+  service()->PerformActionBasedOnOmahaAttributes(kTestExtensionId, attributes);
+
+  // The extension should be disabled again.
+  EXPECT_FALSE(enabled_extensions.Contains(kTestExtensionId));
+  EXPECT_TRUE(disabled_extensions.Contains(kTestExtensionId));
+
+  // Remove extensions from the first greylist state.
+  attributes.SetBoolKey("_policy_violation", false);
+  service()->PerformActionBasedOnOmahaAttributes(kTestExtensionId, attributes);
+
+  // The extension should still be disabled, because it is still in the
+  // potentially unwanted state.
+  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
+  EXPECT_FALSE(enabled_extensions.Contains(kTestExtensionId));
+  EXPECT_TRUE(disabled_extensions.Contains(kTestExtensionId));
+  EXPECT_FALSE(blocklist_prefs::HasOmahaBlocklistState(
+      kTestExtensionId, BitMapBlocklistState::BLOCKLISTED_CWS_POLICY_VIOLATION,
+      prefs));
+  EXPECT_TRUE(blocklist_prefs::HasOmahaBlocklistState(
+      kTestExtensionId, BitMapBlocklistState::BLOCKLISTED_POTENTIALLY_UNWANTED,
+      prefs));
+  EXPECT_EQ(disable_reason::DISABLE_GREYLIST,
+            prefs->GetDisableReasons(kTestExtensionId));
+
+  // Remove the other greylist state.
+  attributes.SetBoolKey("_potentially_uws", false);
+  service()->PerformActionBasedOnOmahaAttributes(kTestExtensionId, attributes);
+
+  // The extension is re-enabled.
+  EXPECT_TRUE(enabled_extensions.Contains(kTestExtensionId));
+  EXPECT_FALSE(disabled_extensions.Contains(kTestExtensionId));
+  EXPECT_FALSE(blocklist_prefs::HasOmahaBlocklistState(
+      kTestExtensionId, BitMapBlocklistState::BLOCKLISTED_POTENTIALLY_UNWANTED,
+      prefs));
+  EXPECT_EQ(disable_reason::DISABLE_NONE,
+            prefs->GetDisableReasons(kTestExtensionId));
+}
+
 TEST_F(OmahaAttributesHandlerUnitTest, KeepDisabledWhenMalwareRemoved) {
   InitializeGoodInstalledExtensionService();
   service()->Init();
@@ -132,13 +239,17 @@
     : public ExtensionServiceTestBase {
  public:
   OmahaAttributesHandlerWithFeatureDisabledUnitTest() {
-    feature_list_.InitAndDisableFeature(
-        extensions_features::kDisablePolicyViolationExtensionsRemotely);
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/
+        {},
+        /*disabled_features=*/{
+            extensions_features::kDisablePolicyViolationExtensionsRemotely,
+            extensions_features::kDisablePotentiallyUwsExtensionsRemotely});
   }
 };
 
 TEST_F(OmahaAttributesHandlerWithFeatureDisabledUnitTest,
-       DoNotDisableRemotelyWhenPolicyViolationFlagDisabled) {
+       DoNotDisableRemotelyWhenFlagsDisabled) {
   InitializeGoodInstalledExtensionService();
   service()->Init();
 
@@ -147,6 +258,7 @@
 
   base::Value attributes(base::Value::Type::DICTIONARY);
   attributes.SetBoolKey("_policy_violation", true);
+  attributes.SetBoolKey("_potentially_uws", true);
   service()->PerformActionBasedOnOmahaAttributes(kTestExtensionId, attributes);
 
   // Since the flag is disabled, we don't expect the extension to be affected.
@@ -155,6 +267,9 @@
   EXPECT_FALSE(blocklist_prefs::HasOmahaBlocklistState(
       kTestExtensionId, BitMapBlocklistState::BLOCKLISTED_CWS_POLICY_VIOLATION,
       ExtensionPrefs::Get(profile())));
+  EXPECT_FALSE(blocklist_prefs::HasOmahaBlocklistState(
+      kTestExtensionId, BitMapBlocklistState::BLOCKLISTED_POTENTIALLY_UNWANTED,
+      ExtensionPrefs::Get(profile())));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/pending_extension_manager.cc b/chrome/browser/extensions/pending_extension_manager.cc
index e85fd53..cf5b490 100644
--- a/chrome/browser/extensions/pending_extension_manager.cc
+++ b/chrome/browser/extensions/pending_extension_manager.cc
@@ -8,7 +8,7 @@
 
 #include "base/containers/contains.h"
 #include "base/logging.h"
-#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/version.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "content/public/browser/browser_thread.h"
@@ -57,12 +57,11 @@
 }
 
 bool PendingExtensionManager::Remove(const std::string& id) {
-  if (base::Contains(expected_policy_reinstalls_, id)) {
-    base::TimeDelta latency =
-        base::TimeTicks::Now() - expected_policy_reinstalls_[id];
-    UMA_HISTOGRAM_LONG_TIMES("Extensions.CorruptPolicyExtensionResolved",
-                             latency);
-    expected_policy_reinstalls_.erase(id);
+  if (base::Contains(expected_reinstalls_, id)) {
+    base::TimeDelta latency = base::TimeTicks::Now() - expected_reinstalls_[id];
+    base::UmaHistogramLongTimes("Extensions.CorruptPolicyExtensionResolved",
+                                latency);
+    expected_reinstalls_.erase(id);
   }
   PendingExtensionList::iterator iter;
   for (iter = pending_extension_list_.begin();
@@ -110,26 +109,35 @@
 
 void PendingExtensionManager::RecordPolicyReinstallReason(
     PolicyReinstallReason reason_for_uma) {
-  UMA_HISTOGRAM_ENUMERATION("Extensions.CorruptPolicyExtensionDetected3",
-                            reason_for_uma);
+  base::UmaHistogramEnumeration("Extensions.CorruptPolicyExtensionDetected3",
+                                reason_for_uma);
 }
 
-void PendingExtensionManager::ExpectPolicyReinstallForCorruption(
+void PendingExtensionManager::RecordExtensionReinstallManifestLocation(
+    mojom::ManifestLocation manifest_location_for_uma) {
+  base::UmaHistogramEnumeration("Extensions.CorruptedExtensionLocation",
+                                manifest_location_for_uma);
+}
+
+void PendingExtensionManager::ExpectReinstallForCorruption(
     const ExtensionId& id,
-    PolicyReinstallReason reason_for_uma) {
-  if (base::Contains(expected_policy_reinstalls_, id))
+    absl::optional<PolicyReinstallReason> reason_for_uma,
+    mojom::ManifestLocation manifest_location_for_uma) {
+  if (base::Contains(expected_reinstalls_, id))
     return;
-  expected_policy_reinstalls_[id] = base::TimeTicks::Now();
-  RecordPolicyReinstallReason(reason_for_uma);
+  expected_reinstalls_[id] = base::TimeTicks::Now();
+  if (reason_for_uma)
+    RecordPolicyReinstallReason(*reason_for_uma);
+  RecordExtensionReinstallManifestLocation(manifest_location_for_uma);
 }
 
-bool PendingExtensionManager::IsPolicyReinstallForCorruptionExpected(
+bool PendingExtensionManager::IsReinstallForCorruptionExpected(
     const ExtensionId& id) const {
-  return base::Contains(expected_policy_reinstalls_, id);
+  return base::Contains(expected_reinstalls_, id);
 }
 
-bool PendingExtensionManager::HasAnyPolicyReinstallForCorruption() const {
-  return !expected_policy_reinstalls_.empty();
+bool PendingExtensionManager::HasAnyReinstallForCorruption() const {
+  return !expected_reinstalls_.empty();
 }
 
 bool PendingExtensionManager::AddFromSync(
@@ -267,9 +275,16 @@
                           kRemoteInstall);
 }
 
-void PendingExtensionManager::GetPendingIdsForUpdateCheck(
-    std::list<std::string>* out_ids_for_update_check) const {
+std::list<std::string> PendingExtensionManager::GetPendingIdsForUpdateCheck()
+    const {
   PendingExtensionList::const_iterator iter;
+  std::list<std::string> result;
+
+  // Add the extensions that need repairing but are not necessarily from an
+  // external loader.
+  for (const auto& iter : expected_reinstalls_)
+    result.push_back(iter.first);
+
   for (iter = pending_extension_list_.begin();
        iter != pending_extension_list_.end();
        ++iter) {
@@ -284,8 +299,11 @@
       continue;
     }
 
-    out_ids_for_update_check->push_back(iter->id());
+    if (!base::Contains(expected_reinstalls_, iter->id()))
+      result.push_back(iter->id());
   }
+
+  return result;
 }
 
 bool PendingExtensionManager::AddExtensionImpl(
diff --git a/chrome/browser/extensions/pending_extension_manager.h b/chrome/browser/extensions/pending_extension_manager.h
index 5b4f2c82..4b9373e7 100644
--- a/chrome/browser/extensions/pending_extension_manager.h
+++ b/chrome/browser/extensions/pending_extension_manager.h
@@ -15,6 +15,7 @@
 #include "chrome/browser/extensions/pending_extension_info.h"
 #include "extensions/common/extension_id.h"
 #include "extensions/common/manifest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom-shared.h"
 
 class GURL;
@@ -120,18 +121,24 @@
   // See https://crbug.com/958794#c22 for details.
   void RecordPolicyReinstallReason(PolicyReinstallReason reason_for_uma);
 
+  void RecordExtensionReinstallManifestLocation(
+      mojom::ManifestLocation manifest_location_for_uma);
+
   // Notifies the manager that we are reinstalling the policy force-installed
   // extension with |id| because we detected corruption in the current copy.
-  // |reason| indicates origin and details of the requires, and is used for
-  // statistics purposes (sent to UMA).
-  void ExpectPolicyReinstallForCorruption(const ExtensionId& id,
-                                          PolicyReinstallReason reason_for_uma);
+  // |reason_for_uma| indicates origin and details of the requires, and is used
+  // for statistics purposes (sent to UMA). |manifest_location_for_uma| the
+  // manifest location, and is used for statistics purposes (sent to UMA)
+  void ExpectReinstallForCorruption(
+      const ExtensionId& id,
+      absl::optional<PolicyReinstallReason> reason_for_uma,
+      mojom::ManifestLocation manifest_location_for_uma);
 
   // Are we expecting a reinstall of the extension with |id| due to corruption?
-  bool IsPolicyReinstallForCorruptionExpected(const ExtensionId& id) const;
+  bool IsReinstallForCorruptionExpected(const ExtensionId& id) const;
 
-  // Whether or not there are any corrupted policy extensions.
-  bool HasAnyPolicyReinstallForCorruption() const;
+  // Whether or not there are any corrupted extensions.
+  bool HasAnyReinstallForCorruption() const;
 
   // Adds an extension in a pending state; the extension with the
   // given info will be installed on the next auto-update cycle.
@@ -174,9 +181,9 @@
 
   // Get the list of pending IDs that should be installed from an update URL.
   // Pending extensions that will be installed from local files will not be
-  // included in the set.
-  void GetPendingIdsForUpdateCheck(
-      std::list<std::string>* out_ids_for_update_check) const;
+  // included in the set. This includes corrupted user installed that should be
+  // repaired.
+  std::list<std::string> GetPendingIdsForUpdateCheck() const;
 
  private:
   typedef std::list<PendingExtensionInfo> PendingExtensionList;
@@ -205,9 +212,9 @@
 
   PendingExtensionList pending_extension_list_;
 
-  // A set of policy force-installed extension ids that are being reinstalled
-  // due to corruption, mapped to the time we detected the corruption.
-  std::map<ExtensionId, base::TimeTicks> expected_policy_reinstalls_;
+  // A set of extension ids that are being reinstalled due to corruption, mapped
+  // to the time we detected the corruption.
+  std::map<ExtensionId, base::TimeTicks> expected_reinstalls_;
 
   FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
                            UpdatePendingExtensionAlreadyInstalled);
diff --git a/chrome/browser/extensions/policy_extension_reinstaller.cc b/chrome/browser/extensions/policy_extension_reinstaller.cc
index 48d8dc9..7dc43d6 100644
--- a/chrome/browser/extensions/policy_extension_reinstaller.cc
+++ b/chrome/browser/extensions/policy_extension_reinstaller.cc
@@ -66,7 +66,7 @@
   PendingExtensionManager* pending_manager =
       service->pending_extension_manager();
   // If there's nothing to repair, then bail out.
-  if (!pending_manager->HasAnyPolicyReinstallForCorruption())
+  if (!pending_manager->HasAnyReinstallForCorruption())
     return;
 
   service->CheckForExternalUpdates();
diff --git a/chrome/browser/extensions/policy_extension_reinstaller_unittest.cc b/chrome/browser/extensions/policy_extension_reinstaller_unittest.cc
index 563f734..bb793b2 100644
--- a/chrome/browser/extensions/policy_extension_reinstaller_unittest.cc
+++ b/chrome/browser/extensions/policy_extension_reinstaller_unittest.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
 #include "chrome/test/base/testing_profile.h"
+#include "extensions/common/mojom/manifest.mojom-shared.h"
 
 namespace extensions {
 
@@ -58,9 +59,11 @@
 // Tests that a single extension corruption will keep retrying reinstallation.
 TEST_F(PolicyExtensionReinstallerUnittest, Retry) {
   InitializeEmptyExtensionService();
-  service()->pending_extension_manager()->ExpectPolicyReinstallForCorruption(
-      kDummyExtensionId, PendingExtensionManager::PolicyReinstallReason::
-                             CORRUPTION_DETECTED_WEBSTORE);
+  service()->pending_extension_manager()->ExpectReinstallForCorruption(
+      kDummyExtensionId,
+      PendingExtensionManager::PolicyReinstallReason::
+          CORRUPTION_DETECTED_WEBSTORE,
+      mojom::ManifestLocation::kInternal);
 
   PolicyExtensionReinstaller reinstaller(profile_.get());
   TestReinstallerTracker tracker;
@@ -77,9 +80,11 @@
 // CheckForExternalUpdates() when one is already in-flight through PostTask.
 TEST_F(PolicyExtensionReinstallerUnittest, DoNotScheduleWhenAlreadyInflight) {
   InitializeEmptyExtensionService();
-  service()->pending_extension_manager()->ExpectPolicyReinstallForCorruption(
-      kDummyExtensionId, PendingExtensionManager::PolicyReinstallReason::
-                             CORRUPTION_DETECTED_WEBSTORE);
+  service()->pending_extension_manager()->ExpectReinstallForCorruption(
+      kDummyExtensionId,
+      PendingExtensionManager::PolicyReinstallReason::
+          CORRUPTION_DETECTED_WEBSTORE,
+      mojom::ManifestLocation::kInternal);
 
   PolicyExtensionReinstaller reinstaller(profile_.get());
   TestReinstallerTracker tracker;
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index c6980e1..715a7b0 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -42,6 +42,7 @@
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/gcm_driver/fake_gcm_profile_service.h"
 #include "components/gcm_driver/instance_id/fake_gcm_driver_for_instance_id.h"
+#include "components/version_info/channel.h"
 #include "components/version_info/version_info.h"
 #include "content/public/browser/console_message.h"
 #include "content/public/browser/navigation_controller.h"
@@ -68,6 +69,7 @@
 #include "extensions/browser/service_worker_task_queue.h"
 #include "extensions/common/api/test.h"
 #include "extensions/common/extensions_client.h"
+#include "extensions/common/features/feature_channel.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/value_builder.h"
@@ -289,6 +291,24 @@
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerBasedBackgroundTest);
 };
 
+// A specialization of ExtensionSettingsApiTest that pretends it's running
+// on version_info::Channel::UNKNOWN.
+class ServiceWorkerBasedBackgroundTrunkTest
+    : public ServiceWorkerBasedBackgroundTest {
+ public:
+  ServiceWorkerBasedBackgroundTrunkTest() = default;
+  ~ServiceWorkerBasedBackgroundTrunkTest() override = default;
+  ServiceWorkerBasedBackgroundTrunkTest(
+      const ServiceWorkerBasedBackgroundTrunkTest& other) = delete;
+  ServiceWorkerBasedBackgroundTrunkTest& operator=(
+      const ServiceWorkerBasedBackgroundTrunkTest& other) = delete;
+
+ private:
+  // TODO(crbug.com/1185226): Remove unknown channel when chrome.storage.session
+  // is released in stable.
+  ScopedCurrentChannel current_channel_{version_info::Channel::UNKNOWN};
+};
+
 class ServiceWorkerBasedBackgroundTestWithNotification
     : public ServiceWorkerBasedBackgroundTest {
  public:
@@ -425,14 +445,22 @@
 }
 
 // Tests chrome.storage APIs.
-IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, StorageSetAndGet) {
+// TODO(crbug.com/1185226): Change parent class to
+// `ServiceWorkerBasedBackgroundTest` when chrome.storage.session is released in
+// stable.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTrunkTest,
+                       StorageSetAndGet) {
   ASSERT_TRUE(
       RunExtensionTest("service_worker/worker_based_background/storage"))
       << message_;
 }
 
-// Tests chrome.storage.local and chrome.storage.local APIs.
-IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, StorageNoPermissions) {
+// Tests chrome.storage APIs are only enabled with permission.
+// TODO(crbug.com/1185226): Change parent class to
+// `ServiceWorkerBasedBackgroundTest` when chrome.storage.session is released in
+// stable.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTrunkTest,
+                       StorageNoPermissions) {
   ASSERT_TRUE(RunExtensionTest(
       "service_worker/worker_based_background/storage_no_permissions"))
       << message_;
diff --git a/chrome/browser/extensions/updater/extension_updater.cc b/chrome/browser/extensions/updater/extension_updater.cc
index 6b1e8c73..f13c162 100644
--- a/chrome/browser/extensions/updater/extension_updater.cc
+++ b/chrome/browser/extensions/updater/extension_updater.cc
@@ -367,24 +367,45 @@
   const PendingExtensionManager* pending_extension_manager =
       service_->pending_extension_manager();
 
-  std::list<ExtensionId> pending_ids;
   ExtensionUpdateCheckParams update_check_params;
 
   if (params.ids.empty()) {
+    std::list<ExtensionId> pending_ids =
+        pending_extension_manager->GetPendingIdsForUpdateCheck();
     // If no extension ids are specified, check for updates for all extensions.
-    pending_extension_manager->GetPendingIdsForUpdateCheck(&pending_ids);
 
     for (const ExtensionId& pending_id : pending_ids) {
       const PendingExtensionInfo* info =
           pending_extension_manager->GetById(pending_id);
-      if (!Manifest::IsAutoUpdateableLocation(info->install_source())) {
+
+      const bool is_corrupt_reinstall =
+          pending_extension_manager->IsReinstallForCorruptionExpected(
+              pending_id);
+
+      // Extensions from the webstore that are corrupted do not have
+      // PendingExtensionInfo but are still available in the extension registry.
+      // They should be disabled because they are corrupted and require to be
+      // repaired.
+      if (!info) {
+        const Extension* extension = registry_->GetExtensionById(
+            pending_id, extensions::ExtensionRegistry::EVERYTHING);
+
+        // It is possible that the user deletes the extension between the time
+        // it was detected as corrupted and now. In that case, `extension` will
+        // be null and we should just skip it.
+        if (!extension)
+          continue;
+        // Policy installed extensions are not necessarily from the webstore,
+        // but should have an `info` and never hit this path.
+        DCHECK(extension->from_webstore()) << "Extension with id " << pending_id
+                                           << " is not from the webstore";
+        DCHECK(is_corrupt_reinstall) << "Extension with id " << pending_id
+                                     << " is not a corrupt reinstall";
+        update_check_params.update_info[pending_id] = ExtensionUpdateData();
+      } else if (!Manifest::IsAutoUpdateableLocation(info->install_source())) {
         VLOG(2) << "Extension " << pending_id << " is not auto updateable";
         continue;
       }
-
-      const bool is_corrupt_reinstall =
-          pending_extension_manager->IsPolicyReinstallForCorruptionExpected(
-              pending_id);
       // We have to mark high-priority extensions (such as policy-forced
       // extensions or external component extensions) with foreground fetch
       // priority; otherwise their installation may be throttled by bandwidth
@@ -395,7 +416,8 @@
       if (CanUseUpdateService(pending_id)) {
         update_check_params.update_info[pending_id].is_corrupt_reinstall =
             is_corrupt_reinstall;
-      } else if (downloader_->AddPendingExtension(
+      } else if (info &&
+                 downloader_->AddPendingExtension(
                      pending_id, info->update_url(), info->install_source(),
                      is_corrupt_reinstall, request_id,
                      is_high_priority_extension_pending
diff --git a/chrome/browser/extensions/updater/extension_updater_unittest.cc b/chrome/browser/extensions/updater/extension_updater_unittest.cc
index f5567585..0b0a8b2 100644
--- a/chrome/browser/extensions/updater/extension_updater_unittest.cc
+++ b/chrome/browser/extensions/updater/extension_updater_unittest.cc
@@ -1074,9 +1074,8 @@
         CreateManifestFetchData(GURL("http://localhost/foo")));
     UpdateManifestResults updates;
 
-    std::list<std::string> ids_for_update_check;
-    pending_extension_manager->GetPendingIdsForUpdateCheck(
-        &ids_for_update_check);
+    std::list<std::string> ids_for_update_check =
+        pending_extension_manager->GetPendingIdsForUpdateCheck();
 
     for (const std::string& id : ids_for_update_check) {
       fetch_data->AddExtension(id, "1.0.0.0", &kNeverPingedData,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 47e6d9b..7e70f210 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -56,7 +56,12 @@
   {
     "name": "adaptive-button-in-top-toolbar",
     "owners": [ "bttk", "chrome-segmentation-platform@google.com" ],
-    "expiry_milestone": 93
+    "expiry_milestone": 94
+  },
+  {
+    "name": "adaptive-button-in-top-toolbar-customization",
+    "owners": [ "bttk", "chrome-segmentation-platform@google.com" ],
+    "expiry_milestone": 94
   },
   {
     "name": "add-to-homescreen-iph",
@@ -3343,6 +3348,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "lacros-selection",
+    "owners": [ "kimjae", "erikchen", "lacros-team@google.com" ],
+    "expiry_milestone": 130
+  },
+  {
     "name": "lacros-stability",
     "owners": [ "jamescook", "erikchen", "lacros-team@google.com" ],
     // Once Lacros is launched, this flag can be removed. Until then, this
@@ -3600,6 +3610,16 @@
     "expiry_milestone": 95
   },
   {
+    "name": "microphone-mute-notifications",
+    "owners": ["tbarzic", "rtiknoff", "amehfooz"],
+    "expiry_milestone": 94
+  },
+  {
+    "name": "microphone-mute-switch-device",
+    "owners": ["tbarzic", "rtiknoff", "amehfooz"],
+    "expiry_milestone": 94
+  },
+  {
     "name": "minutes-delay-to-restore-gaia-cookies-if-deleted",
     "owners": ["fernandex", "chrome-signin-team"],
     "expiry_milestone": 99
@@ -3852,7 +3872,7 @@
   {
     "name": "omnibox-disable-cgi-param-matching",
     "owners": [ "yoangela", "chrome-omnibox-team@google.com" ],
-    "expiry_milestone": 90
+    "expiry_milestone": 96
   },
   {
     "name": "omnibox-drive-suggestions",
@@ -4213,6 +4233,11 @@
     "expiry_milestone": 92
   },
   {
+    "name": "playback-speed-button",
+    "owners": [ "steimel", "media-dev" ],
+    "expiry_milestone": 100
+  },
+  {
     "name": "pluginvm-fullscreen",
     "owners": [ "joelhockey" ],
     "expiry_milestone": 96
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 85ea603..1634b30 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2022,6 +2022,10 @@
     "chrome://flags/#quiet-notification-prompts and `Safe Browsing Enhanced "
     "Protection` to be enabled.";
 
+const char kPlaybackSpeedButtonName[] = "Playback Speed Button";
+const char kPlaybackSpeedButtonDescription[] =
+    "Enable the playback speed button on the media controls.";
+
 const char kPointerLockOptionsName[] = "Enables pointer lock options";
 const char kPointerLockOptionsDescription[] =
     "Enables pointer lock unadjustedMovement. When unadjustedMovement is set "
@@ -3446,6 +3450,10 @@
 const char kAdaptiveButtonInTopToolbarName[] = "Adaptive button in top toolbar";
 const char kAdaptiveButtonInTopToolbarDescription[] =
     "Enables showing an adaptive action button in the top toolbar";
+const char kAdaptiveButtonInTopToolbarCustomizationName[] =
+    "Adaptive button in top toolbar customization";
+const char kAdaptiveButtonInTopToolbarCustomizationDescription[] =
+    "Enables UI for customizing the adaptive action button in the top toolbar";
 const char kShareButtonInTopToolbarName[] = "Share button in top toolbar";
 const char kShareButtonInTopToolbarDescription[] =
     "Enables UI to initiate sharing from the top toolbar. Enabling Adaptive "
@@ -4577,6 +4585,13 @@
 const char kLacrosStabilityLessStableDescription[] = "Weekly updates";
 const char kLacrosStabilityMoreStableDescription[] = "Monthly updates";
 
+const char kLacrosSelectionName[] = "Lacros selection";
+const char kLacrosSelectionDescription[] =
+    "Choosing between rootfs or stateful Lacros.";
+
+const char kLacrosSelectionRootfsDescription[] = "Rootfs";
+const char kLacrosSelectionStatefulDescription[] = "Stateful";
+
 const char kLacrosSupportName[] = "Lacros support";
 const char kLacrosSupportDescription[] =
     "Support for the experimental lacros-chrome browser. Please note that the "
@@ -4639,6 +4654,18 @@
     "The toggle allows users to set whether a network should be considered "
     "metered for purposes of bandwith usage (e.g. for automatic updates).";
 
+const char kMicrophoneMuteNotificationsName[] = "Microphone Mute Notifications";
+const char kMicrophoneMuteNotificationsDescription[] =
+    "Enables notifications that are shown when an app tries to use microphone "
+    "while audio input is muted.";
+
+const char kMicrophoneMuteSwitchDeviceName[] = "Microphone Mute Switch Device";
+const char kMicrophoneMuteSwitchDeviceDescription[] =
+    "Support for detecting the state of hardware microphone mute toggle. Only "
+    "effective on devices that have a microphone mute toggle. Enabling the "
+    "flag does not affect the toggle functionality, it only affects how the "
+    "System UI handles the mute toggle state.";
+
 const char kMultilingualTypingName[] = "Multilingual typing on CrOS";
 const char kMultilingualTypingDescription[] =
     "Enables support for multilingual assistive typing on Chrome OS.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 14ece75..2c1524d5 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1164,6 +1164,9 @@
 extern const char kPermissionPredictionsName[];
 extern const char kPermissionPredictionsDescription[];
 
+extern const char kPlaybackSpeedButtonName[];
+extern const char kPlaybackSpeedButtonDescription[];
+
 extern const char kPointerLockOptionsName[];
 extern const char kPointerLockOptionsDescription[];
 
@@ -1977,6 +1980,8 @@
 
 extern const char kAdaptiveButtonInTopToolbarName[];
 extern const char kAdaptiveButtonInTopToolbarDescription[];
+extern const char kAdaptiveButtonInTopToolbarCustomizationName[];
+extern const char kAdaptiveButtonInTopToolbarCustomizationDescription[];
 extern const char kShareButtonInTopToolbarName[];
 extern const char kShareButtonInTopToolbarDescription[];
 extern const char kVoiceButtonInTopToolbarName[];
@@ -2665,6 +2670,11 @@
 extern const char kLacrosStabilityLessStableDescription[];
 extern const char kLacrosStabilityMoreStableDescription[];
 
+extern const char kLacrosSelectionName[];
+extern const char kLacrosSelectionDescription[];
+extern const char kLacrosSelectionRootfsDescription[];
+extern const char kLacrosSelectionStatefulDescription[];
+
 extern const char kLacrosSupportName[];
 extern const char kLacrosSupportDescription[];
 
@@ -2704,6 +2714,12 @@
 extern const char kMeteredShowToggleName[];
 extern const char kMeteredShowToggleDescription[];
 
+extern const char kMicrophoneMuteNotificationsName[];
+extern const char kMicrophoneMuteNotificationsDescription[];
+
+extern const char kMicrophoneMuteSwitchDeviceName[];
+extern const char kMicrophoneMuteSwitchDeviceDescription[];
+
 extern const char kMultilingualTypingName[];
 extern const char kMultilingualTypingDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index d0b89b9..09e73e5 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -97,7 +97,6 @@
     &download::features::kSmartSuggestionForLargeDownloads,
     &download::features::kUseDownloadOfflineContentProvider,
     &embedder_support::kShowTrustedPublisherURL,
-    &features::kAdaptiveButtonInTopToolbar,
     &features::kClearOldBrowsingData,
     &features::kContinuousFeeds,
     &features::kContinuousSearch,
@@ -132,6 +131,8 @@
     &feed::kWebFeed,
     &feed::kXsurfaceMetricsReporting,
     &history::kHideFromApi3Transitions,
+    &kAdaptiveButtonInTopToolbar,
+    &kAdaptiveButtonInTopToolbarCustomization,
     &kAddToHomescreenIPH,
     &kAllowNewIncognitoTabIntents,
     &kAllowRemoteContextForNotifications,
@@ -335,6 +336,13 @@
 
 // Alphabetical:
 
+const base::Feature kAdaptiveButtonInTopToolbar{
+    "AdaptiveButtonInTopToolbar", base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kAdaptiveButtonInTopToolbarCustomization{
+    "AdaptiveButtonInTopToolbarCustomization",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kAddToHomescreenIPH{"AddToHomescreenIPH",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 6936959a..85d8c21b 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -13,6 +13,8 @@
 namespace android {
 
 // Alphabetical:
+extern const base::Feature kAdaptiveButtonInTopToolbar;
+extern const base::Feature kAdaptiveButtonInTopToolbarCustomization;
 extern const base::Feature kAddToHomescreenIPH;
 extern const base::Feature kAllowNewIncognitoTabIntents;
 extern const base::Feature kAllowRemoteContextForNotifications;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index c6e2d6f..4e3836e 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -204,6 +204,8 @@
 
     /* Alphabetical: */
     public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR = "AdaptiveButtonInTopToolbar";
+    public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION =
+            "AdaptiveButtonInTopToolbarCustomization";
     public static final String ADD_TO_HOMESCREEN_IPH = "AddToHomescreenIPH";
     public static final String ALLOW_NEW_INCOGNITO_TAB_INTENTS = "AllowNewIncognitoTabIntents";
     public static final String ALLOW_REMOTE_CONTEXT_FOR_NOTIFICATIONS =
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.cc b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.cc
index a25d9bf..e80fec91 100644
--- a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.cc
+++ b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.cc
@@ -225,7 +225,7 @@
                       << num_initialize_attempts_;
       db_->Init(base::BindOnce(
           &NearbyShareCertificateStorageImpl::OnDatabaseInitialized,
-          weak_ptr_factory_.GetWeakPtr()));
+          weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now()));
       break;
     case InitStatus::kInitialized:
       NOTREACHED();
@@ -244,9 +244,13 @@
 }
 
 void NearbyShareCertificateStorageImpl::OnDatabaseInitialized(
+    base::TimeTicks initialize_start_time,
     leveldb_proto::Enums::InitStatus status) {
   switch (status) {
     case leveldb_proto::Enums::InitStatus::kOK:
+      base::UmaHistogramLongTimes(
+          "Nearby.Share.Certificates.Storage.InitializeSuccessDuration",
+          base::TimeTicks::Now() - initialize_start_time);
       FinishInitialization(true);
       break;
     case leveldb_proto::Enums::InitStatus::kError:
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.h b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.h
index 918b84c..8ad6389 100644
--- a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.h
+++ b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.h
@@ -89,7 +89,8 @@
   enum class InitStatus { kUninitialized, kInitialized, kFailed };
 
   void Initialize();
-  void OnDatabaseInitialized(leveldb_proto::Enums::InitStatus status);
+  void OnDatabaseInitialized(base::TimeTicks initialize_start_time,
+                             leveldb_proto::Enums::InitStatus status);
   void FinishInitialization(bool success);
 
   void OnDatabaseDestroyedReinitialize(bool success);
diff --git a/chrome/browser/net/private_network_access_browsertest.cc b/chrome/browser/net/private_network_access_browsertest.cc
index 41308ea..d627d9e 100644
--- a/chrome/browser/net/private_network_access_browsertest.cc
+++ b/chrome/browser/net/private_network_access_browsertest.cc
@@ -144,8 +144,14 @@
 //  - specification: https://wicg.github.io/private-network-access.
 //  - feature browsertests in content/: RenderFrameHostImplTest.
 //
-class PrivateNetworkAccessBrowserTest : public InProcessBrowserTest {
+class PrivateNetworkAccessBrowserTestBase : public InProcessBrowserTest {
  public:
+  PrivateNetworkAccessBrowserTestBase(
+      std::vector<base::Feature> enabled_features,
+      std::vector<base::Feature> disabled_features) {
+    features_.InitWithFeatures(enabled_features, disabled_features);
+  }
+
   content::WebContents* web_contents() {
     return browser()->tab_strip_model()->GetActiveWebContents();
   }
@@ -173,17 +179,32 @@
     return server;
   }
 
- private:
+ protected:
   void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
     host_resolver()->AddRule("*", "127.0.0.1");
   }
+
+ private:
+  base::test::ScopedFeatureList features_;
+};
+
+class PrivateNetworkAccessWithFeatureDisabledBrowserTest
+    : public PrivateNetworkAccessBrowserTestBase {
+ public:
+  PrivateNetworkAccessWithFeatureDisabledBrowserTest()
+      : PrivateNetworkAccessBrowserTestBase(
+            {},
+            {
+                features::kBlockInsecurePrivateNetworkRequests,
+            }) {}
 };
 
 // This test verifies that no feature is counted for the initial navigation from
 // a new tab to a page served by localhost.
 //
 // Regression test for https://crbug.com/1134601.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                        DoesNotRecordAddressSpaceFeatureForInitialNavigation) {
   base::HistogramTester histogram_tester;
   std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
@@ -200,7 +221,7 @@
 // TODO(crbug.com/1129326): Revisit this once the story around top-level
 // navigations is closer to being resolved. Counting these events will help
 // decide what to do.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                        DoesNotRecordAddressSpaceFeatureForRegularNavigation) {
   base::HistogramTester histogram_tester;
   std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
@@ -216,7 +237,7 @@
 // space loads a resource from the local network, the correct WebFeature is
 // use-counted.
 // Disabled, as explained in https://crbug.com/1143206
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                        DISABLED_RecordsAddressSpaceFeatureForFetch) {
   base::HistogramTester histogram_tester;
   std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
@@ -237,7 +258,7 @@
 // address space loads a resource from the local network, the correct WebFeature
 // is use-counted.
 IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessBrowserTest,
+    PrivateNetworkAccessWithFeatureDisabledBrowserTest,
     DISABLED_RecordsAddressSpaceFeatureForFetchInNonSecureContext) {
   base::HistogramTester histogram_tester;
   std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
@@ -259,7 +280,7 @@
 // about:blank, no address space feature is recorded. It serves as a basis for
 // comparison with the following tests, which test behavior with iframes.
 IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessBrowserTest,
+    PrivateNetworkAccessWithFeatureDisabledBrowserTest,
     DoesNotRecordAddressSpaceFeatureForAboutBlankNavigation) {
   base::HistogramTester histogram_tester;
   std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
@@ -282,7 +303,7 @@
 // This test verifies that when a non-secure context served from the public
 // address space loads a child frame from the local network, the correct
 // WebFeature is use-counted.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                        RecordsAddressSpaceFeatureForChildNavigation) {
   base::HistogramTester histogram_tester;
   std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
@@ -314,7 +335,7 @@
 // address space loads a grand-child frame from the local network, the correct
 // WebFeature is use-counted. If inheritance did not work correctly, the
 // intermediate about:blank frame might confuse the address space logic.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                        RecordsAddressSpaceFeatureForGrandchildNavigation) {
   base::HistogramTester histogram_tester;
   std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
@@ -349,33 +370,31 @@
 }
 
 class PrivateNetworkAccessWithFeatureEnabledBrowserTest
-    : public PrivateNetworkAccessBrowserTest {
+    : public PrivateNetworkAccessBrowserTestBase {
  public:
-  PrivateNetworkAccessWithFeatureEnabledBrowserTest() {
-    std::vector<base::Feature> enabled_features = {
-        features::kBlockInsecurePrivateNetworkRequests,
-        dom_distiller::kReaderMode,
-    };
-    std::vector<base::Feature> disabled_features;
-    features_.InitWithFeatures(enabled_features, disabled_features);
-  }
+  PrivateNetworkAccessWithFeatureEnabledBrowserTest()
+      : PrivateNetworkAccessBrowserTestBase(
+            {
+                features::kBlockInsecurePrivateNetworkRequests,
+                dom_distiller::kReaderMode,
+            },
+            {}) {}
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
+    PrivateNetworkAccessBrowserTestBase::SetUpCommandLine(command_line);
     command_line->AppendSwitch(switches::kEnableDomDistiller);
   }
 
  private:
   void SetUpOnMainThread() override {
+    PrivateNetworkAccessBrowserTestBase::SetUpOnMainThread();
     // The distiller needs to run in an isolated environment. For tests we
     // can simply use the last value available.
     if (!dom_distiller::DistillerJavaScriptWorldIdIsSet()) {
       dom_distiller::SetDistillerJavaScriptWorldId(
           content::ISOLATED_WORLD_ID_CONTENT_END);
     }
-    host_resolver()->AddRule("*", "127.0.0.1");
   }
-
-  base::test::ScopedFeatureList features_;
 };
 
 // This test verifies that private network requests that are blocked result in
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
index f3bed1a..aca8618 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/optimization_guide/content/browser/optimization_guide_decider.h"
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
 #include "components/optimization_guide/proto/hints.pb.h"
 #include "components/optimization_guide/proto/models.pb.h"
 
@@ -48,7 +49,8 @@
 // and no information will be retrieved.
 class OptimizationGuideKeyedService
     : public KeyedService,
-      public optimization_guide::OptimizationGuideDecider {
+      public optimization_guide::OptimizationGuideDecider,
+      public optimization_guide::OptimizationGuideModelProvider {
  public:
   explicit OptimizationGuideKeyedService(
       content::BrowserContext* browser_context);
@@ -63,13 +65,6 @@
       optimization_guide::proto::OptimizationTarget optimization_target,
       optimization_guide::OptimizationGuideTargetDecisionCallback callback)
       override;
-  void AddObserverForOptimizationTargetModel(
-      optimization_guide::proto::OptimizationTarget optimization_target,
-      const absl::optional<optimization_guide::proto::Any>& model_metadata,
-      optimization_guide::OptimizationTargetModelObserver* observer) override;
-  void RemoveObserverForOptimizationTargetModel(
-      optimization_guide::proto::OptimizationTarget optimization_target,
-      optimization_guide::OptimizationTargetModelObserver* observer) override;
   void RegisterOptimizationTypes(
       const std::vector<optimization_guide::proto::OptimizationType>&
           optimization_types) override;
@@ -82,6 +77,15 @@
       optimization_guide::proto::OptimizationType optimization_type,
       optimization_guide::OptimizationMetadata* optimization_metadata) override;
 
+  // optimization_guide::OptimizationGuideModelProvider implementation:
+  void AddObserverForOptimizationTargetModel(
+      optimization_guide::proto::OptimizationTarget optimization_target,
+      const absl::optional<optimization_guide::proto::Any>& model_metadata,
+      optimization_guide::OptimizationTargetModelObserver* observer) override;
+  void RemoveObserverForOptimizationTargetModel(
+      optimization_guide::proto::OptimizationTarget optimization_target,
+      optimization_guide::OptimizationTargetModelObserver* observer) override;
+
   // Adds hints for a URL with provided metadata to the optimization guide.
   // For testing purposes only. This will flush any callbacks for |url| that
   // were registered via |CanApplyOptimizationAsync|. If no applicable callbacks
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
index 00b8786e..95c7f0cc 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
@@ -35,6 +35,7 @@
 #include "components/optimization_guide/core/optimization_guide_store.h"
 #include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
+#include "components/optimization_guide/core/optimization_target_model_observer.h"
 #include "components/optimization_guide/core/prediction_model.h"
 #include "components/optimization_guide/core/prediction_model_fetcher.h"
 #include "components/optimization_guide/core/prediction_model_file.h"
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
index a51bd3a..a89b66f 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
@@ -27,6 +27,7 @@
 #include "components/optimization_guide/core/optimization_guide_store.h"
 #include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
+#include "components/optimization_guide/core/optimization_target_model_observer.h"
 #include "components/optimization_guide/core/prediction_model.h"
 #include "components/optimization_guide/core/prediction_model_fetcher.h"
 #include "components/optimization_guide/core/proto_database_provider_test_base.h"
diff --git a/chrome/browser/policy/android/BUILD.gn b/chrome/browser/policy/android/BUILD.gn
index 20f45aa3..ffef53d 100644
--- a/chrome/browser/policy/android/BUILD.gn
+++ b/chrome/browser/policy/android/BUILD.gn
@@ -4,12 +4,15 @@
 
 import("//build/config/android/rules.gni")
 
-_jni_sources =
-    [ "java/src/org/chromium/chrome/browser/policy/PolicyServiceFactory.java" ]
+_jni_sources = [
+  "java/src/org/chromium/chrome/browser/policy/CloudManagementSharedPreferences.java",
+  "java/src/org/chromium/chrome/browser/policy/PolicyServiceFactory.java",
+]
 
 android_library("java") {
   deps = [
     "//base:base_java",
+    "//chrome/browser/preferences:java",
     "//chrome/browser/profiles/android:java",
     "//components/policy/android:policy_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
@@ -21,3 +24,20 @@
 generate_jni("jni_headers") {
   sources = _jni_sources
 }
+
+android_library("junit") {
+  # Skip platform checks since Robolectric depends on requires_android targets.
+  bypass_platform_checks = true
+  testonly = true
+
+  sources = [ "java/src/org/chromium/chrome/browser/policy/CloudManagementSharedPreferencesTest.java" ]
+
+  deps = [
+    ":java",
+    "//base:base_junit_test_support",
+    "//chrome/browser/preferences:java",
+    "//third_party/android_deps:robolectric_all_java",
+    "//third_party/androidx:androidx_test_runner_java",
+    "//third_party/junit",
+  ]
+}
diff --git a/chrome/browser/policy/android/cloud_management_shared_preferences.cc b/chrome/browser/policy/android/cloud_management_shared_preferences.cc
new file mode 100644
index 0000000..485d2945
--- /dev/null
+++ b/chrome/browser/policy/android/cloud_management_shared_preferences.cc
@@ -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.
+
+#include "chrome/browser/policy/android/cloud_management_shared_preferences.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "chrome/browser/policy/android/jni_headers/CloudManagementSharedPreferences_jni.h"
+
+namespace policy {
+namespace android {
+
+void SaveDmTokenInSharedPreferences(const std::string& dm_token) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_CloudManagementSharedPreferences_saveDmToken(
+      env, base::android::ConvertUTF8ToJavaString(env, dm_token));
+}
+
+std::string ReadDmTokenFromSharedPreferences() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  return base::android::ConvertJavaStringToUTF8(
+      env, Java_CloudManagementSharedPreferences_readDmToken(env));
+}
+
+}  // namespace android
+}  // namespace policy
diff --git a/chrome/browser/policy/android/cloud_management_shared_preferences.h b/chrome/browser/policy/android/cloud_management_shared_preferences.h
new file mode 100644
index 0000000..9a51436
--- /dev/null
+++ b/chrome/browser/policy/android/cloud_management_shared_preferences.h
@@ -0,0 +1,25 @@
+// 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_POLICY_ANDROID_CLOUD_MANAGEMENT_SHARED_PREFERENCES_H_
+#define CHROME_BROWSER_POLICY_ANDROID_CLOUD_MANAGEMENT_SHARED_PREFERENCES_H_
+
+#include <string>
+
+#include "components/policy/policy_export.h"
+
+namespace policy {
+namespace android {
+
+// Saves the device management token to Shared Preferences. Ignored if
+// |dm_token| is empty.
+void SaveDmTokenInSharedPreferences(const std::string& dm_token);
+
+// Returns the DM token available from Shared Preferences, or
+std::string ReadDmTokenFromSharedPreferences();
+
+}  // namespace android
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_POLICY_ANDROID_CLOUD_MANAGEMENT_SHARED_PREFERENCES_H_
diff --git a/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementSharedPreferences.java b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementSharedPreferences.java
new file mode 100644
index 0000000..fbc9067c
--- /dev/null
+++ b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementSharedPreferences.java
@@ -0,0 +1,37 @@
+// 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.policy;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+
+/**
+ * Gets and sets preferences associated with cloud management.
+ */
+@JNINamespace("policy::android")
+public class CloudManagementSharedPreferences {
+    /**
+     * Sets the "Cloud management DM token" preference.
+     *
+     * @param dmToken The token provided by the DM server when browser registration succeeds.
+     */
+    @CalledByNative
+    public static void saveDmToken(String dmToken) {
+        SharedPreferencesManager.getInstance().writeString(
+                ChromePreferenceKeys.CLOUD_MANAGEMENT_DM_TOKEN, dmToken);
+    }
+
+    /**
+     * Returns the value of the "Cloud management DM token" preference, which is non-empty
+     * if browser registration succeeded.
+     */
+    @CalledByNative
+    public static String readDmToken() {
+        return SharedPreferencesManager.getInstance().readString(
+                ChromePreferenceKeys.CLOUD_MANAGEMENT_DM_TOKEN, "");
+    }
+}
diff --git a/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementSharedPreferencesTest.java b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementSharedPreferencesTest.java
new file mode 100644
index 0000000..2495838
--- /dev/null
+++ b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementSharedPreferencesTest.java
@@ -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.
+
+package org.chromium.chrome.browser.policy;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+
+/**
+ * Unit tests for CloudManagementSharedPreferencesTest.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class CloudManagementSharedPreferencesTest {
+    private static final String DM_TOKEN = "fake-dm-token";
+
+    @Test
+    @SmallTest
+    public void testSaveDmToken() {
+        CloudManagementSharedPreferences.saveDmToken(DM_TOKEN);
+        Assert.assertEquals(SharedPreferencesManager.getInstance().readString(
+                                    ChromePreferenceKeys.CLOUD_MANAGEMENT_DM_TOKEN, ""),
+                DM_TOKEN);
+    }
+
+    @Test
+    @SmallTest
+    public void testReadDmToken() {
+        Assert.assertEquals(CloudManagementSharedPreferences.readDmToken(), "");
+
+        SharedPreferencesManager.getInstance().writeString(
+                ChromePreferenceKeys.CLOUD_MANAGEMENT_DM_TOKEN, DM_TOKEN);
+        Assert.assertEquals(CloudManagementSharedPreferences.readDmToken(), DM_TOKEN);
+    }
+}
diff --git a/chrome/browser/policy/browser_dm_token_storage_android.cc b/chrome/browser/policy/browser_dm_token_storage_android.cc
index 0d22107..0c22e91 100644
--- a/chrome/browser/policy/browser_dm_token_storage_android.cc
+++ b/chrome/browser/policy/browser_dm_token_storage_android.cc
@@ -8,6 +8,7 @@
 
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
+#include "chrome/browser/policy/android/cloud_management_shared_preferences.h"
 #include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 
@@ -15,9 +16,9 @@
 
 namespace {
 
-bool StoreDMTokenInSharedPreferences(const std::string& token,
-                                     const std::string& client_id) {
-  return false;
+bool StoreDmTokenInSharedPreferences(const std::string& dm_token) {
+  android::SaveDmTokenInSharedPreferences(dm_token);
+  return true;
 }
 
 }  // namespace
@@ -36,7 +37,7 @@
 }
 
 std::string BrowserDMTokenStorageAndroid::InitDMToken() {
-  return std::string();
+  return android::ReadDmTokenFromSharedPreferences();
 }
 
 bool BrowserDMTokenStorageAndroid::InitEnrollmentErrorOption() {
@@ -46,7 +47,7 @@
 BrowserDMTokenStorage::StoreTask BrowserDMTokenStorageAndroid::SaveDMTokenTask(
     const std::string& token,
     const std::string& client_id) {
-  return base::BindOnce(&StoreDMTokenInSharedPreferences, token, client_id);
+  return base::BindOnce(&StoreDmTokenInSharedPreferences, token);
 }
 
 scoped_refptr<base::TaskRunner>
diff --git a/chrome/browser/policy/browser_dm_token_storage_android_unittest.cc b/chrome/browser/policy/browser_dm_token_storage_android_unittest.cc
index a9137e8..b19dfc9 100644
--- a/chrome/browser/policy/browser_dm_token_storage_android_unittest.cc
+++ b/chrome/browser/policy/browser_dm_token_storage_android_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
+// 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.
 
@@ -12,6 +12,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using ::testing::Eq;
 using ::testing::Invoke;
 using ::testing::IsEmpty;
 
@@ -57,19 +58,20 @@
   TestStoreDMTokenDelegate callback_delegate;
 
   base::RunLoop run_loop;
-  EXPECT_CALL(callback_delegate, OnDMTokenStored(false))
+  EXPECT_CALL(callback_delegate, OnDMTokenStored(true))
       .WillOnce(Invoke(&run_loop, &base::RunLoop::Quit));
 
-  BrowserDMTokenStorageAndroid storage_delegate;
-  auto task = storage_delegate.SaveDMTokenTask(kDMToken,
-                                               storage_delegate.InitClientId());
+  BrowserDMTokenStorageAndroid storage;
+  auto task = storage.SaveDMTokenTask(kDMToken, storage.InitClientId());
   auto reply = base::BindOnce(&TestStoreDMTokenDelegate::OnDMTokenStored,
                               base::Unretained(&callback_delegate));
-  base::PostTaskAndReplyWithResult(
-      storage_delegate.SaveDMTokenTaskRunner().get(), FROM_HERE,
-      std::move(task), std::move(reply));
+  base::PostTaskAndReplyWithResult(storage.SaveDMTokenTaskRunner().get(),
+                                   FROM_HERE, std::move(task),
+                                   std::move(reply));
 
   run_loop.Run();
+
+  EXPECT_THAT(storage.InitDMToken(), Eq(kDMToken));
 }
 
 }  // namespace policy
diff --git a/chrome/browser/policy/extension_policy_browsertest.cc b/chrome/browser/policy/extension_policy_browsertest.cc
index 1ea17fc..51e4f12 100644
--- a/chrome/browser/policy/extension_policy_browsertest.cc
+++ b/chrome/browser/policy/extension_policy_browsertest.cc
@@ -1838,8 +1838,9 @@
 
   // Step 4: Check that we are going to reinstall the extension and wait for
   // extension reinstall.
-  EXPECT_TRUE(service->pending_extension_manager()
-                  ->IsPolicyReinstallForCorruptionExpected(kGoodCrxId));
+  EXPECT_TRUE(
+      service->pending_extension_manager()->IsReinstallForCorruptionExpected(
+          kGoodCrxId));
   registry_observer.WaitForExtensionWillBeInstalled();
 
   // Extension was reloaded, old extension object is invalid.
@@ -1921,8 +1922,9 @@
 
   // Step 4: Check that we are going to reinstall the extension and wait for
   // extension reinstall.
-  EXPECT_TRUE(service->pending_extension_manager()
-                  ->IsPolicyReinstallForCorruptionExpected(kGoodCrxId));
+  EXPECT_TRUE(
+      service->pending_extension_manager()->IsReinstallForCorruptionExpected(
+          kGoodCrxId));
   observer.WaitForExtensionWillBeInstalled();
 
   // Extension was reloaded, old extension object is invalid.
@@ -1996,8 +1998,9 @@
 
   // Step 4: Check that we are not going to reinstall the extension, but we have
   // detected a corruption.
-  EXPECT_FALSE(service->pending_extension_manager()
-                   ->IsPolicyReinstallForCorruptionExpected(kGoodCrxId));
+  EXPECT_FALSE(
+      service->pending_extension_manager()->IsReinstallForCorruptionExpected(
+          kGoodCrxId));
   histogram_tester.ExpectUniqueSample(
       "Extensions.CorruptPolicyExtensionDetected3",
       extensions::PendingExtensionManager::PolicyReinstallReason::
diff --git a/chrome/browser/predictors/preconnect_manager.cc b/chrome/browser/predictors/preconnect_manager.cc
index 7cbe5b3..18a98d6 100644
--- a/chrome/browser/predictors/preconnect_manager.cc
+++ b/chrome/browser/predictors/preconnect_manager.cc
@@ -10,7 +10,7 @@
 #include "base/trace_event/trace_event.h"
 #include "chrome/browser/predictors/predictors_features.h"
 #include "chrome/browser/predictors/resource_prefetch_predictor.h"
-#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
@@ -69,12 +69,12 @@
 PreresolveJob::~PreresolveJob() = default;
 
 PreconnectManager::PreconnectManager(base::WeakPtr<Delegate> delegate,
-                                     Profile* profile)
+                                     content::BrowserContext* browser_context)
     : delegate_(std::move(delegate)),
-      profile_(profile),
+      browser_context_(browser_context),
       inflight_preresolves_count_(0) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(profile_);
+  DCHECK(browser_context_);
 }
 
 PreconnectManager::~PreconnectManager() = default;
@@ -329,13 +329,13 @@
   if (network_context_)
     return network_context_;
 
-  if (profile_->AsTestingProfile()) {
-    // We're testing and |network_context_| wasn't set. Return nullptr to avoid
-    // hitting the network.
-    return nullptr;
-  }
+#if defined(UNIT_TEST)
+  // We're testing and |network_context_| wasn't set. Return nullptr to avoid
+  // hitting the network.
+  return nullptr;
+#endif
 
-  return profile_->GetDefaultStoragePartition()->GetNetworkContext();
+  return browser_context_->GetDefaultStoragePartition()->GetNetworkContext();
 }
 
 }  // namespace predictors
diff --git a/chrome/browser/predictors/preconnect_manager.h b/chrome/browser/predictors/preconnect_manager.h
index dbdde3d..6493cdec 100644
--- a/chrome/browser/predictors/preconnect_manager.h
+++ b/chrome/browser/predictors/preconnect_manager.h
@@ -20,7 +20,9 @@
 #include "net/base/network_isolation_key.h"
 #include "url/gurl.h"
 
-class Profile;
+namespace content {
+class BrowserContext;
+}
 
 namespace network {
 namespace mojom {
@@ -145,7 +147,8 @@
         bool success) {}
   };
 
-  PreconnectManager(base::WeakPtr<Delegate> delegate, Profile* profile);
+  PreconnectManager(base::WeakPtr<Delegate> delegate,
+                    content::BrowserContext* browser_context);
   virtual ~PreconnectManager();
 
   // Starts preconnect and preresolve jobs keyed by |url|.
@@ -210,7 +213,7 @@
   network::mojom::NetworkContext* GetNetworkContext() const;
 
   base::WeakPtr<Delegate> delegate_;
-  Profile* const profile_;
+  content::BrowserContext* const browser_context_;
   std::list<PreresolveJobId> queued_jobs_;
   PreresolveJobMap preresolve_jobs_;
   std::map<GURL, std::unique_ptr<PreresolveInfo>> preresolve_info_;
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
index 9aed9e2d..a563a2b 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -131,6 +131,12 @@
             "Chrome.Clipboard.SharedUriTimestamp";
 
     /**
+     * The server-side token generated by the Device Management server on browser enrollment for
+     * Cloud Management.
+     */
+    public static final String CLOUD_MANAGEMENT_DM_TOKEN = "Chrome.Policy.CloudManagementDMToken";
+
+    /**
      * Save the timestamp of the last time that chrome-managed commerce subscriptions are
      * initialized.
      */
@@ -997,6 +1003,7 @@
                 CHROME_SURVEY_PROMPT_DISPLAYED_TIMESTAMP.pattern(),
                 CLIPBOARD_SHARED_URI,
                 CLIPBOARD_SHARED_URI_TIMESTAMP,
+                CLOUD_MANAGEMENT_DM_TOKEN,
                 COMMERCE_SUBSCRIPTIONS_CHROME_MANAGED_TIMESTAMP,
                 CONDITIONAL_TAB_STRIP_CONTINUOUS_DISMISS_COUNTER,
                 CONDITIONAL_TAB_STRIP_FEATURE_STATUS,
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index eb6ccd83..21cf3c2 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -425,6 +425,11 @@
 namespace {
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+// Deprecated 5/2021
+const char kFeatureUsageDailySampleESim[] = "feature_usage.daily_sample.ESim";
+const char kFeatureUsageDailySampleFingerprint[] =
+    "feature_usage.daily_sample.Fingerprint";
+
 // Deprecated 12/2020
 const char kLocalSearchServiceSyncMetricsDailySample[] =
     "local_search_service_sync.metrics.daily_sample";
@@ -583,6 +588,8 @@
   registry->RegisterIntegerPref(kLocalSearchServiceSyncMetricsHelpAppCount, 0);
   registry->RegisterIntegerPref(kLocalSearchServiceSyncMetricsCrosSettingsCount,
                                 0);
+
+  registry->RegisterInt64Pref(kFeatureUsageDailySampleESim, 0);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if !defined(OS_ANDROID)
@@ -700,6 +707,10 @@
   registry->RegisterIntegerPref(kToolbarSize, -1);
 #endif
   registry->RegisterBooleanPref(kSessionExitedCleanly, true);
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  registry->RegisterInt64Pref(kFeatureUsageDailySampleFingerprint, 0);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 }  // namespace
@@ -798,7 +809,6 @@
   ash::AudioDevicesPrefHandlerImpl::RegisterPrefs(registry);
   ash::cert_provisioning::RegisterLocalStatePrefs(registry);
   chromeos::CellularESimProfileHandlerImpl::RegisterLocalStatePrefs(registry);
-  chromeos::CellularMetricsLogger::RegisterLocalStatePrefs(registry);
   ash::ChromeUserManagerImpl::RegisterPrefs(registry);
   crosapi::browser_util::RegisterLocalStatePrefs(registry);
   chromeos::CupsPrintersManager::RegisterLocalStatePrefs(registry);
@@ -1252,6 +1262,9 @@
   local_state->ClearPref(kLocalSearchServiceSyncMetricsDailySample);
   local_state->ClearPref(kLocalSearchServiceSyncMetricsCrosSettingsCount);
   local_state->ClearPref(kLocalSearchServiceSyncMetricsHelpAppCount);
+
+  // Added 5/2021
+  local_state->ClearPref(kFeatureUsageDailySampleESim);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if !defined(OS_ANDROID)
@@ -1415,6 +1428,11 @@
 #endif
   profile_prefs->ClearPref(kSessionExitedCleanly);
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Added 05/2021
+  profile_prefs->ClearPref(kFeatureUsageDailySampleFingerprint);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_PROFILE_PREFS
 }
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_settings.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_settings.cc
index b2fadf3d..1f58e004 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_settings.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_settings.cc
@@ -232,13 +232,9 @@
 
   const bool floc_feature_enabled = base::FeatureList::IsEnabled(
       blink::features::kInterestCohortAPIOriginTrial);
-  if (!IsFlocAllowed() || !floc_feature_enabled)
-    return l10n_util::GetStringUTF16(IDS_PRIVACY_SANDBOX_FLOC_INVALID);
-
-  // If FLoC is allowed, but the ID is invalid, a new ID must be being computed.
   auto floc_id = federated_learning::FlocId::ReadFromPrefs(pref_service_);
-  if (!floc_id.IsValid())
-    return l10n_util::GetStringUTF16(IDS_PRIVACY_SANDBOX_FLOC_IN_PROGRESS);
+  if (!IsFlocAllowed() || !floc_feature_enabled || !floc_id.IsValid())
+    return l10n_util::GetStringUTF16(IDS_PRIVACY_SANDBOX_FLOC_INVALID);
 
   return base::NumberToString16(floc_id.ToUint64());
 }
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_unittest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_unittest.cc
index 6d1cb922..67eb94aa 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_unittest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_unittest.cc
@@ -909,14 +909,8 @@
   EXPECT_EQ(std::u16string(u"123456"),
             privacy_sandbox_settings()->GetFlocIdForDisplay());
 
-  // An invalid FLoC id while FLoc is enabled should be interpreted as being
-  // calculated.
-  floc_id.InvalidateIdAndSaveToPrefs(profile()->GetTestingPrefService());
-  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PRIVACY_SANDBOX_FLOC_IN_PROGRESS),
-            privacy_sandbox_settings()->GetFlocIdForDisplay());
-
   // If the FLoC preference, the Sandbox Preference, or the feature is disabled,
-  // the invalid string should be returned.
+  // or the FLoC ID is invalid, the invalid string should be returned.
   feature_list()->Reset();
   feature_list()->InitWithFeatures(
       {}, {blink::features::kInterestCohortAPIOriginTrial});
@@ -937,6 +931,10 @@
       prefs::kPrivacySandboxFlocEnabled, false);
   EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PRIVACY_SANDBOX_FLOC_INVALID),
             privacy_sandbox_settings()->GetFlocIdForDisplay());
+
+  floc_id.InvalidateIdAndSaveToPrefs(profile()->GetTestingPrefService());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PRIVACY_SANDBOX_FLOC_INVALID),
+            privacy_sandbox_settings()->GetFlocIdForDisplay());
 }
 
 TEST_F(PrivacySandboxSettingsTest, GetFlocIdNextUpdateForDisplay) {
diff --git a/chrome/browser/profiles/profile_attributes_entry.cc b/chrome/browser/profiles/profile_attributes_entry.cc
index dc946b1..f7fba4d 100644
--- a/chrome/browser/profiles/profile_attributes_entry.cc
+++ b/chrome/browser/profiles/profile_attributes_entry.cc
@@ -5,6 +5,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/feature_list.h"
 #include "base/hash/hash.h"
 #include "base/logging.h"
 #include "base/notreached.h"
@@ -157,8 +158,11 @@
 
   is_force_signin_enabled_ = signin_util::IsForceSigninEnabled();
   if (is_force_signin_enabled_) {
-    if (!IsAuthenticated())
+    if (!IsAuthenticated() ||
+        (IsAuthError() &&
+         base::FeatureList::IsEnabled(features::kForceSignInReauth))) {
       is_force_signin_profile_locked_ = true;
+    }
 #if defined(OS_MAC) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
     defined(OS_WIN)
   } else if (IsSigninRequired()) {
diff --git a/chrome/browser/profiles/profile_attributes_storage_unittest.cc b/chrome/browser/profiles/profile_attributes_storage_unittest.cc
index 77785d1..2bc043e 100644
--- a/chrome/browser/profiles/profile_attributes_storage_unittest.cc
+++ b/chrome/browser/profiles/profile_attributes_storage_unittest.cc
@@ -767,8 +767,83 @@
   }
 }
 
+TEST_F(ProfileAttributesStorageTest, IsSigninRequiredOnInit_NotAuthenticated) {
+  signin_util::ScopedForceSigninSetterForTesting force_signin_setter(true);
+
+  base::FilePath profile_path = GetProfilePath("testing_profile_path");
+  EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
+
+  ProfileAttributesInitParams params;
+  params.profile_path = profile_path;
+  params.profile_name = u"testing_profile_name";
+  params.is_consented_primary_account = false;
+  storage()->AddProfile(std::move(params));
+
+  VerifyAndResetCallExpectations();
+
+  ProfileAttributesEntry* entry =
+      storage()->GetProfileAttributesWithPath(profile_path);
+  EXPECT_FALSE(entry->IsAuthenticated());
+  EXPECT_TRUE(entry->IsSigninRequired());
+}
+
+TEST_F(ProfileAttributesStorageTest, IsSigninRequiredOnInit_Authenticated) {
+  signin_util::ScopedForceSigninSetterForTesting force_signin_setter(true);
+
+  base::FilePath profile_path = GetProfilePath("testing_profile_path");
+  EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
+
+  ProfileAttributesInitParams params;
+  params.profile_path = profile_path;
+  params.profile_name = u"testing_profile_name";
+  params.gaia_id = "testing_profile_gaia";
+  params.user_name = u"testing_profile_username";
+  params.is_consented_primary_account = true;
+  storage()->AddProfile(std::move(params));
+
+  VerifyAndResetCallExpectations();
+
+  ProfileAttributesEntry* entry =
+      storage()->GetProfileAttributesWithPath(profile_path);
+  EXPECT_TRUE(entry->IsAuthenticated());
+  EXPECT_FALSE(entry->IsSigninRequired());
+}
+
+TEST_F(ProfileAttributesStorageTest,
+       IsSigninRequiredOnInit_AuthenticatedWithError) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kForceSignInReauth);
+  signin_util::ScopedForceSigninSetterForTesting force_signin_setter(true);
+
+  base::FilePath profile_path = GetProfilePath("testing_profile_path");
+  EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
+
+  ProfileAttributesInitParams params;
+  params.profile_path = profile_path;
+  params.profile_name = u"testing_profile_name";
+  params.gaia_id = "testing_profile_gaia";
+  params.user_name = u"testing_profile_username";
+  params.is_consented_primary_account = true;
+  storage()->AddProfile(std::move(params));
+
+  VerifyAndResetCallExpectations();
+
+  // IsAuthError() cannot be set as an init parameter. Set it after an entry
+  // is initialized and reset the cache to reinitialize an entry from prefs.
+  ProfileAttributesEntry* entry =
+      storage()->GetProfileAttributesWithPath(profile_path);
+  entry->SetIsAuthError(true);
+  testing_profile_manager_.DeleteProfileInfoCache();
+
+  entry = storage()->GetProfileAttributesWithPath(profile_path);
+  ASSERT_NE(entry, nullptr);
+  EXPECT_TRUE(entry->IsAuthenticated());
+  EXPECT_TRUE(entry->IsAuthError());
+  EXPECT_TRUE(entry->IsSigninRequired());
+}
+
 TEST_F(ProfileAttributesStorageTest, ProfileForceSigninLock) {
-  signin_util::ScopedForceSigninSetterForTesting signin_setter(true);
+  signin_util::ScopedForceSigninSetterForTesting force_signin_setter(true);
 
   AddTestingProfile();
 
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 0c9949e..41b804e 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -1068,8 +1068,9 @@
     UMA_HISTOGRAM_EXACT_LINEAR(
         "ContextMenu.SelectedOptionDesktop.MisspelledWord", enum_id,
         GetUmaValueMax(UmaEnumIdLookupType::ContextSpecificEnumId));
-  } else if (!params_.selection_text.empty() &&
-             params_.media_type == ContextMenuDataMediaType::kNone) {
+  } else if ((!params_.selection_text.empty() ||
+              params_.opened_from_highlight) &&
+              params_.media_type == ContextMenuDataMediaType::kNone) {
     // Probably just text.
     UMA_HISTOGRAM_EXACT_LINEAR(
         "ContextMenu.SelectedOptionDesktop.SelectedText", enum_id,
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/action_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/action_manager.js
index a89c91a..aabf565 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/action_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/action_manager.js
@@ -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 {FocusRingManager} from './focus_ring_manager.js';
 import {MenuManager} from './menu_manager.js';
 import {Navigator} from './navigator.js';
 import {SAChildNode, SARootNode} from './nodes/switch_access_node.js';
@@ -112,6 +113,7 @@
       case SwitchAccessMenuAction.RIGHT_CLICK:
         // Exit menu, then click (so the action will hit the desired target,
         // instead of the menu).
+        FocusRingManager.clearAll();
         ActionManager.exitCurrentMenu();
         Navigator.byPoint.performMouseAction(action);
         break;
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager.js
index 44c845b7..e56af07e2 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager.js
@@ -172,6 +172,11 @@
    * @private
    */
   updateFocusRings_(primaryRingNode, previewRingNode) {
+    if (SwitchAccess.mode === SAConstants.Mode.POINT_SCAN &&
+        !MenuManager.isMenuOpen()) {
+      return;
+    }
+
     const focusRings = [];
     this.rings_.forEach((ring) => focusRings.push(ring));
     chrome.accessibilityPrivate.setFocusRings(focusRings);
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/point_scan_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/point_scan_manager.js
index eb3e003..fc818e4 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/point_scan_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/point_scan_manager.js
@@ -29,8 +29,8 @@
 
   /** @override */
   start() {
-    SwitchAccess.mode = SAConstants.Mode.POINT_SCAN;
     FocusRingManager.clearAll();
+    SwitchAccess.mode = SAConstants.Mode.POINT_SCAN;
     chrome.accessibilityPrivate.onPointScanSet.addListener(this.pointListener_);
     chrome.accessibilityPrivate.setPointScanState(PointScanState.START);
   }
diff --git a/chrome/browser/resources/history/browser_service.js b/chrome/browser/resources/history/browser_service.js
index d3bd620..7fa9f76 100644
--- a/chrome/browser/resources/history/browser_service.js
+++ b/chrome/browser/resources/history/browser_service.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
 import {RESULTS_PER_PAGE} from './constants.js';
 import {ForeignSession, HistoryEntry, HistoryQuery} from './externs.js';
 
@@ -119,6 +119,17 @@
   startSignInFlow() {
     chrome.send('startSignInFlow');
   }
+
+  /** @return {!BrowserService} */
+  static getInstance() {
+    return instance || (instance = new BrowserService());
+  }
+
+  /** @param {!BrowserService} obj */
+  static setInstance(obj) {
+    instance = obj;
+  }
 }
 
-addSingletonGetter(BrowserService);
+/** @type {?BrowserService} */
+let instance = null;
diff --git a/chrome/browser/resources/settings/privacy_sandbox/app.html b/chrome/browser/resources/settings/privacy_sandbox/app.html
index 0505890..bac75f2 100644
--- a/chrome/browser/resources/settings/privacy_sandbox/app.html
+++ b/chrome/browser/resources/settings/privacy_sandbox/app.html
@@ -83,38 +83,67 @@
     <img class="banner" alt=""
         src="chrome://settings/images/privacy_sandbox_banner.svg">
   </picture>
-  <div>
-    <h2 class="section-header" tabindex="-1">
-      $i18n{privacySandboxPageHeading}
-    </h2>
-  </div>
-  <p>$i18n{privacySandboxPageExplanation1}</p>
-  <p>$i18n{privacySandboxPageExplanation2}</p>
-  <p>$i18n{privacySandboxPageExplanation3}</p>
-  <p>$i18nRaw{privacySandboxPageExplanation4}</p>
+  <template is="dom-if" if="[[!privacySandboxSettings2Enabled_]]">
+    <div>
+      <h2 id="pageHeader" class="section-header" tabindex="-1">
+        $i18n{privacySandboxPageHeading}
+      </h2>
+    </div>
+    <p>$i18n{privacySandboxPageExplanation1}</p>
+    <p>$i18n{privacySandboxPageExplanation2}</p>
+    <p>$i18n{privacySandboxPageExplanation3}</p>
+    <p>$i18nRaw{privacySandboxPageExplanation4}</p>
+  </template>
+  <template is="dom-if" if="[[privacySandboxSettings2Enabled_]]">
+    <p>$i18nRaw{privacySandboxPageExplanation1Phase2}</p>
+    <p>$i18n{privacySandboxPageExplanation2Phase2}</p>
+  </template>
   <div class="card">
     <settings-toggle-button id="apiToggleButton" class="hr"
         pref="{{prefs.privacy_sandbox.apis_enabled}}"
         label="$i18n{privacySandboxPageSettingTitle}"
         on-change="onApiToggleButtonChange_">
     </settings-toggle-button>
-    <div class="cr-row continuation">
-      <div class="secondary">$i18n{privacySandboxPageSettingExplanation1}</div>
-    </div>
-    <div class="cr-row continuation">
-      <div class="secondary">
-        <ul>
-          <li><span>$i18n{privacySandboxPageSettingExplanation2}</span></li>
-          <li><span>$i18n{privacySandboxPageSettingExplanation3}</span></li>
-        </ul>
+    <template is="dom-if" if="[[!privacySandboxSettings2Enabled_]]">
+      <div id="phase1SettingExplanation" class="cr-row continuation">
+        <div class="secondary">
+          $i18n{privacySandboxPageSettingExplanation1}
+        </div>
       </div>
-    </div>
-    <div class="cr-row continuation box-last">
-      <cr-button class="cr-button" id="learnMoreButton" role="button"
-          tabindex="0" on-click="onLearnMoreButtonClick_">
-        $i18n{privacySandboxPageDetails}
-      </cr-button>
-    </div>
+      <div class="cr-row continuation">
+        <div class="secondary">
+          <ul>
+            <li><span>$i18n{privacySandboxPageSettingExplanation2}</span></li>
+            <li><span>$i18n{privacySandboxPageSettingExplanation3}</span></li>
+          </ul>
+        </div>
+      </div>
+      <div class="cr-row continuation box-last">
+        <cr-button class="cr-button" id="learnMoreButton" role="button"
+            tabindex="0" on-click="onLearnMoreButtonClick_">
+          $i18n{privacySandboxPageDetails}
+        </cr-button>
+      </div>
+    </template>
+    <template is="dom-if" if="[[privacySandboxSettings2Enabled_]]">
+      <div id="phase2SettingExplanation" class="cr-row continuation">
+        <div class="secondary">
+          $i18n{privacySandboxPageSettingExplanation1Phase2}
+        </div>
+      </div>
+      <div class="cr-row continuation">
+        <div class="secondary">
+          <ul>
+            <li>
+              <span>$i18n{privacySandboxPageSettingExplanation2Phase2}</span>
+            </li>
+            <li>
+              <span>$i18n{privacySandboxPageSettingExplanation3Phase2}</span>
+            </li>
+          </ul>
+        </div>
+      </div>
+    </template>
   </div>
   <template is="dom-if" if="[[privacySandboxSettings2Enabled_]]">
     <div id="flocCard" class="card">
@@ -128,7 +157,7 @@
           label="$i18n{privacySandboxPageFlocHeading}">
       </settings-toggle-button>
       <div class="cr-row continuation">
-        <div class="secondary">$i18n{privacySandboxPageFlocExplanation}</div>
+        <div class="secondary">$i18nRaw{privacySandboxPageFlocExplanation}</div>
       </div>
       <div class="cr-row continuation">
         <div class="label-wrapper">
diff --git a/chrome/browser/resources/welcome/shared/onboarding_background.html b/chrome/browser/resources/welcome/shared/onboarding_background.html
index 6b9f2f1f..7c8ba48b 100644
--- a/chrome/browser/resources/welcome/shared/onboarding_background.html
+++ b/chrome/browser/resources/welcome/shared/onboarding_background.html
@@ -135,45 +135,45 @@
   .dotted-line {
     -webkit-mask: url(../images/background_svgs/streamer_line.svg);
     -webkit-mask-position: 3px 0;
-    -webkit-mask-size: 153px 6px;
+    -webkit-mask-size: 206px 12px;
     animation: dotted-line 1s infinite linear;
     background-color: var(--welcome-grey);
-    height: 6px;
+    height: 12px;
     left: 50%;
     top: 50%;
     transform-origin: center left;
   }
 
   @keyframes dotted-line {
-    to { -webkit-mask-position: 23px 0; }
+    to { -webkit-mask-position: 29px 0; }
   }
 
   #dotted-line-1 {
     left: 1136px;
     top: 378px;
     transform: rotate(194deg);
-    width: 106px;
+    width: 126px;
   }
 
   #dotted-line-2 {
     left: 1493px;
     top: 271px;
     transform: rotate(-50deg);
-    width: 126px;
+    width: 146px;
   }
 
   #dotted-line-3 {
     left: 1545px;
     top: 525px;
     transform: rotate(25deg);
-    width: 100px;
+    width: 120px;
   }
 
   #dotted-line-4 {
     left: 1219px;
     top: 626px;
     transform: rotate(128deg);
-    width: 113px;
+    width: 133px;
   }
 
   .circle {
@@ -183,9 +183,8 @@
     width: 64px;
   }
 
-  .connectagon {
+  .connectagon-container {
     --connectagon-shape-size: 64.77px;
-
     display: block;
     height: var(--connectagon-shape-size);
     left: 50%;
@@ -193,6 +192,12 @@
     top: 50%;
   }
 
+  .connectagon {
+    height: 100%;
+    transform-origin: 50% 50%;
+    width: 100%;
+  }
+
   .connectagon .circle {
     background-color: var(--connectagon-shape-color);
     border-radius: 50%;
@@ -238,15 +243,26 @@
   #yellow-connectagon {
     --connectagon-shape-color: var(--welcome-yellow);
     --connectagon-connector-color: var(--welcome-yellow-tinted);
-    animation: yellow-connectagon 4s alternate infinite linear;
+    animation: yellow-connectagon-translate 4s alternate infinite linear;
     left: 549px;
     top: 156px;
     transform: translate(-50%, -50%) rotate(51deg);
   }
 
-  @keyframes yellow-connectagon {
+  @keyframes yellow-connectagon-translate {
     to {
-      transform: translate(calc(-50% - 62px), calc(-50% - 36px)) rotate(411deg);
+      transform: translate(calc(-50% - 36px), calc(-50% - 62px)) rotate(51deg);
+    }
+  }
+
+  #yellow-connectagon .connectagon {
+    animation: yellow-connectagon-rotate 4s infinite linear;
+    transform: rotate(0deg);
+  }
+
+  @keyframes yellow-connectagon-rotate {
+    to {
+      transform: rotate(360deg);
     }
   }
 
@@ -272,38 +288,44 @@
     animation: blue-connectagon 4s alternate infinite linear;
     left: 2157px;
     top: 249px;
-    transform: translate(-50%, -50%) rotate(152deg);
+    transform: translate(-50%, -50%);
   }
 
   @keyframes blue-connectagon {
     50% {
-      transform: translate(calc(-50% + 10px), calc(-50% + 20px)) rotate(152deg);
+      transform: translate(calc(-50% + 10px), calc(-50% + 30px));
     }
-
     100% {
-      transform: translate(calc(-50%), calc(-50% + 40px)) rotate(152deg);
+      transform: translate(calc(-50%), calc(-50% + 60px));
     }
   }
 
   #green-connectagon {
     --connectagon-shape-color: var(--welcome-green);
     --connectagon-connector-color: var(--welcome-green-tinted);
-    animation: green-connectagon 6s alternate infinite linear;
+    animation: green-connectagon-translate 6s alternate infinite linear;
     left: 851px;
     top: 766px;
     transform: translate(-50%, -50%) rotate(139deg);
   }
 
-  @keyframes green-connectagon {
-    50% {
-      transform: translate(calc(-50% + 40px), calc(-50% + 10px)) rotate(499deg);
-    }
-
+  @keyframes green-connectagon-translate {
     100% {
       transform: translate(calc(-50% + 80px), calc(-50% + 20px)) rotate(139deg);
     }
   }
 
+  #green-connectagon .connectagon {
+    animation: green-connectagon-rotate 12s infinite linear;
+    transform: rotate(0deg);
+  }
+
+  @keyframes green-connectagon-rotate {
+    100% {
+      transform: rotate(-360deg);
+    }
+  }
+
   #square {
     -webkit-mask: url(../images/background_svgs/square.svg);
     animation: square 4s infinite linear;
@@ -359,7 +381,7 @@
     -webkit-mask: url(../images/background_svgs/password.svg);
     -webkit-mask-position: top left;
     -webkit-mask-size: 400px 400px;
-    animation: password-field-input 4s steps(1) infinite;
+    animation: password-field-input 2s steps(1) infinite;
     background-color: var(--welcome-green);
     left: 604px;
     top: 78px;
@@ -459,20 +481,26 @@
     <div class="shape dotted-line" id="dotted-line-4"></div>
 
     <!-- Connectagons. -->
-    <div class="connectagon" id="yellow-connectagon">
-      <div class="circle"></div>
-      <div class="square"></div>
-      <div class="hexagon"></div>
+    <div class="connectagon-container" id="yellow-connectagon">
+      <div class="connectagon">
+        <div class="circle"></div>
+        <div class="square"></div>
+        <div class="hexagon"></div>
+      </div>
     </div>
-    <div class="connectagon" id="blue-connectagon">
-      <div class="hexagon"></div>
-      <div class="square"></div>
-      <div class="hexagon"></div>
+    <div class="connectagon-container" id="blue-connectagon">
+      <div class="connectagon">
+        <div class="hexagon"></div>
+        <div class="square"></div>
+        <div class="hexagon"></div>
+      </div>
     </div>
-    <div class="connectagon" id="green-connectagon">
-      <div class="circle"></div>
-      <div class="square"></div>
-      <div class="circle"></div>
+    <div class="connectagon-container" id="green-connectagon">
+      <div class="connectagon">
+        <div class="circle"></div>
+        <div class="square"></div>
+        <div class="circle"></div>
+      </div>
     </div>
 
     <!-- Colored shapes. -->
diff --git a/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc b/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
index 225acbcc..1a8577d 100644
--- a/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
+++ b/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
@@ -252,7 +252,6 @@
   // extraction and download ping are skipped.
   if (is_allowlisted) {
     DVLOG(2) << source_url_ << " is on the download allowlist.";
-    RecordCountOfAllowlistedDownload(URL_ALLOWLIST);
     if (ShouldSampleAllowlistedDownload()) {
       skipped_url_whitelist_ = true;
     } else {
@@ -373,7 +372,6 @@
     bool is_allowlisted) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!skipped_url_whitelist_ && is_allowlisted) {
-    RecordCountOfAllowlistedDownload(SIGNATURE_ALLOWLIST);
     if (ShouldSampleAllowlistedDownload()) {
       skipped_certificate_whitelist_ = true;
     } else {
@@ -385,8 +383,6 @@
     }
   }
 
-  RecordCountOfAllowlistedDownload(NO_ALLOWLIST_MATCH);
-
   if (!pingback_enabled_) {
     FinishRequest(DownloadCheckResult::UNKNOWN, REASON_PING_DISABLED);
     return;
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_util.cc b/chrome/browser/safe_browsing/download_protection/download_protection_util.cc
index df4b994a..c62dba5 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_util.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_util.cc
@@ -33,12 +33,6 @@
 
 }  // namespace
 
-void RecordCountOfAllowlistedDownload(AllowlistType type) {
-  // TODO(jkarlin): Rename to Allowlist.
-  UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckWhitelistResult", type,
-                            ALLOWLIST_TYPE_MAX);
-}
-
 void GetCertificateAllowlistStrings(
     const net::X509Certificate& certificate,
     const net::X509Certificate& issuer,
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_util.h b/chrome/browser/safe_browsing/download_protection/download_protection_util.h
index 7fb32a8..b2db86d 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_util.h
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_util.h
@@ -135,8 +135,6 @@
 using PPAPIDownloadRequestCallback =
     PPAPIDownloadRequestCallbackList::CallbackType;
 
-void RecordCountOfAllowlistedDownload(AllowlistType type);
-
 // Given a certificate and its immediate issuer certificate, generates the
 // list of strings that need to be checked against the download allowlist to
 // determine whether the certificate is allowlisted.
diff --git a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
index 469eca8..cde64a4 100644
--- a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
+++ b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
@@ -272,13 +272,7 @@
   CopyArchivedBinaries(archive_results.archived_binary,
                        &results_.archived_binaries);
 
-  // Log metrics for DMG analysis.
-  int64_t uma_file_type =
-      FileTypePolicies::GetInstance()->UmaValueForFile(target_path_);
-
   if (archive_results.success) {
-    base::UmaHistogramSparse("SBClientDownload.DmgFileSuccessByType",
-                             uma_file_type);
     results_.type = ClientDownloadRequest::MAC_EXECUTABLE;
   } else {
     results_.type = ClientDownloadRequest::MAC_ARCHIVE_FAILED_PARSING;
diff --git a/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc b/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
index fb573c8..ec91926 100644
--- a/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
+++ b/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
@@ -150,7 +150,6 @@
 void PPAPIDownloadRequest::AllowlistCheckComplete(bool was_on_allowlist) {
   DVLOG(2) << __func__ << " was_on_allowlist:" << was_on_allowlist;
   if (was_on_allowlist) {
-    RecordCountOfAllowlistedDownload(URL_ALLOWLIST);
     // TODO(asanka): Should sample allowlisted downloads based on
     // service_->allowlist_sample_rate(). http://crbug.com/610924
     Finish(RequestOutcome::ALLOWLIST_HIT, DownloadCheckResult::SAFE);
diff --git a/chrome/browser/share/BUILD.gn b/chrome/browser/share/BUILD.gn
index 40b2d89..f203366 100644
--- a/chrome/browser/share/BUILD.gn
+++ b/chrome/browser/share/BUILD.gn
@@ -13,12 +13,17 @@
     "link_to_text_bridge.cc",
     "qr_code_generation_request.cc",
     "qr_code_generation_request.h",
+    "share_history.cc",
+    "share_history.h",
   ]
   deps = [
     "//base",
+    "//chrome/browser/profiles:profile",
     "//chrome/browser/share/android:jni_headers",
+    "//chrome/browser/share/proto:proto",
     "//chrome/services/qrcode_generator/public/cpp",
     "//chrome/services/qrcode_generator/public/mojom",
+    "//components/leveldb_proto:leveldb_proto",
     "//components/shared_highlighting/core/common",
     "//content/public/browser",
     "//skia",
diff --git a/chrome/browser/share/proto/BUILD.gn b/chrome/browser/share/proto/BUILD.gn
new file mode 100644
index 0000000..c1982c31
--- /dev/null
+++ b/chrome/browser/share/proto/BUILD.gn
@@ -0,0 +1,9 @@
+# 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("//third_party/protobuf/proto_library.gni")
+
+proto_library("proto") {
+  sources = [ "share_history_message.proto" ]
+}
diff --git a/chrome/browser/share/proto/share_history_message.proto b/chrome/browser/share/proto/share_history_message.proto
new file mode 100644
index 0000000..188514f
--- /dev/null
+++ b/chrome/browser/share/proto/share_history_message.proto
@@ -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.
+
+syntax = "proto3";
+
+package sharing.mojom;
+
+option optimize_for = LITE_RUNTIME;
+
+message Target {
+  string component_name = 1;
+}
+
+message ShareHistory {
+  // There are at most WINDOW entries in this list, representing days within
+  // the last WINDOW days for which there is any history.
+  repeated DayShareHistory day_histories = 1;
+}
+
+message DayShareHistory {
+  // Which day this DayShareHistory is for. This is needed in case the app
+  // isn't run for >1 day - we can't always rely on the WINDOW entries
+  // being the most recent WINDOW days.
+  //
+  // This is stored as a number of days since the Unix epoch, and a day in this
+  // context runs from UTC midnight to UTC midnight.
+  int32 day = 1;
+
+  // All of the apps that were shared to on this day.
+  repeated TargetShareHistory target_histories = 2;
+}
+
+message TargetShareHistory {
+  // The actual target is identified by a component name.
+  Target target = 1;
+
+  // How many shares this target received.
+  int32 count = 2;
+}
diff --git a/chrome/browser/share/share_history.cc b/chrome/browser/share/share_history.cc
new file mode 100644
index 0000000..413fd5a
--- /dev/null
+++ b/chrome/browser/share/share_history.cc
@@ -0,0 +1,162 @@
+// 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/share/share_history.h"
+
+#include "base/containers/flat_map.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/time/time.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/share/proto/share_history_message.pb.h"
+#include "components/leveldb_proto/public/proto_database_provider.h"
+#include "content/public/browser/storage_partition.h"
+
+namespace sharing {
+
+namespace {
+
+const char* const kShareHistoryFolder = "share_history";
+static const char kShareHistoryKey[1] = "";
+
+int TodaysDay() {
+  return (base::Time::Now() - base::Time::UnixEpoch()).InDays();
+}
+
+std::unique_ptr<ShareHistory::BackingDb> MakeDefaultDbForProfile(
+    Profile* profile) {
+  return profile->GetDefaultStoragePartition()
+      ->GetProtoDatabaseProvider()
+      ->GetDB<mojom::ShareHistory>(
+          leveldb_proto::ProtoDbType::SHARE_HISTORY_DATABASE,
+          profile->GetPath().Append(kShareHistoryFolder),
+          base::ThreadPool::CreateSequencedTaskRunner(
+              {base::MayBlock(), base::TaskPriority::BEST_EFFORT}));
+}
+
+}  // namespace
+
+// static
+void ShareHistory::CreateForProfile(Profile* profile) {
+  auto instance = std::make_unique<ShareHistory>(profile);
+  profile->SetUserData(kShareHistoryKey, base::WrapUnique(instance.release()));
+}
+
+// static
+ShareHistory* ShareHistory::Get(Profile* profile) {
+  base::SupportsUserData::Data* instance =
+      profile->GetUserData(kShareHistoryKey);
+  DCHECK(instance);
+  return static_cast<ShareHistory*>(instance);
+}
+
+ShareHistory::~ShareHistory() = default;
+
+ShareHistory::ShareHistory(Profile* profile,
+                           std::unique_ptr<BackingDb> backing_db)
+    : db_(backing_db ? std::move(backing_db)
+                     : MakeDefaultDbForProfile(profile)) {
+  Init();
+}
+
+void ShareHistory::AddShareEntry(const std::string& component_name) {
+  if (!init_finished_) {
+    post_init_callbacks_.AddUnsafe(base::BindOnce(
+        &ShareHistory::AddShareEntry, base::Unretained(this), component_name));
+    return;
+  }
+
+  if (db_init_status_ != leveldb_proto::Enums::kOK)
+    return;
+
+  mojom::DayShareHistory* day_history = DayShareHistoryForToday();
+  mojom::TargetShareHistory* target_history =
+      TargetShareHistoryByName(day_history, component_name);
+
+  target_history->set_count(target_history->count() + 1);
+
+  // TODO(ellyjones): Start a write back to the backing database. Once that's
+  // done, un-disable the AddsWrittenToBackingDb test.
+}
+
+void ShareHistory::GetFlatShareHistory(GetFlatHistoryCallback callback,
+                                       int window) {
+  if (!init_finished_) {
+    post_init_callbacks_.AddUnsafe(base::BindOnce(
+        &ShareHistory::GetFlatShareHistory, weak_factory_.GetWeakPtr(),
+        std::move(callback), window));
+    return;
+  }
+
+  if (db_init_status_ != leveldb_proto::Enums::kOK) {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), std::vector<Target>()));
+    return;
+  }
+
+  base::flat_map<std::string, int> counts;
+  int today = TodaysDay();
+  for (const auto& day : history_.day_histories()) {
+    if (window != -1 && today - day.day() > window)
+      continue;
+    for (const auto& target : day.target_histories()) {
+      counts[target.target().component_name()] += target.count();
+    }
+  }
+
+  std::vector<Target> result;
+  for (const auto& count : counts) {
+    Target t;
+    t.component_name = count.first;
+    t.count = count.second;
+    result.push_back(t);
+  }
+
+  std::sort(result.begin(), result.end(),
+            [](const Target& a, const Target& b) { return a.count > b.count; });
+
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), result));
+}
+
+void ShareHistory::Init() {
+  db_->Init(
+      base::BindOnce(&ShareHistory::OnInitDone, weak_factory_.GetWeakPtr()));
+}
+
+void ShareHistory::OnInitDone(leveldb_proto::Enums::InitStatus status) {
+  init_finished_ = true;
+  db_init_status_ = status;
+  post_init_callbacks_.Notify();
+
+  // TODO(ellyjones): Expire entries older than WINDOW days.
+}
+
+mojom::DayShareHistory* ShareHistory::DayShareHistoryForToday() {
+  int today = TodaysDay();
+  for (auto& day : *history_.mutable_day_histories()) {
+    if (day.day() == today)
+      return &day;
+  }
+
+  mojom::DayShareHistory* day = history_.mutable_day_histories()->Add();
+  day->set_day(today);
+  return day;
+}
+
+mojom::TargetShareHistory* ShareHistory::TargetShareHistoryByName(
+    mojom::DayShareHistory* history,
+    const std::string& target_name) {
+  for (auto& target : *history->mutable_target_histories()) {
+    if (target.target().component_name() == target_name)
+      return &target;
+  }
+
+  mojom::TargetShareHistory* target =
+      history->mutable_target_histories()->Add();
+  target->mutable_target()->set_component_name(target_name);
+  return target;
+}
+
+}  // namespace sharing
diff --git a/chrome/browser/share/share_history.h b/chrome/browser/share/share_history.h
new file mode 100644
index 0000000..d641c23
--- /dev/null
+++ b/chrome/browser/share/share_history.h
@@ -0,0 +1,81 @@
+// 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_SHARE_SHARE_HISTORY_H_
+#define CHROME_BROWSER_SHARE_SHARE_HISTORY_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_list.h"
+#include "base/memory/weak_ptr.h"
+#include "base/supports_user_data.h"
+#include "chrome/browser/share/proto/share_history_message.pb.h"
+#include "components/leveldb_proto/public/proto_database.h"
+
+class Profile;
+
+namespace sharing {
+
+// A ShareHistory instance stores and provides access to the history of which
+// targets the user has shared to. This history is stored per-profile and
+// cleared when the user clears browsing data.
+class ShareHistory : public base::SupportsUserData::Data {
+ public:
+  struct Target {
+    std::string component_name;
+    int count;
+  };
+
+  using BackingDb = leveldb_proto::ProtoDatabase<mojom::ShareHistory>;
+  using GetFlatHistoryCallback = base::OnceCallback<void(std::vector<Target>)>;
+
+  static void CreateForProfile(Profile* profile);
+  static ShareHistory* Get(Profile* profile);
+
+  explicit ShareHistory(Profile* profile,
+                        std::unique_ptr<BackingDb> backing_db = nullptr);
+  ~ShareHistory() override;
+
+  void AddShareEntry(const std::string& component_name);
+
+  // Returns the flattened share history. Each entry in this list contains
+  // the total count of shares the corresponding target has received over
+  // the past |window| days. It is required that |window| <= the backend's
+  // WINDOW value. A window of -1 means all available history.
+  void GetFlatShareHistory(GetFlatHistoryCallback callback, int window = -1);
+
+ private:
+  void Init();
+  void OnInitDone(leveldb_proto::Enums::InitStatus status);
+
+  // These two methods get or create entries in the in-memory protobuf; they do
+  // not actually write back to the DB.
+  mojom::DayShareHistory* DayShareHistoryForToday();
+  mojom::TargetShareHistory* TargetShareHistoryByName(
+      mojom::DayShareHistory* history,
+      const std::string& target_name);
+
+  bool init_finished_ = false;
+  leveldb_proto::Enums::InitStatus db_init_status_;
+
+  base::OnceClosureList post_init_callbacks_;
+
+  std::unique_ptr<BackingDb> db_;
+
+  // Cached copy of the data that resides on disk; initialization is complete
+  // when this has first been loaded. After that point, any changes are synced
+  // back to disk asynchronously, but the in-memory version is authoritative.
+  mojom::ShareHistory history_;
+
+  // Used so that callbacks supplied to the backing LevelDB won't call us back
+  // if the Profile we're attached to is destroyed while we have a pending
+  // callback. This *shouldn't* happen, since the LevelDB is *usually* attached
+  // to the Profile too, but it seems like it might be possible anyway.
+  base::WeakPtrFactory<ShareHistory> weak_factory_{this};
+};
+
+}  // namespace sharing
+
+#endif  // CHROME_BROWSER_SHARE_SHARE_HISTORY_H_
diff --git a/chrome/browser/share/share_history_unittest.cc b/chrome/browser/share/share_history_unittest.cc
new file mode 100644
index 0000000..909984d2
--- /dev/null
+++ b/chrome/browser/share/share_history_unittest.cc
@@ -0,0 +1,125 @@
+// 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/share/share_history.h"
+#include "base/cancelable_callback.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/leveldb_proto/testing/fake_db.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace sharing {
+
+// Fixture for tests that test the behavior of ShareHistory. These tests use a
+// fake LevelDB instance so that they don't need to touch disk and have tight
+// control over when callbacks are delivered, etc. These tests are deliberately
+// as close as possible to the real async behavior of the production code, so
+// database init happens asynchronously and so on.
+class ShareHistoryTest : public testing::Test {
+ public:
+  using FakeDB = leveldb_proto::test::FakeDB<mojom::ShareHistory>;
+
+  ShareHistoryTest() {
+    auto backing_db = std::make_unique<FakeDB>(&backing_entries_);
+    backing_db_ = backing_db.get();
+
+    db_ = std::make_unique<ShareHistory>(
+        &profile_, base::WrapUnique(backing_db.release()));
+
+    backing_init_callback_.Reset(base::BindOnce(
+        [](ShareHistoryTest* test) {
+          test->backing_db_->InitStatusCallback(
+              leveldb_proto::Enums::InitStatus::kOK);
+        },
+        base::Unretained(this)));
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, backing_init_callback_.callback());
+  }
+
+  // By default, ShareHistoryTest runs start with a posted task that simulates
+  // database initialization finishing successfully. Tests that need to fake
+  // an init failure, or otherwise have tighter control over when init appears
+  // to complete, can use this method to cancel the default init.
+  void CancelDefaultInit() { backing_init_callback_.Cancel(); }
+
+  // This method is a synchronous wrapper for GetFlatShareHistory() for test
+  // ergonomics. Note that if the test has cancelled default init and not posted
+  // its own init, this can deadlock!
+  std::vector<ShareHistory::Target> GetFlatShareHistory(int window = -1) {
+    base::RunLoop loop;
+    std::vector<ShareHistory::Target> out_result;
+    db()->GetFlatShareHistory(
+        base::BindLambdaForTesting(
+            [&](std::vector<ShareHistory::Target> result) {
+              out_result.swap(result);
+              loop.Quit();
+            }),
+        window);
+    loop.Run();
+    return out_result;
+  }
+
+  ShareHistory* db() { return db_.get(); }
+
+  leveldb_proto::test::FakeDB<mojom::ShareHistory>* backing_db() {
+    return backing_db_;
+  }
+
+  leveldb_proto::test::FakeDB<mojom::ShareHistory>::EntryMap*
+  backing_entries() {
+    return &backing_entries_;
+  }
+
+ private:
+  content::BrowserTaskEnvironment environment_{
+      // This set of tests must use a mock time source. If they don't, and a
+      // test
+      // happens to run across UTC midnight, the day can change mid-test,
+      // causing
+      // surprising results.
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  TestingProfile profile_;
+  std::unique_ptr<ShareHistory> db_;
+  leveldb_proto::test::FakeDB<mojom::ShareHistory>::EntryMap backing_entries_;
+  leveldb_proto::test::FakeDB<mojom::ShareHistory>* backing_db_ = nullptr;
+  base::CancelableOnceClosure backing_init_callback_;
+};
+
+TEST_F(ShareHistoryTest, CreateAndInitializeEmpty) {
+  auto result = GetFlatShareHistory();
+  EXPECT_EQ(result.size(), 0U);
+}
+
+TEST_F(ShareHistoryTest, AddInMemory) {
+  db()->AddShareEntry("foo");
+  db()->AddShareEntry("bar");
+  db()->AddShareEntry("foo");
+
+  auto result = GetFlatShareHistory();
+  EXPECT_EQ(result.size(), 2U);
+  EXPECT_EQ(result[0].component_name, "foo");
+  EXPECT_EQ(result[0].count, 2);
+  EXPECT_EQ(result[1].component_name, "bar");
+  EXPECT_EQ(result[1].count, 1);
+}
+
+// TODO(ellyjones): Writes to the backing leveldb are not yet implemented.
+TEST_F(ShareHistoryTest, DISABLED_AddsWrittenToBackingDb) {
+  db()->AddShareEntry("foo");
+  db()->AddShareEntry("bar");
+  db()->AddShareEntry("foo");
+}
+
+// TODO(ellyjones): reads from the backing leveldb is not yet implemented.
+TEST_F(ShareHistoryTest, DISABLED_BackingDbIsLoaded) {
+  // backing_entries_[key] = MakeAProto();
+  auto result = GetFlatShareHistory();
+
+  EXPECT_EQ(result.size(), 2U);
+  // ...
+}
+
+}  // namespace sharing
diff --git a/chrome/browser/shell_integration.cc b/chrome/browser/shell_integration.cc
index 025b423d..aa6b876 100644
--- a/chrome/browser/shell_integration.cc
+++ b/chrome/browser/shell_integration.cc
@@ -18,7 +18,6 @@
 #include "base/task/single_thread_task_runner_thread_mode.h"
 #include "base/task/task_traits.h"
 #include "base/threading/scoped_blocking_call.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/policy/policy_path_parser.h"
@@ -90,27 +89,6 @@
 }
 
 #if !defined(OS_WIN)
-void AddAppProtocolClients(const AppProtocolMap& app_protocols,
-                           const base::FilePath& profile_path,
-                           AppProtocolWorkerCallback protocol_worker_callback) {
-  base::SequencedTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(std::move(protocol_worker_callback), /*success=*/false));
-}
-
-void RemoveAppProtocolClients(const std::vector<std::string>& protocols,
-                              const base::FilePath& profile_path) {}
-
-void CheckAppIsProtocolClient(
-    const std::string& app_id,
-    const std::string& protocol,
-    const base::FilePath& profile_path,
-    AppProtocolWorkerCallback protocol_worker_callback) {
-  base::SequencedTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(std::move(protocol_worker_callback), /*success=*/false));
-}
-
 bool IsElevationNeededForSettingDefaultProtocolClient() {
   return false;
 }
diff --git a/chrome/browser/shell_integration.h b/chrome/browser/shell_integration.h
index 3413f93..d19b601 100644
--- a/chrome/browser/shell_integration.h
+++ b/chrome/browser/shell_integration.h
@@ -34,34 +34,6 @@
 // all OSs.
 bool SetAsDefaultProtocolClient(const std::string& protocol);
 
-// Maps protocols to handler app ids. A protocol with no app id (absl::nullopt)
-// will be handled by the browser which is useful for app protocols requiring
-// disambiguation.
-using AppProtocolMap = std::map<std::string, absl::optional<std::string>>;
-
-// Called with the outcome of an asynchronous app protocol operation.
-using AppProtocolWorkerCallback = base::OnceCallback<void(bool)>;
-
-// Registers the browser as the handler for all URL protocols in
-// `app_protocols`. Protocols with a corresponding handler app id will be
-// registered to launch that app. `protocol_worker_callback` will be run on the
-// caller's sequence to report the results.
-void AddAppProtocolClients(const AppProtocolMap& app_protocols,
-                           const base::FilePath& profile_path,
-                           AppProtocolWorkerCallback protocol_worker_callback);
-
-// Removes each protocol and its registered web app handler from the OS.
-void RemoveAppProtocolClients(const std::vector<std::string>& protocols,
-                              const base::FilePath& profile_path);
-
-// Determines if web app with `app_id` is the default client application for the
-// given protocol.
-void CheckAppIsProtocolClient(
-    const std::string& app_id,
-    const std::string& protocol,
-    const base::FilePath& profile_path,
-    AppProtocolWorkerCallback protocol_worker_callback);
-
 // The different types of permissions required to set a default web client.
 enum DefaultWebClientSetPermission {
   // The browser distribution is not permitted to be made default.
diff --git a/chrome/browser/shell_integration_win.cc b/chrome/browser/shell_integration_win.cc
index 39e294a..66c6452 100644
--- a/chrome/browser/shell_integration_win.cc
+++ b/chrome/browser/shell_integration_win.cc
@@ -44,7 +44,6 @@
 #include "base/win/windows_version.h"
 #include "chrome/browser/policy/policy_path_parser.h"
 #include "chrome/browser/shell_integration.h"
-#include "chrome/browser/web_applications/components/web_app_handler_registration_utils_win.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/browser/web_applications/components/web_app_shortcut_win.h"
 #include "chrome/browser/win/settings_app_monitor.h"
@@ -579,98 +578,6 @@
   return true;
 }
 
-void AddAppProtocolClients(const AppProtocolMap& app_protocols,
-                           const base::FilePath& profile_path,
-                           AppProtocolWorkerCallback protocol_worker_callback) {
-  auto set_app_as_protocol_client_task = base::BindOnce(
-      [](const AppProtocolMap& app_protocols,
-         const base::FilePath& profile_path) {
-        base::FilePath chrome_exe;
-        if (!base::PathService::Get(base::FILE_EXE, &chrome_exe))
-          return false;
-
-        std::vector<std::pair<std::wstring, std::wstring>>
-            protocol_association_pairs;
-        protocol_association_pairs.reserve(app_protocols.size());
-
-        for (const auto& protocol_pair : app_protocols) {
-          std::wstring protocol = base::UTF8ToWide(protocol_pair.first);
-          const absl::optional<std::string>& app_id = protocol_pair.second;
-          // A protocol with no app id (absl::nullopt) will be handled by the
-          // browser for disambiguation.
-          std::wstring handler_progid =
-              app_id.has_value()
-                  ? web_app::GetProgIdForApp(profile_path, app_id.value())
-                  : ShellUtil::GetProgIdForBrowser(chrome_exe);
-
-          protocol_association_pairs.emplace_back(protocol, handler_progid);
-        }
-
-        ShellUtil::ProtocolAssociations protocol_associations(
-            std::move(protocol_association_pairs));
-        return ShellUtil::AddAppProtocolAssociations(protocol_associations,
-                                                     chrome_exe);
-      },
-      app_protocols, profile_path);
-
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::MayBlock()}, std::move(set_app_as_protocol_client_task),
-      std::move(protocol_worker_callback));
-}
-
-void RemoveAppProtocolClients(const std::vector<std::string>& protocols,
-                              const base::FilePath& profile_path) {
-  auto delete_protocol_registration = base::BindOnce(
-      [](const std::vector<std::string>& protocols,
-         const base::FilePath& profile_path) {
-        // TODO(http://crbug.com/1019239): RemoveAppProtocolClients will remove
-        // all registrations across all profiles. profile_path will be used once
-        // multi-profile support is added.
-        base::FilePath chrome_exe;
-        if (!base::PathService::Get(base::FILE_EXE, &chrome_exe)) {
-          return;
-        }
-
-        std::vector<std::wstring> wstring_protocols;
-        wstring_protocols.reserve(protocols.size());
-
-        for (const auto& protocol : protocols) {
-          wstring_protocols.push_back(base::UTF8ToWide(protocol));
-        }
-
-        ShellUtil::RemoveAppProtocolAssociations(wstring_protocols, chrome_exe,
-                                                 /*elevate_if_not_admin=*/true);
-      },
-      protocols, profile_path);
-
-  base::ThreadPool::PostTask(FROM_HERE, {base::MayBlock()},
-                             std::move(delete_protocol_registration));
-}
-
-void CheckAppIsProtocolClient(
-    const std::string& app_id,
-    const std::string& protocol,
-    const base::FilePath& profile_path,
-    AppProtocolWorkerCallback protocol_worker_callback) {
-  auto check_app_is_protocol_client_task = base::BindOnce(
-      [](const std::string& app_id, const std::string& protocol,
-         const base::FilePath& profile_path) {
-        std::wstring prog_id = web_app::GetProgIdForApp(profile_path, app_id);
-        base::FilePath chrome_exe;
-        if (!base::PathService::Get(base::FILE_EXE, &chrome_exe)) {
-          return false;
-        }
-        return ShellUtil::DoesAppProtocolAssociationExist(
-            base::UTF8ToWide(protocol), prog_id, chrome_exe);
-      },
-      app_id, protocol, profile_path);
-
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::MayBlock()},
-      std::move(check_app_is_protocol_client_task),
-      std::move(protocol_worker_callback));
-}
-
 DefaultWebClientSetPermission GetDefaultWebClientSetPermission() {
   if (!install_static::SupportsSetAsDefaultBrowser())
     return SET_DEFAULT_NOT_ALLOWED;
diff --git a/chrome/browser/sync/test/integration/sync_app_helper.cc b/chrome/browser/sync/test/integration/sync_app_helper.cc
index 458de72..4c271455 100644
--- a/chrome/browser/sync/test/integration/sync_app_helper.cc
+++ b/chrome/browser/sync/test/integration/sync_app_helper.cc
@@ -126,12 +126,11 @@
           ->extension_service()
           ->pending_extension_manager();
 
-  std::list<std::string> pending_crx_ids;
-  pending_extension_manager->GetPendingIdsForUpdateCheck(&pending_crx_ids);
+  std::list<std::string> pending_crx_ids =
+      pending_extension_manager->GetPendingIdsForUpdateCheck();
 
-  for (std::list<std::string>::const_iterator id = pending_crx_ids.begin();
-       id != pending_crx_ids.end(); ++id) {
-    LoadApp(profile, *id, &(app_state_map[*id]));
+  for (const auto& id : pending_crx_ids) {
+    LoadApp(profile, id, &(app_state_map[id]));
   }
 
   return app_state_map;
diff --git a/chrome/browser/sync/test/integration/sync_extension_helper.cc b/chrome/browser/sync/test/integration/sync_extension_helper.cc
index 6e5567ee..cd05200 100644
--- a/chrome/browser/sync/test/integration/sync_extension_helper.cc
+++ b/chrome/browser/sync/test/integration/sync_extension_helper.cc
@@ -187,8 +187,8 @@
           ->extension_service()
           ->pending_extension_manager();
 
-  std::list<std::string> pending_crx_ids;
-  pending_extension_manager->GetPendingIdsForUpdateCheck(&pending_crx_ids);
+  std::list<std::string> pending_crx_ids =
+      pending_extension_manager->GetPendingIdsForUpdateCheck();
 
   std::list<std::string>::const_iterator iter;
   const extensions::PendingExtensionInfo* info = nullptr;
@@ -244,8 +244,8 @@
   const extensions::PendingExtensionManager* pending_extension_manager =
       extension_service->pending_extension_manager();
 
-  std::list<std::string> pending_crx_ids;
-  pending_extension_manager->GetPendingIdsForUpdateCheck(&pending_crx_ids);
+  std::list<std::string> pending_crx_ids =
+      pending_extension_manager->GetPendingIdsForUpdateCheck();
 
   for (const std::string& id : pending_crx_ids) {
     ExtensionState& extension_state = extension_state_map[id];
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 5f2223b5..c87a2b6 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -2881,8 +2881,8 @@
       </message>
       <message name="IDS_NTP_FOR_YOU" desc="Title in the feed header for interest-based feed.">For you</message>
       <message name="IDS_NTP_FOLLOWING" desc="Title in the feed header for user-customized following feed.">Following</message>
-      <message name="IDS_ACCESSIBILITY_NTP_FOLLOWING_UNREAD_CONTENT" desc="Content description for the feed header for user-customized following feed, when unread content is available.">
-      Following, unread stories ready
+      <message name="IDS_ACCESSIBILITY_NTP_FOLLOWING_UNREAD_CONTENT" desc="Content description for the feed header blue dot for user-customized following feed, which indicates that unread content is available.">
+      Unread stories ready
       </message>
       <message name="IDS_NTP_FEED_MENU_IPH" desc="In-product help that points at the menu icon for the news feed on Chrome's new tab page. This string instructs the user to open the menu for settings that let them control the content that appears on the feed.">
         Control your stories and activity here
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_NTP_FOLLOWING_UNREAD_CONTENT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_NTP_FOLLOWING_UNREAD_CONTENT.png.sha1
index 3d0fc06..ebdda47 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_NTP_FOLLOWING_UNREAD_CONTENT.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_NTP_FOLLOWING_UNREAD_CONTENT.png.sha1
@@ -1 +1 @@
-bb5d584ad464b475dc68cb7ddc00b9f92e9a3932
\ No newline at end of file
+203cf01f5805d89258ba434b4f6ceaa9828f9fca
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java
index e71b3f4..d26638c9 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java
@@ -54,6 +54,15 @@
     }
 
     /**
+     * Returns {@code true} if the adaptive button customization is enabled. Requires native
+     * libraries.
+     */
+    public static boolean isCustomizationEnabled() {
+        return ChromeFeatureList.isEnabled(
+                ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION);
+    }
+
+    /**
      * When the adaptive toolbar is configured in a single button variant mode, returns the {@link
      * AdaptiveToolbarButtonVariant} being used. Returns {@link
      * AdaptiveToolbarButtonVariant#UNKNOWN} otherwise.
diff --git a/chrome/browser/ui/ash/clipboard_image_model_request.cc b/chrome/browser/ui/ash/clipboard_image_model_request.cc
index b764d72..60803ba 100644
--- a/chrome/browser/ui/ash/clipboard_image_model_request.cc
+++ b/chrome/browser/ui/ash/clipboard_image_model_request.cc
@@ -145,7 +145,7 @@
       "<!DOCTYPE html>"
       "<html>"
       " <head><meta charset=\"UTF-8\"></meta></head>"
-      " <body contenteditable='true'> "
+      " <body contenteditable='true' style=\"overflow: hidden\"> "
       "  <script>"
       // Focus the Contenteditable body to ensure WebContents::Paste() reaches
       // the body.
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc
index baeed93..64e90eb7 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "components/download/public/common/download_item.h"
 #include "content/public/browser/browser_context.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
 
@@ -20,6 +21,12 @@
 
 // Helpers ---------------------------------------------------------------------
 
+// Returns whether the specified `download_item` is complete.
+bool IsComplete(download::DownloadItem* download_item) {
+  return download_item->GetState() ==
+         download::DownloadItem::DownloadState::COMPLETE;
+}
+
 // Returns whether the specified `download_item` is in progress.
 bool IsInProgress(download::DownloadItem* download_item) {
   return download_item->GetState() ==
@@ -30,7 +37,8 @@
 
 // HoldingSpaceDownloadsDelegate::InProgressDownload ---------------------------
 
-// A class which observes an in-progress `download::DownloadItem`.
+// A wrapper around an in-progress `download::DownloadItem` which notifies its
+// associated delegate of changes in download state.
 // NOTE: Instances of this class are immediately destroyed when the underlying
 // `download::DownloadItem` is no longer in-progress.
 class HoldingSpaceDownloadsDelegate::InProgressDownload
@@ -38,7 +46,7 @@
  public:
   InProgressDownload(HoldingSpaceDownloadsDelegate* delegate,
                      download::DownloadItem* download_item)
-      : delegate_(delegate) {
+      : delegate_(delegate), download_item_(download_item) {
     DCHECK(IsInProgress(download_item));
     download_item_observation_.Observe(download_item);
   }
@@ -47,20 +55,57 @@
   InProgressDownload& operator=(const InProgressDownload&) = delete;
   ~InProgressDownload() override = default;
 
+  // Returns the file path associated with the underlying `download_item_`.
+  // NOTE: The file path may be empty before a target file path has been picked.
+  const base::FilePath& GetFilePath() const {
+    return download_item_->GetFullPath();
+  }
+
+  // Returns the current progress of the underlying `download_item_`.
+  // NOTE: If present, the progress is >= `0.f` and <= `1.f`. If absent, the
+  // progress is indeterminate.
+  absl::optional<float> GetProgress() const {
+    if (IsComplete(download_item_))
+      return 1.f;
+
+    absl::optional<float> progress;
+    if (download_item_->PercentComplete() >= 0) {
+      DCHECK_GE(download_item_->PercentComplete(), 0);
+      DCHECK_LE(download_item_->PercentComplete(), 100);
+      progress = download_item_->PercentComplete() / 100.f;
+    }
+    return progress;
+  }
+
  private:
   // download::DownloadItem::Observer:
   void OnDownloadUpdated(download::DownloadItem* download_item) override {
-    // NOTE: This method invocation may result in destruction.
-    delegate_->OnDownloadUpdated(download_item);
+    switch (download_item->GetState()) {
+      case download::DownloadItem::DownloadState::IN_PROGRESS:
+        delegate_->OnDownloadUpdated(this);
+        break;
+      case download::DownloadItem::DownloadState::COMPLETE:
+        // NOTE: This method invocation will result in destruction.
+        delegate_->OnDownloadCompleted(this);
+        break;
+      case download::DownloadItem::DownloadState::CANCELLED:
+      case download::DownloadItem::DownloadState::INTERRUPTED:
+        // NOTE: This method invocation will result in destruction.
+        delegate_->OnDownloadFailed(this);
+        break;
+      case download::DownloadItem::DownloadState::MAX_DOWNLOAD_STATE:
+        NOTREACHED();
+        break;
+    }
   }
 
   void OnDownloadDestroyed(download::DownloadItem* download_item) override {
     // NOTE: This method invocation will result in destruction.
-    delegate_->OnDownloadDestroyed(download_item);
+    delegate_->OnDownloadFailed(this);
   }
 
-  // NOTE: The `delegate_` owns `this`.
-  HoldingSpaceDownloadsDelegate* const delegate_;
+  HoldingSpaceDownloadsDelegate* const delegate_;  // NOTE: Owns `this`.
+  download::DownloadItem* const download_item_;
 
   base::ScopedObservation<download::DownloadItem,
                           download::DownloadItem::Observer>
@@ -132,7 +177,7 @@
     return;
   }
 
-  OnDownloadCompleted(HoldingSpaceItem::Type::kArcDownload, path);
+  service()->AddDownload(HoldingSpaceItem::Type::kArcDownload, path);
 }
 
 void HoldingSpaceDownloadsDelegate::OnManagerInitialized() {
@@ -150,10 +195,8 @@
 
   for (download::DownloadItem* download_item : downloads) {
     if (IsInProgress(download_item)) {
-      in_progress_downloads_by_id_.emplace(
-          std::piecewise_construct,
-          /*id=*/std::forward_as_tuple(download_item->GetId()),
-          /*in_progress_download=*/std::forward_as_tuple(this, download_item));
+      in_progress_downloads_.emplace(
+          std::make_unique<InProgressDownload>(this, download_item));
     }
   }
 }
@@ -161,7 +204,7 @@
 void HoldingSpaceDownloadsDelegate::ManagerGoingDown(
     content::DownloadManager* manager) {
   download_manager_observation_.Reset();
-  in_progress_downloads_by_id_.clear();
+  in_progress_downloads_.clear();
 }
 
 void HoldingSpaceDownloadsDelegate::OnDownloadCreated(
@@ -173,44 +216,33 @@
     return;
 
   if (IsInProgress(download_item)) {
-    in_progress_downloads_by_id_.emplace(
-        std::piecewise_construct,
-        /*id=*/std::forward_as_tuple(download_item->GetId()),
-        /*in_progress_download=*/std::forward_as_tuple(this, download_item));
+    in_progress_downloads_.emplace(
+        std::make_unique<InProgressDownload>(this, download_item));
   }
 }
 
+// TODO(crbug.com/1184438): Support in-progress downloads.
 void HoldingSpaceDownloadsDelegate::OnDownloadUpdated(
-    const download::DownloadItem* download_item) {
-  switch (download_item->GetState()) {
-    case download::DownloadItem::DownloadState::IN_PROGRESS:
-      // TODO(crbug.com/1184438): Support in-progress downloads.
-      break;
-    case download::DownloadItem::DownloadState::COMPLETE:
-      OnDownloadCompleted(HoldingSpaceItem::Type::kDownload,
-                          download_item->GetFullPath());
-      FALLTHROUGH;
-    case download::DownloadItem::DownloadState::CANCELLED:
-    case download::DownloadItem::DownloadState::INTERRUPTED:
-      in_progress_downloads_by_id_.erase(download_item->GetId());
-      break;
-    case download::DownloadItem::DownloadState::MAX_DOWNLOAD_STATE:
-      NOTREACHED();
-      break;
-  }
-}
-
-void HoldingSpaceDownloadsDelegate::OnDownloadDestroyed(
-    const download::DownloadItem* download_item) {
-  in_progress_downloads_by_id_.erase(download_item->GetId());
-}
+    const InProgressDownload* in_progress_download) {}
 
 void HoldingSpaceDownloadsDelegate::OnDownloadCompleted(
-    HoldingSpaceItem::Type type,
-    const base::FilePath& file_path) {
-  DCHECK(HoldingSpaceItem::IsDownload(type));
-  if (!is_restoring_persistence())
-    service()->AddDownload(type, file_path, /*progress=*/1.f);
+    const InProgressDownload* in_progress_download) {
+  service()->AddDownload(HoldingSpaceItem::Type::kDownload,
+                         in_progress_download->GetFilePath(),
+                         in_progress_download->GetProgress());
+  EraseDownload(in_progress_download);
+}
+
+void HoldingSpaceDownloadsDelegate::OnDownloadFailed(
+    const InProgressDownload* in_progress_download) {
+  EraseDownload(in_progress_download);
+}
+
+void HoldingSpaceDownloadsDelegate::EraseDownload(
+    const InProgressDownload* in_progress_download) {
+  auto it = in_progress_downloads_.find(in_progress_download);
+  DCHECK(it != in_progress_downloads_.end());
+  in_progress_downloads_.erase(it);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h
index 6c99736..633b731 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h
@@ -5,8 +5,10 @@
 #ifndef CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_DOWNLOADS_DELEGATE_H_
 #define CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_DOWNLOADS_DELEGATE_H_
 
-#include <map>
+#include <memory>
+#include <set>
 
+#include "base/containers/unique_ptr_adapters.h"
 #include "base/scoped_observation.h"
 #include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
@@ -55,23 +57,23 @@
   void OnDownloadCreated(content::DownloadManager* manager,
                          download::DownloadItem* download_item) override;
 
-  // Invoked when the specified `download_item` is updated.
-  void OnDownloadUpdated(const download::DownloadItem* download_item);
+  // Invoked when the specified `in_progress_download` is updated.
+  void OnDownloadUpdated(const InProgressDownload* in_progress_download);
 
-  // Invoked when the specified `download_item` is destroyed.
-  void OnDownloadDestroyed(const download::DownloadItem* download_item);
+  // Invoked when the specified `in_progress_download` is completed.
+  void OnDownloadCompleted(const InProgressDownload* in_progress_download);
 
-  // Invoked when a download of the specified `type` at the specified
-  // `file_path` has completed downloading. Note that the specified `type` must
-  // be a download type.
-  void OnDownloadCompleted(HoldingSpaceItem::Type type,
-                           const base::FilePath& file_path);
+  // Invoked when the specified `in_progress_download` fails. This may be due to
+  // cancellation, interruption, or destruction of the underlying download.
+  void OnDownloadFailed(const InProgressDownload* in_progress_download);
 
-  // The collection of in-progress downloads mapped by download ID. In the near
-  // future, each `InProgressDownload` will be associated with an in-progress
-  // holding space item and may be paused/resumed/cancelled/etc. in response to
-  // user interaction.
-  std::map<uint32_t, InProgressDownload> in_progress_downloads_by_id_;
+  // Invoked to erase the specified `in_progress_download` when it is no longer
+  // needed either due to completion or failure of the underlying download.
+  void EraseDownload(const InProgressDownload* in_progress_download);
+
+  // The collection of currently in-progress downloads.
+  std::set<std::unique_ptr<InProgressDownload>, base::UniquePtrComparator>
+      in_progress_downloads_;
 
   base::ScopedObservation<arc::ArcIntentHelperBridge,
                           arc::ArcIntentHelperObserver>
diff --git a/chrome/browser/ui/ash/network/network_state_notifier_unittest.cc b/chrome/browser/ui/ash/network/network_state_notifier_unittest.cc
index 963f327..55132f6 100644
--- a/chrome/browser/ui/ash/network/network_state_notifier_unittest.cc
+++ b/chrome/browser/ui/ash/network/network_state_notifier_unittest.cc
@@ -82,7 +82,6 @@
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
     network_handler_test_helper_ = std::make_unique<NetworkHandlerTestHelper>();
-    CellularMetricsLogger::RegisterLocalStatePrefs(local_state_.registry());
     CellularESimProfileHandlerImpl::RegisterLocalStatePrefs(
         local_state_.registry());
     NetworkMetadataStore::RegisterPrefs(user_prefs_.registry());
diff --git a/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.cc b/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.cc
index ce0d8e6..929477e 100644
--- a/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.cc
+++ b/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.h"
 
 #include "chrome/browser/ui/autofill/autofill_bubble_handler.h"
+#include "chrome/browser/ui/autofill/save_update_address_profile_bubble_controller_impl.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -27,17 +28,16 @@
 
 void EditAddressProfileDialogControllerImpl::OfferEdit(
     const AutofillProfile& profile,
-    bool is_update,
+    const AutofillProfile* original_profile,
     AutofillClient::AddressProfileSavePromptCallback
         address_profile_save_prompt_callback) {
   address_profile_to_edit_ = profile;
-  is_update_ = is_update;
+  original_profile_ = base::OptionalFromPtr(original_profile);
   address_profile_save_prompt_callback_ =
       std::move(address_profile_save_prompt_callback);
   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
-  edit_dialog_ = browser->window()
-                     ->GetAutofillBubbleHandler()
-                     ->ShowEditAddressProfileDialog(web_contents(), this);
+  browser->window()->GetAutofillBubbleHandler()->ShowEditAddressProfileDialog(
+      web_contents(), this);
 }
 
 std::u16string EditAddressProfileDialogControllerImpl::GetWindowTitle() const {
@@ -47,8 +47,9 @@
 std::u16string EditAddressProfileDialogControllerImpl::GetOkButtonLabel()
     const {
   return l10n_util::GetStringUTF16(
-      is_update_ ? IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_OK_BUTTON_LABEL_UPDATE
-                 : IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_OK_BUTTON_LABEL_SAVE);
+      original_profile_.has_value()
+          ? IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_OK_BUTTON_LABEL_UPDATE
+          : IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_OK_BUTTON_LABEL_SAVE);
 }
 
 const AutofillProfile&
@@ -59,17 +60,30 @@
 void EditAddressProfileDialogControllerImpl::OnUserDecision(
     AutofillClient::SaveAddressProfileOfferUserDecision decision,
     const AutofillProfile& profile_with_edits) {
-  edit_dialog_ = nullptr;
-  std::move(address_profile_save_prompt_callback_)
-      .Run(decision, profile_with_edits);
+  // If the user accepted the flow, save the changes directly.
+  if (decision ==
+      AutofillClient::SaveAddressProfileOfferUserDecision::kEditAccepted) {
+    std::move(address_profile_save_prompt_callback_)
+        .Run(decision, profile_with_edits);
+    return;
+  }
+  // If the user hits "Cancel", reopen the previous prompt.
+  SaveUpdateAddressProfileBubbleControllerImpl::CreateForWebContents(
+      web_contents());
+  SaveUpdateAddressProfileBubbleControllerImpl* controller =
+      SaveUpdateAddressProfileBubbleControllerImpl::FromWebContents(
+          web_contents());
+  controller->OfferSave(
+      address_profile_to_edit_, base::OptionalOrNullptr(original_profile_),
+      AutofillClient::SaveAddressProfilePromptOptions{.show_prompt = true},
+      std::move(address_profile_save_prompt_callback_));
 }
 
 void EditAddressProfileDialogControllerImpl::OnDialogClosed() {
-  edit_dialog_ = nullptr;
   if (address_profile_save_prompt_callback_) {
-    std::move(address_profile_save_prompt_callback_)
-        .Run(AutofillClient::SaveAddressProfileOfferUserDecision::kIgnored,
-             address_profile_to_edit_);
+    OnUserDecision(
+        AutofillClient::SaveAddressProfileOfferUserDecision::kIgnored,
+        address_profile_to_edit_);
   }
 }
 
diff --git a/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.h b/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.h
index ed69c316..a5a06cd 100644
--- a/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.h
+++ b/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.h
@@ -13,8 +13,6 @@
 
 namespace autofill {
 
-class AutofillBubbleBase;
-
 // The controller functionality for EditAddressProfileView.
 class EditAddressProfileDialogControllerImpl
     : public EditAddressProfileDialogController,
@@ -29,11 +27,12 @@
   ~EditAddressProfileDialogControllerImpl() override;
 
   // Sets up the controller and offers to edit the |profile| before saving it.
-  // |is_update| indicates whether this edit dialog was triggered from a save or
-  // update prompt. |address_profile_save_prompt_callback| will be invoked once
-  // the user makes a decision with respect to the offer-to-edit prompt.
+  // If `original_profile` is not nullptr, this indicates that this dialog is
+  // opened from an update prompt. The originating prompt (save or update) will
+  // be re-opened once the user makes a decision with respect to the
+  // offer-to-edit prompt.
   void OfferEdit(const AutofillProfile& profile,
-                 bool is_update,
+                 const AutofillProfile* original_profile,
                  AutofillClient::AddressProfileSavePromptCallback
                      address_profile_save_prompt_callback);
 
@@ -61,11 +60,10 @@
   // before saving.
   AutofillProfile address_profile_to_edit_;
 
-  // Whether accepting this Edit dialog will result in saving a new address or
-  // updating an existing one.
-  bool is_update_ = false;
-
-  AutofillBubbleBase* edit_dialog_ = nullptr;
+  // If not nullptr, this dialog was opened from an update prompt. Contains the
+  // details of the address profile that will be updated if the user accepts
+  // that update prompt from which this edit dialog was opened..
+  absl::optional<AutofillProfile> original_profile_;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };
diff --git a/chrome/browser/ui/autofill/save_update_address_profile_bubble_controller_impl.cc b/chrome/browser/ui/autofill/save_update_address_profile_bubble_controller_impl.cc
index 24b882e..c332da15 100644
--- a/chrome/browser/ui/autofill/save_update_address_profile_bubble_controller_impl.cc
+++ b/chrome/browser/ui/autofill/save_update_address_profile_bubble_controller_impl.cc
@@ -88,8 +88,7 @@
   EditAddressProfileDialogControllerImpl::CreateForWebContents(web_contents());
   EditAddressProfileDialogControllerImpl* controller =
       EditAddressProfileDialogControllerImpl::FromWebContents(web_contents());
-  controller->OfferEdit(address_profile_,
-                        /*is_update=*/original_profile_.has_value(),
+  controller->OfferEdit(address_profile_, GetOriginalProfile(),
                         std::move(address_profile_save_prompt_callback_));
   HideBubble();
 }
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index fdf7a50..b2390ee 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -63,6 +63,8 @@
 #include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/web_applications/web_app_ui_manager_impl.h"
+#include "chrome/browser/web_applications/components/os_integration_manager.h"
+#include "chrome/browser/web_applications/components/web_app_provider_base.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_features.h"
@@ -437,19 +439,12 @@
   if (protocol_url.is_empty())
     return false;
 
-  // Check the ProtocolHandlerRegistry for an entry that matches the appId
-  // and the protocol_url provided in the command line. We want to make sure
-  // that we only handle protocol handlers that we've registered for when
-  // installing a web app. This check also filters out file handler url
-  // activation and other unregistered protocol urls that will be handled
-  // by other startup code paths.
-  // TODO::(crbug/1105257) Adjust this once we no longer use
-  // the ProtocolHandlerRegistry to store protocol handler
-  // registration info for web apps.
-  ProtocolHandlerRegistry* handler_registry =
-      ProtocolHandlerRegistryFactory::GetForBrowserContext(profile);
+  web_app::WebAppProviderBase* provider =
+      web_app::WebAppProviderBase::GetProviderBase(profile);
+  web_app::OsIntegrationManager& os_integration_manager =
+      provider->os_integration_manager();
   const std::vector<ProtocolHandler> handlers =
-      handler_registry->GetHandlersFor(protocol_url.scheme());
+      os_integration_manager.GetHandlersForProtocol(protocol_url.scheme());
 
   // Nothing to do if there are no handlers with a web_app_id.
   if (!base::Contains(handlers, true, [](const auto& handler) {
diff --git a/chrome/browser/ui/views/autofill/edit_address_profile_view.cc b/chrome/browser/ui/views/autofill/edit_address_profile_view.cc
index d3b8ab24..2508816 100644
--- a/chrome/browser/ui/views/autofill/edit_address_profile_view.cc
+++ b/chrome/browser/ui/views/autofill/edit_address_profile_view.cc
@@ -30,10 +30,10 @@
 
   SetAcceptCallback(base::BindOnce(
       &EditAddressProfileView::OnUserDecision, base::Unretained(this),
-      AutofillClient::SaveAddressProfileOfferUserDecision::kAccepted));
+      AutofillClient::SaveAddressProfileOfferUserDecision::kEditAccepted));
   SetCancelCallback(base::BindOnce(
       &EditAddressProfileView::OnUserDecision, base::Unretained(this),
-      AutofillClient::SaveAddressProfileOfferUserDecision::kDeclined));
+      AutofillClient::SaveAddressProfileOfferUserDecision::kEditDeclined));
 
   SetLayoutManager(std::make_unique<views::FillLayout>());
   set_margins(ChromeLayoutProvider::Get()->GetInsetsMetric(
diff --git a/chrome/browser/ui/views/autofill/edit_address_profile_view_unittest.cc b/chrome/browser/ui/views/autofill/edit_address_profile_view_unittest.cc
index 96c2c17..fbd65aa 100644
--- a/chrome/browser/ui/views/autofill/edit_address_profile_view_unittest.cc
+++ b/chrome/browser/ui/views/autofill/edit_address_profile_view_unittest.cc
@@ -138,7 +138,7 @@
   EXPECT_CALL(
       *mock_controller(),
       OnUserDecision(
-          AutofillClient::SaveAddressProfileOfferUserDecision::kAccepted,
+          AutofillClient::SaveAddressProfileOfferUserDecision::kEditAccepted,
           AutofillProfileHasInfo(autofill::ServerFieldType::NAME_FULL,
                                  kNewFirstName)));
   dialog()->Accept();
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
index 2d0766f..4947db60 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
@@ -27,7 +27,6 @@
 #include "components/constrained_window/constrained_window_views.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/page_navigator.h"
-#include "content/public/browser/web_contents.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_urls.h"
@@ -142,15 +141,14 @@
 }
 
 void ShowExtensionInstallDialogImpl(
-    ExtensionInstallPromptShowParams* show_params,
+    std::unique_ptr<ExtensionInstallPromptShowParams> show_params,
     ExtensionInstallPrompt::DoneCallback done_callback,
     std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  gfx::NativeWindow parent_window = show_params->GetParentWindow();
   ExtensionInstallDialogView* dialog = new ExtensionInstallDialogView(
-      show_params->profile(), show_params->GetParentWebContents(),
-      std::move(done_callback), std::move(prompt));
-  constrained_window::CreateBrowserModalDialogViews(
-      dialog, show_params->GetParentWindow())
+      std::move(show_params), std::move(done_callback), std::move(prompt));
+  constrained_window::CreateBrowserModalDialogViews(dialog, parent_window)
       ->Show();
 }
 
@@ -207,12 +205,11 @@
 }  // namespace
 
 ExtensionInstallDialogView::ExtensionInstallDialogView(
-    Profile* profile,
-    content::PageNavigator* navigator,
+    std::unique_ptr<ExtensionInstallPromptShowParams> show_params,
     ExtensionInstallPrompt::DoneCallback done_callback,
     std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt)
-    : profile_(profile),
-      navigator_(navigator),
+    : profile_(show_params->profile()),
+      show_params_(std::move(show_params)),
       done_callback_(std::move(done_callback)),
       prompt_(std::move(prompt)),
       title_(prompt_->GetDialogTitle()),
@@ -276,6 +273,15 @@
     OnDialogCanceled();
 }
 
+ExtensionInstallPromptShowParams*
+ExtensionInstallDialogView::GetShowParamsForTesting() {
+  return show_params_.get();
+}
+
+void ExtensionInstallDialogView::ClickLinkForTesting() {
+  LinkClicked();
+}
+
 void ExtensionInstallDialogView::SetInstallButtonDelayForTesting(
     int delay_in_ms) {
   g_install_delay_in_ms = delay_in_ms;
@@ -446,8 +452,9 @@
                        WindowOpenDisposition::NEW_FOREGROUND_TAB,
                        ui::PAGE_TRANSITION_LINK, false);
 
-  if (navigator_) {
-    navigator_->OpenURL(params);
+  DCHECK(show_params_);
+  if (show_params_->GetParentWebContents()) {
+    show_params_->GetParentWebContents()->OpenURL(params);
   } else {
     chrome::ScopedTabbedBrowserDisplayer displayer(profile_);
     displayer.browser()->OpenURL(params);
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view.h b/chrome/browser/ui/views/extensions/extension_install_dialog_view.h
index 1367093..cd08a1d 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view.h
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view.h
@@ -23,10 +23,6 @@
 
 class Profile;
 
-namespace content {
-class PageNavigator;
-}
-
 // Modal dialog that shows when the user attempts to install an extension. Also
 // shown if the extension is already installed but needs additional permissions.
 // Not a normal "bubble" despite being a subclass of BubbleDialogDelegateView.
@@ -40,8 +36,7 @@
   static const int kRatingsViewId = 1;
 
   ExtensionInstallDialogView(
-      Profile* profile,
-      content::PageNavigator* navigator,
+      std::unique_ptr<ExtensionInstallPromptShowParams> show_params,
       ExtensionInstallPrompt::DoneCallback done_callback,
       std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt);
   ExtensionInstallDialogView(const ExtensionInstallDialogView&) = delete;
@@ -64,6 +59,9 @@
   bool IsDialogButtonEnabled(ui::DialogButton button) const override;
   std::u16string GetAccessibleWindowTitle() const override;
 
+  ExtensionInstallPromptShowParams* GetShowParamsForTesting();
+  void ClickLinkForTesting();
+
  private:
   void CloseDialog();
 
@@ -88,7 +86,7 @@
   void UpdateInstallResultHistogram(bool accepted) const;
 
   Profile* profile_;
-  content::PageNavigator* navigator_;
+  std::unique_ptr<ExtensionInstallPromptShowParams> show_params_;
   ExtensionInstallPrompt::DoneCallback done_callback_;
   std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt_;
   std::u16string title_;
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc b/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc
index e32f209..25c749f 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc
@@ -19,10 +19,12 @@
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/extension_icon_manager.h"
 #include "chrome/browser/extensions/extension_install_prompt.h"
+#include "chrome/browser/extensions/extension_install_prompt_show_params.h"
 #include "chrome/browser/extensions/extension_install_prompt_test_helper.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
@@ -38,6 +40,7 @@
 #include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_features.h"
+#include "extensions/common/extension_urls.h"
 #include "extensions/common/permissions/permission_message_provider.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "ui/accessibility/ax_enums.mojom.h"
@@ -133,8 +136,8 @@
 bool ScrollbarTest::IsScrollbarVisible(
     std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt) {
   ExtensionInstallDialogView* dialog = new ExtensionInstallDialogView(
-      profile(), web_contents(), ExtensionInstallPrompt::DoneCallback(),
-      std::move(prompt));
+      std::make_unique<ExtensionInstallPromptShowParams>(web_contents()),
+      ExtensionInstallPrompt::DoneCallback(), std::move(prompt));
 
   // Create the modal view around the install dialog view.
   views::Widget* modal = constrained_window::CreateBrowserModalDialogViews(
@@ -195,7 +198,8 @@
   ExtensionInstallDialogView* CreateAndShowPrompt(
       ExtensionInstallPromptTestHelper* helper) {
     auto dialog = std::make_unique<ExtensionInstallDialogView>(
-        profile(), web_contents(), helper->GetCallback(),
+        std::make_unique<ExtensionInstallPromptShowParams>(web_contents()),
+        helper->GetCallback(),
         CreatePrompt(ExtensionInstallPrompt::INSTALL_PROMPT));
     ExtensionInstallDialogView* delegate_view = dialog.get();
 
@@ -265,6 +269,90 @@
   CloseAndWait(delegate_view->GetWidget());
 }
 
+// Regression test for https://crbug.com/1201060: Ensures that while an
+// ExtensionInstallDialogView is visible, it does not (and cannot) refer to its
+// originator tab/WebContents after the tab's closure.
+//
+// Note that the tab's closure is not typically possible by user interaction.
+// However, it can occur by other means:
+//   a) chrome.tabs.remove() or
+//   b) window.close() from devtools (See repro in crbug).
+// This test uses CloseWebContentsAt to mimic that behavior.
+IN_PROC_BROWSER_TEST_F(ExtensionInstallDialogViewTest,
+                       TabClosureClearsWebContentsFromDialogView) {
+  ExtensionInstallPromptTestHelper helper;
+  ExtensionInstallDialogView* delegate_view = CreateAndShowPrompt(&helper);
+  TabStripModel* tab_strip_model = browser()->tab_strip_model();
+  content::WebContents* originator_contents =
+      tab_strip_model->GetActiveWebContents();
+  ASSERT_TRUE(delegate_view->GetShowParamsForTesting());
+  EXPECT_EQ(originator_contents,
+            delegate_view->GetShowParamsForTesting()->GetParentWebContents());
+
+  // Add a second tab.
+  chrome::AddTabAt(browser(), GURL(url::kAboutBlankURL), -1, true);
+  {
+    // Close the first tab that results in install dialog moving to the second
+    // tab.
+    int tab1_idx = tab_strip_model->GetIndexOfWebContents(originator_contents);
+    content::WebContentsDestroyedWatcher tab_destroyed_watcher(
+        tab_strip_model->GetWebContentsAt(tab1_idx));
+    tab_strip_model->CloseWebContentsAt(tab1_idx, TabStripModel::CLOSE_NONE);
+    tab_destroyed_watcher.Wait();
+    EXPECT_TRUE(tab_strip_model->CloseWebContentsAt(tab1_idx,
+                                                    TabStripModel::CLOSE_NONE));
+  }
+
+  class TabAddedObserver : public TabStripModelObserver {
+   public:
+    explicit TabAddedObserver(TabStripModel* tab_strip_model) {
+      tab_strip_model->AddObserver(this);
+    }
+
+    void WaitForWebstoreTabAdded() { run_loop_.Run(); }
+
+    // TabStripModelObserver:
+    void OnTabStripModelChanged(
+        TabStripModel* tab_strip_model,
+        const TabStripModelChange& change,
+        const TabStripSelectionChange& selection) override {
+      if (change.type() != TabStripModelChange::kInserted)
+        return;
+
+      for (const auto& contents : change.GetInsert()->contents) {
+        // Note: GetVisibleURL() is used instead of GetLastCommittedURL() for
+        // simplicity's sake as this test doesn't serve webstore url and
+        // the url doesn't commit.
+        const GURL& url = contents.contents->GetVisibleURL();
+        if (url.host() == extension_urls::GetWebstoreLaunchURL().host()) {
+          run_loop_.Quit();
+          return;
+        }
+      }
+    }
+
+   private:
+    base::RunLoop run_loop_;
+  };
+
+  // The dialog remains visible even though |originator_contents| is gone. Note
+  // that this doesn't seem quite intuitive, but this is how things are at the
+  // moment. See crbug.com/1201060 for details.
+  EXPECT_TRUE(delegate_view->GetVisible());
+
+  EXPECT_EQ(nullptr,
+            delegate_view->GetShowParamsForTesting()->GetParentWebContents());
+
+  // Click webstore link.
+  {
+    TabAddedObserver observer(tab_strip_model);
+    delegate_view->ClickLinkForTesting();
+    observer.WaitForWebstoreTabAdded();
+  }
+
+  CloseAndWait(delegate_view->GetWidget());
+}
+
 class ExtensionInstallDialogViewInteractiveBrowserTest
     : public DialogBrowserTest {
  public:
@@ -517,7 +605,8 @@
   std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt =
       CreatePrompt(ExtensionInstallPrompt::REPAIR_PROMPT, extension);
   ExtensionInstallDialogView* dialog = new ExtensionInstallDialogView(
-      profile(), web_contents(), base::DoNothing(), std::move(prompt));
+      std::make_unique<ExtensionInstallPromptShowParams>(web_contents()),
+      base::DoNothing(), std::move(prompt));
 
   views::Widget* modal_dialog = views::DialogDelegate::CreateDialogWidget(
       dialog, nullptr,
@@ -544,7 +633,8 @@
   std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt =
       CreatePrompt(ExtensionInstallPrompt::REPAIR_PROMPT, extension);
   ExtensionInstallDialogView* dialog = new ExtensionInstallDialogView(
-      profile(), web_contents(), base::DoNothing(), std::move(prompt));
+      std::make_unique<ExtensionInstallPromptShowParams>(web_contents()),
+      base::DoNothing(), std::move(prompt));
 
   views::Widget* modal_dialog = views::DialogDelegate::CreateDialogWidget(
       dialog, nullptr,
@@ -584,7 +674,8 @@
   prompt->SetWebstoreData("1,234", true, average_rating, num_ratings);
 
   ExtensionInstallDialogView* dialog = new ExtensionInstallDialogView(
-      profile(), web_contents(), base::DoNothing(), std::move(prompt));
+      std::make_unique<ExtensionInstallPromptShowParams>(web_contents()),
+      base::DoNothing(), std::move(prompt));
 
   views::Widget* modal_dialog = views::DialogDelegate::CreateDialogWidget(
       dialog, nullptr,
@@ -647,7 +738,8 @@
       extensions::URLPatternSet());
   prompt->AddPermissionSet(permissions);
   auto dialog = std::make_unique<ExtensionInstallDialogView>(
-      profile(), web_contents(), helper.GetCallback(), std::move(prompt));
+      std::make_unique<ExtensionInstallPromptShowParams>(web_contents()),
+      helper.GetCallback(), std::move(prompt));
   views::BubbleDialogDelegateView* delegate_view = dialog.get();
 
   views::Widget* modal_dialog = views::DialogDelegate::CreateDialogWidget(
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view_supervised_browsertest.cc b/chrome/browser/ui/views/extensions/extension_install_dialog_view_supervised_browsertest.cc
index dee0331b..6602ba8 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view_supervised_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view_supervised_browsertest.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/extension_icon_manager.h"
 #include "chrome/browser/extensions/extension_install_prompt.h"
+#include "chrome/browser/extensions/extension_install_prompt_show_params.h"
 #include "chrome/browser/extensions/extension_install_prompt_test_helper.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/supervised_user/supervised_user_extensions_metrics_recorder.h"
@@ -95,7 +96,8 @@
     ExtensionInstallPromptTestHelper* helper,
     std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt) {
   auto dialog = std::make_unique<ExtensionInstallDialogView>(
-      profile(), web_contents(), helper->GetCallback(), std::move(prompt));
+      std::make_unique<ExtensionInstallPromptShowParams>(web_contents()),
+      helper->GetCallback(), std::move(prompt));
   ExtensionInstallDialogView* delegate_view = dialog.get();
 
   views::Widget* modal_dialog = views::DialogDelegate::CreateDialogWidget(
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index fa91c8b..7599f8d4 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1476,6 +1476,8 @@
          IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION2},
         {"privacySandboxPageExplanation3",
          IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION3},
+        {"privacySandboxPageExplanation2Phase2",
+         IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION2_PHASE2},
         {"privacySandboxPageSettingTitle",
          IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_TITLE},
         {"privacySandboxPageSettingExplanation1",
@@ -1484,12 +1486,16 @@
          IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION2},
         {"privacySandboxPageSettingExplanation3",
          IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION3},
+        {"privacySandboxPageSettingExplanation1Phase2",
+         IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION1_PHASE2},
+        {"privacySandboxPageSettingExplanation2Phase2",
+         IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION2_PHASE2},
+        {"privacySandboxPageSettingExplanation3Phase2",
+         IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_SETTING_EXPLANATION3_PHASE2},
         {"privacySandboxPageDetails",
          IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_DETAILS},
         {"privacySandboxPageFlocHeading",
          IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_HEADING},
-        {"privacySandboxPageFlocExplanation",
-         IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_EXPLANATION},
         {"privacySandboxPageFlocStatus",
          IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_STATUS},
         {"privacySandboxPageFlocCohort",
@@ -1511,6 +1517,19 @@
             IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION4,
             base::ASCIIToUTF16(features::kPrivacySandboxSettingsURL.Get())));
 
+    html_source->AddString(
+        "privacySandboxPageExplanation1Phase2",
+        l10n_util::GetStringFUTF16(
+            IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_EXPLANATION1_PHASE2,
+            base::ASCIIToUTF16(features::kPrivacySandboxSettingsURL.Get())));
+
+    html_source->AddString(
+        "privacySandboxPageFlocExplanation",
+        l10n_util::GetStringFUTF16(
+            IDS_SETTINGS_PRIVACY_SANDBOX_PAGE_FLOC_EXPLANATION,
+            base::ASCIIToUTF16(
+                features::kPrivacySandboxSettings2FlocURL.Get())));
+
     // The FLoC compute frequency string is constant through the life of the
     // profile, and so the relevant string can be injected here, rather than
     // fetched dynamically from JS.
diff --git a/chrome/browser/web_applications/components/BUILD.gn b/chrome/browser/web_applications/components/BUILD.gn
index d701097..cd56b58 100644
--- a/chrome/browser/web_applications/components/BUILD.gn
+++ b/chrome/browser/web_applications/components/BUILD.gn
@@ -121,10 +121,7 @@
   }
 
   if (is_linux) {
-    sources += [
-      "web_app_protocol_handler_registration_linux.cc",
-      "web_app_run_on_os_login_linux.cc",
-    ]
+    sources += [ "web_app_run_on_os_login_linux.cc" ]
   }
 
   if (is_chromeos_ash || is_chromeos_lacros) {
@@ -136,7 +133,6 @@
       "app_shim_registry_mac.cc",
       "app_shim_registry_mac.h",
       "web_app_file_handler_registration_mac.cc",
-      "web_app_protocol_handler_registration_mac.cc",
       "web_app_run_on_os_login_mac.mm",
       "web_app_shortcut_mac.h",
       "web_app_shortcut_mac.mm",
@@ -251,7 +247,6 @@
   if (is_mac) {
     sources += [
       "app_shim_registry_mac_unittest.cc",
-      "web_app_protocol_handler_registration_mac_unittest.cc",
       "web_app_run_on_os_login_mac_unittest.mm",
       "web_app_shortcut_mac_unittest.mm",
     ]
@@ -266,10 +261,7 @@
   }
 
   if (is_linux) {
-    sources += [
-      "web_app_protocol_handler_registration_linux_unittest.cc",
-      "web_app_run_on_os_login_linux_unittest.cc",
-    ]
+    sources += [ "web_app_run_on_os_login_linux_unittest.cc" ]
   }
 
   if (is_win || is_mac || (is_linux && !is_chromeos_lacros)) {
diff --git a/chrome/browser/web_applications/components/os_integration_manager.cc b/chrome/browser/web_applications/components/os_integration_manager.cc
index 1903fc0..05597ee9 100644
--- a/chrome/browser/web_applications/components/os_integration_manager.cc
+++ b/chrome/browser/web_applications/components/os_integration_manager.cc
@@ -314,6 +314,14 @@
   return protocol_handler_manager_->TranslateProtocolUrl(app_id, protocol_url);
 }
 
+std::vector<ProtocolHandler> OsIntegrationManager::GetHandlersForProtocol(
+    const std::string& protocol) {
+  if (!protocol_handler_manager_)
+    return std::vector<ProtocolHandler>();
+
+  return protocol_handler_manager_->GetHandlersFor(protocol);
+}
+
 FileHandlerManager& OsIntegrationManager::file_handler_manager_for_testing() {
   DCHECK(file_handler_manager_);
   return *file_handler_manager_;
diff --git a/chrome/browser/web_applications/components/os_integration_manager.h b/chrome/browser/web_applications/components/os_integration_manager.h
index a85d61ba..2d4e316 100644
--- a/chrome/browser/web_applications/components/os_integration_manager.h
+++ b/chrome/browser/web_applications/components/os_integration_manager.h
@@ -143,6 +143,8 @@
   // Proxy calls for ProtocolHandlerManager.
   virtual absl::optional<GURL> TranslateProtocolUrl(const AppId& app_id,
                                                     const GURL& protocol_url);
+  virtual std::vector<ProtocolHandler> GetHandlersForProtocol(
+      const std::string& protocol);
 
   // Getter for testing FileHandlerManager
   FileHandlerManager& file_handler_manager_for_testing();
diff --git a/chrome/browser/web_applications/components/protocol_handler_manager.cc b/chrome/browser/web_applications/components/protocol_handler_manager.cc
index 139e5316..7efcab7 100644
--- a/chrome/browser/web_applications/components/protocol_handler_manager.cc
+++ b/chrome/browser/web_applications/components/protocol_handler_manager.cc
@@ -93,16 +93,7 @@
 }
 
 void ProtocolHandlerManager::UnregisterOsProtocolHandlers(const AppId& app_id) {
-  const std::vector<apps::ProtocolHandlerInfo> handlers =
-      GetAppProtocolHandlerInfos(app_id);
-  UnregisterOsProtocolHandlers(app_id, handlers);
-}
-
-void ProtocolHandlerManager::UnregisterOsProtocolHandlers(
-    const AppId& app_id,
-    const std::vector<apps::ProtocolHandlerInfo>& protocol_handlers) {
-  if (!protocol_handlers.empty())
-    UnregisterProtocolHandlersWithOs(app_id, profile_, protocol_handlers);
+  UnregisterProtocolHandlersWithOs(app_id, profile_);
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/components/protocol_handler_manager.h b/chrome/browser/web_applications/components/protocol_handler_manager.h
index 769b973..4c0ef77 100644
--- a/chrome/browser/web_applications/components/protocol_handler_manager.h
+++ b/chrome/browser/web_applications/components/protocol_handler_manager.h
@@ -56,16 +56,9 @@
       const std::vector<apps::ProtocolHandlerInfo>& protocol_handlers,
       base::OnceCallback<void(bool)> callback);
 
-  // Unregisters OS specific protocol handlers for OSs that need them, using the
-  // protocol handler information supplied in the app manifest.
+  // Unregisters OS specific protocol handlers for an app.
   void UnregisterOsProtocolHandlers(const AppId& app_id);
 
-  // Unregisters OS specific protocol handlers for OSs that need them, using
-  // arbitrary protocol handler information.
-  void UnregisterOsProtocolHandlers(
-      const AppId& app_id,
-      const std::vector<apps::ProtocolHandlerInfo>& protocol_handlers);
-
   AppRegistrar* app_registrar_;
 
  private:
diff --git a/chrome/browser/web_applications/components/web_app_handler_registration_utils_win.cc b/chrome/browser/web_applications/components/web_app_handler_registration_utils_win.cc
index 0418bc42..bf93607 100644
--- a/chrome/browser/web_applications/components/web_app_handler_registration_utils_win.cc
+++ b/chrome/browser/web_applications/components/web_app_handler_registration_utils_win.cc
@@ -7,6 +7,7 @@
 #include "base/command_line.h"
 #include "base/files/file_util.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -86,40 +87,37 @@
   return app_name_extension;
 }
 
-void UpdateAppRegistration(const web_app::AppId& app_id,
+bool UpdateAppRegistration(const web_app::AppId& app_id,
                            const std::wstring& app_name,
                            const base::FilePath& profile_path,
                            const std::wstring& prog_id,
-                           const std::wstring& app_name_extension,
-                           base::OnceCallback<void(bool)> callback) {
+                           const std::wstring& app_name_extension) {
   if (!base::DeleteFile(ShellUtil::GetApplicationPathForProgId(prog_id))) {
     web_app::RecordRegistration(
         web_app::RegistrationResult::kFailToDeleteExistingRegistration);
-    std::move(callback).Run(false);
-    return;
+    return false;
   }
 
   std::wstring user_visible_app_name(app_name);
   user_visible_app_name.append(app_name_extension);
 
+  base::FilePath web_app_path(web_app::GetOsIntegrationResourcesDirectoryForApp(
+      profile_path, app_id, GURL()));
   absl::optional<base::FilePath> app_launcher_path =
-      web_app::CreateAppLauncherFile(
-          app_name, app_name_extension,
-          web_app::GetOsIntegrationResourcesDirectoryForApp(profile_path,
-                                                            app_id, GURL()));
+      web_app::CreateAppLauncherFile(app_name, app_name_extension,
+                                     web_app_path);
   if (!app_launcher_path) {
-    std::move(callback).Run(false);
-    return;
+    return false;
   }
 
   base::CommandLine app_launch_cmd = web_app::GetAppLauncherCommand(
       app_id, app_launcher_path.value(), profile_path);
   base::FilePath icon_path = web_app::internals::GetIconFilePath(
-      app_launcher_path.value(), base::AsString16(app_name));
+      web_app_path, base::AsString16(app_name));
 
   ShellUtil::AddApplicationClass(prog_id, app_launch_cmd, user_visible_app_name,
                                  app_name, icon_path);
-  std::move(callback).Run(true);
+  return true;
 }
 
 bool AppNameHasProfileExtension(const std::wstring& app_name,
@@ -198,14 +196,18 @@
 // can not be changed.
 std::wstring GetProgIdForApp(const base::FilePath& profile_path,
                              const AppId& app_id) {
-  std::wstring prog_id = install_static::GetBaseAppId();
-  std::string app_specific_part(
-      base::WideToUTF8(profile_path.BaseName().value()));
-  app_specific_part.append(app_id);
-  uint32_t hash = base::PersistentHash(app_specific_part);
-  prog_id.push_back(L'.');
-  prog_id.append(base::ASCIIToWide(base::NumberToString(hash)));
-  return prog_id;
+  // On system-level Win7 installs of the browser we need a user specific
+  // part to differentiate HKLM entries from different Windows profiles.
+  std::wstring user_specific_part;
+  ShellUtil::GetUserSpecificRegistrySuffix(&user_specific_part);
+
+  const uint32_t hash = base::PersistentHash(
+      base::StrCat({base::WideToUTF8(profile_path.BaseName().value()), app_id,
+                    base::WideToUTF8(user_specific_part)}));
+
+  return base::UTF16ToWide(
+      base::StrCat({base::AsStringPiece16(install_static::GetBaseAppId()), u".",
+                    base::NumberToString16(hash)}));
 }
 
 absl::optional<base::FilePath> CreateAppLauncherFile(
@@ -311,12 +313,12 @@
     updated_extension = std::wstring();
   }
 
-  base::ThreadPool::PostTask(
+  base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock()},
       base::BindOnce(&UpdateAppRegistration, app_id, updated_name,
                      external_installation_profile_path,
-                     external_installation_prog_id, updated_extension,
-                     std::move(callback)));
+                     external_installation_prog_id, updated_extension),
+      std::move(callback));
 }
 
 // Record UMA metric for the result of file handler registration.
diff --git a/chrome/browser/web_applications/components/web_app_handler_registration_utils_win_unittest.cc b/chrome/browser/web_applications/components/web_app_handler_registration_utils_win_unittest.cc
index 23301aab..4dd4a70 100644
--- a/chrome/browser/web_applications/components/web_app_handler_registration_utils_win_unittest.cc
+++ b/chrome/browser/web_applications/components/web_app_handler_registration_utils_win_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/test/bind.h"
 #include "base/test/test_reg_util_win.h"
+#include "base/win/registry.h"
 #include "base/win/windows_version.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
@@ -59,9 +60,12 @@
                    const std::wstring& app_name,
                    const std::wstring& app_name_extension,
                    const base::FilePath& profile_path) {
-    absl::optional<base::FilePath> launcher_path = CreateAppLauncherFile(
-        app_name, app_name_extension,
-        GetOsIntegrationResourcesDirectoryForApp(profile_path, app_id, GURL()));
+    base::FilePath web_app_path(
+        web_app::GetOsIntegrationResourcesDirectoryForApp(profile_path, app_id,
+                                                          GURL()));
+
+    absl::optional<base::FilePath> launcher_path =
+        CreateAppLauncherFile(app_name, app_name_extension, web_app_path);
     ASSERT_TRUE(launcher_path.has_value());
 
     base::CommandLine launcher_command =
@@ -70,13 +74,16 @@
     std::wstring user_visible_app_name(app_name);
     user_visible_app_name.append(app_name_extension);
 
-    ASSERT_TRUE(ShellUtil::AddApplicationClass(
-        prog_id, launcher_command, user_visible_app_name, std::wstring(),
-        base::FilePath()));
+    base::FilePath icon_path =
+        internals::GetIconFilePath(web_app_path, base::AsString16(app_name));
+
+    ASSERT_TRUE(ShellUtil::AddApplicationClass(prog_id, launcher_command,
+                                               user_visible_app_name,
+                                               std::wstring(), icon_path));
   }
 
-  // Tests that an app with |app_id| is registered with the expected name /
-  // extension.
+  // Tests that an app with |app_id| is registered with the expected name,
+  // icon and extension.
   void TestRegisteredApp(const AppId& app_id,
                          const std::wstring& expected_app_name,
                          const std::wstring& expected_app_name_extension,
@@ -85,9 +92,15 @@
     std::wstring expected_user_visible_app_name(app_name());
     expected_user_visible_app_name.append(expected_app_name_extension);
     std::wstring app_progid = GetProgIdForApp(profile_path, app_id);
-    ShellUtil::FileAssociationsAndAppName registered_app =
-        ShellUtil::GetFileAssociationsAndAppName(app_progid);
-    EXPECT_EQ(expected_user_visible_app_name, registered_app.app_name);
+    ShellUtil::ApplicationInfo registered_app =
+        ShellUtil::GetApplicationInfoForProgId(app_progid);
+
+    EXPECT_EQ(expected_user_visible_app_name, registered_app.application_name);
+
+    // Ensure that the OS registry contains the expected icon path.
+    EXPECT_EQ(registered_app.application_icon_path,
+              profile_path.Append(base::FilePath(FILE_PATH_LITERAL(
+                  "Web Applications\\_crx_app_id\\app_name.ico"))));
 
     // Ensure that the launcher file contains the expected app name.
     // On Windows 7 the extension is omitted.
diff --git a/chrome/browser/web_applications/components/web_app_protocol_handler_registration.cc b/chrome/browser/web_applications/components/web_app_protocol_handler_registration.cc
index 58957bf..c7cf5d8 100644
--- a/chrome/browser/web_applications/components/web_app_protocol_handler_registration.cc
+++ b/chrome/browser/web_applications/components/web_app_protocol_handler_registration.cc
@@ -10,7 +10,7 @@
 
 namespace web_app {
 
-#if !(defined(OS_WIN) || defined(OS_MAC) || defined(OS_LINUX))
+#if !defined(OS_WIN)
 // Registers a protocol handler for the web app with the OS.
 void RegisterProtocolHandlersWithOs(
     const AppId& app_id,
@@ -19,17 +19,14 @@
     std::vector<apps::ProtocolHandlerInfo> protocol_handlers,
     base::OnceCallback<void(bool)> callback) {
   base::SequencedTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), /*success=*/false));
+      FROM_HERE, base::BindOnce(std::move(callback), /*success=*/true));
 }
 
-// Unregisters a protocol handler for the web app with the OS.
+// Unregisters protocol handlers for a web app with the OS.
 //
 // TODO(crbug.com/1174805): Add a callback as part of the protocol handling
 // unregistration flow.
-void UnregisterProtocolHandlersWithOs(
-    const AppId& app_id,
-    Profile* profile,
-    std::vector<apps::ProtocolHandlerInfo> protocol_handlers) {}
+void UnregisterProtocolHandlersWithOs(const AppId& app_id, Profile* profile) {}
 #endif
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/components/web_app_protocol_handler_registration.h b/chrome/browser/web_applications/components/web_app_protocol_handler_registration.h
index d8fdd2c..54feade 100644
--- a/chrome/browser/web_applications/components/web_app_protocol_handler_registration.h
+++ b/chrome/browser/web_applications/components/web_app_protocol_handler_registration.h
@@ -26,10 +26,7 @@
     std::vector<apps::ProtocolHandlerInfo> protocol_handlers,
     base::OnceCallback<void(bool)> callback);
 
-void UnregisterProtocolHandlersWithOs(
-    const AppId& app_id,
-    Profile* profile,
-    std::vector<apps::ProtocolHandlerInfo> protocol_handlers);
+void UnregisterProtocolHandlersWithOs(const AppId& app_id, Profile* profile);
 
 }  // namespace web_app
 
diff --git a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_linux.cc b/chrome/browser/web_applications/components/web_app_protocol_handler_registration_linux.cc
deleted file mode 100644
index eab5cee2..0000000
--- a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_linux.cc
+++ /dev/null
@@ -1,33 +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/web_applications/components/web_app_protocol_handler_registration.h"
-
-#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
-#include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
-
-namespace web_app {
-
-void RegisterProtocolHandlersWithOs(
-    const AppId& app_id,
-    const std::string& app_name,
-    Profile* profile,
-    std::vector<apps::ProtocolHandlerInfo> protocol_handlers,
-    base::OnceCallback<void(bool)> callback) {
-  ProtocolHandlerRegistry* registry =
-      ProtocolHandlerRegistryFactory::GetForBrowserContext(profile);
-  registry->RegisterAppProtocolHandlers(app_id, protocol_handlers);
-  std::move(callback).Run(true);
-}
-
-void UnregisterProtocolHandlersWithOs(
-    const AppId& app_id,
-    Profile* profile,
-    std::vector<apps::ProtocolHandlerInfo> protocol_handlers) {
-  ProtocolHandlerRegistry* registry =
-      ProtocolHandlerRegistryFactory::GetForBrowserContext(profile);
-  registry->DeregisterAppProtocolHandlers(app_id, protocol_handlers);
-}
-
-}  // namespace web_app
diff --git a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_linux_unittest.cc b/chrome/browser/web_applications/components/web_app_protocol_handler_registration_linux_unittest.cc
deleted file mode 100644
index 5b7c88f2..0000000
--- a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_linux_unittest.cc
+++ /dev/null
@@ -1,143 +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/web_applications/components/web_app_protocol_handler_registration.h"
-
-#include "base/run_loop.h"
-#include "base/task/thread_pool/thread_pool_instance.h"
-#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
-#include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
-#include "chrome/common/chrome_constants.h"
-#include "chrome/test/base/testing_browser_process.h"
-#include "chrome/test/base/testing_profile.h"
-#include "chrome/test/base/testing_profile_manager.h"
-#include "components/services/app_service/public/cpp/protocol_handler_info.h"
-#include "content/public/test/browser_task_environment.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-const char kApp1Id[] = "app1_id";
-const char kApp1Name[] = "app1 name";
-const char kApp1Url[] = "https://app1.com/%s";
-const char kApp2Id[] = "app2_id";
-const char kApp2Name[] = "app2 name";
-const char kApp2Url[] = "https://app2.com/%s";
-
-ProtocolHandler GetProtocolHandler(
-    const apps::ProtocolHandlerInfo& handler_info,
-    const std::string& app_id) {
-  return ProtocolHandler::CreateWebAppProtocolHandler(handler_info.protocol,
-                                                      handler_info.url, app_id);
-}
-
-std::unique_ptr<KeyedService> BuildProtocolHandlerRegistry(
-    content::BrowserContext* context) {
-  Profile* profile = Profile::FromBrowserContext(context);
-  return std::make_unique<ProtocolHandlerRegistry>(
-      profile, std::make_unique<ProtocolHandlerRegistry::Delegate>());
-}
-
-}  // namespace
-
-namespace web_app {
-
-class WebAppProtocolHandlerRegistrationLinuxTest : public testing::Test {
- protected:
-  WebAppProtocolHandlerRegistrationLinuxTest() = default;
-
-  void SetUp() override {
-    testing_profile_manager_ = std::make_unique<TestingProfileManager>(
-        TestingBrowserProcess::GetGlobal());
-    ASSERT_TRUE(testing_profile_manager_->SetUp());
-    profile_ =
-        testing_profile_manager_->CreateTestingProfile(chrome::kInitialProfile);
-
-    ProtocolHandlerRegistryFactory::GetInstance()->SetTestingFactory(
-        profile_, base::BindRepeating(&BuildProtocolHandlerRegistry));
-  }
-
-  void TearDown() override {
-    profile_ = nullptr;
-    testing_profile_manager_->DeleteAllTestingProfiles();
-  }
-
-  Profile* GetProfile() { return profile_; }
-
-  ProtocolHandlerRegistry* protocol_handler_registry() {
-    return ProtocolHandlerRegistryFactory::GetForBrowserContext(GetProfile());
-  }
-
- private:
-  content::BrowserTaskEnvironment task_environment_{
-      content::BrowserTaskEnvironment::IO_MAINLOOP};
-  std::unique_ptr<TestingProfileManager> testing_profile_manager_;
-  TestingProfile* profile_;
-};
-
-TEST_F(WebAppProtocolHandlerRegistrationLinuxTest, RegisterHandlers) {
-  apps::ProtocolHandlerInfo handler1_info;
-  handler1_info.protocol = "mailto";
-  handler1_info.url = GURL(kApp1Url);
-  auto handler1 = GetProtocolHandler(handler1_info, kApp1Id);
-
-  apps::ProtocolHandlerInfo handler2_info;
-  handler2_info.protocol = "web+test";
-  handler2_info.url = GURL(kApp1Url);
-  auto handler2 = GetProtocolHandler(handler2_info, kApp1Id);
-
-  RegisterProtocolHandlersWithOs(kApp1Id, kApp1Name, GetProfile(),
-                                 {handler1_info, handler2_info},
-                                 base::DoNothing());
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler1));
-  EXPECT_TRUE(protocol_handler_registry()->IsDefault(handler1));
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler2));
-  EXPECT_TRUE(protocol_handler_registry()->IsDefault(handler2));
-}
-
-TEST_F(WebAppProtocolHandlerRegistrationLinuxTest,
-       RegisterMultipleHandlersWithSameScheme) {
-  apps::ProtocolHandlerInfo handler1_info;
-  handler1_info.protocol = "mailto";
-  handler1_info.url = GURL(kApp1Url);
-  auto handler1 = GetProtocolHandler(handler1_info, kApp1Id);
-
-  RegisterProtocolHandlersWithOs(kApp1Id, kApp1Name, GetProfile(),
-                                 {handler1_info}, base::DoNothing());
-
-  apps::ProtocolHandlerInfo handler2_info;
-  handler2_info.protocol = "mailto";
-  handler2_info.url = GURL(kApp2Url);
-  auto handler2 = GetProtocolHandler(handler2_info, kApp2Id);
-
-  RegisterProtocolHandlersWithOs(kApp2Id, kApp2Name, GetProfile(),
-                                 {handler2_info}, base::DoNothing());
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler1));
-  EXPECT_TRUE(protocol_handler_registry()->IsDefault(handler1));
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler2));
-  EXPECT_FALSE(protocol_handler_registry()->IsDefault(handler2));
-}
-
-TEST_F(WebAppProtocolHandlerRegistrationLinuxTest, UnregisterHandler) {
-  apps::ProtocolHandlerInfo handler_info;
-  handler_info.protocol = "mailto";
-  handler_info.url = GURL(kApp1Url);
-  auto handler = GetProtocolHandler(handler_info, kApp1Id);
-
-  RegisterProtocolHandlersWithOs(kApp1Id, kApp1Name, GetProfile(),
-                                 {handler_info}, base::DoNothing());
-
-  ASSERT_TRUE(protocol_handler_registry()->IsRegistered(handler));
-  ASSERT_TRUE(protocol_handler_registry()->IsDefault(handler));
-
-  UnregisterProtocolHandlersWithOs(kApp1Id, GetProfile(), {handler_info});
-
-  EXPECT_FALSE(protocol_handler_registry()->IsRegistered(handler));
-  EXPECT_FALSE(protocol_handler_registry()->IsDefault(handler));
-}
-
-}  // namespace web_app
diff --git a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_mac.cc b/chrome/browser/web_applications/components/web_app_protocol_handler_registration_mac.cc
deleted file mode 100644
index eab5cee2..0000000
--- a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_mac.cc
+++ /dev/null
@@ -1,33 +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/web_applications/components/web_app_protocol_handler_registration.h"
-
-#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
-#include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
-
-namespace web_app {
-
-void RegisterProtocolHandlersWithOs(
-    const AppId& app_id,
-    const std::string& app_name,
-    Profile* profile,
-    std::vector<apps::ProtocolHandlerInfo> protocol_handlers,
-    base::OnceCallback<void(bool)> callback) {
-  ProtocolHandlerRegistry* registry =
-      ProtocolHandlerRegistryFactory::GetForBrowserContext(profile);
-  registry->RegisterAppProtocolHandlers(app_id, protocol_handlers);
-  std::move(callback).Run(true);
-}
-
-void UnregisterProtocolHandlersWithOs(
-    const AppId& app_id,
-    Profile* profile,
-    std::vector<apps::ProtocolHandlerInfo> protocol_handlers) {
-  ProtocolHandlerRegistry* registry =
-      ProtocolHandlerRegistryFactory::GetForBrowserContext(profile);
-  registry->DeregisterAppProtocolHandlers(app_id, protocol_handlers);
-}
-
-}  // namespace web_app
diff --git a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_mac_unittest.cc b/chrome/browser/web_applications/components/web_app_protocol_handler_registration_mac_unittest.cc
deleted file mode 100644
index a1e9e4c..0000000
--- a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_mac_unittest.cc
+++ /dev/null
@@ -1,143 +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/web_applications/components/web_app_protocol_handler_registration.h"
-
-#include "base/run_loop.h"
-#include "base/task/thread_pool/thread_pool_instance.h"
-#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
-#include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
-#include "chrome/common/chrome_constants.h"
-#include "chrome/test/base/testing_browser_process.h"
-#include "chrome/test/base/testing_profile.h"
-#include "chrome/test/base/testing_profile_manager.h"
-#include "components/services/app_service/public/cpp/protocol_handler_info.h"
-#include "content/public/test/browser_task_environment.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-const char kApp1Id[] = "app1_id";
-const char kApp1Name[] = "app1 name";
-const char kApp1Url[] = "https://app1.com/%s";
-const char kApp2Id[] = "app2_id";
-const char kApp2Name[] = "app2 name";
-const char kApp2Url[] = "https://app2.com/%s";
-
-ProtocolHandler GetProtocolHandler(
-    const apps::ProtocolHandlerInfo& handler_info,
-    const std::string& app_id) {
-  return ProtocolHandler::CreateWebAppProtocolHandler(handler_info.protocol,
-                                                      handler_info.url, app_id);
-}
-
-std::unique_ptr<KeyedService> BuildProtocolHandlerRegistry(
-    content::BrowserContext* context) {
-  Profile* profile = Profile::FromBrowserContext(context);
-  return std::make_unique<ProtocolHandlerRegistry>(
-      profile, std::make_unique<ProtocolHandlerRegistry::Delegate>());
-}
-
-}  // namespace
-
-namespace web_app {
-
-class WebAppProtocolHandlerRegistrationMacTest : public testing::Test {
- protected:
-  WebAppProtocolHandlerRegistrationMacTest() = default;
-
-  void SetUp() override {
-    testing_profile_manager_ = std::make_unique<TestingProfileManager>(
-        TestingBrowserProcess::GetGlobal());
-    ASSERT_TRUE(testing_profile_manager_->SetUp());
-    profile_ =
-        testing_profile_manager_->CreateTestingProfile(chrome::kInitialProfile);
-
-    ProtocolHandlerRegistryFactory::GetInstance()->SetTestingFactory(
-        profile_, base::BindRepeating(&BuildProtocolHandlerRegistry));
-  }
-
-  void TearDown() override {
-    profile_ = nullptr;
-    testing_profile_manager_->DeleteAllTestingProfiles();
-  }
-
-  Profile* GetProfile() { return profile_; }
-
-  ProtocolHandlerRegistry* protocol_handler_registry() {
-    return ProtocolHandlerRegistryFactory::GetForBrowserContext(GetProfile());
-  }
-
- private:
-  content::BrowserTaskEnvironment task_environment_{
-      content::BrowserTaskEnvironment::IO_MAINLOOP};
-  std::unique_ptr<TestingProfileManager> testing_profile_manager_;
-  TestingProfile* profile_;
-};
-
-TEST_F(WebAppProtocolHandlerRegistrationMacTest, RegisterHandlers) {
-  apps::ProtocolHandlerInfo handler1_info;
-  handler1_info.protocol = "mailto";
-  handler1_info.url = GURL(kApp1Url);
-  auto handler1 = GetProtocolHandler(handler1_info, kApp1Id);
-
-  apps::ProtocolHandlerInfo handler2_info;
-  handler2_info.protocol = "web+test";
-  handler2_info.url = GURL(kApp1Url);
-  auto handler2 = GetProtocolHandler(handler2_info, kApp1Id);
-
-  RegisterProtocolHandlersWithOs(kApp1Id, kApp1Name, GetProfile(),
-                                 {handler1_info, handler2_info},
-                                 base::DoNothing());
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler1));
-  EXPECT_TRUE(protocol_handler_registry()->IsDefault(handler1));
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler2));
-  EXPECT_TRUE(protocol_handler_registry()->IsDefault(handler2));
-}
-
-TEST_F(WebAppProtocolHandlerRegistrationMacTest,
-       RegisterMultipleHandlersWithSameScheme) {
-  apps::ProtocolHandlerInfo handler1_info;
-  handler1_info.protocol = "mailto";
-  handler1_info.url = GURL(kApp1Url);
-  auto handler1 = GetProtocolHandler(handler1_info, kApp1Id);
-
-  RegisterProtocolHandlersWithOs(kApp1Id, kApp1Name, GetProfile(),
-                                 {handler1_info}, base::DoNothing());
-
-  apps::ProtocolHandlerInfo handler2_info;
-  handler2_info.protocol = "mailto";
-  handler2_info.url = GURL(kApp2Url);
-  auto handler2 = GetProtocolHandler(handler2_info, kApp2Id);
-
-  RegisterProtocolHandlersWithOs(kApp2Id, kApp2Name, GetProfile(),
-                                 {handler2_info}, base::DoNothing());
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler1));
-  EXPECT_TRUE(protocol_handler_registry()->IsDefault(handler1));
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler2));
-  EXPECT_FALSE(protocol_handler_registry()->IsDefault(handler2));
-}
-
-TEST_F(WebAppProtocolHandlerRegistrationMacTest, UnregisterHandler) {
-  apps::ProtocolHandlerInfo handler_info;
-  handler_info.protocol = "mailto";
-  handler_info.url = GURL(kApp1Url);
-  auto handler = GetProtocolHandler(handler_info, kApp1Id);
-
-  RegisterProtocolHandlersWithOs(kApp1Id, kApp1Name, GetProfile(),
-                                 {handler_info}, base::DoNothing());
-
-  ASSERT_TRUE(protocol_handler_registry()->IsRegistered(handler));
-  ASSERT_TRUE(protocol_handler_registry()->IsDefault(handler));
-
-  UnregisterProtocolHandlersWithOs(kApp1Id, GetProfile(), {handler_info});
-
-  EXPECT_FALSE(protocol_handler_registry()->IsRegistered(handler));
-  EXPECT_FALSE(protocol_handler_registry()->IsDefault(handler));
-}
-
-}  // namespace web_app
diff --git a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_win.cc b/chrome/browser/web_applications/components/web_app_protocol_handler_registration_win.cc
index cc1d7de..b54defb 100644
--- a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_win.cc
+++ b/chrome/browser/web_applications/components/web_app_protocol_handler_registration_win.cc
@@ -65,25 +65,20 @@
   base::FilePath icon_path = web_app::internals::GetIconFilePath(
       web_app_path, base::AsString16(app_name));
 
-  ShellUtil::AddApplicationClass(web_app::GetProgIdForApp(profile_path, app_id),
-                                 app_specific_launcher_command,
+  std::wstring prog_id = web_app::GetProgIdForApp(profile_path, app_id);
+  ShellUtil::AddApplicationClass(prog_id, app_specific_launcher_command,
                                  user_visible_app_name, user_visible_app_name,
                                  icon_path);
 
-  // Post to UI thread to access ProtocolHandlerRegistry.
-  // TODO(crbug.com/1174805): We should move this to ProtocolHandlerManager and
-  // use a callback instead.
-  content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](Profile* profile, const web_app::AppId& app_id,
-             std::vector<apps::ProtocolHandlerInfo> protocol_handlers) {
-            ProtocolHandlerRegistry* registry =
-                ProtocolHandlerRegistryFactory::GetForBrowserContext(profile);
+  std::vector<std::wstring> wstring_protocols;
+  wstring_protocols.reserve(protocol_handlers.size());
 
-            registry->RegisterAppProtocolHandlers(app_id, protocol_handlers);
-          },
-          profile, app_id, std::move(protocol_handlers)));
+  for (const auto& protocol_handler : protocol_handlers) {
+    wstring_protocols.push_back(base::UTF8ToWide(protocol_handler.protocol));
+  }
+
+  // Add protocol associations to the Windows registry.
+  ShellUtil::AddAppProtocolAssociations(wstring_protocols, prog_id);
 }
 
 void UnregisterProtocolHandlersWithOsInBackground(
@@ -102,19 +97,13 @@
   // by default doesn't remove the web application directory.
   base::DeleteFile(app_specific_launcher_path);
 
-  // Clean up application class registry key.
+  // Remove application class registry key.
   ShellUtil::DeleteApplicationClass(prog_id);
-}
 
-// TODO(crbug/1019239): Update CheckAndUpdateExternalInstallations
-// to receive a callback that returns a bool. For now, return the call back
-// below for test purposes (StartupBrowserWebAppProtocolHandlingTest).
-void VerifyExternalInstallations(const base::FilePath& cur_profile_path,
-                                 const web_app::AppId& app_id,
-                                 base::OnceCallback<void(bool)> callback) {
-  web_app::CheckAndUpdateExternalInstallations(cur_profile_path, app_id,
-                                               base::DoNothing::Once<bool>());
-  std::move(callback).Run(true);
+  // Remove protocol associations from the Windows registry.
+  ShellUtil::RemoveAppProtocolAssociations(
+      web_app::GetProgIdForApp(profile_path, app_id),
+      /*elevate_if_not_admin=*/true);
 }
 
 }  // namespace
@@ -133,25 +122,19 @@
   std::wstring app_name_extension =
       GetAppNameExtensionForNextInstall(app_id, profile->GetPath());
 
+  // TODO(crbug/1019239): Update CheckAndUpdateExternalInstallations
+  // to receive a callback that returns a bool.
   base::ThreadPool::PostTaskAndReply(
       FROM_HERE,
       {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
       base::BindOnce(&RegisterProtocolHandlersWithOSInBackground, app_id,
                      base::UTF8ToWide(app_name), profile, profile->GetPath(),
                      std::move(protocol_handlers), app_name_extension),
-      base::BindOnce(&VerifyExternalInstallations, profile->GetPath(), app_id,
-                     std::move(callback)));
+      base::BindOnce(&CheckAndUpdateExternalInstallations, profile->GetPath(),
+                     app_id, std::move(callback)));
 }
 
-void UnregisterProtocolHandlersWithOs(
-    const AppId& app_id,
-    Profile* profile,
-    std::vector<apps::ProtocolHandlerInfo> protocol_handlers) {
-  ProtocolHandlerRegistry* registry =
-      ProtocolHandlerRegistryFactory::GetForBrowserContext(profile);
-
-  registry->DeregisterAppProtocolHandlers(app_id, protocol_handlers);
-
+void UnregisterProtocolHandlersWithOs(const AppId& app_id, Profile* profile) {
   base::ThreadPool::PostTaskAndReply(
       FROM_HERE,
       {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
diff --git a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_win_unittest.cc b/chrome/browser/web_applications/components/web_app_protocol_handler_registration_win_unittest.cc
index dc33a66..df8750b 100644
--- a/chrome/browser/web_applications/components/web_app_protocol_handler_registration_win_unittest.cc
+++ b/chrome/browser/web_applications/components/web_app_protocol_handler_registration_win_unittest.cc
@@ -4,12 +4,25 @@
 
 #include "chrome/browser/web_applications/components/web_app_protocol_handler_registration.h"
 
+#include <string>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
 #include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/test/bind.h"
-#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
-#include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
+#include "base/test/test_reg_util_win.h"
+#include "base/win/registry.h"
+#include "base/win/windows_version.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/web_applications/components/web_app_handler_registration_utils_win.h"
 #include "chrome/common/chrome_constants.h"
+#include "chrome/common/custom_handlers/protocol_handler.h"
+#include "chrome/install_static/install_util.h"
+#include "chrome/installer/util/shell_util.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
@@ -24,21 +37,6 @@
 const char kApp2Id[] = "app2_id";
 const char kApp2Name[] = "app2 name";
 const char kApp2Url[] = "https://app2.com/%s";
-
-ProtocolHandler GetProtocolHandler(
-    const apps::ProtocolHandlerInfo& handler_info,
-    const std::string& app_id) {
-  return ProtocolHandler::CreateWebAppProtocolHandler(handler_info.protocol,
-                                                      handler_info.url, app_id);
-}
-
-std::unique_ptr<KeyedService> BuildProtocolHandlerRegistry(
-    content::BrowserContext* context) {
-  Profile* profile = Profile::FromBrowserContext(context);
-  return std::make_unique<ProtocolHandlerRegistry>(
-      profile, std::make_unique<ProtocolHandlerRegistry::Delegate>());
-}
-
 }  // namespace
 
 namespace web_app {
@@ -53,9 +51,6 @@
     ASSERT_TRUE(testing_profile_manager_->SetUp());
     profile_ =
         testing_profile_manager_->CreateTestingProfile(chrome::kInitialProfile);
-
-    ProtocolHandlerRegistryFactory::GetInstance()->SetTestingFactory(
-        profile_, base::BindRepeating(&BuildProtocolHandlerRegistry));
   }
 
   void TearDown() override {
@@ -64,9 +59,107 @@
   }
 
   Profile* GetProfile() { return profile_; }
+  ProfileManager* profile_manager() {
+    return testing_profile_manager_->profile_manager();
+  }
+  TestingProfileManager* testing_profile_manager() {
+    return testing_profile_manager_.get();
+  }
 
-  ProtocolHandlerRegistry* protocol_handler_registry() {
-    return ProtocolHandlerRegistryFactory::GetForBrowserContext(GetProfile());
+  // Ensures that URLAssociations entries are created for a given protocol.
+  // "<root hkey>\Software\<prog_id>\Capabilities\URLAssociations\<protocol>".
+  bool ProgIdRegisteredForProtocol(const std::string& protocol,
+                                   const AppId& app_id,
+                                   Profile* profile) {
+    std::wstring prog_id = GetProgIdForApp(profile->GetPath(), app_id);
+
+    std::wstring url_associations_key_name(install_static::GetRegistryPath());
+    url_associations_key_name.append(ShellUtil::kRegAppProtocolHandlers);
+    url_associations_key_name.push_back(base::FilePath::kSeparators[0]);
+    url_associations_key_name.append(prog_id);
+    url_associations_key_name.append(L"\\Capabilities");
+    url_associations_key_name.append(L"\\URLAssociations");
+
+    HKEY root = base::win::GetVersion() == base::win::Version::WIN7
+                    ? HKEY_LOCAL_MACHINE
+                    : HKEY_CURRENT_USER;
+
+    base::win::RegKey key;
+    std::wstring value;
+    bool association_exists = key.Open(root, url_associations_key_name.c_str(),
+                                       KEY_READ) == ERROR_SUCCESS;
+
+    bool entry_matches = (key.ReadValue(base::UTF8ToWide(protocol).c_str(),
+                                        &value) == ERROR_SUCCESS) &&
+                         value == prog_id;
+
+    return association_exists && entry_matches;
+  }
+
+  void AddAndVerifyProtocolAssociations(const AppId& app_id,
+                                        const std::string& app_name,
+                                        const std::string& app_url,
+                                        Profile* profile,
+                                        const char* app_name_extension) {
+    std::string sanitized_app_name(app_name);
+    sanitized_app_name.append(app_name_extension);
+    base::FilePath expected_app_launcher_path =
+        GetLauncherPathForApp(profile, app_id, sanitized_app_name);
+
+    apps::ProtocolHandlerInfo handler1_info;
+    handler1_info.protocol = "mailto";
+    handler1_info.url = GURL(app_url);
+
+    apps::ProtocolHandlerInfo handler2_info;
+    handler2_info.protocol = "web+test";
+    handler2_info.url = GURL(app_url);
+
+    base::RunLoop run_loop;
+    RegisterProtocolHandlersWithOs(
+        app_id, app_name, profile, {handler1_info, handler2_info},
+        base::BindLambdaForTesting([&](bool success) {
+          EXPECT_TRUE(success);
+          run_loop.Quit();
+        }));
+    run_loop.Run();
+
+    base::FilePath registered_app_path = ShellUtil::GetApplicationPathForProgId(
+        GetProgIdForApp(profile->GetPath(), app_id));
+
+    EXPECT_TRUE(base::PathExists(registered_app_path));
+    EXPECT_EQ(registered_app_path, expected_app_launcher_path);
+
+    // Both protocols should have been registered with the OS registry.
+    EXPECT_TRUE(
+        ProgIdRegisteredForProtocol(handler1_info.protocol, app_id, profile));
+    EXPECT_TRUE(
+        ProgIdRegisteredForProtocol(handler2_info.protocol, app_id, profile));
+  }
+
+  // Gets the launcher file path for |sanitized_app_name|. If not
+  // on Win7, the name will have the ".exe" extension.
+  base::FilePath GetAppSpecificLauncherFilePath(
+      const std::string& sanitized_app_name) {
+    base::FilePath app_specific_launcher_filepath(
+        base::ASCIIToWide(sanitized_app_name));
+    if (base::win::GetVersion() > base::win::Version::WIN7) {
+      app_specific_launcher_filepath =
+          app_specific_launcher_filepath.AddExtension(L"exe");
+    }
+    return app_specific_launcher_filepath;
+  }
+
+  // Returns the expected app launcher path inside the subdirectory for
+  // |app_id|.
+  base::FilePath GetLauncherPathForApp(Profile* profile,
+                                       const AppId app_id,
+                                       const std::string& sanitized_app_name) {
+    base::FilePath web_app_dir(GetOsIntegrationResourcesDirectoryForApp(
+        profile->GetPath(), app_id, GURL()));
+    base::FilePath app_specific_launcher_filepath =
+        GetAppSpecificLauncherFilePath(sanitized_app_name);
+
+    return web_app_dir.Append(app_specific_launcher_filepath);
   }
 
  private:
@@ -76,94 +169,130 @@
   TestingProfile* profile_;
 };
 
-TEST_F(WebAppProtocolHandlerRegistrationWinTest, RegisterHandlers) {
-  apps::ProtocolHandlerInfo handler1_info;
-  handler1_info.protocol = "mailto";
-  handler1_info.url = GURL(kApp1Url);
-  auto handler1 = GetProtocolHandler(handler1_info, kApp1Id);
-
-  apps::ProtocolHandlerInfo handler2_info;
-  handler2_info.protocol = "web+test";
-  handler2_info.url = GURL(kApp1Url);
-  auto handler2 = GetProtocolHandler(handler2_info, kApp1Id);
-
-  base::RunLoop run_loop;
-  RegisterProtocolHandlersWithOs(kApp1Name, kApp1Id, GetProfile(),
-                                 {handler1_info, handler2_info},
-                                 base::BindLambdaForTesting([&](bool success) {
-                                   EXPECT_TRUE(success);
-                                   run_loop.Quit();
-                                 }));
-  run_loop.Run();
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler1));
-  EXPECT_TRUE(protocol_handler_registry()->IsDefault(handler1));
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler2));
-  EXPECT_TRUE(protocol_handler_registry()->IsDefault(handler2));
+TEST_F(WebAppProtocolHandlerRegistrationWinTest,
+       AddAndVerifyProtocolAssociations) {
+  AddAndVerifyProtocolAssociations(kApp1Id, kApp1Name, kApp1Url, GetProfile(),
+                                   "");
 }
 
 TEST_F(WebAppProtocolHandlerRegistrationWinTest,
        RegisterMultipleHandlersWithSameScheme) {
-  apps::ProtocolHandlerInfo handler1_info;
-  handler1_info.protocol = "mailto";
-  handler1_info.url = GURL(kApp1Url);
-  auto handler1 = GetProtocolHandler(handler1_info, kApp1Id);
-
-  base::RunLoop handler1_run_loop;
-  RegisterProtocolHandlersWithOs(kApp1Name, kApp1Id, GetProfile(),
-                                 {handler1_info},
-                                 base::BindLambdaForTesting([&](bool success) {
-                                   EXPECT_TRUE(success);
-                                   handler1_run_loop.Quit();
-                                 }));
-  handler1_run_loop.Run();
-
-  apps::ProtocolHandlerInfo handler2_info;
-  handler2_info.protocol = "mailto";
-  handler2_info.url = GURL(kApp2Url);
-  auto handler2 = GetProtocolHandler(handler2_info, kApp2Id);
-
-  base::RunLoop handler2_run_loop;
-  RegisterProtocolHandlersWithOs(kApp2Name, kApp2Id, GetProfile(),
-                                 {handler2_info},
-                                 base::BindLambdaForTesting([&](bool success) {
-                                   EXPECT_TRUE(success);
-                                   handler2_run_loop.Quit();
-                                 }));
-  handler2_run_loop.Run();
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler1));
-  EXPECT_TRUE(protocol_handler_registry()->IsDefault(handler1));
-
-  EXPECT_TRUE(protocol_handler_registry()->IsRegistered(handler2));
-  EXPECT_FALSE(protocol_handler_registry()->IsDefault(handler2));
+  AddAndVerifyProtocolAssociations(kApp1Id, kApp1Name, kApp1Url, GetProfile(),
+                                   "");
+  AddAndVerifyProtocolAssociations(kApp2Id, kApp2Name, kApp2Url, GetProfile(),
+                                   "");
 }
 
-TEST_F(WebAppProtocolHandlerRegistrationWinTest, UnregisterHandler) {
-  apps::ProtocolHandlerInfo handler_info;
-  handler_info.protocol = "mailto";
-  handler_info.url = GURL(kApp1Url);
-  auto handler = GetProtocolHandler(handler_info, kApp1Id);
+// When an app is registered in one profile, and then is registered in a second
+// profile, the disambiguation dialog for both app registrations should include
+// the profile name, e.g., "app name (Default)" and "app name (Profile 2)".
+TEST_F(WebAppProtocolHandlerRegistrationWinTest,
+       RegisterProtocolHandlersForWebAppIn2Profiles) {
+  AddAndVerifyProtocolAssociations(kApp1Id, kApp1Name, kApp1Url, GetProfile(),
+                                   "");
 
-  base::RunLoop run_loop;
-  RegisterProtocolHandlersWithOs(kApp1Name, kApp1Id, GetProfile(),
-                                 {handler_info},
-                                 base::BindLambdaForTesting([&](bool success) {
-                                   EXPECT_TRUE(success);
-                                   run_loop.Quit();
-                                 }));
-  run_loop.Run();
+  Profile* profile2 =
+      testing_profile_manager()->CreateTestingProfile("Profile 2");
+  ProfileAttributesStorage& storage =
+      profile_manager()->GetProfileAttributesStorage();
+  ASSERT_EQ(2u, storage.GetNumberOfProfiles());
 
-  ASSERT_TRUE(protocol_handler_registry()->IsRegistered(handler));
-  ASSERT_TRUE(protocol_handler_registry()->IsDefault(handler));
+  AddAndVerifyProtocolAssociations(kApp1Id, kApp1Name, kApp1Url, profile2,
+                                   " (Profile 2)");
 
-  UnregisterProtocolHandlersWithOs(kApp1Id, GetProfile(), {handler_info});
+  ShellUtil::ApplicationInfo app_info = ShellUtil::GetApplicationInfoForProgId(
+      GetProgIdForApp(GetProfile()->GetPath(), kApp1Id));
+
+  ASSERT_FALSE(app_info.application_name.empty());
+  // Profile 1's app name should now include the profile in the name.
+  EXPECT_EQ(base::WideToUTF8(app_info.application_name), "app1 name (Default)");
+
+  // Profile 1's app_launcher should include the profile in its name.
+  base::FilePath profile1_app_specific_launcher_path =
+      GetAppSpecificLauncherFilePath("app1 name (Default)");
+  base::FilePath profile1_launcher_path =
+      ShellUtil::GetApplicationPathForProgId(
+          GetProgIdForApp(GetProfile()->GetPath(), kApp1Id));
+  EXPECT_EQ(profile1_launcher_path.BaseName(),
+            profile1_app_specific_launcher_path);
+
+  // Verify that the app is still registered for "mailto" and "web+test" in
+  // profile 1.
+  EXPECT_TRUE(ProgIdRegisteredForProtocol("mailto", kApp1Id, GetProfile()));
+  EXPECT_TRUE(ProgIdRegisteredForProtocol("web+test", kApp1Id, GetProfile()));
+}
+
+// When an app is registered in two profiles, and then unregistered in one of
+// them, the remaining registration should no longer be profile-specific. It
+// should not have the profile name in app_launcher executable name, or the
+// registered app name.
+TEST_F(WebAppProtocolHandlerRegistrationWinTest,
+       UnRegisterProtocolHandlersForWebAppIn2Profiles) {
+  AddAndVerifyProtocolAssociations(kApp1Id, kApp1Name, kApp1Url, GetProfile(),
+                                   "");
+  base::FilePath app_specific_launcher_path =
+      ShellUtil::GetApplicationPathForProgId(
+          GetProgIdForApp(GetProfile()->GetPath(), kApp1Id));
+
+  Profile* profile2 =
+      testing_profile_manager()->CreateTestingProfile("Profile 2");
+  ProfileAttributesStorage& storage =
+      profile_manager()->GetProfileAttributesStorage();
+  ASSERT_EQ(2u, storage.GetNumberOfProfiles());
+  AddAndVerifyProtocolAssociations(kApp1Id, kApp1Name, kApp1Url, profile2,
+                                   " (Profile 2)");
+
+  UnregisterProtocolHandlersWithOs(kApp1Id, GetProfile());
+  base::ThreadPoolInstance::Get()->FlushForTesting();
+  base::RunLoop().RunUntilIdle();
+  base::ThreadPoolInstance::Get()->FlushForTesting();
+  EXPECT_FALSE(base::PathExists(app_specific_launcher_path));
+
+  // Verify that "(Profile 2)" was removed from the web app launcher and
+  // protocol association registry entries.
+  ShellUtil::ApplicationInfo app_info = ShellUtil::GetApplicationInfoForProgId(
+      GetProgIdForApp(profile2->GetPath(), kApp1Id));
+  ASSERT_FALSE(app_info.application_name.empty());
+
+  // Profile 2's app name should no longer include the profile in the name.
+  EXPECT_EQ(base::WideToUTF8(app_info.application_name), kApp1Name);
+
+  // Profile 2's app_launcher should no longer include the profile in its name.
+  base::FilePath profile2_app_specific_launcher_path =
+      GetAppSpecificLauncherFilePath(kApp1Name);
+  base::FilePath profile2_launcher_path =
+      ShellUtil::GetApplicationPathForProgId(
+          GetProgIdForApp(profile2->GetPath(), kApp1Id));
+  EXPECT_EQ(profile2_launcher_path.BaseName(),
+            profile2_app_specific_launcher_path);
+
+  // Verify that the app is still registered for "mailto" and "web+test" in
+  // profile 2.
+  EXPECT_TRUE(ProgIdRegisteredForProtocol("mailto", kApp1Id, profile2));
+  EXPECT_TRUE(ProgIdRegisteredForProtocol("web+test", kApp1Id, profile2));
+}
+
+// Register protocol handlers, and verify that unregistering removes the
+// registry settings and the app-specific launcher.
+TEST_F(WebAppProtocolHandlerRegistrationWinTest,
+       UnregisterProtocolHandlersForWebApp) {
+  AddAndVerifyProtocolAssociations(kApp1Id, kApp1Name, kApp1Url, GetProfile(),
+                                   "");
+  base::FilePath app_specific_launcher_path =
+      ShellUtil::GetApplicationPathForProgId(
+          GetProgIdForApp(GetProfile()->GetPath(), kApp1Id));
+
+  UnregisterProtocolHandlersWithOs(kApp1Id, GetProfile());
   base::ThreadPoolInstance::Get()->FlushForTesting();
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_FALSE(protocol_handler_registry()->IsRegistered(handler));
-  EXPECT_FALSE(protocol_handler_registry()->IsDefault(handler));
+  EXPECT_FALSE(base::PathExists(app_specific_launcher_path));
+  EXPECT_FALSE(ProgIdRegisteredForProtocol("mailto", kApp1Id, GetProfile()));
+  EXPECT_FALSE(ProgIdRegisteredForProtocol("web+test", kApp1Id, GetProfile()));
+
+  ShellUtil::ApplicationInfo app_info = ShellUtil::GetApplicationInfoForProgId(
+      GetProgIdForApp(GetProfile()->GetPath(), kApp1Id));
+  EXPECT_TRUE(app_info.application_name.empty());
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/window_placement/window_placement_permission_context_browsertest.cc b/chrome/browser/window_placement/window_placement_permission_context_browsertest.cc
index c119c1be..bee737f 100644
--- a/chrome/browser/window_placement/window_placement_permission_context_browsertest.cc
+++ b/chrome/browser/window_placement/window_placement_permission_context_browsertest.cc
@@ -162,17 +162,17 @@
   // See https://w3c.github.io/webappsec-permissions-policy/ for more
   // information on permissions policies and allowing cross-origin iframes
   // to have particular permissions.
-  //
-  // TODO(enne): This code causes a user activation, so can't check that below
-  // like other tests.  Figure out why this is and try to clear it / address it.
   EXPECT_TRUE(ExecJs(tab, R"(const frame = document.getElementById('test');
-    frame.setAttribute('allow', 'window-placement');)"));
+    frame.setAttribute('allow', 'window-placement');)",
+                     content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   GURL subframe_url(https_test_server()->GetURL("b.test", "/title1.html"));
   content::NavigateIframeToURL(tab, /*iframe_id=*/"test", subframe_url);
 
   content::RenderFrameHost* child = ChildFrameAt(tab->GetMainFrame(), 0);
   ASSERT_TRUE(child);
+  EXPECT_FALSE(tab->GetMainFrame()->HasTransientUserActivation());
+  EXPECT_FALSE(child->GetMainFrame()->HasTransientUserActivation());
 
   permissions::PermissionRequestManager* permission_request_manager =
       permissions::PermissionRequestManager::FromWebContents(tab);
@@ -181,6 +181,8 @@
       permissions::PermissionRequestManager::ACCEPT_ALL);
   EXPECT_EQ("granted", EvalJs(child, kGetScreens,
                               content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+  EXPECT_TRUE(tab->GetMainFrame()->HasTransientUserActivation());
+  EXPECT_TRUE(child->GetMainFrame()->HasTransientUserActivation());
 }
 
 }  // namespace
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 434ebf5..b3424ee 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1621410951-b7677a4a5a50589ed33e1ed404c1e098a7189e08.profdata
+chrome-win32-master-1621447189-bda12fafea78e5ddeafef2f6099b794eeb4ffb44.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index acf2d178..69715c0 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1621435901-90994329b9c6299f68e9909710dedd7ebb7a6abc.profdata
+chrome-win64-master-1621447189-0acc1aeb3b834bdede092c7b4e776bb25a5fa9c0.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 5e984f2..bbdfc370 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -23,12 +23,6 @@
     "ActivityReportingSessionType", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif  // defined(IS_CHROMEOS_ASH)
 
-#if defined(OS_ANDROID)
-// Enables showing an adaptive action button in the top toolbar.
-const base::Feature kAdaptiveButtonInTopToolbar{
-    "AdaptiveButtonInTopToolbar", base::FEATURE_DISABLED_BY_DEFAULT};
-#endif  // defined(OS_ANDROID)
-
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 // Enables or disables logging for adaptive screen brightness on Chrome OS.
 const base::Feature kAdaptiveScreenBrightnessLogging{
@@ -780,6 +774,9 @@
 // Enables additional control set 2 on the privacy sandbox settings page.
 const base::Feature kPrivacySandboxSettings2{"PrivacySandboxSettings2",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
+const base::FeatureParam<std::string> kPrivacySandboxSettings2FlocURL{
+    &kPrivacySandboxSettings2, "floc-website-url",
+    "https://privacysandbox.com/proposals/floc"};
 
 // Enables or disables push subscriptions keeping Chrome running in the
 // background when closed.
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 919a28ae..0f4c144 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -30,11 +30,6 @@
 extern const base::Feature kActivityReportingSessionType;
 #endif  // defined(IS_CHROMEOS_ASH)
 
-#if defined(OS_ANDROID)
-COMPONENT_EXPORT(CHROME_FEATURES)
-extern const base::Feature kAdaptiveButtonInTopToolbar;
-#endif  // defined(OS_ANDROID)
-
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kAdaptiveScreenBrightnessLogging;
@@ -116,6 +111,8 @@
 
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kPrivacySandboxSettings2;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::FeatureParam<std::string> kPrivacySandboxSettings2FlocURL;
 
 #if defined(OS_ANDROID)
 COMPONENT_EXPORT(CHROME_FEATURES)
diff --git a/chrome/common/extensions/api/scripting.idl b/chrome/common/extensions/api/scripting.idl
index 3a5d306..ec514a32 100644
--- a/chrome/common/extensions/api/scripting.idl
+++ b/chrome/common/extensions/api/scripting.idl
@@ -37,6 +37,11 @@
     // specified.
     [serializableFunction]InjectedFunction? func;
 
+    // The arguments to curry into a provided function. This is only valid if
+    // the <code>func</code> parameter is specified. These arguments must be
+    // JSON-serializable.
+    any[]? args;
+
     // We used to call the injected function `function`, but this is
     // incompatible with JavaScript's object declaration shorthand (see
     // https://crbug.com/1166438). We leave this silently in for backwards
diff --git a/chrome/installer/linux/common/desktop.template b/chrome/installer/linux/common/desktop.template
index 73bb065..2eb13ee1a 100644
--- a/chrome/installer/linux/common/desktop.template
+++ b/chrome/installer/linux/common/desktop.template
@@ -111,7 +111,7 @@
 Icon=@@PACKAGE@@
 Type=Application
 Categories=Network;WebBrowser;
-MimeType=application/pdf;application/rdf+xml;application/rss+xml;application/xhtml+xml;application/xhtml_xml;application/xml;image/gif;image/jpeg;image/png;image/webp;text/html;text/xml;x-scheme-handler/ftp;x-scheme-handler/http;x-scheme-handler/https;
+MimeType=application/pdf;application/rdf+xml;application/rss+xml;application/xhtml+xml;application/xhtml_xml;application/xml;image/gif;image/jpeg;image/png;image/webp;text/html;text/xml;x-scheme-handler/http;x-scheme-handler/https;
 Actions=new-window;new-private-window;
 
 [Desktop Action new-window]
diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc
index a39bd5f..9c285be 100644
--- a/chrome/installer/setup/setup_main.cc
+++ b/chrome/installer/setup/setup_main.cc
@@ -926,15 +926,35 @@
         status = installer::IN_USE_UPDATED;
       }
     } else if (cmd_line.HasSwitch(
-                   installer::switches::kDeregisterURLProtocol)) {
-      const std::wstring protocols_value = cmd_line.GetSwitchValueNative(
-          installer::switches::kDeregisterURLProtocol);
-      std::vector<std::wstring> protocols = base::SplitString(
-          protocols_value, L",", base::WhitespaceHandling::TRIM_WHITESPACE,
+                   installer::switches::kRegisterWebAppURLProtocols)) {
+      const std::wstring switch_value = cmd_line.GetSwitchValueNative(
+          installer::switches::kRegisterWebAppURLProtocols);
+      std::vector<std::wstring> switch_parts = base::SplitString(
+          switch_value, L":", base::WhitespaceHandling::TRIM_WHITESPACE,
           base::SplitResult::SPLIT_WANT_NONEMPTY);
 
-      if (!protocols.empty() && ShellUtil::RemoveAppProtocolAssociations(
-                                    protocols, chrome_exe, false)) {
+      if (switch_parts.size() == 2) {
+        std::wstring prog_id = switch_parts[0];
+        std::vector<std::wstring> protocols = base::SplitString(
+            switch_parts[1], L",", base::WhitespaceHandling::TRIM_WHITESPACE,
+            base::SplitResult::SPLIT_WANT_NONEMPTY);
+
+        // ShellUtil::RegisterChromeForProtocol performs all registration
+        // done by ShellUtil::RegisterChromeBrowser, as well as registering
+        // with Windows as capable of handling the supplied protocols.
+        if (!protocols.empty() && !prog_id.empty() &&
+            ShellUtil::RegisterApplicationForProtocols(protocols, prog_id,
+                                                       chrome_exe, false)) {
+          status = installer::IN_USE_UPDATED;
+        }
+      }
+    } else if (cmd_line.HasSwitch(
+                   installer::switches::kUnregisterWebAppProgId)) {
+      const std::wstring prog_id = cmd_line.GetSwitchValueNative(
+          installer::switches::kUnregisterWebAppProgId);
+
+      if (!prog_id.empty() &&
+          ShellUtil::RemoveAppProtocolAssociations(prog_id, false)) {
         status = installer::IN_USE_UPDATED;
       }
     } else {
diff --git a/chrome/installer/util/shell_util.cc b/chrome/installer/util/shell_util.cc
index 4b04e6b..072d79d 100644
--- a/chrome/installer/util/shell_util.cc
+++ b/chrome/installer/util/shell_util.cc
@@ -175,40 +175,6 @@
   return true;
 }
 
-// Details about a Windows application, to be entered into the registry for the
-// purpose of file associations.
-struct ApplicationInfo {
-  ApplicationInfo() : file_type_icon_index(0), application_icon_index(0) {}
-
-  // The ProgId used by Windows for file associations with this application.
-  // Must not be empty or start with a '.'.
-  std::wstring prog_id;
-  // The friendly name, and the path of the icon that will be used for files of
-  // these types when associated with this application by default. (They are NOT
-  // the name/icon that will represent the application under the Open With
-  // menu.)
-  std::wstring file_type_name;
-  base::FilePath file_type_icon_path;
-  int file_type_icon_index;
-  // The command to execute when opening a file via this association. It should
-  // contain "%1" (to tell Windows to pass the filename as an argument).
-  // TODO(mgiuca): |command_line| should be a base::CommandLine.
-  std::wstring command_line;
-  // The AppUserModelId used by Windows 8 for this application. Distinct from
-  // |prog_id|.
-  std::wstring app_id;
-
-  // User-visible details about this application. Any of these may be empty.
-  std::wstring application_name;
-  base::FilePath application_icon_path;
-  int application_icon_index;
-  std::wstring application_description;
-  std::wstring publisher_name;
-
-  // The CLSID for the application's DelegateExecute handler. May be empty.
-  std::wstring delegate_clsid;
-};
-
 // Returns the Windows browser client registration key for Chrome.  For example:
 // "Software\Clients\StartMenuInternet\Chromium[.user]".  Strictly speaking, we
 // should use the name of the executable (e.g., "chrome.exe"), but that ship has
@@ -235,7 +201,7 @@
 // needed for registering a web browser, not for general associations.
 std::vector<std::unique_ptr<RegistryEntry>> GetChromeDelegateExecuteEntries(
     const base::FilePath& chrome_exe,
-    const ApplicationInfo& app_info) {
+    const ShellUtil::ApplicationInfo& app_info) {
   std::vector<std::unique_ptr<RegistryEntry>> entries;
 
   std::wstring app_id_shell_key(ShellUtil::kRegClasses);
@@ -297,7 +263,7 @@
 
 // Gets the registry entries to register an application in the Windows registry.
 // |app_info| provides all of the information needed.
-void GetProgIdEntries(const ApplicationInfo& app_info,
+void GetProgIdEntries(const ShellUtil::ApplicationInfo& app_info,
                       std::vector<std::unique_ptr<RegistryEntry>>* entries) {
   // Basic sanity checks.
   DCHECK(!app_info.prog_id.empty());
@@ -370,7 +336,7 @@
     std::vector<std::unique_ptr<RegistryEntry>>* entries) {
   int chrome_icon_index = install_static::GetIconResourceIndex();
 
-  ApplicationInfo app_info;
+  ShellUtil::ApplicationInfo app_info;
   app_info.prog_id = GetBrowserProgId(suffix);
   app_info.file_type_name = install_static::GetProgIdDescription();
   // File types associated with Chrome are just given the Chrome icon.
@@ -1647,9 +1613,11 @@
 
 }  // namespace
 
+const wchar_t* ShellUtil::kRegAppProtocolHandlers = L"\\AppProtocolHandlers";
 const wchar_t* ShellUtil::kRegDefaultIcon = L"\\DefaultIcon";
 const wchar_t* ShellUtil::kRegShellPath = L"\\shell";
 const wchar_t* ShellUtil::kRegShellOpen = L"\\shell\\open\\command";
+const wchar_t* ShellUtil::kRegSoftware = L"Software\\";
 const wchar_t* ShellUtil::kRegStartMenuInternet =
     L"Software\\Clients\\StartMenuInternet";
 const wchar_t* ShellUtil::kRegClasses = L"Software\\Classes";
@@ -1696,6 +1664,13 @@
 
 ShellUtil::ShortcutProperties::~ShortcutProperties() {}
 
+ShellUtil::ApplicationInfo::ApplicationInfo() = default;
+
+ShellUtil::ApplicationInfo::ApplicationInfo(ApplicationInfo&& other) noexcept =
+    default;
+
+ShellUtil::ApplicationInfo::~ApplicationInfo() = default;
+
 ShellUtil::FileAssociationsAndAppName::FileAssociationsAndAppName() = default;
 
 ShellUtil::FileAssociationsAndAppName::FileAssociationsAndAppName(
@@ -1931,6 +1906,21 @@
   return icon_string;
 }
 
+absl::optional<std::pair<base::FilePath, int>> ShellUtil::ParseIconLocation(
+    const std::wstring& argument) {
+  std::vector<std::wstring> icon_parts =
+      base::SplitString(argument, std::wstring(L","), base::TRIM_WHITESPACE,
+                        base::SPLIT_WANT_NONEMPTY);
+
+  if (icon_parts.size() < 2)
+    return absl::nullopt;
+
+  int icon_index = 0;
+  base::StringToInt(icon_parts[1], &icon_index);
+
+  return std::make_pair(base::FilePath(icon_parts[0]), icon_index);
+}
+
 std::wstring ShellUtil::GetChromeShellOpenCmd(
     const base::FilePath& chrome_exe) {
   return base::CommandLine(chrome_exe).GetCommandLineStringForShell();
@@ -2666,21 +2656,21 @@
 
 // static
 bool ShellUtil::AddAppProtocolAssociations(
-    const ProtocolAssociations& protocol_associations,
-    const base::FilePath& chrome_exe) {
-  std::wstring suffix;
-  if (!GetInstallationSpecificSuffix(chrome_exe, &suffix))
-    return false;
-
-  if (!RegisterChromeForProtocols(chrome_exe, suffix, protocol_associations,
-                                  true)) {
+    const std::vector<std::wstring>& protocols,
+    const std::wstring& prog_id) {
+  base::FilePath chrome_exe;
+  if (!base::PathService::Get(base::FILE_EXE, &chrome_exe)) {
+    NOTREACHED();
     return false;
   }
 
-  for (const auto& protocol_association : protocol_associations.associations) {
+  if (!RegisterApplicationForProtocols(protocols, prog_id, chrome_exe, true)) {
+    return false;
+  }
+
+  for (const auto& protocol : protocols) {
     // This registry value tells Windows that this 'class' is a URL scheme.
-    // <root hkey>\Software\Classes\<protocol>\URL Protocol
-    const std::wstring& protocol = protocol_association.first;
+    // HKEY_CURRENT_USER\Software\Classes\<protocol>\URL Protocol
     std::wstring url_key(ShellUtil::kRegClasses);
     url_key.push_back(base::FilePath::kSeparators[0]);
     url_key.append(protocol);
@@ -2731,60 +2721,118 @@
   return true;
 }
 
-bool ShellUtil::DoesAppProtocolAssociationExist(
-    const std::wstring& protocol,
-    const std::wstring& app_progid,
-    const base::FilePath& chrome_exe) {
-  std::wstring suffix;
-  if (!GetInstallationSpecificSuffix(chrome_exe, &suffix))
-    return false;
-
-  bool user_level = InstallUtil::IsPerUserInstall();
-  uint32_t look_for_in = user_level ? RegistryEntry::LOOK_IN_HKCU_THEN_HKLM
-                                    : RegistryEntry::LOOK_IN_HKLM;
-
-  ProtocolAssociations protocol_associations({{protocol, app_progid}});
-
-  return IsChromeRegisteredForProtocolAssociations(
-      suffix, protocol_associations, look_for_in);
-}
-
-bool ShellUtil::RemoveAppProtocolAssociations(
+// static
+bool ShellUtil::RegisterApplicationForProtocols(
     const std::vector<std::wstring>& protocols,
+    const std::wstring& prog_id,
     const base::FilePath& chrome_exe,
     bool elevate_if_not_admin) {
+  HKEY root = base::win::GetVersion() == base::win::Version::WIN7
+                  ? HKEY_LOCAL_MACHINE
+                  : HKEY_CURRENT_USER;
+
+  // Register directly if we can, otherwise elevate and use the installer.
+  if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
+    std::vector<std::unique_ptr<RegistryEntry>> entries;
+
+    ShellUtil::ApplicationInfo app_info = GetApplicationInfoForProgId(prog_id);
+
+    // Build the Windows Default Programs capabilities key for the app.
+    // "<root_hkey>\Software\[CompanyPathName\]ProductPathName[install_suffix]\AppProtocolHandlers\|prog_id|\Capabilities".
+    std::wstring capabilities_path(install_static::GetRegistryPath());
+    capabilities_path.append(ShellUtil::kRegAppProtocolHandlers);
+    capabilities_path.push_back(base::FilePath::kSeparators[0]);
+    capabilities_path.append(prog_id);
+    capabilities_path.append(L"\\Capabilities");
+
+    entries.push_back(std::make_unique<RegistryEntry>(
+        capabilities_path, ShellUtil::kRegApplicationName,
+        app_info.application_name));
+
+    // Use name as app description if description from |prog_id| registration is
+    // empty. The description is required for the app to show in various places
+    // of Windows 7.
+    std::wstring app_description = app_info.application_description.empty()
+                                       ? app_info.application_name
+                                       : app_info.application_description;
+    entries.push_back(std::make_unique<RegistryEntry>(
+        capabilities_path, ShellUtil::kRegApplicationDescription,
+        app_description));
+
+    // Create URLAssociations
+    const std::wstring url_associations(
+        std::wstring(capabilities_path).append(L"\\URLAssociations"));
+
+    for (const auto& protocol : protocols) {
+      entries.push_back(
+          std::make_unique<RegistryEntry>(url_associations, protocol, prog_id));
+    }
+
+    // Add the |prog_id| value to <root hkey>\RegisteredApplications.
+    entries.push_back(std::make_unique<RegistryEntry>(
+        ShellUtil::kRegRegisteredApplications, prog_id, capabilities_path));
+
+    uint32_t look_for_in = root == HKEY_CURRENT_USER
+                               ? RegistryEntry::LOOK_IN_HKCU
+                               : RegistryEntry::LOOK_IN_HKLM;
+
+    return AreEntriesAsDesired(entries, look_for_in) ||
+           ShellUtil::AddRegistryEntries(root, entries);
+  }
+
+  // Admin rights are required to modify system-level protocol associations.
+  if (!elevate_if_not_admin)
+    return false;
+
+  // Elevate to do the whole job
+  std::wstring protocols_switch_value = base::JoinString(protocols, L",");
+  base::CommandLine::SwitchMap switches{
+      {installer::switches::kRegisterWebAppURLProtocols,
+       prog_id + L":" + protocols_switch_value}};
+  std::wstring suffix;
+
+  return GetInstallationSpecificSuffix(chrome_exe, &suffix) &&
+         ElevateAndRegisterChrome(chrome_exe, suffix, &switches);
+}
+
+bool ShellUtil::RemoveAppProtocolAssociations(const std::wstring& prog_id,
+                                              bool elevate_if_not_admin) {
+  HKEY root = base::win::GetVersion() == base::win::Version::WIN7
+                  ? HKEY_LOCAL_MACHINE
+                  : HKEY_CURRENT_USER;
+
+  if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
+    // Delete the |prog_id| value from <root hkey>\RegisteredApplications.
+    InstallUtil::DeleteRegistryValue(root,
+                                     ShellUtil::kRegRegisteredApplications,
+                                     WorkItem::kWow64Default, prog_id);
+
+    // Delete the key
+    // <root_hkey>\Software\[CompanyPathName\]ProductPathName[install_suffix]\AppProtocolHandlers\|prog_id|.
+    std::wstring app_key_path(install_static::GetRegistryPath());
+    app_key_path.append(ShellUtil::kRegAppProtocolHandlers);
+    app_key_path.push_back(base::FilePath::kSeparators[0]);
+    app_key_path.append(prog_id);
+
+    return InstallUtil::DeleteRegistryKey(root, app_key_path,
+                                          WorkItem::kWow64Default);
+  }
+
+  // Admin rights are required to modify system-level protocol associations.
+  if (!elevate_if_not_admin)
+    return false;
+
+  base::FilePath chrome_exe;
+  if (!base::PathService::Get(base::FILE_EXE, &chrome_exe))
+    return false;
+
   std::wstring suffix;
   if (!GetInstallationSpecificSuffix(chrome_exe, &suffix))
     return false;
 
-  const bool user_level = InstallUtil::IsPerUserInstall();
-  const HKEY root = DetermineRegistrationRoot(user_level);
-
-  if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
-    // We can do this operation directly. Delete associations for |protocols|.
-    std::wstring url_associations_path =
-        GetCapabilitiesKey(suffix).append(L"\\URLAssociations");
-
-    bool success = true;
-    for (const auto& protocol : protocols) {
-      if (!InstallUtil::DeleteRegistryValue(
-              root, url_associations_path, WorkItem::kWow64Default, protocol)) {
-        success = false;
-      }
-    }
-
-    return success;
-  }
-
-  if (!elevate_if_not_admin) {
-    // Admin rights are required to modify system-level protocol associations.
-    return false;
-  }
-
   // Elevate to do the whole job.
-  std::wstring switch_value = base::JoinString(protocols, L",");
   base::CommandLine::SwitchMap switches{
-      {installer::switches::kDeregisterURLProtocol, switch_value}};
+      {installer::switches::kUnregisterWebAppProgId, prog_id}};
   return ElevateAndRegisterChrome(chrome_exe, suffix, &switches);
 }
 
@@ -2806,6 +2854,7 @@
 
   app_info.prog_id = prog_id;
   app_info.file_type_name = application_description;
+  app_info.application_description = application_description;
   app_info.file_type_icon_path = icon_path;
   app_info.command_line =
       shell_open_command_line.GetCommandLineStringForShell();
@@ -2832,6 +2881,69 @@
 }
 
 // static
+ShellUtil::ApplicationInfo ShellUtil::GetApplicationInfoForProgId(
+    const std::wstring& prog_id) {
+  ApplicationInfo app_info;
+  app_info.prog_id = prog_id;
+
+  std::wstring prog_id_path(kRegClasses);
+  prog_id_path.push_back(base::FilePath::kSeparators[0]);
+  prog_id_path.append(prog_id);
+
+  RegKey class_key(HKEY_CURRENT_USER, prog_id_path.c_str(), KEY_QUERY_VALUE);
+
+  class_key.ReadValue(L"", &app_info.file_type_name);
+
+  // file_type_icon_*
+  std::wstring file_type_icon_path = prog_id_path + kRegDefaultIcon;
+  RegKey file_type_icon_key(HKEY_CURRENT_USER, file_type_icon_path.c_str(),
+                            KEY_QUERY_VALUE);
+
+  std::wstring file_type_icon_value;
+  file_type_icon_key.ReadValue(L"", &file_type_icon_value);
+  absl::optional<std::pair<base::FilePath, int>> file_type_icon_parts =
+      ShellUtil::ParseIconLocation(file_type_icon_value);
+
+  if (file_type_icon_parts.has_value()) {
+    app_info.file_type_icon_path = file_type_icon_parts->first;
+    app_info.file_type_icon_index = file_type_icon_parts->second;
+  }
+
+  // app_info.command_line
+  RegKey command_line_key(HKEY_CURRENT_USER,
+                          (prog_id_path + kRegShellOpen).c_str(),
+                          KEY_QUERY_VALUE);
+  command_line_key.ReadValue(L"", &app_info.command_line);
+
+  std::wstring application_path = prog_id_path + kRegApplication;
+  RegKey application_key(HKEY_CURRENT_USER, application_path.c_str(),
+                         KEY_QUERY_VALUE);
+
+  // app_info.app_id
+  application_key.ReadValue(kRegAppUserModelId, &app_info.app_id);
+
+  // User-visible details
+  application_key.ReadValue(kRegApplicationName, &app_info.application_name);
+  application_key.ReadValue(kRegApplicationDescription,
+                            &app_info.application_description);
+  application_key.ReadValue(kRegApplicationCompany, &app_info.publisher_name);
+
+  // application_icon_*
+  std::wstring application_icon_value;
+  application_key.ReadValue(ShellUtil::kRegApplicationIcon,
+                            &application_icon_value);
+  absl::optional<std::pair<base::FilePath, int>> application_icon_parts =
+      ShellUtil::ParseIconLocation(application_icon_value);
+
+  if (application_icon_parts.has_value()) {
+    app_info.application_icon_path = application_icon_parts.value().first;
+    app_info.application_icon_index = application_icon_parts.value().second;
+  }
+
+  return app_info;
+}
+
+// static
 ShellUtil::FileAssociationsAndAppName ShellUtil::GetFileAssociationsAndAppName(
     const std::wstring& prog_id) {
   FileAssociationsAndAppName file_associations_and_app_name;
diff --git a/chrome/installer/util/shell_util.h b/chrome/installer/util/shell_util.h
index bca7750..20013fb 100644
--- a/chrome/installer/util/shell_util.h
+++ b/chrome/installer/util/shell_util.h
@@ -227,9 +227,49 @@
     std::wstring app_name;
   };
 
+  // Details about a Windows application, to be entered into the registry for
+  // the purpose of file associations.
+  struct ApplicationInfo {
+    ApplicationInfo();
+    ApplicationInfo(ApplicationInfo&& other) noexcept;
+    ~ApplicationInfo();
+
+    // The ProgId used by Windows for file associations with this application.
+    // Must not be empty or start with a '.'.
+    std::wstring prog_id;
+    // The friendly name, and the path of the icon that will be used for files
+    // of these types when associated with this application by default. (They
+    // are NOT the name/icon that will represent the application under the Open
+    // With menu.)
+    std::wstring file_type_name;
+    base::FilePath file_type_icon_path;
+    int file_type_icon_index = 0;
+    // The command to execute when opening a file via this association. It
+    // should contain "%1" (to tell Windows to pass the filename as an
+    // argument).
+    // TODO(mgiuca): |command_line| should be a base::CommandLine.
+    std::wstring command_line;
+    // The AppUserModelId used by Windows 8 for this application. Distinct from
+    // |prog_id|.
+    std::wstring app_id;
+
+    // User-visible details about this application. Any of these may be empty.
+    std::wstring application_name;
+    base::FilePath application_icon_path;
+    int application_icon_index = 0;
+    std::wstring application_description;
+    std::wstring publisher_name;
+
+    // The CLSID for the application's DelegateExecute handler. May be empty.
+    std::wstring delegate_clsid;
+  };
+
   // Relative path of the URL Protocol registry entry (prefixed with '\').
   static const wchar_t* kRegURLProtocol;
 
+  // Registry key under which web app protocol handler prog_ids are stored.
+  static const wchar_t* kRegAppProtocolHandlers;
+
   // Relative path of DefaultIcon registry entry (prefixed with '\').
   static const wchar_t* kRegDefaultIcon;
 
@@ -240,6 +280,9 @@
   // (i.e. \\shell\\open\\command).
   static const wchar_t* kRegShellOpen;
 
+  // Relative path of registry key under which applications need to register.
+  static const wchar_t* kRegSoftware;
+
   // Relative path of registry key under which applications need to register
   // to control Windows Start menu links.
   static const wchar_t* kRegStartMenuInternet;
@@ -364,6 +407,13 @@
   static std::wstring FormatIconLocation(const base::FilePath& icon_path,
                                          int icon_index);
 
+  // Returns the pair <|icon_path|,|icon_index|> given a properly formatted icon
+  // location. The input should be formatted by FormatIconLocation above,
+  // or follow one of the formats specified in
+  // http://msdn.microsoft.com/library/windows/desktop/dd391573.aspx.
+  static absl::optional<std::pair<base::FilePath, int>> ParseIconLocation(
+      const std::wstring& argument);
+
   // This method returns the command to open URLs/files using chrome. Typically
   // this command is written to the registry under shell\open\command key.
   // |chrome_exe|: the full path to chrome.exe
@@ -705,7 +755,7 @@
   // HKCU\SOFTWARE\classes\<prog_id> capable of handling file type /
   // protocol associations.
   //
-  // |prog_id| is the ProgId used by Windows to uniquely identity this
+  // |prog_id| is the ProgId used by Windows to uniquely identify this
   // application. Must not be empty or start with a '.'.
   // |shell_open_command_line| is the command to execute when opening the app
   // via association.
@@ -726,33 +776,55 @@
   // Removes all entries of an application at HKCU\SOFTWARE\classes\<prog_id>.
   static bool DeleteApplicationClass(const std::wstring& prog_id);
 
+  // Returns application details for HKCU\SOFTWARE\classes\|prog_id|. The
+  // returned instance's members will be empty if not found.
+  static ApplicationInfo GetApplicationInfoForProgId(
+      const std::wstring& prog_id);
+
   // Returns the app name and file associations registered for a particular
   // application in the Windows registry. If there is no entry in the registry
   // for |prog_id|, nothing will be returned.
   static FileAssociationsAndAppName GetFileAssociationsAndAppName(
       const std::wstring& prog_id);
 
-  // For each association in |protocol_associations|, the web app is designated
-  // as the non-default handler for the corresponding protocol. For protocols
-  // uncontested by other handlers on the OS, the app will be promoted to
-  // default handler.
+  // For each protocol in |protocols|, the web app represented by |prog_id| is
+  // designated as the non-default handler for the corresponding protocol. For
+  // protocols uncontested by other handlers on the OS, the app will be
+  // promoted to default handler.
   static bool AddAppProtocolAssociations(
-      const ProtocolAssociations& protocol_associations,
-      const base::FilePath& chrome_exe);
-
-  // Returns whether |app_progid| is registered to handle |protocol| within the
-  // browser's protocol associations.
-  static bool DoesAppProtocolAssociationExist(const std::wstring& protocol,
-                                              const std::wstring& app_progid,
-                                              const base::FilePath& chrome_exe);
-
-  // For each protocol in |protocols|, the corresponding app handler registered
-  // within the browser's protocol associations is removed.
-  static bool RemoveAppProtocolAssociations(
       const std::vector<std::wstring>& protocols,
+      const std::wstring& prog_id);
+
+  // Registers a set of protocols for a particular application in the Windows
+  // registry.
+  //
+  // This method requires write access to HKLM (prior to Win8).
+  // If write to HKLM is required, but fails, and:
+  // - |elevate_if_not_admin| is true:
+  //   tries to launch setup.exe with admin privileges (by prompting the user
+  //   with a UAC) to do these tasks.
+  // - |elevate_if_not_admin| is false:
+  //   adds the ProgId entries to HKCU. These entries will not make the app show
+  //   in Default Programs but they are still useful because the app can be
+  //   registered to run when the user clicks on a protocol link.
+  //
+  // |protocols| is the set of protocols to register. Must not be empty.
+  // |prog_id| is the ProgId used by Windows for protocol associations with this
+  // application. Must not be empty or start with a '.'.
+  // |chrome_exe|: the full path to chrome.exe.
+  // |elevate_if_not_admin| if true will make this method try alternate methods
+  // as described above.
+  static bool RegisterApplicationForProtocols(
+      const std::vector<std::wstring>& protocols,
+      const std::wstring& prog_id,
       const base::FilePath& chrome_exe,
       bool elevate_if_not_admin);
 
+  // Removes all protocol associations for a particular web app from the Windows
+  // registry.
+  static bool RemoveAppProtocolAssociations(const std::wstring& prog_id,
+                                            bool elevate_if_not_admin);
+
   // Returns the browser's ProgId for the current install.
   static std::wstring GetProgIdForBrowser(const base::FilePath& chrome_exe);
 
diff --git a/chrome/installer/util/shell_util_unittest.cc b/chrome/installer/util/shell_util_unittest.cc
index b840d0e..fb293129 100644
--- a/chrome/installer/util/shell_util_unittest.cc
+++ b/chrome/installer/util/shell_util_unittest.cc
@@ -42,6 +42,7 @@
 const wchar_t kTestProgid[] = L"TestApp";
 const wchar_t kTestOpenCommand[] = L"C:\\test.exe";
 const wchar_t kTestApplicationName[] = L"Test Application";
+const wchar_t kTestApplicationDescription[] = L"Application Description";
 const wchar_t kTestFileTypeName[] = L"Test File Type";
 const wchar_t kTestIconPath[] = L"D:\\test.ico";
 const wchar_t* kTestFileExtensions[] = {
@@ -1097,14 +1098,6 @@
 
   base::FilePath& chrome_exe() { return chrome_exe_; }
 
-  std::wstring url_associations_key_name() {
-    std::wstring browser_installation_key =
-        install_static::GetBaseAppName().append(
-            ShellUtil::GetCurrentInstallationSuffix(chrome_exe()));
-    return std::wstring(ShellUtil::kRegStartMenuInternet) + L"\\" +
-           browser_installation_key + L"\\Capabilities\\URLAssociations";
-  }
-
  private:
   registry_util::RegistryOverrideManager registry_overrides_;
   base::ScopedTempDir temp_dir_;
@@ -1283,36 +1276,90 @@
   EXPECT_EQ(file_associations_and_app_name.file_associations, FileExtensions());
 }
 
+TEST_F(ShellUtilRegistryTest, GetApplicationInfoForProgId) {
+  ShellUtil::ApplicationInfo empty_application_info(
+      ShellUtil::GetApplicationInfoForProgId(kTestProgid));
+  EXPECT_TRUE(empty_application_info.application_name.empty());
+
+  // Add application class and test that GetApplicationInfoForProgId returns
+  // the registered application properties.
+  EXPECT_TRUE(ShellUtil::AddApplicationClass(
+      std::wstring(kTestProgid), OpenCommand(), kTestApplicationName,
+      kTestApplicationDescription, base::FilePath(kTestIconPath)));
+
+  ShellUtil::ApplicationInfo app_info(
+      ShellUtil::GetApplicationInfoForProgId(kTestProgid));
+
+  EXPECT_EQ(kTestProgid, app_info.prog_id);
+
+  EXPECT_EQ(app_info.application_description, app_info.file_type_name);
+  EXPECT_EQ(base::FilePath(kTestIconPath), app_info.file_type_icon_path);
+  EXPECT_EQ(0, app_info.file_type_icon_index);
+
+  EXPECT_EQ(L"\"C:\\test.exe\" --single-argument %1", app_info.command_line);
+
+  if (base::win::GetVersion() >= base::win::Version::WIN8)
+    EXPECT_EQ(L"", app_info.app_id);
+
+  EXPECT_EQ(kTestApplicationName, app_info.application_name);
+  EXPECT_EQ(kTestApplicationDescription, app_info.application_description);
+  EXPECT_EQ(L"", app_info.publisher_name);
+
+  EXPECT_EQ(base::FilePath(kTestIconPath), app_info.application_icon_path);
+  EXPECT_EQ(0, app_info.application_icon_index);
+}
+
 TEST_F(ShellUtilRegistryTest, AddAppProtocolAssociations) {
-  // Create protocol associations.
-  std::wstring app_progid1 = L"app_progid1";
-  std::wstring app_progid2 = L"app_progid2";
+  // Create test protocol associations.
+  const std::wstring app_progid = L"app_progid1";
+  const std::vector<std::wstring> app_protocols = {L"web+test", L"mailto"};
 
-  ShellUtil::ProtocolAssociations protocol_associations;
-  protocol_associations.associations[L"web+test"] = app_progid1;
-  protocol_associations.associations[L"mailto"] = app_progid2;
+  ASSERT_TRUE(ShellUtil::AddAppProtocolAssociations(app_protocols, app_progid));
 
-  ASSERT_TRUE(ShellUtil::AddAppProtocolAssociations(protocol_associations,
-                                                    chrome_exe()));
-
-  // Ensure that the registry keys have been correctly set.
+  // Ensure that classes were created for each protocol.
+  // HKEY_CURRENT_USER\Software\Classes\<protocol>\URL Protocol
   base::win::RegKey key;
   std::wstring value;
   ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER,
                                     L"Software\\Classes\\web+test", KEY_READ));
   EXPECT_TRUE(key.HasValue(L"URL Protocol"));
+  ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER,
+                                    L"Software\\Classes\\mailto", KEY_READ));
+  EXPECT_TRUE(key.HasValue(L"URL Protocol"));
+
+  // Ensure that URLAssociations entries were created for each protocol.
+  // "<root_hkey>\Software\[CompanyPathName\]ProductPathName[install_suffix]\AppProtocolHandlers\|prog_id|\Capabilities\URLAssociations\<protocol>".
+  std::wstring capabilities_path(install_static::GetRegistryPath());
+  capabilities_path.append(ShellUtil::kRegAppProtocolHandlers);
+  capabilities_path.push_back(base::FilePath::kSeparators[0]);
+  capabilities_path.append(app_progid);
+  capabilities_path.append(L"\\Capabilities");
+
+  const std::wstring url_associations_key_name =
+      capabilities_path + L"\\URLAssociations";
 
   HKEY root = base::win::GetVersion() == base::win::Version::WIN7
                   ? HKEY_LOCAL_MACHINE
                   : HKEY_CURRENT_USER;
   ASSERT_EQ(ERROR_SUCCESS,
-            key.Open(root, url_associations_key_name().c_str(), KEY_READ));
+            key.Open(root, url_associations_key_name.c_str(), KEY_READ));
 
   ASSERT_EQ(ERROR_SUCCESS, key.ReadValue(L"web+test", &value));
-  EXPECT_EQ(app_progid1, std::wstring(value));
+  EXPECT_EQ(app_progid, std::wstring(value));
 
   ASSERT_EQ(ERROR_SUCCESS, key.ReadValue(L"mailto", &value));
-  EXPECT_EQ(app_progid2, std::wstring(value));
+  EXPECT_EQ(app_progid, std::wstring(value));
+
+  // Ensure that app was registered correctly under RegisteredApplications.
+  // <root hkey>\RegisteredApplications\<prog_id>
+  ASSERT_EQ(
+      ERROR_SUCCESS,
+      key.Open(root,
+               std::wstring(ShellUtil::kRegRegisteredApplications).c_str(),
+               KEY_READ));
+
+  ASSERT_EQ(ERROR_SUCCESS, key.ReadValue(app_progid.c_str(), &value));
+  EXPECT_EQ(capabilities_path, std::wstring(value));
 }
 
 TEST_F(ShellUtilRegistryTest, ToAndFromCommandLineArgument) {
@@ -1324,9 +1371,6 @@
   protocol_associations.associations[L"web+test"] = app_progid1;
   protocol_associations.associations[L"mailto"] = app_progid2;
 
-  ASSERT_TRUE(ShellUtil::AddAppProtocolAssociations(protocol_associations,
-                                                    chrome_exe()));
-
   // Ensure the above protocol_associations creates correct command line
   // arguments correctly.
   std::wstring command_line = protocol_associations.ToCommandLineArgument();
@@ -1342,45 +1386,39 @@
             parsed_protocol_associations.value().associations[L"web+test"]);
 }
 
-TEST_F(ShellUtilRegistryTest, DoesAppProtocolAssocationExist) {
-  std::wstring app_progid = L"app_progid";
-  ShellUtil::ProtocolAssociations protocol_associations;
-  protocol_associations.associations[L"web+test"] = app_progid;
-
-  // Create protocol associations and ensure that
-  // DoesAppProtocolAssociationExist returns true.
-  ASSERT_TRUE(ShellUtil::AddAppProtocolAssociations(protocol_associations,
-                                                    chrome_exe()));
-  EXPECT_TRUE(ShellUtil::DoesAppProtocolAssociationExist(
-      L"web+test", app_progid, chrome_exe()));
-
-  // Delete protocol associations and ensure that
-  // DoesAppProtocolAssociationExist returns false.
-  ASSERT_TRUE(ShellUtil::RemoveAppProtocolAssociations({L"web+test"},
-                                                       chrome_exe(), false));
-  EXPECT_FALSE(ShellUtil::DoesAppProtocolAssociationExist(
-      L"web+test", app_progid, chrome_exe()));
-}
-
 TEST_F(ShellUtilRegistryTest, RemoveAppProtocolAssociations) {
-  std::wstring app_progid = L"app_progid";
-  ShellUtil::ProtocolAssociations protocol_associations;
-  protocol_associations.associations[L"web+test"] = app_progid;
-  ASSERT_TRUE(ShellUtil::AddAppProtocolAssociations(protocol_associations,
-                                                    chrome_exe()));
+  // Create test protocol associations.
+  const std::wstring app_progid = L"app_progid1";
+  const std::vector<std::wstring> app_protocols = {L"web+test"};
+
+  ASSERT_TRUE(ShellUtil::AddAppProtocolAssociations(app_protocols, app_progid));
 
   // Delete associations and ensure that the protocol entry does not exist.
-  EXPECT_TRUE(ShellUtil::RemoveAppProtocolAssociations({L"web+test"},
-                                                       chrome_exe(), false));
+  EXPECT_TRUE(ShellUtil::RemoveAppProtocolAssociations(app_progid, false));
+
+  // Ensure that the software registration key was removed.
+  // "<root_hkey>\Software\[CompanyPathName\]ProductPathName[install_suffix]\AppProtocolHandlers\|prog_id|".
+  std::wstring capabilities_path(install_static::GetRegistryPath());
+  capabilities_path.append(ShellUtil::kRegAppProtocolHandlers);
+  capabilities_path.push_back(base::FilePath::kSeparators[0]);
+  capabilities_path.append(app_progid);
 
   HKEY root = base::win::GetVersion() == base::win::Version::WIN7
                   ? HKEY_LOCAL_MACHINE
                   : HKEY_CURRENT_USER;
   base::win::RegKey key;
-  std::wstring value;
-  ASSERT_EQ(ERROR_SUCCESS,
-            key.Open(root, url_associations_key_name().c_str(), KEY_READ));
-  EXPECT_FALSE(key.HasValue(L"web+test"));
+
+  ASSERT_EQ(ERROR_FILE_NOT_FOUND,
+            key.Open(root, capabilities_path.c_str(), KEY_READ));
+
+  // Ensure that the RegisteredApplications entry was removed.
+  // <root hkey>\RegisteredApplications\<prog_id>
+  ASSERT_EQ(
+      ERROR_SUCCESS,
+      key.Open(root,
+               std::wstring(ShellUtil::kRegRegisteredApplications).c_str(),
+               KEY_READ));
+  EXPECT_FALSE(key.HasValue(app_progid.c_str()));
 
   // Protocol class entry should still exist after the deleted association is
   // removed so that other associations are not affected.
diff --git a/chrome/installer/util/util_constants.cc b/chrome/installer/util/util_constants.cc
index 2422adb9..8bc66950 100644
--- a/chrome/installer/util/util_constants.cc
+++ b/chrome/installer/util/util_constants.cc
@@ -38,10 +38,6 @@
 // kUninstall, otherwise it is silently ignored.
 const char kDeleteProfile[] = "delete-profile";
 
-// Specifies a comma-separated list of protocols to remove from the browser's
-// protocol associations list.
-const char kDeregisterURLProtocol[] = "deregister-url-protocol";
-
 // Disable logging
 const char kDisableLogging[] = "disable-logging";
 
@@ -137,6 +133,11 @@
 // in addition to the standard set of protocols.
 const char kRegisterURLProtocol[] = "register-url-protocol";
 
+// Specifies a comma-separated list of protocols to add as protocol associations
+// for a web application, preceded by the prog_id of that web application.
+// --register-web-app-url-protocols=<progid>:<protocol>,<protocol>,<protocol>..]
+const char kRegisterWebAppURLProtocols[] = "register-web-app-url-protocols";
+
 // Removes Chrome registration from current machine. Requires admin rights.
 const char kRemoveChromeRegistration[] = "remove-chrome-registration";
 
@@ -176,6 +177,10 @@
 // path given by --new-setup-exe.
 const char kUpdateSetupExe[] = "update-setup-exe";
 
+// Switch to pass the ProdId of a web application to be unregistered for
+// protocol handling.
+const char kUnregisterWebAppProgId[] = "unregister-web-app-prog-id";
+
 // Enable verbose logging (info level).
 const char kVerboseLogging[] = "verbose-logging";
 
diff --git a/chrome/installer/util/util_constants.h b/chrome/installer/util/util_constants.h
index 60f85638..1445a9e 100644
--- a/chrome/installer/util/util_constants.h
+++ b/chrome/installer/util/util_constants.h
@@ -159,7 +159,6 @@
 extern const char kCriticalUpdateVersion[];
 extern const char kDeleteOldVersions[];
 extern const char kDeleteProfile[];
-extern const char kDeregisterURLProtocol[];
 extern const char kDisableLogging[];
 extern const char kDoNotLaunchChrome[];
 extern const char kDoNotRegisterForUpdateLaunch[];
@@ -184,6 +183,7 @@
 extern const char kRegisterChromeBrowserSuffix[];
 extern const char kRegisterDevChrome[];
 extern const char kRegisterURLProtocol[];
+extern const char kRegisterWebAppURLProtocols[];
 extern const char kRemoveChromeRegistration[];
 extern const char kRenameChromeExe[];
 extern const char kRunAsAdmin[];
@@ -195,6 +195,7 @@
 extern const char kUncompressedArchive[];
 extern const char kUninstall[];
 extern const char kUpdateSetupExe[];
+extern const char kUnregisterWebAppProgId[];
 extern const char kVerboseLogging[];
 
 }  // namespace switches
diff --git a/chrome/renderer/chrome_render_frame_observer.cc b/chrome/renderer/chrome_render_frame_observer.cc
index 7997735..fa2931ae 100644
--- a/chrome/renderer/chrome_render_frame_observer.cc
+++ b/chrome/renderer/chrome_render_frame_observer.cc
@@ -312,7 +312,7 @@
 
 void ChromeRenderFrameObserver::SetWindowFeatures(
     blink::mojom::WindowFeaturesPtr window_features) {
-  render_frame()->GetRenderView()->GetWebView()->SetWindowFeatures(
+  render_frame()->GetWebView()->SetWindowFeatures(
       content::ConvertMojoWindowFeaturesToWebWindowFeatures(*window_features));
 }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 5f8d07e..72c9ce5 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4145,6 +4145,7 @@
       "../browser/optimization_guide/android/optimization_guide_tab_url_provider_android_unittest.cc",
       "../browser/password_manager/android/password_ui_view_android_unittest.cc",
       "../browser/search/contextual_search_policy_handler_android_unittest.cc",
+      "../browser/share/share_history_unittest.cc",
       "../browser/translate/android/translate_bridge_unittest.cc",
       "../browser/ui/android/autofill_assistant/autofill_assistant_tab_helper_unittest.cc",
       "../browser/ui/android/tab_model/tab_model_list_unittest.cc",
diff --git a/chrome/test/data/autofill/shadowdom.html b/chrome/test/data/autofill/shadowdom.html
new file mode 100644
index 0000000..6f2050d
--- /dev/null
+++ b/chrome/test/data/autofill/shadowdom.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+
+<p>go to chrome://settings/addresses to set up autofill</p>
+<p>autofill is not supported in file:// urls</p>
+
+<form>
+  <div>
+    <label for=input1>name</label>
+    <span id=input1>
+      <template shadowroot=open>
+        <span>
+          <template shadowroot=open>
+            <input>
+          </template>
+        </span>
+      </template>
+    </span>
+  </div>
+  <div>
+    <label for=input2>address</label>
+    <span id=input2>
+      <template shadowroot=open>
+        <input>
+      </template>
+    </span>
+  </div>
+  <div>
+    <label for=input3>city</label>
+    <span id=input3>
+      <template shadowroot=open>
+        <input id=shadowinput name=shadowname>
+      </template>
+    </span>
+  </div>
+  <div>
+    <label for=input4>state</label>
+    <span id=input4>
+      <template shadowroot=open>
+        <select>
+          <option value=WA>WA</option>
+          <option value=CA>CA</option>
+          <option value=TX>TX</option>
+        </select>
+      </template>
+    </span>
+  </div>
+  <div>
+    <label for=input5>zip</label>
+    <span id=input5>
+      <template shadowroot=open>
+        <input id=shadowinput>
+      </template>
+    </span>
+  </div>
+</form>
+
+<script>
+function getNameElement() {
+  return input1.shadowRoot.querySelector('span').shadowRoot.querySelector('input');
+}
+
+function getName() {
+  return getNameElement().value;
+}
+
+function getAddress() {
+  return input2.shadowRoot.querySelector('input').value;
+}
+
+function getCity() {
+  return input3.shadowRoot.querySelector('input').value;
+}
+
+function getState() {
+  return input4.shadowRoot.querySelector('select').value;
+}
+
+function getZip() {
+  return input5.shadowRoot.querySelector('input').value;
+}
+</script>
diff --git a/chrome/test/data/extensions/api_test/native_bindings/extension/background.js b/chrome/test/data/extensions/api_test/native_bindings/extension/background.js
index 37d2409a..25ef4e20 100644
--- a/chrome/test/data/extensions/api_test/native_bindings/extension/background.js
+++ b/chrome/test/data/extensions/api_test/native_bindings/extension/background.js
@@ -155,6 +155,8 @@
     chrome.test.assertTrue(!!chrome.storage.managed, 'managed');
     chrome.test.assertFalse(!!chrome.storage.managed.QUOTA_BYTES,
                             'managed quota bytes');
+    chrome.test.assertTrue(!!chrome.storage.session.QUOTA_BYTES,
+                           'session quota bytes');
     chrome.storage.local.set({foo: 'bar', nullkey: null}, () => {
       chrome.storage.local.get(['foo', 'nullkey'], (results) => {
         chrome.test.assertTrue(!!results, 'no results');
diff --git a/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js b/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js
index d10f384..667362b 100644
--- a/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js
+++ b/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js
@@ -14,6 +14,16 @@
   return document.title;
 }
 
+function injectedFunctionWithArgument(newTitle) {
+  document.title = newTitle;
+  return document.title;
+}
+
+function echoArguments() {
+  const args = Array.from(arguments);
+  return args;
+}
+
 async function getSingleTab(query) {
   const tabs = await new Promise(resolve => {
     chrome.tabs.query(query, resolve);
@@ -39,6 +49,69 @@
     chrome.test.succeed();
   },
 
+  async function changeTitleWithCurriedArguments() {
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    const customNewTitle = 'Custom Title';
+    const results = await chrome.scripting.executeScript({
+      target: {
+        tabId: tab.id,
+      },
+      func: injectedFunctionWithArgument,
+      args: [customNewTitle],
+    });
+    chrome.test.assertEq(1, results.length);
+    chrome.test.assertEq(customNewTitle, results[0].result);
+    tab = await getSingleTab(query);
+    chrome.test.assertEq(customNewTitle, tab.title);
+    chrome.test.succeed();
+  },
+
+  async function echoArgsOfDifferentTypes() {
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    const args = [
+        42,
+        0.07,
+        'foo',
+        true,
+        [1, 2, 3],
+        { key: 'value' },
+        null,
+    ];
+    const results = await chrome.scripting.executeScript({
+      target: {
+        tabId: tab.id,
+      },
+      func: echoArguments,
+      args: args,
+    });
+    chrome.test.assertEq(1, results.length);
+    chrome.test.assertEq(args, results[0].result);
+    chrome.test.succeed();
+  },
+
+  async function nullInArgsIsNotPreserved() {
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    const args = [
+        { key: 'value', nullKey: null },
+    ];
+    const results = await chrome.scripting.executeScript({
+      target: {
+        tabId: tab.id,
+      },
+      func: echoArguments,
+      args: args,
+    });
+    chrome.test.assertEq(1, results.length);
+    // Currently, null values in objects are not preserved. We should fix this,
+    // but the IDL extension schema currently does not support the preserveNull
+    // attribute, and adding it in for arrays is non-trivial.
+    chrome.test.assertEq([{ key: 'value' }], results[0].result);
+    chrome.test.succeed();
+  },
+
   async function changeTitleFromFile() {
     const query = {url: 'http://example.com/*'};
     let tab = await getSingleTab(query);
@@ -231,4 +304,41 @@
     chrome.test.assertEq(expectedTitle, tab.title);
     chrome.test.succeed();
   },
+
+  async function unserializableCurriedArguments() {
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    const expectedError =
+        'Error in invocation of scripting.executeScript(' +
+        'scripting.ScriptInjection injection, optional function callback): ' +
+        'Error at parameter \'injection\': Error at property \'args\': ' +
+        'Error at index 0: Value is unserializable.';
+    chrome.test.assertThrows(
+        chrome.scripting.executeScript,
+        [{
+          target: {
+            tabId: tab.id,
+          },
+          func: echoArguments,
+          args: [function() {}],
+        }],
+        expectedError);
+    chrome.test.succeed();
+  },
+
+  async function argsPassedWithFiles() {
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    const expectedError =
+    await chrome.test.assertPromiseRejects(
+        chrome.scripting.executeScript({
+          target: {
+            tabId: tab.id,
+          },
+          files: ['script_file.js'],
+          args: ['foo'],
+        }),
+        `Error: 'args' may not be used with file injections.`);
+    chrome.test.succeed();
+  }
 ]);
diff --git a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage/service_worker_background.js b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage/service_worker_background.js
index 50d9027..9d8bf2d 100644
--- a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage/service_worker_background.js
+++ b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage/service_worker_background.js
@@ -97,6 +97,8 @@
 var localValue = 'this is a local value';
 var syncKey = '_sync_key';
 var syncValue = 'this is a sync value';
+var sessionKey = '_session_key';
+var sessionValue = 'this is a session value';
 
 chrome.test.runTests([
   function testLocalSet() {
@@ -141,4 +143,10 @@
   function testSyncOnStorageChanged() {
     testOnStorageChanged(chrome.storage.sync);
   },
+  function testSessionSet() {
+    testSetStorage(chrome.storage.session, sessionKey, sessionValue);
+  },
+  function testSessionGet() {
+    testGetStorage(chrome.storage.session, sessionKey, sessionValue);
+  },
 ]);
diff --git a/chrome/test/data/extensions/api_test/settings/simple_test/background.js b/chrome/test/data/extensions/api_test/settings/simple_test/background.js
index f641a60..e6b03d0 100644
--- a/chrome/test/data/extensions/api_test/settings/simple_test/background.js
+++ b/chrome/test/data/extensions/api_test/settings/simple_test/background.js
@@ -6,11 +6,11 @@
 var assertTrue = chrome.test.assertTrue;
 var succeed = chrome.test.succeed;
 
-function test(stage0) {
-  var apis = [
-    chrome.storage.sync,
-    chrome.storage.local
-  ];
+function test(stage0, sessionSuported = false) {
+  let apis = [chrome.storage.sync, chrome.storage.local];
+  if (sessionSuported) {
+    apis.push(chrome.storage.session)
+  }
   apis.forEach(function(api) {
     api.succeed = chrome.test.callbackPass(api.clear.bind(api));
     stage0.call(api);
@@ -34,7 +34,7 @@
       assertEq({}, settings);
       this.succeed();
     }
-    test(stage0);
+    test(stage0, true);
   },
 
   function getWhenNonempty() {
@@ -70,7 +70,7 @@
       }, settings);
       this.succeed();
     }
-    test(stage0);
+    test(stage0, true);
   },
 
   function removeWhenEmpty() {
@@ -164,7 +164,7 @@
       }, settings);
       this.succeed();
     }
-    test(stage0);
+    test(stage0, true);
   },
 
   function clearWhenEmpty() {
@@ -295,6 +295,35 @@
     test(stage0);
   },
 
+  // TODO(crbug.com/1185226): Temporary function for `session` to test default
+  // values until `remove` and `clear` are implemented. `getWithDefaultValues()`
+  // uses `remove`, and `clear` between test calls, and `session` only has `set`
+  // and `get` implemented.
+  function getWithDefaultValuesSession() {
+    var area = chrome.storage.session;
+    function stage0() {
+      area.get({a: 'defaultA', b: ['b', 'b', 'b']}, stage1);
+    }
+    function stage1(settings) {
+      assertEq({a: 'defaultA', b: ['b', 'b', 'b']}, settings);
+      area.set({a: 'A'}, stage2);
+    }
+    function stage2() {
+      area.get({a: 'defaultA', b: ['b', 'b', 'b']}, stage3);
+    }
+    function stage3(settings) {
+      assertEq({a: 'A', b: ['b', 'b', 'b']}, settings);
+      area.set({b: {}}, stage4);
+    }
+    function stage4() {
+      area.get({a: 'defaultA', b: ['b', 'b', 'b']}, stage5);
+    }
+    function stage5(settings) {
+      assertEq({a: 'A', b: {}}, settings);
+      succeed();
+    }
+    area.clear(stage0);
+  },
 
   function quota() {
     // Just check that the constants are defined; no need to be forced to
@@ -307,6 +336,8 @@
     assertEq('undefined', typeof chrome.storage.local.QUOTA_BYTES_PER_ITEM);
     assertEq('undefined', typeof chrome.storage.local.MAX_ITEMS);
 
+    assertTrue(chrome.storage.session.QUOTA_BYTES > 0);
+
     var area = chrome.storage.sync;
     function stage0() {
       area.getBytesInUse(null, stage1);
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_extra_headers.js b/chrome/test/data/extensions/api_test/webrequest/test_extra_headers.js
index e0936f2..86a52e48 100644
--- a/chrome/test/data/extensions/api_test/webrequest/test_extra_headers.js
+++ b/chrome/test/data/extensions/api_test/webrequest/test_extra_headers.js
@@ -121,26 +121,6 @@
     });
   },
 
-  function testSpecialResponseHeadersVisibleForAuth() {
-    var url = getServerURL('auth-basic?set-cookie-if-challenged');
-    var extraHeadersListener = callbackPass(function(details) {
-      checkHeaders(details.responseHeaders, ['set-cookie'], []);
-    });
-    chrome.webRequest.onAuthRequired.addListener(extraHeadersListener,
-        {urls: [url]}, ['responseHeaders', 'extraHeaders']);
-
-    var standardListener = callbackPass(function(details) {
-      checkHeaders(details.responseHeaders, [], ['set-cookie']);
-    });
-    chrome.webRequest.onAuthRequired.addListener(standardListener,
-        {urls: [url]}, ['responseHeaders']);
-
-    navigateAndWait(url, function() {
-      chrome.webRequest.onAuthRequired.removeListener(extraHeadersListener);
-      chrome.webRequest.onAuthRequired.removeListener(standardListener);
-    });
-  },
-
   function testModifySpecialRequestHeaders() {
     // Set a cookie so the cookie request header is set.
     navigateAndWait(getSetCookieUrl('foo', 'bar'), function() {
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_extra_headers_auth.html b/chrome/test/data/extensions/api_test/webrequest/test_extra_headers_auth.html
new file mode 100644
index 0000000..8d1112ac
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest/test_extra_headers_auth.html
@@ -0,0 +1,7 @@
+<!--
+ * 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.
+-->
+<script src="framework.js"></script>
+<script src="test_extra_headers_auth.js"></script>
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_extra_headers_auth.js b/chrome/test/data/extensions/api_test/webrequest/test_extra_headers_auth.js
new file mode 100644
index 0000000..e09d738a
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest/test_extra_headers_auth.js
@@ -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.
+
+var callbackPass = chrome.test.callbackPass;
+
+runTests([
+  function testSpecialResponseHeadersVisibleForAuth() {
+    var url = getServerURL('auth-basic?set-cookie-if-challenged');
+    var extraHeadersListener = callbackPass(function(details) {
+      checkHeaders(details.responseHeaders, ['set-cookie'], []);
+    });
+    chrome.webRequest.onAuthRequired.addListener(extraHeadersListener,
+        {urls: [url]}, ['responseHeaders', 'extraHeaders']);
+
+    var standardListener = callbackPass(function(details) {
+      checkHeaders(details.responseHeaders, [], ['set-cookie']);
+    });
+    chrome.webRequest.onAuthRequired.addListener(standardListener,
+        {urls: [url]}, ['responseHeaders']);
+
+    navigateAndWait(url, function() {
+      chrome.webRequest.onAuthRequired.removeListener(extraHeadersListener);
+      chrome.webRequest.onAuthRequired.removeListener(standardListener);
+    });
+  }
+]);
diff --git a/chrome/test/data/extensions/content_verifier/storage_permission.crx b/chrome/test/data/extensions/content_verifier/storage_permission.crx
new file mode 100644
index 0000000..09e5649
--- /dev/null
+++ b/chrome/test/data/extensions/content_verifier/storage_permission.crx
Binary files differ
diff --git a/chrome/test/data/extensions/content_verifier/storage_permission.crx.INFO b/chrome/test/data/extensions/content_verifier/storage_permission.crx.INFO
new file mode 100644
index 0000000..1bc03966
--- /dev/null
+++ b/chrome/test/data/extensions/content_verifier/storage_permission.crx.INFO
@@ -0,0 +1,5 @@
+Information about storage_permission.crx:
+
+A fake test extension.
+extension_id: lpibhlnkgcbcgdjlaacppnkmmlfpapij
+The extension has a storage permission and a background.js script.
diff --git a/chrome/test/data/extensions/content_verifier/storage_permission/background.js b/chrome/test/data/extensions/content_verifier/storage_permission/background.js
new file mode 100644
index 0000000..27918c07
--- /dev/null
+++ b/chrome/test/data/extensions/content_verifier/storage_permission/background.js
@@ -0,0 +1,13 @@
+// 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.
+
+chrome.storage.local.get(['count'], ({count}) => {
+  console.log(`Count : ${count}`);
+  var newCount = count !== undefined ? count + 1 : 0;
+  chrome.storage.local.set(
+    {'count':  newCount},
+    () => {
+      console.log(`New Count : ${newCount}`);
+    });
+});
diff --git a/chrome/test/data/extensions/content_verifier/storage_permission/manifest.json b/chrome/test/data/extensions/content_verifier/storage_permission/manifest.json
new file mode 100644
index 0000000..7915ccd
--- /dev/null
+++ b/chrome/test/data/extensions/content_verifier/storage_permission/manifest.json
@@ -0,0 +1,8 @@
+{
+  "name": "Storage permission - Background script",
+  "version": "1.0",
+  "manifest_version": 2,
+  "permissions": ["storage"],
+  "background": {"scripts": ["background.js"]},
+  "update_url": "https://clients2.google.com/service/update2/crx"
+}
\ No newline at end of file
diff --git a/chrome/test/data/webui/history/history_drawer_test.js b/chrome/test/data/webui/history/history_drawer_test.js
index e8e0bbfb..535efff 100644
--- a/chrome/test/data/webui/history/history_drawer_test.js
+++ b/chrome/test/data/webui/history/history_drawer_test.js
@@ -12,7 +12,7 @@
   setup(function() {
     document.body.innerHTML = '';
     const testService = new TestBrowserService();
-    BrowserService.instance_ = testService;
+    BrowserService.setInstance(testService);
     app = document.createElement('history-app');
     document.body.appendChild(app);
     return Promise.all([
diff --git a/chrome/test/data/webui/history/history_item_focus_test.js b/chrome/test/data/webui/history/history_item_focus_test.js
index 082a2ff..69b7f49 100644
--- a/chrome/test/data/webui/history/history_item_focus_test.js
+++ b/chrome/test/data/webui/history/history_item_focus_test.js
@@ -12,7 +12,7 @@
 
   setup(function() {
     document.body.innerHTML = '';
-    BrowserService.instance_ = new TestBrowserService();
+    BrowserService.setInstance(new TestBrowserService());
 
     item = document.createElement('history-item');
     item.item = createHistoryEntry('2016-03-16 10:00', 'http://www.google.com');
diff --git a/chrome/test/data/webui/history/history_item_test.js b/chrome/test/data/webui/history/history_item_test.js
index 6057a5b..f315eb9 100644
--- a/chrome/test/data/webui/history/history_item_test.js
+++ b/chrome/test/data/webui/history/history_item_test.js
@@ -28,7 +28,7 @@
 
   setup(function() {
     document.body.innerHTML = '';
-    BrowserService.instance_ = new TestBrowserService();
+    BrowserService.setInstance(new TestBrowserService());
 
     item = document.createElement('history-item');
     item.item = TEST_HISTORY_RESULTS[0];
@@ -72,7 +72,7 @@
   setup(function() {
     document.body.innerHTML = '';
     const testService = new TestBrowserService();
-    BrowserService.instance_ = testService;
+    BrowserService.setInstance(testService);
 
     const app = document.createElement('history-app');
     document.body.appendChild(app);
diff --git a/chrome/test/data/webui/history/history_list_focus_test.js b/chrome/test/data/webui/history/history_list_focus_test.js
index 3d32c19..7240f6b 100644
--- a/chrome/test/data/webui/history/history_list_focus_test.js
+++ b/chrome/test/data/webui/history/history_list_focus_test.js
@@ -26,7 +26,7 @@
     window.history.replaceState({}, '', '/');
     document.body.innerHTML = '';
     testService = new TestBrowserService();
-    BrowserService.instance_ = testService;
+    BrowserService.setInstance(testService);
     testService.setQueryResult({
       info: createHistoryInfo(),
       value: TEST_HISTORY_RESULTS,
diff --git a/chrome/test/data/webui/history/history_list_test.js b/chrome/test/data/webui/history/history_list_test.js
index 10f12477..e3cf13a 100644
--- a/chrome/test/data/webui/history/history_list_test.js
+++ b/chrome/test/data/webui/history/history_list_test.js
@@ -69,7 +69,7 @@
     window.history.replaceState({}, '', '/');
     document.body.innerHTML = '';
     testService = new TestBrowserService();
-    BrowserService.instance_ = testService;
+    BrowserService.setInstance(testService);
 
     app = document.createElement('history-app');
   });
diff --git a/chrome/test/data/webui/history/history_metrics_test.js b/chrome/test/data/webui/history/history_metrics_test.js
index 15a8335e..5917777a 100644
--- a/chrome/test/data/webui/history/history_metrics_test.js
+++ b/chrome/test/data/webui/history/history_metrics_test.js
@@ -22,7 +22,7 @@
   setup(async () => {
     document.body.innerHTML = '';
 
-    BrowserService.instance_ = new TestBrowserService();
+    BrowserService.setInstance(new TestBrowserService());
     testService = BrowserService.getInstance();
 
     actionMap = testService.actionMap;
diff --git a/chrome/test/data/webui/history/history_overflow_menu_test.js b/chrome/test/data/webui/history/history_overflow_menu_test.js
index 981266e..d8e6a1ec 100644
--- a/chrome/test/data/webui/history/history_overflow_menu_test.js
+++ b/chrome/test/data/webui/history/history_overflow_menu_test.js
@@ -15,7 +15,7 @@
   setup(function() {
     document.body.innerHTML = '';
     const testService = new TestBrowserService();
-    BrowserService.instance_ = testService;
+    BrowserService.setInstance(testService);
 
     const app = document.createElement('history-app');
     document.body.appendChild(app);
diff --git a/chrome/test/data/webui/history/history_routing_test.js b/chrome/test/data/webui/history/history_routing_test.js
index a4a8b5c..ce244d3 100644
--- a/chrome/test/data/webui/history/history_routing_test.js
+++ b/chrome/test/data/webui/history/history_routing_test.js
@@ -23,7 +23,7 @@
   setup(function() {
     window.history.replaceState({}, '', '/');
     document.body.innerHTML = '';
-    BrowserService.instance_ = new TestBrowserService();
+    BrowserService.setInstance(new TestBrowserService());
     app = document.createElement('history-app');
     document.body.appendChild(app);
 
diff --git a/chrome/test/data/webui/history/history_routing_with_query_param_test.js b/chrome/test/data/webui/history/history_routing_with_query_param_test.js
index e07762c4..151e4f9 100644
--- a/chrome/test/data/webui/history/history_routing_with_query_param_test.js
+++ b/chrome/test/data/webui/history/history_routing_with_query_param_test.js
@@ -17,7 +17,7 @@
     document.body.innerHTML = '';
     window.history.replaceState({}, '', '/?q=query');
     testService = new TestBrowserService();
-    BrowserService.instance_ = testService;
+    BrowserService.setInstance(testService);
     // Ignore the initial empty query so that we can correctly check the
     // search term for the second call to queryHistory().
     testService.ignoreNextQuery();
diff --git a/chrome/test/data/webui/history/history_supervised_user_test.js b/chrome/test/data/webui/history/history_supervised_user_test.js
index 67634d9..dd595f5 100644
--- a/chrome/test/data/webui/history/history_supervised_user_test.js
+++ b/chrome/test/data/webui/history/history_supervised_user_test.js
@@ -18,7 +18,7 @@
   setup(function() {
     document.body.innerHTML = '';
     testService = new TestBrowserService();
-    BrowserService.instance_ = testService;
+    BrowserService.setInstance(testService);
 
     testService.setQueryResult({
       info: createHistoryInfo(),
diff --git a/chrome/test/data/webui/history/history_synced_tabs_test.js b/chrome/test/data/webui/history/history_synced_tabs_test.js
index ebc1aac..1528b2f 100644
--- a/chrome/test/data/webui/history/history_synced_tabs_test.js
+++ b/chrome/test/data/webui/history/history_synced_tabs_test.js
@@ -34,7 +34,7 @@
     document.body.innerHTML = '';
     window.history.replaceState({}, '', '/');
     testService = new TestBrowserService();
-    BrowserService.instance_ = testService;
+    BrowserService.setInstance(testService);
 
     // Need to ensure lazy_load.html has been imported so that the device
     // manager custom element is defined.
diff --git a/chrome/test/data/webui/history/history_toolbar_focus_test.js b/chrome/test/data/webui/history/history_toolbar_focus_test.js
index 62eff6cb..4346c4e 100644
--- a/chrome/test/data/webui/history/history_toolbar_focus_test.js
+++ b/chrome/test/data/webui/history/history_toolbar_focus_test.js
@@ -15,7 +15,7 @@
   setup(function() {
     document.body.innerHTML = '';
     window.history.replaceState({}, '', '/');
-    BrowserService.instance_ = new TestBrowserService();
+    BrowserService.setInstance(new TestBrowserService());
 
     app = document.createElement('history-app');
     document.body.appendChild(app);
diff --git a/chrome/test/data/webui/history/history_toolbar_test.js b/chrome/test/data/webui/history/history_toolbar_test.js
index b0b9776..821a516 100644
--- a/chrome/test/data/webui/history/history_toolbar_test.js
+++ b/chrome/test/data/webui/history/history_toolbar_test.js
@@ -19,7 +19,7 @@
   setup(function() {
     document.body.innerHTML = '';
     testService = new TestBrowserService();
-    BrowserService.instance_ = testService;
+    BrowserService.setInstance(testService);
 
     app = document.createElement('history-app');
     document.body.appendChild(app);
diff --git a/chrome/test/data/webui/history/link_click_test.js b/chrome/test/data/webui/history/link_click_test.js
index 5f4b04b..4bb7208 100644
--- a/chrome/test/data/webui/history/link_click_test.js
+++ b/chrome/test/data/webui/history/link_click_test.js
@@ -10,7 +10,7 @@
   test('click handler', async () => {
     document.body.innerHTML = '';
     const testService = new TestBrowserService();
-    BrowserService.instance_ = testService;
+    BrowserService.setInstance(testService);
 
     listenForPrivilegedLinkClicks();
     document.body.innerHTML = `
diff --git a/chrome/test/data/webui/settings/privacy_sandbox_test.js b/chrome/test/data/webui/settings/privacy_sandbox_test.js
index 393f74b..eb0b2bc6 100644
--- a/chrome/test/data/webui/settings/privacy_sandbox_test.js
+++ b/chrome/test/data/webui/settings/privacy_sandbox_test.js
@@ -106,8 +106,12 @@
     return testHatsBrowserProxy.whenCalled('tryShowPrivacySandboxSurvey');
   });
 
-  test('flocCardVisibility', function() {
+  test('phase2Visibility', function() {
+    assertTrue(isChildVisible(page, '#learnMoreButton'));
+    assertTrue(isChildVisible(page, '#pageHeader'));
+    assertTrue(isChildVisible(page, '#phase1SettingExplanation'));
     assertFalse(isChildVisible(page, '#flocCard'));
+    assertFalse(isChildVisible(page, '#phase2SettingExplanation'));
   });
 });
 
@@ -199,4 +203,12 @@
     page.set('prefs.generated.floc_enabled.value', false);
     await testPrivacySandboxBrowserProxy.whenCalled('getFlocId');
   });
+
+  test('phase2Visibility', function() {
+    assertFalse(isChildVisible(page, '#learnMoreButton'));
+    assertFalse(isChildVisible(page, '#pageHeader'));
+    assertFalse(isChildVisible(page, '#phase1SettingExplanation'));
+    assertTrue(isChildVisible(page, '#flocCard'));
+    assertTrue(isChildVisible(page, '#phase2SettingExplanation'));
+  });
 });
diff --git a/chromecast/browser/metrics/cast_browser_metrics.cc b/chromecast/browser/metrics/cast_browser_metrics.cc
index 93129e3..ca980b02 100644
--- a/chromecast/browser/metrics/cast_browser_metrics.cc
+++ b/chromecast/browser/metrics/cast_browser_metrics.cc
@@ -99,7 +99,7 @@
 
 void CastBrowserMetrics::Finalize() {
 #if !defined(OS_ANDROID)
-  // Set clean_shutdown bit.
+  // Signal that the session has exited cleanly.
   metrics_service_client_->GetMetricsService()->RecordCompletedSessionEnd();
 #endif  // !defined(OS_ANDROID)
 
diff --git a/chromecast/metrics/cast_metrics_service_client.cc b/chromecast/metrics/cast_metrics_service_client.cc
index e213660..f344a90d8 100644
--- a/chromecast/metrics/cast_metrics_service_client.cc
+++ b/chromecast/metrics/cast_metrics_service_client.cc
@@ -325,8 +325,10 @@
 
   metrics_service_->InitializeMetricsRecordingState();
 #if !defined(OS_ANDROID)
-  // Reset clean_shutdown bit after InitializeMetricsRecordingState().
-  metrics_service_->LogNeedForCleanShutdown();
+  // Signal that the session has not yet exited cleanly. We later signal that
+  // the session exited cleanly via MetricsService::RecordCompletedSessionEnd().
+  // TODO(crbug.com/1208587): See whether this can be called even earlier.
+  metrics_state_manager_->LogHasSessionShutdownCleanly(false);
 #endif  // !defined(OS_ANDROID)
 
   if (IsReportingEnabled())
diff --git a/chromeos/components/diagnostics_ui/resources/BUILD.gn b/chromeos/components/diagnostics_ui/resources/BUILD.gn
index 0a34685..22f6675e 100644
--- a/chromeos/components/diagnostics_ui/resources/BUILD.gn
+++ b/chromeos/components/diagnostics_ui/resources/BUILD.gn
@@ -22,6 +22,7 @@
     ":diagnostics_utils",
     ":ethernet_info",
     ":fake_data",
+    ":fake_input_data_provider",
     ":fake_network_health_provider",
     ":fake_system_data_provider",
     ":fake_system_routine_controller",
@@ -137,6 +138,13 @@
   deps = [ ":diagnostics_types" ]
 }
 
+js_library("fake_input_data_provider") {
+  deps = [
+    "//ash/content/common/resources:fake_method_resolver",
+    "//ui/webui/resources/js:cr.m",
+  ]
+}
+
 js_library("fake_network_health_provider") {
   deps = [
     "//ash/content/common/resources:fake_observables",
@@ -180,6 +188,7 @@
   deps = [
     ":diagnostics_types",
     ":fake_data",
+    ":fake_input_data_provider",
     ":fake_network_health_provider",
     ":fake_system_data_provider",
     ":fake_system_routine_controller",
diff --git a/chromeos/components/diagnostics_ui/resources/diagnostics_app_resources.grd b/chromeos/components/diagnostics_ui/resources/diagnostics_app_resources.grd
index 73c693f..a994f8f6 100644
--- a/chromeos/components/diagnostics_ui/resources/diagnostics_app_resources.grd
+++ b/chromeos/components/diagnostics_ui/resources/diagnostics_app_resources.grd
@@ -25,11 +25,13 @@
       <include name="IDR_DIAGNOSTICS_DATA_POINT_JS" file="${root_gen_dir}/chromeos/components/diagnostics_ui/resources/data_point.js" resource_path="data_point.js" use_base_dir="false" type="BINDATA"/>
       <include name="IDR_DIAGNOSTICS_ETHERNET_INFO_JS" file="${root_gen_dir}/chromeos/components/diagnostics_ui/resources/ethernet_info.js" resource_path="ethernet_info.js" use_base_dir="false" type="BINDATA"/>
       <include name="IDR_DIAGNOSTICS_FAKE_DATA_JS" file="fake_data.js" type="BINDATA"/>
+      <include name="IDR_DIAGNOSTICS_FAKE_INPUT_DATA_PROVIDER_JS" file="fake_input_data_provider.js" type="BINDATA"/>
       <include name="IDR_DIAGNOSTICS_FAKE_NETWORK_HEALTH_PROVIDER_JS" file="fake_network_health_provider.js" type="BINDATA"/>
       <include name="IDR_DIAGNOSTICS_FAKE_SYSTEM_DATA_PROVIDER_JS" file="fake_system_data_provider.js" type="BINDATA"/>
       <include name="IDR_DIAGNOSTICS_FAKE_SYSTEM_ROUTINE_CONTROLLER_JS" file="fake_system_routine_controller.js" type="BINDATA"/>
       <include name="IDR_DIAGNOSTICS_FONTS_CSS_JS" file="${root_gen_dir}/chromeos/components/diagnostics_ui/resources/diagnostics_fonts_css.js" resource_path="diagnostics_fonts_css.js" use_base_dir="false" type="BINDATA"/>
       <include name="IDR_DIAGNOSTICS_ICONS_JS" file="${root_gen_dir}/chromeos/components/diagnostics_ui/resources/icons.js" resource_path="icons.js" use_base_dir="false" type="BINDATA"/>
+      <include name="IDR_DIAGNOSTICS_INPUT_DATA_PROVIDER_MOJO_LITE_JS" file="${root_gen_dir}/chromeos/components/diagnostics_ui/mojom/input_data_provider.mojom-lite.js" resource_path="input_data_provider.mojom-lite.js" use_base_dir="false" type="BINDATA" />
       <include name="IDR_DIAGNOSTICS_MEMORY_CARD_JS" file="${root_gen_dir}/chromeos/components/diagnostics_ui/resources/memory_card.js" resource_path="memory_card.js" use_base_dir="false" type="BINDATA"/>
       <include name="IDR_DIAGNOSTICS_MOJO_INTERFACE_PROVIDER_JS" file="mojo_interface_provider.js" type="BINDATA"/>
       <include name="IDR_DIAGNOSTICS_MOJO_UTILS_JS" file="mojo_utils.js" type="BINDATA"/>
diff --git a/chromeos/components/diagnostics_ui/resources/diagnostics_types.js b/chromeos/components/diagnostics_ui/resources/diagnostics_types.js
index 5f58c5422..12b6a5f 100644
--- a/chromeos/components/diagnostics_ui/resources/diagnostics_types.js
+++ b/chromeos/components/diagnostics_ui/resources/diagnostics_types.js
@@ -380,3 +380,40 @@
   kEthernet: 1,
   kWiFi: 2,
 };
+
+/**
+ * Type alias for ConnectionType.
+ * @typedef {chromeos.diagnostics.mojom.ConnectionType}
+ */
+export let ConnectionType;
+
+/**
+ * Type alias for KeyboardInfo.
+ * @typedef {chromeos.diagnostics.mojom.KeyboardInfo}
+ */
+export let KeyboardInfo;
+
+/**
+ * Type alias for TouchDeviceType.
+ * @typedef {chromeos.diagnostics.mojom.TouchDeviceType}
+ */
+export let TouchDeviceType;
+
+/**
+ * Type alias for TouchDeviceInfo.
+ * @typedef {chromeos.diagnostics.mojom.TouchDeviceInfo}
+ */
+export let TouchDeviceInfo;
+
+/**
+ * Type alias for the the response from InputDataProvider.GetConnectedDevices.
+ * @typedef {{keyboards: !Array<!KeyboardInfo>,
+ *            touchDevices: !Array<!TouchDeviceInfo>}}
+ */
+export let GetConnectedDevicesResponse;
+
+/**
+ * Type alias for InputDataProviderInterface.
+ * @typedef {chromeos.diagnostics.mojom.InputDataProviderInterface}
+ */
+export let InputDataProviderInterface;
diff --git a/chromeos/components/diagnostics_ui/resources/fake_input_data_provider.js b/chromeos/components/diagnostics_ui/resources/fake_input_data_provider.js
new file mode 100644
index 0000000..ebea4d1
--- /dev/null
+++ b/chromeos/components/diagnostics_ui/resources/fake_input_data_provider.js
@@ -0,0 +1,44 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {ConnectionType, GetConnectedDevicesResponse, InputDataProviderInterface, KeyboardInfo, TouchDeviceInfo, TouchDeviceType} from './diagnostics_types.js';
+import {FakeMethodResolver} from 'chrome://resources/ash/common/fake_method_resolver.js';
+
+/**
+ * @fileoverview
+ * Implements a fake version of the InputDataProvider Mojo interface.
+ */
+
+/** @implements {InputDataProviderInterface} */
+export class FakeInputDataProvider {
+  constructor() {
+    this.methods_ = new FakeMethodResolver();
+
+    this.registerMethods();
+  }
+
+  /**
+   * Setup method resolvers.
+   */
+  registerMethods() {
+    this.methods_.register('getConnectedDevices');
+  }
+
+  /**
+   * @return {!Promise<!GetConnectedDevicesResponse>}
+   */
+  getConnectedDevices() {
+    return this.methods_.resolveMethod('getConnectedDevices');
+  }
+
+  /**
+   * Sets the values that will be returned when calling getConnectedDevices().
+   * @param {!Array<!KeyboardInfo>} keyboards
+   * @param {!Array<!TouchDeviceInfo>} touchDevices
+   */
+  setFakeConnectedDevices(keyboards, touchDevices) {
+    this.methods_.setResult('getConnectedDevices',
+                            {keyboards: keyboards, touchDevices: touchDevices});
+  }
+}
diff --git a/chromeos/components/diagnostics_ui/resources/mojo_interface_provider.js b/chromeos/components/diagnostics_ui/resources/mojo_interface_provider.js
index 71e3e50a7..ab91347 100644
--- a/chromeos/components/diagnostics_ui/resources/mojo_interface_provider.js
+++ b/chromeos/components/diagnostics_ui/resources/mojo_interface_provider.js
@@ -4,7 +4,7 @@
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 
-import {NetworkHealthProviderInterface, PowerRoutineResult, RoutineType, StandardRoutineResult, SystemDataProvider, SystemDataProviderInterface, SystemInfo, SystemRoutineController, SystemRoutineControllerInterface} from './diagnostics_types.js';
+import {InputDataProviderInterface, NetworkHealthProviderInterface, PowerRoutineResult, RoutineType, StandardRoutineResult, SystemDataProvider, SystemDataProviderInterface, SystemInfo, SystemRoutineController, SystemRoutineControllerInterface} from './diagnostics_types.js';
 import {fakeAllNetworksAvailable, fakeBatteryChargeStatus, fakeBatteryHealth, fakeBatteryInfo, fakeCellularNetwork, fakeCpuUsage, fakeEthernetNetwork, fakeMemoryUsage, fakePowerRoutineResults, fakeRoutineResults, fakeSystemInfo, fakeWifiNetwork} from './fake_data.js';
 import {FakeNetworkHealthProvider} from './fake_network_health_provider.js';
 import {FakeSystemDataProvider} from './fake_system_data_provider.js';
@@ -38,6 +38,11 @@
 let networkHealthProvider = null;
 
 /**
+ * @type {?InputDataProviderInterface}
+ */
+let inputDataProvider = null;
+
+/**
  * @param {!SystemDataProviderInterface} testProvider
  */
 export function setSystemDataProviderForTesting(testProvider) {
@@ -141,3 +146,23 @@
   assert(!!networkHealthProvider);
   return networkHealthProvider;
 }
+
+/**
+ * @param {!InputDataProviderInterface} testProvider
+ */
+export function setInputDataProviderForTesting(testProvider) {
+  inputDataProvider = testProvider;
+}
+
+/**
+ * @return {!InputDataProviderInterface}
+ */
+export function getInputDataProvider() {
+  if (!inputDataProvider) {
+    inputDataProvider =
+        chromeos.diagnostics.mojom.InputDataProvider.getRemote();
+  }
+
+  assert(!!inputDataProvider);
+  return inputDataProvider;
+}
diff --git a/chromeos/components/feature_usage/README.md b/chromeos/components/feature_usage/README.md
index 49b3c9b..3f29859 100644
--- a/chromeos/components/feature_usage/README.md
+++ b/chromeos/components/feature_usage/README.md
@@ -22,8 +22,8 @@
 * Failed attempt to use the feature.
 * Record the usage time of the feature.
 
-The first two are reported once per day. To correctly track 1-, 7-, 28-days
-users. The feature usage component encapsulates this logic.
+The first two are reported periodically every 30 minutes. To correctly track 1-,
+7-, 28-days users. The feature usage component encapsulates this logic.
 
 For more details see original [CL](https://crrev.com/c/2596263)
 
@@ -35,7 +35,6 @@
 detail below.
 
 *   [Append your feature to the usage logging features list](#Appending-your-feature)
-*   [Register your feature with a pref service](#Registering-with-pref-service)
 *   [Create a component object](#Creating-component-object) and pass the
     delegate inside.
 *   [Record feature usage](#Recording-feature-usage)
@@ -54,23 +53,10 @@
   </variant>
 ```
 
-### Registering with pref service
-
-You could use any pref service that is suitable for you. Prefs are required to
-store last daily event timestamp.
-
-```c++
-FeatureUsageMetrics::RegisterPref(registry, "YourFeature");
-```
-
 ### Creating component object
-`FeatureUsageMetrics` object requires a prefs object which must correspond to the
-pref registry used in the previous step.
-
-You also need to implement `FeatureUsageMetrics::Delegate` and pass it to the
-`FeatureUsageMetrics`. Delegate is called to report daily events (eligible,
-enabled). Note that if an object of this type is destroyed and created in the
-same day, metrics eligibility and enablement will only be reported once.
+You need to implement `FeatureUsageMetrics::Delegate` and pass it to the
+`FeatureUsageMetrics`. Delegate is called to report periodic events (eligible,
+enabled).
 
 ```c++
 class MyDelegate : public FeatureUsageMetrics::Delegate {
@@ -87,10 +73,11 @@
 
 ```c++
 feature_usage_metrics_ = std::make_unique<FeatureUsageMetrics>(
-        "YourFeature", prefs, my_delegate);
+        "YourFeature", my_delegate);
 ```
 
-`Prefs` and `MyDelegate` objects must outlive the `FeatureUsageMetrics` object.
+`YourFeature` must correspond to the histogram and never change. `MyDelegate`
+object must outlive the `FeatureUsageMetrics` object.
 
 ### Recording feature usage
 Call `feature_usage_metrics_->RecordUsage(bool success);` on every usage
@@ -98,8 +85,39 @@
 Your feature might not have failed attempts. In that case always call with
 `success=true`.
 
-Call `feature_usage_metrics_->RecordUsetime(base::TimeDelta usetime);` with
-however long the user spends using the feature, if applicable.
+`MyDelegate::IsEligible` and `MyDelegate::IsEnabled` functions must return
+`true` when `RecordUsage` is called.
+
+#### Recording usage time
+If your feature has a notion of time usage use
+`feature_usage_metrics_->StartUsage();` and
+`feature_usage_metrics_->StopUsage();` to record feature usage time.
+
+* There should be no consecutive `StartUsage` calls without `StopUsage` call
+in-between.
+* After `StartUsage` is called the usage time is reported periodically together
+with `IsEligible` and `IsEnabled`.
+* If `StartUsage` is not followed by `StopUsage` the remaining usage time is
+recorded at the object shutdown.
+* `StartUsage` must be preceded by exactly one `RecordUsage(true)`. There should
+be no `RecordUsage` calls in-between `StartUsage` and `StopUsage` calls.
+
+Example:
+```c++
+// feature_usage_metrics_->StartUsage(); should be preceded by RecordUsage(true)
+feature_usage_metrics_->RecordUsage(false);
+// feature_usage_metrics_->StartUsage(); should be preceded by RecordUsage(true)
+feature_usage_metrics_->RecordUsage(true);
+feature_usage_metrics_->StartUsage();
+feature_usage_metrics_->StopUsage();
+// feature_usage_metrics_->StartUsage(); should be preceded by RecordUsage(true)
+feature_usage_metrics_->RecordUsage(true);
+feature_usage_metrics_->RecordUsage(true);
+// feature_usage_metrics_->StartUsage(); should be preceded by exactly one RecordUsage(true)
+....
+feature_usage_metrics_->StartUsage();
+feature_usage_metrics_->reset(); // Usage time is recorded similar to StopUsage
+```
 
 ### Testing
 Use `base::HistogramTester` to verify attempt events are reported.
@@ -119,5 +137,5 @@
 // Emulate the amount of time |usetime| (base::TimeDelta) using the feature.
 histogram_tester_->ExpectTimeBucketCount(
   "ChromeOS.FeatureUsage.YouFeature.Usetime", usetime, 1);
-
 ```
+
diff --git a/chromeos/components/feature_usage/feature_usage_metrics.cc b/chromeos/components/feature_usage/feature_usage_metrics.cc
index d4962e0..b6d619d 100644
--- a/chromeos/components/feature_usage/feature_usage_metrics.cc
+++ b/chromeos/components/feature_usage/feature_usage_metrics.cc
@@ -4,98 +4,80 @@
 
 #include "chromeos/components/feature_usage/feature_usage_metrics.h"
 
+#include "base/bind.h"
 #include "base/callback.h"
+#include "base/location.h"
 #include "base/logging.h"
 #include "base/metrics/histogram.h"
 #include "base/metrics/histogram_functions.h"
-#include "components/metrics/daily_event.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
 
 namespace feature_usage {
 
 namespace {
 
-// Interval for asking metrics::DailyEvent to check whether a day has passed.
-constexpr base::TimeDelta kCheckDailyEventInternal =
-    base::TimeDelta::FromMinutes(30);
-
-constexpr char kDailySamplePrefPrefix[] = "feature_usage.daily_sample.";
 constexpr char kFeatureUsageMetricPrefix[] = "ChromeOS.FeatureUsage.";
 constexpr char kFeatureUsetimeMetricPostfix[] = ".Usetime";
 
-std::string FeatureToPref(const std::string& feature_name) {
-  return kDailySamplePrefPrefix + feature_name;
-}
-
 std::string FeatureToHistogram(const std::string& feature_name) {
   return kFeatureUsageMetricPrefix + feature_name;
 }
 
-// `DailyEventObserver` implements `metrics::DailyEvent::Observer`. It runs
-// `callback_` on the first usage and when the day has passed.
-class DailyEventObserver : public metrics::DailyEvent::Observer {
- public:
-  explicit DailyEventObserver(base::RepeatingClosure callback)
-      : callback_(std::move(callback)) {
-    DCHECK(callback_);
-  }
-
-  DailyEventObserver(const DailyEventObserver&) = delete;
-  DailyEventObserver& operator=(const DailyEventObserver&) = delete;
-  ~DailyEventObserver() override = default;
-
-  // metrics::DailyEvent::Observer:
-  void OnDailyEvent(metrics::DailyEvent::IntervalType type) final {
-    if (type == metrics::DailyEvent::IntervalType::DAY_ELAPSED ||
-        type == metrics::DailyEvent::IntervalType::FIRST_RUN) {
-      callback_.Run();
-    }
-  }
-
- private:
-  base::RepeatingClosure callback_;
-};
-
 }  // namespace
 
-FeatureUsageMetrics::FeatureUsageMetrics(const std::string& feature_name,
-                                         PrefService* pref_service,
-                                         Delegate* const delegate)
-    : FeatureUsageMetrics(feature_name, pref_service, delegate, nullptr) {}
+// First time periodic metrics are reported after 'kInitialInterval` time.
+constexpr base::TimeDelta FeatureUsageMetrics::kInitialInterval =
+    base::TimeDelta::FromMinutes(1);
+
+// Consecutive reports run every `kRepeatedInterval`
+constexpr base::TimeDelta FeatureUsageMetrics::kRepeatedInterval =
+    base::TimeDelta::FromMinutes(30);
 
 FeatureUsageMetrics::FeatureUsageMetrics(const std::string& feature_name,
-                                         PrefService* pref_service,
+                                         Delegate* const delegate)
+    : FeatureUsageMetrics(feature_name,
+                          delegate,
+                          base::DefaultTickClock::GetInstance()) {}
+
+FeatureUsageMetrics::FeatureUsageMetrics(const std::string& feature_name,
                                          Delegate* const delegate,
                                          const base::TickClock* tick_clock)
     : histogram_name_(FeatureToHistogram(feature_name)),
-      pref_name_(FeatureToPref(feature_name)),
       delegate_(delegate),
-      timer_(tick_clock ? std::make_unique<base::RepeatingTimer>(tick_clock)
-                        : std::make_unique<base::RepeatingTimer>()) {
+      tick_clock_(tick_clock),
+      timer_(std::make_unique<base::OneShotTimer>(tick_clock)) {
   DCHECK(delegate_);
 
-  daily_event_ = std::make_unique<metrics::DailyEvent>(
-      pref_service, pref_name_.c_str(), /*histogram_name=*/std::string());
-
-  auto observer = std::make_unique<DailyEventObserver>(base::BindRepeating(
-      // base::Unretained is safe because `this` owns `daily_event_` which owns
-      // the `observer`.
-      &FeatureUsageMetrics::ReportDailyMetrics, base::Unretained(this)));
-
-  daily_event_->AddObserver(std::move(observer));
-  daily_event_->CheckInterval();
-  timer_->Start(FROM_HERE, kCheckDailyEventInternal, daily_event_.get(),
-                &metrics::DailyEvent::CheckInterval);
+  // Schedule the first run some time in the future to not overload startup
+  // flow.
+  SetupTimer(kInitialInterval);
 }
 
-FeatureUsageMetrics::~FeatureUsageMetrics() = default;
-
-// static
-void FeatureUsageMetrics::RegisterPref(PrefRegistrySimple* registry,
-                                       const std::string& feature_name) {
-  metrics::DailyEvent::RegisterPref(registry, FeatureToPref(feature_name));
+void FeatureUsageMetrics::SetupTimer(base::TimeDelta delta) {
+  timer_->Start(FROM_HERE, delta,
+                base::BindOnce(&FeatureUsageMetrics::MaybeReportPeriodicMetrics,
+                               base::Unretained(this)));
 }
 
-void FeatureUsageMetrics::RecordUsage(bool success) const {
+FeatureUsageMetrics::~FeatureUsageMetrics() {
+  if (!start_usage_.is_null())
+    StopUsage();
+}
+
+void FeatureUsageMetrics::RecordUsage(bool success) {
+  DCHECK(delegate_->IsEligible());
+  // TODO(https://crbug.com/1208743) Remove this case when unit tests are fixed.
+  if (histogram_name_ != "ChromeOS.FeatureUsage.ESim") {
+    DCHECK(delegate_->IsEnabled());
+  }
+  DCHECK(start_usage_.is_null());
+#if DCHECK_IS_ON()
+  last_record_usage_outcome_ = success;
+#endif
+  MaybeReportPeriodicMetrics();
+
   Event e = success ? Event::kUsedWithSuccess : Event::kUsedWithFailure;
   base::UmaHistogramEnumeration(histogram_name_, e);
 }
@@ -105,13 +87,64 @@
                                  usetime);
 }
 
-void FeatureUsageMetrics::ReportDailyMetrics() const {
-  if (delegate_->IsEligible())
+void FeatureUsageMetrics::StartUsage() {
+  DCHECK(start_usage_.is_null());
+#if DCHECK_IS_ON()
+  DCHECK(last_record_usage_outcome_.value_or(false))
+      << "Start usage must be preceded by RecordUsage(true)";
+  last_record_usage_outcome_.reset();
+#endif
+  start_usage_ = tick_clock_->NowTicks();
+}
+
+void FeatureUsageMetrics::StopUsage() {
+  DCHECK(!start_usage_.is_null());
+#if DCHECK_IS_ON()
+  DCHECK(!last_record_usage_outcome_.has_value())
+      << "There must be no RecordUsage calls between Start and StopUsage";
+#endif
+  const base::TimeDelta use_time = tick_clock_->NowTicks() - start_usage_;
+  RecordUsetime(use_time);
+  start_usage_ = base::TimeTicks();
+}
+
+void FeatureUsageMetrics::MaybeReportPeriodicMetrics() {
+  if (!last_time_enabled_reported_.is_null()) {
+    const base::TimeDelta time_left =
+        kRepeatedInterval -
+        (tick_clock_->NowTicks() - last_time_enabled_reported_);
+    // Do not report periodic metrics more often than once per
+    // `kRepeatedInterval`.
+    if (time_left > base::TimeDelta()) {
+      // This could only happen when `RecordUsage` is called. In that case
+      // `IsEnabled` must be true. And because `last_time_enabled_reported_` is
+      // not null - `IsEnabled` was already reported recently.
+      SetupTimer(time_left);
+      return;
+    }
+  }
+
+  bool is_eligible = delegate_->IsEligible();
+  bool is_enabled = delegate_->IsEnabled();
+  DCHECK(!is_enabled || is_eligible);
+
+  if (is_eligible)
     base::UmaHistogramEnumeration(histogram_name_, Event::kEligible);
-  if (delegate_->IsEnabled()) {
-    DCHECK(delegate_->IsEligible());
+
+  if (is_enabled) {
+    last_time_enabled_reported_ = tick_clock_->NowTicks();
     base::UmaHistogramEnumeration(histogram_name_, Event::kEnabled);
   }
+
+  if (!start_usage_.is_null()) {
+    DCHECK(is_eligible);
+    DCHECK(is_enabled);
+    base::TimeDelta use_time = tick_clock_->NowTicks() - start_usage_;
+    RecordUsetime(use_time);
+    start_usage_ = tick_clock_->NowTicks();
+  }
+
+  SetupTimer(kRepeatedInterval);
 }
 
 }  // namespace feature_usage
diff --git a/chromeos/components/feature_usage/feature_usage_metrics.h b/chromeos/components/feature_usage/feature_usage_metrics.h
index aa17ace..be73a89 100644
--- a/chromeos/components/feature_usage/feature_usage_metrics.h
+++ b/chromeos/components/feature_usage/feature_usage_metrics.h
@@ -7,24 +7,19 @@
 
 #include <memory>
 
-#include "base/observer_list.h"
+#include "base/dcheck_is_on.h"
 #include "base/time/tick_clock.h"
+#include "base/time/time.h"
 #include "base/timer/timer.h"
-
-class PrefRegistrySimple;
-class PrefService;
-
-namespace metrics {
-class DailyEvent;
-}
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace feature_usage {
 
 // Helper class to unify tracking features usage by users.
 // It provides unified naming for the tracked events. Which reduces effort on
 // the data analytics side to incorporate a new feature.
-// This class also provides a way to report daily if the device is eligible for
-// the feature and whether user has it enabled.
+// This class also provides a way to report periodically if the device is
+// eligible for the feature and whether user has it enabled.
 class FeatureUsageMetrics {
  public:
   // These values are persisted to logs. Entries should not be renumbered and
@@ -37,8 +32,8 @@
     kMaxValue = kUsedWithFailure,
   };
 
-  // Consumers should implement this interface to report daily if the feature
-  // is eligible on the device and enabled.
+  // Consumers should implement this interface to report periodically if the
+  // feature is eligible on the device and enabled.
   class Delegate {
    public:
     // Whether the device is capable of running the feature.
@@ -51,13 +46,16 @@
     virtual ~Delegate() = default;
   };
 
-  // `feature_name` and `pref_service` must correspond to the `RegisterPref`
-  // call.
+  static const base::TimeDelta kInitialInterval;
+  static const base::TimeDelta kRepeatedInterval;
+
+  // `feature_name` must correspond to the entry of `FeaturesLoggingUsageEvents`
+  // and should never change.
   FeatureUsageMetrics(const std::string& feature_name,
-                      PrefService* pref_service,
                       Delegate* delegate);
+  // Custom `tick_clock` could be passed for testing purposes. Must not be
+  // nullptr.
   FeatureUsageMetrics(const std::string& feature_name,
-                      PrefService* pref_service,
                       Delegate* delegate,
                       const base::TickClock* tick_clock);
   FeatureUsageMetrics(const FeatureUsageMetrics&) = delete;
@@ -68,25 +66,31 @@
   // indicates whether the usage was successful. For example if user touches the
   // fingerprint sensor and the finger was not recognized `RecordUsage` should
   // be called with `false`.
-  void RecordUsage(bool success) const;
+  void RecordUsage(bool success);
 
-  // `RecordUsetime` should be called with the duration of time that the
-  // feature is used. All |usetime|s of the feature will be aggregated together.
-  void RecordUsetime(base::TimeDelta usetime) const;
-
-  static void RegisterPref(PrefRegistrySimple* registry,
-                           const std::string& feature_name);
+  // Use `StartUsage` and `StopUsage` to record feature usage time.
+  // See ./README.md#Recording-usage-time for more details.
+  void StartUsage();
+  void StopUsage();
 
  private:
-  void ReportDailyMetrics() const;
+  void SetupTimer(base::TimeDelta delta);
+  void MaybeReportPeriodicMetrics();
+
+  void RecordUsetime(base::TimeDelta usetime) const;
 
   const std::string histogram_name_;
-  const std::string pref_name_;
   const Delegate* const delegate_;
-  std::unique_ptr<metrics::DailyEvent> daily_event_;
 
-  // Instructs |daily_event_| to check if a day has passed.
-  std::unique_ptr<base::RepeatingTimer> timer_;
+  base::TimeTicks last_time_enabled_reported_;
+
+  const base::TickClock* const tick_clock_;
+  base::TimeTicks start_usage_;
+  std::unique_ptr<base::OneShotTimer> timer_;
+
+#if DCHECK_IS_ON()
+  absl::optional<bool> last_record_usage_outcome_;
+#endif
 };
 
 }  // namespace feature_usage
diff --git a/chromeos/components/feature_usage/feature_usage_metrics_unittest.cc b/chromeos/components/feature_usage/feature_usage_metrics_unittest.cc
index 1581e4d..2d99a6c 100644
--- a/chromeos/components/feature_usage/feature_usage_metrics_unittest.cc
+++ b/chromeos/components/feature_usage/feature_usage_metrics_unittest.cc
@@ -9,7 +9,6 @@
 #include "base/test/task_environment.h"
 #include "base/time/clock.h"
 #include "base/time/time.h"
-#include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace feature_usage {
@@ -26,10 +25,9 @@
                                 public FeatureUsageMetrics::Delegate {
  public:
   FeatureUsageMetricsTest() {
-    FeatureUsageMetrics::RegisterPref(prefs_.registry(), kTestFeature);
     ResetHistogramTester();
     feature_usage_metrics_ = std::make_unique<FeatureUsageMetrics>(
-        kTestFeature, &prefs_, this, env_.GetMockTickClock());
+        kTestFeature, this, env_.GetMockTickClock());
   }
 
   // FeatureUsageMetrics::Delegate:
@@ -49,7 +47,6 @@
 
   std::unique_ptr<base::HistogramTester> histogram_tester_;
 
-  TestingPrefServiceSimple prefs_;
   std::unique_ptr<FeatureUsageMetrics> feature_usage_metrics_;
 };
 
@@ -68,13 +65,43 @@
 }
 
 TEST_F(FeatureUsageMetricsTest, RecordUsetime) {
-  const base::TimeDelta kUsetime = base::TimeDelta::FromSeconds(10);
-  feature_usage_metrics_->RecordUsetime(kUsetime);
-  histogram_tester_->ExpectTimeBucketCount(kTestUsetimeMetric, kUsetime, 1);
+  const base::TimeDelta use_time = base::TimeDelta::FromSeconds(10);
+  feature_usage_metrics_->RecordUsage(/*success=*/true);
+  feature_usage_metrics_->StartUsage();
+  env_.FastForwardBy(use_time);
+  feature_usage_metrics_->StopUsage();
+  histogram_tester_->ExpectUniqueTimeSample(kTestUsetimeMetric, use_time, 1);
 }
 
-TEST_F(FeatureUsageMetricsTest, DailyMetricsTest) {
-  // Initial metrics should be reported on IntervalType::FIRST_RUN.
+TEST_F(FeatureUsageMetricsTest, RecordLongUsetime) {
+  size_t repeated_periods = 4;
+  const base::TimeDelta extra_small_use_time = base::TimeDelta::FromMinutes(3);
+  const base::TimeDelta use_time =
+      FeatureUsageMetrics::kRepeatedInterval * repeated_periods +
+      extra_small_use_time;
+
+  feature_usage_metrics_->RecordUsage(/*success=*/true);
+  feature_usage_metrics_->StartUsage();
+  env_.FastForwardBy(use_time);
+  feature_usage_metrics_->StopUsage();
+  histogram_tester_->ExpectTimeBucketCount(
+      kTestUsetimeMetric, FeatureUsageMetrics::kRepeatedInterval,
+      repeated_periods);
+  histogram_tester_->ExpectTimeBucketCount(kTestUsetimeMetric,
+                                           extra_small_use_time, 1);
+}
+
+TEST_F(FeatureUsageMetricsTest, PeriodicMetricsTest) {
+  // Periodic metrics are not reported on creation.
+  histogram_tester_->ExpectBucketCount(
+      kTestMetric, static_cast<int>(FeatureUsageMetrics::Event::kEligible), 0);
+  histogram_tester_->ExpectBucketCount(
+      kTestMetric, static_cast<int>(FeatureUsageMetrics::Event::kEnabled), 0);
+
+  ResetHistogramTester();
+  // Trigger initial periodic metrics report.
+  env_.FastForwardBy(FeatureUsageMetrics::kInitialInterval);
+
   histogram_tester_->ExpectBucketCount(
       kTestMetric, static_cast<int>(FeatureUsageMetrics::Event::kEligible), 1);
   histogram_tester_->ExpectBucketCount(
@@ -82,8 +109,8 @@
 
   ResetHistogramTester();
   is_enabled_ = false;
-  // Trigger IntervalType::DAY_ELAPSED event.
-  env_.FastForwardBy(base::TimeDelta::FromHours(24));
+  // Trigger repeated periodic metrics report.
+  env_.FastForwardBy(FeatureUsageMetrics::kRepeatedInterval);
   histogram_tester_->ExpectBucketCount(
       kTestMetric, static_cast<int>(FeatureUsageMetrics::Event::kEligible), 1);
   histogram_tester_->ExpectBucketCount(
@@ -91,8 +118,8 @@
 
   ResetHistogramTester();
   is_eligible_ = false;
-  // Trigger IntervalType::DAY_ELAPSED event.
-  env_.FastForwardBy(base::TimeDelta::FromHours(24));
+  // Trigger repeated periodic metrics report.
+  env_.FastForwardBy(FeatureUsageMetrics::kRepeatedInterval);
   histogram_tester_->ExpectBucketCount(
       kTestMetric, static_cast<int>(FeatureUsageMetrics::Event::kEligible), 0);
   histogram_tester_->ExpectBucketCount(
diff --git a/chromeos/components/sync_wifi/network_test_helper.cc b/chromeos/components/sync_wifi/network_test_helper.cc
index 04b3290..da26a036 100644
--- a/chromeos/components/sync_wifi/network_test_helper.cc
+++ b/chromeos/components/sync_wifi/network_test_helper.cc
@@ -33,7 +33,6 @@
   PrefProxyConfigTrackerImpl::RegisterPrefs(local_state_.registry());
   ::onc::RegisterProfilePrefs(user_prefs_.registry());
   ::onc::RegisterPrefs(local_state_.registry());
-  CellularMetricsLogger::RegisterLocalStatePrefs(local_state_.registry());
   CellularESimProfileHandlerImpl::RegisterLocalStatePrefs(
       local_state_.registry());
   NetworkMetadataStore::RegisterPrefs(user_prefs_.registry());
diff --git a/chromeos/network/cellular_metrics_logger.cc b/chromeos/network/cellular_metrics_logger.cc
index 346c4ed6..bb9c9c8 100644
--- a/chromeos/network/cellular_metrics_logger.cc
+++ b/chromeos/network/cellular_metrics_logger.cc
@@ -4,9 +4,13 @@
 
 #include "chromeos/network/cellular_metrics_logger.h"
 
+#include <memory>
+
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/task/post_task.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/tick_clock.h"
 #include "chromeos/components/feature_usage/feature_usage_metrics.h"
 #include "chromeos/dbus/hermes/hermes_manager_client.h"
 #include "chromeos/network/cellular_esim_profile.h"
@@ -130,12 +134,6 @@
   }
 }
 
-// static
-void CellularMetricsLogger::RegisterLocalStatePrefs(
-    PrefRegistrySimple* registry) {
-  feature_usage::FeatureUsageMetrics::RegisterPref(registry,
-                                                   kESimUMAFeatureName);
-}
 
 // static
 void CellularMetricsLogger::LogCellularUserInitiatedConnectionSuccessHistogram(
@@ -247,11 +245,11 @@
 class ESimFeatureUsageMetrics
     : public feature_usage::FeatureUsageMetrics::Delegate {
  public:
-  explicit ESimFeatureUsageMetrics(PrefService* device_prefs) {
-    DCHECK(device_prefs);
+  explicit ESimFeatureUsageMetrics(const base::TickClock* tick_clock) {
+    DCHECK(tick_clock);
     feature_usage_metrics_ =
         std::make_unique<feature_usage::FeatureUsageMetrics>(
-            kESimUMAFeatureName, device_prefs, this);
+            kESimUMAFeatureName, this, tick_clock);
   }
 
   ~ESimFeatureUsageMetrics() override = default;
@@ -279,10 +277,8 @@
     feature_usage_metrics_->RecordUsage(success);
   }
 
-  // Should be called after an ESim network is disconnected from.
-  void RecordUsetime(base::TimeDelta usetime) const {
-    feature_usage_metrics_->RecordUsetime(usetime);
-  }
+  void StartUsage() { feature_usage_metrics_->StartUsage(); }
+  void StopUsage() { feature_usage_metrics_->StopUsage(); }
 
  private:
   std::unique_ptr<feature_usage::FeatureUsageMetrics> feature_usage_metrics_;
@@ -297,15 +293,12 @@
   } else {
     base::UmaHistogramEnumeration(kESimAllConnectionResultHistogram,
                                   start_connect_result);
-
-    // |esim_feature_usage_metrics_| may not have been created yet.
-    if (!esim_feature_usage_metrics_.get())
-      return;
-
-    // All initiated connects should be logged as feature usage.
-    esim_feature_usage_metrics_->RecordUsage(
-        start_connect_result ==
-        CellularMetricsLogger::ShillConnectResult::kSuccess);
+    // If there is a failure to connect, log a failed usage attempt to
+    // FeatureUsageMetrics.
+    if (start_connect_result !=
+        CellularMetricsLogger::ShillConnectResult::kSuccess) {
+      esim_feature_usage_metrics_->RecordUsage(/*success=*/false);
+    }
   }
 }
 
@@ -323,7 +316,8 @@
 
 CellularMetricsLogger::ConnectionInfo::~ConnectionInfo() = default;
 
-CellularMetricsLogger::CellularMetricsLogger() = default;
+CellularMetricsLogger::CellularMetricsLogger()
+    : CellularMetricsLogger(base::DefaultTickClock::GetInstance()) {}
 
 CellularMetricsLogger::~CellularMetricsLogger() {
   if (network_state_handler_)
@@ -455,13 +449,6 @@
   CheckForConnectionStateMetric(network);
 }
 
-void CellularMetricsLogger::SetDevicePrefs(PrefService* device_prefs) {
-  if (!device_prefs)
-    return;
-  esim_feature_usage_metrics_ =
-      std::make_unique<ESimFeatureUsageMetrics>(device_prefs);
-}
-
 void CellularMetricsLogger::CheckForTimeToConnectedMetric(
     const NetworkState* network) {
   if (network->activation_state() != shill::kActivationStateActivated)
@@ -539,6 +526,10 @@
   connection_info->disconnect_requested = true;
 }
 
+CellularMetricsLogger::CellularMetricsLogger(const base::TickClock* tick_clock)
+    : esim_feature_usage_metrics_(
+          std::make_unique<ESimFeatureUsageMetrics>(tick_clock)) {}
+
 const NetworkState* CellularMetricsLogger::GetCellularNetwork(
     const std::string& service_path) {
   const NetworkState* network =
@@ -795,9 +786,13 @@
 
         UMA_HISTOGRAM_LONG_TIMES("Network.Cellular.ESim.Usage.Duration",
                                  usage_duration);
-        if (esim_feature_usage_metrics_.get())
-          esim_feature_usage_metrics_->RecordUsetime(usage_duration);
+        esim_feature_usage_metrics_->StopUsage();
       }
+      if (usage != CellularUsage::kNotConnected)
+        esim_feature_usage_metrics_->RecordUsage(/*success=*/true);
+
+      if (usage == CellularUsage::kConnectedAndOnlyNetwork)
+        esim_feature_usage_metrics_->StartUsage();
     }
 
     esim_usage_elapsed_timer_ = base::ElapsedTimer();
diff --git a/chromeos/network/cellular_metrics_logger.h b/chromeos/network/cellular_metrics_logger.h
index e08e5ef..af06039f 100644
--- a/chromeos/network/cellular_metrics_logger.h
+++ b/chromeos/network/cellular_metrics_logger.h
@@ -16,8 +16,9 @@
 #include "chromeos/network/network_state_handler_observer.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-class PrefService;
-class PrefRegistrySimple;
+namespace base {
+class TickClock;
+}
 
 namespace chromeos {
 
@@ -77,10 +78,6 @@
       const SimPinOperation& pin_operation,
       const absl::optional<std::string>& shill_error_name = absl::nullopt);
 
-  // Registers device preferences used by this class in the provided
-  // |registry|.
-  static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
-
   CellularMetricsLogger();
   ~CellularMetricsLogger() override;
 
@@ -103,8 +100,6 @@
                      const std::string& error_name) override;
   void DisconnectRequested(const std::string& service_path) override;
 
-  void SetDevicePrefs(PrefService* device_prefs);
-
  private:
   friend class CellularMetricsLoggerTest;
   FRIEND_TEST_ALL_PREFIXES(CellularMetricsLoggerTest,
@@ -132,6 +127,9 @@
   FRIEND_TEST_ALL_PREFIXES(NetworkDeviceHandlerTest, UnblockPin);
   FRIEND_TEST_ALL_PREFIXES(NetworkDeviceHandlerTest, ChangePin);
 
+  // Custom `tick_clock` could be used for tests.
+  explicit CellularMetricsLogger(const base::TickClock* tick_clock);
+
   // The amount of time after cellular device is added to device list,
   // after which cellular device is considered initialized.
   static const base::TimeDelta kInitializationTimeout;
diff --git a/chromeos/network/cellular_metrics_logger_unittest.cc b/chromeos/network/cellular_metrics_logger_unittest.cc
index 490f74d..9dae7ed 100644
--- a/chromeos/network/cellular_metrics_logger_unittest.cc
+++ b/chromeos/network/cellular_metrics_logger_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
+#include "base/time/time.h"
 #include "chromeos/components/feature_usage/feature_usage_metrics.h"
 #include "chromeos/login/login_state/login_state.h"
 #include "chromeos/network/cellular_esim_profile.h"
@@ -18,7 +19,6 @@
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_test_helper.h"
 #include "chromeos/network/test_cellular_esim_profile_handler.h"
-#include "components/prefs/testing_pref_service.h"
 #include "dbus/object_path.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
@@ -75,7 +75,6 @@
   void SetUp() override {
     ResetHistogramTester();
     LoginState::Initialize();
-    CellularMetricsLogger::RegisterLocalStatePrefs(prefs_.registry());
 
     cellular_inhibitor_ = std::make_unique<CellularInhibitor>();
     cellular_esim_profile_handler_ =
@@ -91,14 +90,16 @@
   }
 
   void SetUpMetricsLogger() {
-    cellular_metrics_logger_ = std::make_unique<CellularMetricsLogger>();
+    cellular_metrics_logger_.reset(
+        new CellularMetricsLogger(task_environment_.GetMockTickClock()));
     cellular_metrics_logger_->Init(
         network_state_test_helper_.network_state_handler(),
         /* network_connection_handler */ nullptr,
         cellular_esim_profile_handler_.get());
 
     histogram_tester_->ExpectTotalCount(kESimFeatureUsageMetric, 0);
-    cellular_metrics_logger_->SetDevicePrefs(&prefs_);
+    task_environment_.FastForwardBy(
+        feature_usage::FeatureUsageMetrics::kInitialInterval);
     histogram_tester_->ExpectBucketCount(
         kESimFeatureUsageMetric,
         static_cast<int>(feature_usage::FeatureUsageMetrics::Event::kEligible),
@@ -202,7 +203,6 @@
 
   base::test::TaskEnvironment task_environment_;
   std::unique_ptr<base::HistogramTester> histogram_tester_;
-  TestingPrefServiceSimple prefs_;
   NetworkStateTestHelper network_state_test_helper_{
       false /* use_default_devices_and_services */};
   std::unique_ptr<CellularInhibitor> cellular_inhibitor_;
diff --git a/chromeos/network/network_handler.cc b/chromeos/network/network_handler.cc
index 6c6f95ff..0bda413 100644
--- a/chromeos/network/network_handler.cc
+++ b/chromeos/network/network_handler.cc
@@ -157,7 +157,6 @@
     PrefService* device_prefs) {
   if (features::IsCellularActivationUiEnabled()) {
     cellular_esim_profile_handler_->SetDevicePrefs(device_prefs);
-    cellular_metrics_logger_->SetDevicePrefs(device_prefs);
   }
   ui_proxy_config_service_.reset(new UIProxyConfigService(
       logged_in_profile_prefs, device_prefs, network_state_handler_.get(),
@@ -173,7 +172,6 @@
 void NetworkHandler::ShutdownPrefServices() {
   if (features::IsCellularActivationUiEnabled()) {
     cellular_esim_profile_handler_->SetDevicePrefs(nullptr);
-    cellular_metrics_logger_->SetDevicePrefs(nullptr);
   }
   ui_proxy_config_service_.reset();
   network_metadata_store_.reset();
diff --git a/chromeos/services/network_config/cros_network_config_unittest.cc b/chromeos/services/network_config/cros_network_config_unittest.cc
index 58bae4c..acc1de0b 100644
--- a/chromeos/services/network_config/cros_network_config_unittest.cc
+++ b/chromeos/services/network_config/cros_network_config_unittest.cc
@@ -96,7 +96,6 @@
     ::onc::RegisterPrefs(local_state_.registry());
     NetworkMetadataStore::RegisterPrefs(user_prefs_.registry());
     NetworkMetadataStore::RegisterPrefs(local_state_.registry());
-    CellularMetricsLogger::RegisterLocalStatePrefs(local_state_.registry());
     CellularESimProfileHandlerImpl::RegisterLocalStatePrefs(
         local_state_.registry());
     NetworkHandler::Get()->InitializePrefServices(&user_prefs_, &local_state_);
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index e5c3d38..2a2b47c 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -80,6 +80,10 @@
 // Maximal length of all button titles.
 const int kMaxLengthForAllButtonTitles = 200;
 
+// Number of shadow roots to traverse upwards when looking for relevant forms
+// and labels of an input element inside a shadow root.
+const int kMaxShadowLevelsUp = 2;
+
 // Text features to detect form submission buttons. Features are selected based
 // on analysis of real forms and their buttons.
 // TODO(crbug.com/910546): Consider to add more features (e.g. non-English
@@ -1292,28 +1296,77 @@
 // pointer.
 struct CompareByRendererId {
   using is_transparent = void;
-  constexpr bool operator()(const FormFieldData* f,
-                            const FormFieldData* g) const {
-    DCHECK(f && g);
-    return f->unique_renderer_id < g->unique_renderer_id;
+  bool operator()(const std::pair<FormFieldData*, ShadowFieldData>& f,
+                  const std::pair<FormFieldData*, ShadowFieldData>& g) const {
+    DCHECK(f.first && g.first);
+    return f.first->unique_renderer_id < g.first->unique_renderer_id;
   }
-  constexpr bool operator()(const FieldRendererId f,
-                            const FormFieldData* g) const {
-    DCHECK(g);
-    return f < g->unique_renderer_id;
+  bool operator()(const FieldRendererId f,
+                  const std::pair<FormFieldData*, ShadowFieldData>& g) const {
+    DCHECK(g.first);
+    return f < g.first->unique_renderer_id;
   }
-  constexpr bool operator()(const FormFieldData* f, FieldRendererId g) const {
-    DCHECK(f);
-    return f->unique_renderer_id < g;
+  bool operator()(const std::pair<FormFieldData*, ShadowFieldData>& f,
+                  FieldRendererId g) const {
+    DCHECK(f.first);
+    return f.first->unique_renderer_id < g;
   }
 };
 
+// Searches field_set for a matching named element in the case that
+// Label::CorrespondingControl from blink didn't return a matching form control
+// element. Returns nullptr if no match was found.
+FormFieldData* SearchForFormControlByName(
+    const std::u16string& target_name,
+    const base::flat_set<std::pair<FormFieldData*, ShadowFieldData>,
+                         CompareByRendererId>& field_set) {
+  // Sometimes site authors will incorrectly specify the corresponding
+  // field element's name rather than its id, so we compensate here.
+  if (target_name.empty())
+    return nullptr;
+
+  // Look through the list for elements with this name. There can actually
+  // be more than one. In this case, the label may not be particularly
+  // useful, so just discard it.
+  FormFieldData* field_data = nullptr;
+  for (const auto& iter : field_set) {
+    if (iter.first->name == target_name) {
+      if (field_data) {
+        field_data = nullptr;
+        break;
+      }
+      field_data = iter.first;
+    }
+  }
+
+  if (field_data)
+    return field_data;
+
+  // If there is identifying information that will help us find the target
+  // form control in the form control's shadow host(s), look there too.
+  for (const auto& iter : field_set) {
+    for (const std::u16string& shadow_host_name :
+         iter.second.shadow_host_name_attributes) {
+      if (shadow_host_name == target_name)
+        return iter.first;
+    }
+    for (const std::u16string& shadow_host_id :
+         iter.second.shadow_host_id_attributes) {
+      if (shadow_host_id == target_name)
+        return iter.first;
+    }
+  }
+
+  return nullptr;
+}
+
 // Updates the FormFieldData::label of each field in `field_set` according to
 // the <label> descendant of |form_or_fieldset|, if there is any. The extracted
 // label is label.firstChild().nodeValue() of the label element.
 void MatchLabelsAndFields(
     const WebElement& form_or_fieldset,
-    const base::flat_set<FormFieldData*, CompareByRendererId>& field_set) {
+    const base::flat_set<std::pair<FormFieldData*, ShadowFieldData>,
+                         CompareByRendererId>& field_set) {
   static base::NoDestructor<WebString> kLabel("label");
   static base::NoDestructor<WebString> kFor("for");
   static base::NoDestructor<WebString> kHidden("hidden");
@@ -1329,23 +1382,8 @@
     FormFieldData* field_data = nullptr;
 
     if (control.IsNull()) {
-      // Sometimes site authors will incorrectly specify the corresponding
-      // field element's name rather than its id, so we compensate here.
-      std::u16string element_name = label.GetAttribute(*kFor).Utf16();
-      if (element_name.empty())
-        continue;
-      // Look through the field set with this name. There can actually
-      // be more than one. In this case, the label may not be particularly
-      // useful, so just discard it.
-      for (FormFieldData* field : field_set) {
-        if (field->name == element_name) {
-          if (field_data) {
-            field_data = nullptr;
-            break;
-          }
-          field_data = field;
-        }
-      }
+      field_data = SearchForFormControlByName(label.GetAttribute(*kFor).Utf16(),
+                                              field_set);
     } else if (control.IsFormControlElement()) {
       WebFormControlElement form_control = control.To<WebFormControlElement>();
       if (form_control.FormControlTypeForAutofill() == *kHidden)
@@ -1355,7 +1393,7 @@
           FieldRendererId(form_control.UniqueRendererFormControlId()));
       if (iter == field_set.end())
         continue;
-      field_data = *iter;
+      field_data = iter->first;
     }
 
     if (!field_data)
@@ -1397,6 +1435,8 @@
   // requirements and thus will be in the resulting |form|.
   std::vector<bool> fields_extracted(control_elements.size(), false);
 
+  std::vector<ShadowFieldData> shadow_field_data;
+
   // Extracts the fields from |control_elements| with |extract_mask| to
   // |form_fields|. |fields_extracted| should have as many elements as
   // |control_elements|, initialized to false. Returns true if the number of
@@ -1407,9 +1447,12 @@
     if (!IsAutofillableElement(control_element))
       continue;
 
-    form->fields.push_back(FormFieldData());
+    form->fields.emplace_back(FormFieldData());
+    ShadowFieldData shadow_field;
     WebFormControlElementToFormField(control_element, field_data_manager,
-                                     extract_mask, &form->fields.back());
+                                     extract_mask, &form->fields.back(),
+                                     &shadow_field);
+    shadow_field_data.emplace_back(shadow_field);
     fields_extracted[i] = true;
 
     // To reduce computational costs, we impose a maximum number of allowable
@@ -1421,11 +1464,15 @@
   }
 
   {
-    std::vector<FormFieldData*> items;
-    for (FormFieldData& field : form->fields)
-      items.push_back(&field);
-    base::flat_set<FormFieldData*, CompareByRendererId> field_set(
-        std::move(items));
+    std::vector<std::pair<FormFieldData*, ShadowFieldData>> items;
+    DCHECK_EQ(form->fields.size(), shadow_field_data.size());
+    for (size_t i = 0; i < form->fields.size(); i++) {
+      items.emplace_back(
+          std::make_pair(&form->fields[i], std::move(shadow_field_data[i])));
+    }
+    base::flat_set<std::pair<FormFieldData*, ShadowFieldData>,
+                   CompareByRendererId>
+        field_set(std::move(items));
 
     if (form_element) {
       MatchLabelsAndFields(*form_element, field_set);
@@ -1538,6 +1585,39 @@
   return base::flat_map<FieldRendererId, size_t>(std::move(items));
 }
 
+std::string GetAutocompleteAttribute(const WebElement& element) {
+  static base::NoDestructor<WebString> kAutocomplete("autocomplete");
+  std::string autocomplete_attribute =
+      element.GetAttribute(*kAutocomplete).Utf8();
+  if (autocomplete_attribute.size() > kMaxDataLength) {
+    // Discard overly long attribute values to avoid DOS-ing the browser
+    // process.  However, send over a default string to indicate that the
+    // attribute was present.
+    return "x-max-data-length-exceeded";
+  }
+  return autocomplete_attribute;
+}
+
+void FindFormElementUpShadowRoots(const WebElement& element,
+                                  WebFormElement* found_form_element) {
+  // If we are in shadowdom, then look to see if the host(s) are inside a form
+  // element we can use.
+  int levels_up = kMaxShadowLevelsUp;
+  for (WebElement host = element.OwnerShadowHost(); !host.IsNull() && levels_up;
+       host = host.OwnerShadowHost(), --levels_up) {
+    for (WebNode parent = host; !parent.IsNull();
+         parent = parent.ParentNode()) {
+      if (parent.IsElementNode()) {
+        WebElement parentElement = parent.To<WebElement>();
+        if (parentElement.HasHTMLTagName("form")) {
+          *found_form_element = parentElement.To<WebFormElement>();
+          return;
+        }
+      }
+    }
+  }
+}
+
 }  // namespace
 
 void GetDataListSuggestions(const WebInputElement& element,
@@ -1740,11 +1820,11 @@
     const WebFormControlElement& element,
     const FieldDataManager* field_data_manager,
     ExtractMask extract_mask,
-    FormFieldData* field) {
+    FormFieldData* field,
+    ShadowFieldData* shadow_data) {
   DCHECK(field);
   DCHECK(!element.IsNull());
   DCHECK(element.GetDocument().GetFrame());
-  static base::NoDestructor<WebString> kAutocomplete("autocomplete");
   static base::NoDestructor<WebString> kName("name");
   static base::NoDestructor<WebString> kRole("role");
   static base::NoDestructor<WebString> kPlaceholder("placeholder");
@@ -1763,13 +1843,7 @@
       FieldRendererId(element.UniqueRendererFormControlId());
   field->form_control_ax_id = element.GetAxId();
   field->form_control_type = element.FormControlTypeForAutofill().Utf8();
-  field->autocomplete_attribute = element.GetAttribute(*kAutocomplete).Utf8();
-  if (field->autocomplete_attribute.size() > kMaxDataLength) {
-    // Discard overly long attribute values to avoid DOS-ing the browser
-    // process.  However, send over a default string to indicate that the
-    // attribute was present.
-    field->autocomplete_attribute = "x-max-data-length-exceeded";
-  }
+  field->autocomplete_attribute = GetAutocompleteAttribute(element);
   if (base::LowerCaseEqualsASCII(element.GetAttribute(*kRole).Utf16(),
                                  "presentation")) {
     field->role = FormFieldData::RoleAttribute::kPresentation;
@@ -1788,6 +1862,40 @@
   field->aria_label = GetAriaLabel(element.GetDocument(), element);
   field->aria_description = GetAriaDescription(element.GetDocument(), element);
 
+  // Traverse up through shadow hosts to see if we can gather missing fields.
+  WebFormElement form_element_up_shadow_hosts;
+  FindFormElementUpShadowRoots(element, &form_element_up_shadow_hosts);
+  int levels_up = kMaxShadowLevelsUp;
+  for (WebElement host = element.OwnerShadowHost();
+       !host.IsNull() && levels_up &&
+       (!form_element_up_shadow_hosts.IsNull() &&
+        form_element_up_shadow_hosts.OwnerShadowHost() != host);
+       host = host.OwnerShadowHost(), --levels_up) {
+    std::u16string shadow_host_id = host.GetIdAttribute().Utf16();
+    if (shadow_data && !shadow_host_id.empty())
+      shadow_data->shadow_host_id_attributes.push_back(shadow_host_id);
+    std::u16string shadow_host_name = host.GetAttribute(*kName).Utf16();
+    if (shadow_data && !shadow_host_name.empty())
+      shadow_data->shadow_host_name_attributes.push_back(shadow_host_name);
+
+    if (field->id_attribute.empty())
+      field->id_attribute = host.GetIdAttribute().Utf16();
+    if (field->name_attribute.empty())
+      field->name_attribute = host.GetAttribute(*kName).Utf16();
+    if (field->name.empty()) {
+      field->name = field->name_attribute.empty() ? field->id_attribute
+                                                  : field->name_attribute;
+    }
+    if (field->autocomplete_attribute.empty())
+      field->autocomplete_attribute = GetAutocompleteAttribute(host);
+    if (field->css_classes.empty() && host.HasAttribute(*kClass))
+      field->css_classes = host.GetAttribute(*kClass).Utf16();
+    if (field->aria_label.empty())
+      field->aria_label = GetAriaLabel(host.GetDocument(), host);
+    if (field->aria_description.empty())
+      field->aria_description = GetAriaDescription(host.GetDocument(), host);
+  }
+
   if (!IsAutofillableElement(element))
     return;
 
@@ -1998,7 +2106,11 @@
 
   extract_mask =
       static_cast<ExtractMask>(EXTRACT_VALUE | EXTRACT_OPTIONS | extract_mask);
-  const WebFormElement form_element = element.Form();
+  WebFormElement form_element = element.Form();
+
+  if (form_element.IsNull())
+    FindFormElementUpShadowRoots(element, &form_element);
+
   if (form_element.IsNull()) {
     // No associated form, try the synthetic form for unowned form elements.
     WebDocument document = element.GetDocument();
@@ -2027,6 +2139,9 @@
     const FormData& form,
     const WebFormControlElement& element) {
   WebFormElement form_element = element.Form();
+  if (form_element.IsNull())
+    FindFormElementUpShadowRoots(element, &form_element);
+
   if (form_element.IsNull()) {
     return ForEachMatchingUnownedFormField(element, form,
                                            FILTER_ALL_NON_EDITABLE_ELEMENTS,
@@ -2046,6 +2161,9 @@
     const FormData& form,
     const WebFormControlElement& element) {
   WebFormElement form_element = element.Form();
+  if (form_element.IsNull())
+    FindFormElementUpShadowRoots(element, &form_element);
+
   if (form_element.IsNull()) {
     return ForEachMatchingUnownedFormField(element, form,
                                            FILTER_ALL_NON_EDITABLE_ELEMENTS,
@@ -2316,7 +2434,7 @@
 }  // namespace
 
 std::u16string GetAriaLabel(const blink::WebDocument& document,
-                            const WebFormControlElement& element) {
+                            const WebElement& element) {
   static const base::NoDestructor<WebString> kAriaLabelledBy("aria-labelledby");
   if (element.HasAttribute(*kAriaLabelledBy)) {
     std::u16string text =
@@ -2333,11 +2451,16 @@
 }
 
 std::u16string GetAriaDescription(const blink::WebDocument& document,
-                                  const WebFormControlElement& element) {
+                                  const WebElement& element) {
   static const base::NoDestructor<WebString> kAriaDescribedBy(
       "aria-describedby");
   return CoalesceTextByIdList(document,
                               element.GetAttribute(*kAriaDescribedBy));
 }
+
+ShadowFieldData::ShadowFieldData() = default;
+ShadowFieldData::ShadowFieldData(const ShadowFieldData& other) = default;
+ShadowFieldData::~ShadowFieldData() = default;
+
 }  // namespace form_util
 }  // namespace autofill
diff --git a/components/autofill/content/renderer/form_autofill_util.h b/components/autofill/content/renderer/form_autofill_util.h
index e85e0b7..7f34f41 100644
--- a/components/autofill/content/renderer/form_autofill_util.h
+++ b/components/autofill/content/renderer/form_autofill_util.h
@@ -70,6 +70,18 @@
                                  // kMaxDataLength.
 };
 
+struct ShadowFieldData {
+  ShadowFieldData();
+  ShadowFieldData(const ShadowFieldData& other);
+  ~ShadowFieldData();
+
+  // If the form control is inside shadow DOM, then these lists will contain
+  // id and name attributes of the parent shadow host elements. There may be
+  // more than one if the form control is in nested shadow DOM.
+  std::vector<std::u16string> shadow_host_id_attributes;
+  std::vector<std::u16string> shadow_host_name_attributes;
+};
+
 // Gets up to kMaxListSize data list values (with corresponding label) for the
 // given element, each value and label have as far as kMaxDataLength.
 void GetDataListSuggestions(const blink::WebInputElement& element,
@@ -167,7 +179,8 @@
     const blink::WebFormControlElement& element,
     const FieldDataManager* field_data_manager,
     ExtractMask extract_mask,
-    FormFieldData* field);
+    FormFieldData* field,
+    ShadowFieldData* shadow_data = nullptr);
 
 // Fills |form| with the FormData object corresponding to the |form_element|.
 // If |field| is non-NULL, also fills |field| with the FormField object
@@ -346,12 +359,12 @@
 // attribute of |element| or the value of the aria-label attribute of
 // |element|, with priority given to the aria-labelledby attribute.
 std::u16string GetAriaLabel(const blink::WebDocument& document,
-                            const blink::WebFormControlElement& element);
+                            const blink::WebElement& element);
 
 // Returns the ARIA label text of the elements denoted by the aria-describedby
 // attribute of |element|.
 std::u16string GetAriaDescription(const blink::WebDocument& document,
-                                  const blink::WebFormControlElement& element);
+                                  const blink::WebElement& element);
 
 }  // namespace form_util
 }  // namespace autofill
diff --git a/components/autofill/content/renderer/password_generation_agent.cc b/components/autofill/content/renderer/password_generation_agent.cc
index dd26bf68..c26014a 100644
--- a/components/autofill/content/renderer/password_generation_agent.cc
+++ b/components/autofill/content/renderer/password_generation_agent.cc
@@ -234,7 +234,7 @@
     password_agent_->TrackAutofilledElement(password_element);
     // Advance focus to the next input field. We assume password fields in
     // an account creation form are always adjacent.
-    render_frame()->GetRenderView()->GetWebView()->AdvanceFocus(false);
+    render_frame()->GetWebView()->AdvanceFocus(false);
   }
 
   std::unique_ptr<FormData> presaved_form_data(CreateFormDataToPresave());
diff --git a/components/autofill/core/browser/DEPS b/components/autofill/core/browser/DEPS
index 6908278..5af68a6 100644
--- a/components/autofill/core/browser/DEPS
+++ b/components/autofill/core/browser/DEPS
@@ -51,14 +51,14 @@
     "+device/fido/authenticator_selection_criteria.h",
     "+device/fido/fido_constants.h",
     "+device/fido/fido_types.h",
-    "+third_party/blink/public/mojom/webauthn/authenticator.mojom.h",
+    "+third_party/blink/public/mojom/webauthn",
   ],
   "(test_)?internal_authenticator\.h": [
     "+device/fido/fido_constants.h",
-    "+third_party/blink/public/mojom/webauthn/authenticator.mojom.h",
+    "+third_party/blink/public/mojom/webauthn",
   ],
   "autofill_driver\.h": [
-    "+third_party/blink/public/mojom/webauthn/internal_authenticator.mojom.h"
+    "+third_party/blink/public/mojom/webauthn",
   ],
   "test_autofill_driver\.h": [
     "+components/autofill/content/browser/content_autofill_driver.h"
diff --git a/components/autofill/core/browser/payments/credit_card_fido_authenticator.h b/components/autofill/core/browser/payments/credit_card_fido_authenticator.h
index a946720..ac4ce6c 100644
--- a/components/autofill/core/browser/payments/credit_card_fido_authenticator.h
+++ b/components/autofill/core/browser/payments/credit_card_fido_authenticator.h
@@ -18,7 +18,7 @@
 #include "components/autofill/core/browser/payments/payments_client.h"
 #include "device/fido/fido_constants.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
+#include "third_party/blink/public/mojom/webauthn/authenticator.mojom-forward.h"
 
 namespace autofill {
 
diff --git a/components/autofill_strings.grdp b/components/autofill_strings.grdp
index fc3c4f7f..4f82fc2 100644
--- a/components/autofill_strings.grdp
+++ b/components/autofill_strings.grdp
@@ -316,48 +316,87 @@
     </message>
   </if>
 
-  <!-- Explicit save/update address prompt strings  -->
-  <message name="IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_TITLE" desc="Title shown at the top of prompt that offers the user to save a new address.">
-    Save address?
-  </message>
+  <!-- Explicit save/update address prompt strings -->
+  <!-- Common on all platforms (platform-specific defined later): -->
   <message name="IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_OK_BUTTON_LABEL" desc="Label of the OK button in the prompt that offers the user to save a new address.">
     Save
   </message>
-  <message name="IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_CANCEL_BUTTON_LABEL" desc="Label of the Cancel button in the prompt that offers the user to save a new address.">
-    No thanks
-  </message>
-  <message name="IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_EDIT_BUTTON_TOOLTIP" desc="Tooltip of the Edit button in the prompt that offers the user to save a new address.">
-    Edit address
-  </message>
-
-  <message name="IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_TITLE" desc="Title shown at the top of prompt that offers the user to update an existing address.">
-    Update address?
-  </message>
   <message name="IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_OK_BUTTON_LABEL" desc="Label of the OK button in the prompt that offers the user to update an existing address.">
     Update
   </message>
-  <message name="IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_CANCEL_BUTTON_LABEL" desc="Label of the Cancel button in the prompt that offers the user to update an existing address.">
-    No thanks
-  </message>
   <message name="IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_NEW_VALUES_SECTION_LABEL" meaning="In the address update prompt" desc="Label shown next to the section that displays the new address values in the prompt that offers the user to update an existing address.">
     New
   </message>
   <message name="IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_OLD_VALUES_SECTION_LABEL" meaning="In the address update prompt" desc="Label shown next to the section that displays the old address values in the prompt that offers the user to update an existing address.">
     Old
   </message>
-
-  <message name="IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_TITLE" desc="Title shown at the top of dialog that edits an address before saving it">
-    Edit address
-  </message>
-  <message name="IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_OK_BUTTON_LABEL_SAVE" desc="Label of the OK button in the dialog that edits the address before saving it as a new address.">
-    Save
-  </message>
-  <message name="IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_OK_BUTTON_LABEL_UPDATE" desc="Label of the OK button in the dialog that edits the address before using it to update an existing address.">
-    Update
-  </message>
-  <message name="IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_CANCEL_BUTTON_LABEL" desc="Label of the Cancel button in the dialog that edits an address before saving it.">
-    Cancel
-  </message>
-
-
+  <!-- Used on Android: -->
+  <if expr="is_android">
+    <message name="IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_TITLE" desc="Title shown at the top of prompt that offers the user to save a new address.">
+      Save address?
+    </message>
+    <message name="IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_TITLE" desc="Title shown at the top of prompt that offers the user to update an existing address.">
+      Update address?
+    </message>
+    <message name="IDS_ANDROID_AUTOFILL_SAVE_ADDRESS_PROMPT_CANCEL_BUTTON_LABEL" desc="Label of the negative button in the modal dialog that offers the user to save a new address.">
+      Cancel
+    </message>
+    <message name="IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_EDIT_BUTTON_TOOLTIP" desc="Tooltip of the Edit button in the prompt that offers the user to save a new address.">
+      Edit address
+    </message>
+    <message name="IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_TITLE" desc="Title shown at the top of dialog that edits an address before saving it">
+      Edit address
+    </message>
+  </if>
+  <!-- Used on iOS: -->
+  <if expr="is_ios">
+    <message name="IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_TITLE" desc="In Title Case: Title shown in the message that offers the user to proceed with saving a new address." meaning="In Title Case for iOS">
+      Save Address?
+    </message>
+    <message name="IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_PRIMARY_ACTION" desc="Label of the primary action button in the message that offers the user to proceed with saving a new address.">
+      Save…
+    </message>
+    <message name="IDS_IOS_AUTOFILL_UPDATE_ADDRESS_MESSAGE_TITLE" desc="In Title Case: Title shown in the message that offers the user to proceed with updating an existing address." meaning="In Title Case for iOS">
+      Update Address?
+    </message>
+    <message name="IDS_IOS_AUTOFILL_UPDATE_ADDRESS_MESSAGE_PRIMARY_ACTION" desc="Label of the primary action button in the message that offers the user to proceed with updating an existing address.">
+      Update…
+    </message>
+    <message name="IDS_IOS_AUTOFILL_SAVE_ADDRESS_PROMPT_TITLE" desc="In Title Case: Title shown at the top of modal dialog that offers the user to save a new address." meaning="In Title Case for iOS">
+      Save Address
+    </message>
+    <message name="IDS_IOS_AUTOFILL_UPDATE_ADDRESS_PROMPT_TITLE" desc="In Title Case: Title shown at the top of modal dialog that offers the user to update an existing address." meaning="In Title Case for iOS">
+      Update Address
+    </message>
+  </if>
+  <!-- Used on Desktop: -->
+  <if expr="not is_android and not is_ios">
+    <message name="IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_TITLE" desc="Title shown at the top of prompt that offers the user to save a new address.">
+      Save address?
+    </message>
+    <message name="IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_CANCEL_BUTTON_LABEL" desc="Label of the Cancel button in the prompt that offers the user to save a new address.">
+      No thanks
+    </message>
+    <message name="IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_EDIT_BUTTON_TOOLTIP" desc="Tooltip of the Edit button in the prompt that offers the user to save a new address.">
+      Edit address
+    </message>
+    <message name="IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_TITLE" desc="Title shown at the top of prompt that offers the user to update an existing address.">
+      Update address?
+    </message>
+    <message name="IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_CANCEL_BUTTON_LABEL" desc="Label of the Cancel button in the prompt that offers the user to update an existing address.">
+      No thanks
+    </message>
+    <message name="IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_TITLE" desc="Title shown at the top of dialog that edits an address before saving it">
+      Edit address
+    </message>
+    <message name="IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_OK_BUTTON_LABEL_SAVE" desc="Label of the OK button in the dialog that edits the address before saving it as a new address.">
+      Save
+    </message>
+    <message name="IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_OK_BUTTON_LABEL_UPDATE" desc="Label of the OK button in the dialog that edits the address before using it to update an existing address.">
+      Update
+    </message>
+    <message name="IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_CANCEL_BUTTON_LABEL" desc="Label of the Cancel button in the dialog that edits an address before saving it.">
+      Cancel
+    </message>
+  </if>
 </grit-part>
diff --git a/components/autofill_strings_grdp/IDS_ANDROID_AUTOFILL_SAVE_ADDRESS_PROMPT_CANCEL_BUTTON_LABEL.png.sha1 b/components/autofill_strings_grdp/IDS_ANDROID_AUTOFILL_SAVE_ADDRESS_PROMPT_CANCEL_BUTTON_LABEL.png.sha1
new file mode 100644
index 0000000..51b3663c3
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_ANDROID_AUTOFILL_SAVE_ADDRESS_PROMPT_CANCEL_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@
+f3ebbd06ed833a22a584ee3031b0882f5ffaa9a8
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_PRIMARY_ACTION.png.sha1 b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_PRIMARY_ACTION.png.sha1
new file mode 100644
index 0000000..9e758da
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_PRIMARY_ACTION.png.sha1
@@ -0,0 +1 @@
+9a7a9f643f7f8eff2243669712464e91c64180eb
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_TITLE.png.sha1 b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_TITLE.png.sha1
new file mode 100644
index 0000000..9e758da
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_TITLE.png.sha1
@@ -0,0 +1 @@
+9a7a9f643f7f8eff2243669712464e91c64180eb
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_PROMPT_TITLE.png.sha1 b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_PROMPT_TITLE.png.sha1
new file mode 100644
index 0000000..24ad858
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_PROMPT_TITLE.png.sha1
@@ -0,0 +1 @@
+a821463ac6f6eb607114546ac35fe80dc4a67418
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_UPDATE_ADDRESS_MESSAGE_PRIMARY_ACTION.png.sha1 b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_UPDATE_ADDRESS_MESSAGE_PRIMARY_ACTION.png.sha1
new file mode 100644
index 0000000..8594d5a
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_UPDATE_ADDRESS_MESSAGE_PRIMARY_ACTION.png.sha1
@@ -0,0 +1 @@
+2bf8eb1bb5f0554c06a157d0e767dcf7c9d2858d
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_UPDATE_ADDRESS_MESSAGE_TITLE.png.sha1 b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_UPDATE_ADDRESS_MESSAGE_TITLE.png.sha1
new file mode 100644
index 0000000..8594d5a
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_UPDATE_ADDRESS_MESSAGE_TITLE.png.sha1
@@ -0,0 +1 @@
+2bf8eb1bb5f0554c06a157d0e767dcf7c9d2858d
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_UPDATE_ADDRESS_PROMPT_TITLE.png.sha1 b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_UPDATE_ADDRESS_PROMPT_TITLE.png.sha1
new file mode 100644
index 0000000..6c59691
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_UPDATE_ADDRESS_PROMPT_TITLE.png.sha1
@@ -0,0 +1 @@
+89a78a7382c457c7f9433a697deded79702c13ab
\ No newline at end of file
diff --git a/components/cast_streaming/browser/BUILD.gn b/components/cast_streaming/browser/BUILD.gn
index 30200c8..80cc8436 100644
--- a/components/cast_streaming/browser/BUILD.gn
+++ b/components/cast_streaming/browser/BUILD.gn
@@ -127,7 +127,6 @@
     ":test_receiver",
     ":test_sender",
     "//base/test:test_support",
-    "//components/cast/message_port:test_message_port_receiver",
     "//components/openscreen_platform:openscreen_platform_net",
     "//media",
     "//mojo/core/embedder",
@@ -140,12 +139,16 @@
   ]
 }
 
-# NOTE: This source set is intentionally empty. It is used to force the building
-# of the code defined in this directory, as it is production code which must
-# be validated as part of the CQ.
-# TODO(crbug.com/1207721): Add unit tests for code in this directory.
+# TODO(crbug.com/1207721): Add more unit tests for code in this directory.
 source_set("unit_tests") {
   testonly = true
-  deps = [ ":browser" ]
-  sources = []
+  deps = [
+    ":browser",
+    ":core",
+    "//base",
+    "//base/test:test_support",
+    "//components/cast/message_port:test_message_port_receiver",
+    "//testing/gtest",
+  ]
+  sources = [ "cast_message_port_impl_unittest.cc" ]
 }
diff --git a/fuchsia/cast_streaming/cast_message_port_impl_unittest.cc b/components/cast_streaming/browser/cast_message_port_impl_unittest.cc
similarity index 97%
rename from fuchsia/cast_streaming/cast_message_port_impl_unittest.cc
rename to components/cast_streaming/browser/cast_message_port_impl_unittest.cc
index 80cc0262..a5d1308 100644
--- a/fuchsia/cast_streaming/cast_message_port_impl_unittest.cc
+++ b/components/cast_streaming/browser/cast_message_port_impl_unittest.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "fuchsia/cast_streaming/cast_message_port_impl.h"
+#include "components/cast_streaming/browser/cast_message_port_impl.h"
 
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "components/cast/message_port/test_message_port_receiver.h"
-#include "fuchsia/cast_streaming/message_serialization.h"
+#include "components/cast_streaming/browser/message_serialization.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cast_streaming {
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json
index 3e34fdc..b154b67 100644
--- a/components/certificate_transparency/data/log_list.json
+++ b/components/certificate_transparency/data/log_list.json
@@ -608,6 +608,22 @@
       ],
       "logs": [
         {
+          "description": "Trust Asia CT2021",
+          "log_id": "qNxS9j1rJCXlMeN89ORKcU8UKiCAOw0E0uLuBmR5SiM=",
+          "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESdAfC+h1SZNsSARs188n/dCiNYjGSgkT7avLYe1mmXJzzHhsmxmAorHtOzhDkFgaCSCrUPrXdunK946eyIeSmA==",
+          "url": "https://ct2021.trustasia.com/log2021/",
+          "mmd": 86400,
+          "state": {
+            "qualified": {
+              "timestamp": "2021-05-11T07:00:00Z"
+            }
+          },
+          "temporal_interval": {
+            "start_inclusive": "2021-01-01T00:00:00Z",
+            "end_exclusive": "2022-01-01T00:00:00Z"
+          }
+        },
+        {
           "description": "Trust Asia Log2021",
           "log_id": "Z422Wz50Q7bzo3DV4TqxtDvgoNNR98p0IlDHxvpRqIo=",
           "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjwlzYzssDEG4DpPoOS73Ewsdohc0MzaohzRmUz9dih7Z8SHyyviKmnQL1KKfY6VGFnt0ulbVupzGXSaYUAoupA==",
diff --git a/components/constrained_window/native_web_contents_modal_dialog_manager_views.cc b/components/constrained_window/native_web_contents_modal_dialog_manager_views.cc
index dd4e56f..47992aa 100644
--- a/components/constrained_window/native_web_contents_modal_dialog_manager_views.cc
+++ b/components/constrained_window/native_web_contents_modal_dialog_manager_views.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/auto_reset.h"
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/web_modal/web_contents_modal_dialog_host.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
@@ -36,15 +37,15 @@
     NativeWebContentsModalDialogManagerViews(
         gfx::NativeWindow dialog,
         SingleWebContentsDialogManagerDelegate* native_delegate)
-    : native_delegate_(native_delegate),
-      dialog_(dialog),
-      host_(nullptr),
-      host_destroying_(false) {
+    : native_delegate_(native_delegate), dialog_(dialog) {
   ManageDialog();
 }
 
 NativeWebContentsModalDialogManagerViews::
     ~NativeWebContentsModalDialogManagerViews() {
+  // Temporary, for debugging https://crbug.com/1207814.
+  CHECK(!within_show_);
+
   if (host_)
     host_->RemoveObserver(this);
 
@@ -97,11 +98,16 @@
         widget->GetNativeWindow()->parent());
   }
 #endif
-  // |host_| may be null during tab drag.
-  if (host_)
-    constrained_window::UpdateWebContentsModalDialogPosition(widget, host_);
+
+  // `host_` should not be null. If you can reproduce this, please comment on
+  // https://crbug.com/1207814.
+  CHECK(host_);
+  // Temporary, for debugging https://crbug.com/1207814.
+  base::AutoReset<bool> within_show(&within_show_, true);
+
+  constrained_window::UpdateWebContentsModalDialogPosition(widget, host_);
   widget->Show();
-  if (host_ && host_->ShouldActivateDialog())
+  if (host_->ShouldActivateDialog())
     Focus();
 
 #if defined(USE_AURA)
@@ -180,7 +186,7 @@
 
   host_ = new_host;
 
-  // |host_| may be null during WebContents destruction or Win32 tab dragging.
+  // |host_| may be null during WebContents destruction.
   if (host_) {
     host_->AddObserver(this);
 
diff --git a/components/constrained_window/native_web_contents_modal_dialog_manager_views.h b/components/constrained_window/native_web_contents_modal_dialog_manager_views.h
index 109d6d1..03eaacd 100644
--- a/components/constrained_window/native_web_contents_modal_dialog_manager_views.h
+++ b/components/constrained_window/native_web_contents_modal_dialog_manager_views.h
@@ -73,8 +73,9 @@
 
   web_modal::SingleWebContentsDialogManagerDelegate* native_delegate_;
   gfx::NativeWindow dialog_;
-  web_modal::WebContentsModalDialogHost* host_;
-  bool host_destroying_;
+  web_modal::WebContentsModalDialogHost* host_ = nullptr;
+  bool within_show_ = false;
+  bool host_destroying_ = false;
   std::set<views::Widget*> observed_widgets_;
   std::set<views::Widget*> shown_widgets_;
 
diff --git a/components/download/internal/common/download_item_impl.cc b/components/download/internal/common/download_item_impl.cc
index c3beb2e4..2d56947d 100644
--- a/components/download/internal/common/download_item_impl.cc
+++ b/components/download/internal/common/download_item_impl.cc
@@ -1585,7 +1585,12 @@
     destination_info_.hash.clear();
     deferred_interrupt_reason_ = new_create_info.result;
     TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
-    DetermineDownloadTarget();
+    // We're posting the call to DetermineDownloadTarget() instead of calling it
+    // directly to ensure that OnDownloadTargetDetermined() is not called
+    // synchronously. See crbug.com/1209856 for more details.
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(&DownloadItemImpl::DetermineDownloadTarget,
+                                  weak_ptr_factory_.GetWeakPtr()));
     return;
   }
 
diff --git a/components/leveldb_proto/public/shared_proto_database_client_list.cc b/components/leveldb_proto/public/shared_proto_database_client_list.cc
index bd625c1..669844f 100644
--- a/components/leveldb_proto/public/shared_proto_database_client_list.cc
+++ b/components/leveldb_proto/public/shared_proto_database_client_list.cc
@@ -93,6 +93,8 @@
       return "CommerceSubscriptionDatabase";
     case ProtoDbType::MERCHANT_TRUST_SIGNAL_DATABASE:
       return "MerchantTrustSignalEventDatabase";
+    case ProtoDbType::SHARE_HISTORY_DATABASE:
+      return "ShareHistoryDatabase";
     case ProtoDbType::LAST:
       NOTREACHED();
       return std::string();
diff --git a/components/leveldb_proto/public/shared_proto_database_client_list.h b/components/leveldb_proto/public/shared_proto_database_client_list.h
index 442adb6b..512ad14 100644
--- a/components/leveldb_proto/public/shared_proto_database_client_list.h
+++ b/components/leveldb_proto/public/shared_proto_database_client_list.h
@@ -58,6 +58,7 @@
   CART_DATABASE = 32,
   COMMERCE_SUBSCRIPTION_DATABASE = 33,
   MERCHANT_TRUST_SIGNAL_DATABASE = 34,
+  SHARE_HISTORY_DATABASE = 35,
   LAST,
 };
 
diff --git a/components/messages/android/BUILD.gn b/components/messages/android/BUILD.gn
index c1c7638..e5e1056e 100644
--- a/components/messages/android/BUILD.gn
+++ b/components/messages/android/BUILD.gn
@@ -7,6 +7,7 @@
 
 android_library("java") {
   sources = [
+    "java/src/org/chromium/components/messages/MessageAutodismissDurationProvider.java",
     "java/src/org/chromium/components/messages/MessageBannerProperties.java",
     "java/src/org/chromium/components/messages/MessageContainer.java",
     "java/src/org/chromium/components/messages/MessageDispatcher.java",
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageDispatcherImpl.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageDispatcherImpl.java
index 37086384..3173de4 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageDispatcherImpl.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageDispatcherImpl.java
@@ -19,7 +19,7 @@
     private final MessageQueueManager mMessageQueueManager = new MessageQueueManager();
     private final MessageContainer mMessageContainer;
     private final Supplier<Integer> mMessageMaxTranslationSupplier;
-    private final Supplier<Long> mAutodismissDurationMs;
+    private final MessageAutodismissDurationProvider mAutodismissDurationProvider;
     private final Callback<Animator> mAnimatorStartCallback;
 
     /**
@@ -27,18 +27,19 @@
      * @param messageContainer A container view for displaying message banners.
      * @param messageMaxTranslation A {@link Supplier} that supplies the maximum translation Y value
      *         the message banner can have as a result of the animations or the gestures.
-     * @param autodismissDurationMs A {@link Supplier} providing autodismiss duration for message
-     *         banner.
+     * @param autodismissDurationProvider A {@link MessageAutodismissDurationProvider} providing
+     *         autodismiss duration for message banner.
      * @param animatorStartCallback The {@link Callback} that will be used by the message to
      *         delegate starting the animations to the {@link WindowAndroid}.
      */
     public MessageDispatcherImpl(MessageContainer messageContainer,
-            Supplier<Integer> messageMaxTranslation, Supplier<Long> autodismissDurationMs,
+            Supplier<Integer> messageMaxTranslation,
+            MessageAutodismissDurationProvider autodismissDurationProvider,
             Callback<Animator> animatorStartCallback) {
         mMessageContainer = messageContainer;
         mMessageMaxTranslationSupplier = messageMaxTranslation;
         mAnimatorStartCallback = animatorStartCallback;
-        mAutodismissDurationMs = autodismissDurationMs;
+        mAutodismissDurationProvider = autodismissDurationProvider;
     }
 
     @Override
@@ -46,7 +47,7 @@
             @MessageScopeType int scopeType, boolean highPriority) {
         MessageStateHandler messageStateHandler = new SingleActionMessage(mMessageContainer,
                 messageProperties, this::dismissMessage, mMessageMaxTranslationSupplier,
-                mAutodismissDurationMs, mAnimatorStartCallback);
+                mAutodismissDurationProvider, mAnimatorStartCallback);
         ScopeKey scopeKey = new ScopeKey(scopeType, webContents);
         mMessageQueueManager.enqueueMessage(
                 messageStateHandler, messageProperties, scopeKey, highPriority);
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesFactory.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesFactory.java
index 32923a96..c864138 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesFactory.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesFactory.java
@@ -20,14 +20,15 @@
      *         to the MessageContainer. When messages are shown, they will be animated down the
      *         screen, starting at the negative |messageMaxTranslation| y translation to the resting
      *         position in the MessageContainer.
-     * @param autodismissDurationMs The {@link Supplier} providing autodismiss duration for message
-     *         banner.
+     * @param autodismissDurationMs The {@link MessageAutodismissDurationProvider} providing
+     *         autodismiss duration for message banner.
      * @param animatorStartCallback The {@link Callback} that will be used by the message to
      *         delegate starting the animations to the {@link WindowAndroid}.
      * @return The constructed ManagedMessageDispatcher.
      */
     public static ManagedMessageDispatcher createMessageDispatcher(MessageContainer container,
-            Supplier<Integer> messageMaxTranslation, Supplier<Long> autodismissDurationMs,
+            Supplier<Integer> messageMaxTranslation,
+            MessageAutodismissDurationProvider autodismissDurationMs,
             Callback<Animator> animatorStartCallback) {
         return new MessageDispatcherImpl(
                 container, messageMaxTranslation, autodismissDurationMs, animatorStartCallback);
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessage.java b/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessage.java
index 52ffc5f8..799abe8 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessage.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessage.java
@@ -44,22 +44,30 @@
      * @param model The PropertyModel with {@link MessageBannerProperties#ALL_KEYS}.
      * @param dismissHandler The {@link DismissCallback} able to dismiss a message by given property
      *         model.
-     * @param autodismissDurationMs A {@link Supplier} providing autodismiss duration for message
-     *         banner.
+     * @param autodismissDurationMs A {@link MessageAutodismissDurationProvider} providing
+     *         autodismiss duration for message banner. The actual duration can be extended by
+     *         clients.
      * @param maxTranslationSupplier A {@link Supplier} that supplies the maximum translation Y
      * @param animatorStartCallback The {@link Callback} that will be used by the message banner to
      *         delegate starting the animations to the {@link WindowAndroid}.
      */
     public SingleActionMessage(MessageContainer container, PropertyModel model,
             DismissCallback dismissHandler, Supplier<Integer> maxTranslationSupplier,
-            Supplier<Long> autodismissDurationMs, Callback<Animator> animatorStartCallback) {
+            MessageAutodismissDurationProvider autodismissDurationProvider,
+            Callback<Animator> animatorStartCallback) {
         mModel = model;
         mContainer = container;
         mDismissHandler = dismissHandler;
-        mAutodismissDurationMs = autodismissDurationMs;
         mMaxTranslationSupplier = maxTranslationSupplier;
         mAnimatorStartCallback = animatorStartCallback;
 
+        long dismissalDurationExtend = mModel.getAllSetProperties().contains(
+                                               MessageBannerProperties.DISMISSAL_DURATION_EXTEND)
+                ? mModel.get(MessageBannerProperties.DISMISSAL_DURATION_EXTEND)
+                : 0;
+
+        mAutodismissDurationMs = () -> autodismissDurationProvider.get(dismissalDurationExtend);
+
         mModel.set(
                 MessageBannerProperties.PRIMARY_BUTTON_CLICK_LISTENER, this::handlePrimaryAction);
     }
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessageTest.java b/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessageTest.java
index 7bacd3ed..b22628f00 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessageTest.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessageTest.java
@@ -49,6 +49,18 @@
 
     private static Activity sActivity;
 
+    private class MockDurationProvider implements MessageAutodismissDurationProvider {
+        private long mDuration;
+        public MockDurationProvider(long duration) {
+            mDuration = duration;
+        }
+
+        @Override
+        public long get(long extension) {
+            return mDuration;
+        }
+    }
+
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock
@@ -75,8 +87,9 @@
     public void testAddAndRemoveSingleActionMessage() throws Exception {
         MessageContainer container = new MessageContainer(sActivity, null);
         PropertyModel model = createBasicSingleActionMessageModel();
-        SingleActionMessage message = new SingleActionMessage(
-                container, model, mEmptyDismissCallback, () -> 0, () -> 0L, mAnimatorStartCallback);
+        SingleActionMessage message =
+                new SingleActionMessage(container, model, mEmptyDismissCallback,
+                        () -> 0, new MockDurationProvider(0L), mAnimatorStartCallback);
         final MessageBannerCoordinator messageBanner = Mockito.mock(MessageBannerCoordinator.class);
         final MessageBannerView view = new MessageBannerView(sActivity, null);
         view.setId(R.id.message_banner);
@@ -105,28 +118,43 @@
         MessageContainer container = new MessageContainer(sActivity, null);
         PropertyModel model = createBasicSingleActionMessageModel();
         long duration = 42;
-        SingleActionMessage message = new SingleActionMessage(container, model,
-                mEmptyDismissCallback, () -> 0, () -> duration, mAnimatorStartCallback);
+        SingleActionMessage message =
+                new SingleActionMessage(container, model, mEmptyDismissCallback,
+                        () -> 0, new MockDurationProvider(duration), mAnimatorStartCallback);
         Assert.assertEquals("Autodismiss duration is not propagated correctly.", duration,
                 message.getAutoDismissDuration());
     }
 
+    @Test
+    @MediumTest
+    public void testAutoDismissDurationExtended() {
+        MessageContainer container = new MessageContainer(sActivity, null);
+        PropertyModel model = createBasicSingleActionMessageModel();
+        model.set(MessageBannerProperties.DISMISSAL_DURATION_EXTEND, 1000);
+        long duration = 42;
+        SingleActionMessage message =
+                new SingleActionMessage(container, model, mEmptyDismissCallback,
+                        () -> 0, new MockDurationProvider(duration + 1000), mAnimatorStartCallback);
+        Assert.assertEquals("Autodismiss duration is not propagated correctly.", duration + 1000,
+                message.getAutoDismissDuration());
+    }
+
     @Test(expected = IllegalStateException.class)
     @MediumTest
     public void testAddMultipleSingleActionMessage() {
         MessageContainer container = new MessageContainer(sActivity, null);
         PropertyModel m1 = createBasicSingleActionMessageModel();
         PropertyModel m2 = createBasicSingleActionMessageModel();
-        SingleActionMessage message1 = new SingleActionMessage(
-                container, m1, mEmptyDismissCallback, () -> 0, () -> 0L, mAnimatorStartCallback);
+        SingleActionMessage message1 = new SingleActionMessage(container, m1, mEmptyDismissCallback,
+                () -> 0, new MockDurationProvider(0L), mAnimatorStartCallback);
         final MessageBannerCoordinator messageBanner1 =
                 Mockito.mock(MessageBannerCoordinator.class);
         final MessageBannerView view1 = new MessageBannerView(sActivity, null);
         view1.setId(R.id.message_banner);
         message1.setMessageBannerForTesting(messageBanner1);
         message1.setViewForTesting(view1);
-        SingleActionMessage message2 = new SingleActionMessage(
-                container, m2, mEmptyDismissCallback, () -> 0, () -> 0L, mAnimatorStartCallback);
+        SingleActionMessage message2 = new SingleActionMessage(container, m2, mEmptyDismissCallback,
+                () -> 0, new MockDurationProvider(0L), mAnimatorStartCallback);
         final MessageBannerCoordinator messageBanner2 =
                 Mockito.mock(MessageBannerCoordinator.class);
         final MessageBannerView view2 = new MessageBannerView(sActivity, null);
diff --git a/components/messages/android/java/src/org/chromium/components/messages/MessageAutodismissDurationProvider.java b/components/messages/android/java/src/org/chromium/components/messages/MessageAutodismissDurationProvider.java
new file mode 100644
index 0000000..5ab6f7f
--- /dev/null
+++ b/components/messages/android/java/src/org/chromium/components/messages/MessageAutodismissDurationProvider.java
@@ -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.
+
+package org.chromium.components.messages;
+
+/**
+ * Interface to provide a duration time for message.
+ */
+public interface MessageAutodismissDurationProvider {
+    /**
+     * Provide a duration time based on given custom duration and whether a11y mode is on.
+     * @param customDuration customDuration in milliseconds in non-a11y mode.
+     *        Set 0 to get default duration.
+     * @return The expected duration for the message.
+     */
+    long get(long customDuration);
+}
diff --git a/components/messages/android/java/src/org/chromium/components/messages/MessageBannerProperties.java b/components/messages/android/java/src/org/chromium/components/messages/MessageBannerProperties.java
index 57e3884..f44a59e 100644
--- a/components/messages/android/java/src/org/chromium/components/messages/MessageBannerProperties.java
+++ b/components/messages/android/java/src/org/chromium/components/messages/MessageBannerProperties.java
@@ -14,6 +14,7 @@
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableFloatPropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableLongPropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
 
 /**
@@ -52,6 +53,9 @@
             new WritableObjectPropertyKey<>();
     public static final WritableObjectPropertyKey<String> SECONDARY_ICON_CONTENT_DESCRIPTION =
             new WritableObjectPropertyKey<>();
+    // Unit: milliseconds.
+    public static final WritableLongPropertyKey DISMISSAL_DURATION_EXTEND =
+            new WritableLongPropertyKey();
     /**
      * The callback invoked when the message is dismissed. DismissReason is passed through the
      * callback's parameter.
@@ -73,6 +77,7 @@
     public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {PRIMARY_BUTTON_TEXT,
             PRIMARY_BUTTON_CLICK_LISTENER, TITLE, DESCRIPTION, ICON, ICON_RESOURCE_ID,
             ICON_TINT_COLOR, SECONDARY_ICON, SECONDARY_ICON_RESOURCE_ID, SECONDARY_BUTTON_MENU_TEXT,
-            SECONDARY_ICON_CONTENT_DESCRIPTION, TRANSLATION_X, TRANSLATION_Y, ALPHA,
-            ON_TOUCH_RUNNABLE, ON_PRIMARY_ACTION, ON_SECONDARY_ACTION, ON_DISMISSED};
+            SECONDARY_ICON_CONTENT_DESCRIPTION, DISMISSAL_DURATION_EXTEND, TRANSLATION_X,
+            TRANSLATION_Y, ALPHA, ON_TOUCH_RUNNABLE, ON_PRIMARY_ACTION, ON_SECONDARY_ACTION,
+            ON_DISMISSED};
 }
diff --git a/components/messages/android/java/src/org/chromium/components/messages/MessageWrapper.java b/components/messages/android/java/src/org/chromium/components/messages/MessageWrapper.java
index d60918e..37764d0 100644
--- a/components/messages/android/java/src/org/chromium/components/messages/MessageWrapper.java
+++ b/components/messages/android/java/src/org/chromium/components/messages/MessageWrapper.java
@@ -109,6 +109,11 @@
     }
 
     @CalledByNative
+    void setDurationExtension(long extension) {
+        mMessageProperties.set(MessageBannerProperties.DISMISSAL_DURATION_EXTEND, extension);
+    }
+
+    @CalledByNative
     void clearNativePtr() {
         mNativeMessageWrapper = 0;
     }
diff --git a/components/messages/android/message_wrapper.cc b/components/messages/android/message_wrapper.cc
index f08bd47..562754c 100644
--- a/components/messages/android/message_wrapper.cc
+++ b/components/messages/android/message_wrapper.cc
@@ -121,6 +121,12 @@
   secondary_action_callback_ = std::move(callback);
 }
 
+void MessageWrapper::SetDurationExtension(long extension) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_MessageWrapper_setDurationExtension(env, java_message_wrapper_,
+                                           extension);
+}
+
 void MessageWrapper::HandleActionClick(JNIEnv* env) {
   if (!action_callback_.is_null())
     std::move(action_callback_).Run();
@@ -149,4 +155,4 @@
   return java_message_wrapper_;
 }
 
-}  // namespace messages
\ No newline at end of file
+}  // namespace messages
diff --git a/components/messages/android/message_wrapper.h b/components/messages/android/message_wrapper.h
index 50e02e12..2ba5c25 100644
--- a/components/messages/android/message_wrapper.h
+++ b/components/messages/android/message_wrapper.h
@@ -52,6 +52,8 @@
 
   void SetSecondaryActionCallback(base::OnceClosure callback);
 
+  void SetDurationExtension(long extension);
+
   // Following methods forward calls from java to provided callbacks.
   void HandleActionClick(JNIEnv* env);
   void HandleSecondaryActionClick(JNIEnv* env);
@@ -69,4 +71,4 @@
 
 }  // namespace messages
 
-#endif  // COMPONENTS_MESSAGES_ANDROID_MESSAGE_WRAPPER_H_
\ No newline at end of file
+#endif  // COMPONENTS_MESSAGES_ANDROID_MESSAGE_WRAPPER_H_
diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc
index 8dd54e0d..75def06 100644
--- a/components/metrics/metrics_service.cc
+++ b/components/metrics/metrics_service.cc
@@ -140,6 +140,7 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "components/metrics/clean_exit_beacon.h"
 #include "components/metrics/environment_recorder.h"
 #include "components/metrics/field_trials_provider.h"
 #include "components/metrics/metrics_log.h"
@@ -175,15 +176,6 @@
 // The browser last live timestamp is updated every 15 minutes.
 const int kUpdateAliveTimestampSeconds = 15 * 60;
 
-#if defined(OS_ANDROID) || defined(OS_IOS)
-void MarkAppCleanShutdownAndCommit(CleanExitBeacon* clean_exit_beacon,
-                                   PrefService* local_state) {
-  clean_exit_beacon->WriteBeaconValue(true);
-  // Start writing right away (write happens on a different thread).
-  local_state->CommitPendingWrite();
-}
-#endif  // defined(OS_ANDROID) || defined(OS_IOS)
-
 }  // namespace
 
 // static
@@ -397,8 +389,9 @@
     reporting_service_.Stop();
   }
 
-  MarkAppCleanShutdownAndCommit(state_manager_->clean_exit_beacon(),
-                                local_state_);
+  state_manager_->LogHasSessionShutdownCleanly(true);
+  // Schedule a write, which happens on a different thread.
+  local_state_->CommitPendingWrite();
 
   // Give providers a chance to persist histograms as part of being
   // backgrounded.
@@ -419,7 +412,7 @@
 
 void MetricsService::OnAppEnterForeground(bool force_open_new_log) {
   is_in_foreground_ = true;
-  state_manager_->clean_exit_beacon()->WriteBeaconValue(false);
+  state_manager_->LogHasSessionShutdownCleanly(false);
   StartSchedulerIfNecessary();
 
   if (force_open_new_log && recording_active() && state_ >= SENDING_LOGS) {
@@ -432,11 +425,10 @@
 
 #else
 void MetricsService::LogNeedForCleanShutdown() {
-  state_manager_->clean_exit_beacon()->WriteBeaconValue(false);
+  state_manager_->LogHasSessionShutdownCleanly(false);
 }
 #endif  // defined(OS_ANDROID) || defined(OS_IOS)
 
-
 void MetricsService::ClearSavedStabilityMetrics() {
   delegating_provider_.ClearSavedStabilityMetrics();
 }
@@ -899,7 +891,7 @@
 
 void MetricsService::LogCleanShutdown(bool end_completed) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  state_manager_->clean_exit_beacon()->WriteBeaconValue(true);
+  state_manager_->LogHasSessionShutdownCleanly(true);
   StabilityMetricsProvider(local_state_).MarkSessionEndCompleted(end_completed);
 }
 
diff --git a/components/metrics/metrics_service.h b/components/metrics/metrics_service.h
index 94f4382..b0312a1 100644
--- a/components/metrics/metrics_service.h
+++ b/components/metrics/metrics_service.h
@@ -25,7 +25,6 @@
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "components/metrics/clean_exit_beacon.h"
 #include "components/metrics/delegating_provider.h"
 #include "components/metrics/metrics_log.h"
 #include "components/metrics/metrics_log_manager.h"
@@ -136,7 +135,8 @@
   // Called when the application is coming out of background mode.
   void OnAppEnterForeground(bool force_open_new_log = false);
 #else
-  // Set the dirty flag, which will require a later call to LogCleanShutdown().
+  // Signals that the session has not yet exited cleanly. Calling this later
+  // requires a call to LogCleanShutdown().
   void LogNeedForCleanShutdown();
 #endif  // defined(OS_ANDROID) || defined(OS_IOS)
 
diff --git a/components/metrics/metrics_state_manager.cc b/components/metrics/metrics_state_manager.cc
index 44f3df5..77a708d3 100644
--- a/components/metrics/metrics_state_manager.cc
+++ b/components/metrics/metrics_state_manager.cc
@@ -221,6 +221,11 @@
   return entropy_state_.GetLowEntropySource();
 }
 
+void MetricsStateManager::LogHasSessionShutdownCleanly(
+    bool has_session_shutdown_cleanly) {
+  clean_exit_beacon_.WriteBeaconValue(has_session_shutdown_cleanly);
+}
+
 void MetricsStateManager::ForceClientIdCreation() {
   // TODO(asvitkine): Ideally, all tests would actually set up consent properly,
   // so the command-line check wouldn't be needed here.
diff --git a/components/metrics/metrics_state_manager.h b/components/metrics/metrics_state_manager.h
index 0ae78a31..f9c98c4 100644
--- a/components/metrics/metrics_state_manager.h
+++ b/components/metrics/metrics_state_manager.h
@@ -66,6 +66,15 @@
     return &clean_exit_beacon_;
   }
 
+  // Signals whether the session has shutdown cleanly. Passing `false` means
+  // that Chrome has launched and has not yet shut down safely. Passing `true`
+  // signals that Chrome has shut down safely.
+  //
+  // Seeing a call with `false` without a matching call with `true` suggests
+  // that Chrome crashed or otherwise did not shut down cleanly, e.g. maybe the
+  // OS crashed.
+  void LogHasSessionShutdownCleanly(bool has_session_shutdown_cleanly);
+
   // Forces the client ID to be generated. This is useful in case it's needed
   // before recording.
   void ForceClientIdCreation();
diff --git a/components/metrics/metrics_state_manager_unittest.cc b/components/metrics/metrics_state_manager_unittest.cc
index 2b04e59..6c45c142 100644
--- a/components/metrics/metrics_state_manager_unittest.cc
+++ b/components/metrics/metrics_state_manager_unittest.cc
@@ -30,7 +30,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace metrics {
-
 namespace {
 
 // Verifies that the client id follows the expected pattern.
@@ -222,6 +221,22 @@
   EXPECT_NE(kInitialClientId, prefs_.GetString(prefs::kMetricsClientID));
 }
 
+TEST_F(MetricsStateManagerTest, LogHasSessionShutdownCleanly) {
+  std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+  prefs_.SetBoolean(prefs::kStabilityExitedCleanly, false);
+  state_manager->LogHasSessionShutdownCleanly(
+      /*has_session_shutdown_cleanly=*/true);
+  EXPECT_TRUE(prefs_.GetBoolean(prefs::kStabilityExitedCleanly));
+}
+
+TEST_F(MetricsStateManagerTest, LogSessionHasNotYetShutdownCleanly) {
+  std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+  ASSERT_TRUE(prefs_.GetBoolean(prefs::kStabilityExitedCleanly));
+  state_manager->LogHasSessionShutdownCleanly(
+      /*has_session_shutdown_cleanly=*/false);
+  EXPECT_FALSE(prefs_.GetBoolean(prefs::kStabilityExitedCleanly));
+}
+
 TEST_F(MetricsStateManagerTest, ForceClientIdCreation) {
   const int64_t kFakeInstallationDate = 12345;
   prefs_.SetInt64(prefs::kInstallDate, kFakeInstallationDate);
diff --git a/components/omnibox/browser/actions/omnibox_action.h b/components/omnibox/browser/actions/omnibox_action.h
index f83bcc1..fec801f 100644
--- a/components/omnibox/browser/actions/omnibox_action.h
+++ b/components/omnibox/browser/actions/omnibox_action.h
@@ -71,6 +71,12 @@
   // Provides read access to labels associated with this Action.
   const LabelStrings& GetLabelStrings() const;
 
+  // Records that the action was shown.
+  virtual void RecordActionShown() const {}
+
+  // Records that the action was executed.
+  virtual void RecordActionExecuted() const {}
+
   // Takes the action associated with this Action.  Non-navigation
   // Actions must override the default, but Navigation Actions don't need to.
   virtual void Execute(ExecutionContext& context) const;
diff --git a/components/omnibox/browser/actions/omnibox_pedal.cc b/components/omnibox/browser/actions/omnibox_pedal.cc
index d0d9bf1..6d68d7a1 100644
--- a/components/omnibox/browser/actions/omnibox_pedal.cc
+++ b/components/omnibox/browser/actions/omnibox_pedal.cc
@@ -8,6 +8,7 @@
 #include <cctype>
 #include <numeric>
 
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/trace_event/memory_usage_estimator.h"
 #include "components/omnibox/browser/buildflags.h"
@@ -241,6 +242,16 @@
   synonym_groups_.push_back(std::move(group));
 }
 
+void OmniboxPedal::RecordActionShown() const {
+  base::UmaHistogramEnumeration("Omnibox.PedalShown", id(),
+                                OmniboxPedalId::TOTAL_COUNT);
+}
+
+void OmniboxPedal::RecordActionExecuted() const {
+  base::UmaHistogramEnumeration("Omnibox.SuggestionUsed.Pedal", id(),
+                                OmniboxPedalId::TOTAL_COUNT);
+}
+
 size_t OmniboxPedal::EstimateMemoryUsage() const {
   size_t total = 0;
   total += OmniboxAction::EstimateMemoryUsage();
diff --git a/components/omnibox/browser/actions/omnibox_pedal.h b/components/omnibox/browser/actions/omnibox_pedal.h
index 5fcb26d..d9bcf01 100644
--- a/components/omnibox/browser/actions/omnibox_pedal.h
+++ b/components/omnibox/browser/actions/omnibox_pedal.h
@@ -163,17 +163,11 @@
 #if (!defined(OS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !defined(OS_IOS)
   // Returns the default vector icon to use for Pedals that do not specify one.
   static const gfx::VectorIcon& GetDefaultVectorIcon();
-
-  // Returns the vector icon to represent this Pedal's action in suggestion.
-  const gfx::VectorIcon& GetVectorIcon() const override;
 #endif
 
   // Move a synonym group into this Pedal's collection.
   void AddSynonymGroup(SynonymGroup&& group);
 
-  // Estimates RAM usage in bytes for this Pedal.
-  size_t EstimateMemoryUsage() const override;
-
   OmniboxPedalId id() const { return id_; }
 
   // If a sufficient set of triggering synonym groups are present in
@@ -183,6 +177,14 @@
   // this returns false. |match_sequence| is consumed/mutated by this method.
   bool IsConceptMatch(TokenSequence& match_sequence) const;
 
+  // OmniboxAction overrides:
+  void RecordActionShown() const override;
+  void RecordActionExecuted() const override;
+#if (!defined(OS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !defined(OS_IOS)
+  const gfx::VectorIcon& GetVectorIcon() const override;
+#endif
+  size_t EstimateMemoryUsage() const override;
+
  protected:
   FRIEND_TEST_ALL_PREFIXES(OmniboxPedalTest, SynonymGroupErasesFirstMatchOnly);
   FRIEND_TEST_ALL_PREFIXES(OmniboxPedalTest, SynonymGroupsDriveConceptMatches);
diff --git a/components/omnibox/browser/in_memory_url_index.cc b/components/omnibox/browser/in_memory_url_index.cc
index ef94797..b8572404 100644
--- a/components/omnibox/browser/in_memory_url_index.cc
+++ b/components/omnibox/browser/in_memory_url_index.cc
@@ -256,7 +256,7 @@
 
 void InMemoryURLIndex::PostRestoreFromCacheFileTask() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  TRACE_EVENT0("browser", "InMemoryURLIndex::PostRestoreFromCacheFileTask");
+  TRACE_EVENT0("omnibox", "InMemoryURLIndex::PostRestoreFromCacheFileTask");
 
   if (base::FeatureList::IsEnabled(
           omnibox::kHistoryQuickProviderAblateInMemoryURLIndexCacheFile)) {
@@ -342,6 +342,9 @@
 // Restoring from the History DB -----------------------------------------------
 
 void InMemoryURLIndex::ScheduleRebuildFromHistory() {
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
+      "omnibox", "InMemoryURLIndex::ScheduleRebuildFromHistory",
+      TRACE_ID_LOCAL(this));
   DCHECK(history_service_);
   history_service_->ScheduleDBTask(
       FROM_HERE,
@@ -354,6 +357,9 @@
 void InMemoryURLIndex::DoneRebuidingPrivateDataFromHistoryDB(
     bool succeeded,
     scoped_refptr<URLIndexPrivateData> private_data) {
+  TRACE_EVENT_NESTABLE_ASYNC_END0(
+      "omnibox", "InMemoryURLIndex::ScheduleRebuildFromHistory",
+      TRACE_ID_LOCAL(this));
   DCHECK(thread_checker_.CalledOnValidThread());
   if (succeeded) {
     private_data_tracker_.TryCancelAll();
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index 9a98fce..986753e 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -724,15 +724,12 @@
   // Record the presence of any Pedals in the result set.
   for (const AutocompleteMatch& match_in_result : result()) {
     if (match_in_result.pedal) {
-      base::UmaHistogramEnumeration("Omnibox.PedalShown",
-                                    match_in_result.pedal->id(),
-                                    OmniboxPedalId::TOTAL_COUNT);
+      match_in_result.pedal->RecordActionShown();
     }
   }
 
-  // Record the use of this Pedal.
-  base::UmaHistogramEnumeration("Omnibox.SuggestionUsed.Pedal", pedal->id(),
-                                OmniboxPedalId::TOTAL_COUNT);
+  pedal->RecordActionExecuted();
+
   {
     // This block resets omnibox to unedited state and closes popup, which
     // may not seem necessary in cases of navigation but makes sense for
@@ -763,9 +760,7 @@
   // Record the presence of any Pedals in the result set.
   for (const AutocompleteMatch& match_in_result : result()) {
     if (match_in_result.pedal) {
-      base::UmaHistogramEnumeration("Omnibox.PedalShown",
-                                    match_in_result.pedal->id(),
-                                    OmniboxPedalId::TOTAL_COUNT);
+      match_in_result.pedal->RecordActionShown();
     }
   }
 
diff --git a/components/optimization_guide/DEPS b/components/optimization_guide/DEPS
index 9a4a580..c7b5a4d 100644
--- a/components/optimization_guide/DEPS
+++ b/components/optimization_guide/DEPS
@@ -12,6 +12,7 @@
   "+third_party/re2",
   "+third_party/smhasher",
   "+third_party/tflite",
+  "+third_party/tflite-support",
 
   # Optimization Guide is a layered component; subdirectories must explicitly
   # introduce the ability to use the content layer as appropriate.
diff --git a/components/optimization_guide/content/browser/BUILD.gn b/components/optimization_guide/content/browser/BUILD.gn
index b69d4fac1..9f608e2 100644
--- a/components/optimization_guide/content/browser/BUILD.gn
+++ b/components/optimization_guide/content/browser/BUILD.gn
@@ -21,11 +21,6 @@
   ]
   if (build_with_tflite_lib) {
     sources += [
-      "base_model_executor.h",
-      "base_model_executor_helpers.h",
-      "bert_model_executor.cc",
-      "bert_model_executor.h",
-      "model_executor.h",
       "page_content_annotations_model_manager.cc",
       "page_content_annotations_model_manager.h",
     ]
@@ -73,15 +68,12 @@
     "page_text_observer_unittest.cc",
   ]
   if (build_with_tflite_lib) {
-    sources += [
-      "bert_model_executor_unittest.cc",
-      "model_executor_unittest.cc",
-      "page_content_annotations_model_manager_unittest.cc",
-    ]
+    sources += [ "page_content_annotations_model_manager_unittest.cc" ]
   }
   deps = [
     ":browser",
     ":test_support",
+    "//components/optimization_guide/core:test_support",
     "//content/test:test_support",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/components/optimization_guide/content/browser/DEPS b/components/optimization_guide/content/browser/DEPS
index e9a2bafcb..d173161 100644
--- a/components/optimization_guide/content/browser/DEPS
+++ b/components/optimization_guide/content/browser/DEPS
@@ -8,6 +8,4 @@
   "+mojo/public",
   "+services/service_manager/public",
   "+third_party/blink/public",
-  "+third_party/tflite",
-  "+third_party/tflite-support",
 ]
diff --git a/components/optimization_guide/content/browser/optimization_guide_decider.h b/components/optimization_guide/content/browser/optimization_guide_decider.h
index 96fec734..3f6f34b5 100644
--- a/components/optimization_guide/content/browser/optimization_guide_decider.h
+++ b/components/optimization_guide/content/browser/optimization_guide_decider.h
@@ -10,7 +10,6 @@
 #include "base/callback_forward.h"
 #include "base/containers/flat_map.h"
 #include "components/optimization_guide/core/optimization_metadata.h"
-#include "components/optimization_guide/core/optimization_target_model_observer.h"
 #include "components/optimization_guide/proto/hints.pb.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -67,28 +66,6 @@
       proto::OptimizationTarget optimization_target,
       OptimizationGuideTargetDecisionCallback callback) = 0;
 
-  // Adds an observer for updates to the model for |optimization_target|.
-  //
-  // It is assumed that any model retrieved this way will be passed to the
-  // Machine Learning Service for inference.
-  //
-  // It is also assumed that there will only be one observer per optimization
-  // target, so if multiple observers are registered, this will crash in debug
-  // builds and be a no-op in release builds.
-  virtual void AddObserverForOptimizationTargetModel(
-      proto::OptimizationTarget optimization_target,
-      const absl::optional<proto::Any>& model_metadata,
-      OptimizationTargetModelObserver* observer) = 0;
-
-  // Removes an observer for updates to the model for |optimization_target|.
-  //
-  // If |observer| is registered for multiple targets, |observer| must be
-  // removed for all targets that it is added for in order for it to be fully
-  // removed from receiving any calls.
-  virtual void RemoveObserverForOptimizationTargetModel(
-      proto::OptimizationTarget optimization_target,
-      OptimizationTargetModelObserver* observer) = 0;
-
   // Registers the optimization types that intend to be queried during the
   // session. It is expected for this to be called after the browser has been
   // initialized.
diff --git a/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc b/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc
index d7af131f..a739396 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc
@@ -8,7 +8,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
-#include "components/optimization_guide/content/browser/optimization_guide_decider.h"
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
 
 namespace optimization_guide {
 
@@ -26,7 +26,7 @@
 }  // namespace
 
 PageContentAnnotationsModelManager::PageContentAnnotationsModelManager(
-    optimization_guide::OptimizationGuideDecider* optimization_guide_decider) {
+    OptimizationGuideModelProvider* optimization_guide_model_provider) {
   proto::Any model_metadata;
   model_metadata.set_type_url(kPageTopicsModelMetadataTypeUrl);
   proto::PageTopicsModelMetadata page_topics_model_metadata;
@@ -38,7 +38,7 @@
 
   page_topics_model_executor_handle_ =
       std::make_unique<BertModelExecutorHandle>(
-          optimization_guide_decider,
+          optimization_guide_model_provider,
           base::ThreadPool::CreateSequencedTaskRunner(
               {base::MayBlock(), base::TaskPriority::BEST_EFFORT}),
           proto::OPTIMIZATION_TARGET_PAGE_TOPICS, model_metadata);
diff --git a/components/optimization_guide/content/browser/page_content_annotations_model_manager.h b/components/optimization_guide/content/browser/page_content_annotations_model_manager.h
index 6a2bdbf..48ebc0d 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_model_manager.h
+++ b/components/optimization_guide/content/browser/page_content_annotations_model_manager.h
@@ -6,12 +6,12 @@
 #define COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_MODEL_MANAGER_H_
 
 #include "components/history/core/browser/url_row.h"
-#include "components/optimization_guide/content/browser/bert_model_executor.h"
+#include "components/optimization_guide/core/bert_model_executor.h"
 #include "components/optimization_guide/proto/page_topics_model_metadata.pb.h"
 
 namespace optimization_guide {
 
-class OptimizationGuideDecider;
+class OptimizationGuideModelProvider;
 
 // Callback to inform the caller that the page content has been annotated.
 using PageContentAnnotatedCallback = base::OnceCallback<void(
@@ -21,7 +21,7 @@
 class PageContentAnnotationsModelManager {
  public:
   explicit PageContentAnnotationsModelManager(
-      OptimizationGuideDecider* optimization_guide_decider);
+      OptimizationGuideModelProvider* optimization_guide_model_provider);
   ~PageContentAnnotationsModelManager();
   PageContentAnnotationsModelManager(
       const PageContentAnnotationsModelManager&) = delete;
diff --git a/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc b/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc
index a9df46c8f..9df98ca7 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc
@@ -5,7 +5,7 @@
 #include "components/optimization_guide/content/browser/page_content_annotations_model_manager.h"
 
 #include "base/path_service.h"
-#include "components/optimization_guide/content/browser/test_optimization_guide_decider.h"
+#include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
 #include "components/optimization_guide/machine_learning_tflite_buildflags.h"
 #include "components/optimization_guide/proto/page_topics_model_metadata.pb.h"
 #include "content/public/test/browser_task_environment.h"
@@ -16,7 +16,8 @@
 
 using testing::UnorderedElementsAre;
 
-class PageTopicsModelObserverTracker : public TestOptimizationGuideDecider {
+class PageTopicsModelObserverTracker
+    : public TestOptimizationGuideModelProvider {
  public:
   void AddObserverForOptimizationTargetModel(
       proto::OptimizationTarget target,
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.cc b/components/optimization_guide/content/browser/page_content_annotations_service.cc
index 75b55e4..ec39ffd 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.cc
@@ -6,9 +6,9 @@
 
 #include "base/metrics/histogram_functions.h"
 #include "components/history/core/browser/history_service.h"
-#include "components/optimization_guide/content/browser/optimization_guide_decider.h"
 #include "components/optimization_guide/core/optimization_guide_enums.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/web_contents.h"
 
@@ -34,14 +34,14 @@
 #endif
 
 PageContentAnnotationsService::PageContentAnnotationsService(
-    OptimizationGuideDecider* optimization_guide_decider,
+    OptimizationGuideModelProvider* optimization_guide_model_provider,
     history::HistoryService* history_service) {
-  DCHECK(optimization_guide_decider);
+  DCHECK(optimization_guide_model_provider);
   DCHECK(history_service);
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
   history_service_ = history_service;
   model_manager_ = std::make_unique<PageContentAnnotationsModelManager>(
-      optimization_guide_decider);
+      optimization_guide_model_provider);
 #endif
 }
 
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.h b/components/optimization_guide/content/browser/page_content_annotations_service.h
index a8311212..296e5e0 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.h
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.h
@@ -25,7 +25,7 @@
 
 namespace optimization_guide {
 
-class OptimizationGuideDecider;
+class OptimizationGuideModelProvider;
 class PageContentAnnotationsModelManager;
 
 // The information used by HistoryService to identify a visit to a URL.
@@ -38,7 +38,7 @@
 class PageContentAnnotationsService : public KeyedService {
  public:
   explicit PageContentAnnotationsService(
-      OptimizationGuideDecider* optimization_guide_decider,
+      OptimizationGuideModelProvider* optimization_guide_model_provider,
       history::HistoryService* history_service);
   ~PageContentAnnotationsService() override;
   PageContentAnnotationsService(const PageContentAnnotationsService&) = delete;
diff --git a/components/optimization_guide/content/browser/page_content_annotations_web_contents_helper_unittest.cc b/components/optimization_guide/content/browser/page_content_annotations_web_contents_helper_unittest.cc
index acc8eab5..d55436b 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_web_contents_helper_unittest.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_web_contents_helper_unittest.cc
@@ -8,8 +8,8 @@
 #include "components/history/core/browser/history_service.h"
 #include "components/optimization_guide/content/browser/page_content_annotations_service.h"
 #include "components/optimization_guide/content/browser/page_text_dump_result.h"
-#include "components/optimization_guide/content/browser/test_optimization_guide_decider.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
 #include "content/public/test/mock_navigation_handle.h"
 #include "content/public/test/navigation_simulator.h"
 #include "content/public/test/test_renderer_host.h"
@@ -37,9 +37,9 @@
 class FakePageContentAnnotationsService : public PageContentAnnotationsService {
  public:
   explicit FakePageContentAnnotationsService(
-      OptimizationGuideDecider* optimization_guide_decider,
+      OptimizationGuideModelProvider* optimization_guide_model_provider,
       history::HistoryService* history_service)
-      : PageContentAnnotationsService(optimization_guide_decider,
+      : PageContentAnnotationsService(optimization_guide_model_provider,
                                       history_service) {}
   ~FakePageContentAnnotationsService() override = default;
 
@@ -62,12 +62,12 @@
   void SetUp() override {
     content::RenderViewHostTestHarness::SetUp();
 
-    optimization_guide_decider_ =
-        std::make_unique<TestOptimizationGuideDecider>();
+    optimization_guide_model_provider_ =
+        std::make_unique<TestOptimizationGuideModelProvider>();
     history_service_ = std::make_unique<history::HistoryService>();
     page_content_annotations_service_ =
         std::make_unique<FakePageContentAnnotationsService>(
-            optimization_guide_decider_.get(), history_service_.get());
+            optimization_guide_model_provider_.get(), history_service_.get());
 
     page_text_observer_ = new TestPageTextObserver(web_contents());
     web_contents()->SetUserData(TestPageTextObserver::UserDataKey(),
@@ -80,7 +80,7 @@
   void TearDown() override {
     page_text_observer_ = nullptr;
     page_content_annotations_service_.reset();
-    optimization_guide_decider_.reset();
+    optimization_guide_model_provider_.reset();
 
     content::RenderViewHostTestHarness::TearDown();
   }
@@ -107,7 +107,8 @@
   }
 
  private:
-  std::unique_ptr<TestOptimizationGuideDecider> optimization_guide_decider_;
+  std::unique_ptr<TestOptimizationGuideModelProvider>
+      optimization_guide_model_provider_;
   std::unique_ptr<history::HistoryService> history_service_;
   std::unique_ptr<FakePageContentAnnotationsService>
       page_content_annotations_service_;
diff --git a/components/optimization_guide/content/browser/test_optimization_guide_decider.cc b/components/optimization_guide/content/browser/test_optimization_guide_decider.cc
index 2d6a017..8042a079 100644
--- a/components/optimization_guide/content/browser/test_optimization_guide_decider.cc
+++ b/components/optimization_guide/content/browser/test_optimization_guide_decider.cc
@@ -32,15 +32,6 @@
                           /*optimization_metadata=*/{});
 }
 
-void TestOptimizationGuideDecider::AddObserverForOptimizationTargetModel(
-    optimization_guide::proto::OptimizationTarget optimization_target,
-    const absl::optional<proto::Any>& model_metadata,
-    optimization_guide::OptimizationTargetModelObserver* observer) {}
-
-void TestOptimizationGuideDecider::RemoveObserverForOptimizationTargetModel(
-    optimization_guide::proto::OptimizationTarget optimization_target,
-    optimization_guide::OptimizationTargetModelObserver* observer) {}
-
 OptimizationGuideDecision TestOptimizationGuideDecider::CanApplyOptimization(
     const GURL& url,
     proto::OptimizationType optimization_type,
diff --git a/components/optimization_guide/content/browser/test_optimization_guide_decider.h b/components/optimization_guide/content/browser/test_optimization_guide_decider.h
index e2fdbe7..17e91f2 100644
--- a/components/optimization_guide/content/browser/test_optimization_guide_decider.h
+++ b/components/optimization_guide/content/browser/test_optimization_guide_decider.h
@@ -27,13 +27,6 @@
       content::NavigationHandle* navigation_handle,
       proto::OptimizationTarget optimization_target,
       OptimizationGuideTargetDecisionCallback callback) override;
-  void AddObserverForOptimizationTargetModel(
-      proto::OptimizationTarget optimization_target,
-      const absl::optional<proto::Any>& model_metadata,
-      OptimizationTargetModelObserver* observer) override;
-  void RemoveObserverForOptimizationTargetModel(
-      proto::OptimizationTarget optimization_target,
-      OptimizationTargetModelObserver* observer) override;
   void RegisterOptimizationTypes(
       const std::vector<proto::OptimizationType>& optimization_types) override;
   void CanApplyOptimizationAsync(
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index 51b57e9..0bf68b3 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -36,6 +36,7 @@
     "optimization_guide_enums.h",
     "optimization_guide_features.cc",
     "optimization_guide_features.h",
+    "optimization_guide_model_provider.h",
     "optimization_guide_permissions_util.cc",
     "optimization_guide_permissions_util.h",
     "optimization_guide_prefs.cc",
@@ -65,11 +66,29 @@
     "url_pattern_with_wildcards.cc",
     "url_pattern_with_wildcards.h",
   ]
+  if (build_with_tflite_lib) {
+    sources += [
+      "base_model_executor.h",
+      "base_model_executor_helpers.h",
+      "bert_model_executor.cc",
+      "bert_model_executor.h",
+      "model_executor.h",
+    ]
+  }
 
   public_deps = [
     "//components/optimization_guide:machine_learning_tflite_buildflags",
     "//third_party/re2",
   ]
+  if (build_with_tflite_lib) {
+    public_deps += [
+      "//components/optimization_guide/core:machine_learning",
+      "//third_party/tflite",
+      "//third_party/tflite:tflite_public_headers",
+      "//third_party/tflite-support",
+      "//third_party/tflite-support:tflite-support-proto",
+    ]
+  }
 
   deps = [
     "//base",
@@ -110,6 +129,8 @@
     "proto_database_provider_test_base.h",
     "test_hints_component_creator.cc",
     "test_hints_component_creator.h",
+    "test_optimization_guide_model_provider.cc",
+    "test_optimization_guide_model_provider.h",
   ]
   deps = [
     ":core",
@@ -144,6 +165,12 @@
     "store_update_data_unittest.cc",
     "url_pattern_with_wildcards_unittest.cc",
   ]
+  if (build_with_tflite_lib) {
+    sources += [
+      "bert_model_executor_unittest.cc",
+      "model_executor_unittest.cc",
+    ]
+  }
 
   deps = [
     ":core",
@@ -164,4 +191,12 @@
     "//testing/gtest",
     "//url:url",
   ]
+  if (build_with_tflite_lib) {
+    deps += [
+      "//third_party/tflite",
+      "//third_party/tflite:tflite_public_headers",
+      "//third_party/tflite-support",
+      "//third_party/tflite-support:tflite-support-proto",
+    ]
+  }
 }
diff --git a/components/optimization_guide/content/browser/base_model_executor.h b/components/optimization_guide/core/base_model_executor.h
similarity index 87%
rename from components/optimization_guide/content/browser/base_model_executor.h
rename to components/optimization_guide/core/base_model_executor.h
index 479b1b6..446777c 100644
--- a/components/optimization_guide/content/browser/base_model_executor.h
+++ b/components/optimization_guide/core/base_model_executor.h
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_BASE_MODEL_EXECUTOR_H_
-#define COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_BASE_MODEL_EXECUTOR_H_
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_BASE_MODEL_EXECUTOR_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_BASE_MODEL_EXECUTOR_H_
 
-#include "components/optimization_guide/content/browser/base_model_executor_helpers.h"
-#include "components/optimization_guide/content/browser/model_executor.h"
+#include "components/optimization_guide/core/base_model_executor_helpers.h"
+#include "components/optimization_guide/core/model_executor.h"
 #include "components/optimization_guide/core/tflite_op_resolver.h"
 #include "third_party/tflite-support/src/tensorflow_lite_support/cc/task/core/base_task_api.h"
 
@@ -72,4 +72,4 @@
 
 }  // namespace optimization_guide
 
-#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_BASE_MODEL_EXECUTOR_H_
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_BASE_MODEL_EXECUTOR_H_
diff --git a/components/optimization_guide/content/browser/base_model_executor_helpers.h b/components/optimization_guide/core/base_model_executor_helpers.h
similarity index 88%
rename from components/optimization_guide/content/browser/base_model_executor_helpers.h
rename to components/optimization_guide/core/base_model_executor_helpers.h
index e68e7762..45cb63ff 100644
--- a/components/optimization_guide/content/browser/base_model_executor_helpers.h
+++ b/components/optimization_guide/core/base_model_executor_helpers.h
@@ -2,9 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_BASE_MODEL_EXECUTOR_HELPERS_H_
-#define COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_BASE_MODEL_EXECUTOR_HELPERS_H_
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_BASE_MODEL_EXECUTOR_HELPERS_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_BASE_MODEL_EXECUTOR_HELPERS_H_
 
+#include <memory>
+#include <vector>
+
+#include "base/check.h"
 #include "third_party/tflite-support/src/tensorflow_lite_support/cc/task/core/base_task_api.h"
 
 namespace optimization_guide {
@@ -64,4 +68,4 @@
 
 }  // namespace optimization_guide
 
-#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_BASE_MODEL_EXECUTOR_HELPERS_H_
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_BASE_MODEL_EXECUTOR_HELPERS_H_
diff --git a/components/optimization_guide/content/browser/bert_model_executor.cc b/components/optimization_guide/core/bert_model_executor.cc
similarity index 91%
rename from components/optimization_guide/content/browser/bert_model_executor.cc
rename to components/optimization_guide/core/bert_model_executor.cc
index cc821f35..5130dede 100644
--- a/components/optimization_guide/content/browser/bert_model_executor.cc
+++ b/components/optimization_guide/core/bert_model_executor.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/optimization_guide/content/browser/bert_model_executor.h"
+#include "components/optimization_guide/core/bert_model_executor.h"
 
 #include "components/optimization_guide/core/tflite_op_resolver.h"
 #include "third_party/tflite-support/src/tensorflow_lite_support/cc/task/text/nlclassifier/bert_nl_classifier.h"
@@ -34,12 +34,12 @@
 }
 
 BertModelExecutorHandle::BertModelExecutorHandle(
-    OptimizationGuideDecider* decider,
+    OptimizationGuideModelProvider* model_provider,
     scoped_refptr<base::SequencedTaskRunner> background_task_runner,
     proto::OptimizationTarget optimization_target,
     const absl::optional<proto::Any>& model_metadata)
     : ModelHandler<std::vector<tflite::task::core::Category>,
-                   const std::string&>(decider,
+                   const std::string&>(model_provider,
                                        background_task_runner,
                                        std::make_unique<BertModelExecutor>(),
                                        optimization_target,
diff --git a/components/optimization_guide/content/browser/bert_model_executor.h b/components/optimization_guide/core/bert_model_executor.h
similarity index 83%
rename from components/optimization_guide/content/browser/bert_model_executor.h
rename to components/optimization_guide/core/bert_model_executor.h
index 3d0770b..3de568b 100644
--- a/components/optimization_guide/content/browser/bert_model_executor.h
+++ b/components/optimization_guide/core/bert_model_executor.h
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_BERT_MODEL_EXECUTOR_H_
-#define COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_BERT_MODEL_EXECUTOR_H_
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_BERT_MODEL_EXECUTOR_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_BERT_MODEL_EXECUTOR_H_
 
-#include "components/optimization_guide/content/browser/model_executor.h"
+#include "components/optimization_guide/core/model_executor.h"
 #include "third_party/tflite-support/src/tensorflow_lite_support/cc/task/core/category.h"
 
 namespace optimization_guide {
@@ -19,7 +19,7 @@
                           const std::string&> {
  public:
   BertModelExecutorHandle(
-      OptimizationGuideDecider* decider,
+      OptimizationGuideModelProvider* model_provider,
       scoped_refptr<base::SequencedTaskRunner> background_task_runner,
       proto::OptimizationTarget optimization_target,
       const absl::optional<proto::Any>& model_metadata);
@@ -51,4 +51,4 @@
 
 }  // namespace optimization_guide
 
-#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_BERT_MODEL_EXECUTOR_H_
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_BERT_MODEL_EXECUTOR_H_
diff --git a/components/optimization_guide/content/browser/bert_model_executor_unittest.cc b/components/optimization_guide/core/bert_model_executor_unittest.cc
similarity index 88%
rename from components/optimization_guide/content/browser/bert_model_executor_unittest.cc
rename to components/optimization_guide/core/bert_model_executor_unittest.cc
index 95e60fe..1a756e5 100644
--- a/components/optimization_guide/content/browser/bert_model_executor_unittest.cc
+++ b/components/optimization_guide/core/bert_model_executor_unittest.cc
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/optimization_guide/content/browser/bert_model_executor.h"
+#include "components/optimization_guide/core/bert_model_executor.h"
 
 #include "base/path_service.h"
 #include "base/test/task_environment.h"
-#include "components/optimization_guide/content/browser/test_optimization_guide_decider.h"
+#include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace optimization_guide {
@@ -14,8 +14,8 @@
 class BertModelExecutorTest : public testing::Test {
  public:
   void SetUp() override {
-    optimization_guide_decider_ =
-        std::make_unique<TestOptimizationGuideDecider>();
+    optimization_guide_model_provider_ =
+        std::make_unique<TestOptimizationGuideModelProvider>();
   }
 
   void TearDown() override {
@@ -25,7 +25,7 @@
 
   void CreateModelExecutor() {
     model_executor_handle_ = std::make_unique<BertModelExecutorHandle>(
-        optimization_guide_decider_.get(),
+        optimization_guide_model_provider_.get(),
         task_environment_.GetMainThreadTaskRunner(),
         proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
         /*model_metadata=*/absl::nullopt);
@@ -56,7 +56,8 @@
  private:
   base::test::TaskEnvironment task_environment_;
 
-  std::unique_ptr<TestOptimizationGuideDecider> optimization_guide_decider_;
+  std::unique_ptr<TestOptimizationGuideModelProvider>
+      optimization_guide_model_provider_;
   std::unique_ptr<BertModelExecutorHandle> model_executor_handle_;
 };
 
diff --git a/components/optimization_guide/content/browser/model_executor.h b/components/optimization_guide/core/model_executor.h
similarity index 95%
rename from components/optimization_guide/content/browser/model_executor.h
rename to components/optimization_guide/core/model_executor.h
index eb8511ee1..19ceb024 100644
--- a/components/optimization_guide/content/browser/model_executor.h
+++ b/components/optimization_guide/core/model_executor.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_MODEL_EXECUTOR_H_
-#define COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_MODEL_EXECUTOR_H_
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTOR_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTOR_H_
 
 #include "base/bind.h"
 #include "base/callback_forward.h"
@@ -14,9 +14,9 @@
 #include "base/sequence_checker.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
-#include "components/optimization_guide/content/browser/optimization_guide_decider.h"
 #include "components/optimization_guide/core/optimization_guide_enums.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/optimization_guide/core/optimization_target_model_observer.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -281,23 +281,23 @@
 template <class OutputType, class... InputTypes>
 class ModelHandler : public OptimizationTargetModelObserver {
  public:
-  ModelHandler(OptimizationGuideDecider* decider,
+  ModelHandler(OptimizationGuideModelProvider* model_provider,
                scoped_refptr<base::SequencedTaskRunner> background_task_runner,
                std::unique_ptr<ModelExecutor<OutputType, InputTypes...>>
                    background_executor,
                proto::OptimizationTarget optimization_target,
                const absl::optional<proto::Any>& model_metadata)
-      : decider_(decider),
+      : model_provider_(model_provider),
         optimization_target_(optimization_target),
         background_executor_(std::move(background_executor)),
         background_task_runner_(background_task_runner) {
-    DCHECK(decider_);
+    DCHECK(model_provider_);
     DCHECK(background_executor_);
     DCHECK_NE(optimization_target_,
               proto::OptimizationTarget::OPTIMIZATION_TARGET_UNKNOWN);
 
-    decider_->AddObserverForOptimizationTargetModel(optimization_target_,
-                                                    model_metadata, this);
+    model_provider_->AddObserverForOptimizationTargetModel(
+        optimization_target_, model_metadata, this);
     background_executor_->InitializeAndMoveToBackgroundThread(
         optimization_target_, background_task_runner_,
         base::SequencedTaskRunnerHandle::Get());
@@ -305,8 +305,8 @@
   ~ModelHandler() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-    decider_->RemoveObserverForOptimizationTargetModel(optimization_target_,
-                                                       this);
+    model_provider_->RemoveObserverForOptimizationTargetModel(
+        optimization_target_, this);
 
     // |background_executor_|'s  WeakPtrs are used on the background thread, so
     // that is also where the class must be destroyed.
@@ -407,7 +407,8 @@
   }
 
   // Not owned. Guaranteed to outlive |this|.
-  OptimizationGuideDecider* decider_ GUARDED_BY_CONTEXT(sequence_checker_);
+  OptimizationGuideModelProvider* model_provider_
+      GUARDED_BY_CONTEXT(sequence_checker_);
 
   const proto::OptimizationTarget optimization_target_;
 
@@ -433,4 +434,4 @@
 
 }  // namespace optimization_guide
 
-#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_MODEL_EXECUTOR_H_
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTOR_H_
diff --git a/components/optimization_guide/content/browser/model_executor_unittest.cc b/components/optimization_guide/core/model_executor_unittest.cc
similarity index 97%
rename from components/optimization_guide/content/browser/model_executor_unittest.cc
rename to components/optimization_guide/core/model_executor_unittest.cc
index 3cde83e..e6baf8e 100644
--- a/components/optimization_guide/content/browser/model_executor_unittest.cc
+++ b/components/optimization_guide/core/model_executor_unittest.cc
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/optimization_guide/content/browser/model_executor.h"
+#include "components/optimization_guide/core/model_executor.h"
 
 #include "base/path_service.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
-#include "components/optimization_guide/content/browser/base_model_executor.h"
-#include "components/optimization_guide/content/browser/test_optimization_guide_decider.h"
+#include "components/optimization_guide/core/base_model_executor.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
 #include "components/optimization_guide/proto/common_types.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/tflite-support/src/tensorflow_lite_support/cc/task/core/task_utils.h"
@@ -41,10 +41,10 @@
     : public ModelHandler<std::vector<float>, const std::vector<float>&> {
  public:
   explicit TestModelExecutorHandle(
-      OptimizationGuideDecider* decider,
+      OptimizationGuideModelProvider* model_provider,
       scoped_refptr<base::SequencedTaskRunner> background_task_runner)
       : ModelHandler<std::vector<float>, const std::vector<float>&>(
-            decider,
+            model_provider,
             background_task_runner,
             std::make_unique<TestModelExecutor>(),
             proto::OptimizationTarget::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
@@ -58,7 +58,7 @@
   // absl::optional<proto::Any> supported_features_for_loaded_model();
 };
 
-class ModelObserverTracker : public TestOptimizationGuideDecider {
+class ModelObserverTracker : public TestOptimizationGuideModelProvider {
  public:
   void AddObserverForOptimizationTargetModel(
       proto::OptimizationTarget target,
diff --git a/components/optimization_guide/core/optimization_guide_model_provider.h b/components/optimization_guide/core/optimization_guide_model_provider.h
new file mode 100644
index 0000000..3e83564
--- /dev/null
+++ b/components/optimization_guide/core/optimization_guide_model_provider.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_OPTIMIZATION_GUIDE_CORE_OPTIMIZATION_GUIDE_MODEL_PROVIDER_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_OPTIMIZATION_GUIDE_MODEL_PROVIDER_H_
+
+#include "components/optimization_guide/core/optimization_target_model_observer.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace optimization_guide {
+
+// Provides models configured to be served by the Optimization Guide to be used
+// for inference.
+class OptimizationGuideModelProvider {
+ public:
+  // Adds an observer for updates to the model for |optimization_target|.
+  //
+  // It is assumed that any model retrieved this way will be passed to the
+  // Machine Learning Service for inference.
+  //
+  // It is also assumed that there will only be one observer per optimization
+  // target, so if multiple observers are registered, this will crash in debug
+  // builds and be a no-op in release builds.
+  virtual void AddObserverForOptimizationTargetModel(
+      proto::OptimizationTarget optimization_target,
+      const absl::optional<proto::Any>& model_metadata,
+      OptimizationTargetModelObserver* observer) = 0;
+
+  // Removes an observer for updates to the model for |optimization_target|.
+  //
+  // If |observer| is registered for multiple targets, |observer| must be
+  // removed for all targets that it is added for in order for it to be fully
+  // removed from receiving any calls.
+  virtual void RemoveObserverForOptimizationTargetModel(
+      proto::OptimizationTarget optimization_target,
+      OptimizationTargetModelObserver* observer) = 0;
+
+ protected:
+  OptimizationGuideModelProvider() = default;
+  virtual ~OptimizationGuideModelProvider() = default;
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_OPTIMIZATION_GUIDE_MODEL_PROVIDER_H_
diff --git a/components/optimization_guide/core/test_optimization_guide_model_provider.cc b/components/optimization_guide/core/test_optimization_guide_model_provider.cc
new file mode 100644
index 0000000..8cf7af1e
--- /dev/null
+++ b/components/optimization_guide/core/test_optimization_guide_model_provider.cc
@@ -0,0 +1,24 @@
+// 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/optimization_guide/core/test_optimization_guide_model_provider.h"
+
+namespace optimization_guide {
+
+TestOptimizationGuideModelProvider::TestOptimizationGuideModelProvider() =
+    default;
+TestOptimizationGuideModelProvider::~TestOptimizationGuideModelProvider() =
+    default;
+
+void TestOptimizationGuideModelProvider::AddObserverForOptimizationTargetModel(
+    optimization_guide::proto::OptimizationTarget optimization_target,
+    const absl::optional<proto::Any>& model_metadata,
+    optimization_guide::OptimizationTargetModelObserver* observer) {}
+
+void TestOptimizationGuideModelProvider::
+    RemoveObserverForOptimizationTargetModel(
+        optimization_guide::proto::OptimizationTarget optimization_target,
+        optimization_guide::OptimizationTargetModelObserver* observer) {}
+
+}  // namespace optimization_guide
diff --git a/components/optimization_guide/core/test_optimization_guide_model_provider.h b/components/optimization_guide/core/test_optimization_guide_model_provider.h
new file mode 100644
index 0000000..a29d253
--- /dev/null
+++ b/components/optimization_guide/core/test_optimization_guide_model_provider.h
@@ -0,0 +1,37 @@
+// 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_OPTIMIZATION_GUIDE_CORE_TEST_OPTIMIZATION_GUIDE_MODEL_PROVIDER_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_TEST_OPTIMIZATION_GUIDE_MODEL_PROVIDER_H_
+
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
+
+namespace optimization_guide {
+
+// An implementation of |OptimizationGuideModelProvider| that can be selectively
+// mocked out for unit testing features that rely on the Optimization Guide in
+// //components/...
+class TestOptimizationGuideModelProvider
+    : public OptimizationGuideModelProvider {
+ public:
+  TestOptimizationGuideModelProvider();
+  ~TestOptimizationGuideModelProvider() override;
+  TestOptimizationGuideModelProvider(
+      const TestOptimizationGuideModelProvider&) = delete;
+  TestOptimizationGuideModelProvider& operator=(
+      const TestOptimizationGuideModelProvider&) = delete;
+
+  // OptimizationGuideModelProvider implementation:
+  void AddObserverForOptimizationTargetModel(
+      proto::OptimizationTarget optimization_target,
+      const absl::optional<proto::Any>& model_metadata,
+      OptimizationTargetModelObserver* observer) override;
+  void RemoveObserverForOptimizationTargetModel(
+      proto::OptimizationTarget optimization_target,
+      OptimizationTargetModelObserver* observer) override;
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_TEST_OPTIMIZATION_GUIDE_MODEL_PROVIDER_H_
diff --git a/components/payments/content/payment_handler_navigation_throttle.cc b/components/payments/content/payment_handler_navigation_throttle.cc
index 7c463b6..8c06a17 100644
--- a/components/payments/content/payment_handler_navigation_throttle.cc
+++ b/components/payments/content/payment_handler_navigation_throttle.cc
@@ -40,7 +40,8 @@
 std::unique_ptr<PaymentHandlerNavigationThrottle>
 PaymentHandlerNavigationThrottle::MaybeCreateThrottleFor(
     content::NavigationHandle* handle) {
-  if (!handle->GetWebContents()->GetUserData(
+  if (!handle || !handle->GetWebContents() ||
+      !handle->GetWebContents()->GetUserData(
           kPaymentHandlerWebContentsUserDataKey)) {
     return nullptr;
   }
@@ -49,6 +50,8 @@
 
 content::NavigationThrottle::ThrottleCheckResult
 PaymentHandlerNavigationThrottle::WillProcessResponse() {
+  if (!navigation_handle())
+    return PROCEED;
   const net::HttpResponseHeaders* response_headers =
       navigation_handle()->GetResponseHeaders();
   if (!response_headers)
diff --git a/components/payments/content/secure_payment_confirmation_app.cc b/components/payments/content/secure_payment_confirmation_app.cc
index a63fe77..f3390ba 100644
--- a/components/payments/content/secure_payment_confirmation_app.cc
+++ b/components/payments/content/secure_payment_confirmation_app.cc
@@ -28,6 +28,7 @@
 #include "device/fido/fido_transport_protocol.h"
 #include "device/fido/fido_types.h"
 #include "device/fido/public_key_credential_descriptor.h"
+#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
 #include "url/url_constants.h"
 
 namespace payments {
diff --git a/components/payments/content/secure_payment_confirmation_app.h b/components/payments/content/secure_payment_confirmation_app.h
index 1112b2a1..71c5ebf 100644
--- a/components/payments/content/secure_payment_confirmation_app.h
+++ b/components/payments/content/secure_payment_confirmation_app.h
@@ -17,7 +17,7 @@
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
-#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
+#include "third_party/blink/public/mojom/webauthn/authenticator.mojom-forward.h"
 #include "url/origin.h"
 
 class SkBitmap;
diff --git a/components/privacy_sandbox_strings.grdp b/components/privacy_sandbox_strings.grdp
index 8c04b5c..25d97c8 100644
--- a/components/privacy_sandbox_strings.grdp
+++ b/components/privacy_sandbox_strings.grdp
@@ -3,13 +3,19 @@
   <message name="IDS_PRIVACY_SANDBOX_FLOC_INVALID" desc="A message shown in place of the user's FLoC ID if the ID is invalid and will not be recomputed">
     None
   </message>
-  <message name="IDS_PRIVACY_SANDBOX_FLOC_IN_PROGRESS" desc="A message shown in place of the user's FLoC ID if the ID is currently being calculated">
-    Calculating...
+  <message name="IDS_PRIVACY_SANDBOX_FLOC_DESCRIPTION" desc="Description of how FLoC works displayed to the user.">
+    {NUM_DAYS, plural,
+     =0 {When this control is on and the status is active, Chrome identifies which large group of people, or "cohort," your recent browsing activity is most similar to. Advertisers can select ads for the group and your browsing activity is kept private on your device. Your group is updated every day.}
+     =1 {When this control is on and the status is active, Chrome identifies which large group of people, or "cohort," your recent browsing activity is most similar to. Advertisers can select ads for the group and your browsing activity is kept private on your device. Your group is updated every day.}
+     other {When this control is on and the status is active, Chrome identifies which large group of people, or "cohort," your recent browsing activity is most similar to. Advertisers can select ads for the group and your browsing activity is kept private on your device. Your group is updated every {NUM_DAYS} days.}}
+  </message>
+  <message name="IDS_PRIVACY_SANDBOX_FLOC_TRIAL_ACTIVE" desc="Sentence indicating that FLoC is only available in some regions, including a link to a page that describes exactly what regions it is available. This will only every appear directly after another sentence.">
+    This trial is active only in <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1"&gt;</ph>some regions<ph name="END_LINK">&lt;/a&gt;</ph>.
   </message>
   <message name="IDS_PRIVACY_SANDBOX_FLOC_TIME_TO_NEXT_COMPUTE" desc="Description of how long until the user's FLoC ID is recomputed, in days.">
     {NUM_DAYS, plural,
-     =0 {In less than 1 day}
-     =1 {In 1 day}
+     =0 {In less than a day}
+     =1 {In a day}
      other {In {NUM_DAYS} days}}
   </message>
   <message name="IDS_PRIVACY_SANDBOX_FLOC_TIME_TO_NEXT_COMPUTE_INVALID" desc="A message shown in place of when the user's FLoC ID will be next computed if the user's ID will not be recomputed for any reason, such as having FLoC disabled">
@@ -17,9 +23,9 @@
   </message>
   <message name="IDS_PRIVACY_SANDBOX_FLOC_RESET_EXPLANATION" desc="Description of what happens when a user chooses to reset their Federated Learning of Cohorts (FLoC) identifier">
     {NUM_DAYS, plural,
-     =0 {When you reset ID, your current ID will become invalid and a new one will be issued in less than 1 day}
-     =1 {When you reset ID, your current ID will become invalid and a new one will be issued in 1 day}
-     other {When you reset ID, your current ID will become invalid and a new one will be issued in {NUM_DAYS} days}}
+     =0 {You can reset your group at any time. It takes about a day to join a new group.}
+     =1 {You can reset your group at any time. It takes about a day to join a new group.}
+     other {You can reset your group at any time. It takes {NUM_DAYS} days to join a new group.}}
   </message>
   <message name="IDS_PRIVACY_SANDBOX_FLOC_STATUS_ACTIVE" desc="Description of the user's FLoC status when they have the FLoC setting enabled, and the Origin Trial feature enabled, and so FLoC is fully active.">
     Trial is active
diff --git a/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_DESCRIPTION.png.sha1 b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..c234fff
--- /dev/null
+++ b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+7b4c64079a5687478ebd04d5debdbf23f7be580e
\ No newline at end of file
diff --git a/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_IN_PROGRESS.png.sha1 b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_IN_PROGRESS.png.sha1
deleted file mode 100644
index 9a791b9..0000000
--- a/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_IN_PROGRESS.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-9d4398b849832a6cd8e9bfc2bdc5052d51af4289
\ No newline at end of file
diff --git a/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_RESET_EXPLANATION.png.sha1 b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_RESET_EXPLANATION.png.sha1
index 812faa9a..c234fff 100644
--- a/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_RESET_EXPLANATION.png.sha1
+++ b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_RESET_EXPLANATION.png.sha1
@@ -1 +1 @@
-7e9ddb36c37ced70b6e123838810306035b112d4
\ No newline at end of file
+7b4c64079a5687478ebd04d5debdbf23f7be580e
\ No newline at end of file
diff --git a/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_TIME_TO_NEXT_COMPUTE.png.sha1 b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_TIME_TO_NEXT_COMPUTE.png.sha1
index c7482d5a..aa6d6f7 100644
--- a/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_TIME_TO_NEXT_COMPUTE.png.sha1
+++ b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_TIME_TO_NEXT_COMPUTE.png.sha1
@@ -1 +1 @@
-162239d9e10c268b4f6e9e0a971291c337c58ce5
\ No newline at end of file
+26eb02bf641228865c1090d21689f8345b78730e
\ No newline at end of file
diff --git a/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_TRIAL_ACTIVE.png.sha1 b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_TRIAL_ACTIVE.png.sha1
new file mode 100644
index 0000000..c234fff
--- /dev/null
+++ b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_FLOC_TRIAL_ACTIVE.png.sha1
@@ -0,0 +1 @@
+7b4c64079a5687478ebd04d5debdbf23f7be580e
\ No newline at end of file
diff --git a/components/segmentation_platform/DEPS b/components/segmentation_platform/DEPS
index 262374a..46291ef 100644
--- a/components/segmentation_platform/DEPS
+++ b/components/segmentation_platform/DEPS
@@ -1,6 +1,8 @@
 include_rules = [
   "+components/keyed_service",
   "+components/leveldb_proto",
-  "+components/optimization_guide/proto/models.pb.h",
-  "+components/optimization_guide/proto/models.proto",
+  "+components/optimization_guide",
+  "-components/optimization_guide/content",
+  "+third_party/tflite",
+  "+third_party/tflite-support",
 ]
diff --git a/components/segmentation_platform/README.md b/components/segmentation_platform/README.md
index ef2f0a6..c47fcb7 100644
--- a/components/segmentation_platform/README.md
+++ b/components/segmentation_platform/README.md
@@ -20,3 +20,12 @@
 Includes factories to instantiate the service.
 
 `SegmentationPlatformService` - Public interface for segmentation platform service.
+
+## Test models
+
+[components/test/data/segmentation_platform](../test/data/segmentation_platform)
+contains ML models used for testing.
+
+*   `adder.tflite`: Takes two floats as input in a single tensor. Outputs a
+    single tensor with a single element which is the sum of the two floats given
+    as input.
diff --git a/components/segmentation_platform/components_unittests.filter b/components/segmentation_platform/components_unittests.filter
index d9622197..4a2f3c9c 100644
--- a/components/segmentation_platform/components_unittests.filter
+++ b/components/segmentation_platform/components_unittests.filter
@@ -1,3 +1,4 @@
+SegmentationModelExecutorTest.*
 SegmentationPlatformServiceImplTest.*
 SignalFilterProcessorTest.*
-UserActionSignalHandlerTest.*
\ No newline at end of file
+UserActionSignalHandlerTest.*
diff --git a/components/segmentation_platform/internal/BUILD.gn b/components/segmentation_platform/internal/BUILD.gn
index ef71a25..ce85f6d 100644
--- a/components/segmentation_platform/internal/BUILD.gn
+++ b/components/segmentation_platform/internal/BUILD.gn
@@ -6,6 +6,7 @@
   import("//build/config/android/config.gni")
   import("//build/config/android/rules.gni")
 }
+import("//components/optimization_guide/features.gni")
 
 static_library("internal") {
   visibility = [
@@ -25,6 +26,7 @@
   ]
 
   deps = [
+    ":content",
     "//base",
     "//components/keyed_service/core",
     "//components/leveldb_proto",
@@ -34,6 +36,52 @@
   ]
 }
 
+source_set("content") {
+  visibility = [ ":*" ]
+
+  if (build_with_tflite_lib) {
+    sources = [
+      "content/segmentation_model_executor.cc",
+      "content/segmentation_model_executor.h",
+      "content/segmentation_model_handler.cc",
+      "content/segmentation_model_handler.h",
+    ]
+
+    deps = [
+      "//base",
+      "//third_party/tflite:tflite_public_headers",
+      "//third_party/tflite-support",
+    ]
+
+    public_deps = [
+      "//components/optimization_guide/core",
+      "//components/optimization_guide/proto:optimization_guide_proto",
+    ]
+  }
+}
+
+source_set("content_unit_tests") {
+  testonly = true
+
+  visibility = [ ":unit_tests" ]
+
+  if (build_with_tflite_lib) {
+    # IMPORTANT NOTE: When adding new tests, also remember to update the list of
+    # tests in //components/segmentation_platform/components_unittests.filter
+    sources = [ "content/segmentation_model_executor_unittest.cc" ]
+
+    deps = [
+      ":content",
+      ":internal",
+      "//base",
+      "//base/test:test_support",
+      "//components/optimization_guide/core:test_support",
+      "//testing/gmock",
+      "//testing/gtest",
+    ]
+  }
+}
+
 source_set("unit_tests") {
   testonly = true
 
@@ -50,6 +98,7 @@
   ]
 
   deps = [
+    ":content_unit_tests",
     ":internal",
     "//base/test:test_support",
     "//components/leveldb_proto:test_support",
diff --git a/components/segmentation_platform/internal/content/DEPS b/components/segmentation_platform/internal/content/DEPS
new file mode 100644
index 0000000..3477af99
--- /dev/null
+++ b/components/segmentation_platform/internal/content/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/optimization_guide/content",
+]
diff --git a/components/segmentation_platform/internal/content/segmentation_model_executor.cc b/components/segmentation_platform/internal/content/segmentation_model_executor.cc
new file mode 100644
index 0000000..becdd8910
--- /dev/null
+++ b/components/segmentation_platform/internal/content/segmentation_model_executor.cc
@@ -0,0 +1,45 @@
+// 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/segmentation_platform/internal/content/segmentation_model_executor.h"
+
+#include <vector>
+
+#include "base/check_op.h"
+#include "third_party/tflite-support/src/tensorflow_lite_support/cc/task/core/task_utils.h"
+#include "third_party/tflite/src/tensorflow/lite/c/common.h"
+
+namespace segmentation_platform {
+
+SegmentationModelExecutor::SegmentationModelExecutor() = default;
+
+SegmentationModelExecutor::~SegmentationModelExecutor() = default;
+
+void SegmentationModelExecutor::Preprocess(
+    const std::vector<TfLiteTensor*>& input_tensors,
+    const std::vector<float>& input) {
+  // The model must have a single float input tensor, and the length of the
+  // input data must match the length of the tensor.
+  DCHECK_EQ(1u, input_tensors.size());
+  DCHECK_EQ(kTfLiteFloat32, input_tensors[0]->type);
+  DCHECK_EQ(input_tensors[0]->bytes / sizeof(input_tensors[0]->type),
+            input.size());
+
+  tflite::task::core::PopulateTensor<float>(input, input_tensors[0]);
+}
+
+float SegmentationModelExecutor::Postprocess(
+    const std::vector<const TfLiteTensor*>& output_tensors) {
+  // The output must be a single tensor with a single float element.
+  DCHECK_EQ(1u, output_tensors.size());
+  DCHECK_EQ(kTfLiteFloat32, output_tensors[0]->type);
+  DCHECK_EQ(1u, output_tensors[0]->bytes / sizeof(output_tensors[0]->type));
+
+  std::vector<float> data;
+  tflite::task::core::PopulateVector<float>(output_tensors[0], &data);
+  DCHECK_EQ(1u, data.size());
+  return data[0];
+}
+
+}  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/content/segmentation_model_executor.h b/components/segmentation_platform/internal/content/segmentation_model_executor.h
new file mode 100644
index 0000000..a3cf4c72
--- /dev/null
+++ b/components/segmentation_platform/internal/content/segmentation_model_executor.h
@@ -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.
+
+#ifndef COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_EXECUTOR_H_
+#define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_EXECUTOR_H_
+
+#include <memory>
+#include <vector>
+
+#include "components/optimization_guide/core/base_model_executor.h"
+#include "components/optimization_guide/proto/models.pb.h"
+
+struct TfLiteTensor;
+
+namespace segmentation_platform {
+
+// Provides a framework for executing a particular segmentation model.
+// It requires the loaded TensorFlow Lite model to use a single one dimensional
+// tensor of floats as the input and a single output tensor with a single float.
+// The length of the vector of floats given as input to execution needs to
+// exactly match the length of the one-dimensional input tensor.
+// Since the shape of the inputs and outputs across all segmentation models are
+// the same, this class can be re-used across all the segmentation model
+// executors.
+class SegmentationModelExecutor
+    : public optimization_guide::BaseModelExecutor<float,
+                                                   const std::vector<float>&> {
+ public:
+  SegmentationModelExecutor();
+  ~SegmentationModelExecutor() override;
+
+  // Disallow copy/assign.
+  SegmentationModelExecutor(const SegmentationModelExecutor&) = delete;
+  SegmentationModelExecutor& operator=(const SegmentationModelExecutor&) =
+      delete;
+
+ protected:
+  // optimization_guide::BaseModelExecutor overrides.
+  void Preprocess(const std::vector<TfLiteTensor*>& input_tensors,
+                  const std::vector<float>& input) override;
+  float Postprocess(
+      const std::vector<const TfLiteTensor*>& output_tensors) override;
+};
+
+}  // namespace segmentation_platform
+
+#endif  // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_EXECUTOR_H_
diff --git a/components/segmentation_platform/internal/content/segmentation_model_executor_unittest.cc b/components/segmentation_platform/internal/content/segmentation_model_executor_unittest.cc
new file mode 100644
index 0000000..27a454f
--- /dev/null
+++ b/components/segmentation_platform/internal/content/segmentation_model_executor_unittest.cc
@@ -0,0 +1,112 @@
+// 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/segmentation_platform/internal/content/segmentation_model_executor.h"
+
+#include <memory>
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
+#include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "components/segmentation_platform/internal/content/segmentation_model_handler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+// TODO(nyquist): Change to optimization target for segmentation once available.
+const auto kOptimizationTarget = optimization_guide::proto::OptimizationTarget::
+    OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD;
+}  // namespace
+
+namespace segmentation_platform {
+
+class SegmentationModelExecutorTest : public testing::Test {
+ public:
+  SegmentationModelExecutorTest() = default;
+  ~SegmentationModelExecutorTest() override = default;
+
+  void SetUp() override {
+    base::FilePath source_root_dir;
+    base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir);
+    model_file_path_ = source_root_dir.AppendASCII("components")
+                           .AppendASCII("test")
+                           .AppendASCII("data")
+                           .AppendASCII("segmentation_platform")
+                           .AppendASCII("adder.tflite");
+
+    optimization_guide_model_provider_ = std::make_unique<
+        optimization_guide::TestOptimizationGuideModelProvider>();
+  }
+
+  void TearDown() override { ResetModelExecutor(); }
+
+  void CreateModelExecutor() {
+    if (model_executor_handle_)
+      model_executor_handle_.reset();
+
+    model_executor_handle_ = std::make_unique<SegmentationModelHandler>(
+        optimization_guide_model_provider_.get(),
+        task_environment_.GetMainThreadTaskRunner(), kOptimizationTarget);
+  }
+
+  void ResetModelExecutor() {
+    model_executor_handle_.reset();
+    // Allow for the background class to be destroyed.
+    RunUntilIdle();
+  }
+
+  void PushModelFileToModelExecutor() {
+    DCHECK(model_executor_handle_);
+    model_executor_handle_->OnModelFileUpdated(kOptimizationTarget,
+                                               absl::nullopt, model_file_path_);
+    RunUntilIdle();
+  }
+
+  SegmentationModelHandler* model_executor_handle() {
+    return model_executor_handle_.get();
+  }
+
+  void RunUntilIdle() { task_environment_.RunUntilIdle(); }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+
+  base::FilePath model_file_path_;
+  std::unique_ptr<optimization_guide::TestOptimizationGuideModelProvider>
+      optimization_guide_model_provider_;
+
+  std::unique_ptr<SegmentationModelHandler> model_executor_handle_;
+};
+
+TEST_F(SegmentationModelExecutorTest, ExecuteWithLoadedModel) {
+  CreateModelExecutor();
+
+  PushModelFileToModelExecutor();
+  EXPECT_TRUE(model_executor_handle()->ModelAvailable());
+
+  std::vector<float> input = {4, 5};
+
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  model_executor_handle()->ExecuteModelWithInput(
+      base::BindOnce(
+          [](base::RunLoop* run_loop, const absl::optional<float>& output) {
+            EXPECT_TRUE(output.has_value());
+            // 4 + 5 = 9
+            EXPECT_NEAR(9, output.value(), 1e-1);
+
+            run_loop->Quit();
+          },
+          run_loop.get()),
+      input);
+  run_loop->Run();
+
+  ResetModelExecutor();
+}
+
+}  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/content/segmentation_model_handler.cc b/components/segmentation_platform/internal/content/segmentation_model_handler.cc
new file mode 100644
index 0000000..9af032d1
--- /dev/null
+++ b/components/segmentation_platform/internal/content/segmentation_model_handler.cc
@@ -0,0 +1,29 @@
+// 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/segmentation_platform/internal/content/segmentation_model_handler.h"
+
+#include <memory>
+#include <vector>
+
+#include "components/optimization_guide/core/model_executor.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "components/segmentation_platform/internal/content/segmentation_model_executor.h"
+
+namespace segmentation_platform {
+
+SegmentationModelHandler::SegmentationModelHandler(
+    optimization_guide::OptimizationGuideModelProvider* model_provider,
+    scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+    optimization_guide::proto::OptimizationTarget optimization_target)
+    : optimization_guide::ModelHandler<float, const std::vector<float>&>(
+          model_provider,
+          background_task_runner,
+          std::make_unique<SegmentationModelExecutor>(),
+          optimization_target,
+          /*model_metadata=*/absl::nullopt) {}
+
+SegmentationModelHandler::~SegmentationModelHandler() = default;
+
+}  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/content/segmentation_model_handler.h b/components/segmentation_platform/internal/content/segmentation_model_handler.h
new file mode 100644
index 0000000..0c6412f
--- /dev/null
+++ b/components/segmentation_platform/internal/content/segmentation_model_handler.h
@@ -0,0 +1,42 @@
+// 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_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_HANDLER_H_
+#define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_HANDLER_H_
+
+#include <memory>
+#include <vector>
+
+#include "components/optimization_guide/core/model_executor.h"
+#include "components/optimization_guide/proto/models.pb.h"
+
+namespace optimization_guide {
+class OptimizationGuideModelProvider;
+}  // namespace optimization_guide
+
+namespace segmentation_platform {
+
+// A simple wrapper around a ModelHandler which is usable for all segmentation
+// models. This class constructs and owns the SegmentationModelExecutor through
+// its parent class.
+// See documentation for SegmentationModelExecutor for details on the
+// requirements for the ML model and the inputs to execution.
+class SegmentationModelHandler
+    : public optimization_guide::ModelHandler<float,
+                                              const std::vector<float>&> {
+ public:
+  explicit SegmentationModelHandler(
+      optimization_guide::OptimizationGuideModelProvider* model_provider,
+      scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+      optimization_guide::proto::OptimizationTarget optimization_target);
+  ~SegmentationModelHandler() override;
+
+  // Disallow copy/assign.
+  SegmentationModelHandler(const SegmentationModelHandler&) = delete;
+  SegmentationModelHandler& operator=(const SegmentationModelHandler&) = delete;
+};
+
+}  // namespace segmentation_platform
+
+#endif  // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_HANDLER_H_
diff --git a/components/site_isolation/site_isolation_policy.cc b/components/site_isolation/site_isolation_policy.cc
index 615c7aa..5bae0ef 100644
--- a/components/site_isolation/site_isolation_policy.cc
+++ b/components/site_isolation/site_isolation_policy.cc
@@ -241,15 +241,35 @@
   // persistence, but don't remove them from prefs otherwise.
   if (content::SiteIsolationPolicy::ShouldPersistIsolatedCOOPSites()) {
     std::vector<url::Origin> origins;
+    std::vector<std::string> expired_entries;
 
-    // TODO(alexmos): Implement support for skipping and expiring entries that
-    // are older than a predefined threshold.
-
-    auto* dict = user_prefs::UserPrefs::Get(browser_context)
-                     ->GetDictionary(prefs::kWebTriggeredIsolatedOrigins);
+    auto* pref_service = user_prefs::UserPrefs::Get(browser_context);
+    auto* dict =
+        pref_service->GetDictionary(prefs::kWebTriggeredIsolatedOrigins);
     if (dict) {
-      for (const auto& site_time_pair : dict->DictItems())
-        origins.push_back(url::Origin::Create(GURL(site_time_pair.first)));
+      for (const auto& site_time_pair : dict->DictItems()) {
+        // Only isolate origins that haven't expired.
+        absl::optional<base::Time> timestamp =
+            util::ValueToTime(site_time_pair.second);
+        base::TimeDelta expiration_timeout =
+            ::features::
+                kSiteIsolationForCrossOriginOpenerPolicyExpirationTimeoutParam
+                    .Get();
+        if (timestamp.has_value() &&
+            base::Time::Now() - timestamp.value() <= expiration_timeout) {
+          origins.push_back(url::Origin::Create(GURL(site_time_pair.first)));
+        } else {
+          expired_entries.push_back(site_time_pair.first);
+        }
+      }
+      // Remove expired entries (as well as those with an invalid timestamp).
+      if (!expired_entries.empty()) {
+        DictionaryPrefUpdate update(pref_service,
+                                    prefs::kWebTriggeredIsolatedOrigins);
+        base::DictionaryValue* updated_dict = update.Get();
+        for (const auto& entry : expired_entries)
+          updated_dict->RemoveKey(entry);
+      }
     }
 
     if (!origins.empty()) {
diff --git a/components/site_isolation/site_isolation_policy_unittest.cc b/components/site_isolation/site_isolation_policy_unittest.cc
index d1ed1dd..a9f9bf6 100644
--- a/components/site_isolation/site_isolation_policy_unittest.cc
+++ b/components/site_isolation/site_isolation_policy_unittest.cc
@@ -103,7 +103,10 @@
 
 class SiteIsolationPolicyTest : public BaseSiteIsolationTest {
  public:
-  SiteIsolationPolicyTest() {
+  explicit SiteIsolationPolicyTest(
+      content::BrowserTaskEnvironment::TimeSource time_source =
+          content::BrowserTaskEnvironment::TimeSource::DEFAULT)
+      : task_environment_(time_source) {
     prefs_.registry()->RegisterListPref(prefs::kUserTriggeredIsolatedOrigins);
     prefs_.registry()->RegisterDictionaryPref(
         prefs::kWebTriggeredIsolatedOrigins);
@@ -115,6 +118,10 @@
 
   PrefService* prefs() { return &prefs_; }
 
+  content::BrowserTaskEnvironment* task_environment() {
+    return &task_environment_;
+  }
+
  private:
   content::BrowserTaskEnvironment task_environment_;
   content::TestBrowserContext browser_context_;
@@ -125,7 +132,10 @@
 
 class WebTriggeredIsolatedOriginsPolicyTest : public SiteIsolationPolicyTest {
  public:
-  WebTriggeredIsolatedOriginsPolicyTest() = default;
+  explicit WebTriggeredIsolatedOriginsPolicyTest(
+      content::BrowserTaskEnvironment::TimeSource time_source =
+          content::BrowserTaskEnvironment::TimeSource::DEFAULT)
+      : SiteIsolationPolicyTest(time_source) {}
 
   void PersistOrigin(const std::string& origin) {
     SiteIsolationPolicy::PersistIsolatedOrigin(
@@ -144,11 +154,31 @@
 
  protected:
   void SetUp() override {
-    // Limit the max number of stored sites to 3.
-    feature_list_.InitAndEnableFeatureWithParameters(
+    // Set up the COOP isolation feature with persistence enabled and a maximum
+    // of 3 stored sites.
+    base::test::ScopedFeatureList::FeatureAndParams coop_feature = {
         ::features::kSiteIsolationForCrossOriginOpenerPolicy,
-        {{"stored_sites_max_size", base::NumberToString(3)},
-         {"should_persist_across_restarts", "true"}});
+        {{::features::kSiteIsolationForCrossOriginOpenerPolicyMaxSitesParam
+              .name,
+          base::NumberToString(3)},
+         {::features::kSiteIsolationForCrossOriginOpenerPolicyShouldPersistParam
+              .name,
+          "true"}}};
+
+    // Some machines running this test may be below the default memory
+    // threshold.  To ensure that COOP isolation is also enabled on those
+    // machines, set a very low 128MB threshold.
+    base::test::ScopedFeatureList::FeatureAndParams memory_threshold_feature = {
+        site_isolation::features::kSitePerProcessOnlyForHighMemoryClients,
+        {{site_isolation::features::
+              kSitePerProcessOnlyForHighMemoryClientsParamName,
+          "128"}}};
+
+    feature_list_.InitWithFeaturesAndParameters(
+        /* enabled_features = */ {coop_feature, memory_threshold_feature},
+        /* disabled_features = */ {});
+
+    // Disable strict site isolation to observe effects of COOP isolation.
     SetEnableStrictSiteIsolation(false);
     SiteIsolationPolicyTest::SetUp();
   }
@@ -227,6 +257,68 @@
                   "https://foo4.com", "https://foo5.com", "https://foo6.com"));
 }
 
+// WebTriggeredIsolatedOriginsPolicyTest subclass for tests that want to use
+// mock time.
+class WebTriggeredIsolatedOriginsPolicyTestWithMockTime
+    : public WebTriggeredIsolatedOriginsPolicyTest {
+ public:
+  WebTriggeredIsolatedOriginsPolicyTestWithMockTime()
+      : WebTriggeredIsolatedOriginsPolicyTest(
+            content::BrowserTaskEnvironment::TimeSource::MOCK_TIME) {}
+};
+
+// Verify that when origins stored in prefs expire, we don't apply them when
+// loading persisted isolated origins, and we remove them from prefs.
+TEST_F(WebTriggeredIsolatedOriginsPolicyTestWithMockTime, Expiration) {
+  // Running this test with a command-line --site-per-process flag (which might
+  // be the case on some bots) conflicts with the feature configuration in this
+  // test.
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kSitePerProcess)) {
+    return;
+  }
+
+  EXPECT_TRUE(content::SiteIsolationPolicy::IsSiteIsolationForCOOPEnabled());
+  EXPECT_TRUE(content::SiteIsolationPolicy::ShouldPersistIsolatedCOOPSites());
+
+  // Persist two origins which will eventually expire.
+  PersistOrigin("https://foo1.com");
+  PersistOrigin("https://foo2.com");
+  EXPECT_THAT(GetStoredOrigins(), testing::UnorderedElementsAre(
+                                      "https://foo1.com", "https://foo2.com"));
+
+  // Fast-forward time so we exceed the default expiration timeout.
+  base::TimeDelta default_timeout =
+      ::features::kSiteIsolationForCrossOriginOpenerPolicyExpirationTimeoutParam
+          .default_value;
+  task_environment()->FastForwardBy(default_timeout +
+                                    base::TimeDelta::FromDays(1));
+
+  // foo1.com and foo2.com should still be in prefs. (Expired entries are only
+  // removed when we try to load them from prefs.)
+  EXPECT_THAT(GetStoredOrigins(), testing::UnorderedElementsAre(
+                                      "https://foo1.com", "https://foo2.com"));
+
+  // Persist another origin which should remain below expiration threshold.
+  PersistOrigin("https://foo3.com");
+  EXPECT_THAT(GetStoredOrigins(),
+              testing::UnorderedElementsAre(
+                  "https://foo1.com", "https://foo2.com", "https://foo3.com"));
+
+  // Loading persisted isolated origins should only load foo3.com.  Also,
+  // it should remove foo1.com and foo2.com from prefs.
+  SiteIsolationPolicy::ApplyPersistedIsolatedOrigins(browser_context());
+
+  auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance();
+  std::vector<url::Origin> isolated_origins = cpsp->GetIsolatedOrigins(
+      IsolatedOriginSource::WEB_TRIGGERED, browser_context());
+  EXPECT_THAT(isolated_origins,
+              testing::UnorderedElementsAre(
+                  url::Origin::Create(GURL("https://foo3.com"))));
+  EXPECT_THAT(GetStoredOrigins(),
+              testing::UnorderedElementsAre("https://foo3.com"));
+}
+
 // Helper class that enables site isolation for password sites.
 class PasswordSiteIsolationPolicyTest : public SiteIsolationPolicyTest {
  public:
diff --git a/components/test/data/segmentation_platform/adder.tflite b/components/test/data/segmentation_platform/adder.tflite
new file mode 100644
index 0000000..b4888a3
--- /dev/null
+++ b/components/test/data/segmentation_platform/adder.tflite
Binary files differ
diff --git a/components/translate/content/browser/translate_model_service.cc b/components/translate/content/browser/translate_model_service.cc
index de06961..57e51a0 100644
--- a/components/translate/content/browser/translate_model_service.cc
+++ b/components/translate/content/browser/translate_model_service.cc
@@ -10,7 +10,7 @@
 #include "base/files/file_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/task/post_task.h"
-#include "components/optimization_guide/content/browser/optimization_guide_decider.h"
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "content/public/browser/browser_thread.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -58,7 +58,7 @@
 namespace translate {
 
 TranslateModelService::TranslateModelService(
-    optimization_guide::OptimizationGuideDecider* opt_guide,
+    optimization_guide::OptimizationGuideModelProvider* opt_guide,
     const scoped_refptr<base::SequencedTaskRunner>& background_task_runner)
     : opt_guide_(opt_guide), background_task_runner_(background_task_runner) {
   opt_guide_->AddObserverForOptimizationTargetModel(
diff --git a/components/translate/content/browser/translate_model_service.h b/components/translate/content/browser/translate_model_service.h
index 09d8fd6..26a3978 100644
--- a/components/translate/content/browser/translate_model_service.h
+++ b/components/translate/content/browser/translate_model_service.h
@@ -16,7 +16,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace optimization_guide {
-class OptimizationGuideDecider;
+class OptimizationGuideModelProvider;
 }  // namespace optimization_guide
 
 namespace translate {
@@ -31,7 +31,7 @@
   using GetModelCallback = base::OnceCallback<void(base::File)>;
 
   TranslateModelService(
-      optimization_guide::OptimizationGuideDecider* opt_guide,
+      optimization_guide::OptimizationGuideModelProvider* opt_guide,
       const scoped_refptr<base::SequencedTaskRunner>& background_task_runner);
   ~TranslateModelService() override;
 
@@ -54,7 +54,7 @@
   // Optimization Guide Service that provides model files for this service.
   // Optimization Guide Service is a BrowserContextKeyedServiceFactory and
   // should not be used after Shutdown.
-  optimization_guide::OptimizationGuideDecider* opt_guide_;
+  optimization_guide::OptimizationGuideModelProvider* opt_guide_;
 
   // The file that contains the language detection model. Available when the
   // file path has been provided by the Optimization Guide and has been
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index aa1a356f..44225be 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -682,6 +682,8 @@
     "conversions/conversion_storage_sql_migrations.h",
     "conversions/rate_limit_table.cc",
     "conversions/rate_limit_table.h",
+    "conversions/sent_report_info.cc",
+    "conversions/sent_report_info.h",
     "conversions/sql_utils.cc",
     "conversions/sql_utils.h",
     "conversions/storable_conversion.cc",
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index 0107ebd..ff05ac3 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -104,6 +104,7 @@
 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom.h"
 #include "third_party/blink/public/mojom/choosers/color_chooser.mojom.h"
 #include "third_party/blink/public/mojom/compute_pressure/compute_pressure.mojom.h"
+#include "third_party/blink/public/mojom/contacts/contacts_manager.mojom.h"
 #include "third_party/blink/public/mojom/content_index/content_index.mojom.h"
 #include "third_party/blink/public/mojom/cookie_store/cookie_store.mojom.h"
 #include "third_party/blink/public/mojom/credentialmanager/credential_manager.mojom.h"
@@ -112,6 +113,7 @@
 #include "third_party/blink/public/mojom/file/file_utilities.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom.h"
 #include "third_party/blink/public/mojom/filesystem/file_system.mojom.h"
+#include "third_party/blink/public/mojom/font_access/font_access.mojom.h"
 #include "third_party/blink/public/mojom/geolocation/geolocation_service.mojom.h"
 #include "third_party/blink/public/mojom/idle/idle_manager.mojom.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
diff --git a/content/browser/conversions/conversion_internals.mojom b/content/browser/conversions/conversion_internals.mojom
index 3432306..fcb32b3 100644
--- a/content/browser/conversions/conversion_internals.mojom
+++ b/content/browser/conversions/conversion_internals.mojom
@@ -5,6 +5,7 @@
 module mojom;
 
 import "url/mojom/origin.mojom";
+import "url/mojom/url.mojom";
 
 // Represents StorableImpression::SourceType.
 enum SourceType {
@@ -35,8 +36,16 @@
   string priority;
 };
 
-// Mojo interface used for communication between a WebUI and the storage layer
-// for conversion measurement.
+// Struct containing info for reports sent in this session.
+struct SentReportInfo {
+  url.mojom.Url report_url;
+  // The JSON POSTed to the report URL.
+  string report_body;
+  int32 http_response_code;
+};
+
+// Mojo interface for the conversion internals WebUI to communicate with the
+// storage layer.
 interface ConversionInternalsHandler {
   // Returns whether conversion measurement and the debug mode are enabled in
   // the browsing context the WebUI is in.
@@ -51,6 +60,9 @@
   // being sent.
   GetPendingReports() => (array<WebUIConversionReport> reports);
 
+  // Returns the info for all reports that have been sent in this session.
+  GetSentReports() => (array<SentReportInfo> reports);
+
   // Sends all stored reports, ignoring delay, returning when the
   // operation has been completed and all reports have been cleared from
   // storage.
diff --git a/content/browser/conversions/conversion_internals_browsertest.cc b/content/browser/conversions/conversion_internals_browsertest.cc
index 2c4b51f9..aa333cc7 100644
--- a/content/browser/conversions/conversion_internals_browsertest.cc
+++ b/content/browser/conversions/conversion_internals_browsertest.cc
@@ -216,6 +216,66 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+                       WebUIShownWithNoSentReports_NoSentReportsDisplayed) {
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+
+  TestConversionManager manager;
+  OverrideWebUIConversionManager(&manager);
+
+  std::string wait_script = R"(
+    let table = document.getElementById("sent-report-table-body");
+    let obs = new MutationObserver(() => {
+      if (table.children.length === 1 &&
+          table.children[0].children[0].innerText ===
+          "No sent reports.") {
+        document.title = $1;
+      }
+    });
+    obs.observe(table, {'childList': true});)";
+  EXPECT_TRUE(ExecJsInWebUI(JsReplace(wait_script, kCompleteTitle)));
+
+  TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
+  ClickRefreshButton();
+  EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+                       WebUIShownWithSentReport_SentReportsDisplayed) {
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+
+  TestConversionManager manager;
+  manager.SetSentReportsForWebUI({
+      {.report_url = GURL("https://example.com/1"),
+       .report_body = "a",
+       .http_response_code = 200},
+      {.report_url = GURL("https://example.com/2"),
+       .report_body = "b",
+       .http_response_code = 404},
+  });
+  OverrideWebUIConversionManager(&manager);
+
+  std::string wait_script = R"(
+    let table = document.getElementById("sent-report-table-body");
+    let obs = new MutationObserver(() => {
+      if (table.children.length === 2 &&
+          table.children[0].children[0].innerText === "https://example.com/1" &&
+          table.children[0].children[1].innerText === "a" &&
+          table.children[0].children[2].innerText === "200" &&
+          table.children[1].children[0].innerText === "https://example.com/2" &&
+          table.children[1].children[1].innerText === "b" &&
+          table.children[1].children[2].innerText === "404") {
+        document.title = $1;
+      }
+    });
+    obs.observe(table, {'childList': true});)";
+  EXPECT_TRUE(ExecJsInWebUI(JsReplace(wait_script, kCompleteTitle)));
+
+  TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
+  ClickRefreshButton();
+  EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
                        WebUIShownWithManager_DebugModeDisabled) {
   EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
 
diff --git a/content/browser/conversions/conversion_internals_handler_impl.cc b/content/browser/conversions/conversion_internals_handler_impl.cc
index c8ad497..b9e3bcb 100644
--- a/content/browser/conversions/conversion_internals_handler_impl.cc
+++ b/content/browser/conversions/conversion_internals_handler_impl.cc
@@ -10,10 +10,12 @@
 #include "base/callback.h"
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
+#include "base/containers/circular_deque.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/time/time.h"
 #include "content/browser/conversions/conversion_manager_impl.h"
 #include "content/browser/conversions/conversion_report.h"
+#include "content/browser/conversions/sent_report_info.h"
 #include "content/browser/conversions/storable_impression.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/browser_context.h"
@@ -115,7 +117,7 @@
     ::mojom::ConversionInternalsHandler::GetPendingReportsCallback callback) {
   if (ConversionManager* manager =
           manager_provider_->GetManager(web_ui_->GetWebContents())) {
-    manager->GetReportsForWebUI(
+    manager->GetPendingReportsForWebUI(
         base::BindOnce(&ForwardReportsToWebUI, std::move(callback)),
         base::Time::Max());
   } else {
@@ -123,6 +125,25 @@
   }
 }
 
+void ConversionInternalsHandlerImpl::GetSentReports(
+    ::mojom::ConversionInternalsHandler::GetSentReportsCallback callback) {
+  if (ConversionManager* manager =
+          manager_provider_->GetManager(web_ui_->GetWebContents())) {
+    const base::circular_deque<SentReportInfo>& sent_reports =
+        manager->GetSentReportsForWebUI();
+    std::vector<::mojom::SentReportInfoPtr> web_ui_reports;
+    web_ui_reports.reserve(sent_reports.size());
+
+    for (const SentReportInfo& info : sent_reports) {
+      web_ui_reports.push_back(::mojom::SentReportInfo::New(
+          info.report_url, info.report_body, info.http_response_code));
+    }
+    std::move(callback).Run(std::move(web_ui_reports));
+  } else {
+    std::move(callback).Run({});
+  }
+}
+
 void ConversionInternalsHandlerImpl::SendPendingReports(
     ::mojom::ConversionInternalsHandler::SendPendingReportsCallback callback) {
   if (ConversionManager* manager =
diff --git a/content/browser/conversions/conversion_internals_handler_impl.h b/content/browser/conversions/conversion_internals_handler_impl.h
index 76f6213..6e05edf 100644
--- a/content/browser/conversions/conversion_internals_handler_impl.h
+++ b/content/browser/conversions/conversion_internals_handler_impl.h
@@ -35,6 +35,9 @@
   void GetPendingReports(
       ::mojom::ConversionInternalsHandler::GetPendingReportsCallback callback)
       override;
+  void GetSentReports(
+      ::mojom::ConversionInternalsHandler::GetSentReportsCallback callback)
+      override;
   void SendPendingReports(
       ::mojom::ConversionInternalsHandler::SendPendingReportsCallback callback)
       override;
diff --git a/content/browser/conversions/conversion_manager.h b/content/browser/conversions/conversion_manager.h
index ca1e1a0..9c81d704 100644
--- a/content/browser/conversions/conversion_manager.h
+++ b/content/browser/conversions/conversion_manager.h
@@ -9,8 +9,10 @@
 
 #include "base/callback.h"
 #include "base/callback_forward.h"
+#include "base/containers/circular_deque.h"
 #include "content/browser/conversions/conversion_policy.h"
 #include "content/browser/conversions/conversion_report.h"
+#include "content/browser/conversions/sent_report_info.h"
 #include "content/browser/conversions/storable_conversion.h"
 #include "content/browser/conversions/storable_impression.h"
 #include "content/common/content_export.h"
@@ -54,10 +56,15 @@
 
   // Get all pending reports that are currently stored in this partition. Used
   // for populating WebUI.
-  virtual void GetReportsForWebUI(
+  virtual void GetPendingReportsForWebUI(
       base::OnceCallback<void(std::vector<ConversionReport>)> callback,
       base::Time max_report_time) = 0;
 
+  // Get all reports sent in this session. Used for populating WebUI. Limited to
+  // last 100.
+  virtual const base::circular_deque<SentReportInfo>&
+  GetSentReportsForWebUI() = 0;
+
   // Sends all pending reports immediately, and runs |done| once they have all
   // been sent.
   virtual void SendReportsForWebUI(base::OnceClosure done) = 0;
diff --git a/content/browser/conversions/conversion_manager_impl.cc b/content/browser/conversions/conversion_manager_impl.cc
index f24895e..33efb02 100644
--- a/content/browser/conversions/conversion_manager_impl.cc
+++ b/content/browser/conversions/conversion_manager_impl.cc
@@ -27,6 +27,8 @@
 
 namespace {
 
+const size_t kMaxSentReportsToStore = 100;
+
 // The shared-task runner for all conversion storage operations. Note that
 // different ConversionManagerImpl perform operations on the same task
 // runner. This prevents any potential races when a given context is destroyed
@@ -77,10 +79,11 @@
     std::unique_ptr<ConversionPolicy> policy,
     const base::Clock* clock,
     const base::FilePath& user_data_directory,
-    scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy) {
+    scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
+    size_t max_sent_reports_to_store) {
   return base::WrapUnique<ConversionManagerImpl>(new ConversionManagerImpl(
       std::move(reporter), std::move(policy), clock, user_data_directory,
-      std::move(special_storage_policy)));
+      std::move(special_storage_policy), max_sent_reports_to_store));
 }
 
 ConversionManagerImpl::ConversionManagerImpl(
@@ -96,14 +99,16 @@
                   switches::kConversionsDebugMode)),
           base::DefaultClock::GetInstance(),
           user_data_directory,
-          std::move(special_storage_policy)) {}
+          std::move(special_storage_policy),
+          kMaxSentReportsToStore) {}
 
 ConversionManagerImpl::ConversionManagerImpl(
     std::unique_ptr<ConversionReporter> reporter,
     std::unique_ptr<ConversionPolicy> policy,
     const base::Clock* clock,
     const base::FilePath& user_data_directory,
-    scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy)
+    scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
+    size_t max_sent_reports_to_store)
     : debug_mode_(base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kConversionsDebugMode)),
       clock_(clock),
@@ -113,6 +118,7 @@
           user_data_directory,
           std::make_unique<ConversionStorageDelegateImpl>(debug_mode_),
           clock_)),
+      max_sent_reports_to_store_(max_sent_reports_to_store),
       conversion_policy_(std::move(policy)),
       special_storage_policy_(std::move(special_storage_policy)),
       weak_factory_(this) {
@@ -178,13 +184,18 @@
       .Then(std::move(callback));
 }
 
-void ConversionManagerImpl::GetReportsForWebUI(
+void ConversionManagerImpl::GetPendingReportsForWebUI(
     base::OnceCallback<void(std::vector<ConversionReport>)> callback,
     base::Time max_report_time) {
   const int kMaxReports = 1000;
   GetAndHandleReports(std::move(callback), max_report_time, kMaxReports);
 }
 
+const base::circular_deque<SentReportInfo>&
+ConversionManagerImpl::GetSentReportsForWebUI() {
+  return sent_reports_;
+}
+
 void ConversionManagerImpl::SendReportsForWebUI(base::OnceClosure done) {
   GetAndHandleReports(
       base::BindOnce(&ConversionManagerImpl::HandleReportsSentFromWebUI,
@@ -280,15 +291,27 @@
                           base::Unretained(this), std::move(all_reports_sent)));
 }
 
-void ConversionManagerImpl::OnReportSent(int64_t conversion_id) {
+void ConversionManagerImpl::MaybeStoreSentReportInfo(
+    absl::optional<SentReportInfo> info) {
+  if (info.has_value()) {
+    while (sent_reports_.size() >= max_sent_reports_to_store_)
+      sent_reports_.pop_front();
+    sent_reports_.push_back(*info);
+  }
+}
+
+void ConversionManagerImpl::OnReportSent(int64_t conversion_id,
+                                         absl::optional<SentReportInfo> info) {
   conversion_storage_.AsyncCall(&ConversionStorage::DeleteConversion)
       .WithArgs(conversion_id)
       .Then(base::DoNothing::Once<bool>());
+  MaybeStoreSentReportInfo(std::move(info));
 }
 
 void ConversionManagerImpl::OnReportSentFromWebUI(
     base::OnceClosure reports_sent_barrier,
-    int64_t conversion_id) {
+    int64_t conversion_id,
+    absl::optional<SentReportInfo> info) {
   // |reports_sent_barrier| is a OnceClosure view of a RepeatingClosure obtained
   // by base::BarrierClosure().
   conversion_storage_.AsyncCall(&ConversionStorage::DeleteConversion)
@@ -296,6 +319,7 @@
       .Then(base::BindOnce([](base::OnceClosure callback,
                               bool result) { std::move(callback).Run(); },
                            std::move(reports_sent_barrier)));
+  MaybeStoreSentReportInfo(std::move(info));
 }
 
 }  // namespace content
diff --git a/content/browser/conversions/conversion_manager_impl.h b/content/browser/conversions/conversion_manager_impl.h
index 5556f5ce..717208e 100644
--- a/content/browser/conversions/conversion_manager_impl.h
+++ b/content/browser/conversions/conversion_manager_impl.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "base/containers/circular_deque.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
@@ -20,6 +21,7 @@
 #include "content/browser/conversions/conversion_policy.h"
 #include "content/browser/conversions/conversion_storage.h"
 #include "storage/browser/quota/special_storage_policy.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
 
@@ -63,10 +65,12 @@
 
     // Adds |reports| to a shared queue of reports that need to be sent. Runs
     // |report_sent_callback| for every report that is sent, with the associated
-    // |conversion_id| of the report.
+    // |conversion_id| of the report. |SentReportInfo| is not available if the
+    // report was not sent.
     virtual void AddReportsToQueue(
         std::vector<ConversionReport> reports,
-        base::RepeatingCallback<void(int64_t)> report_sent_callback) = 0;
+        base::RepeatingCallback<void(int64_t, absl::optional<SentReportInfo>)>
+            report_sent_callback) = 0;
   };
 
   // Configures underlying storage to be setup in memory, rather than on
@@ -78,7 +82,8 @@
       std::unique_ptr<ConversionPolicy> policy,
       const base::Clock* clock,
       const base::FilePath& user_data_directory,
-      scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy);
+      scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
+      size_t max_sent_reports_to_store);
 
   ConversionManagerImpl(
       StoragePartition* storage_partition,
@@ -94,9 +99,10 @@
   void GetActiveImpressionsForWebUI(
       base::OnceCallback<void(std::vector<StorableImpression>)> callback)
       override;
-  void GetReportsForWebUI(
+  void GetPendingReportsForWebUI(
       base::OnceCallback<void(std::vector<ConversionReport>)> callback,
       base::Time max_report_time) override;
+  const base::circular_deque<SentReportInfo>& GetSentReportsForWebUI() override;
   void SendReportsForWebUI(base::OnceClosure done) override;
   const ConversionPolicy& GetConversionPolicy() const override;
   void ClearData(base::Time delete_begin,
@@ -110,7 +116,8 @@
       std::unique_ptr<ConversionPolicy> policy,
       const base::Clock* clock,
       const base::FilePath& user_data_directory,
-      scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy);
+      scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
+      size_t max_sent_reports_to_store);
 
   // Retrieves at most |limit| reports from storage whose |report_time| <=
   // |max_report_time|, and calls |handler_function| on them; use a negative
@@ -134,14 +141,17 @@
   void HandleReportsSentFromWebUI(base::OnceClosure done,
                                   std::vector<ConversionReport> reports);
 
+  void MaybeStoreSentReportInfo(absl::optional<SentReportInfo> info);
+
   // Notify storage to delete the given |conversion_id| when its associated
   // report has been sent.
-  void OnReportSent(int64_t conversion_id);
+  void OnReportSent(int64_t conversion_id, absl::optional<SentReportInfo> info);
 
   // Similar to OnReportSent, but invokes |reports_sent_barrier| when the
   // report has been removed from storage.
   void OnReportSentFromWebUI(base::OnceClosure reports_sent_barrier,
-                             int64_t conversion_id);
+                             int64_t conversion_id,
+                             absl::optional<SentReportInfo> info);
 
   // Friend to expose the ConversionStorage for certain tests.
   friend std::vector<ConversionReport> GetConversionsToReportForTesting(
@@ -164,6 +174,13 @@
 
   base::SequenceBound<ConversionStorage> conversion_storage_;
 
+  // Stores info for the last |max_sent_reports_to_store_| reports sent in this
+  // session for display in conversion internals UI.
+  base::circular_deque<SentReportInfo> sent_reports_;
+
+  // This is needed to avoid leaking memory.
+  const size_t max_sent_reports_to_store_;
+
   // Policy used for controlling API configurations such as reporting and
   // attribution models. Unique ptr so it can be overridden for testing.
   std::unique_ptr<ConversionPolicy> conversion_policy_;
diff --git a/content/browser/conversions/conversion_manager_impl_unittest.cc b/content/browser/conversions/conversion_manager_impl_unittest.cc
index cc3a932e..0cb9dbe0 100644
--- a/content/browser/conversions/conversion_manager_impl_unittest.cc
+++ b/content/browser/conversions/conversion_manager_impl_unittest.cc
@@ -22,11 +22,13 @@
 #include "build/build_config.h"
 #include "content/browser/conversions/conversion_report.h"
 #include "content/browser/conversions/conversion_test_utils.h"
+#include "content/browser/conversions/sent_report_info.h"
 #include "content/browser/conversions/storable_conversion.h"
 #include "content/browser/conversions/storable_impression.h"
 #include "content/public/test/browser_task_environment.h"
 #include "storage/browser/test/mock_special_storage_policy.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace content {
 
@@ -56,14 +58,15 @@
   // ConversionManagerImpl::ConversionReporter
   void AddReportsToQueue(
       std::vector<ConversionReport> reports,
-      base::RepeatingCallback<void(int64_t)> report_sent_callback) override {
+      base::RepeatingCallback<void(int64_t, absl::optional<SentReportInfo>)>
+          report_sent_callback) override {
     num_reports_ += reports.size();
     last_conversion_id_ = *reports.back().conversion_id;
     last_report_time_ = reports.back().report_time;
 
     if (should_run_report_sent_callbacks_) {
       for (const auto& report : reports) {
-        report_sent_callback.Run(*report.conversion_id);
+        report_sent_callback.Run(*report.conversion_id, sent_report_info_);
       }
     }
 
@@ -75,6 +78,10 @@
     should_run_report_sent_callbacks_ = should_run_report_sent_callbacks;
   }
 
+  void SetSentReportInfo(absl::optional<SentReportInfo> info) {
+    sent_report_info_ = info;
+  }
+
   size_t num_reports() { return num_reports_; }
 
   int64_t last_conversion_id() { return last_conversion_id_; }
@@ -93,6 +100,7 @@
 
  private:
   bool should_run_report_sent_callbacks_ = false;
+  absl::optional<SentReportInfo> sent_report_info_ = absl::nullopt;
   size_t expected_num_reports_ = 0u;
   size_t num_reports_ = 0u;
   int64_t last_conversion_id_ = 0UL;
@@ -107,6 +115,8 @@
 // Give impressions a sufficiently long expiry.
 constexpr base::TimeDelta kImpressionExpiry = base::TimeDelta::FromDays(30);
 
+const size_t kMaxSentReportsToStore = 3;
+
 }  // namespace
 
 class ConversionManagerImplTest : public testing::Test {
@@ -124,7 +134,8 @@
     test_reporter_ = reporter.get();
     conversion_manager_ = ConversionManagerImpl::CreateForTesting(
         std::move(reporter), std::make_unique<ConstantStartupDelayPolicy>(),
-        task_environment_.GetMockClock(), dir_.GetPath(), mock_storage_policy_);
+        task_environment_.GetMockClock(), dir_.GetPath(), mock_storage_policy_,
+        kMaxSentReportsToStore);
   }
 
   void ExpectNumStoredImpressions(size_t expected_num_impressions) {
@@ -147,8 +158,8 @@
           EXPECT_EQ(expected_num_reports, reports.size());
           report_loop.Quit();
         });
-    conversion_manager_->GetReportsForWebUI(std::move(reports_callback),
-                                            base::Time::Max());
+    conversion_manager_->GetPendingReportsForWebUI(std::move(reports_callback),
+                                                   base::Time::Max());
     report_loop.Run();
   }
 
@@ -222,8 +233,8 @@
         EXPECT_TRUE(ReportsEqual({expected_report}, reports));
         run_loop.Quit();
       });
-  conversion_manager_->GetReportsForWebUI(std::move(reports_callback),
-                                          base::Time::Max());
+  conversion_manager_->GetPendingReportsForWebUI(std::move(reports_callback),
+                                                 base::Time::Max());
   run_loop.Run();
 }
 
@@ -270,6 +281,67 @@
   EXPECT_EQ(1u, test_reporter_->num_reports());
 }
 
+TEST_F(ConversionManagerImplTest, QueuedReportSent_SentReportInfoUpdated) {
+  const SentReportInfo sent_report_info_1 = {
+      .report_url = GURL("https://example/a"),
+      .http_response_code = 200,
+  };
+  const SentReportInfo sent_report_info_2 = {
+      .report_url = GURL("https://example/b"),
+      .http_response_code = 404,
+  };
+
+  test_reporter_->ShouldRunReportSentCallbacks(true);
+
+  test_reporter_->SetSentReportInfo(sent_report_info_1);
+  conversion_manager_->HandleImpression(
+      ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build());
+  conversion_manager_->HandleConversion(DefaultConversion());
+  task_environment_.FastForwardBy(kFirstReportingWindow -
+                                  kConversionManagerQueueReportsInterval);
+
+  test_reporter_->SetSentReportInfo(absl::nullopt);
+  conversion_manager_->HandleImpression(
+      ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build());
+  conversion_manager_->HandleConversion(DefaultConversion());
+  task_environment_.FastForwardBy(kFirstReportingWindow -
+                                  kConversionManagerQueueReportsInterval);
+
+  test_reporter_->SetSentReportInfo(sent_report_info_2);
+  conversion_manager_->HandleImpression(
+      ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build());
+  conversion_manager_->HandleConversion(DefaultConversion());
+  task_environment_.FastForwardBy(kFirstReportingWindow -
+                                  kConversionManagerQueueReportsInterval);
+
+  EXPECT_TRUE(
+      SentReportInfosEqual({sent_report_info_1, sent_report_info_2},
+                           conversion_manager_->GetSentReportsForWebUI()));
+}
+
+TEST_F(ConversionManagerImplTest, QueuedReportSent_StoresLastN) {
+  test_reporter_->ShouldRunReportSentCallbacks(true);
+
+  // Process |kMaxSentReportsToStore + 1| reports.
+  for (int i : {1, 2, 3, 4}) {
+    test_reporter_->SetSentReportInfo(SentReportInfo{.http_response_code = i});
+    conversion_manager_->HandleImpression(
+        ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build());
+    conversion_manager_->HandleConversion(DefaultConversion());
+    task_environment_.FastForwardBy(kFirstReportingWindow -
+                                    kConversionManagerQueueReportsInterval);
+  }
+
+  // Only the last |kMaxSentReportsToStore| should be stored.
+  EXPECT_TRUE(SentReportInfosEqual(
+      {
+          {.http_response_code = 2},
+          {.http_response_code = 3},
+          {.http_response_code = 4},
+      },
+      conversion_manager_->GetSentReportsForWebUI()));
+}
+
 // Add a conversion to storage and reset the manager to mimic a report being
 // available at startup.
 TEST_F(ConversionManagerImplTest, ExpiredReportsAtStartup_Queued) {
diff --git a/content/browser/conversions/conversion_network_sender_impl.cc b/content/browser/conversions/conversion_network_sender_impl.cc
index b1ba2ec..9febbf8e 100644
--- a/content/browser/conversions/conversion_network_sender_impl.cc
+++ b/content/browser/conversions/conversion_network_sender_impl.cc
@@ -15,6 +15,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/time/time.h"
 #include "base/values.h"
+#include "content/browser/conversions/sent_report_info.h"
 #include "content/public/browser/storage_partition.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
@@ -117,8 +118,10 @@
         storage_partition_->GetURLLoaderFactoryForBrowserProcess();
   }
 
+  GURL report_url = GetReportUrl(*report);
+
   auto resource_request = std::make_unique<network::ResourceRequest>();
-  resource_request->url = GetReportUrl(*report);
+  resource_request->url = report_url;
   resource_request->referrer =
       GURL(report->impression.ConversionDestination().Serialize());
   resource_request->method = net::HttpRequestHeaders::kPostMethod;
@@ -180,6 +183,7 @@
       url_loader_factory_.get(),
       base::BindOnce(&ConversionNetworkSenderImpl::OnReportSent,
                      base::Unretained(this), std::move(it),
+                     std::move(report_url), std::move(report_body),
                      std::move(sent_callback)));
   LogMetricsOnReportSend(report);
 }
@@ -191,10 +195,18 @@
 
 void ConversionNetworkSenderImpl::OnReportSent(
     UrlLoaderList::iterator it,
+    GURL report_url,
+    std::string report_body,
     ReportSentCallback sent_callback,
     scoped_refptr<net::HttpResponseHeaders> headers) {
   network::SimpleURLLoader* loader = it->get();
 
+  SentReportInfo sent_report_info = {
+      .report_url = std::move(report_url),
+      .report_body = std::move(report_body),
+      .http_response_code = headers ? headers->response_code() : 0,
+  };
+
   // Consider a non-200 HTTP code as a non-internal error.
   int net_error = loader->NetError();
   bool internal_ok =
@@ -219,7 +231,7 @@
   }
 
   loaders_in_progress_.erase(it);
-  std::move(sent_callback).Run();
+  std::move(sent_callback).Run(std::move(sent_report_info));
 }
 
 }  // namespace content
diff --git a/content/browser/conversions/conversion_network_sender_impl.h b/content/browser/conversions/conversion_network_sender_impl.h
index 0dd23717..8447acc9 100644
--- a/content/browser/conversions/conversion_network_sender_impl.h
+++ b/content/browser/conversions/conversion_network_sender_impl.h
@@ -8,12 +8,14 @@
 #include <stdint.h>
 #include <list>
 #include <memory>
+#include <string>
 
 #include "base/callback.h"
 #include "content/browser/conversions/conversion_report.h"
 #include "content/browser/conversions/conversion_reporter_impl.h"
 #include "content/common/content_export.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "url/gurl.h"
 
 namespace network {
 class SimpleURLLoader;
@@ -53,6 +55,8 @@
 
   // Called when headers are available for a sent report.
   void OnReportSent(UrlLoaderList::iterator it,
+                    GURL report_url,
+                    std::string report_body,
                     ReportSentCallback sent_callback,
                     scoped_refptr<net::HttpResponseHeaders> headers);
 
diff --git a/content/browser/conversions/conversion_network_sender_impl_unittest.cc b/content/browser/conversions/conversion_network_sender_impl_unittest.cc
index a5e7f423..dab0e1d 100644
--- a/content/browser/conversions/conversion_network_sender_impl_unittest.cc
+++ b/content/browser/conversions/conversion_network_sender_impl_unittest.cc
@@ -9,12 +9,14 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "base/containers/circular_deque.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/post_task.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/simple_test_clock.h"
 #include "content/browser/conversions/conversion_test_utils.h"
+#include "content/browser/conversions/sent_report_info.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
@@ -69,7 +71,9 @@
   }
 
  protected:
-  size_t num_reports_sent_ = 0u;
+  size_t num_reports_sent() const { return sent_reports_.size(); }
+
+  base::circular_deque<SentReportInfo> sent_reports_;
 
   // |task_enviorment_| must be initialized first.
   content::BrowserTaskEnvironment task_environment_;
@@ -79,7 +83,7 @@
   network::TestURLLoaderFactory test_url_loader_factory_;
 
  private:
-  void OnReportSent() { num_reports_sent_++; }
+  void OnReportSent(SentReportInfo info) { sent_reports_.push_back(info); }
 
   scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
 };
@@ -99,7 +103,13 @@
   EXPECT_EQ(1, test_url_loader_factory_.NumPending());
   EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
       kReportUrl, ""));
-  EXPECT_EQ(1u, num_reports_sent_);
+  EXPECT_TRUE(SentReportInfosEqual(
+      {{
+          .report_url = GURL(kReportUrl),
+          .report_body = R"({"source_event_id":"1","trigger_data":1})",
+          .http_response_code = 200,
+      }},
+      sent_reports_));
 }
 
 TEST_F(ConversionNetworkSenderTest, SenderDeletedDuringRequest_NoCrash) {
@@ -109,7 +119,7 @@
   network_sender_.reset();
   EXPECT_FALSE(test_url_loader_factory_.SimulateResponseForPendingRequest(
       kReportUrl, ""));
-  EXPECT_EQ(0u, num_reports_sent_);
+  EXPECT_TRUE(SentReportInfosEqual({}, sent_reports_));
 }
 
 TEST_F(ConversionNetworkSenderTest, ReportRequestHangs_TimesOut) {
@@ -123,11 +133,19 @@
   EXPECT_EQ(0, test_url_loader_factory_.NumPending());
 
   // Also verify that the sent callback runs if the request times out.
-  EXPECT_EQ(1u, num_reports_sent_);
+  // TODO(apaseltiner): Should we propagate the timeout via the SentReportInfo
+  // instead of just setting |http_response_code = 0|?
+  EXPECT_TRUE(SentReportInfosEqual(
+      {{
+          .report_url = GURL(kReportUrl),
+          .report_body = R"({"source_event_id":"1","trigger_data":1})",
+          .http_response_code = 0,
+      }},
+      sent_reports_));
 }
 
 TEST_F(ConversionNetworkSenderTest,
-       ReportRequesFailsDueToNetworkChange_Retries) {
+       ReportRequestFailsDueToNetworkChange_Retries) {
   // Retry fails
   {
     base::HistogramTester histograms;
@@ -154,7 +172,7 @@
     // We should not retry again. Verify the report sent callback only gets
     // fired once.
     EXPECT_EQ(0, test_url_loader_factory_.NumPending());
-    EXPECT_EQ(1u, num_reports_sent_);
+    EXPECT_EQ(1u, num_reports_sent());
 
     histograms.ExpectUniqueSample("Conversions.ReportRetrySucceed", false, 1);
   }
@@ -236,12 +254,18 @@
 TEST_F(ConversionNetworkSenderTest, ReportResultsInHttpError_SentCallbackRuns) {
   auto report = GetReport(/*conversion_id=*/1);
   network_sender_->SendReport(&report, GetSentCallback());
-  EXPECT_EQ(0u, num_reports_sent_);
+  EXPECT_TRUE(SentReportInfosEqual({}, sent_reports_));
 
   // We should run the sent callback even if there is an http error.
   EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
       kReportUrl, "", net::HttpStatusCode::HTTP_BAD_REQUEST));
-  EXPECT_EQ(1u, num_reports_sent_);
+  EXPECT_TRUE(SentReportInfosEqual(
+      {{
+          .report_url = GURL(kReportUrl),
+          .report_body = R"({"source_event_id":"1","trigger_data":1})",
+          .http_response_code = net::HttpStatusCode::HTTP_BAD_REQUEST,
+      }},
+      sent_reports_));
 }
 
 TEST_F(ConversionNetworkSenderTest, ManyReports_AllSentSuccessfully) {
@@ -259,7 +283,7 @@
     EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
         kReportUrl, ""));
   }
-  EXPECT_EQ(10u, num_reports_sent_);
+  EXPECT_EQ(10u, num_reports_sent());
   EXPECT_EQ(0, test_url_loader_factory_.NumPending());
 }
 
diff --git a/content/browser/conversions/conversion_reporter_impl.cc b/content/browser/conversions/conversion_reporter_impl.cc
index 5382a85..a4640f4 100644
--- a/content/browser/conversions/conversion_reporter_impl.cc
+++ b/content/browser/conversions/conversion_reporter_impl.cc
@@ -28,7 +28,8 @@
 
 void ConversionReporterImpl::AddReportsToQueue(
     std::vector<ConversionReport> reports,
-    base::RepeatingCallback<void(int64_t)> report_sent_callback) {
+    base::RepeatingCallback<void(int64_t, absl::optional<SentReportInfo>)>
+        report_sent_callback) {
   DCHECK(!reports.empty());
 
   std::vector<std::unique_ptr<ConversionReport>> swappable_reports;
@@ -73,14 +74,14 @@
           &report->impression.reporting_origin())) {
     network_sender_->SendReport(
         report_queue_.top().get(),
-        base::BindOnce(&ConversionReporterImpl::OnReportSent,
+        base::BindOnce(&ConversionReporterImpl::OnReportSentWithInfo,
                        base::Unretained(this), report->conversion_id.value()));
   } else {
     // If measurement is disallowed, just drop the report on the floor. We need
     // to make sure we forward that the report was "sent" to ensure it is
-    // deleted from storage, etc. This simulate sending the report through a
+    // deleted from storage, etc. This simulates sending the report through a
     // null channel.
-    OnReportSent(*report_queue_.top()->conversion_id);
+    OnReportSent(*report_queue_.top()->conversion_id, /*info=*/absl::nullopt);
   }
   report_queue_.pop();
   MaybeScheduleNextReport();
@@ -106,13 +107,19 @@
                      base::Unretained(this)));
 }
 
-void ConversionReporterImpl::OnReportSent(int64_t conversion_id) {
+void ConversionReporterImpl::OnReportSent(int64_t conversion_id,
+                                          absl::optional<SentReportInfo> info) {
   auto it = conversion_report_callbacks_.find(conversion_id);
   DCHECK(it != conversion_report_callbacks_.end());
-  std::move(it->second).Run(conversion_id);
+  std::move(it->second).Run(conversion_id, std::move(info));
   conversion_report_callbacks_.erase(it);
 }
 
+void ConversionReporterImpl::OnReportSentWithInfo(int64_t conversion_id,
+                                                  SentReportInfo info) {
+  OnReportSent(conversion_id, std::move(info));
+}
+
 bool ConversionReporterImpl::ReportComparator::operator()(
     const std::unique_ptr<ConversionReport>& a,
     const std::unique_ptr<ConversionReport>& b) const {
diff --git a/content/browser/conversions/conversion_reporter_impl.h b/content/browser/conversions/conversion_reporter_impl.h
index 86c6baf..979373d 100644
--- a/content/browser/conversions/conversion_reporter_impl.h
+++ b/content/browser/conversions/conversion_reporter_impl.h
@@ -15,6 +15,7 @@
 #include "base/timer/timer.h"
 #include "content/browser/conversions/conversion_manager_impl.h"
 #include "content/browser/conversions/conversion_report.h"
+#include "content/browser/conversions/sent_report_info.h"
 #include "content/common/content_export.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
@@ -42,7 +43,7 @@
     virtual ~NetworkSender() = default;
 
     // Callback used to notify caller that the requested report has been sent.
-    using ReportSentCallback = base::OnceCallback<void()>;
+    using ReportSentCallback = base::OnceCallback<void(SentReportInfo)>;
 
     // Generates and sends a conversion report matching |report|. This should
     // generate a secure POST quest with no-credentials. Does not persist the
@@ -60,7 +61,8 @@
   // ConversionManagerImpl::ConversionReporter:
   void AddReportsToQueue(
       std::vector<ConversionReport> reports,
-      base::RepeatingCallback<void(int64_t)> report_sent_callback) override;
+      base::RepeatingCallback<void(int64_t, absl::optional<SentReportInfo>)>
+          report_sent_callback) override;
 
   void SetNetworkSenderForTesting(
       std::unique_ptr<NetworkSender> network_sender);
@@ -71,7 +73,9 @@
 
   // Called when a conversion report sent via NetworkSender::SendReport() has
   // completed loading.
-  void OnReportSent(int64_t conversion_id);
+  void OnReportSent(int64_t conversion_id, absl::optional<SentReportInfo> info);
+  // Adapter for use with |NetworkSender::SendReport()|.
+  void OnReportSentWithInfo(int64_t conversion_id, SentReportInfo info);
 
   // Comparator used to order ConversionReports by their report time, with the
   // smallest time at the top of |report_queue_|.
@@ -91,7 +95,9 @@
   // sent by |network_sender_|, and their associated report sent callbacks. The
   // number of concurrent conversion reports being sent at any time is expected
   // to be small, so a flat_map is used.
-  base::flat_map<int64_t, base::OnceCallback<void(int64_t)>>
+  base::flat_map<
+      int64_t,
+      base::OnceCallback<void(int64_t, absl::optional<SentReportInfo>)>>
       conversion_report_callbacks_;
 
   const base::Clock* clock_;
diff --git a/content/browser/conversions/conversion_reporter_impl_unittest.cc b/content/browser/conversions/conversion_reporter_impl_unittest.cc
index c4c4efc..aa73c0c 100644
--- a/content/browser/conversions/conversion_reporter_impl_unittest.cc
+++ b/content/browser/conversions/conversion_reporter_impl_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/test/simple_test_clock.h"
 #include "content/browser/conversions/conversion_manager.h"
 #include "content/browser/conversions/conversion_test_utils.h"
+#include "content/browser/conversions/sent_report_info.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/content_client.h"
@@ -47,7 +48,7 @@
                   ReportSentCallback sent_callback) override {
     last_sent_report_id_ = *conversion_report->conversion_id;
     num_reports_sent_++;
-    std::move(sent_callback).Run();
+    std::move(sent_callback).Run({.http_response_code = 200});
   }
 
   int64_t last_sent_report_id() { return last_sent_report_id_; }
@@ -95,7 +96,11 @@
   reporter_->AddReportsToQueue(
       {GetReport(clock().Now(), clock().Now(), /*conversion_id=*/1)},
       base::BindRepeating(
-          [](int64_t conversion_id) { EXPECT_EQ(1L, conversion_id); }));
+          [](int64_t conversion_id, absl::optional<SentReportInfo> info) {
+            EXPECT_EQ(1L, conversion_id);
+            EXPECT_TRUE(info.has_value());
+            EXPECT_EQ(200, info->http_response_code);
+          }));
 
   // Fast forward by 0, as we yield the thread when a report is scheduled to be
   // sent.
@@ -109,7 +114,11 @@
       {GetReport(clock().Now(), clock().Now() - base::TimeDelta::FromHours(10),
                  /*conversion_id=*/1)},
       base::BindRepeating(
-          [](int64_t conversion_id) { EXPECT_EQ(1L, conversion_id); }));
+          [](int64_t conversion_id, absl::optional<SentReportInfo> info) {
+            EXPECT_EQ(1L, conversion_id);
+            EXPECT_TRUE(info.has_value());
+            EXPECT_EQ(200, info->http_response_code);
+          }));
 
   // Fast forward by 0, as we yield the thread when a report is scheduled to be
   // sent.
@@ -175,9 +184,11 @@
                                 /*conversion_id=*/i));
   }
   reporter_->AddReportsToQueue(
-      reports, base::BindLambdaForTesting([&](int64_t conversion_id) {
-        last_report_id = conversion_id;
-      }));
+      reports,
+      base::BindLambdaForTesting(
+          [&](int64_t conversion_id, absl::optional<SentReportInfo> info) {
+            last_report_id = conversion_id;
+          }));
   task_environment_.FastForwardBy(base::TimeDelta());
   EXPECT_EQ(0u, sender_->num_reports_sent());
 
@@ -193,7 +204,9 @@
 TEST_F(ConversionReporterImplTest, ManyReportsAddedSeparately_SentInOrder) {
   int64_t last_report_id = 0;
   auto report_sent_callback = base::BindLambdaForTesting(
-      [&](int64_t conversion_id) { last_report_id = conversion_id; });
+      [&](int64_t conversion_id, absl::optional<SentReportInfo> info) {
+        last_report_id = conversion_id;
+      });
   for (int i = 1; i < 10; i++) {
     reporter_->AddReportsToQueue(
         {GetReport(clock().Now(),
@@ -220,7 +233,10 @@
   reporter_->AddReportsToQueue(
       {GetReport(clock().Now(), clock().Now(), /*conversion_id=*/1)},
       base::BindRepeating(
-          [](int64_t conversion_id) { EXPECT_EQ(1L, conversion_id); }));
+          [](int64_t conversion_id, absl::optional<SentReportInfo> info) {
+            EXPECT_EQ(1L, conversion_id);
+            EXPECT_FALSE(info.has_value());
+          }));
 
   // Fast forward by 0, as we yield the thread when a report is scheduled to be
   // sent.
@@ -269,10 +285,13 @@
         ConversionReport(std::move(impression),
                          /*conversion_data=*/"", clock().Now(), clock().Now(),
                          /*conversion_id=*/1)};
-    reporter_->AddReportsToQueue(std::move(reports),
-                                 base::BindRepeating([](int64_t conversion_id) {
-                                   EXPECT_EQ(1L, conversion_id);
-                                 }));
+    reporter_->AddReportsToQueue(
+        std::move(reports),
+        base::BindLambdaForTesting(
+            [&](int64_t conversion_id, absl::optional<SentReportInfo> info) {
+              EXPECT_EQ(1L, conversion_id);
+              EXPECT_EQ(test_case.report_allowed, info.has_value());
+            }));
 
     // Fast forward by 0, as we yield the thread when a report is scheduled to
     // be sent.
diff --git a/content/browser/conversions/conversion_test_utils.cc b/content/browser/conversions/conversion_test_utils.cc
index 2f6b7c3..338956e 100644
--- a/content/browser/conversions/conversion_test_utils.cc
+++ b/content/browser/conversions/conversion_test_utils.cc
@@ -149,12 +149,17 @@
   std::move(callback).Run(impressions_);
 }
 
-void TestConversionManager::GetReportsForWebUI(
+void TestConversionManager::GetPendingReportsForWebUI(
     base::OnceCallback<void(std::vector<ConversionReport>)> callback,
     base::Time max_report_time) {
   std::move(callback).Run(reports_);
 }
 
+const base::circular_deque<SentReportInfo>&
+TestConversionManager::GetSentReportsForWebUI() {
+  return sent_reports_;
+}
+
 void TestConversionManager::SendReportsForWebUI(base::OnceClosure done) {
   reports_.clear();
   std::move(done).Run();
@@ -184,6 +189,11 @@
   reports_ = std::move(reports);
 }
 
+void TestConversionManager::SetSentReportsForWebUI(
+    base::circular_deque<SentReportInfo> reports) {
+  sent_reports_ = std::move(reports);
+}
+
 void TestConversionManager::Reset() {
   num_impressions_ = 0u;
   num_conversions_ = 0u;
@@ -315,6 +325,29 @@
   return testing::AssertionSuccess();
 }
 
+testing::AssertionResult SentReportInfosEqual(
+    const base::circular_deque<SentReportInfo>& expected,
+    const base::circular_deque<SentReportInfo>& actual) {
+  const auto tie = [](const SentReportInfo& info) {
+    return std::make_tuple(info.report_url, info.report_body,
+                           info.http_response_code);
+  };
+
+  if (expected.size() != actual.size())
+    return testing::AssertionFailure() << "Expected length " << expected.size()
+                                       << ", actual: " << actual.size();
+
+  for (size_t i = 0; i < expected.size(); i++) {
+    if (tie(expected[i]) != tie(actual[i])) {
+      return testing::AssertionFailure()
+             << "Expected " << expected[i] << " at index " << i
+             << ", actual: " << actual[i];
+    }
+  }
+
+  return testing::AssertionSuccess();
+}
+
 std::vector<ConversionReport> GetConversionsToReportForTesting(
     ConversionManagerImpl* manager,
     base::Time max_report_time) {
diff --git a/content/browser/conversions/conversion_test_utils.h b/content/browser/conversions/conversion_test_utils.h
index 02840f6..579bde3 100644
--- a/content/browser/conversions/conversion_test_utils.h
+++ b/content/browser/conversions/conversion_test_utils.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/containers/circular_deque.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/sequenced_task_runner.h"
 #include "base/time/time.h"
@@ -16,6 +17,7 @@
 #include "content/browser/conversions/conversion_manager_impl.h"
 #include "content/browser/conversions/conversion_report.h"
 #include "content/browser/conversions/conversion_storage.h"
+#include "content/browser/conversions/sent_report_info.h"
 #include "content/browser/conversions/storable_conversion.h"
 #include "content/browser/conversions/storable_impression.h"
 #include "content/test/test_content_browser_client.h"
@@ -144,9 +146,10 @@
   void GetActiveImpressionsForWebUI(
       base::OnceCallback<void(std::vector<StorableImpression>)> callback)
       override;
-  void GetReportsForWebUI(
+  void GetPendingReportsForWebUI(
       base::OnceCallback<void(std::vector<ConversionReport>)> callback,
       base::Time max_report_time) override;
+  const base::circular_deque<SentReportInfo>& GetSentReportsForWebUI() override;
   void SendReportsForWebUI(base::OnceClosure done) override;
   const ConversionPolicy& GetConversionPolicy() const override;
   void ClearData(base::Time delete_begin,
@@ -157,6 +160,8 @@
   void SetActiveImpressionsForWebUI(
       std::vector<StorableImpression> impressions);
   void SetReportsForWebUI(std::vector<ConversionReport> reports);
+  void SetSentReportsForWebUI(
+      base::circular_deque<SentReportInfo> sent_reports);
 
   // Resets all counters on this.
   void Reset();
@@ -192,6 +197,7 @@
 
   std::vector<StorableImpression> impressions_;
   std::vector<ConversionReport> reports_;
+  base::circular_deque<SentReportInfo> sent_reports_;
 };
 
 // Helper class to construct a StorableImpression for tests using default data.
@@ -243,6 +249,10 @@
     const std::vector<ConversionReport>& expected,
     const std::vector<ConversionReport>& actual);
 
+testing::AssertionResult SentReportInfosEqual(
+    const base::circular_deque<SentReportInfo>& expected,
+    const base::circular_deque<SentReportInfo>& actual);
+
 std::vector<ConversionReport> GetConversionsToReportForTesting(
     ConversionManagerImpl* manager,
     base::Time max_report_time);
diff --git a/content/browser/conversions/sent_report_info.cc b/content/browser/conversions/sent_report_info.cc
new file mode 100644
index 0000000..b40a990
--- /dev/null
+++ b/content/browser/conversions/sent_report_info.cc
@@ -0,0 +1,16 @@
+// 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 "content/browser/conversions/sent_report_info.h"
+
+namespace content {
+
+std::ostream& operator<<(std::ostream& out, const SentReportInfo& info) {
+  out << "report_url: " << info.report_url
+      << ", report_body: " << info.report_body
+      << ", http_response_code: " << info.http_response_code;
+  return out;
+}
+
+}  // namespace content
diff --git a/content/browser/conversions/sent_report_info.h b/content/browser/conversions/sent_report_info.h
new file mode 100644
index 0000000..225b20b3
--- /dev/null
+++ b/content/browser/conversions/sent_report_info.h
@@ -0,0 +1,30 @@
+// 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 CONTENT_BROWSER_CONVERSIONS_SENT_REPORT_INFO_H_
+#define CONTENT_BROWSER_CONVERSIONS_SENT_REPORT_INFO_H_
+
+#include <ostream>
+#include <string>
+
+#include "content/common/content_export.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// Struct that contains data about sent reports for display in the Conversion
+// Internals WebUI.
+struct CONTENT_EXPORT SentReportInfo {
+  GURL report_url;
+  std::string report_body;
+  int http_response_code;
+};
+
+// Only used for logging.
+CONTENT_EXPORT std::ostream& operator<<(std::ostream& out,
+                                        const SentReportInfo& info);
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_CONVERSIONS_SENT_REPORT_INFO_H_
diff --git a/content/browser/devtools/protocol/emulation_handler.cc b/content/browser/devtools/protocol/emulation_handler.cc
index 973cb6d..9074497 100644
--- a/content/browser/devtools/protocol/emulation_handler.cc
+++ b/content/browser/devtools/protocol/emulation_handler.cc
@@ -15,12 +15,14 @@
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/idle_manager.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/url_constants.h"
 #include "net/http/http_util.h"
 #include "services/device/public/cpp/geolocation/geoposition.h"
 #include "services/device/public/mojom/geolocation_context.mojom.h"
 #include "services/device/public/mojom/geoposition.mojom.h"
+#include "third_party/blink/public/mojom/idle/idle_manager.mojom.h"
 #include "third_party/blink/public/mojom/widget/screen_orientation.mojom.h"
 #include "ui/events/gesture_detection/gesture_provider_config_helper.h"
 
diff --git a/content/browser/media/capture/desktop_capture_device.cc b/content/browser/media/capture/desktop_capture_device.cc
index c5a4d5dd..d5bff8c 100644
--- a/content/browser/media/capture/desktop_capture_device.cc
+++ b/content/browser/media/capture/desktop_capture_device.cc
@@ -14,6 +14,7 @@
 #include "base/bind.h"
 #include "base/check_op.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/location.h"
 #include "base/macros.h"
 #include "base/message_loop/message_pump_type.h"
@@ -37,6 +38,7 @@
 #include "content/public/common/content_switches.h"
 #include "media/base/video_util.h"
 #include "media/capture/content/capture_resolution_chooser.h"
+#include "media/webrtc/webrtc_switches.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/device/public/mojom/wake_lock.mojom.h"
 #include "services/device/public/mojom/wake_lock_provider.mojom.h"
@@ -517,6 +519,12 @@
   std::unique_ptr<webrtc::DesktopCapturer> capturer;
   std::unique_ptr<media::VideoCaptureDevice> result;
 
+#if defined(OS_WIN)
+  options.set_allow_cropping_window_capturer(true);
+  if (base::FeatureList::IsEnabled(features::kWebRtcAllowWgcDesktopCapturer))
+    options.set_allow_wgc_capturer(true);
+#endif
+
   // For browser tests, to create a fake desktop capturer.
   if (source.id == DesktopMediaID::kFakeId) {
     capturer = std::make_unique<webrtc::FakeDesktopCapturer>();
@@ -554,7 +562,7 @@
                                     webrtc::DesktopCaptureOptions()));
 #else
       std::unique_ptr<webrtc::DesktopCapturer> window_capturer =
-          webrtc::CroppingWindowCapturer::CreateCapturer(options);
+          webrtc::DesktopCapturer::CreateWindowCapturer(options);
 #endif
       if (window_capturer && window_capturer->SelectSource(source.id)) {
         window_capturer->FocusOnSelectedSource();
diff --git a/content/browser/renderer_host/cors_rfc1918_browsertest.cc b/content/browser/renderer_host/cors_rfc1918_browsertest.cc
index 5cc235c6..dfd3050 100644
--- a/content/browser/renderer_host/cors_rfc1918_browsertest.cc
+++ b/content/browser/renderer_host/cors_rfc1918_browsertest.cc
@@ -227,7 +227,8 @@
  protected:
   // Allows subclasses to construct instances with different features enabled.
   explicit CorsRfc1918BrowserTestBase(
-      const std::vector<base::Feature>& enabled_features)
+      const std::vector<base::Feature>& enabled_features,
+      const std::vector<base::Feature>& disabled_features)
       : insecure_local_server_(net::EmbeddedTestServer::TYPE_HTTP,
                                network::mojom::IPAddressSpace::kLocal,
                                GetTestDataFilePath()),
@@ -246,7 +247,7 @@
         secure_public_server_(net::EmbeddedTestServer::TYPE_HTTPS,
                               network::mojom::IPAddressSpace::kPublic,
                               GetTestDataFilePath()) {
-    feature_list_.InitWithFeatures(enabled_features, {});
+    feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
   void SetUpOnMainThread() override {
@@ -300,8 +301,10 @@
  public:
   CorsRfc1918BrowserTest()
       : CorsRfc1918BrowserTestBase(
-            {features::kBlockInsecurePrivateNetworkRequests,
-             features::kWarnAboutSecurePrivateNetworkRequests}) {}
+            {
+                features::kWarnAboutSecurePrivateNetworkRequests,
+            },
+            {}) {}
 };
 
 // Test with insecure private network requests blocked, including navigations.
@@ -309,17 +312,23 @@
     : public CorsRfc1918BrowserTestBase {
  public:
   CorsRfc1918BrowserTestBlockNavigations()
-      : CorsRfc1918BrowserTestBase({
-            features::kBlockInsecurePrivateNetworkRequests,
-            features::kWarnAboutSecurePrivateNetworkRequests,
-            features::kBlockInsecurePrivateNetworkRequestsForNavigations,
-        }) {}
+      : CorsRfc1918BrowserTestBase(
+            {
+                features::kWarnAboutSecurePrivateNetworkRequests,
+                features::kBlockInsecurePrivateNetworkRequestsForNavigations,
+            },
+            {}) {}
 };
 
 // Test with insecure private network requests allowed.
 class CorsRfc1918BrowserTestNoBlocking : public CorsRfc1918BrowserTestBase {
  public:
-  CorsRfc1918BrowserTestNoBlocking() : CorsRfc1918BrowserTestBase({}) {}
+  CorsRfc1918BrowserTestNoBlocking()
+      : CorsRfc1918BrowserTestBase(
+            {},
+            {
+                features::kBlockInsecurePrivateNetworkRequests,
+            }) {}
 };
 
 // This test verifies that when the right feature is enabled, iframe requests:
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 1a036bb..2005105 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -128,6 +128,7 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/navigation/navigation_policy.h"
 #include "third_party/blink/public/common/net/ip_address_space_util.h"
+#include "third_party/blink/public/common/permissions_policy/document_policy.h"
 #include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
 #include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
diff --git a/content/browser/renderer_host/navigation_request_browsertest.cc b/content/browser/renderer_host/navigation_request_browsertest.cc
index cf5b825..3b93fc0 100644
--- a/content/browser/renderer_host/navigation_request_browsertest.cc
+++ b/content/browser/renderer_host/navigation_request_browsertest.cc
@@ -17,6 +17,7 @@
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/site_instance_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/content_navigation_policy.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_throttle.h"
diff --git a/content/browser/renderer_host/policy_container_host_browsertest.cc b/content/browser/renderer_host/policy_container_host_browsertest.cc
index 6f654f5..2a2ac87a 100644
--- a/content/browser/renderer_host/policy_container_host_browsertest.cc
+++ b/content/browser/renderer_host/policy_container_host_browsertest.cc
@@ -2,10 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/policy_container_host.h"
+
+#include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/content_navigation_policy.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 04b53ab..77062f0c 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -63,6 +63,7 @@
 #include "content/browser/dom_storage/dom_storage_context_wrapper.h"
 #include "content/browser/download/data_url_blob_reader.h"
 #include "content/browser/download/mhtml_generation_manager.h"
+#include "content/browser/feature_observer.h"
 #include "content/browser/file_system/file_system_manager_impl.h"
 #include "content/browser/file_system/file_system_url_loader_factory.h"
 #include "content/browser/file_system_access/file_system_access_manager_impl.h"
@@ -236,6 +237,7 @@
 #include "third_party/blink/public/common/loader/inter_process_time_ticks_converter.h"
 #include "third_party/blink/public/common/loader/resource_type_util.h"
 #include "third_party/blink/public/common/messaging/transferable_message.h"
+#include "third_party/blink/public/common/permissions_policy/document_policy.h"
 #include "third_party/blink/public/common/permissions_policy/document_policy_features.h"
 #include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
 #include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 602041271..a2eb626 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -17,26 +17,27 @@
 #include <vector>
 
 #include "base/callback.h"
-#include "base/compiler_specific.h"
-#include "base/containers/circular_deque.h"
+#include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
-#include "base/containers/id_map.h"
 #include "base/containers/unique_ptr_adapters.h"
+#include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
 #include "base/i18n/rtl.h"
 #include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/process/kill.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/string_piece.h"
 #include "base/supports_user_data.h"
 #include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "base/unguessable_token.h"
 #include "build/build_config.h"
 #include "content/browser/accessibility/browser_accessibility_manager.h"
 #include "content/browser/bad_message.h"
 #include "content/browser/browser_interface_broker_impl.h"
 #include "content/browser/can_commit_status.h"
-#include "content/browser/feature_observer.h"
-#include "content/browser/idle/idle_manager_impl.h"
 #include "content/browser/net/cross_origin_opener_policy_reporter.h"
 #include "content/browser/prerender/prerender_host.h"
 #include "content/browser/renderer_host/back_forward_cache_metrics.h"
@@ -46,16 +47,13 @@
 #include "content/browser/renderer_host/policy_container_host.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/site_instance_impl.h"
-#include "content/browser/web_exposed_isolation_info.h"
 #include "content/browser/webui/web_ui_impl.h"
 #include "content/common/buildflags.h"
 #include "content/common/content_export.h"
-#include "content/common/content_navigation_policy.h"
 #include "content/common/dom_automation_controller.mojom.h"
 #include "content/common/frame.mojom.h"
 #include "content/common/input/input_injector.mojom-forward.h"
 #include "content/common/navigation_client.mojom-forward.h"
-#include "content/common/navigation_params.mojom.h"
 #include "content/common/render_accessibility.mojom.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/content_browser_client.h"
@@ -71,7 +69,6 @@
 #include "media/mojo/services/media_metrics_provider.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
-#include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -80,89 +77,59 @@
 #include "mojo/public/cpp/system/data_pipe.h"
 #include "net/base/isolation_info.h"
 #include "net/base/network_isolation_key.h"
-#include "net/cookies/canonical_cookie.h"
-#include "net/cookies/cookie_inclusion_status.h"
-#include "net/http/http_response_headers.h"
-#include "services/device/public/mojom/sensor_provider.mojom.h"
-#include "services/device/public/mojom/wake_lock_context.mojom.h"
-#include "services/metrics/public/cpp/ukm_recorder.h"
+#include "services/device/public/mojom/sensor_provider.mojom-forward.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
-#include "services/network/public/cpp/content_security_policy/csp_context.h"
 #include "services/network/public/cpp/cross_origin_embedder_policy.h"
 #include "services/network/public/cpp/cross_origin_opener_policy.h"
 #include "services/network/public/mojom/fetch_api.mojom-forward.h"
-#include "services/network/public/mojom/network_context.mojom.h"
-#include "services/network/public/mojom/trust_tokens.mojom.h"
 #include "services/network/public/mojom/url_loader_network_service_observer.mojom-forward.h"
-#include "services/service_manager/public/mojom/interface_provider.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/loader/previews_state.h"
-#include "third_party/blink/public/common/permissions_policy/document_policy.h"
 #include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
 #include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
-#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
-#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
-#include "third_party/blink/public/mojom/choosers/popup_menu.mojom.h"
-#include "third_party/blink/public/mojom/commit_result/commit_result.mojom.h"
+#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom-forward.h"
 #include "third_party/blink/public/mojom/compute_pressure/compute_pressure.mojom-forward.h"
-#include "third_party/blink/public/mojom/contacts/contacts_manager.mojom.h"
-#include "third_party/blink/public/mojom/devtools/devtools_agent.mojom.h"
-#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
-#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
+#include "third_party/blink/public/mojom/contacts/contacts_manager.mojom-forward.h"
+#include "third_party/blink/public/mojom/feature_observer/feature_observer.mojom-forward.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom-forward.h"
-#include "third_party/blink/public/mojom/font_access/font_access.mojom.h"
+#include "third_party/blink/public/mojom/font_access/font_access.mojom-forward.h"
 #include "third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom.h"
-#include "third_party/blink/public/mojom/frame/blocked_navigation_types.mojom.h"
 #include "third_party/blink/public/mojom/frame/find_in_page.mojom.h"
 #include "third_party/blink/public/mojom/frame/frame.mojom-forward.h"
-#include "third_party/blink/public/mojom/frame/frame_owner_element_type.mojom.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom-forward.h"
 #include "third_party/blink/public/mojom/frame/reporting_observer.mojom-forward.h"
-#include "third_party/blink/public/mojom/frame/tree_scope_type.mojom.h"
-#include "third_party/blink/public/mojom/frame/user_activation_update_types.mojom.h"
-#include "third_party/blink/public/mojom/idle/idle_manager.mojom.h"
+#include "third_party/blink/public/mojom/idle/idle_manager.mojom-forward.h"
 #include "third_party/blink/public/mojom/image_downloader/image_downloader.mojom.h"
 #include "third_party/blink/public/mojom/input/focus_type.mojom-forward.h"
-#include "third_party/blink/public/mojom/input/input_handler.mojom.h"
-#include "third_party/blink/public/mojom/installedapp/installed_app_provider.mojom.h"
+#include "third_party/blink/public/mojom/installedapp/installed_app_provider.mojom-forward.h"
 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-forward.h"
-#include "third_party/blink/public/mojom/loader/transferrable_url_loader.mojom.h"
 #include "third_party/blink/public/mojom/notifications/notification_service.mojom-forward.h"
-#include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
-#include "third_party/blink/public/mojom/peerconnection/peer_connection_tracker.mojom.h"
-#include "third_party/blink/public/mojom/permissions/permission.mojom.h"
+#include "third_party/blink/public/mojom/peerconnection/peer_connection_tracker.mojom-forward.h"
 #include "third_party/blink/public/mojom/portal/portal.mojom-forward.h"
-#include "third_party/blink/public/mojom/prerender/prerender.mojom.h"
-#include "third_party/blink/public/mojom/presentation/presentation.mojom.h"
+#include "third_party/blink/public/mojom/presentation/presentation.mojom-forward.h"
 #include "third_party/blink/public/mojom/screen_enumeration/screen_enumeration.mojom-forward.h"
-#include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom.h"
 #include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-forward.h"
-#include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
 #include "third_party/blink/public/mojom/sms/webotp_service.mojom-forward.h"
 #include "third_party/blink/public/mojom/speech/speech_synthesis.mojom-forward.h"
-#include "third_party/blink/public/mojom/usb/web_usb_service.mojom.h"
 #include "third_party/blink/public/mojom/webaudio/audio_context_manager.mojom-forward.h"
-#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
-#include "third_party/blink/public/mojom/webauthn/virtual_authenticator.mojom.h"
+#include "third_party/blink/public/mojom/webauthn/authenticator.mojom-forward.h"
+#include "third_party/blink/public/mojom/webauthn/virtual_authenticator.mojom-forward.h"
 #include "third_party/blink/public/mojom/webid/federated_auth_request.mojom-forward.h"
 #include "third_party/blink/public/mojom/webid/federated_auth_response.mojom-forward.h"
-#include "third_party/blink/public/mojom/websockets/websocket_connector.mojom.h"
-#include "third_party/blink/public/mojom/webtransport/web_transport_connector.mojom.h"
-#include "third_party/blink/public/mojom/worker/dedicated_worker_host_factory.mojom.h"
+#include "third_party/blink/public/mojom/webtransport/web_transport_connector.mojom-forward.h"
+#include "third_party/blink/public/mojom/worker/dedicated_worker_host_factory.mojom-forward.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/accessibility/ax_action_handler_base.h"
-#include "ui/accessibility/ax_event.h"
-#include "ui/accessibility/ax_mode.h"
-#include "ui/accessibility/ax_node_data.h"
 #include "ui/accessibility/ax_tree_update.h"
 #include "ui/base/page_transition_types.h"
 #include "ui/gfx/geometry/rect.h"
 
 #if defined(OS_ANDROID)
+#include "base/containers/id_map.h"
 #include "services/device/public/mojom/nfc.mojom.h"
 #else
-#include "third_party/blink/public/mojom/hid/hid.mojom.h"
-#include "third_party/blink/public/mojom/serial/serial.mojom.h"
+#include "third_party/blink/public/mojom/hid/hid.mojom-forward.h"
+#include "third_party/blink/public/mojom/serial/serial.mojom-forward.h"
 #endif
 
 #if BUILDFLAG(ENABLE_MEDIA_REMOTING)
@@ -178,6 +145,7 @@
 namespace blink {
 class AssociatedInterfaceProvider;
 class AssociatedInterfaceRegistry;
+class DocumentPolicy;
 struct FramePolicy;
 struct TransferableMessage;
 struct UntrustworthyContextMenuParams;
@@ -195,6 +163,10 @@
 class Range;
 }
 
+namespace mojo {
+class MessageFilter;
+}
+
 namespace network {
 class ResourceRequestBody;
 }  // namespace network
@@ -207,18 +179,24 @@
 class ClipboardFormatType;
 }
 
+namespace ukm {
+class UkmRecorder;
+}
+
 namespace content {
 class AgentSchedulingGroupHost;
 class AppCacheNavigationHandle;
-class BackForwardCacheMetrics;
 class CrossOriginEmbedderPolicyReporter;
+class FeatureObserver;
 class FrameTree;
 class FrameTreeNode;
 class GeolocationServiceImpl;
+class IdleManager;
+class IdleManagerImpl;
 class MediaInterfaceProxy;
 class NavigationEarlyHintsManager;
-class NavigationEntryImpl;
 class NavigationRequest;
+class PeakGpuMemoryTracker;
 class PeerConnectionTrackerHost;
 class PepperPluginInstanceHost;
 class Portal;
@@ -235,16 +213,16 @@
 class RenderWidgetHostViewBase;
 class ScreenEnumerationImpl;
 class SensorProviderProxyImpl;
-class SerialService;
 class SpeechSynthesisImpl;
+class SubresourceWebBundleNavigationInfo;
 class TimeoutMonitor;
 class WebAuthRequestSecurityChecker;
 class WebBluetoothServiceImpl;
 class WebBundleHandle;
-class SubresourceWebBundleNavigationInfo;
 class WebBundleHandleTracker;
+class WebExposedIsolationInfo;
+class WebUIImpl;
 struct PendingNavigation;
-
 struct ResourceTimingInfo;
 struct SubresourceLoaderParams;
 
diff --git a/content/browser/resources/conversions/conversion_internals.html b/content/browser/resources/conversions/conversion_internals.html
index 1224cfe..df608b8 100644
--- a/content/browser/resources/conversions/conversion_internals.html
+++ b/content/browser/resources/conversions/conversion_internals.html
@@ -104,6 +104,28 @@
       <button id="send-reports">Send All Reports</button>
     </div>
   </div>
+  <h2> Sent Conversion Reports </h2>
+  <div class="content">
+    <div class="table-wrapper">
+      <table id="sent-report-table">
+        <thead>
+          <tr class="header-row">
+            <th>
+              Report URL
+            </th>
+            <th>
+              Report Body
+            </th>
+            <th>
+              HTTP Response Code
+            </th>
+          </tr>
+        </thead>
+        <tbody id="sent-report-table-body">
+        </tbody>
+      </table>
+    </div>
+  </div>
 </div>
 
 <template id="impressionrow">
@@ -129,5 +151,13 @@
     <td class="source-type-cell"></td>
   </tr>
 </template>
+
+<template id="sentreportrow">
+  <tr>
+    <td class="report-url-data-cell"></td>
+    <td class="report-body-data-cell"></td>
+    <td class="http-response-code-data-cell"></td>
+  </tr>
+</template>
 </body>
 </html>
diff --git a/content/browser/resources/conversions/conversion_internals.js b/content/browser/resources/conversions/conversion_internals.js
index b1a92225..9546358 100644
--- a/content/browser/resources/conversions/conversion_internals.js
+++ b/content/browser/resources/conversions/conversion_internals.js
@@ -5,7 +5,7 @@
 import {$} from 'chrome://resources/js/util.m.js';
 import {Origin} from 'chrome://resources/mojo/url/mojom/origin.mojom-webui.js';
 
-import {ConversionInternalsHandler, ConversionInternalsHandlerRemote, SourceType, WebUIImpression} from './conversion_internals.mojom-webui.js';
+import {ConversionInternalsHandler, ConversionInternalsHandlerRemote, SentReportInfo, SourceType, WebUIConversionReport, WebUIImpression} from './conversion_internals.mojom-webui.js';
 
 /**
  * Reference to the backend providing all the data.
@@ -20,12 +20,18 @@
 let impressions = null;
 
 /**
- * All impressions held in storage at last update.
- * @type {!Array<!WebUIImpression>}
+ * All reports held in storage at last update.
+ * @type {!Array<!WebUIConversionReport>}
  */
 let reports = null;
 
 /**
+ * All sent reports at last update.
+ * @type {!Array<!SentReportInfo>}
+ */
+let sentReports = null;
+
+/**
  * This is used to create TrustedHTML.
  * @type {!TrustedTypePolicy}
  */
@@ -113,6 +119,21 @@
 }
 
 /**
+ * Creates a single row for the sent report table.
+ * @param {!SentReportInfo} info The info to create the row.
+ * @return {!HTMLElement}
+ */
+function createSentReportRow(info) {
+  const template = $('sentreportrow').cloneNode(true);
+  const td = template.content.querySelectorAll('td');
+
+  td[0].textContent = info.reportUrl.url;
+  td[1].textContent = info.reportBody;
+  td[2].textContent = info.httpResponseCode;
+  return document.importNode(template.content, true);
+}
+
+/**
  * Regenerates the impression table from |impressions|.
  */
 function renderImpressionTable() {
@@ -151,8 +172,27 @@
 }
 
 /**
- * Fetch all active impressions and pending reports from the backend and
- * populate the tables. Also update measurement enabled status.
+ * Regenerates the sent report table from |sentReports|.
+ */
+function renderSentReportTable() {
+  const sentReportTable = $('sent-report-table-body');
+  clearTable(sentReportTable);
+  sentReports.forEach(
+      report => sentReportTable.appendChild(createSentReportRow(report)));
+
+  // If there are no sent reports, add an empty row to indicate the table is
+  // purposefully empty.
+  if (!sentReports.length) {
+    const template = $('sentreportrow').cloneNode(true);
+    const td = template.content.querySelectorAll('td');
+    td[0].textContent = 'No sent reports.';
+    sentReportTable.appendChild(document.importNode(template.content, true));
+  }
+}
+
+/**
+ * Fetch all active impressions, pending reports, and sent reports from the
+ * backend and populate the tables. Also update measurement enabled status.
  */
 function updatePageData() {
   // Get the feature status for ConversionMeasurement and populate it.
@@ -189,6 +229,11 @@
     reports = response.reports;
     renderReportTable();
   });
+
+  pageHandler.getSentReports().then((response) => {
+    sentReports = response.reports;
+    renderSentReportTable();
+  });
 }
 
 /**
diff --git a/content/browser/resources/media/manager.js b/content/browser/resources/media/manager.js
index cbecdae..30f968d9 100644
--- a/content/browser/resources/media/manager.js
+++ b/content/browser/resources/media/manager.js
@@ -23,7 +23,6 @@
     var copyAllPlayerButton = $('copy-all-player-button');
     var copyAllAudioButton = $('copy-all-audio-button');
     var hidePlayersButton = $('hide-players-button');
-    var devtoolsNoticeWindow = $('devtools-notice-window');
 
     // In tests we may not have these buttons.
     if (copyAllPlayerButton) {
@@ -42,9 +41,6 @@
     if (hidePlayersButton) {
       hidePlayersButton.onclick = this.hidePlayers_.bind(this);
     }
-    if (devtoolsNoticeWindow) {
-      devtoolsNoticeWindow.onclick = this.hideNoticeWindow_;
-    }
   }
 
   /**
@@ -130,10 +126,6 @@
     }, this);
   }
 
-  hideNoticeWindow_() {
-    this.style.display = 'none';
-  }
-
   updatePlayerInfoNoRecord(id, timestamp, key, value) {
     if (!this.players_[id]) {
       console.error('[updatePlayerInfo] Id ' + id + ' does not exist');
diff --git a/content/browser/resources/media/media_internals.css b/content/browser/resources/media/media_internals.css
index 9c03303..a7e24a9 100644
--- a/content/browser/resources/media/media_internals.css
+++ b/content/browser/resources/media/media_internals.css
@@ -237,19 +237,3 @@
 #audio-focus-session-list {
   list-style: none;
 }
-
-#devtools-notice-window {
-  background-color: rgb(252, 247, 134);
-  border: 1px solid black;
-  border-radius: 4px;
-  font-size: 10px;
-  height: 60px;
-  left: 50%;
-  line-height: 20px;
-  margin-inline-start: -300px;
-  position: fixed;
-  text-align: center;
-  top: 10px;
-  width: 420px;
-  z-index: 1;
-}
\ No newline at end of file
diff --git a/content/browser/resources/media/media_internals.html b/content/browser/resources/media/media_internals.html
index 3d5b95b..6ad49a9 100644
--- a/content/browser/resources/media/media_internals.html
+++ b/content/browser/resources/media/media_internals.html
@@ -15,14 +15,6 @@
 </head>
 
 <body>
-  <div id="devtools-notice-window">
-    <div>
-    chrome://media-internals is deprecated and will be removed in <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1146594">Chrome 91</a>. Please migrate to the new <a href="https://developers.google.com/web/tools/chrome-devtools/media-panel">devtools panel</a>. If this deprecation breaks your workflow, please fill out <a href="https://docs.google.com/forms/d/e/1FAIpQLSe5X3Al_esm00XL3jUMG78xD9_CSy9bdMl6XZWVfdUJP5wSqg/viewform">this survey</a> to ensure your feedback is considered before removal.
-    </div>
-    <div>
-    (click to dismiss)
-    </div>
-  </div>
   <tabbox>
     <tabs>
       <tab>Players</tab>
diff --git a/content/browser/webauth/authenticator_environment_impl.h b/content/browser/webauth/authenticator_environment_impl.h
index 4050a85..6814320f 100644
--- a/content/browser/webauth/authenticator_environment_impl.h
+++ b/content/browser/webauth/authenticator_environment_impl.h
@@ -15,7 +15,7 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/authenticator_environment.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "third_party/blink/public/mojom/webauthn/virtual_authenticator.mojom.h"
+#include "third_party/blink/public/mojom/webauthn/virtual_authenticator.mojom-forward.h"
 
 namespace device {
 class FidoDiscoveryFactory;
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index 9c204aa3..84d1ce1 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -3174,7 +3174,7 @@
           [&usb_device_lost_done]() { usb_device_lost_done.Quit(); }));
 
   authenticator->GetAssertion(std::move(options), callback_receiver.callback());
-  fake_hid_discovery->WaitForCallToStartAndSimulateSuccess();
+  fake_hid_discovery->WaitForCallToStart();
   fake_hid_discovery->AddDevice(std::move(mock_usb_device));
   usb_device_found_done.Run();
 
diff --git a/content/browser/webauth/webauth_request_security_checker.cc b/content/browser/webauth/webauth_request_security_checker.cc
index 113eee9c..c2c9972 100644
--- a/content/browser/webauth/webauth_request_security_checker.cc
+++ b/content/browser/webauth/webauth_request_security_checker.cc
@@ -12,7 +12,9 @@
 #include "device/fido/features.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
+#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 #include "url/url_util.h"
diff --git a/content/browser/webauth/webauth_request_security_checker.h b/content/browser/webauth/webauth_request_security_checker.h
index 27b0fa8..42e6951 100644
--- a/content/browser/webauth/webauth_request_security_checker.h
+++ b/content/browser/webauth/webauth_request_security_checker.h
@@ -9,8 +9,9 @@
 
 #include "base/memory/ref_counted.h"
 #include "content/common/content_export.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
+#include "third_party/blink/public/mojom/webauthn/authenticator.mojom-forward.h"
+
+class GURL;
 
 namespace url {
 class Origin;
diff --git a/content/browser/webauth/webauth_request_security_checker_unittest.cc b/content/browser/webauth/webauth_request_security_checker_unittest.cc
index 8d379930..ab19e3e 100644
--- a/content/browser/webauth/webauth_request_security_checker_unittest.cc
+++ b/content/browser/webauth/webauth_request_security_checker_unittest.cc
@@ -16,6 +16,7 @@
 #include "device/fido/features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
+#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index f7463f2..1fcb3b40 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -347,6 +347,7 @@
       runtimeFeatureNameToChromiumFeatureMapping[] = {
           {"AllowContentInitiatedDataUrlNavigations",
            features::kAllowContentInitiatedDataUrlNavigations},
+          {"AutofillShadowDOM", blink::features::kAutofillShadowDOM},
           {"AndroidDownloadableFontsMatching",
            features::kAndroidDownloadableFontsMatching},
           {"BlockCredentialedSubresources",
diff --git a/content/public/PRESUBMIT.py b/content/public/PRESUBMIT.py
deleted file mode 100644
index 670a23d6..0000000
--- a/content/public/PRESUBMIT.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Content public presubmit script
-
-See https://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
-for more details about the presubmit API built into gcl.
-"""
-
-def _CheckConstInterfaces(input_api, output_api):
-  # Matches 'virtual...const = 0;', 'virtual...const;' or 'virtual...const {}'
-  pattern = input_api.re.compile(r'virtual[^;]*const\s*(=\s*0)?\s*({}|;)',
-                                 input_api.re.MULTILINE)
-
-  files = []
-  for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
-    if not f.LocalPath().endswith('.h'):
-      continue
-
-    contents = input_api.ReadFile(f)
-    if pattern.search(contents):
-      files.append(f)
-
-  if len(files):
-      return [output_api.PresubmitError(
-          'Do not add const to content/public '
-          'interfaces. See '
-          'https://www.chromium.org/developers/content-module/content-api',
-          files) ]
-
-  return []
-
-def CheckChangeOnUpload(input_api, output_api):
-  results = []
-  results.extend(_CheckConstInterfaces(input_api, output_api))
-  return results
-
-
-def CheckChangeOnCommit(input_api, output_api):
-  results = []
-  results.extend(_CheckConstInterfaces(input_api, output_api))
-  return results
diff --git a/content/public/README.md b/content/public/README.md
index 6897e11d..e0491b9 100644
--- a/content/public/README.md
+++ b/content/public/README.md
@@ -64,10 +64,9 @@
   higher level.
 - We avoid single-method delegate interfaces, and in those case we use
   callbacks.
-- Don't add the `const` identifier to interfaces. For interfaces implemented by
-  the embedder, we can't make assumptions about what the embedder needs to
-  implement it. For interfaces implemented by content, the implementation
-  details doesn't have to be exposed.
+- The `const` identifier can be added to simple getter APIs implemented by
+  content. Don't add `const` to interfaces implemented by the embedder, where
+  we can't make assumptions about what the embedder needs to implement it.
 - Observer interfaces (i.e. `WebContentsObserver`, `RenderFrameObserver`,
   `RenderViewObserver`) should only have void methods. This is because otherwise
   the order that observers are registered would matter, and we don't want that.
diff --git a/content/public/browser/site_isolation_policy.cc b/content/public/browser/site_isolation_policy.cc
index 0c3ca3e..252ecbb9 100644
--- a/content/public/browser/site_isolation_policy.cc
+++ b/content/public/browser/site_isolation_policy.cc
@@ -154,7 +154,7 @@
 
 // static
 bool SiteIsolationPolicy::IsSiteIsolationForCOOPEnabled() {
-  // If the user has explicitly enabled site isolation for OAuth sites from the
+  // If the user has explicitly enabled site isolation for COOP sites from the
   // command line, honor this regardless of policies that may disable site
   // isolation.
   if (base::FeatureList::GetInstance()->IsFeatureOverriddenFromCommandLine(
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 21f09d8..f3db582 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -107,7 +107,7 @@
 //  - https://wicg.github.io/cors-rfc1918/#integration-fetch
 //  - kBlockInsecurePrivateNetworkRequestsForNavigations
 const base::Feature kBlockInsecurePrivateNetworkRequests{
-    "BlockInsecurePrivateNetworkRequests", base::FEATURE_DISABLED_BY_DEFAULT};
+    "BlockInsecurePrivateNetworkRequests", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // When both kBlockInsecurePrivateNetworkRequestsForNavigations and
 // kBlockInsecurePrivateNetworkRequests are enabled, navigations initiated
@@ -735,6 +735,12 @@
     kSiteIsolationForCrossOriginOpenerPolicyMaxSitesParam{
         &kSiteIsolationForCrossOriginOpenerPolicy, "stored_sites_max_size",
         100};
+// This feature param controls the period of time for which the stored sites
+// should remain valid. Only used when persistence is also enabled.
+const base::FeatureParam<base::TimeDelta>
+    kSiteIsolationForCrossOriginOpenerPolicyExpirationTimeoutParam{
+        &kSiteIsolationForCrossOriginOpenerPolicy, "expiration_timeout",
+        base::TimeDelta::FromDays(7)};
 
 // Controls whether SpareRenderProcessHostManager tries to always have a warm
 // spare renderer process around for the most recently requested BrowserContext.
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 9b24661..ffd3511 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -169,6 +169,8 @@
     kSiteIsolationForCrossOriginOpenerPolicyShouldPersistParam;
 CONTENT_EXPORT extern const base::FeatureParam<int>
     kSiteIsolationForCrossOriginOpenerPolicyMaxSitesParam;
+CONTENT_EXPORT extern const base::FeatureParam<base::TimeDelta>
+    kSiteIsolationForCrossOriginOpenerPolicyExpirationTimeoutParam;
 CONTENT_EXPORT extern const base::Feature
     kSkipEarlyCommitPendingForCrashedFrame;
 CONTENT_EXPORT extern const base::Feature kWebOTP;
diff --git a/content/public/renderer/render_frame.h b/content/public/renderer/render_frame.h
index b0f946d1..577f42d5 100644
--- a/content/public/renderer/render_frame.h
+++ b/content/public/renderer/render_frame.h
@@ -42,6 +42,7 @@
 class WebLocalFrame;
 class WebPlugin;
 struct WebPluginParams;
+class WebView;
 }  // namespace blink
 
 namespace gfx {
@@ -151,8 +152,13 @@
   // Get the routing ID of the frame.
   virtual int GetRoutingID() = 0;
 
+  // Returns the associated WebView.
+  virtual blink::WebView* GetWebView() = 0;
+  virtual const blink::WebView* GetWebView() const = 0;
+
   // Returns the associated WebFrame.
   virtual blink::WebLocalFrame* GetWebFrame() = 0;
+  virtual const blink::WebLocalFrame* GetWebFrame() const = 0;
 
   // Gets WebKit related preferences associated with this frame.
   virtual const blink::web_pref::WebPreferences& GetBlinkPreferences() = 0;
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index 27b995c..1290fbc 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -279,7 +279,7 @@
     bounds->offset_container_id = offset_container.AxID();
 
   if (content::AXShouldIncludePageScaleFactorInRoot() && obj.Equals(root())) {
-    const WebView* web_view = render_frame_->GetRenderView()->GetWebView();
+    const WebView* web_view = render_frame_->GetWebView();
     std::unique_ptr<gfx::Transform> container_transform =
         std::make_unique<gfx::Transform>(web_container_transform);
     container_transform->Scale(web_view->PageScaleFactor(),
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index 8b5ecc7..6c902e05 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -111,7 +111,7 @@
   content::RenderThread::Get()->BindHostReceiver(
       recorder.InitWithNewPipeAndPassReceiver());
   ukm_recorder_ = std::make_unique<ukm::MojoUkmRecorder>(std::move(recorder));
-  WebView* web_view = render_frame_->GetRenderView()->GetWebView();
+  WebView* web_view = render_frame_->GetWebView();
   WebSettings* settings = web_view->GetSettings();
 
   SetAccessibilityCrashKey(mode);
@@ -309,7 +309,7 @@
     // The following transformation of the input point is naive, but works
     // fairly well. It will fail with CSS transforms that rotate or shear.
     // https://crbug.com/981959.
-    WebView* web_view = render_frame_->GetRenderView()->GetWebView();
+    WebView* web_view = render_frame_->GetWebView();
     gfx::PointF viewport_offset = web_view->VisualViewportOffset();
     transformed_point +=
         gfx::Vector2d(viewport_offset.x(), viewport_offset.y()) -
@@ -1378,8 +1378,7 @@
 }
 
 blink::WebDocument RenderAccessibilityImpl::GetPopupDocument() {
-  blink::WebPagePopup* popup =
-      render_frame_->GetRenderView()->GetWebView()->GetPagePopup();
+  blink::WebPagePopup* popup = render_frame_->GetWebView()->GetPagePopup();
   if (popup)
     return popup->GetDocument();
   return WebDocument();
diff --git a/content/renderer/pepper/pepper_plugin_instance_impl.cc b/content/renderer/pepper/pepper_plugin_instance_impl.cc
index b130a6a..4772e27 100644
--- a/content/renderer/pepper/pepper_plugin_instance_impl.cc
+++ b/content/renderer/pepper/pepper_plugin_instance_impl.cc
@@ -1974,7 +1974,7 @@
     return false;
 
   if (fullscreen) {
-    if (!render_frame_->render_view()
+    if (!render_frame_->GetWebView()
              ->GetRendererPreferences()
              .plugin_fullscreen_allowed) {
       return false;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index ffde530..bc609e7 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -1451,7 +1451,7 @@
   // The WebFrame created here was already attached to the Page as its main
   // frame, and the WebFrameWidget has been initialized, so we can call
   // WebView's DidAttachLocalMainFrame().
-  render_view->GetWebView()->DidAttachLocalMainFrame();
+  render_frame->GetWebView()->DidAttachLocalMainFrame();
 
   // The WebFrameWidget should start with valid VisualProperties, including a
   // non-zero size. While WebFrameWidget would not normally receive IPCs and
@@ -2279,13 +2279,30 @@
   return frame_;
 }
 
+const blink::WebLocalFrame* RenderFrameImpl::GetWebFrame() const {
+  DCHECK(frame_);
+  return frame_;
+}
+
+blink::WebView* RenderFrameImpl::GetWebView() {
+  blink::WebView* web_view = GetWebFrame()->View();
+  DCHECK(web_view);
+  return web_view;
+}
+
+const blink::WebView* RenderFrameImpl::GetWebView() const {
+  const blink::WebView* web_view = GetWebFrame()->View();
+  DCHECK(web_view);
+  return web_view;
+}
+
 const blink::web_pref::WebPreferences& RenderFrameImpl::GetBlinkPreferences() {
-  return GetWebFrame()->View()->GetWebPreferences();
+  return GetWebView()->GetWebPreferences();
 }
 
 const blink::RendererPreferences& RenderFrameImpl::GetRendererPreferences()
     const {
-  return render_view_->GetRendererPreferences();
+  return GetWebView()->GetRendererPreferences();
 }
 
 void RenderFrameImpl::ShowVirtualKeyboard() {
@@ -2818,8 +2835,7 @@
   AssertNavigationCommits assert_navigation_commits(
       this, kMayReplaceInitialEmptyDocument);
 
-  DCHECK(render_view_->GetWebView());
-  render_view_->GetWebView()->SetHistoryListFromNavigation(
+  GetWebView()->SetHistoryListFromNavigation(
       commit_params->current_history_list_offset,
       commit_params->current_history_list_length);
 
@@ -3214,7 +3230,7 @@
 
   mojo::PendingReceiver<blink::mojom::RendererPreferenceWatcher>
       watcher_receiver;
-  render_view()->RegisterRendererPreferenceWatcher(
+  GetWebView()->RegisterRendererPreferenceWatcher(
       watcher_receiver.InitWithNewPipeAndPassRemote());
 
   mojo::PendingRemote<blink::mojom::ResourceLoadInfoNotifier>
@@ -3229,7 +3245,7 @@
   // non-PlzDedicatedWorker and worklets.
   scoped_refptr<WebWorkerFetchContextImpl> worker_fetch_context =
       WebWorkerFetchContextImpl::Create(
-          provider->context(), render_view_->GetRendererPreferences(),
+          provider->context(), GetWebView()->GetRendererPreferences(),
           std::move(watcher_receiver), GetLoaderFactoryBundle()->Clone(),
           GetLoaderFactoryBundle()->CloneWithoutAppCacheFactory(),
           /*pending_subresource_loader_updater=*/mojo::NullReceiver(),
@@ -3256,7 +3272,7 @@
 
   mojo::PendingReceiver<blink::mojom::RendererPreferenceWatcher>
       watcher_receiver;
-  render_view()->RegisterRendererPreferenceWatcher(
+  GetWebView()->RegisterRendererPreferenceWatcher(
       watcher_receiver.InitWithNewPipeAndPassRemote());
 
   mojo::PendingRemote<blink::mojom::ResourceLoadInfoNotifier>
@@ -3269,7 +3285,7 @@
   scoped_refptr<WebWorkerFetchContextImpl> worker_fetch_context =
       static_cast<DedicatedWorkerHostFactoryClient*>(factory_client)
           ->CreateWorkerFetchContext(
-              render_view_->GetRendererPreferences(),
+              GetWebView()->GetRendererPreferences(),
               std::move(watcher_receiver),
               std::move(pending_resource_load_info_notifier));
 
@@ -3944,7 +3960,7 @@
     bool for_main_frame,
     ui::PageTransition transition_type,
     ForRedirect for_redirect) {
-  if (render_view_->GetRendererPreferences().enable_do_not_track) {
+  if (GetWebView()->GetRendererPreferences().enable_do_not_track) {
     request.SetHttpHeaderField(
         blink::WebString::FromUTF8(blink::kDoNotTrackHeader), "1");
   }
@@ -3994,7 +4010,7 @@
       is_for_no_state_prefetch);
   url_request_extra_data->set_frame_request_blocker(frame_request_blocker_);
   url_request_extra_data->set_allow_cross_origin_auth_prompt(
-      render_view_->GetRendererPreferences().allow_cross_origin_auth_prompt);
+      GetWebView()->GetRendererPreferences().allow_cross_origin_auth_prompt);
   url_request_extra_data->set_top_frame_origin(GetSecurityOriginOfTopFrame());
 
   request.SetDownloadToNetworkCacheOnly(is_for_no_state_prefetch &&
@@ -4012,7 +4028,7 @@
 
   request.SetHasUserGesture(frame_->HasTransientUserActivation());
 
-  if (!render_view_->GetRendererPreferences().enable_referrers) {
+  if (!GetWebView()->GetRendererPreferences().enable_referrers) {
     request.SetReferrerString(WebString());
     request.SetReferrerPolicy(network::mojom::ReferrerPolicy::kNever);
   }
@@ -4168,7 +4184,8 @@
 
 blink::WebString RenderFrameImpl::UserAgentOverride() {
   if (ShouldUseUserAgentOverride()) {
-    return WebString::FromUTF8(render_view_->GetRendererPreferences()
+    return WebString::FromUTF8(GetWebView()
+                                   ->GetRendererPreferences()
                                    .user_agent_override.ua_string_override);
   }
 
@@ -4192,27 +4209,22 @@
 absl::optional<blink::UserAgentMetadata>
 RenderFrameImpl::UserAgentMetadataOverride() {
   if (ShouldUseUserAgentOverride()) {
-    return render_view_->GetRendererPreferences()
+    return GetWebView()
+        ->GetRendererPreferences()
         .user_agent_override.ua_metadata_override;
   }
   return absl::nullopt;
 }
 
 bool RenderFrameImpl::ShouldUseUserAgentOverride() const {
-  if (!render_view_->GetWebView() || !render_view_->GetWebView()->MainFrame() ||
-      render_view_->GetRendererPreferences()
-          .user_agent_override.ua_string_override.empty()) {
-    return false;
-  }
-
+  auto* web_view = GetWebView();
   // TODO(nasko): When the top-level frame is remote, there is no
   // WebDocumentLoader associated with it, so the checks below are not valid.
   // Temporarily return early and fix properly as part of
   // https://crbug.com/426555.
-  if (render_view_->GetWebView()->MainFrame()->IsWebRemoteFrame())
+  if (web_view->MainFrame()->IsWebRemoteFrame())
     return false;
-  WebLocalFrame* main_frame =
-      render_view_->GetWebView()->MainFrame()->ToWebLocalFrame();
+  const WebLocalFrame* main_frame = web_view->MainFrame()->ToWebLocalFrame();
 
   WebDocumentLoader* document_loader = main_frame->GetDocumentLoader();
   InternalDocumentStateData* internal_data =
@@ -4223,7 +4235,7 @@
 }
 
 blink::WebString RenderFrameImpl::DoNotTrackValue() {
-  if (render_view_->GetRendererPreferences().enable_do_not_track)
+  if (GetWebView()->GetRendererPreferences().enable_do_not_track)
     return WebString::FromUTF8("1");
   return WebString();
 }
@@ -4503,7 +4515,7 @@
       blink::WebString::FromUTF8(unique_name_helper_.value()));
 
   bool is_new_navigation = commit_type == blink::kWebStandardCommit;
-  blink::WebView* webview = render_view_->GetWebView();
+  blink::WebView* webview = GetWebView();
   if (commit_params.should_clear_history_list) {
     webview->SetHistoryListFromNavigation(/*history_offset*/ 0,
                                           /*history_length*/ 1);
@@ -4541,7 +4553,7 @@
   UpdateNavigationHistory(commit_type);
 
   if (internal_data->must_reset_scroll_and_scale_state()) {
-    render_view_->GetWebView()->ResetScrollAndScaleState();
+    GetWebView()->ResetScrollAndScaleState();
     internal_data->set_must_reset_scroll_and_scale_state(false);
   }
   if (!frame_->Parent()) {  // Only for top frames.
@@ -4576,13 +4588,13 @@
   // existing autoplay flags on the Page. This is because flags are stored at
   // the page level so subframes would only add to them.
   if (!frame_->Parent() && !navigation_state->WasWithinSameDocument()) {
-    render_view_->GetWebView()->ClearAutoplayFlags();
+    GetWebView()->ClearAutoplayFlags();
   }
 
   // Set the correct autoplay flags on the Page and wipe the cached origin so
   // this will not be used incorrectly.
   if (url::Origin(frame_->GetSecurityOrigin()) == autoplay_flags_.first) {
-    render_view_->GetWebView()->AddAutoplayFlags(autoplay_flags_.second);
+    GetWebView()->AddAutoplayFlags(autoplay_flags_.second);
     autoplay_flags_.first = url::Origin();
   }
 }
@@ -4639,8 +4651,7 @@
   GetContentClient()->SetActiveURL(
       url, frame_->Top()->GetSecurityOrigin().ToString().Utf8());
 
-  DCHECK(render_view_->GetWebView());
-  render_view_->GetWebView()->SetHistoryListFromNavigation(
+  GetWebView()->SetHistoryListFromNavigation(
       commit_params.current_history_list_offset,
       commit_params.current_history_list_length);
 }
@@ -4742,7 +4753,7 @@
     // The WebFrame being swapped in here has now been attached to the Page as
     // its main frame, and the WebFrameWidget was previously initialized when
     // the frame was created so we can call WebView's DidAttachLocalMainFrame().
-    render_view_->GetWebView()->DidAttachLocalMainFrame();
+    GetWebView()->DidAttachLocalMainFrame();
   }
 
   return true;
@@ -4846,7 +4857,8 @@
 
   // If the browser is interested, then give it a chance to look at the request.
   if (IsTopLevelNavigation(frame_) &&
-      render_view_->GetRendererPreferences()
+      GetWebView()
+          ->GetRendererPreferences()
           .browser_handles_all_top_level_requests) {
     OpenURL(std::move(info));
     return;  // Suppress the load here.
@@ -5119,8 +5131,8 @@
   params->blob_url_token = CloneBlobURLToken(info->blob_url_token);
   params->should_replace_current_entry =
       info->frame_load_type == WebFrameLoadType::kReplaceCurrentItem &&
-      render_view_->GetWebView()->HistoryBackListCount() +
-          render_view_->GetWebView()->HistoryForwardListCount() + 1;
+      GetWebView()->HistoryBackListCount() +
+          GetWebView()->HistoryForwardListCount() + 1;
   params->user_gesture = info->has_transient_user_activation;
   params->initiator_policy_container_keep_alive_handle =
       std::move(info->initiator_policy_container_keep_alive_handle);
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 9074724d..d60ea42 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -337,6 +337,9 @@
   std::unique_ptr<AXTreeSnapshotter> CreateAXTreeSnapshotter() override;
   int GetRoutingID() override;
   blink::WebLocalFrame* GetWebFrame() override;
+  const blink::WebLocalFrame* GetWebFrame() const override;
+  blink::WebView* GetWebView() override;
+  const blink::WebView* GetWebView() const override;
   const blink::web_pref::WebPreferences& GetBlinkPreferences() override;
   void ShowVirtualKeyboard() override;
   blink::WebPlugin* CreatePlugin(const WebPluginInfo& info,
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 57269d6..5bb7fec 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -1166,7 +1166,14 @@
 
   bool support_locking = false;
   bool support_raster_interface = true;
+
+  // Only support OOPR on this context if the general feature is enabled in
+  // addition to OOPR for canvas. Otherwise this context will raster canvas
+  // through Skia's GrContext.
   bool support_oop_rasterization =
+      gpu_channel_host->gpu_feature_info()
+              .status_values[gpu::GPU_FEATURE_TYPE_OOP_RASTERIZATION] ==
+          gpu::kGpuFeatureStatusEnabled &&
       base::FeatureList::IsEnabled(features::kCanvasOopRasterization);
   bool support_gles2_interface = false;
   bool support_grcontext = !support_oop_rasterization;
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 67af0a7a..6115849 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -902,7 +902,8 @@
 }
 
 TEST_F(RenderViewImplTest, BeginNavigationHandlesAllTopLevel) {
-  blink::RendererPreferences prefs = view()->GetRendererPreferences();
+  blink::RendererPreferences prefs =
+      view()->GetWebView()->GetRendererPreferences();
   prefs.browser_handles_all_top_level_requests = true;
   view()->GetWebView()->SetRendererPreferences(prefs);
 
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 37efa41..5d85a561 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -362,7 +362,7 @@
   DCHECK_EQ(GetRoutingID(), creator_frame->render_view()->GetRoutingID());
 
   view_params->window_was_created_with_opener = true;
-  view_params->renderer_preferences = GetRendererPreferences();
+  view_params->renderer_preferences = webview_->GetRendererPreferences();
   view_params->web_preferences = webview_->GetWebPreferences();
   view_params->view_id = reply->route_id;
 
@@ -450,16 +450,6 @@
                               this, &RenderViewImpl::SendFrameStateUpdates);
 }
 
-void RenderViewImpl::RegisterRendererPreferenceWatcher(
-    mojo::PendingRemote<blink::mojom::RendererPreferenceWatcher> watcher) {
-  GetWebView()->RegisterRendererPreferenceWatcher(std::move(watcher));
-}
-
-const blink::RendererPreferences& RenderViewImpl::GetRendererPreferences()
-    const {
-  return webview_->GetRendererPreferences();
-}
-
 void RenderViewImpl::OnPageFrozenChanged(bool frozen) {
   if (frozen) {
     // Make sure browser has the latest info before the page is frozen. If the
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 96fb064..e49feae 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -118,14 +118,6 @@
   // be coalesced into one update.
   void StartNavStateSyncTimerIfNecessary(RenderFrameImpl* frame);
 
-  // Registers a watcher to observe changes in the
-  // blink::RendererPreferences.
-  void RegisterRendererPreferenceWatcher(
-      mojo::PendingRemote<blink::mojom::RendererPreferenceWatcher> watcher);
-
-  // Returns the current instance of blink::RendererPreferences.
-  const blink::RendererPreferences& GetRendererPreferences() const;
-
   // blink::WebViewClient implementation --------------------------------------
 
   blink::WebView* CreateView(
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index d5ebc98..ee6c7ef 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -795,8 +795,15 @@
   attributes.sample_buffers = 0;
   attributes.bind_generates_resource = false;
   attributes.enable_raster_interface = web_attributes.enable_raster_interface;
+
+  // Only support OOPR on this context if the general feature is enabled in
+  // addition to OOPR for canvas. Otherwise this context will raster canvas
+  // through Skia's GrContext.
   attributes.enable_oop_rasterization =
       attributes.enable_raster_interface &&
+      gpu_channel_host->gpu_feature_info()
+              .status_values[gpu::GPU_FEATURE_TYPE_OOP_RASTERIZATION] ==
+          gpu::kGpuFeatureStatusEnabled &&
       base::FeatureList::IsEnabled(features::kCanvasOopRasterization);
   attributes.enable_gles2_interface = !attributes.enable_oop_rasterization;
 
diff --git a/courgette/DIR_METADATA b/courgette/DIR_METADATA
index 0b40f66..fea315d 100644
--- a/courgette/DIR_METADATA
+++ b/courgette/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "EnterpriseRetailMode"
diff --git a/device/fido/cable/v2_constants.h b/device/fido/cable/v2_constants.h
index 91717bc..3a991b8d 100644
--- a/device/fido/cable/v2_constants.h
+++ b/device/fido/cable/v2_constants.h
@@ -48,7 +48,11 @@
 // kPostHandshakeMsgPaddingGranularity is the granularity of the padding added
 // to the post-handshake message. This should be sufficiently large to pad away
 // all information about the contents of this message.
-constexpr size_t kPostHandshakeMsgPaddingGranularity = 512;
+constexpr size_t kPostHandshakeMsgPaddingGranularity = 256;
+// kFuturePostHandshakeMsgPaddingGranularity will be the granularity of the
+// padding added to the post-handshake message. This is currently only used for
+// testing.
+constexpr size_t kFuturePostHandshakeMsgPaddingGranularity = 512;
 
 }  // namespace cablev2
 }  // namespace device
diff --git a/device/fido/cable/v2_handshake.cc b/device/fido/cable/v2_handshake.cc
index 67603aa..2ec2245 100644
--- a/device/fido/cable/v2_handshake.cc
+++ b/device/fido/cable/v2_handshake.cc
@@ -437,13 +437,6 @@
 
 absl::optional<std::vector<uint8_t>> EncodePaddedCBORMap(
     cbor::Value::MapValue map) {
-  // The number of padding bytes is a uint16_t, so the granularity cannot be
-  // larger than that.
-  static_assert(kPostHandshakeMsgPaddingGranularity > 0, "");
-  static_assert(kPostHandshakeMsgPaddingGranularity - 1 <=
-                    std::numeric_limits<uint16_t>::max(),
-                "");
-
   absl::optional<std::vector<uint8_t>> cbor_bytes =
       cbor::Writer::Write(cbor::Value(std::move(map)));
   if (!cbor_bytes) {
@@ -451,30 +444,32 @@
   }
 
   base::CheckedNumeric<size_t> padded_size_checked = cbor_bytes->size();
-  padded_size_checked += sizeof(uint16_t);  // padding-length bytes
-  padded_size_checked =
-      (padded_size_checked + kPostHandshakeMsgPaddingGranularity - 1) &
-      ~(kPostHandshakeMsgPaddingGranularity - 1);
+  padded_size_checked += 1;  // padding-length byte
+  padded_size_checked = (padded_size_checked + 255) & ~255;
   if (!padded_size_checked.IsValid()) {
     return absl::nullopt;
   }
 
   const size_t padded_size = padded_size_checked.ValueOrDie();
-  DCHECK_GE(padded_size, cbor_bytes->size() + sizeof(uint16_t));
-  const size_t extra_bytes = padded_size - cbor_bytes->size();
-  const size_t num_padding_bytes =
-      extra_bytes - sizeof(uint16_t) /* length of padding length */;
+  DCHECK_GT(padded_size, cbor_bytes->size());
+  const size_t extra_padding = padded_size - cbor_bytes->size();
 
   cbor_bytes->resize(padded_size);
-  const uint16_t num_padding_bytes16 =
-      base::checked_cast<uint16_t>(num_padding_bytes);
-  memcpy(&cbor_bytes.value()[padded_size - sizeof(num_padding_bytes16)],
-         &num_padding_bytes16, sizeof(num_padding_bytes16));
+  DCHECK_LE(extra_padding, 256u);
+  cbor_bytes->at(padded_size - 1) = static_cast<uint8_t>(extra_padding - 1);
 
   return *cbor_bytes;
 }
 
-absl::optional<cbor::Value> DecodePaddedCBORMap(
+// DecodePaddedCBORMap16 is the future replacement for |DecodePaddedCBORMap|,
+// below. It parses a slightly different format that allows for more padding.
+// (This is needed because some structures ended up larger than initially
+// expected.) In order to transition, |DecodePaddedCBORMap| calls this function
+// if it fails to parse. In the future we can drop supporting the old format and
+// start sending new-format messages. This works because CBOR parsing doesn't
+// depend on the length of the input (other than to fail on truncation) so
+// there's no ambiguity about the parse.
+absl::optional<cbor::Value> DecodePaddedCBORMap16(
     base::span<const uint8_t> input) {
   if (input.size() < sizeof(uint16_t)) {
     return absl::nullopt;
@@ -499,6 +494,28 @@
   return payload;
 }
 
+absl::optional<cbor::Value> DecodePaddedCBORMap(
+    const base::span<const uint8_t> input) {
+  // TODO: replace this with the body of |DecodePaddedCBORMap16| once M92 is
+  // everywhere.
+  if (input.empty()) {
+    return absl::nullopt;
+  }
+
+  const size_t padding_length = input.back();
+  if (padding_length + 1 > input.size()) {
+    return absl::nullopt;
+  }
+  auto unpadded_input = input.subspan(0, input.size() - padding_length - 1);
+
+  absl::optional<cbor::Value> payload = cbor::Reader::Read(unpadded_input);
+  if (!payload || !payload->is_map()) {
+    return DecodePaddedCBORMap16(input);
+  }
+
+  return payload;
+}
+
 Crypter::Crypter(base::span<const uint8_t, 32> read_key,
                  base::span<const uint8_t, 32> write_key)
     : read_key_(fido_parsing_utils::Materialize(read_key)),
diff --git a/device/fido/cable/v2_handshake_unittest.cc b/device/fido/cable/v2_handshake_unittest.cc
index b99cd8c..c2c633a 100644
--- a/device/fido/cable/v2_handshake_unittest.cc
+++ b/device/fido/cable/v2_handshake_unittest.cc
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 #include "device/fido/cable/v2_handshake.h"
+#include "components/cbor/reader.h"
 #include "components/cbor/values.h"
+#include "components/cbor/writer.h"
 #include "crypto/random.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/boringssl/src/include/openssl/ec.h"
@@ -97,6 +99,69 @@
   EXPECT_EQ(1u, decoded->GetMap().size());
 }
 
+// FutureEncodePaddedCBORMapFunction is the future replacement for
+// |EncodePaddedCBORMap|. See comment on |DecodePaddedCBORMap16|.
+absl::optional<std::vector<uint8_t>> FutureEncodePaddedCBORMapFunction(
+    cbor::Value::MapValue map) {
+  // TODO: when promoting this function, update comment on
+  // |kPostHandshakeMsgPaddingGranularity|.
+
+  // The number of padding bytes is a uint16_t, so the granularity cannot be
+  // larger than that.
+  static_assert(kFuturePostHandshakeMsgPaddingGranularity > 0, "");
+  static_assert(kFuturePostHandshakeMsgPaddingGranularity - 1 <=
+                    std::numeric_limits<uint16_t>::max(),
+                "");
+
+  absl::optional<std::vector<uint8_t>> cbor_bytes =
+      cbor::Writer::Write(cbor::Value(std::move(map)));
+  if (!cbor_bytes) {
+    return absl::nullopt;
+  }
+
+  base::CheckedNumeric<size_t> padded_size_checked = cbor_bytes->size();
+  padded_size_checked += sizeof(uint16_t);  // padding-length bytes
+  padded_size_checked =
+      (padded_size_checked + kFuturePostHandshakeMsgPaddingGranularity - 1) &
+      ~(kFuturePostHandshakeMsgPaddingGranularity - 1);
+  if (!padded_size_checked.IsValid()) {
+    return absl::nullopt;
+  }
+
+  const size_t padded_size = padded_size_checked.ValueOrDie();
+  DCHECK_GE(padded_size, cbor_bytes->size() + sizeof(uint16_t));
+  const size_t extra_bytes = padded_size - cbor_bytes->size();
+  const size_t num_padding_bytes =
+      extra_bytes - sizeof(uint16_t) /* length of padding length */;
+
+  cbor_bytes->resize(padded_size);
+  const uint16_t num_padding_bytes16 =
+      base::checked_cast<uint16_t>(num_padding_bytes);
+  memcpy(&cbor_bytes.value()[padded_size - sizeof(num_padding_bytes16)],
+         &num_padding_bytes16, sizeof(num_padding_bytes16));
+
+  return *cbor_bytes;
+}
+
+TEST(CableV2Encoding, FuturePaddedCBOR) {
+  // Test that we can decode messages padded by the encoding function that
+  // will be used in the future.
+  for (size_t i = 0; i < 512; i++) {
+    SCOPED_TRACE(i);
+
+    // Check that new->old direction works.
+    const std::vector<uint8_t> dummy_array(i);
+    cbor::Value::MapValue map;
+    map.emplace(1, dummy_array);
+    absl::optional<std::vector<uint8_t>> encoded =
+        FutureEncodePaddedCBORMapFunction(std::move(map));
+    ASSERT_TRUE(encoded);
+
+    absl::optional<cbor::Value> decoded = DecodePaddedCBORMap(*encoded);
+    ASSERT_TRUE(decoded);
+  }
+}
+
 std::array<uint8_t, kP256X962Length> PublicKeyOf(const EC_KEY* private_key) {
   std::array<uint8_t, kP256X962Length> ret;
   CHECK_EQ(ret.size(),
diff --git a/device/fido/fake_fido_discovery.cc b/device/fido/fake_fido_discovery.cc
index ff909ff..3fe4ada 100644
--- a/device/fido/fake_fido_discovery.cc
+++ b/device/fido/fake_fido_discovery.cc
@@ -31,11 +31,6 @@
   NotifyDiscoveryStarted(success);
 }
 
-void FakeFidoDiscovery::WaitForCallToStartAndSimulateSuccess() {
-  WaitForCallToStart();
-  SimulateStarted(true /* success */);
-}
-
 void FakeFidoDiscovery::StartInternal() {
   wait_for_start_loop_.Quit();
 
diff --git a/device/fido/fake_fido_discovery.h b/device/fido/fake_fido_discovery.h
index ee5aa1f..29878b3 100644
--- a/device/fido/fake_fido_discovery.h
+++ b/device/fido/fake_fido_discovery.h
@@ -71,9 +71,6 @@
   // Simulates the discovery actually starting.
   void SimulateStarted(bool success);
 
-  // Combines WaitForCallToStart + SimulateStarted(true).
-  void WaitForCallToStartAndSimulateSuccess();
-
   // Tests are to directly call Add/RemoveDevice to simulate adding/removing
   // devices. Observers are automatically notified.
   using FidoDeviceDiscovery::AddDevice;
@@ -103,12 +100,14 @@
   //
   // It is an error not to call the relevant method prior to a call to
   // FidoDeviceDiscovery::Create with the respective transport.
-  FakeFidoDiscovery* ForgeNextHidDiscovery(StartMode mode = StartMode::kManual);
-  FakeFidoDiscovery* ForgeNextNfcDiscovery(StartMode mode = StartMode::kManual);
+  FakeFidoDiscovery* ForgeNextHidDiscovery(
+      StartMode mode = StartMode::kAutomatic);
+  FakeFidoDiscovery* ForgeNextNfcDiscovery(
+      StartMode mode = StartMode::kAutomatic);
   FakeFidoDiscovery* ForgeNextCableDiscovery(
-      StartMode mode = StartMode::kManual);
+      StartMode mode = StartMode::kAutomatic);
   FakeFidoDiscovery* ForgeNextPlatformDiscovery(
-      StartMode mode = StartMode::kManual);
+      StartMode mode = StartMode::kAutomatic);
 
   // device::FidoDiscoveryFactory:
   std::vector<std::unique_ptr<FidoDiscoveryBase>> Create(
diff --git a/device/fido/fake_fido_discovery_unittest.cc b/device/fido/fake_fido_discovery_unittest.cc
index cc29172..69cebb9 100644
--- a/device/fido/fake_fido_discovery_unittest.cc
+++ b/device/fido/fake_fido_discovery_unittest.cc
@@ -65,7 +65,8 @@
 
   EXPECT_CALL(observer, DiscoveryStarted(&discovery, true,
                                          std::vector<FidoAuthenticator*>()));
-  discovery.WaitForCallToStartAndSimulateSuccess();
+  discovery.WaitForCallToStart();
+  discovery.SimulateStarted(true);
   ASSERT_TRUE(discovery.is_running());
   ASSERT_TRUE(discovery.is_start_requested());
 }
diff --git a/device/fido/fido_request_handler_base.cc b/device/fido/fido_request_handler_base.cc
index 67aa095..c489f00 100644
--- a/device/fido/fido_request_handler_base.cc
+++ b/device/fido/fido_request_handler_base.cc
@@ -50,9 +50,9 @@
   // signal that they have started.
   unsigned num_discoveries_pending = 0;
 
-  bool CanMakeCallback() const {
-    return !callback_made && !ble_information_pending &&
-           !platform_credential_check_pending && num_discoveries_pending == 0;
+  bool Ready() const {
+    return !ble_information_pending && !platform_credential_check_pending &&
+           num_discoveries_pending == 0;
   }
 };
 
@@ -168,7 +168,14 @@
 
 void FidoRequestHandlerBase::StartAuthenticatorRequest(
     const std::string& authenticator_id) {
-  InitializeAuthenticatorAndDispatchRequest(authenticator_id);
+  auto authenticator_it = active_authenticators_.find(authenticator_id);
+  if (authenticator_it == active_authenticators_.end()) {
+    return;
+  }
+  FidoAuthenticator* authenticator = authenticator_it->second;
+  authenticator->InitializeAuthenticator(
+      base::BindOnce(&FidoRequestHandlerBase::DispatchOrQueueAuthenticator,
+                     weak_factory_.GetWeakPtr(), authenticator_id));
 }
 
 void FidoRequestHandlerBase::CancelActiveAuthenticators(
@@ -318,18 +325,13 @@
   }
 
   if (!embedder_controls_dispatch) {
-    // Post |InitializeAuthenticatorAndDispatchRequest| into its own task. This
+    // Post |StartAuthenticatorRequest| into its own task. This
     // avoids hairpinning, even if the authenticator immediately invokes the
     // request callback.
-    VLOG(2)
-        << "Request handler dispatching request to authenticator immediately.";
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
-        base::BindOnce(
-            &FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest,
-            GetWeakPtr(), authenticator->GetId()));
-  } else {
-    VLOG(2) << "Embedder controls the dispatch.";
+        base::BindOnce(&FidoRequestHandlerBase::StartAuthenticatorRequest,
+                       GetWeakPtr(), authenticator->GetId()));
   }
 
 #if defined(OS_WIN)
@@ -373,25 +375,40 @@
 }
 
 void FidoRequestHandlerBase::MaybeSignalTransportsEnumerated() {
-  if (!observer_ ||
-      !transport_availability_callback_readiness_->CanMakeCallback()) {
+  if (!transport_availability_callback_readiness_->Ready()) {
     return;
   }
 
-  transport_availability_callback_readiness_->callback_made = true;
-  observer_->OnTransportAvailabilityEnumerated(transport_availability_info_);
+  for (const auto& id : authenticator_ids_queued_for_dispatch_) {
+    auto it = active_authenticators_.find(id);
+    if (it == active_authenticators_.end()) {
+      return;
+    }
+
+    DispatchRequest(it->second);
+  }
+
+  authenticator_ids_queued_for_dispatch_.clear();
+
+  if (observer_ && !transport_availability_callback_readiness_->callback_made) {
+    transport_availability_callback_readiness_->callback_made = true;
+    observer_->OnTransportAvailabilityEnumerated(transport_availability_info_);
+    // This object may have been deleted in the callback.
+  }
 }
 
-void FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest(
-    const std::string& authenticator_id) {
-  auto authenticator_it = active_authenticators_.find(authenticator_id);
-  if (authenticator_it == active_authenticators_.end()) {
+void FidoRequestHandlerBase::DispatchOrQueueAuthenticator(
+    std::string authenticator_id) {
+  auto it = active_authenticators_.find(authenticator_id);
+  if (it == active_authenticators_.end()) {
     return;
   }
-  FidoAuthenticator* authenticator = authenticator_it->second;
-  authenticator->InitializeAuthenticator(
-      base::BindOnce(&FidoRequestHandlerBase::DispatchRequest,
-                     weak_factory_.GetWeakPtr(), authenticator));
+
+  if (transport_availability_callback_readiness_->Ready()) {
+    DispatchRequest(it->second);
+  } else {
+    authenticator_ids_queued_for_dispatch_.emplace(std::move(authenticator_id));
+  }
 }
 
 void FidoRequestHandlerBase::ConstructBleAdapterPowerManager() {
diff --git a/device/fido/fido_request_handler_base.h b/device/fido/fido_request_handler_base.h
index 862d9a4..c080ae3 100644
--- a/device/fido/fido_request_handler_base.h
+++ b/device/fido/fido_request_handler_base.h
@@ -198,8 +198,11 @@
       const base::flat_set<FidoTransportProtocol>& available_transports);
   ~FidoRequestHandlerBase() override;
 
-  // Triggers DispatchRequest() if |active_authenticators_| hold
-  // FidoAuthenticator with given |authenticator_id|.
+  // Invokes |FidoAuthenticator::InitializeAuthenticator|, followed by
+  // either calling |DispatchRequest| or queuing the authenticator until
+  // |TransportAvailabilityInfo| is ready. |InitializeAuthenticator| sends a
+  // GetInfo command to FidoDeviceAuthenticator instances in order to determine
+  // their protocol versions before a request can be dispatched.
   void StartAuthenticatorRequest(const std::string& authenticator_id);
 
   // Invokes |FidoAuthenticator::Cancel| on all authenticators, except if
@@ -302,15 +305,15 @@
 
   void MaybeSignalTransportsEnumerated();
 
-  // Invokes FidoAuthenticator::InitializeAuthenticator(), followed by
-  // DispatchRequest(). InitializeAuthenticator() sends a GetInfo command
-  // to FidoDeviceAuthenticator instances in order to determine their protocol
-  // versions before a request can be dispatched.
-  void InitializeAuthenticatorAndDispatchRequest(
-      const std::string& authenticator_id);
+  // DispatchOrQueueAuthenticator either calls |DispatchRequest| on the
+  // indicated authenticator, starting the full request flow or, if
+  // TransportAvailabilityInfo is not yet ready, queues the authenticator so
+  // that can be done later.
+  void DispatchOrQueueAuthenticator(std::string authenticator_id);
   void ConstructBleAdapterPowerManager();
 
   AuthenticatorMap active_authenticators_;
+  base::flat_set<std::string> authenticator_ids_queued_for_dispatch_;
   std::vector<std::unique_ptr<FidoDiscoveryBase>> discoveries_;
   Observer* observer_ = nullptr;
   TransportAvailabilityInfo transport_availability_info_;
diff --git a/device/fido/fido_request_handler_unittest.cc b/device/fido/fido_request_handler_unittest.cc
index b959aaf..e4f45f43 100644
--- a/device/fido/fido_request_handler_unittest.cc
+++ b/device/fido/fido_request_handler_unittest.cc
@@ -320,7 +320,7 @@
 
 TEST_F(FidoRequestHandlerTest, TestSingleDeviceSuccess) {
   auto request_handler = CreateFakeHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = std::make_unique<MockFidoDevice>();
   device->ExpectCtap2CommandAndRespondWith(
@@ -341,7 +341,7 @@
 // command must be invoked to all connected authenticators.
 TEST_F(FidoRequestHandlerTest, TestAuthenticatorHandlerReset) {
   auto request_handler = CreateFakeHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device0 = std::make_unique<MockFidoDevice>();
   device0->ExpectCtap2CommandAndRespondWith(
@@ -368,7 +368,7 @@
 // from only a single device(device1) and the remaining device hangs.
 TEST_F(FidoRequestHandlerTest, TestRequestWithMultipleDevices) {
   auto request_handler = CreateFakeHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   // Represents a connected device that hangs without a response.
   auto device0 = std::make_unique<MockFidoDevice>();
@@ -401,7 +401,7 @@
 // party, and cancel request should be sent to the other authenticator.
 TEST_F(FidoRequestHandlerTest, TestRequestWithMultipleSuccessResponses) {
   auto request_handler = CreateFakeHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   // Represents a connected device that responds successfully after small time
   // delay.
@@ -443,7 +443,7 @@
 // relying party and cancel command should be sent to the remaining device.
 TEST_F(FidoRequestHandlerTest, TestRequestWithMultipleFailureResponses) {
   auto request_handler = CreateFakeHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   // Represents a connected device that immediately responds with a processing
   // error.
@@ -525,8 +525,8 @@
 
   discovery()->AddDevice(std::move(device0));
   platform_discovery->AddDevice(std::move(device1));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
-  platform_discovery->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
+  platform_discovery->WaitForCallToStart();
 
   task_environment_.FastForwardUntilNoTasksRemain();
   callback().WaitForCallback();
@@ -538,7 +538,7 @@
 TEST_F(FidoRequestHandlerTest,
        TestRequestWithOperationDeniedErrorCrossPlatform) {
   auto request_handler = CreateFakeHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   // Device will send CTAP2_ERR_OPERATION_DENIED.
   auto device0 = MockFidoDevice::MakeCtapWithGetInfoExpectation();
@@ -607,7 +607,7 @@
        TransportAvailabilityNotificationOnObserverSetLate) {
   TestObserver observer;
   auto request_handler = CreateFakeHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   task_environment_.FastForwardUntilNoTasksRemain();
 
   request_handler->set_observer(&observer);
@@ -633,7 +633,7 @@
     // If the windows API is not enabled, the request is dispatched to the USB
     // discovery. Simulate a success to fill the transport availability info.
     if (!api_available)
-      discovery()->WaitForCallToStartAndSimulateSuccess();
+      discovery()->WaitForCallToStart();
 
     task_environment_.FastForwardUntilNoTasksRemain();
 
diff --git a/device/fido/get_assertion_handler_unittest.cc b/device/fido/get_assertion_handler_unittest.cc
index 46037c1..d7e8615 100644
--- a/device/fido/get_assertion_handler_unittest.cc
+++ b/device/fido/get_assertion_handler_unittest.cc
@@ -75,13 +75,6 @@
 #endif
   }
 
-  void ForgeDiscoveries() {
-    discovery_ = fake_discovery_factory_->ForgeNextHidDiscovery();
-    cable_discovery_ = fake_discovery_factory_->ForgeNextCableDiscovery();
-    nfc_discovery_ = fake_discovery_factory_->ForgeNextNfcDiscovery();
-    platform_discovery_ = fake_discovery_factory_->ForgeNextPlatformDiscovery();
-  }
-
   CtapGetAssertionRequest CreateTestRequestWithCableExtension() {
     CtapGetAssertionRequest request(test_data::kRelyingPartyId,
                                     test_data::kClientDataJson);
@@ -110,7 +103,10 @@
 
   std::unique_ptr<GetAssertionRequestHandler>
   CreateGetAssertionHandlerWithRequest(CtapGetAssertionRequest request) {
-    ForgeDiscoveries();
+    discovery_ = fake_discovery_factory_->ForgeNextHidDiscovery();
+    cable_discovery_ = fake_discovery_factory_->ForgeNextCableDiscovery();
+    nfc_discovery_ = fake_discovery_factory_->ForgeNextNfcDiscovery();
+    platform_discovery_ = fake_discovery_factory_->ForgeNextPlatformDiscovery();
 
     auto handler = std::make_unique<GetAssertionRequestHandler>(
         fake_discovery_factory_.get(), supported_transports_,
@@ -124,13 +120,13 @@
       base::flat_set<FidoTransportProtocol> transports) {
     using Transport = FidoTransportProtocol;
     if (base::Contains(transports, Transport::kUsbHumanInterfaceDevice))
-      discovery()->WaitForCallToStartAndSimulateSuccess();
+      discovery()->WaitForCallToStart();
     if (base::Contains(transports, Transport::kCloudAssistedBluetoothLowEnergy))
-      cable_discovery()->WaitForCallToStartAndSimulateSuccess();
+      cable_discovery()->WaitForCallToStart();
     if (base::Contains(transports, Transport::kNearFieldCommunication))
-      nfc_discovery()->WaitForCallToStartAndSimulateSuccess();
+      nfc_discovery()->WaitForCallToStart();
     if (base::Contains(transports, Transport::kInternal))
-      platform_discovery()->WaitForCallToStartAndSimulateSuccess();
+      platform_discovery()->WaitForCallToStart();
 
     task_environment_.FastForwardUntilNoTasksRemain();
     EXPECT_FALSE(get_assertion_callback().was_called());
@@ -208,7 +204,7 @@
 
 TEST_F(FidoGetAssertionHandlerTest, CtapRequestOnSingleDevice) {
   auto request_handler = CreateGetAssertionHandlerCtap();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
   device->ExpectCtap2CommandAndRespondWith(
       CtapRequestCommand::kAuthenticatorGetAssertion,
@@ -224,7 +220,7 @@
 // Test a scenario where the connected authenticator is a U2F device.
 TEST_F(FidoGetAssertionHandlerTest, TestU2fSign) {
   auto request_handler = CreateGetAssertionHandlerU2f();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
   device->ExpectRequestAndRespondWith(
@@ -243,7 +239,7 @@
   request.user_verification = UserVerificationRequirement::kRequired;
   auto request_handler =
       CreateGetAssertionHandlerWithRequest(std::move(request));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestGetInfoResponseWithoutUvSupport);
@@ -269,7 +265,7 @@
   request.user_verification = UserVerificationRequirement::kRequired;
   auto request_handler =
       CreateGetAssertionHandlerWithRequest(std::move(request));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
   device->ExpectRequestAndRespondWith(
@@ -286,7 +282,7 @@
   auto request_handler =
       CreateGetAssertionHandlerWithRequest(CtapGetAssertionRequest(
           test_data::kRelyingPartyId, test_data::kClientDataJson));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
   device->ExpectCtap2CommandAndRespondWith(
       CtapRequestCommand::kAuthenticatorGetAssertion,
@@ -309,7 +305,7 @@
       fido_parsing_utils::Materialize(test_data::kKeyHandleAlpha))};
   auto request_handler =
       CreateGetAssertionHandlerWithRequest(std::move(request));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   // Resident Keys must be disabled, otherwise allow list check is skipped.
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestGetInfoResponseWithoutResidentKeySupport);
@@ -331,7 +327,7 @@
 // the response is returned to the relying party.
 TEST_F(FidoGetAssertionHandlerTest, ValidEmptyCredential) {
   auto request_handler = CreateGetAssertionHandlerCtap();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   // Resident Keys must be disabled, otherwise allow list check is skipped.
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestGetInfoResponseWithoutResidentKeySupport);
@@ -359,7 +355,7 @@
   //
   // [1] https://www.w3.org/TR/webauthn/#sctn-user-credential-params
   auto request_handler = CreateGetAssertionHandlerCtap();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestCtap2OnlyAuthenticatorGetInfoResponse);
   device->ExpectCtap2CommandAndRespondWith(
@@ -381,7 +377,7 @@
   // response, and the UTF-8 string contains invalid code-points that
   // |base::IsStringUTF8| will be unhappy with.
   auto request_handler = CreateGetAssertionHandlerCtap();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestCtap2OnlyAuthenticatorGetInfoResponse);
   device->ExpectCtap2CommandAndRespondWith(
@@ -400,7 +396,7 @@
   auto request_handler =
       CreateGetAssertionHandlerWithRequest(CtapGetAssertionRequest(
           test_data::kRelyingPartyId, test_data::kClientDataJson));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
   device->ExpectCtap2CommandAndRespondWith(
       CtapRequestCommand::kAuthenticatorGetAssertion,
@@ -520,7 +516,7 @@
   device->ExpectCtap2CommandAndRespondWith(
       CtapRequestCommand::kAuthenticatorGetAssertion,
       test_data::kTestGetAssertionResponse);
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::move(device));
 
   get_assertion_callback().WaitForCallback();
@@ -553,7 +549,7 @@
   device->ExpectCtap2CommandAndRespondWith(
       CtapRequestCommand::kAuthenticatorGetAssertion,
       test_data::kTestGetAssertionResponse);
-  nfc_discovery()->WaitForCallToStartAndSimulateSuccess();
+  nfc_discovery()->WaitForCallToStart();
   nfc_discovery()->AddDevice(std::move(device));
 
   get_assertion_callback().WaitForCallback();
@@ -591,7 +587,7 @@
   device->ExpectCtap2CommandAndRespondWith(
       CtapRequestCommand::kAuthenticatorGetAssertion,
       test_data::kTestGetAssertionResponse);
-  platform_discovery()->WaitForCallToStartAndSimulateSuccess();
+  platform_discovery()->WaitForCallToStart();
   platform_discovery()->AddDevice(std::move(device));
 
   get_assertion_callback().WaitForCallback();
@@ -618,7 +614,7 @@
       CtapRequestCommand::kAuthenticatorGetAssertion,
       CtapDeviceResponseCode::kCtap2ErrOperationDenied,
       base::TimeDelta::FromMicroseconds(10));
-  platform_discovery()->WaitForCallToStartAndSimulateSuccess();
+  platform_discovery()->WaitForCallToStart();
   platform_discovery()->AddDevice(std::move(platform_device));
 
   auto other_device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
@@ -626,7 +622,7 @@
       CtapRequestCommand::kAuthenticatorGetAssertion);
   EXPECT_CALL(*other_device, Cancel);
 
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::move(other_device));
 
   task_environment_.FastForwardUntilNoTasksRemain();
@@ -645,7 +641,7 @@
       CtapDeviceResponseCode::kCtap2ErrOperationDenied);
 
   auto request_handler = CreateGetAssertionHandlerCtap();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::move(device));
 
   task_environment_.FastForwardUntilNoTasksRemain();
@@ -663,7 +659,7 @@
       CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid);
 
   auto request_handler = CreateGetAssertionHandlerCtap();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::move(device));
 
   task_environment_.FastForwardUntilNoTasksRemain();
@@ -704,7 +700,7 @@
           ::testing::Return(0)));
 
   auto request_handler = CreateGetAssertionHandlerCtap();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::move(broken_device));
 
   get_assertion_callback().WaitForCallback();
@@ -730,7 +726,7 @@
   request.user_verification = UserVerificationRequirement::kRequired;
   auto request_handler =
       CreateGetAssertionHandlerWithRequest(std::move(request));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::make_unique<VirtualCtap2Device>(
       std::move(state), std::move(config)));
 
diff --git a/device/fido/make_credential_handler_unittest.cc b/device/fido/make_credential_handler_unittest.cc
index 4fe4795a..2f9c411 100644
--- a/device/fido/make_credential_handler_unittest.cc
+++ b/device/fido/make_credential_handler_unittest.cc
@@ -61,15 +61,12 @@
     BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
   }
 
-  void ForgeDiscoveries() {
+  std::unique_ptr<MakeCredentialRequestHandler> CreateMakeCredentialHandler(
+      AuthenticatorSelectionCriteria authenticator_selection_criteria = {}) {
     discovery_ = fake_discovery_factory_->ForgeNextHidDiscovery();
     nfc_discovery_ = fake_discovery_factory_->ForgeNextNfcDiscovery();
     platform_discovery_ = fake_discovery_factory_->ForgeNextPlatformDiscovery();
-  }
 
-  std::unique_ptr<MakeCredentialRequestHandler> CreateMakeCredentialHandler(
-      AuthenticatorSelectionCriteria authenticator_selection_criteria = {}) {
-    ForgeDiscoveries();
     PublicKeyCredentialRpEntity rp(test_data::kRelyingPartyId);
     PublicKeyCredentialUserEntity user(
         fido_parsing_utils::Materialize(test_data::kUserId));
@@ -89,7 +86,7 @@
         std::move(request_parameter), std::move(options), cb_.callback());
     if (pending_mock_platform_device_) {
       platform_discovery_->AddDevice(std::move(pending_mock_platform_device_));
-      platform_discovery_->WaitForCallToStartAndSimulateSuccess();
+      platform_discovery_->WaitForCallToStart();
     }
     return handler;
   }
@@ -99,9 +96,9 @@
       base::flat_set<FidoTransportProtocol> transports) {
     using Transport = FidoTransportProtocol;
     if (base::Contains(transports, Transport::kUsbHumanInterfaceDevice))
-      discovery()->WaitForCallToStartAndSimulateSuccess();
+      discovery()->WaitForCallToStart();
     if (base::Contains(transports, Transport::kNearFieldCommunication))
-      nfc_discovery()->WaitForCallToStartAndSimulateSuccess();
+      nfc_discovery()->WaitForCallToStart();
 
     task_environment_.FastForwardUntilNoTasksRemain();
     EXPECT_FALSE(callback().was_called());
@@ -171,7 +168,7 @@
 
 TEST_F(FidoMakeCredentialHandlerTest, TestCtap2MakeCredential) {
   auto request_handler = CreateMakeCredentialHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
   device->ExpectCtap2CommandAndRespondWith(
@@ -186,7 +183,7 @@
 // Test a scenario where the connected authenticator is a U2F device.
 TEST_F(FidoMakeCredentialHandlerTest, TestU2fRegister) {
   auto request_handler = CreateMakeCredentialHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
   device->ExpectRequestAndRespondWith(
@@ -203,7 +200,7 @@
       CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
           UserVerificationRequirement::kRequired));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
   device->ExpectRequestAndRespondWith(
@@ -221,7 +218,7 @@
       CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
           UserVerificationRequirement::kPreferred));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
   device->ExpectRequestAndRespondWith(
@@ -239,7 +236,7 @@
       CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
           UserVerificationRequirement::kRequired));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestGetInfoResponseWithoutUvSupport);
@@ -297,7 +294,7 @@
       CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
           UserVerificationRequirement::kPreferred));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestGetInfoResponseWithoutResidentKeySupport);
@@ -411,7 +408,7 @@
       CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
           UserVerificationRequirement::kRequired));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device1 = MockFidoDevice::MakeCtapWithGetInfoExpectation();
   auto device2 = MockFidoDevice::MakeCtapWithGetInfoExpectation();
@@ -462,7 +459,7 @@
           DoAll(WithoutArgs(Invoke(delete_request_handler)), Return(token)));
   EXPECT_CALL(*device, Cancel(token));
 
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::move(device));
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(request_handler);
@@ -475,7 +472,7 @@
       AuthenticatorSelectionCriteria(AuthenticatorAttachment::kCrossPlatform,
                                      ResidentKeyRequirement::kRequired,
                                      UserVerificationRequirement::kRequired));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
   device->ExpectCtap2CommandAndRespondWith(
@@ -528,7 +525,7 @@
       AuthenticatorSelectionCriteria(AuthenticatorAttachment::kCrossPlatform,
                                      ResidentKeyRequirement::kDiscouraged,
                                      UserVerificationRequirement::kPreferred));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestGetInfoResponsePlatformDevice);
@@ -591,7 +588,7 @@
       CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
           UserVerificationRequirement::kPreferred));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
   device->ExpectCtap2CommandAndRespondWith(
@@ -619,7 +616,7 @@
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
           UserVerificationRequirement::kPreferred));
 
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::make_unique<VirtualCtap2Device>(
       std::move(state), std::move(config)));
 
@@ -638,7 +635,7 @@
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
           UserVerificationRequirement::kPreferred));
 
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::move(device));
 
   task_environment_.FastForwardUntilNoTasksRemain();
@@ -683,7 +680,7 @@
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
           UserVerificationRequirement::kPreferred));
 
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::move(device));
 
   task_environment_.FastForwardUntilNoTasksRemain();
@@ -712,7 +709,7 @@
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
           UserVerificationRequirement::kDiscouraged));
   discovery()->AddDevice(std::move(device));
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
 
   callback().WaitForCallback();
   EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status());
@@ -731,7 +728,7 @@
           AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
           UserVerificationRequirement::kPreferred));
 
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::move(device));
 
   task_environment_.FastForwardUntilNoTasksRemain();
@@ -771,7 +768,7 @@
           ::testing::Return(0)));
 
   auto request_handler = CreateMakeCredentialHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::move(broken_device));
 
   callback().WaitForCallback();
@@ -789,7 +786,7 @@
   state->fingerprints_enrolled = true;
 
   auto request_handler = CreateMakeCredentialHandler();
-  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->WaitForCallToStart();
   discovery()->AddDevice(std::make_unique<VirtualCtap2Device>(
       std::move(state), std::move(config)));
 
diff --git a/extensions/browser/api/storage/storage_api.cc b/extensions/browser/api/storage/storage_api.cc
index 76232713..88a6ca9e 100644
--- a/extensions/browser/api/storage/storage_api.cc
+++ b/extensions/browser/api/storage/storage_api.cc
@@ -14,11 +14,15 @@
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/trace_event.h"
 #include "base/values.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/storage/session_storage_manager.h"
 #include "extensions/browser/api/storage/storage_frontend.h"
+#include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/quota_service.h"
 #include "extensions/common/api/storage.h"
+#include "extensions/common/features/feature_channel.h"
 
 namespace extensions {
 
@@ -26,6 +30,9 @@
 
 namespace {
 
+constexpr char kSessionStorageManagerKeyName[] =
+    "StorageAPI SessionStorageManager";
+
 // Adds all StringValues from a ListValue to a vector of strings.
 void AddAllStringValues(const base::ListValue& from,
                         std::vector<std::string>* to) {
@@ -38,6 +45,19 @@
   }
 }
 
+// Returns a vector of any strings within the given list.
+std::vector<std::string> GetKeysFromList(const base::Value& list) {
+  DCHECK(list.is_list());
+  std::vector<std::string> keys;
+  keys.reserve(list.GetList().size());
+  for (const auto& value : list.GetList()) {
+    auto* as_string = value.GetIfString();
+    if (as_string)
+      keys.push_back(*as_string);
+  }
+  return keys;
+}
+
 // Gets the keys of a DictionaryValue.
 std::vector<std::string> GetKeys(const base::DictionaryValue& dict) {
   std::vector<std::string> keys;
@@ -47,6 +67,26 @@
   return keys;
 }
 
+// Returns a vector of keys within the given dict.
+std::vector<std::string> GetKeysFromDict(const base::Value& dict) {
+  DCHECK(dict.is_dict());
+  std::vector<std::string> keys;
+  keys.reserve(dict.DictSize());
+  for (const auto& value : dict.DictItems()) {
+    keys.push_back(value.first);
+  }
+  return keys;
+}
+
+// Converts a map to a Value::Type::DICTIONARY.
+base::Value MapAsValueDict(
+    const std::map<std::string, const base::Value*>& values) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  for (const auto& value : values)
+    dict.SetKey(value.first, value.second->Clone());
+  return dict;
+}
+
 // Creates quota heuristics for settings modification.
 void GetModificationQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) {
   // See storage.json for the current value of these limits.
@@ -66,6 +106,27 @@
       "MAX_WRITE_OPERATIONS_PER_HOUR"));
 }
 
+// Creates the SessionStorageManager if it doesn't exist and returns it.
+SessionStorageManager* GetOrCreateSessionStorage(
+    content::BrowserContext* context) {
+  // Share storage between incognito and on-the-record profiles by using the
+  // original context of an incognito window.
+  content::BrowserContext* original_context =
+      ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+
+  SessionStorageManager* storage = static_cast<SessionStorageManager*>(
+      original_context->GetUserData(kSessionStorageManagerKeyName));
+  if (storage)
+    return storage;
+
+  auto session_manager_ptr = std::make_unique<SessionStorageManager>(
+      api::storage::session::QUOTA_BYTES);
+  auto* session_manager = session_manager_ptr.get();
+  original_context->SetUserData(kSessionStorageManagerKeyName,
+                                std::move(session_manager_ptr));
+  return session_manager;
+}
+
 }  // namespace
 
 // SettingsFunction
@@ -92,13 +153,20 @@
   args_->Remove(0, nullptr);
   storage_area_ = StorageAreaFromString(storage_area_string);
   EXTENSION_FUNCTION_VALIDATE(storage_area_ != StorageAreaNamespace::kInvalid);
+
+  // Session is the only storage area that does not use ValueStore, and will
+  // return synchronously.
+  if (storage_area_ == StorageAreaNamespace::kSession) {
+    // TODO(crbug.com/1185226): Get observers to dispatch OnChanged event after
+    // creating OnChangedEventSession in the observer.
+    return RespondNow(RunInSession());
+  }
+
+  // All other StorageAreas use ValueStore with settings_namespace, and will
+  // return asynchronously if successful.
   settings_namespace_ = StorageAreaToSettingsNamespace(storage_area_);
-  // TODO(emiliapaz): Currently we only have sync, local and managed implemented
-  // in this function. This check will change after we introduce `session`.
-  EXTENSION_FUNCTION_VALIDATE(
-      settings_namespace_ == settings_namespace::SYNC ||
-      settings_namespace_ == settings_namespace::LOCAL ||
-      settings_namespace_ == settings_namespace::MANAGED);
+  EXTENSION_FUNCTION_VALIDATE(settings_namespace_ !=
+                              settings_namespace::INVALID);
 
   if (extension()->is_login_screen_extension() &&
       storage_area_ != StorageAreaNamespace::kManaged) {
@@ -177,6 +245,8 @@
     }
 
     case base::Value::Type::LIST: {
+      // TODO(crbug.com/1200931): Replace with GetKeysFromList() and delete
+      // AddAllStringValues().
       std::vector<std::string> as_string_list;
       AddAllStringValues(*static_cast<base::ListValue*>(input),
                          &as_string_list);
@@ -184,6 +254,8 @@
     }
 
     case base::Value::Type::DICTIONARY: {
+      // TODO(crbug.com/1200931): Replace with GetKeysFromDict() and delete
+      // GetKeys().
       base::DictionaryValue* as_dict =
           static_cast<base::DictionaryValue*>(input);
       ValueStore::ReadResult result = storage->Get(GetKeys(*as_dict));
@@ -203,6 +275,50 @@
   }
 }
 
+ExtensionFunction::ResponseValue StorageStorageAreaGetFunction::RunInSession() {
+  base::Value* input = nullptr;
+  if (!args_->Get(0, &input))
+    return BadMessage();
+
+  base::Value value_dict(base::Value::Type::DICTIONARY);
+  SessionStorageManager* session_manager =
+      GetOrCreateSessionStorage(browser_context());
+
+  switch (input->type()) {
+    case base::Value::Type::NONE:
+      value_dict = MapAsValueDict(session_manager->GetAll(extension_id()));
+      break;
+
+    case base::Value::Type::STRING:
+      value_dict = MapAsValueDict(session_manager->Get(
+          extension_id(), std::vector<std::string>(1, input->GetString())));
+      break;
+
+    case base::Value::Type::LIST:
+      value_dict = MapAsValueDict(
+          session_manager->Get(extension_id(), GetKeysFromList(*input)));
+      break;
+
+    case base::Value::Type::DICTIONARY: {
+      std::map<std::string, const base::Value*> values =
+          session_manager->Get(extension_id(), GetKeysFromDict(*input));
+
+      for (auto default_value : input->DictItems()) {
+        auto value_it = values.find(default_value.first);
+        value_dict.SetKey(default_value.first,
+                          value_it != values.end()
+                              ? value_it->second->Clone()
+                              : std::move(default_value.second));
+      }
+      break;
+    }
+    default:
+      return BadMessage();
+  }
+
+  return OneArgument(std::move(value_dict));
+}
+
 ExtensionFunction::ResponseValue
 StorageStorageAreaGetBytesInUseFunction::RunWithStorage(ValueStore* storage) {
   TRACE_EVENT1("browser",
@@ -242,6 +358,13 @@
   return OneArgument(base::Value(static_cast<int>(bytes_in_use)));
 }
 
+ExtensionFunction::ResponseValue
+StorageStorageAreaGetBytesInUseFunction::RunInSession() {
+  // TODO(crbug.com/1185226): Implement RunInSession for
+  // chrome.storage.session.getBytesInUse .
+  return NoArguments();
+}
+
 ExtensionFunction::ResponseValue StorageStorageAreaSetFunction::RunWithStorage(
     ValueStore* storage) {
   TRACE_EVENT1("browser", "StorageStorageAreaSetFunction::RunWithStorage",
@@ -252,6 +375,37 @@
   return UseWriteResult(storage->Set(ValueStore::DEFAULTS, *input));
 }
 
+ExtensionFunction::ResponseValue StorageStorageAreaSetFunction::RunInSession() {
+  // Retrieve and delete input from `args_` since they will be moved to storage.
+  auto list = args_->GetList();
+  if (list.empty() || !list[0].is_dict())
+    return BadMessage();
+  base::Value input = std::move(list[0]);
+  args_->EraseListIter(list.begin());
+
+  std::map<std::string, base::Value> values;
+  for (auto item : input.DictItems()) {
+    values.emplace(std::move(item.first), std::move(item.second));
+  }
+
+  std::vector<SessionStorageManager::ValueChange> changes;
+  bool result = GetOrCreateSessionStorage(browser_context())
+                    ->Set(extension_id(), std::move(values), changes);
+
+  if (!result) {
+    // TODO(crbug.com/1185226): Add API test that triggers this behavior.
+    return Error(
+        "Session storage quota bytes exceeded. Values were not stored.");
+  }
+
+  if (!changes.empty()) {
+    // TODO(crbug.com/1185226): Notify changes after creating
+    // OnChangedEventSession in the observer.
+  }
+
+  return NoArguments();
+}
+
 void StorageStorageAreaSetFunction::GetQuotaLimitHeuristics(
     QuotaLimitHeuristics* heuristics) const {
   GetModificationQuotaLimitHeuristics(heuristics);
@@ -284,6 +438,13 @@
   }
 }
 
+ExtensionFunction::ResponseValue
+StorageStorageAreaRemoveFunction::RunInSession() {
+  // TODO(crbug.com/1185226): Implement RunInSession for
+  // chrome.storage.session.remove .
+  return NoArguments();
+}
+
 void StorageStorageAreaRemoveFunction::GetQuotaLimitHeuristics(
     QuotaLimitHeuristics* heuristics) const {
   GetModificationQuotaLimitHeuristics(heuristics);
@@ -296,6 +457,13 @@
   return UseWriteResult(storage->Clear());
 }
 
+ExtensionFunction::ResponseValue
+StorageStorageAreaClearFunction::RunInSession() {
+  // TODO(crbug.com/1185226): Implement RunInSession for
+  // chrome.storage.session.clear .
+  return NoArguments();
+}
+
 void StorageStorageAreaClearFunction::GetQuotaLimitHeuristics(
     QuotaLimitHeuristics* heuristics) const {
   GetModificationQuotaLimitHeuristics(heuristics);
diff --git a/extensions/browser/api/storage/storage_api.h b/extensions/browser/api/storage/storage_api.h
index d5e3d33..3259796 100644
--- a/extensions/browser/api/storage/storage_api.h
+++ b/extensions/browser/api/storage/storage_api.h
@@ -29,6 +29,10 @@
   // The StorageFrontend makes sure this is posted to the appropriate thread.
   virtual ResponseValue RunWithStorage(ValueStore* storage) = 0;
 
+  // Extension settings function implementations in `session` namespace should
+  // do their work here.
+  virtual ResponseValue RunInSession() = 0;
+
   // Convert the |result| of a read function to the appropriate response value.
   // - If the |result| succeeded this will return a response object argument.
   // - If the |result| failed will return an error object.
@@ -67,6 +71,7 @@
 
   // SettingsFunction:
   ResponseValue RunWithStorage(ValueStore* storage) override;
+  ResponseValue RunInSession() override;
 };
 
 class StorageStorageAreaSetFunction : public SettingsFunction {
@@ -78,6 +83,7 @@
 
   // SettingsFunction:
   ResponseValue RunWithStorage(ValueStore* storage) override;
+  ResponseValue RunInSession() override;
 
   // ExtensionFunction:
   void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const override;
@@ -92,6 +98,7 @@
 
   // SettingsFunction:
   ResponseValue RunWithStorage(ValueStore* storage) override;
+  ResponseValue RunInSession() override;
 
   // ExtensionFunction:
   void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const override;
@@ -106,6 +113,7 @@
 
   // SettingsFunction:
   ResponseValue RunWithStorage(ValueStore* storage) override;
+  ResponseValue RunInSession() override;
 
   // ExtensionFunction:
   void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const override;
@@ -120,6 +128,7 @@
 
   // SettingsFunction:
   ResponseValue RunWithStorage(ValueStore* storage) override;
+  ResponseValue RunInSession() override;
 };
 
 }  // namespace extensions
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index 688f60e..aa91cd5 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -556,6 +556,10 @@
     "dependencies": ["permission:storage"],
     "contexts": ["blessed_extension", "unblessed_extension", "content_script"]
   },
+  "storage.session": {
+    "channel": "canary"
+    // TODO(crbug.com/1185226): Restrict to Manifest V3.
+  },
   "system.cpu": {
     "dependencies": ["permission:system.cpu"],
     "contexts": ["blessed_extension"]
diff --git a/extensions/common/api/storage.json b/extensions/common/api/storage.json
index 2f81bdf..6721d18 100644
--- a/extensions/common/api/storage.json
+++ b/extensions/common/api/storage.json
@@ -231,6 +231,18 @@
         "$ref": "StorageArea",
         "description": "Items in the <code>managed</code> storage area are set by the domain administrator, and are read-only for the extension; trying to modify this namespace results in an error.",
         "value": [ "managed" ]
+      },
+      "session" : {
+        "$ref": "StorageArea",
+        "description": "Items in the <code>session</code> storage area are stored in-memory and will not be persisted to disk.",
+        "nodoc": true,
+        "value": ["session"],
+        "properties": {
+          "QUOTA_BYTES": {
+            "value": 1048576,
+            "description": "The maximum amount (in bytes) of data that can be stored in memory, as measured by estimating the dynamically allocated memory usage of every value and key. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
+          }
+        }
       }
     }
   }
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index a9e1b277..42b4360 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -23,6 +23,12 @@
     "DisablePolicyViolationExtensionsRemotely",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Controls whether we disable extensions that are marked as potentially
+// unwanted by the Omaha attribute.
+const base::Feature kDisablePotentiallyUwsExtensionsRemotely{
+    "DisablePotentiallyUwsExtensionsRemotely",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls whether we show an install friction dialog when an Enhanced Safe
 // Browsing user tries to install an extension that is not included in the
 // Safe Browsing CRX allowlist. This feature also controls if we show a warning
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h
index baa0c07..3d4c1ea 100644
--- a/extensions/common/extension_features.h
+++ b/extensions/common/extension_features.h
@@ -11,6 +11,7 @@
 
 extern const base::Feature kDisableMalwareExtensionsRemotely;
 extern const base::Feature kDisablePolicyViolationExtensionsRemotely;
+extern const base::Feature kDisablePotentiallyUwsExtensionsRemotely;
 extern const base::Feature kSafeBrowsingCrxAllowlistShowWarnings;
 extern const base::Feature kSafeBrowsingCrxAllowlistAutoDisable;
 
diff --git a/extensions/renderer/storage_area.cc b/extensions/renderer/storage_area.cc
index 5c7a39d..ccaffa4 100644
--- a/extensions/renderer/storage_area.cc
+++ b/extensions/renderer/storage_area.cc
@@ -169,6 +169,43 @@
 
 gin::WrapperInfo ManagedStorageArea::kWrapperInfo = {gin::kEmbedderNativeGin};
 
+class SessionStorageArea final : public gin::Wrappable<SessionStorageArea> {
+ public:
+  SessionStorageArea(APIRequestHandler* request_handler,
+                     APIEventHandler* event_handler,
+                     const APITypeReferenceMap* type_refs,
+                     const BindingAccessChecker* access_checker)
+      : storage_area_(request_handler,
+                      event_handler,
+                      type_refs,
+                      "session",
+                      access_checker) {}
+  ~SessionStorageArea() override = default;
+
+  static gin::WrapperInfo kWrapperInfo;
+
+  gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+      v8::Isolate* isolate) override {
+    return Wrappable<SessionStorageArea>::GetObjectTemplateBuilder(isolate)
+        .SetMethod("get", &SessionStorageArea::Get)
+        .SetMethod("set", &SessionStorageArea::Set)
+        .SetMethod("remove", &SessionStorageArea::Remove)
+        .SetMethod("clear", &SessionStorageArea::Clear)
+        .SetMethod("getBytesInUse", &SessionStorageArea::GetBytesInUse)
+        .SetProperty("onChanged", &SessionStorageArea::GetOnChangedEvent)
+        .SetValue("QUOTA_BYTES", api::storage::session::QUOTA_BYTES);
+  }
+
+ private:
+  DEFINE_STORAGE_AREA_HANDLERS()
+
+  StorageArea storage_area_;
+
+  DISALLOW_COPY_AND_ASSIGN(SessionStorageArea);
+};
+
+gin::WrapperInfo SessionStorageArea::kWrapperInfo = {gin::kEmbedderNativeGin};
+
 #undef DEFINE_STORAGE_AREA_HANDLERS
 
 }  // namespace
@@ -205,6 +242,11 @@
         isolate, new SyncStorageArea(request_handler, event_handler, type_refs,
                                      access_checker));
     object = handle.ToV8().As<v8::Object>();
+  } else if (property_name == "session") {
+    gin::Handle<SessionStorageArea> handle = gin::CreateHandle(
+        isolate, new SessionStorageArea(request_handler, event_handler,
+                                        type_refs, access_checker));
+    object = handle.ToV8().As<v8::Object>();
   } else {
     CHECK_EQ("managed", property_name);
     gin::Handle<ManagedStorageArea> handle = gin::CreateHandle(
diff --git a/fuchsia/cast_streaming/DEPS b/fuchsia/cast_streaming/DEPS
deleted file mode 100644
index 386b6182..0000000
--- a/fuchsia/cast_streaming/DEPS
+++ /dev/null
@@ -1,9 +0,0 @@
-include_rules = [
-  "+components/cast/message_port",
-  "+components/openscreen_platform",
-  "+media/base",
-  "+media/mojo",
-  "+mojo/core",
-  "+mojo/public",
-  "+third_party/openscreen/src",
-]
diff --git a/fuchsia/cast_streaming/cast_message_port_impl.cc b/fuchsia/cast_streaming/cast_message_port_impl.cc
deleted file mode 100644
index 97255129..0000000
--- a/fuchsia/cast_streaming/cast_message_port_impl.cc
+++ /dev/null
@@ -1,167 +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.
-
-#include "fuchsia/cast_streaming/cast_message_port_impl.h"
-
-#include "base/json/json_reader.h"
-#include "base/json/json_writer.h"
-#include "base/logging.h"
-#include "base/values.h"
-#include "fuchsia/cast_streaming/message_serialization.h"
-#include "third_party/openscreen/src/platform/base/error.h"
-
-namespace cast_streaming {
-
-CastMessagePortImpl::CastMessagePortImpl(
-    std::unique_ptr<cast_api_bindings::MessagePort> message_port,
-    base::OnceClosure on_close)
-    : message_port_(std::move(message_port)), on_close_(std::move(on_close)) {
-  DVLOG(1) << __func__;
-  message_port_->SetReceiver(this);
-
-  // Initialize the connection with the Cast Streaming Sender.
-  PostMessage(kValueSystemSenderId, kSystemNamespace, kInitialConnectMessage);
-}
-
-CastMessagePortImpl::~CastMessagePortImpl() = default;
-
-void CastMessagePortImpl::MaybeClose() {
-  if (message_port_) {
-    message_port_.reset();
-  }
-  if (client_) {
-    client_->OnError(
-        openscreen::Error(openscreen::Error::Code::kCastV2CastSocketError));
-  }
-  if (on_close_) {
-    // |this| might be deleted as part of |on_close_| being run. Do not add any
-    // code after running the closure.
-    std::move(on_close_).Run();
-  }
-}
-
-void CastMessagePortImpl::SetClient(
-    openscreen::cast::MessagePort::Client* client,
-    std::string client_sender_id) {
-  DVLOG(2) << __func__;
-  DCHECK_NE(!client_, !client);
-  client_ = client;
-  if (!client_)
-    MaybeClose();
-}
-
-void CastMessagePortImpl::ResetClient() {
-  client_ = nullptr;
-  MaybeClose();
-}
-
-void CastMessagePortImpl::SendInjectResponse(const std::string& sender_id,
-                                             const std::string& message) {
-  absl::optional<base::Value> value = base::JSONReader::Read(message);
-  if (!value) {
-    LOG(ERROR) << "Malformed message from sender " << sender_id
-               << ": not a json payload:" << message;
-    return;
-  }
-
-  if (!value->is_dict()) {
-    LOG(ERROR) << "Malformed message from sender " << sender_id
-               << ": non-dictionary json payload: " << message;
-    return;
-  }
-
-  const std::string* type = value->FindStringKey(kKeyType);
-  if (!type) {
-    LOG(ERROR) << "Malformed message from sender " << sender_id
-               << ": no message type: " << message;
-    return;
-  }
-  if (*type != kValueWrapped) {
-    LOG(ERROR) << "Malformed message from sender " << sender_id
-               << ": unknown message type: " << *type;
-    return;
-  }
-
-  absl::optional<int> request_id = value->FindIntKey(kKeyRequestId);
-  if (!request_id) {
-    LOG(ERROR) << "Malformed message from sender " << sender_id
-               << ": no request id: " << message;
-    return;
-  }
-
-  // Build the response message.
-  base::Value response_value(base::Value::Type::DICTIONARY);
-  response_value.SetKey(kKeyType, base::Value(kValueError));
-  response_value.SetKey(kKeyRequestId, base::Value(request_id.value()));
-  response_value.SetKey(kKeyData, base::Value(kValueInjectNotSupportedError));
-  response_value.SetKey(kKeyCode, base::Value(kValueWrappedError));
-
-  std::string json_message;
-  CHECK(base::JSONWriter::Write(response_value, &json_message));
-  PostMessage(sender_id, kInjectNamespace, json_message);
-}
-
-void CastMessagePortImpl::PostMessage(const std::string& sender_id,
-                                      const std::string& message_namespace,
-                                      const std::string& message) {
-  DVLOG(3) << __func__;
-  if (!message_port_)
-    return;
-
-  DVLOG(3) << "Received Open Screen message. SenderId: " << sender_id
-           << ". Namespace: " << message_namespace << ". Message: " << message;
-  message_port_->PostMessage(
-      SerializeCastMessage(sender_id, message_namespace, message));
-}
-
-bool CastMessagePortImpl::OnMessage(
-    base::StringPiece message,
-    std::vector<std::unique_ptr<cast_api_bindings::MessagePort>> ports) {
-  DVLOG(3) << __func__;
-
-  // If |client_| was cleared, |message_port_| should have been reset.
-  DCHECK(client_);
-
-  if (!ports.empty()) {
-    // We should never receive any ports for Cast Streaming.
-    LOG(ERROR) << "Received ports on Cast Streaming MessagePort.";
-    return false;
-  }
-
-  std::string sender_id;
-  std::string message_namespace;
-  std::string str_message;
-  if (!DeserializeCastMessage(message, &sender_id, &message_namespace,
-                              &str_message)) {
-    LOG(ERROR) << "Received bad message.";
-    client_->OnError(
-        openscreen::Error(openscreen::Error::Code::kCastV2InvalidMessage));
-    return true;
-  }
-  DVLOG(3) << "Received Cast message. SenderId: " << sender_id
-           << ". Namespace: " << message_namespace
-           << ". Message: " << str_message;
-
-  // TODO(b/156118960): Have Open Screen handle message namespaces.
-  if (message_namespace == kMirroringNamespace ||
-      message_namespace == kRemotingNamespace) {
-    client_->OnMessage(sender_id, message_namespace, str_message);
-  } else if (message_namespace == kInjectNamespace) {
-    SendInjectResponse(sender_id, str_message);
-  } else if (message_namespace != kSystemNamespace) {
-    // System messages are ignored, log messages from unknown namespaces.
-    DVLOG(2) << "Unknown message from " << sender_id
-             << ", namespace=" << message_namespace
-             << ", message=" << str_message;
-  }
-
-  return true;
-}
-
-void CastMessagePortImpl::OnPipeError() {
-  DVLOG(3) << __func__;
-  MaybeClose();
-}
-
-}  // namespace cast_streaming
diff --git a/fuchsia/cast_streaming/cast_message_port_impl.h b/fuchsia/cast_streaming/cast_message_port_impl.h
deleted file mode 100644
index 2bd90d24..0000000
--- a/fuchsia/cast_streaming/cast_message_port_impl.h
+++ /dev/null
@@ -1,57 +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.
-
-#ifndef FUCHSIA_CAST_STREAMING_CAST_MESSAGE_PORT_IMPL_H_
-#define FUCHSIA_CAST_STREAMING_CAST_MESSAGE_PORT_IMPL_H_
-
-#include "base/callback.h"
-#include "components/cast/message_port/message_port.h"
-#include "third_party/openscreen/src/cast/common/public/message_port.h"
-
-namespace cast_streaming {
-
-// Wrapper for a cast MessagePort that provides an Open Screen MessagePort
-// implementation.
-class CastMessagePortImpl : public openscreen::cast::MessagePort,
-                            public cast_api_bindings::MessagePort::Receiver {
- public:
-  CastMessagePortImpl(
-      std::unique_ptr<cast_api_bindings::MessagePort> message_port,
-      base::OnceClosure on_close);
-  ~CastMessagePortImpl() final;
-
-  CastMessagePortImpl(const CastMessagePortImpl&) = delete;
-  CastMessagePortImpl& operator=(const CastMessagePortImpl&) = delete;
-
-  // openscreen::cast::MessagePort implementation.
-  void SetClient(Client* client, std::string client_sender_id) final;
-  void ResetClient() final;
-  void PostMessage(const std::string& sender_id,
-                   const std::string& message_namespace,
-                   const std::string& message) final;
-
- private:
-  // Resets |message_port_| if it is open and signals an error to |client_| if
-  // |client_| is set.
-  void MaybeClose();
-
-  // Returns a "not supported" error message to the sender for messages from
-  // the inject namespace.
-  void SendInjectResponse(const std::string& sender_id,
-                          const std::string& message);
-
-  // cast_api_bindings::MessagePort::Receiver implementation.
-  bool OnMessage(
-      base::StringPiece message,
-      std::vector<std::unique_ptr<cast_api_bindings::MessagePort>> ports) final;
-  void OnPipeError() final;
-
-  Client* client_ = nullptr;
-  std::unique_ptr<cast_api_bindings::MessagePort> message_port_;
-  base::OnceClosure on_close_;
-};
-
-}  // namespace cast_streaming
-
-#endif  // FUCHSIA_CAST_STREAMING_CAST_MESSAGE_PORT_IMPL_H_
diff --git a/fuchsia/cipd/BUILD.gn b/fuchsia/cipd/BUILD.gn
index 0f6604c..ecb41b0 100644
--- a/fuchsia/cipd/BUILD.gn
+++ b/fuchsia/cipd/BUILD.gn
@@ -41,39 +41,40 @@
 # a manifest file.
 #
 # Parameters:
-#   package_basename: Determines the package basename in CIPD.
+#   package_basename: Determines the package basename in CIPD. Only required if
+#                     |package_relative_path| is not specified
 #   package_relative_path: Specify the package location relative to the fuchsia/
 #                          CIPD subdirectory. Defaults to a package name
-#                          based on package_basename.
+#                          based on |package_basename|.
 #   description: Sets the "description" field in CIPD package definition.
 #
 # Optional parameters used directly by fuchsia_cipd_package template:
-#   "package_root",
-#   "package_definition_name",
-#   "package_definition_dir",
 #   "install_mode",
-#   "files",
-#   "directories",
 #   "sources",
+#   "data",
+#   "data_deps"
+#   "deps",
+#   "testonly",
 
 template("cipd_archive") {
   forward_variables_from(invoker,
                          [
                            "package_basename",
                            "package_relative_path",
-                           "package_root",
-                           "package_definition_name",
-                           "package_definition_dir",
                            "description",
                            "install_mode",
-                           "files",
-                           "directories",
                            "sources",
+                           "data",
+                           "data_deps",
                            "deps",
                            "testonly",
-                           "visibility",
                          ])
 
+  assert(
+      (defined(package_basename) && !defined(package_relative_path)) ||
+          (!defined(package_basename) && defined(package_relative_path)),
+      "only one of \"package_basename\" and \"package_relative_path\" should be set")
+
   # Produces a consolidated license file.
   action("${target_name}_license") {
     _license_path = "${target_gen_dir}/${target_name}/LICENSE"
diff --git a/fuchsia/engine/BUILD.gn b/fuchsia/engine/BUILD.gn
index fb45173..cfdc7e59 100644
--- a/fuchsia/engine/BUILD.gn
+++ b/fuchsia/engine/BUILD.gn
@@ -249,7 +249,6 @@
     "context_provider_impl.h",
     "context_provider_main.cc",
     "context_provider_main.h",
-    "features.h",
     "renderer/url_request_rules_receiver.cc",
     "renderer/url_request_rules_receiver.h",
     "renderer/web_engine_content_renderer_client.cc",
@@ -267,11 +266,25 @@
   visibility = [ ":*" ]
 }
 
+# TODO(crbug.com/1081525): Rename to features_and_switches or collapse into
+# common. Consider moving these and other files in engine/ to common/ or
+# elsewhere.
 source_set("switches") {
+  deps = [ "//base" ]
+
   sources = [
+    "features.h",
     "switches.cc",
     "switches.h",
   ]
+
+  visibility = [
+    ":*",
+    "./*",
+
+    # TODO(crbug.com/1117629): Remove this when fixed.
+    "//media/renderers",
+  ]
 }
 
 executable("web_engine_exe") {
diff --git a/fuchsia/engine/browser/autoplay_browsertest.cc b/fuchsia/engine/browser/autoplay_browsertest.cc
index 0cacab9c..d57bec5 100644
--- a/fuchsia/engine/browser/autoplay_browsertest.cc
+++ b/fuchsia/engine/browser/autoplay_browsertest.cc
@@ -16,7 +16,7 @@
 
 namespace {
 
-constexpr char kAutoplayVp8Url[] = "/play_vp8.html?autoplay=1&codecs=vp8";
+constexpr char kAutoplayVp8Url[] = "/play_video.html?autoplay=1&codecs=vp8";
 
 }  // namespace
 
diff --git a/fuchsia/engine/browser/frame_impl_browsertest.cc b/fuchsia/engine/browser/frame_impl_browsertest.cc
index 4c7bb15..8ae66daa 100644
--- a/fuchsia/engine/browser/frame_impl_browsertest.cc
+++ b/fuchsia/engine/browser/frame_impl_browsertest.cc
@@ -80,7 +80,7 @@
 const int64_t kOnLoadScriptId = 0;
 const char kChildQueryParamName[] = "child_url";
 const char kPopupChildFile[] = "popup_child.html";
-const char kAutoplayFileAndQuery[] = "play_vp8.html?autoplay=1&codecs=vp8";
+const char kAutoplayFileAndQuery[] = "play_video.html?autoplay=1&codecs=vp8";
 const char kAutoPlayBlockedTitle[] = "blocked";
 const char kAutoPlaySuccessTitle[] = "playing";
 
diff --git a/fuchsia/engine/browser/media_browsertest.cc b/fuchsia/engine/browser/media_browsertest.cc
index 84c12e4..dfc9d79 100644
--- a/fuchsia/engine/browser/media_browsertest.cc
+++ b/fuchsia/engine/browser/media_browsertest.cc
@@ -9,10 +9,11 @@
 #include "base/files/file_path.h"
 #include "base/fuchsia/scoped_service_binding.h"
 #include "base/fuchsia/test_component_context_for_process.h"
+#include "base/test/scoped_feature_list.h"
 #include "content/public/test/browser_test.h"
 #include "fuchsia/base/test/frame_test_util.h"
 #include "fuchsia/base/test/test_navigation_listener.h"
-#include "fuchsia/engine/switches.h"
+#include "fuchsia/engine/features.h"
 #include "fuchsia/engine/test/test_data.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -21,9 +22,9 @@
 
 // Currently, VP8 can only be decoded in software, and VP9 can be decoded in
 // hardware and software. The tests rely on this.
-// TODO(crbug.com/1207695): Rename play_vp8.html to play_video.html.
-constexpr char kLoadSoftwareOnlyCodecUrl[] = "/play_vp8.html?codecs=vp8";
-constexpr char kLoadHardwareAndSoftwareCodecUrl[] = "/play_vp8.html?codecs=vp9";
+constexpr char kLoadSoftwareOnlyCodecUrl[] = "/play_video.html?codecs=vp8";
+constexpr char kLoadHardwareAndSoftwareCodecUrl[] =
+    "/play_video.html?codecs=vp9";
 constexpr char kCanPlaySoftwareOnlyCodecUrl[] = "/can_play_vp8.html";
 
 }  // namespace
@@ -52,28 +53,32 @@
   cr_fuchsia::TestNavigationListener navigation_listener_;
 };
 
-using SoftwareDecoderEnabledTest = MediaTest;
+using SoftwareOnlyDecodersEnabledTest = MediaTest;
 
-// MediaTest with switches::kDisableSoftwareVideoDecoders.
-class SoftwareDecoderDisabledTest : public MediaTest {
+// MediaTest with kEnableSoftwareOnlyVideoCodecs disabled.
+class SoftwareOnlyDecodersDisabledTest : public MediaTest {
  public:
-  SoftwareDecoderDisabledTest() = default;
-  ~SoftwareDecoderDisabledTest() override = default;
+  SoftwareOnlyDecodersDisabledTest() = default;
+  ~SoftwareOnlyDecodersDisabledTest() override = default;
 
  protected:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitch(switches::kDisableSoftwareVideoDecoders);
-    MediaTest::SetUpCommandLine(command_line);
+  void SetUp() override {
+    scoped_feature_list_.InitAndDisableFeature(
+        features::kEnableSoftwareOnlyVideoCodecs);
+    MediaTest::SetUp();
   }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-// SoftwareDecoderDisabledTest with fuchsia.mediacodec.CodecFactory
+// SoftwareOnlyDecodersDisabledTest with fuchsia.mediacodec.CodecFactory
 // disconnected.
-class SoftwareDecoderDisabledAndHardwareDecoderFailureTest
-    : public SoftwareDecoderDisabledTest {
+class SoftwareOnlyDecodersDisabledAndHardwareDecoderFailureTest
+    : public SoftwareOnlyDecodersDisabledTest {
  public:
-  SoftwareDecoderDisabledAndHardwareDecoderFailureTest() = default;
-  ~SoftwareDecoderDisabledAndHardwareDecoderFailureTest() override = default;
+  SoftwareOnlyDecodersDisabledAndHardwareDecoderFailureTest() = default;
+  ~SoftwareOnlyDecodersDisabledAndHardwareDecoderFailureTest() override =
+      default;
 
  protected:
   // Removes the decoder service to cause calls to it to fail.
@@ -83,7 +88,7 @@
     component_context_->additional_services()
         ->RemovePublicService<fuchsia::mediacodec::CodecFactory>();
 
-    SoftwareDecoderDisabledTest::SetUpOnMainThread();
+    SoftwareOnlyDecodersDisabledTest::SetUpOnMainThread();
   }
 
   // Used to disconnect fuchsia.mediacodec.CodecFactory.
@@ -91,8 +96,8 @@
 };
 
 // Verify that a codec only supported by a software decoder is reported as
-// playable if kDisableSoftwareVideoDecoders is not present.
-IN_PROC_BROWSER_TEST_F(SoftwareDecoderEnabledTest,
+// playable if kEnableSoftwareOnlyVideoCodecs is enabled.
+IN_PROC_BROWSER_TEST_F(SoftwareOnlyDecodersEnabledTest,
                        CanPlayTypeSoftwareOnlyCodecIsTrue) {
   const GURL kUrl(embedded_test_server()->GetURL(kCanPlaySoftwareOnlyCodecUrl));
 
@@ -111,8 +116,8 @@
 }
 
 // Verify that a codec only supported by a software decoder is reported as not
-// playable if kDisableSoftwareVideoDecoders is present.
-IN_PROC_BROWSER_TEST_F(SoftwareDecoderDisabledTest,
+// playable if kEnableSoftwareOnlyVideoCodecs is disabled.
+IN_PROC_BROWSER_TEST_F(SoftwareOnlyDecodersDisabledTest,
                        CanPlayTypeSoftwareOnlyCodecIsFalse) {
   const GURL kUrl(embedded_test_server()->GetURL(kCanPlaySoftwareOnlyCodecUrl));
 
@@ -127,8 +132,8 @@
 }
 
 // Verify that a codec only supported by a software decoder is loaded if
-// kDisableSoftwareVideoDecoders is not present.
-IN_PROC_BROWSER_TEST_F(SoftwareDecoderEnabledTest,
+// kEnableSoftwareOnlyVideoCodecs is enabled.
+IN_PROC_BROWSER_TEST_F(SoftwareOnlyDecodersEnabledTest,
                        PlaySoftwareOnlyCodecSucceeds) {
   const GURL kUrl(embedded_test_server()->GetURL(kLoadSoftwareOnlyCodecUrl));
 
@@ -143,8 +148,8 @@
 }
 
 // Verify that a codec only supported by a software decoder is not loaded if
-// kDisableSoftwareVideoDecoders is present.
-IN_PROC_BROWSER_TEST_F(SoftwareDecoderDisabledTest,
+// kEnableSoftwareOnlyVideoCodecs is disabled.
+IN_PROC_BROWSER_TEST_F(SoftwareOnlyDecodersDisabledTest,
                        LoadSoftwareOnlyCodecFails) {
   const GURL kUrl(embedded_test_server()->GetURL(kLoadSoftwareOnlyCodecUrl));
 
@@ -159,12 +164,11 @@
 }
 
 // Verify that a codec supported by hardware and software decoders plays if
-// kDisableSoftwareVideoDecoders is present.
-// Unlike the software-only codec, this codec loads and plays (when a hardware)
-// decoder is actually available, such as on real hardware.
-// TODO(crbug.com/1207695): Correct the final expected result to "playing".
-// Currently, this fails in emulators. Fixing the bug will change this.
-IN_PROC_BROWSER_TEST_F(SoftwareDecoderDisabledTest,
+// kEnableSoftwareOnlyVideoCodecs is disabled.
+// Unlike the software-only codec, this codec loads because it is supposed to be
+// supported. It then plays because a hardware decoder is used (when on real
+// hardware) or it falls back to the software decoder (i.e., on emulator).
+IN_PROC_BROWSER_TEST_F(SoftwareOnlyDecodersDisabledTest,
                        PlayHardwareAndSoftwareCodecSucceeds) {
   const GURL kUrl(
       embedded_test_server()->GetURL(kLoadHardwareAndSoftwareCodecUrl));
@@ -181,15 +185,16 @@
   navigation_listener_.RunUntilUrlAndTitleEquals(kUrl, "loaded");
   cr_fuchsia::ExecuteJavaScript(frame.get(), "bear.play()");
 
-  navigation_listener_.RunUntilUrlAndTitleEquals(kUrl, "media element error");
+  navigation_listener_.RunUntilUrlAndTitleEquals(kUrl, "playing");
 }
 
-// Verify that a codec supported by hardware and software does not play if
-// kDisableSoftwareVideoDecoders is present and the hardware decoder fails.
+// Verify that a codec supported by hardware and software plays if
+// kEnableSoftwareOnlyVideoCodecs is disabled and the hardware decoder fails.
 // Unlike the software-only codec, this codec loads because it is supposed to be
-// supported but fails when the hardware decoder is unavailable.
-IN_PROC_BROWSER_TEST_F(SoftwareDecoderDisabledAndHardwareDecoderFailureTest,
-                       PlayHardwareAndSoftwareCodecFails) {
+// supported. It then plays because it falls back to the software decoder.
+IN_PROC_BROWSER_TEST_F(
+    SoftwareOnlyDecodersDisabledAndHardwareDecoderFailureTest,
+    PlayHardwareAndSoftwareCodecFails) {
   const GURL kUrl(
       embedded_test_server()->GetURL(kLoadHardwareAndSoftwareCodecUrl));
 
@@ -205,5 +210,5 @@
   navigation_listener_.RunUntilUrlAndTitleEquals(kUrl, "loaded");
   cr_fuchsia::ExecuteJavaScript(frame.get(), "bear.play()");
 
-  navigation_listener_.RunUntilUrlAndTitleEquals(kUrl, "media element error");
+  navigation_listener_.RunUntilUrlAndTitleEquals(kUrl, "playing");
 }
diff --git a/fuchsia/engine/browser/web_engine_content_browser_client.cc b/fuchsia/engine/browser/web_engine_content_browser_client.cc
index 56b429f..92c0cc3 100644
--- a/fuchsia/engine/browser/web_engine_content_browser_client.cc
+++ b/fuchsia/engine/browser/web_engine_content_browser_client.cc
@@ -171,7 +171,6 @@
   // TODO(https://crbug.com/1083520): Pass based on process type.
   constexpr char const* kSwitchesToCopy[] = {
       switches::kCorsExemptHeaders,
-      switches::kDisableSoftwareVideoDecoders,
       switches::kEnableCastStreamingReceiver,
       switches::kEnableContentDirectories,
       switches::kEnableProtectedVideoBuffers,
diff --git a/fuchsia/engine/features.h b/fuchsia/engine/features.h
index 4331250..8963f68 100644
--- a/fuchsia/engine/features.h
+++ b/fuchsia/engine/features.h
@@ -12,6 +12,14 @@
 constexpr base::Feature kHandleMemoryPressureInRenderer{
     "HandleMemoryPressureInRenderer", base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enables the use of video codecs that cannot be hardware-accelerated.
+// When disabled, software video decoders are still available in case they are
+// needed as a fallback due to a hardware decoder failure. Does not affect
+// WebRTC; see media::kExposeSwDecodersToWebRTC and
+// media::kUseDecoderStreamForWebRTC.
+constexpr base::Feature kEnableSoftwareOnlyVideoCodecs{
+    "SoftwareOnlyVideoCodecs", base::FEATURE_ENABLED_BY_DEFAULT};
+
 }  // namespace features
 
 #endif  // FUCHSIA_ENGINE_FEATURES_H_
diff --git a/fuchsia/engine/renderer/web_engine_content_renderer_client.cc b/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
index 1dfce51..747307a 100644
--- a/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
+++ b/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
@@ -20,6 +20,7 @@
 #include "fuchsia/engine/renderer/web_engine_url_loader_throttle_provider.h"
 #include "fuchsia/engine/switches.h"
 #include "media/base/eme_constants.h"
+#include "media/base/media_switches.h"
 #include "media/base/video_codecs.h"
 #include "services/network/public/cpp/features.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
@@ -140,8 +141,7 @@
   // Both the RenderView and WebView should be guaranteed to be non-null, since
   // the |render_frame| was only just created.
   if (render_frame->IsMainFrame()) {
-    render_frame->GetRenderView()->GetWebView()->SetBaseBackgroundColor(
-        SK_AlphaTRANSPARENT);
+    render_frame->GetWebView()->SetBaseBackgroundColor(SK_AlphaTRANSPARENT);
   }
 
   // Add WebEngine services to the new RenderFrame.
@@ -236,10 +236,9 @@
 
 bool WebEngineContentRendererClient::IsSupportedVideoType(
     const media::VideoType& type) {
-  // Fall back to default codec querying logic if software codecs aren't
-  // disabled.
-  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kDisableSoftwareVideoDecoders)) {
+  // Fall back to default codec querying logic if software-only codecs are
+  // enabled.
+  if (base::FeatureList::IsEnabled(features::kEnableSoftwareOnlyVideoCodecs)) {
     return ContentRendererClient::IsSupportedVideoType(type);
   }
 
diff --git a/fuchsia/engine/switches.cc b/fuchsia/engine/switches.cc
index a227636..8488c4e 100644
--- a/fuchsia/engine/switches.cc
+++ b/fuchsia/engine/switches.cc
@@ -7,7 +7,6 @@
 namespace switches {
 
 const char kEnableContentDirectories[] = "enable-content-directories";
-const char kDisableSoftwareVideoDecoders[] = "disable-software-video-decoders";
 const char kEnableWidevine[] = "enable-widevine";
 const char kIncognito[] = "incognito";
 const char kPlayreadyKeySystem[] = "playready-key-system";
diff --git a/fuchsia/engine/switches.h b/fuchsia/engine/switches.h
index 7cdfb76..2dafdd3 100644
--- a/fuchsia/engine/switches.h
+++ b/fuchsia/engine/switches.h
@@ -12,9 +12,6 @@
 // Directories will be mounted under a directory in the browser's namespace.
 extern const char kEnableContentDirectories[];
 
-// Prevents the use of video codecs that are not hardware-accelerated.
-extern const char kDisableSoftwareVideoDecoders[];
-
 // Enables Widevine CDM support.
 extern const char kEnableWidevine[];
 
diff --git a/fuchsia/engine/test/data/play_vp8.html b/fuchsia/engine/test/data/play_video.html
similarity index 100%
rename from fuchsia/engine/test/data/play_vp8.html
rename to fuchsia/engine/test/data/play_video.html
diff --git a/fuchsia/engine/web_engine_integration_test.cc b/fuchsia/engine/web_engine_integration_test.cc
index 942e445..8e9fc40 100644
--- a/fuchsia/engine/web_engine_integration_test.cc
+++ b/fuchsia/engine/web_engine_integration_test.cc
@@ -30,14 +30,13 @@
 constexpr char kInvalidUserAgentProduct[] = "Test/Product";
 constexpr char kInvalidUserAgentVersion[] = "dev/12345";
 
-// TODO(crbug.com/1207695): Rename play_vp8.html to play_video.html.
 constexpr char kAutoplayVp9OpusUrl[] =
-    "fuchsia-dir://testdata/play_vp8.html?codecs=vp9,opus&autoplay=1";
+    "fuchsia-dir://testdata/play_video.html?codecs=vp9,opus&autoplay=1";
 constexpr char kAutoplayVp9OpusToEndUrl[] =
     "fuchsia-dir://testdata/"
-    "play_vp8.html?codecs=vp9,opus&autoplay=1&reportended=1";
+    "play_video.html?codecs=vp9,opus&autoplay=1&reportended=1";
 constexpr char kLoadVp9OpusUrl[] =
-    "fuchsia-dir://testdata/play_vp8.html?codecs=vp9,opus";
+    "fuchsia-dir://testdata/play_video.html?codecs=vp9,opus";
 
 }  // namespace
 
diff --git a/fuchsia/engine/web_instance_host/web_instance_host.cc b/fuchsia/engine/web_instance_host/web_instance_host.cc
index e77473da..0165825 100644
--- a/fuchsia/engine/web_instance_host/web_instance_host.cc
+++ b/fuchsia/engine/web_instance_host/web_instance_host.cc
@@ -48,6 +48,7 @@
 #include "content/public/common/content_switches.h"
 #include "fuchsia/base/config_reader.h"
 #include "fuchsia/base/string_util.h"
+#include "fuchsia/engine/features.h"
 #include "fuchsia/engine/switches.h"
 #include "gpu/command_buffer/service/gpu_switches.h"
 #include "gpu/config/gpu_finch_features.h"
@@ -603,7 +604,8 @@
       return ZX_ERR_INVALID_ARGS;
     }
 
-    launch_args.AppendSwitch(switches::kDisableSoftwareVideoDecoders);
+    AppendFeature(switches::kDisableFeatures,
+                  features::kEnableSoftwareOnlyVideoCodecs.name, &launch_args);
   }
 
   if (!HandleCdmDataDirectoryParam(&params, &launch_args, &launch_info)) {
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 52084d88..33892f4 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -15627,34 +15627,11 @@
     DCHECK(type == GL_UNSIGNED_BYTE);
     switch (internal_format) {
       case GL_RGB:
-        // Changing the internal format here is probably not completely
-        // correct. This is the "effective" internal format, and the spec
-        // says "effective internal format is used by the GL for purposes
-        // such as texture completeness or type checks for CopyTex*
-        // commands". But we don't have a separate concept of "effective"
-        // vs. "actual" internal format, so this will have to do for now. See
-        // Table 3.12 and associated explanatory text in the OpenGL ES 3.0.6
-        // spec for more information.
-        //
-        // Unfortunately, changing the internal format here conflicts with a
-        // macos workaround flag, so don't do it if the workaround applies.
-        if (!workarounds().use_intermediary_for_copy_texture_image ||
-            target == GL_TEXTURE_2D) {
-          internal_format = GL_RGB8;
-        }
-        break;
       case GL_RGBA:
-        if (!workarounds().use_intermediary_for_copy_texture_image ||
-            target == GL_TEXTURE_2D) {
-          internal_format = GL_RGBA8;
-        }
-        break;
       case GL_LUMINANCE_ALPHA:
       case GL_LUMINANCE:
       case GL_ALPHA:
       case GL_BGRA_EXT:
-        // There are no GL constants for sized versions of these internal
-        // formats. We'll just go ahead with the unsized ones.
         break;
       default:
         // Other unsized internal_formats are invalid in ES3.
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
index b6362a6..906f062 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
@@ -1556,14 +1556,6 @@
     GLsizei width,
     GLsizei height,
     GLint border) {
-  GLenum translated_internal_format = internal_format;
-  if (group_->feature_info()->IsWebGL2OrES3Context()) {
-    if (internal_format == GL_RGB) {
-      translated_internal_format = GL_RGB8;
-    } else if (internal_format == GL_RGBA) {
-      translated_internal_format = GL_RGBA8;
-    }
-  }
   // For GL_BGRA_EXT, we have to fall back to TexImage2D and
   // CopyTexSubImage2D, since GL_BGRA_EXT is not accepted by CopyTexImage2D.
   // In some cases this fallback further triggers set and restore of
@@ -1596,15 +1588,14 @@
       EXPECT_CALL(*gl_, TexParameteri(target, GL_TEXTURE_SWIZZLE_A, _))
           .Times(testing::AtLeast(1));
     } else {
-      EXPECT_CALL(
-          *gl_, CopyTexImage2D(target, level, translated_internal_format, 0, 0,
-                               width, height, border))
+      EXPECT_CALL(*gl_, CopyTexImage2D(target, level, internal_format, 0, 0,
+                                       width, height, border))
           .Times(1)
           .RetiresOnSaturation();
     }
   } else {
-    EXPECT_CALL(*gl_, CopyTexImage2D(target, level, translated_internal_format,
-                                     0, 0, width, height, border))
+    EXPECT_CALL(*gl_, CopyTexImage2D(target, level, internal_format, 0, 0,
+                                     width, height, border))
         .Times(1)
         .RetiresOnSaturation();
   }
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 675bd68..84f8ed45d 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -3415,9 +3415,7 @@
       "vendor_id": "0x8086",
       "intel_gpu_series": [
         "geminilake",
-        "kabylake",
-        "whiskeylake",
-        "cometlake"
+        "kabylake"
       ],
       "features": [
         "disable_accelerated_vp8_encode"
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index a196913a..179fd23 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -24259,7 +24259,7 @@
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:ios14-beta-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
+      dimensions: "os:Mac-11|Mac-10.16"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -24327,18 +24327,18 @@
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:ios14-sdk-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
+      dimensions: "os:Mac-11|Mac-10.16"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"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\"},\"$recipe_engine/resultdb/test_presentation\":{\"column_keys\":[],\"grouping_keys\":[\"status\",\"v.test_suite\"]},\"builder_group\":\"chromium.fyi\",\"recipe\":\"chromium\",\"xcode_build_version\":\"12d4e\"}"
+      properties: "{\"$build/goma\":{\"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\"},\"$recipe_engine/resultdb/test_presentation\":{\"column_keys\":[],\"grouping_keys\":[\"status\",\"v.test_suite\"]},\"builder_group\":\"chromium.fyi\",\"recipe\":\"chromium\",\"xcode_build_version\":\"12e262\"}"
       execution_timeout_secs: 36000
       caches {
-        name: "xcode_ios_12d4e"
-        path: "xcode_ios_12d4e.app"
+        name: "xcode_ios_12e262"
+        path: "xcode_ios_12e262.app"
       }
       build_numbers: YES
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -45547,7 +45547,7 @@
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:ios14-beta-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
+      dimensions: "os:Mac-11|Mac-10.16"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -45618,14 +45618,14 @@
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:ios14-sdk-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
+      dimensions: "os:Mac-11|Mac-10.16"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"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\"},\"$recipe_engine/resultdb/test_presentation\":{\"column_keys\":[],\"grouping_keys\":[\"status\",\"v.test_suite\"]},\"builder_group\":\"tryserver.chromium.mac\",\"recipe\":\"chromium_trybot\",\"xcode_build_version\":\"12d4e\"}"
+      properties: "{\"$build/goma\":{\"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\"},\"$recipe_engine/resultdb/test_presentation\":{\"column_keys\":[],\"grouping_keys\":[\"status\",\"v.test_suite\"]},\"builder_group\":\"tryserver.chromium.mac\",\"recipe\":\"chromium_trybot\",\"xcode_build_version\":\"12e262\"}"
       execution_timeout_secs: 14400
       expiration_secs: 7200
       grace_period {
@@ -45636,8 +45636,8 @@
         path: "win_toolchain"
       }
       caches {
-        name: "xcode_ios_12d4e"
-        path: "xcode_ios_12d4e.app"
+        name: "xcode_ios_12e262"
+        path: "xcode_ios_12e262.app"
       }
       build_numbers: YES
       service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index c93ca53..61d0129e 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -167,6 +167,8 @@
     x12a7209 = xcode_enum("12a7209"),
     # (current default for iOS) xc12.4 gm seed
     x12d4e = xcode_enum("12d4e"),
+    # Xcode 12.5. Requires Mac11+ OS.
+    x12e262 = xcode_enum("12e262"),
 )
 
 ################################################################################
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index abfc9b50..688ca2a 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -4178,6 +4178,7 @@
         category = "iOS|iOS14",
         short_name = "ios14",
     ),
+    os = os.MAC_11,
 )
 
 ci.fyi_ios_builder(
@@ -4186,7 +4187,8 @@
         category = "iOS|iOS14",
         short_name = "sdk14",
     ),
-    xcode = xcode.x12d4e,
+    os = os.MAC_11,
+    xcode = xcode.x12e262,
 )
 
 ci.fyi_mac_builder(
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index d0838d6..7690ad17 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -1697,11 +1697,13 @@
 
 try_.chromium_mac_ios_builder(
     name = "ios14-beta-simulator",
+    os = os.MAC_11,
 )
 
 try_.chromium_mac_ios_builder(
     name = "ios14-sdk-simulator",
-    xcode = xcode.x12d4e,
+    os = os.MAC_11,
+    xcode = xcode.x12e262,
 )
 
 try_.chromium_updater_mac_builder(
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index b39a141ae..69b5631 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -2450,6 +2450,14 @@
       <message name="IDS_IOS_TAB_GRID_UNDO_CLOSE_ALL_BUTTON" desc="Title of the button in the tab grid UI that revert the close all action recently taken by the user. [iOS only]">
         Undo
       </message>
+      <message name="IDS_IOS_TAB_GRID_CLOSE_TABS_BUTTON" desc="Title of the button in the tab grid UI that the closes selected tabs. count refers to the number of tabs selected. According to its value, the string goes from signular to plural. [iOS only]">
+        {count, plural,
+          =1 {Close Tab}
+          other {Close Tabs}}
+      </message>
+      <message name="IDS_IOS_TAB_GRID_ADD_TO_BUTTON" desc="Title of the button in the tab grid UI that when tapped, will provide the user with a menu to add selected tabs to bookmarks or to the reading list. [iOS only]">
+        Add To...
+      </message>
       <message name="IDS_IOS_TAB_GRID_INCOGNITO_TABS_TITLE" desc="The accessibility label of the Incognito Tabs page in the tab grid UI. [iOS only]">
         Incognito Tabs
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TAB_GRID_ADD_TO_BUTTON.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TAB_GRID_ADD_TO_BUTTON.png.sha1
new file mode 100644
index 0000000..beadc42c
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TAB_GRID_ADD_TO_BUTTON.png.sha1
@@ -0,0 +1 @@
+8673496bd806e213a0e2591207254efbb28ad0cb
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TAB_GRID_CLOSE_TABS_BUTTON.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TAB_GRID_CLOSE_TABS_BUTTON.png.sha1
new file mode 100644
index 0000000..beadc42c
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TAB_GRID_CLOSE_TABS_BUTTON.png.sha1
@@ -0,0 +1 @@
+8673496bd806e213a0e2591207254efbb28ad0cb
\ No newline at end of file
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.h
index 9684edcd..6758cda5 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.h
@@ -24,10 +24,17 @@
 //   Incognito & Regular page: [                               newTabButton]
 //   Remote page:              [                                           ]
 @interface TabGridBottomToolbar : UIView
-// This property together with self.traitCollection control the items shown
-// in toolbar and its background color. Setting this property will also set it
-// on |newTabButton|.
+// This property together with |mode| and self.traitCollection control the items
+// shown in toolbar and its background color. Setting this property will also
+// set it on |newTabButton|.
 @property(nonatomic, assign) TabGridPage page;
+// This property together with |page| and self.traitCollection control the
+// items shown in toolbar and its background color.
+@property(nonatomic, assign) TabGridMode mode;
+// This property indicates the count of selected tabs when the tab grid is in
+// selection mode. It will be used to update the buttons to use the correct
+// title (singular or plural).
+@property(nonatomic, assign) int selectedTabsCount;
 // These components are publicly available to allow the user to set their
 // contents, visibility and actions.
 @property(nonatomic, strong, readonly) UIBarButtonItem* leadingButton;
@@ -37,6 +44,8 @@
 - (void)setNewTabButtonTarget:(id)target action:(SEL)action;
 // Set |enabled| on the new tab button.
 - (void)setNewTabButtonEnabled:(BOOL)enabled;
+// Set |enabled| on the selection mode buttons.
+- (void)setSelectionModeButtonsEnabled:(BOOL)enabled;
 
 // Hides components and uses a black background color for tab grid transition
 // animation.
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm
index 2c0f6d3..1192b85 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm
@@ -4,11 +4,15 @@
 
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.h"
 
+#include "base/strings/sys_string_conversions.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_new_tab_button.h"
 #import "ios/chrome/browser/ui/thumb_strip/thumb_strip_feature.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
+#include "ios/chrome/grit/ios_strings.h"
+#include "ui/base/l10n/l10n_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -23,6 +27,9 @@
   NSLayoutConstraint* _largeNewTabButtonBottomAnchor;
   TabGridNewTabButton* _smallNewTabButton;
   TabGridNewTabButton* _largeNewTabButton;
+  UIBarButtonItem* _addToButton;
+  UIBarButtonItem* _closeTabsButton;
+  UIBarButtonItem* _shareButton;
 }
 
 #pragma mark - UIView
@@ -78,6 +85,16 @@
   [self updateLayout];
 }
 
+- (void)setMode:(TabGridMode)mode {
+  _mode = mode;
+  [self updateLayout];
+}
+
+- (void)setSelectedTabsCount:(int)count {
+  _selectedTabsCount = count;
+  [self updateSelectionButtonsTitle];
+}
+
 - (void)setNewTabButtonTarget:(id)target action:(SEL)action {
   [_smallNewTabButton addTarget:target
                          action:action
@@ -92,6 +109,12 @@
   _largeNewTabButton.enabled = enabled;
 }
 
+- (void)setSelectionModeButtonsEnabled:(BOOL)enabled {
+  _addToButton.enabled = enabled;
+  _closeTabsButton.enabled = enabled;
+  _shareButton.enabled = enabled;
+}
+
 - (void)hide {
   _smallNewTabButton.alpha = 0.0;
   _largeNewTabButton.alpha = 0.0;
@@ -138,6 +161,24 @@
   _newTabButtonItem =
       [[UIBarButtonItem alloc] initWithCustomView:_smallNewTabButton];
 
+  // Create selection mode buttons
+  if (IsTabsBulkActionsEnabled()) {
+    _addToButton = [[UIBarButtonItem alloc] init];
+    _addToButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor);
+    _addToButton.title = l10n_util::GetNSString(IDS_IOS_TAB_GRID_ADD_TO_BUTTON);
+    _addToButton.accessibilityIdentifier = kTabGridAddToButtonIdentifier;
+    _shareButton = [[UIBarButtonItem alloc]
+        initWithBarButtonSystemItem:UIBarButtonSystemItemAction
+                             target:nil
+                             action:nil];
+    _shareButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor);
+    _shareButton.accessibilityIdentifier = kTabGridShareButtonIdentifier;
+    _closeTabsButton = [[UIBarButtonItem alloc] init];
+    _closeTabsButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor);
+    _closeTabsButton.accessibilityIdentifier = kTabGridCloseButtonIdentifier;
+    [self updateSelectionButtonsTitle];
+  }
+
   _compactConstraints = @[
     [_toolbar.topAnchor constraintEqualToAnchor:self.topAnchor],
     [_toolbar.bottomAnchor
@@ -181,10 +222,27 @@
   _newTabButtonItem.title = _largeNewTabButton.accessibilityLabel;
 }
 
+- (void)updateSelectionButtonsTitle {
+  _closeTabsButton.title =
+      base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
+          IDS_IOS_TAB_GRID_CLOSE_TABS_BUTTON, _selectedTabsCount));
+}
+
 - (void)updateLayout {
   _largeNewTabButtonBottomAnchor.constant =
       -kTabGridFloatingButtonVerticalInset;
 
+  if (self.mode == TabGridModeSelection) {
+    [_toolbar setItems:@[
+      _closeTabsButton, _spaceItem, _shareButton, _spaceItem, _addToButton
+    ]];
+    [NSLayoutConstraint deactivateConstraints:_floatingConstraints];
+    [_largeNewTabButton removeFromSuperview];
+    [self addSubview:_toolbar];
+    [NSLayoutConstraint activateConstraints:_compactConstraints];
+    return;
+  }
+
   if ([self shouldUseCompactLayout]) {
     // For incognito/regular pages, display all 3 buttons;
     // For remote tabs page, only display new tab button.
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.h
index 6358705..ea7e4f85 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.h
@@ -15,6 +15,9 @@
 extern NSString* const kTabGridDoneButtonIdentifier;
 extern NSString* const kTabGridCloseAllButtonIdentifier;
 extern NSString* const kTabGridUndoCloseAllButtonIdentifier;
+extern NSString* const kTabGridCloseButtonIdentifier;
+extern NSString* const kTabGridAddToButtonIdentifier;
+extern NSString* const kTabGridShareButtonIdentifier;
 extern NSString* const kTabGridIncognitoTabsEmptyStateIdentifier;
 extern NSString* const kTabGridRegularTabsEmptyStateIdentifier;
 extern NSString* const kTabGridScrollViewIdentifier;
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.mm
index f7cb311..ff53983 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.mm
@@ -20,6 +20,10 @@
     @"TabGridCloseAllButtonIdentifier";
 NSString* const kTabGridUndoCloseAllButtonIdentifier =
     @"TabGridUndoCloseAllButtonIdentifier";
+NSString* const kTabGridCloseButtonIdentifier =
+    @"TabGridUndoCloseButtonIdentifier";
+NSString* const kTabGridAddToButtonIdentifier = @"TabGridAddToButtonIdentifier";
+NSString* const kTabGridShareButtonIdentifier = @"TabGridShareButtonIdentifier";
 NSString* const kTabGridIncognitoTabsEmptyStateIdentifier =
     @"TabGridIncognitoTabsEmptyStateIdentifier";
 NSString* const kTabGridRegularTabsEmptyStateIdentifier =
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_paging.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_paging.h
index 7256814c..ce487a01 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_paging.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_paging.h
@@ -12,12 +12,19 @@
   TabGridPageRemoteTabs = 2,
 };
 
-// An object implementing this protocol can change the active "page" of the tab
-// grid.
+// Page enumerates the modes of the tab grid.
+typedef NS_ENUM(NSUInteger, TabGridMode) {
+  TabGridModeNormal = 0,
+  TabGridModeSelection = 1,
+};
+
+// An object implementing this protocol can change the active "page" or the mode
+// of the tab grid.
 @protocol TabGridPaging <NSObject>
 // Active page of the tab grid. The active page is the page that
 // contains the most recent active tab.
 @property(nonatomic, assign) TabGridPage activePage;
+@property(nonatomic, assign) TabGridMode tabGridMode;
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_TAB_GRID_PAGING_H_
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
index 60cedb3..f79686b 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
@@ -176,6 +176,7 @@
 @implementation TabGridViewController
 // TabGridPaging property.
 @synthesize activePage = _activePage;
+@synthesize tabGridMode = _tabGridMode;
 
 - (instancetype)initWithPageConfiguration:
     (TabGridPageConfiguration)tabGridPageConfiguration {
@@ -1181,6 +1182,8 @@
   topToolbar.trailingButton.target = self;
   topToolbar.trailingButton.action = @selector(doneButtonTapped:);
   [topToolbar setNewTabButtonTarget:self action:@selector(newTabButtonTapped:)];
+  [topToolbar setSelectTabButtonTarget:self
+                                action:@selector(selectButtonTapped:)];
 
   // Configure and initialize the page control.
   [topToolbar.pageControl addTarget:self
@@ -1284,6 +1287,7 @@
 
 - (void)configureButtonsForActiveAndCurrentPage {
   self.bottomToolbar.page = self.currentPage;
+  self.bottomToolbar.mode = self.tabGridMode;
   // When current page is a remote tabs page.
   if (self.currentPage == TabGridPageRemoteTabs) {
     if (self.pageConfiguration ==
@@ -1796,6 +1800,12 @@
   }
 }
 
+- (void)selectButtonTapped:(id)sender {
+  self.tabGridMode = TabGridModeSelection;
+  self.bottomToolbar.mode = self.tabGridMode;
+  base::RecordAction(base::UserMetricsAction("MobileTabGridSelectTabs"));
+}
+
 // Shows an action sheet that asks for confirmation when 'Close All' button is
 // tapped.
 - (void)closeAllButtonTappedShowConfirmation {
diff --git a/ios/chrome/browser/ui/webui/net_export/net_export_ui.mm b/ios/chrome/browser/ui/webui/net_export/net_export_ui.mm
index a4ad5e8..922dc78e 100644
--- a/ios/chrome/browser/ui/webui/net_export/net_export_ui.mm
+++ b/ios/chrome/browser/ui/webui/net_export/net_export_ui.mm
@@ -10,7 +10,7 @@
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/macros.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 #include "base/strings/string_util.h"
 #include "base/values.h"
 #include "components/grit/dev_ui_components_resources.h"
@@ -81,9 +81,9 @@
   // This is owned by the ApplicationContext.
   net_log::NetExportFileWriter* file_writer_;
 
-  ScopedObserver<net_log::NetExportFileWriter,
-                 net_log::NetExportFileWriter::StateObserver>
-      state_observer_manager_;
+  base::ScopedObservation<net_log::NetExportFileWriter,
+                          net_log::NetExportFileWriter::StateObserver>
+      state_observation_manager_{this};
 
   base::WeakPtrFactory<NetExportMessageHandler> weak_ptr_factory_;
 
@@ -92,7 +92,6 @@
 
 NetExportMessageHandler::NetExportMessageHandler()
     : file_writer_(GetApplicationContext()->GetNetExportFileWriter()),
-      state_observer_manager_(this),
       weak_ptr_factory_(this) {
   file_writer_->Initialize();
 }
@@ -125,8 +124,8 @@
 void NetExportMessageHandler::OnEnableNotifyUIWithState(
     const base::ListValue* list) {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
-  if (!state_observer_manager_.IsObservingSources()) {
-    state_observer_manager_.Add(file_writer_);
+  if (!state_observation_manager_.IsObserving()) {
+    state_observation_manager_.Observe(file_writer_);
   }
   NotifyUIWithState(file_writer_->GetState());
 }
diff --git a/media/base/win/dxgi_device_manager.h b/media/base/win/dxgi_device_manager.h
index d489b316..98e8301 100644
--- a/media/base/win/dxgi_device_manager.h
+++ b/media/base/win/dxgi_device_manager.h
@@ -33,7 +33,7 @@
 };
 
 class MF_INITIALIZER_EXPORT DXGIDeviceManager
-    : public base::RefCounted<DXGIDeviceManager> {
+    : public base::RefCountedThreadSafe<DXGIDeviceManager> {
  public:
   DXGIDeviceManager(const DXGIDeviceManager&) = delete;
   DXGIDeviceManager& operator=(const DXGIDeviceManager&) = delete;
@@ -61,7 +61,7 @@
   Microsoft::WRL::ComPtr<IMFDXGIDeviceManager> GetMFDXGIDeviceManager();
 
  protected:
-  friend class base::RefCounted<DXGIDeviceManager>;
+  friend class base::RefCountedThreadSafe<DXGIDeviceManager>;
   DXGIDeviceManager(
       Microsoft::WRL::ComPtr<IMFDXGIDeviceManager> mf_dxgi_device_manager,
       UINT d3d_device_reset_token);
diff --git a/media/filters/BUILD.gn b/media/filters/BUILD.gn
index 3cc37a43..9f7dc8c 100644
--- a/media/filters/BUILD.gn
+++ b/media/filters/BUILD.gn
@@ -11,6 +11,7 @@
   # should be using //media and not directly DEP this roll-up target.
   visibility = [
     "//media",
+    "//media/fuchsia/audio",
     "//media/renderers",
   ]
 
diff --git a/media/fuchsia/audio/BUILD.gn b/media/fuchsia/audio/BUILD.gn
index eb54727..618fdd7 100644
--- a/media/fuchsia/audio/BUILD.gn
+++ b/media/fuchsia/audio/BUILD.gn
@@ -7,11 +7,16 @@
 
   deps = [
     "//base",
-    "//media",
+    "//media/base",
+    "//media/filters",
+    "//media/fuchsia/cdm",
+    "//media/fuchsia/common",
     "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.media.audio",
     "//third_party/fuchsia-sdk/sdk/pkg/sys_cpp",
   ]
 
+  configs += [ "//media:subcomponent_config" ]
+
   sources = [
     "fuchsia_audio_capturer_source.cc",
     "fuchsia_audio_capturer_source.h",
@@ -40,7 +45,6 @@
   testonly = true
 
   deps = [
-    ":audio",
     ":test_support",
     "//base",
     "//base/test:test_support",
@@ -52,5 +56,6 @@
   sources = [
     "fuchsia_audio_capturer_source_test.cc",
     "fuchsia_audio_output_device_test.cc",
+    "fuchsia_audio_renderer_test.cc",
   ]
 }
diff --git a/media/fuchsia/audio/fuchsia_audio_capturer_source.h b/media/fuchsia/audio/fuchsia_audio_capturer_source.h
index 58fb18f..43563e9 100644
--- a/media/fuchsia/audio/fuchsia_audio_capturer_source.h
+++ b/media/fuchsia/audio/fuchsia_audio_capturer_source.h
@@ -10,10 +10,11 @@
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
 #include "media/base/audio_capturer_source.h"
+#include "media/base/media_export.h"
 
 namespace media {
 
-class FuchsiaAudioCapturerSource : public AudioCapturerSource {
+class MEDIA_EXPORT FuchsiaAudioCapturerSource : public AudioCapturerSource {
  public:
   explicit FuchsiaAudioCapturerSource(
       fidl::InterfaceHandle<fuchsia::media::AudioCapturer> capturer_handle);
diff --git a/media/fuchsia/audio/fuchsia_audio_output_device.h b/media/fuchsia/audio/fuchsia_audio_output_device.h
index a08617ad..7c16229 100644
--- a/media/fuchsia/audio/fuchsia_audio_output_device.h
+++ b/media/fuchsia/audio/fuchsia_audio_output_device.h
@@ -16,6 +16,7 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "media/base/audio_renderer_sink.h"
+#include "media/base/media_export.h"
 
 namespace base {
 class SingleThreadTaskRunner;
@@ -31,7 +32,7 @@
 // All work is performed on the TaskRunner passed to Create(). It must be an IO
 // thread to allow FIDL usage. AudioRendererSink can be used on a different
 // thread.
-class FuchsiaAudioOutputDevice : public AudioRendererSink {
+class MEDIA_EXPORT FuchsiaAudioOutputDevice : public AudioRendererSink {
  public:
   static scoped_refptr<FuchsiaAudioOutputDevice> Create(
       fidl::InterfaceHandle<fuchsia::media::AudioConsumer>
diff --git a/media/fuchsia/audio/fuchsia_audio_renderer.cc b/media/fuchsia/audio/fuchsia_audio_renderer.cc
index 8adadab..f47f525 100644
--- a/media/fuchsia/audio/fuchsia_audio_renderer.cc
+++ b/media/fuchsia/audio/fuchsia_audio_renderer.cc
@@ -14,7 +14,8 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/renderer_client.h"
-#include "media/filters/decrypting_demuxer_stream.h"
+#include "media/fuchsia/common/decrypting_sysmem_buffer_stream.h"
+#include "media/fuchsia/common/passthrough_sysmem_buffer_stream.h"
 
 namespace media {
 
@@ -85,12 +86,10 @@
 FuchsiaAudioRenderer::FuchsiaAudioRenderer(
     MediaLog* media_log,
     fidl::InterfaceHandle<fuchsia::media::AudioConsumer> audio_consumer_handle)
-    : media_log_(media_log),
-      audio_consumer_handle_(std::move(audio_consumer_handle)) {
+    : audio_consumer_handle_(std::move(audio_consumer_handle)) {
   DETACH_FROM_THREAD(thread_checker_);
 }
 
-
 FuchsiaAudioRenderer::~FuchsiaAudioRenderer() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 }
@@ -105,6 +104,7 @@
   DCHECK(!init_cb_);
   init_cb_ = std::move(init_cb);
 
+  demuxer_stream_ = stream;
   client_ = client;
 
   audio_consumer_.Bind(std::move(audio_consumer_handle_));
@@ -118,8 +118,6 @@
   audio_consumer_.events().OnEndOfStream = [this]() { OnEndOfStream(); };
   RequestAudioConsumerStatus();
 
-  InitializeStreamSink(stream->audio_decoder_config());
-
   // AAC streams require bitstream conversion. Without it the demuxer may
   // produce decoded stream without ADTS headers which are required for AAC
   // streams in AudioConsumer.
@@ -128,21 +126,17 @@
     stream->EnableBitstreamConverter();
   }
 
-  // DecryptingDemuxerStream handles both encrypted and clear streams, so
-  // initialize it long as we have cdm_context.
+  // DecryptingSysmemBufferStream handles both encrypted and clear streams, so
+  // initialize it long as we have |cdm_context|.
   if (cdm_context) {
-    WaitingCB waiting_cb = base::BindRepeating(&RendererClient::OnWaiting,
-                                               base::Unretained(client_));
-    decrypting_demuxer_stream_ = std::make_unique<DecryptingDemuxerStream>(
-        base::ThreadTaskRunnerHandle::Get(), media_log_, waiting_cb);
-    decrypting_demuxer_stream_->Initialize(
-        stream, cdm_context,
-        base::BindRepeating(&FuchsiaAudioRenderer::OnDecryptorInitialized,
-                            base::Unretained(this)));
-    return;
+    sysmem_buffer_stream_ = std::make_unique<DecryptingSysmemBufferStream>(
+        &sysmem_allocator_, cdm_context, Decryptor::kAudio);
+  } else {
+    sysmem_buffer_stream_ =
+        std::make_unique<PassthroughSysmemBufferStream>(&sysmem_allocator_);
   }
 
-  demuxer_stream_ = stream;
+  sysmem_buffer_stream_->Initialize(this, kBufferSize, kNumBuffers);
 
   std::move(init_cb_).Run(PIPELINE_OK);
 }
@@ -159,53 +153,37 @@
   volume_control_->SetVolume(volume_);
 }
 
-void FuchsiaAudioRenderer::InitializeStreamSink(
-    const AudioDecoderConfig& config) {
+void FuchsiaAudioRenderer::OnBuffersAcquired(
+    std::vector<VmoBuffer> buffers,
+    const fuchsia::sysmem::SingleBufferSettings&) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  input_buffers_ = std::move(buffers);
+  InitializeStreamSink();
+
+  while (!delayed_packets_.empty()) {
+    auto packet = std::move(delayed_packets_.front());
+    delayed_packets_.pop_front();
+    SendInputPacket(std::move(packet));
+  }
+}
+
+void FuchsiaAudioRenderer::InitializeStreamSink() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!stream_sink_);
 
-  // Allocate input buffers for the StreamSink.
-  std::vector<VmoBuffer> buffers;
-  buffers.resize(kNumBuffers);
-
+  // Clone |buffers| to pass to StreamSink.
   std::vector<zx::vmo> vmos_for_stream_sink;
-  vmos_for_stream_sink.reserve(kNumBuffers);
-  for (VmoBuffer& buffer : buffers) {
-    zx::vmo vmo;
-    zx_status_t status = zx::vmo::create(kBufferSize, 0, &vmo);
-    ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
-
-    constexpr char kName[] = "cr-audio-renderer";
-    status = vmo.set_property(ZX_PROP_NAME, kName, base::size(kName) - 1);
-    ZX_DCHECK(status == ZX_OK, status);
-
-    // Duplicate VMO handle to pass to AudioConsumer.
-    zx::vmo readonly_vmo;
-    status =
-        vmo.duplicate(ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER | ZX_RIGHT_READ |
-                          ZX_RIGHT_MAP | ZX_RIGHT_GET_PROPERTY,
-                      &readonly_vmo);
-    ZX_CHECK(status == ZX_OK, status) << "zx_handle_duplicate";
-
-    bool buffer_initialized =
-        buffer.Initialize(std::move(vmo), /*writable=*/true, /*offset=*/0,
-                          kBufferSize, fuchsia::sysmem::CoherencyDomain::RAM);
-    CHECK(buffer_initialized);
-
-    vmos_for_stream_sink.push_back(std::move(readonly_vmo));
+  vmos_for_stream_sink.reserve(input_buffers_.size());
+  for (VmoBuffer& buffer : input_buffers_) {
+    vmos_for_stream_sink.push_back(buffer.Duplicate(/*writable=*/false));
   }
 
-  input_queue_.Start(
-      std::move(buffers),
-      base::BindRepeating(&FuchsiaAudioRenderer::SendInputPacket,
-                          base::Unretained(this)),
-      base::BindRepeating(&FuchsiaAudioRenderer::ProcessEndOfStream,
-                          base::Unretained(this)));
-
+  auto config = demuxer_stream_->audio_decoder_config();
   auto compression = GetFuchsiaCompressionFromDecoderConfig(config);
   if (!compression) {
     LOG(ERROR) << "Unsupported audio codec: " << GetCodecName(config.codec());
-    std::move(init_cb_).Run(AUDIO_RENDERER_ERROR);
+    OnError(AUDIO_RENDERER_ERROR);
     return;
   }
 
@@ -220,7 +198,7 @@
     if (!sample_format) {
       LOG(ERROR) << "Unsupported sample format: "
                  << SampleFormatToString(config.sample_format());
-      std::move(init_cb_).Run(AUDIO_RENDERER_ERROR);
+      OnError(AUDIO_RENDERER_ERROR);
       return;
     }
     stream_type.sample_format = sample_format.value();
@@ -233,6 +211,8 @@
   audio_consumer_->CreateStreamSink(
       std::move(vmos_for_stream_sink), std::move(stream_type),
       std::move(compression).value(), stream_sink_.NewRequest());
+
+  ScheduleReadDemuxerStream();
 }
 
 TimeSource* FuchsiaAudioRenderer::GetTimeSource() {
@@ -243,12 +223,15 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   FlushInternal();
+  renderer_started_ = false;
+
   std::move(callback).Run();
 }
 
 void FuchsiaAudioRenderer::StartPlaying() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
+  renderer_started_ = true;
   ScheduleReadDemuxerStream();
 }
 
@@ -396,6 +379,8 @@
 
   audio_consumer_.Unbind();
   stream_sink_.Unbind();
+  sysmem_buffer_stream_.reset();
+
   if (init_cb_) {
     std::move(init_cb_).Run(status);
   } else if (client_) {
@@ -403,22 +388,6 @@
   }
 }
 
-void FuchsiaAudioRenderer::OnDecryptorInitialized(PipelineStatus status) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  // |init_cb_| may be cleared in OnError(), e.g. if AudioConsumer was
-  // disconnected.
-  if (!init_cb_) {
-    return;
-  }
-
-  if (status == PIPELINE_OK) {
-    demuxer_stream_ = decrypting_demuxer_stream_.get();
-  }
-
-  std::move(init_cb_).Run(status);
-}
-
 void FuchsiaAudioRenderer::RequestAudioConsumerStatus() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   audio_consumer_->WatchStatus(fit::bind_member(
@@ -435,6 +404,8 @@
     return;
   }
 
+  bool reschedule_read_timer = false;
+
   if (status.has_presentation_timeline()) {
     if (GetPlaybackState() != PlaybackState::kStopped) {
       base::AutoLock lock(timeline_lock_);
@@ -447,10 +418,11 @@
           status.presentation_timeline().subject_time);
       reference_delta_ = status.presentation_timeline().reference_delta;
       media_delta_ = status.presentation_timeline().subject_delta;
+
+      reschedule_read_timer = true;
     }
   }
 
-  bool reschedule_read_timer = false;
   if (status.has_min_lead_time()) {
     auto new_min_lead_time =
         base::TimeDelta::FromZxDuration(status.min_lead_time());
@@ -481,20 +453,27 @@
 void FuchsiaAudioRenderer::ScheduleReadDemuxerStream() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (!demuxer_stream_ || read_timer_.IsRunning() || is_demuxer_read_pending_ ||
-      is_at_end_of_stream_ || input_queue_.IsBlocked()) {
+  if (!renderer_started_ || !demuxer_stream_ || read_timer_.IsRunning() ||
+      is_demuxer_read_pending_ || is_at_end_of_stream_) {
     return;
   }
 
   base::TimeDelta next_read_delay;
   if (!last_packet_timestamp_.is_min()) {
-    auto relative_buffer_pos = last_packet_timestamp_ - CurrentMediaTime();
+    std::vector<base::TimeTicks> wall_clock_times;
+    bool is_time_moving =
+        GetWallClockTimes({last_packet_timestamp_}, &wall_clock_times);
+    base::TimeDelta relative_buffer_pos =
+        wall_clock_times[0] - base::TimeTicks::Now();
 
-    if (!min_lead_time_.is_zero() && relative_buffer_pos > min_lead_time_) {
-      SetBufferState(BUFFERING_HAVE_ENOUGH);
-    }
+    // Check if we have buffered more than |max_lead_time_|.
+    if (relative_buffer_pos >= max_lead_time_) {
+      // If playback is not active then there is no need to buffer more.
+      if (!is_time_moving)
+        return;
 
-    if (!max_lead_time_.is_zero() && relative_buffer_pos > max_lead_time_) {
+      // If the buffer is larger than |max_lead_time_|, then the next read
+      // should be delayed.
       next_read_delay = relative_buffer_pos - max_lead_time_;
     }
   }
@@ -534,10 +513,10 @@
       OnError(PIPELINE_ERROR_READ);
     } else if (read_status == DemuxerStream::kConfigChanged) {
       stream_sink_.Unbind();
-      input_queue_.ResetBuffers();
+      sysmem_buffer_stream_->Reset();
 
-      InitializeStreamSink(demuxer_stream_->audio_decoder_config());
-      ScheduleReadDemuxerStream();
+      InitializeStreamSink();
+      client_->OnAudioConfigChange(demuxer_stream_->audio_decoder_config());
     } else {
       DCHECK_EQ(read_status, DemuxerStream::kAborted);
     }
@@ -552,16 +531,15 @@
       return;
     }
 
-    last_packet_timestamp_ = buffer->timestamp();
+    last_packet_timestamp_ = buffer->timestamp() + buffer->duration();
   }
 
-  input_queue_.EnqueueBuffer(std::move(buffer));
+  sysmem_buffer_stream_->EnqueueBuffer(std::move(buffer));
 
   ScheduleReadDemuxerStream();
 }
 
 void FuchsiaAudioRenderer::SendInputPacket(
-    const DecoderBuffer* buffer,
     StreamProcessorHelper::IoPacket packet) {
   fuchsia::media::StreamPacket stream_packet;
   stream_packet.payload_buffer_id = packet.buffer_index();
@@ -584,23 +562,20 @@
   client_->OnStatisticsUpdate(stats);
 }
 
-void FuchsiaAudioRenderer::ProcessEndOfStream() {
-  is_at_end_of_stream_ = true;
-  stream_sink_->EndOfStream();
-
-  // No more data is going to be buffered. Update buffering state to ensure
-  // RendererImpl starts playback in case it was waiting for buffering to
-  // finish.
-  SetBufferState(BUFFERING_HAVE_ENOUGH);
-}
-
 void FuchsiaAudioRenderer::OnStreamSendDone(
     std::unique_ptr<StreamProcessorHelper::IoPacket> packet) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  // Release the packet first to ensure it's returned to VmoBufferWriterQueue
-  // before ScheduleReadDemuxerStream().
-  packet.reset();
+  // Check if we need to update buffering state after sending more than
+  // |min_lead_time_| to the AudioConsumer.
+  if (buffer_state_ == BUFFERING_HAVE_NOTHING) {
+    std::vector<base::TimeTicks> wall_clock_times;
+    GetWallClockTimes({packet->timestamp()}, &wall_clock_times);
+    base::TimeDelta relative_buffer_pos =
+        wall_clock_times[0] - base::TimeTicks::Now();
+    if (relative_buffer_pos >= min_lead_time_)
+      SetBufferState(BUFFERING_HAVE_ENOUGH);
+  }
 
   ScheduleReadDemuxerStream();
 }
@@ -617,7 +592,9 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(GetPlaybackState() == PlaybackState::kStopped || is_at_end_of_stream_);
 
-  stream_sink_->DiscardAllPacketsNoReply();
+  if (stream_sink_)
+    stream_sink_->DiscardAllPacketsNoReply();
+
   SetBufferState(BUFFERING_HAVE_NOTHING);
   last_packet_timestamp_ = base::TimeDelta::Min();
   read_timer_.Stop();
@@ -655,4 +632,60 @@
                           media_delta_ / reference_delta_;
 }
 
+void FuchsiaAudioRenderer::OnSysmemBufferStreamBufferCollectionToken(
+    fuchsia::sysmem::BufferCollectionTokenPtr token) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  // Drop old buffers.
+  input_buffers_.clear();
+  stream_sink_.Unbind();
+
+  // Acquire buffers for the new buffer collection.
+  input_buffer_collection_ =
+      sysmem_allocator_.BindSharedCollection(std::move(token));
+  fuchsia::sysmem::BufferCollectionConstraints buffer_constraints =
+      VmoBuffer::GetRecommendedConstraints(kNumBuffers, kBufferSize,
+                                           /*writable=*/false);
+  input_buffer_collection_->Initialize(std::move(buffer_constraints),
+                                       "CrAudioRenderer");
+  input_buffer_collection_->AcquireBuffers(base::BindOnce(
+      &FuchsiaAudioRenderer::OnBuffersAcquired, base::Unretained(this)));
+}
+
+void FuchsiaAudioRenderer::OnSysmemBufferStreamOutputPacket(
+    StreamProcessorHelper::IoPacket packet) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (stream_sink_) {
+    SendInputPacket(std::move(packet));
+  } else {
+    // The packet will be sent after StreamSink is connected.
+    delayed_packets_.push_back(std::move(packet));
+  }
+
+  ScheduleReadDemuxerStream();
+}
+
+void FuchsiaAudioRenderer::OnSysmemBufferStreamEndOfStream() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  is_at_end_of_stream_ = true;
+  stream_sink_->EndOfStream();
+
+  // No more data is going to be buffered. Update buffering state to ensure
+  // RendererImpl starts playback in case it was waiting for buffering to
+  // finish.
+  SetBufferState(BUFFERING_HAVE_ENOUGH);
+}
+
+void FuchsiaAudioRenderer::OnSysmemBufferStreamError() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  OnError(AUDIO_RENDERER_ERROR);
+}
+
+void FuchsiaAudioRenderer::OnSysmemBufferStreamNoKey() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  client_->OnWaiting(WaitingReason::kNoDecryptionKey);
+}
+
 }  // namespace media
diff --git a/media/fuchsia/audio/fuchsia_audio_renderer.h b/media/fuchsia/audio/fuchsia_audio_renderer.h
index e13ed2a5..d68d88b 100644
--- a/media/fuchsia/audio/fuchsia_audio_renderer.h
+++ b/media/fuchsia/audio/fuchsia_audio_renderer.h
@@ -13,18 +13,22 @@
 #include "media/base/audio_renderer.h"
 #include "media/base/buffering_state.h"
 #include "media/base/demuxer_stream.h"
+#include "media/base/media_export.h"
 #include "media/base/time_source.h"
-#include "media/fuchsia/common/vmo_buffer_writer_queue.h"
+#include "media/fuchsia/common/sysmem_buffer_stream.h"
+#include "media/fuchsia/common/sysmem_client.h"
+#include "media/fuchsia/common/vmo_buffer.h"
 
 namespace media {
 
-class DecryptingDemuxerStream;
 class MediaLog;
 
 // AudioRenderer implementation that output audio to AudioConsumer interface on
 // Fuchsia. Unlike the default AudioRendererImpl it doesn't decode audio and
 // sends encoded stream directly to AudioConsumer provided by the platform.
-class FuchsiaAudioRenderer : public AudioRenderer, public TimeSource {
+class MEDIA_EXPORT FuchsiaAudioRenderer : public AudioRenderer,
+                                          public TimeSource,
+                                          public SysmemBufferStream::Sink {
  public:
   FuchsiaAudioRenderer(MediaLog* media_log,
                        fidl::InterfaceHandle<fuchsia::media::AudioConsumer>
@@ -82,12 +86,14 @@
   // |volume_|.
   void UpdateVolume();
 
+  // Callback for input_buffer_collection_.AcquireBuffers().
+  void OnBuffersAcquired(
+      std::vector<VmoBuffer> buffers,
+      const fuchsia::sysmem::SingleBufferSettings& buffer_settings);
+
   // Initializes |stream_sink_|. Called during initialization and every time
   // configuration changes.
-  void InitializeStreamSink(const AudioDecoderConfig& config);
-
-  // Callback for DecryptingDemuxerStream::Initialize().
-  void OnDecryptorInitialized(PipelineStatus status);
+  void InitializeStreamSink();
 
   // Helpers to receive AudioConsumerStatus from the |audio_consumer_|.
   void RequestAudioConsumerStatus();
@@ -99,10 +105,8 @@
   void OnDemuxerStreamReadDone(DemuxerStream::Status status,
                                scoped_refptr<DecoderBuffer> buffer);
 
-  // Callbacks for VmoBufferWriterQueue.
-  void SendInputPacket(const DecoderBuffer* buffer,
-                       StreamProcessorHelper::IoPacket packet);
-  void ProcessEndOfStream();
+  // Sends the specified packet to |stream_sink_|.
+  void SendInputPacket(StreamProcessorHelper::IoPacket packet);
 
   // Result handler for StreamSink::SendPacket().
   void OnStreamSendDone(
@@ -132,7 +136,14 @@
   base::TimeDelta CurrentMediaTimeLocked()
       EXCLUSIVE_LOCKS_REQUIRED(timeline_lock_);
 
-  MediaLog* const media_log_;
+  // SysmemBufferStream::Sink implementation.
+  void OnSysmemBufferStreamBufferCollectionToken(
+      fuchsia::sysmem::BufferCollectionTokenPtr token) override;
+  void OnSysmemBufferStreamOutputPacket(
+      StreamProcessorHelper::IoPacket packet) override;
+  void OnSysmemBufferStreamEndOfStream() override;
+  void OnSysmemBufferStreamError() override;
+  void OnSysmemBufferStreamNoKey() override;
 
   // Handle for |audio_consumer_|. It's stored here until Initialize() is
   // called.
@@ -153,19 +164,32 @@
   // Initialize() completion callback.
   PipelineStatusCallback init_cb_;
 
-  std::unique_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream_;
+  // Indicates that StartPlaying() has been called. Note that playback doesn't
+  // start until TimeSource::StartTicking() is called.
+  bool renderer_started_ = false;
 
   BufferingState buffer_state_ = BUFFERING_HAVE_NOTHING;
 
   base::TimeDelta last_packet_timestamp_ = base::TimeDelta::Min();
   base::OneShotTimer read_timer_;
 
-  VmoBufferWriterQueue input_queue_;
+  SysmemAllocatorClient sysmem_allocator_{"CrFuchsiaAudioRenderer"};
+  std::unique_ptr<SysmemCollectionClient> input_buffer_collection_;
 
-  // Lead time range requested by the |audio_consumer_|. Initialized to 0 until
-  // the initial AudioConsumerStatus is received.
-  base::TimeDelta min_lead_time_;
-  base::TimeDelta max_lead_time_;
+  std::unique_ptr<SysmemBufferStream> sysmem_buffer_stream_;
+
+  // VmoBuffers for the buffers |input_buffer_collection_|.
+  std::vector<VmoBuffer> input_buffers_;
+
+  // Packets produced before the |stream_sink_| is connected. They are sent as
+  // soon as input buffers are acquired and |stream_sink_| is connected in
+  // OnBuffersAcquired().
+  std::list<StreamProcessorHelper::IoPacket> delayed_packets_;
+
+  // Lead time range requested by the |audio_consumer_|. Initialized to  the
+  // [100ms, 500ms] until the initial AudioConsumerStatus is received.
+  base::TimeDelta min_lead_time_ = base::TimeDelta::FromMilliseconds(100);
+  base::TimeDelta max_lead_time_ = base::TimeDelta::FromMilliseconds(500);
 
   // Set to true after we've received end-of-stream from the |demuxer_stream_|.
   // The renderer may be restarted after Flush().
diff --git a/media/fuchsia/audio/fuchsia_audio_renderer_test.cc b/media/fuchsia/audio/fuchsia_audio_renderer_test.cc
new file mode 100644
index 0000000..3585253
--- /dev/null
+++ b/media/fuchsia/audio/fuchsia_audio_renderer_test.cc
@@ -0,0 +1,852 @@
+// 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 "media/fuchsia/audio/fuchsia_audio_renderer.h"
+
+#include <fuchsia/media/audio/cpp/fidl_test_base.h>
+#include <fuchsia/media/cpp/fidl_test_base.h>
+#include <lib/fidl/cpp/binding.h>
+
+#include "base/containers/queue.h"
+#include "base/logging.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/renderer_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+namespace {
+
+constexpr int kDefaultSampleRate = 48000;
+constexpr base::TimeDelta kPacketDuration =
+    base::TimeDelta::FromMilliseconds(20);
+constexpr base::TimeDelta kMinLeadTime = base::TimeDelta::FromMilliseconds(100);
+constexpr base::TimeDelta kMaxLeadTime = base::TimeDelta::FromMilliseconds(500);
+const base::TimeDelta kTimeStep = base::TimeDelta::FromMilliseconds(2);
+
+class TestDemuxerStream : public DemuxerStream {
+ public:
+  struct ReadResult {
+    explicit ReadResult(scoped_refptr<DecoderBuffer> buffer) : buffer(buffer) {}
+    explicit ReadResult(const AudioDecoderConfig& config) : config(config) {}
+
+    Status status;
+    absl::optional<AudioDecoderConfig> config;
+    scoped_refptr<DecoderBuffer> buffer;
+  };
+
+  explicit TestDemuxerStream(const AudioDecoderConfig& config)
+      : config_(config) {}
+
+  ~TestDemuxerStream() override {}
+
+  void QueueReadResult(ReadResult read_result) {
+    CHECK(read_result.config.has_value() == !read_result.buffer);
+    read_queue_.push(std::move(read_result));
+    SatisfyRead();
+  }
+
+  void DiscardQueueAndAbortRead() {
+    while (!read_queue_.empty())
+      read_queue_.pop();
+    if (read_cb_)
+      std::move(read_cb_).Run(kAborted, nullptr);
+  }
+
+  bool is_read_pending() const { return !!read_cb_; }
+
+  // DemuxerStream implementation.
+  void Read(ReadCB read_cb) override {
+    read_cb_ = std::move(read_cb);
+    SatisfyRead();
+  }
+  AudioDecoderConfig audio_decoder_config() override { return config_; }
+  VideoDecoderConfig video_decoder_config() override {
+    NOTREACHED();
+    return VideoDecoderConfig();
+  }
+  Type type() const override { return AUDIO; }
+  Liveness liveness() const override { return LIVENESS_RECORDED; }
+  bool SupportsConfigChanges() override { return true; }
+
+ private:
+  void SatisfyRead() {
+    if (read_queue_.empty() || !read_cb_)
+      return;
+
+    auto result = std::move(read_queue_.front());
+    read_queue_.pop();
+
+    Status status;
+    if (result.config) {
+      config_ = result.config.value();
+      status = kConfigChanged;
+    } else {
+      status = kOk;
+    }
+
+    std::move(read_cb_).Run(status, result.buffer);
+  }
+
+  AudioDecoderConfig config_;
+  ReadCB read_cb_;
+
+  base::queue<ReadResult> read_queue_;
+};
+
+class TestStreamSink : public fuchsia::media::testing::StreamSink_TestBase {
+ public:
+  TestStreamSink(
+      std::vector<zx::vmo> buffers,
+      fuchsia::media::AudioStreamType stream_type,
+      std::unique_ptr<fuchsia::media::Compression> compression,
+      fidl::InterfaceRequest<fuchsia::media::StreamSink> stream_sink_request)
+      : binding_(this, std::move(stream_sink_request)),
+        buffers_(std::move(buffers)),
+        stream_type_(std::move(stream_type)),
+        compression_(std::move(compression)) {}
+
+  const std::vector<zx::vmo>& buffers() const { return buffers_; }
+  const fuchsia::media::AudioStreamType& stream_type() const {
+    return stream_type_;
+  }
+  const fuchsia::media::Compression* compression() const {
+    return compression_.get();
+  }
+
+  std::vector<fuchsia::media::StreamPacket>* received_packets() {
+    return &received_packets_;
+  }
+
+  std::vector<fuchsia::media::StreamPacket>* discarded_packets() {
+    return &discarded_packets_;
+  }
+
+  bool received_end_of_stream() const { return received_end_of_stream_; }
+
+  // fuchsia::media::StreamSink overrides.
+  void SendPacket(fuchsia::media::StreamPacket packet,
+                  SendPacketCallback callback) override {
+    EXPECT_FALSE(received_end_of_stream_);
+    received_packets_.push_back(std::move(packet));
+    callback();
+  }
+  void EndOfStream() override { received_end_of_stream_ = true; }
+  void DiscardAllPackets(DiscardAllPacketsCallback callback) override {
+    DiscardAllPacketsNoReply();
+    callback();
+  }
+  void DiscardAllPacketsNoReply() override {
+    std::move(std::begin(received_packets_), std::end(received_packets_),
+              std::back_inserter(discarded_packets_));
+    received_packets_.clear();
+  }
+
+  // Other methods are not expected to be called.
+  void NotImplemented_(const std::string& name) final {
+    FAIL() << ": " << name;
+  }
+
+ private:
+  fidl::Binding<fuchsia::media::StreamSink> binding_;
+
+  std::vector<zx::vmo> buffers_;
+  fuchsia::media::AudioStreamType stream_type_;
+  std::unique_ptr<fuchsia::media::Compression> compression_;
+
+  std::vector<fuchsia::media::StreamPacket> received_packets_;
+  std::vector<fuchsia::media::StreamPacket> discarded_packets_;
+
+  bool received_end_of_stream_ = false;
+};
+
+class TestAudioConsumer
+    : public fuchsia::media::testing::AudioConsumer_TestBase,
+      public fuchsia::media::audio::testing::VolumeControl_TestBase {
+ public:
+  explicit TestAudioConsumer(
+      fidl::InterfaceRequest<fuchsia::media::AudioConsumer> request)
+      : binding_(this, std::move(request)), volume_control_binding_(this) {}
+
+  std::unique_ptr<TestStreamSink> TakeStreamSink() {
+    return std::move(stream_sink_);
+  }
+
+  std::unique_ptr<TestStreamSink> WaitStreamSinkConnected() {
+    if (!stream_sink_) {
+      base::RunLoop run_loop;
+      stream_sink_created_loop_ = &run_loop;
+      run_loop.Run();
+      stream_sink_created_loop_ = nullptr;
+    }
+    EXPECT_TRUE(stream_sink_);
+    return TakeStreamSink();
+  }
+
+  void UpdateStatus(absl::optional<base::TimeTicks> reference_time,
+                    absl::optional<base::TimeDelta> media_time) {
+    fuchsia::media::AudioConsumerStatus status;
+    if (reference_time) {
+      CHECK(media_time);
+
+      fuchsia::media::TimelineFunction timeline;
+      timeline.reference_time = reference_time->ToZxTime();
+      timeline.subject_time = media_time->ToZxDuration();
+      timeline.reference_delta = 1000;
+      timeline.subject_delta = static_cast<int>(playback_rate_ * 1000.0);
+      status.set_presentation_timeline(std::move(timeline));
+    }
+
+    status.set_min_lead_time(kMinLeadTime.ToZxDuration());
+    status.set_max_lead_time(kMaxLeadTime.ToZxDuration());
+    status_update_ = std::move(status);
+
+    if (status_callback_)
+      CallStatusCallback();
+  }
+
+  void SignalEndOfStream() { binding_.events().OnEndOfStream(); }
+
+  bool started() const { return started_; }
+  base::TimeDelta start_media_time() const { return start_media_time_; }
+  float playback_rate() const { return playback_rate_; }
+  float volume() const { return volume_; }
+
+  // fuchsia::media::AudioConsumer overrides.
+  void CreateStreamSink(
+      std::vector<zx::vmo> buffers,
+      fuchsia::media::AudioStreamType stream_type,
+      std::unique_ptr<fuchsia::media::Compression> compression,
+      fidl::InterfaceRequest<fuchsia::media::StreamSink> stream_sink_request)
+      override {
+    stream_sink_ = std::make_unique<TestStreamSink>(
+        std::move(buffers), std::move(stream_type), std::move(compression),
+        std::move(stream_sink_request));
+    if (stream_sink_created_loop_)
+      stream_sink_created_loop_->Quit();
+  }
+
+  void Start(fuchsia::media::AudioConsumerStartFlags flags,
+             int64_t reference_time,
+             int64_t media_time) override {
+    EXPECT_FALSE(started_);
+    EXPECT_EQ(reference_time, fuchsia::media::NO_TIMESTAMP);
+    started_ = true;
+    start_media_time_ = base::TimeDelta::FromZxDuration(media_time);
+  }
+
+  void Stop() override {
+    EXPECT_TRUE(started_);
+    started_ = false;
+  }
+
+  void SetRate(float rate) override { playback_rate_ = rate; }
+
+  void BindVolumeControl(
+      fidl::InterfaceRequest<fuchsia::media::audio::VolumeControl>
+          volume_control_request) override {
+    volume_control_binding_.Bind(std::move(volume_control_request));
+  }
+  void WatchStatus(WatchStatusCallback callback) override {
+    EXPECT_FALSE(!!status_callback_);
+
+    status_callback_ = std::move(callback);
+
+    if (status_update_) {
+      CallStatusCallback();
+    }
+  }
+
+  // fuchsia::media::audio::VolumeControl overrides.
+  void SetVolume(float volume) override { volume_ = volume; }
+
+  // Other methods are not expected to be called.
+  void NotImplemented_(const std::string& name) final {
+    FAIL() << ": " << name;
+  }
+
+ private:
+  void CallStatusCallback() {
+    EXPECT_TRUE(status_callback_);
+    EXPECT_TRUE(status_update_);
+
+    std::move(status_callback_)(std::move(status_update_.value()));
+    status_callback_ = {};
+    status_update_ = absl::nullopt;
+  }
+
+  fidl::Binding<fuchsia::media::AudioConsumer> binding_;
+  fidl::Binding<fuchsia::media::audio::VolumeControl> volume_control_binding_;
+  std::unique_ptr<TestStreamSink> stream_sink_;
+
+  base::RunLoop* stream_sink_created_loop_ = nullptr;
+
+  WatchStatusCallback status_callback_;
+  absl::optional<fuchsia::media::AudioConsumerStatus> status_update_;
+
+  bool started_ = false;
+  base::TimeDelta start_media_time_;
+
+  float playback_rate_ = 1.0;
+
+  float volume_ = 1.0;
+};
+
+class TestRendererClient : public RendererClient {
+ public:
+  TestRendererClient() = default;
+  ~TestRendererClient() {
+    EXPECT_EQ(expected_error_, PIPELINE_OK);
+    EXPECT_FALSE(expect_eos_);
+  }
+
+  void ExpectError(PipelineStatus expected_error) {
+    EXPECT_EQ(expected_error_, PIPELINE_OK);
+    expected_error_ = expected_error;
+  }
+
+  void ExpectEos() {
+    EXPECT_FALSE(expect_eos_);
+    expect_eos_ = true;
+  }
+
+  BufferingState buffering_state() const { return buffering_state_; }
+
+  absl::optional<AudioDecoderConfig> last_config_change() const {
+    return last_config_change_;
+  }
+
+  // RendererClient implementation.
+  void OnError(PipelineStatus status) override {
+    EXPECT_EQ(status, expected_error_);
+    EXPECT_FALSE(have_error_);
+    have_error_ = true;
+    expected_error_ = PIPELINE_OK;
+  }
+
+  void OnEnded() override {
+    EXPECT_TRUE(expect_eos_);
+    expect_eos_ = false;
+  }
+
+  void OnStatisticsUpdate(const PipelineStatistics& stats) override {
+    bytes_decoded_ += stats.audio_bytes_decoded;
+  }
+
+  void OnBufferingStateChange(BufferingState state,
+                              BufferingStateChangeReason reason) override {
+    buffering_state_ = state;
+  }
+
+  void OnWaiting(WaitingReason reason) override {}
+  void OnAudioConfigChange(const AudioDecoderConfig& config) override {
+    last_config_change_ = config;
+  }
+  void OnVideoConfigChange(const VideoDecoderConfig& config) override {
+    FAIL();
+  }
+  void OnVideoNaturalSizeChange(const gfx::Size& size) override { FAIL(); }
+  void OnVideoOpacityChange(bool opaque) override { FAIL(); }
+  void OnVideoFrameRateChange(absl::optional<int> fps) override { FAIL(); }
+
+ private:
+  PipelineStatus expected_error_ = PIPELINE_OK;
+  bool have_error_ = false;
+  bool expect_eos_ = false;
+  BufferingState buffering_state_ = BUFFERING_HAVE_NOTHING;
+  size_t bytes_decoded_ = 0;
+  absl::optional<AudioDecoderConfig> last_config_change_;
+};
+
+}  // namespace
+
+class FuchsiaAudioRendererTest : public testing::Test {
+ public:
+  FuchsiaAudioRendererTest() = default;
+  ~FuchsiaAudioRendererTest() override = default;
+
+  void CreateUninitializedRenderer();
+  void InitializeRenderer();
+  void CreateAndInitializeRenderer();
+  void ProduceDemuxerPacket(base::TimeDelta duration);
+  void FillDemuxerStream(base::TimeDelta end_pos);
+  void FillBuffer();
+  void StartPlayback(base::TimeDelta start_time = base::TimeDelta());
+  void CheckGetWallClockTimes(absl::optional<base::TimeDelta> media_timestamp,
+                              base::TimeTicks expected_wall_clock,
+                              bool is_time_moving);
+
+  // Starts playback from |start_time| at the specified |playback_rate| and
+  // verifies that the clock works correctly.
+  void StartPlaybackAndVerifyClock(base::TimeDelta start_time,
+                                   float playback_rate);
+
+ protected:
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO,
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+  std::unique_ptr<TestAudioConsumer> audio_consumer_;
+  std::unique_ptr<TestStreamSink> stream_sink_;
+  std::unique_ptr<TestDemuxerStream> demuxer_stream_;
+  TestRendererClient client_;
+
+  std::unique_ptr<AudioRenderer> audio_renderer_;
+  TimeSource* time_source_;
+  base::TimeDelta demuxer_stream_pos_;
+};
+
+void FuchsiaAudioRendererTest::CreateUninitializedRenderer() {
+  fidl::InterfaceHandle<fuchsia::media::AudioConsumer> audio_consumer_handle;
+  audio_consumer_ =
+      std::make_unique<TestAudioConsumer>(audio_consumer_handle.NewRequest());
+  audio_renderer_ = std::make_unique<FuchsiaAudioRenderer>(
+      /*media_log=*/nullptr, std::move(audio_consumer_handle));
+  time_source_ = audio_renderer_->GetTimeSource();
+}
+
+void FuchsiaAudioRendererTest::InitializeRenderer() {
+  AudioDecoderConfig config(kCodecPCM, kSampleFormatF32, CHANNEL_LAYOUT_MONO,
+                            kDefaultSampleRate, {},
+                            EncryptionScheme::kUnencrypted);
+  demuxer_stream_ = std::make_unique<TestDemuxerStream>(config);
+
+  base::RunLoop run_loop;
+  PipelineStatus pipeline_status;
+  audio_renderer_->Initialize(
+      demuxer_stream_.get(), /*cdm_context=*/nullptr, &client_,
+      base::BindLambdaForTesting(
+          [&run_loop, &pipeline_status](PipelineStatus s) {
+            pipeline_status = s;
+            run_loop.Quit();
+          }));
+  run_loop.Run();
+
+  ASSERT_EQ(pipeline_status, PIPELINE_OK);
+
+  audio_consumer_->UpdateStatus(absl::nullopt, absl::nullopt);
+
+  task_environment_.RunUntilIdle();
+}
+
+void FuchsiaAudioRendererTest::CreateAndInitializeRenderer() {
+  CreateUninitializedRenderer();
+  InitializeRenderer();
+}
+
+void FuchsiaAudioRendererTest::ProduceDemuxerPacket(base::TimeDelta duration) {
+  // Create a dummy packet that contains just 1 byte.
+  const size_t kBufferSize = 1;
+  scoped_refptr<DecoderBuffer> buffer = new DecoderBuffer(kBufferSize);
+  buffer->set_timestamp(demuxer_stream_pos_);
+  buffer->set_duration(duration);
+  demuxer_stream_pos_ += duration;
+  demuxer_stream_->QueueReadResult(TestDemuxerStream::ReadResult(buffer));
+}
+
+void FuchsiaAudioRendererTest::FillDemuxerStream(base::TimeDelta end_pos) {
+  EXPECT_LT(demuxer_stream_pos_, end_pos);
+  while (demuxer_stream_pos_ < end_pos) {
+    ProduceDemuxerPacket(kPacketDuration);
+  }
+}
+
+void FuchsiaAudioRendererTest::FillBuffer() {
+  if (!stream_sink_) {
+    stream_sink_ = audio_consumer_->WaitStreamSinkConnected();
+  }
+
+  // The renderer expects one extra packet after reaching kMinLeadTime to get
+  // to the BUFFERING_HAVE_ENOUGH state.
+  const size_t kNumPackets = kMinLeadTime / kPacketDuration + 1;
+  for (size_t i = 0; i < kNumPackets; ++i) {
+    ProduceDemuxerPacket(kPacketDuration);
+  }
+  task_environment_.RunUntilIdle();
+
+  // Renderer should not start reading demuxer untile StartPlaying() is
+  // called.
+  EXPECT_EQ(client_.buffering_state(), BUFFERING_HAVE_NOTHING);
+  EXPECT_EQ(stream_sink_->received_packets()->size(), 0U);
+
+  // Start playback. The renderer should push queued packets to the
+  // AudioConsumer and updated buffering state.
+  audio_renderer_->StartPlaying();
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(stream_sink_->received_packets()->size(), kNumPackets);
+  EXPECT_EQ(client_.buffering_state(), BUFFERING_HAVE_ENOUGH);
+}
+
+void FuchsiaAudioRendererTest::StartPlayback(base::TimeDelta start_time) {
+  EXPECT_FALSE(audio_consumer_->started());
+  time_source_->SetMediaTime(start_time);
+
+  ASSERT_NO_FATAL_FAILURE(FillBuffer());
+  time_source_->StartTicking();
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(audio_consumer_->started());
+  EXPECT_EQ(audio_consumer_->start_media_time(), start_time);
+
+  audio_consumer_->UpdateStatus(base::TimeTicks::Now(), start_time);
+  task_environment_.RunUntilIdle();
+}
+
+void FuchsiaAudioRendererTest::CheckGetWallClockTimes(
+    absl::optional<base::TimeDelta> media_timestamp,
+    base::TimeTicks expected_wall_clock,
+    bool is_time_moving) {
+  std::vector<base::TimeDelta> media_timestamps;
+  if (media_timestamp)
+    media_timestamps.push_back(media_timestamp.value());
+  std::vector<base::TimeTicks> wall_clock;
+  bool result = time_source_->GetWallClockTimes(media_timestamps, &wall_clock);
+  EXPECT_EQ(wall_clock[0], expected_wall_clock);
+  EXPECT_EQ(result, is_time_moving);
+}
+
+void FuchsiaAudioRendererTest::StartPlaybackAndVerifyClock(
+    base::TimeDelta start_time,
+    float playback_rate) {
+  time_source_->SetMediaTime(start_time);
+  time_source_->SetPlaybackRate(playback_rate);
+
+  demuxer_stream_pos_ = start_time;
+  ASSERT_NO_FATAL_FAILURE(FillBuffer());
+
+  EXPECT_FALSE(audio_consumer_->started());
+  time_source_->StartTicking();
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(audio_consumer_->started());
+
+  // Start position should be reported before updated status is received.
+  EXPECT_EQ(time_source_->CurrentMediaTime(), start_time);
+  task_environment_.FastForwardBy(kTimeStep);
+  EXPECT_EQ(time_source_->CurrentMediaTime(), start_time);
+
+  CheckGetWallClockTimes(absl::nullopt, base::TimeTicks(), false);
+  CheckGetWallClockTimes(start_time + kTimeStep,
+                         base::TimeTicks::Now() + kTimeStep, false);
+
+  // MediaTime will start moving once AudioConsumer updates timeline.
+  const base::TimeDelta kStartDelay = base::TimeDelta::FromMilliseconds(3);
+  base::TimeTicks start_wall_clock = base::TimeTicks::Now() + kStartDelay;
+  audio_consumer_->UpdateStatus(start_wall_clock, start_time);
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(time_source_->CurrentMediaTime(),
+            start_time - kStartDelay * playback_rate);
+  task_environment_.FastForwardBy(kTimeStep);
+  EXPECT_EQ(time_source_->CurrentMediaTime(),
+            start_time + (-kStartDelay + kTimeStep) * playback_rate);
+
+  CheckGetWallClockTimes(absl::nullopt, base::TimeTicks::Now(), true);
+  CheckGetWallClockTimes(start_time + kTimeStep,
+                         start_wall_clock + kTimeStep / playback_rate, true);
+  CheckGetWallClockTimes(start_time + 2 * kTimeStep,
+                         start_wall_clock + 2.0 * kTimeStep / playback_rate,
+                         true);
+}
+
+TEST_F(FuchsiaAudioRendererTest, Initialize) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+}
+
+TEST_F(FuchsiaAudioRendererTest, InitializeAndBuffer) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  ASSERT_NO_FATAL_FAILURE(FillBuffer());
+
+  // Extra packets should be sent to AudioConsumer immediately.
+  stream_sink_->received_packets()->clear();
+  ProduceDemuxerPacket(base::TimeDelta::FromMilliseconds(10));
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(stream_sink_->received_packets()->size(), 1U);
+}
+
+TEST_F(FuchsiaAudioRendererTest, StartPlaybackBeforeStreamSinkConnected) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+
+  // Start playing immediately after initialization. The renderer should wait
+  // for buffers to be allocated before it starts reading from the demuxer.
+  audio_renderer_->StartPlaying();
+  ProduceDemuxerPacket(base::TimeDelta::FromMilliseconds(10));
+  task_environment_.RunUntilIdle();
+
+  stream_sink_ = audio_consumer_->WaitStreamSinkConnected();
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(stream_sink_->received_packets()->size(), 1U);
+}
+
+TEST_F(FuchsiaAudioRendererTest, StartTicking) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  ASSERT_NO_FATAL_FAILURE(StartPlaybackAndVerifyClock(
+      /*start_pos=*/base::TimeDelta::FromMilliseconds(123),
+      /*playback_rate=*/1.0));
+}
+
+TEST_F(FuchsiaAudioRendererTest, StartTickingRate1_5) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  ASSERT_NO_FATAL_FAILURE(StartPlaybackAndVerifyClock(
+      /*start_pos=*/base::TimeDelta::FromMilliseconds(123),
+      /*playback_rate=*/1.5));
+}
+
+TEST_F(FuchsiaAudioRendererTest, StartTickingRate0_5) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  ASSERT_NO_FATAL_FAILURE(StartPlaybackAndVerifyClock(
+      /*start_pos=*/base::TimeDelta::FromMilliseconds(123),
+      /*playback_rate=*/0.5));
+}
+
+// Verify that the renderer doesn't send packets more than kMaxLeadTime ahead of
+// time.
+TEST_F(FuchsiaAudioRendererTest, MaxLeadTime) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  ASSERT_NO_FATAL_FAILURE(FillBuffer());
+
+  // Queue packets up to kMaxLeadTime with 10 extra packets. The Renderer
+  // shouldn't read these extra packets at the end.
+  FillDemuxerStream(kMaxLeadTime + kPacketDuration * 10);
+
+  task_environment_.RunUntilIdle();
+
+  // Verify that the renderer has filled the buffer to kMaxLeadTime.
+  size_t expected_packets = kMaxLeadTime / kPacketDuration;
+  EXPECT_EQ(expected_packets, stream_sink_->received_packets()->size());
+}
+
+TEST_F(FuchsiaAudioRendererTest, Seek) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+
+  constexpr base::TimeDelta kStartPos = base::TimeDelta();
+  ASSERT_NO_FATAL_FAILURE(StartPlayback(kStartPos));
+  task_environment_.FastForwardBy(kTimeStep);
+  time_source_->StopTicking();
+  demuxer_stream_->DiscardQueueAndAbortRead();
+
+  // Media time should be stopped after StopTicking().
+  EXPECT_EQ(time_source_->CurrentMediaTime(), kStartPos + kTimeStep);
+  task_environment_.FastForwardBy(kTimeStep);
+  EXPECT_EQ(time_source_->CurrentMediaTime(), kStartPos + kTimeStep);
+
+  // Flush the renderer.
+  base::RunLoop run_loop;
+  audio_renderer_->Flush(run_loop.QuitClosure());
+  run_loop.Run();
+
+  // Restart playback from a new position.
+  const base::TimeDelta kSeekPos = base::TimeDelta::FromMilliseconds(123);
+  ASSERT_NO_FATAL_FAILURE(StartPlaybackAndVerifyClock(kSeekPos,
+                                                      /*playback_rate=*/1.0));
+
+  ProduceDemuxerPacket(kPacketDuration);
+
+  // Verify that old packets were discarded and StreamSink started received
+  // packets at the correct position.
+  EXPECT_GT(stream_sink_->discarded_packets()->size(), 0u);
+  EXPECT_EQ(stream_sink_->received_packets()->at(0).pts,
+            kSeekPos.ToZxDuration());
+}
+
+TEST_F(FuchsiaAudioRendererTest, ChangeConfig) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  ASSERT_NO_FATAL_FAILURE(StartPlayback());
+
+  const auto kConfigChangePos = base::TimeDelta::FromSeconds(1);
+
+  // Queue packets up to kConfigChangePos.
+  FillDemuxerStream(kConfigChangePos);
+
+  const size_t kNewSampleRate = 44100;
+  const std::vector<uint8_t> kArbitraryExtraData = {1, 2, 3};
+  AudioDecoderConfig updated_config(
+      kCodecOpus, kSampleFormatF32, CHANNEL_LAYOUT_STEREO, kNewSampleRate,
+      kArbitraryExtraData, EncryptionScheme::kUnencrypted);
+  demuxer_stream_->QueueReadResult(
+      TestDemuxerStream::ReadResult(updated_config));
+
+  // Queue one more packet with the new config.
+  ProduceDemuxerPacket(kPacketDuration);
+
+  task_environment_.FastForwardBy(kConfigChangePos);
+
+  // The renderer should have created new StreamSink when config was changed.
+  auto new_stream_sink = audio_consumer_->TakeStreamSink();
+  ASSERT_TRUE(new_stream_sink);
+
+  ASSERT_TRUE(client_.last_config_change().has_value());
+  EXPECT_TRUE(client_.last_config_change()->Matches(updated_config));
+
+  EXPECT_EQ(stream_sink_->stream_type().channels, 1U);
+  EXPECT_EQ(stream_sink_->stream_type().frames_per_second,
+            static_cast<uint32_t>(kDefaultSampleRate));
+  EXPECT_EQ(stream_sink_->received_packets()->size(),
+            kConfigChangePos / kPacketDuration);
+
+  EXPECT_EQ(new_stream_sink->stream_type().channels,
+            static_cast<uint32_t>(updated_config.channels()));
+  EXPECT_EQ(new_stream_sink->stream_type().frames_per_second, kNewSampleRate);
+  EXPECT_TRUE(new_stream_sink->compression());
+  EXPECT_EQ(new_stream_sink->compression()->type,
+            fuchsia::media::AUDIO_ENCODING_OPUS);
+  EXPECT_EQ(new_stream_sink->compression()->parameters, kArbitraryExtraData);
+  EXPECT_EQ(new_stream_sink->received_packets()->size(), 1U);
+  EXPECT_EQ(new_stream_sink->received_packets()->at(0).pts,
+            kConfigChangePos.ToZxDuration());
+}
+
+TEST_F(FuchsiaAudioRendererTest, UpdateTimeline) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  ASSERT_NO_FATAL_FAILURE(StartPlayback());
+
+  FillDemuxerStream(base::TimeDelta::FromSeconds(2));
+
+  const auto kTimelineChangePos = base::TimeDelta::FromSeconds(1);
+  task_environment_.FastForwardBy(kTimelineChangePos);
+
+  // Shift the timeline by 2ms.
+  const auto kMediaDelta = base::TimeDelta::FromMilliseconds(2);
+  audio_consumer_->UpdateStatus(base::TimeTicks::Now(),
+                                kTimelineChangePos + kMediaDelta);
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(time_source_->CurrentMediaTime(), kTimelineChangePos + kMediaDelta);
+  task_environment_.FastForwardBy(kTimeStep);
+  EXPECT_EQ(time_source_->CurrentMediaTime(),
+            kTimelineChangePos + kMediaDelta + kTimeStep);
+}
+
+TEST_F(FuchsiaAudioRendererTest, PauseAndResume) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  ASSERT_NO_FATAL_FAILURE(StartPlayback());
+
+  const auto kPauseTimestamp = base::TimeDelta::FromSeconds(1);
+  const auto kStreamLength = base::TimeDelta::FromSeconds(2);
+
+  FillDemuxerStream(kStreamLength);
+
+  task_environment_.FastForwardBy(kPauseTimestamp);
+
+  // Pause playback by setting playback rate to 0.0.
+  time_source_->SetPlaybackRate(0.0);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(audio_consumer_->playback_rate(), 0.0);
+
+  task_environment_.FastForwardBy(kTimeStep);
+  audio_consumer_->UpdateStatus(base::TimeTicks::Now(), kPauseTimestamp);
+  task_environment_.RunUntilIdle();
+
+  const size_t kExpectedQueuedPackets =
+      (kPauseTimestamp + kMaxLeadTime) / kPacketDuration + 1;
+  EXPECT_EQ(stream_sink_->received_packets()->size(), kExpectedQueuedPackets);
+  EXPECT_EQ(time_source_->CurrentMediaTime(), kPauseTimestamp);
+
+  // Keep the stream paused for 10 seconds. The Renderer should not be sending
+  // new packets
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(10));
+  EXPECT_EQ(stream_sink_->received_packets()->size(), kExpectedQueuedPackets);
+  EXPECT_EQ(time_source_->CurrentMediaTime(), kPauseTimestamp);
+
+  // Resume playback.
+  time_source_->SetPlaybackRate(1.0);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(audio_consumer_->playback_rate(), 1.0);
+  audio_consumer_->UpdateStatus(base::TimeTicks::Now(), kPauseTimestamp);
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(time_source_->CurrentMediaTime(), kPauseTimestamp);
+
+  // The renderer should start sending packets again.
+  task_environment_.FastForwardBy(kPacketDuration);
+  EXPECT_EQ(stream_sink_->received_packets()->size(),
+            kExpectedQueuedPackets + 1);
+
+  EXPECT_EQ(time_source_->CurrentMediaTime(),
+            kPauseTimestamp + kPacketDuration);
+}
+
+// Verify that end-of-stream is handled correctly when the renderer is buffered.
+TEST_F(FuchsiaAudioRendererTest, EndOfStreamBuffered) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  ASSERT_NO_FATAL_FAILURE(StartPlayback());
+
+  const auto kStreamLength = base::TimeDelta::FromSeconds(1);
+  FillDemuxerStream(kStreamLength);
+  demuxer_stream_->QueueReadResult(
+      TestDemuxerStream::ReadResult(DecoderBuffer::CreateEOSBuffer()));
+
+  task_environment_.FastForwardBy(kStreamLength);
+
+  EXPECT_EQ(stream_sink_->received_packets()->size(),
+            kStreamLength / kPacketDuration);
+  EXPECT_TRUE(stream_sink_->received_end_of_stream());
+
+  client_.ExpectEos();
+  audio_consumer_->SignalEndOfStream();
+  task_environment_.RunUntilIdle();
+}
+
+// Verifies that buffering state is updated after reaching EOS. See
+// https://crbug.com/1162503 .
+TEST_F(FuchsiaAudioRendererTest, EndOfStreamWhenBuffering) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  stream_sink_ = audio_consumer_->WaitStreamSinkConnected();
+
+  // Produce stream shorter than kMinLeadTime.
+  const auto kStreamLength = kMinLeadTime / 2;
+  FillDemuxerStream(kStreamLength);
+  demuxer_stream_->QueueReadResult(
+      TestDemuxerStream::ReadResult(DecoderBuffer::CreateEOSBuffer()));
+  task_environment_.RunUntilIdle();
+
+  // Start playback. The renderer should push queued packets to the
+  // AudioConsumer and updated buffering state when it reaches EOS.
+  audio_renderer_->StartPlaying();
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(client_.buffering_state(), BUFFERING_HAVE_ENOUGH);
+  EXPECT_TRUE(stream_sink_->received_end_of_stream());
+}
+
+TEST_F(FuchsiaAudioRendererTest, EndOfStreamStart) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+  stream_sink_ = audio_consumer_->WaitStreamSinkConnected();
+
+  // Queue EOS without any preceding packets.
+  demuxer_stream_->QueueReadResult(
+      TestDemuxerStream::ReadResult(DecoderBuffer::CreateEOSBuffer()));
+  task_environment_.RunUntilIdle();
+
+  // Start playback. The renderer should push queued packets to the
+  // AudioConsumer and updated buffering state when it reaches EOS.
+  audio_renderer_->StartPlaying();
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(client_.buffering_state(), BUFFERING_HAVE_ENOUGH);
+  EXPECT_TRUE(stream_sink_->received_end_of_stream());
+}
+
+TEST_F(FuchsiaAudioRendererTest, SetVolume) {
+  ASSERT_NO_FATAL_FAILURE(CreateAndInitializeRenderer());
+
+  audio_renderer_->SetVolume(0.5);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(audio_consumer_->volume(), 0.5);
+}
+
+TEST_F(FuchsiaAudioRendererTest, SetVolumeBeforeInitialize) {
+  ASSERT_NO_FATAL_FAILURE(CreateUninitializedRenderer());
+
+  // SetVolume() may be called before AudioRenderer is initialized. It should
+  // still be handled.
+  audio_renderer_->SetVolume(0.5);
+
+  ASSERT_NO_FATAL_FAILURE(InitializeRenderer());
+  EXPECT_EQ(audio_consumer_->volume(), 0.5);
+}
+
+}  // namespace media
diff --git a/media/fuchsia/common/BUILD.gn b/media/fuchsia/common/BUILD.gn
index cd3f1c12..381db17 100644
--- a/media/fuchsia/common/BUILD.gn
+++ b/media/fuchsia/common/BUILD.gn
@@ -6,8 +6,13 @@
 
 source_set("common") {
   sources = [
+    "decrypting_sysmem_buffer_stream.cc",
+    "decrypting_sysmem_buffer_stream.h",
+    "passthrough_sysmem_buffer_stream.cc",
+    "passthrough_sysmem_buffer_stream.h",
     "stream_processor_helper.cc",
     "stream_processor_helper.h",
+    "sysmem_buffer_stream.h",
     "sysmem_client.cc",
     "sysmem_client.h",
     "vmo_buffer.cc",
diff --git a/media/fuchsia/common/decrypting_sysmem_buffer_stream.cc b/media/fuchsia/common/decrypting_sysmem_buffer_stream.cc
new file mode 100644
index 0000000..51a7498
--- /dev/null
+++ b/media/fuchsia/common/decrypting_sysmem_buffer_stream.cc
@@ -0,0 +1,123 @@
+// 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 "media/fuchsia/common/decrypting_sysmem_buffer_stream.h"
+
+#include "media/base/callback_registry.h"
+#include "media/base/decoder_buffer.h"
+
+namespace media {
+
+DecryptingSysmemBufferStream::DecryptingSysmemBufferStream(
+    SysmemAllocatorClient* sysmem_allocator,
+    CdmContext* cdm_context,
+    Decryptor::StreamType stream_type)
+    : passthrough_stream_(sysmem_allocator),
+      decryptor_(cdm_context->GetDecryptor()),
+      stream_type_(stream_type) {
+  DCHECK(decryptor_);
+
+  event_cb_registration_ = cdm_context->RegisterEventCB(
+      base::BindRepeating(&DecryptingSysmemBufferStream::OnCdmContextEvent,
+                          weak_factory_.GetWeakPtr()));
+}
+
+DecryptingSysmemBufferStream::~DecryptingSysmemBufferStream() = default;
+
+void DecryptingSysmemBufferStream::Initialize(Sink* sink,
+                                              size_t min_buffer_size,
+                                              size_t min_buffer_count) {
+  sink_ = sink;
+  passthrough_stream_.Initialize(sink, min_buffer_size, min_buffer_count);
+}
+
+void DecryptingSysmemBufferStream::EnqueueBuffer(
+    scoped_refptr<DecoderBuffer> buffer) {
+  buffer_queue_.push_back(std::move(buffer));
+  DecryptNextBuffer();
+}
+
+void DecryptingSysmemBufferStream::Reset() {
+  buffer_queue_.clear();
+
+  if (state_ == State::kDecryptPending) {
+    decryptor_->CancelDecrypt(stream_type_);
+  }
+
+  state_ = State::kIdle;
+  retry_on_no_key_ = false;
+}
+
+void DecryptingSysmemBufferStream::OnCdmContextEvent(CdmContext::Event event) {
+  if (event != CdmContext::Event::kHasAdditionalUsableKey)
+    return;
+
+  switch (state_) {
+    case State::kIdle:
+      break;
+
+    case State::kDecryptPending:
+      retry_on_no_key_ = true;
+      break;
+
+    case State::kWaitingKey:
+      state_ = State::kIdle;
+      DecryptNextBuffer();
+      break;
+  }
+}
+
+void DecryptingSysmemBufferStream::DecryptNextBuffer() {
+  if (buffer_queue_.empty() || state_ != State::kIdle)
+    return;
+
+  if (buffer_queue_.front()->end_of_stream()) {
+    scoped_refptr<DecoderBuffer> buffer = std::move(buffer_queue_.front());
+    buffer_queue_.pop_front();
+    DCHECK(buffer_queue_.empty());
+    passthrough_stream_.EnqueueBuffer(std::move(buffer));
+    return;
+  }
+
+  state_ = State::kDecryptPending;
+  decryptor_->Decrypt(
+      stream_type_, buffer_queue_.front(),
+      base::BindOnce(&DecryptingSysmemBufferStream::OnBufferDecrypted,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void DecryptingSysmemBufferStream::OnBufferDecrypted(
+    Decryptor::Status status,
+    scoped_refptr<DecoderBuffer> decrypted_buffer) {
+  DCHECK(state_ == State::kDecryptPending);
+  state_ = State::kIdle;
+
+  switch (status) {
+    case Decryptor::kError:
+      sink_->OnSysmemBufferStreamError();
+      return;
+
+    case Decryptor::kNoKey:
+      if (retry_on_no_key_) {
+        retry_on_no_key_ = false;
+        DecryptNextBuffer();
+      } else {
+        state_ = State::kWaitingKey;
+        sink_->OnSysmemBufferStreamNoKey();
+      }
+      return;
+
+    case Decryptor::kNeedMoreData:
+      break;
+
+    case Decryptor::kSuccess:
+      passthrough_stream_.EnqueueBuffer(std::move(decrypted_buffer));
+  }
+
+  buffer_queue_.pop_front();
+
+  DecryptNextBuffer();
+}
+
+}  // namespace media
diff --git a/media/fuchsia/common/decrypting_sysmem_buffer_stream.h b/media/fuchsia/common/decrypting_sysmem_buffer_stream.h
new file mode 100644
index 0000000..8fb0030
--- /dev/null
+++ b/media/fuchsia/common/decrypting_sysmem_buffer_stream.h
@@ -0,0 +1,66 @@
+// 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 MEDIA_FUCHSIA_COMMON_DECRYPTING_SYSMEM_BUFFER_STREAM_H_
+#define MEDIA_FUCHSIA_COMMON_DECRYPTING_SYSMEM_BUFFER_STREAM_H_
+
+#include "media/fuchsia/common/passthrough_sysmem_buffer_stream.h"
+
+#include <deque>
+
+#include "media/base/cdm_context.h"
+#include "media/base/decryptor.h"
+
+namespace media {
+
+// A SysmemBufferStream that decrypts the stream before copying the data to
+// sysmem buffers.
+class MEDIA_EXPORT DecryptingSysmemBufferStream : public SysmemBufferStream {
+ public:
+  DecryptingSysmemBufferStream(SysmemAllocatorClient* sysmem_allocator,
+                               CdmContext* cdm_context,
+                               Decryptor::StreamType stream_type);
+  ~DecryptingSysmemBufferStream() override;
+
+  DecryptingSysmemBufferStream(const DecryptingSysmemBufferStream&) = delete;
+  DecryptingSysmemBufferStream& operator=(const DecryptingSysmemBufferStream&) =
+      delete;
+
+  // SysmemBufferStream implementation:
+  void Initialize(Sink* sink,
+                  size_t min_buffer_size,
+                  size_t min_buffer_count) override;
+  void EnqueueBuffer(scoped_refptr<DecoderBuffer> buffer) override;
+  void Reset() override;
+
+ private:
+  enum class State {
+    kIdle,
+    kDecryptPending,
+    kWaitingKey,
+  };
+
+  void OnCdmContextEvent(CdmContext::Event event);
+  void DecryptNextBuffer();
+  void OnBufferDecrypted(Decryptor::Status status,
+                         scoped_refptr<DecoderBuffer> decrypted_buffer);
+
+  PassthroughSysmemBufferStream passthrough_stream_;
+  Decryptor* const decryptor_;
+  const Decryptor::StreamType stream_type_;
+
+  std::unique_ptr<CallbackRegistration> event_cb_registration_;
+
+  Sink* sink_ = nullptr;
+  std::deque<scoped_refptr<DecoderBuffer>> buffer_queue_;
+  State state_ = State::kIdle;
+
+  bool retry_on_no_key_ = false;
+
+  base::WeakPtrFactory<DecryptingSysmemBufferStream> weak_factory_{this};
+};
+
+}  // namespace media
+
+#endif  // MEDIA_FUCHSIA_COMMON_DECRYPTING_SYSMEM_BUFFER_STREAM_H_
\ No newline at end of file
diff --git a/media/fuchsia/common/passthrough_sysmem_buffer_stream.cc b/media/fuchsia/common/passthrough_sysmem_buffer_stream.cc
new file mode 100644
index 0000000..bb04295
--- /dev/null
+++ b/media/fuchsia/common/passthrough_sysmem_buffer_stream.cc
@@ -0,0 +1,71 @@
+// 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 "media/fuchsia/common/passthrough_sysmem_buffer_stream.h"
+
+#include "media/base/decoder_buffer.h"
+
+namespace media {
+
+PassthroughSysmemBufferStream::PassthroughSysmemBufferStream(
+    SysmemAllocatorClient* sysmem_allocator)
+    : sysmem_allocator_(sysmem_allocator) {
+  DCHECK(sysmem_allocator_);
+}
+
+PassthroughSysmemBufferStream::~PassthroughSysmemBufferStream() = default;
+
+void PassthroughSysmemBufferStream::Initialize(Sink* sink,
+                                               size_t min_buffer_size,
+                                               size_t min_buffer_count) {
+  DCHECK(sink);
+  sink_ = sink;
+
+  fuchsia::sysmem::BufferCollectionConstraints buffer_constraints =
+      VmoBuffer::GetRecommendedConstraints(min_buffer_count, min_buffer_size,
+                                           /*writable=*/true);
+
+  // Create buffer collection.
+  output_buffer_collection_ = sysmem_allocator_->AllocateNewCollection();
+  output_buffer_collection_->CreateSharedToken(
+      base::BindOnce(&Sink::OnSysmemBufferStreamBufferCollectionToken,
+                     base::Unretained(sink_)));
+  output_buffer_collection_->Initialize(std::move(buffer_constraints),
+                                        "CrPassthroughSysmemBufferStream");
+  output_buffer_collection_->AcquireBuffers(
+      base::BindOnce(&PassthroughSysmemBufferStream::OnBuffersAcquired,
+                     base::Unretained(this)));
+}
+
+void PassthroughSysmemBufferStream::EnqueueBuffer(
+    scoped_refptr<DecoderBuffer> buffer) {
+  queue_.EnqueueBuffer(std::move(buffer));
+}
+
+void PassthroughSysmemBufferStream::Reset() {
+  queue_.ResetQueue();
+}
+
+void PassthroughSysmemBufferStream::OnBuffersAcquired(
+    std::vector<VmoBuffer> buffers,
+    const fuchsia::sysmem::SingleBufferSettings& buffer_settings) {
+  queue_.Start(
+      std::move(buffers),
+      base::BindRepeating(&PassthroughSysmemBufferStream::ProcessOutputPacket,
+                          base::Unretained(this)),
+      base::BindRepeating(&PassthroughSysmemBufferStream::ProcessEndOfStream,
+                          base::Unretained(this)));
+}
+
+void PassthroughSysmemBufferStream::ProcessOutputPacket(
+    const DecoderBuffer* buffer,
+    StreamProcessorHelper::IoPacket packet) {
+  sink_->OnSysmemBufferStreamOutputPacket(std::move(packet));
+}
+
+void PassthroughSysmemBufferStream::ProcessEndOfStream() {
+  sink_->OnSysmemBufferStreamEndOfStream();
+}
+
+}  // namespace media
diff --git a/media/fuchsia/common/passthrough_sysmem_buffer_stream.h b/media/fuchsia/common/passthrough_sysmem_buffer_stream.h
new file mode 100644
index 0000000..4cd9373
--- /dev/null
+++ b/media/fuchsia/common/passthrough_sysmem_buffer_stream.h
@@ -0,0 +1,52 @@
+// 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 MEDIA_FUCHSIA_COMMON_PASSTHROUGH_SYSMEM_BUFFER_STREAM_H_
+#define MEDIA_FUCHSIA_COMMON_PASSTHROUGH_SYSMEM_BUFFER_STREAM_H_
+
+#include "media/fuchsia/common/sysmem_buffer_stream.h"
+#include "media/fuchsia/common/sysmem_client.h"
+#include "media/fuchsia/common/vmo_buffer_writer_queue.h"
+
+namespace media {
+
+// A SysmemBufferStream that simply writes the stream to sysmem buffers.
+class MEDIA_EXPORT PassthroughSysmemBufferStream : public SysmemBufferStream {
+ public:
+  explicit PassthroughSysmemBufferStream(
+      SysmemAllocatorClient* sysmem_allocator);
+  ~PassthroughSysmemBufferStream() override;
+
+  PassthroughSysmemBufferStream(const PassthroughSysmemBufferStream&) = delete;
+  PassthroughSysmemBufferStream& operator=(
+      const PassthroughSysmemBufferStream&) = delete;
+
+  // SysmemBufferStream implementation:
+  void Initialize(Sink* sink,
+                  size_t min_buffer_size,
+                  size_t min_buffer_count) override;
+  void EnqueueBuffer(scoped_refptr<DecoderBuffer> buffer) override;
+  void Reset() override;
+
+ private:
+  void OnBuffersAcquired(
+      std::vector<VmoBuffer> buffers,
+      const fuchsia::sysmem::SingleBufferSettings& buffer_settings);
+
+  // Callbacks for VmoBufferWriterQueue.
+  void ProcessOutputPacket(const DecoderBuffer* buffer,
+                           StreamProcessorHelper::IoPacket packet);
+  void ProcessEndOfStream();
+
+  Sink* sink_ = nullptr;
+
+  SysmemAllocatorClient* const sysmem_allocator_;
+  std::unique_ptr<SysmemCollectionClient> output_buffer_collection_;
+
+  VmoBufferWriterQueue queue_;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_FUCHSIA_COMMON_PASSTHROUGH_SYSMEM_BUFFER_STREAM_H_
\ No newline at end of file
diff --git a/media/fuchsia/common/sysmem_buffer_stream.h b/media/fuchsia/common/sysmem_buffer_stream.h
new file mode 100644
index 0000000..f02ebe9
--- /dev/null
+++ b/media/fuchsia/common/sysmem_buffer_stream.h
@@ -0,0 +1,72 @@
+// 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 MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_STREAM_H_
+#define MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_STREAM_H_
+
+#include <fuchsia/sysmem/cpp/fidl.h>
+
+#include "base/memory/scoped_refptr.h"
+#include "media/fuchsia/common/stream_processor_helper.h"
+
+namespace media {
+
+class DecoderBuffer;
+
+// Abstract interface for media stream processors. SysmemBufferStream takes a
+// stream of buffers in DecoderBuffer, processes them and then writes the output
+// to sysmem buffers.
+class MEDIA_EXPORT SysmemBufferStream {
+ public:
+  class Sink {
+   public:
+    // Called to set BufferCollectionToken for the output buffer collection.
+    virtual void OnSysmemBufferStreamBufferCollectionToken(
+        fuchsia::sysmem::BufferCollectionTokenPtr token) = 0;
+
+    // Called when a packet has been processed. The client should drop the
+    // |packet| only after it's finished using it.
+    virtual void OnSysmemBufferStreamOutputPacket(
+        StreamProcessorHelper::IoPacket packet) = 0;
+
+    // Called when the end of stream has been reached.
+    virtual void OnSysmemBufferStreamEndOfStream() = 0;
+
+    // Called on error.
+    virtual void OnSysmemBufferStreamError() = 0;
+
+    // Called to notify the sink that the SysmemBufferStream has stopped
+    // because it doesn't have a key. It will resume automatically once a new
+    // key is received.
+    virtual void OnSysmemBufferStreamNoKey() = 0;
+
+   protected:
+    virtual ~Sink() = default;
+  };
+
+  SysmemBufferStream() {}
+  virtual ~SysmemBufferStream() {}
+
+  // Allocates a buffer collection for the output and starts processing the
+  // stream, passing the output to the specified |sink|. |min_buffer_size| and
+  // |min_buffer_count| specify the minimum number of packets in the output
+  // buffer collection.
+  virtual void Initialize(Sink* sink,
+                          size_t min_buffer_size,
+                          size_t min_buffer_count) = 0;
+
+  // Enqueues the specified buffer to the input queue. Caller is allowed to
+  // queue as many buffers as it needs without waiting for results from the
+  // previous Process() calls. May be called before Initialize(). Queued buffers
+  // will be processed only after Initialize().
+  virtual void EnqueueBuffer(scoped_refptr<DecoderBuffer> buffer) = 0;
+
+  // Stops processing queued buffers and drops them. Keeps the |sink| passed to
+  // Start() and the output buffer collections.
+  virtual void Reset() = 0;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_STREAM_H_
diff --git a/media/fuchsia/common/vmo_buffer.cc b/media/fuchsia/common/vmo_buffer.cc
index b899e00..6fb77333 100644
--- a/media/fuchsia/common/vmo_buffer.cc
+++ b/media/fuchsia/common/vmo_buffer.cc
@@ -99,6 +99,7 @@
     return false;
   }
 
+  vmo_ = std::move(vmo);
   base_address_ = reinterpret_cast<uint8_t*>(addr);
 
   return true;
@@ -154,4 +155,17 @@
   return base::bits::Align(offset_ + size_, base::GetPageSize());
 }
 
+zx::vmo VmoBuffer::Duplicate(bool writable) {
+  zx_rights_t rights = ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER | ZX_RIGHT_READ |
+                       ZX_RIGHT_MAP | ZX_RIGHT_GET_PROPERTY;
+  if (writable)
+    rights |= ZX_RIGHT_WRITE;
+
+  zx::vmo vmo;
+  zx_status_t status = vmo_.duplicate(rights, &vmo);
+  ZX_CHECK(status == ZX_OK, status) << "zx_handle_duplicate";
+
+  return vmo;
+}
+
 }  // namespace media
diff --git a/media/fuchsia/common/vmo_buffer.h b/media/fuchsia/common/vmo_buffer.h
index 644200a3..8a6ab02 100644
--- a/media/fuchsia/common/vmo_buffer.h
+++ b/media/fuchsia/common/vmo_buffer.h
@@ -77,10 +77,15 @@
   // invalidated.
   void FlushCache(size_t flush_offset, size_t flush_size, bool invalidate);
 
+  // Duplicates VMO.
+  zx::vmo Duplicate(bool writable);
+
  private:
   // Returns size of the mapped region.
   size_t mapped_size();
 
+  zx::vmo vmo_;
+
   uint8_t* base_address_ = nullptr;
 
   bool writable_ = false;
diff --git a/media/fuchsia/common/vmo_buffer_writer_queue.cc b/media/fuchsia/common/vmo_buffer_writer_queue.cc
index d8511988..fc81602 100644
--- a/media/fuchsia/common/vmo_buffer_writer_queue.cc
+++ b/media/fuchsia/common/vmo_buffer_writer_queue.cc
@@ -75,7 +75,7 @@
 }
 
 bool VmoBufferWriterQueue::IsBlocked() const {
-  return input_queue_position_ < pending_buffers_.size();
+  return unused_buffers_.empty();
 }
 
 void VmoBufferWriterQueue::PumpPackets() {
diff --git a/media/media_options.gni b/media/media_options.gni
index caa5cc0..303b2e32 100644
--- a/media/media_options.gni
+++ b/media/media_options.gni
@@ -249,6 +249,7 @@
 
 if (is_fuchsia) {
   media_subcomponent_deps += [
+    "//media/fuchsia/audio",
     "//media/fuchsia/cdm",
     "//media/fuchsia/common",
   ]
diff --git a/media/renderers/default_decoder_factory.cc b/media/renderers/default_decoder_factory.cc
index eafe4947..5e242d36 100644
--- a/media/renderers/default_decoder_factory.cc
+++ b/media/renderers/default_decoder_factory.cc
@@ -100,15 +100,6 @@
     }
   }
 
-#if defined(OS_FUCHSIA)
-  // TODO(crbug.com/1173503): Implement capabilities for fuchsia.
-  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kDisableSoftwareVideoDecoders)) {
-    // Bypass software codec registration.
-    return supported_configs;
-  }
-#endif
-
   if (!base::FeatureList::IsEnabled(media::kExposeSwDecodersToWebRTC))
     return supported_configs;
 
@@ -191,12 +182,6 @@
           << "Can't create FuchsiaVideoDecoder due to GPU context loss.";
     }
   }
-
-  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kDisableSoftwareVideoDecoders)) {
-    // Bypass software codec registration.
-    return;
-  }
 #endif
 
 #if BUILDFLAG(ENABLE_LIBVPX)
diff --git a/media/webrtc/webrtc_switches.cc b/media/webrtc/webrtc_switches.cc
index 3c16994..7157986 100644
--- a/media/webrtc/webrtc_switches.cc
+++ b/media/webrtc/webrtc_switches.cc
@@ -20,6 +20,11 @@
 
 namespace features {
 
+// When enabled we will tell WebRTC that we want to use the
+// Windows.Graphics.Capture API based DesktopCapturer, if it is available.
+const base::Feature kWebRtcAllowWgcDesktopCapturer{
+    "AllowWgcDesktopCapturer", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables multichannel capture audio to be processed without downmixing in the
 // WebRTC audio processing module.
 const base::Feature kWebRtcEnableCaptureMultiChannelApm{
diff --git a/media/webrtc/webrtc_switches.h b/media/webrtc/webrtc_switches.h
index 1c223d9..e81aa89 100644
--- a/media/webrtc/webrtc_switches.h
+++ b/media/webrtc/webrtc_switches.h
@@ -19,6 +19,9 @@
 namespace features {
 
 COMPONENT_EXPORT(MEDIA_WEBRTC)
+extern const base::Feature kWebRtcAllowWgcDesktopCapturer;
+
+COMPONENT_EXPORT(MEDIA_WEBRTC)
 extern const base::Feature kWebRtcEnableCaptureMultiChannelApm;
 
 COMPONENT_EXPORT(MEDIA_WEBRTC)
diff --git a/mojo/DIR_METADATA b/mojo/DIR_METADATA
index 06992d0..c080aa1 100644
--- a/mojo/DIR_METADATA
+++ b/mojo/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Mojo"
diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc
index 02d848f..83232c7 100644
--- a/net/cookies/canonical_cookie.cc
+++ b/net/cookies/canonical_cookie.cc
@@ -468,6 +468,9 @@
   RecordCookieSameSiteAttributeValueHistogram(samesite_string,
                                               parsed_cookie.IsSameParty());
 
+  UMA_HISTOGRAM_BOOLEAN("Cookie.ControlCharacterTruncation",
+                        parsed_cookie.HasTruncatedNameOrValue());
+
   return cc;
 }
 
diff --git a/net/cookies/parsed_cookie.cc b/net/cookies/parsed_cookie.cc
index e77a2a4..4fc5a19 100644
--- a/net/cookies/parsed_cookie.cc
+++ b/net/cookies/parsed_cookie.cc
@@ -440,6 +440,23 @@
     if (it != end)
       ++it;
   }
+
+  // For metrics on name/value truncation.
+  //
+  // If we stopped before the (real) end of the string and the leftovers include
+  // something other than terminating characters or whitespace then this cookie
+  // was truncated due to a terminating CTL.
+  //
+  // We only care about the name or value being truncated which means we only
+  // care about the first pair. If the pairs_.size() > 1 then that means the
+  // truncation happened after name/value parsing which we're not concerned
+  // about for metrics.
+  using std::string_literals::operator""s;
+  if (pairs_.size() == 1 &&
+      cookie_line.find_first_not_of("\n\r\0 \t"s, end - start) !=
+          std::string::npos) {
+    truncated_name_or_value_ = true;
+  }
 }
 
 void ParsedCookie::SetupAttributes() {
diff --git a/net/cookies/parsed_cookie.h b/net/cookies/parsed_cookie.h
index 638257e..cd9cfed0 100644
--- a/net/cookies/parsed_cookie.h
+++ b/net/cookies/parsed_cookie.h
@@ -54,6 +54,7 @@
       CookieSameSiteString* samesite_string = nullptr) const;
   CookiePriority Priority() const;
   bool IsSameParty() const { return same_party_index_ != 0; }
+  bool HasTruncatedNameOrValue() const { return truncated_name_or_value_; }
 
   // Returns the number of attributes, for example, returning 2 for:
   //   "BLAH=hah; path=/; domain=.google.com"
@@ -159,6 +160,9 @@
   size_t same_site_index_ = 0;
   size_t priority_index_ = 0;
   size_t same_party_index_ = 0;
+  // For metrics on cookie name/value truncation. See usage at the bottom of
+  // `ParseTokenValuePairs()` for more details.
+  bool truncated_name_or_value_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(ParsedCookie);
 };
diff --git a/net/cookies/parsed_cookie_unittest.cc b/net/cookies/parsed_cookie_unittest.cc
index b458742..c12c1b83 100644
--- a/net/cookies/parsed_cookie_unittest.cc
+++ b/net/cookies/parsed_cookie_unittest.cc
@@ -833,4 +833,51 @@
   EXPECT_EQ(pc8_literal, pc8.ToCookieLine());
 }
 
+TEST(ParsedCookieTest, TruncatedNameOrValue) {
+  using std::string_literals::operator""s;
+
+  const char kCtlChars[] = {'\x0', '\xA', '\xD'};
+
+  for (char ctl_char : kCtlChars) {
+    std::string ctl_string(1, ctl_char);
+
+    std::string truncated_name_string = "fo"s + ctl_string + "o=bar"s;
+    ParsedCookie truncated_name(truncated_name_string);
+    EXPECT_TRUE(truncated_name.IsValid());
+    EXPECT_TRUE(truncated_name.HasTruncatedNameOrValue());
+
+    std::string truncated_value_string = "foo=b"s + ctl_string + "ar"s;
+    ParsedCookie truncated_value(truncated_value_string);
+    EXPECT_TRUE(truncated_value.IsValid());
+    EXPECT_TRUE(truncated_value.HasTruncatedNameOrValue());
+
+    std::string not_truncated_string = "foo=bar"s + ctl_string;
+    ParsedCookie not_truncated(not_truncated_string);
+    EXPECT_TRUE(not_truncated.IsValid());
+    EXPECT_FALSE(not_truncated.HasTruncatedNameOrValue());
+
+    std::string not_truncated_string_extra_ctl_chars =
+        "foo=bar"s + ctl_string + "\n\r\0"s;
+    ParsedCookie not_truncated_extra_ctl_chars(
+        not_truncated_string_extra_ctl_chars);
+    EXPECT_TRUE(not_truncated_extra_ctl_chars.IsValid());
+    EXPECT_FALSE(not_truncated_extra_ctl_chars.HasTruncatedNameOrValue());
+
+    std::string not_truncated_string_whitespace =
+        "foo=bar"s + ctl_string + " \t "s;
+    ParsedCookie not_truncated_whitespace(not_truncated_string_whitespace);
+    EXPECT_TRUE(not_truncated_whitespace.IsValid());
+    EXPECT_FALSE(not_truncated_whitespace.HasTruncatedNameOrValue());
+
+    std::string not_truncated_string_attribute_parsing =
+        "foo=bar; Secure; Http"s + ctl_string + "Only"s;
+    ParsedCookie not_truncated_attribute_parsing(
+        not_truncated_string_attribute_parsing);
+    EXPECT_TRUE(not_truncated_attribute_parsing.IsValid());
+    EXPECT_TRUE(not_truncated_attribute_parsing.IsSecure());
+    EXPECT_FALSE(not_truncated_attribute_parsing.IsHttpOnly());
+    EXPECT_FALSE(not_truncated_attribute_parsing.HasTruncatedNameOrValue());
+  }
+}
+
 }  // namespace net
diff --git a/printing/printing_context_mac.h b/printing/printing_context_mac.h
index 07fcfad..0fb5c91 100644
--- a/printing/printing_context_mac.h
+++ b/printing/printing_context_mac.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/mac/scoped_nsobject.h"
+#include "base/strings/string_piece.h"
 #include "printing/mojom/print.mojom.h"
 #include "printing/print_job_constants.h"
 #include "printing/printing_context.h"
@@ -94,6 +95,10 @@
   // Returns true if resolution is set.
   bool SetResolution(const gfx::Size& dpi_size);
 
+  // Sets key-value pair in PMPrintSettings.
+  // Returns true is the pair is set.
+  bool SetKeyValue(base::StringPiece key, base::StringPiece value);
+
   // The native print info object.
   base::scoped_nsobject<NSPrintInfo> print_info_;
 
diff --git a/printing/printing_context_mac.mm b/printing/printing_context_mac.mm
index 1a52ea3..9f80fe6 100644
--- a/printing/printing_context_mac.mm
+++ b/printing/printing_context_mac.mm
@@ -13,6 +13,7 @@
 
 #include "base/check.h"
 #include "base/mac/scoped_cftyperef.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
@@ -388,24 +389,64 @@
 }
 
 bool PrintingContextMac::SetOutputColor(int color_mode) {
-  PMPrintSettings print_settings =
-      static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
-  std::string color_setting_name;
-  std::string color_value;
-  mojom::ColorModel color_model = ColorModeToColorModel(color_mode);
-  if (base::FeatureList::IsEnabled(features::kCupsIppPrintingBackend)) {
-    color_setting_name = CUPS_PRINT_COLOR_MODE;
-    color_value = GetIppColorModelForModel(color_model);
-  } else {
-    GetColorModelForModel(color_model, &color_setting_name, &color_value);
-  }
-  base::ScopedCFTypeRef<CFStringRef> color_setting(
-      base::SysUTF8ToCFStringRef(color_setting_name));
-  base::ScopedCFTypeRef<CFStringRef> output_color(
-      base::SysUTF8ToCFStringRef(color_value));
+  const mojom::ColorModel color_model = ColorModeToColorModel(color_mode);
 
-  return PMPrintSettingsSetValue(print_settings, color_setting.get(),
-                                 output_color.get(), false) == noErr;
+  if (!base::FeatureList::IsEnabled(features::kCupsIppPrintingBackend)) {
+    std::string color_setting_name;
+    std::string color_value;
+    GetColorModelForModel(color_model, &color_setting_name, &color_value);
+    return SetKeyValue(color_setting_name, color_value);
+  }
+
+  // First, set the default CUPS IPP output color.
+  if (!SetKeyValue(CUPS_PRINT_COLOR_MODE,
+                   GetIppColorModelForModel(color_model))) {
+    return false;
+  }
+
+  struct PpdColorSetting {
+    constexpr PpdColorSetting(base::StringPiece name,
+                              base::StringPiece bw,
+                              base::StringPiece color)
+        : name(name), bw(bw), color(color) {}
+    base::StringPiece name;
+    base::StringPiece bw;
+    base::StringPiece color;
+  };
+
+  // TODO(crbug.com/1210992): Move `kKnownPpdColorSettings` elsewhere so it can
+  // be used for general CUPS printing code (e.g., for parsing PPDs).
+  static constexpr PpdColorSetting kKnownPpdColorSettings[] = {
+      {"ARCMode", "CMBW", "CMColor"},                         // Sharp
+      {"BLW", "TrueM", "FalseM"},                             // Lexmark
+      {"BRMonoColor", "Mono", "FullColor"},                   // Brother
+      {"BRPrintQuality", "Black", "Color"},                   // Brother
+      {"CNIJGrayScale", "1", "0"},                            // Canon
+      {"ColorMode", "Monochrome", "Color"},                   // Samsung
+      {"ColorModel", "Gray", "Color"},                        // Generic
+      {"HPColorMode", "GrayscalePrint", "ColorPrint"},        // HP
+      {"Ink", "MONO", "COLOR"},                               // Epson
+      {"OKControl", "Gray", "Auto"},                          // Oki
+      {"PrintoutMode", "Normal.Gray", "Normal"},              // Foomatic
+      {"SelectColor", "Grayscale", "Color"},                  // Konica Minolta
+      {"XRXColor", "BW", "Automatic"},                        // Xerox
+      {"XROutputColor", "PrintAsGrayscale", "PrintAsColor"},  // Xerox
+  };
+
+  // Even when interfacing with printer settings using CUPS IPP, the print job
+  // may still expect PPD color values if the printer was added to the system
+  // with a PPD. To avoid parsing PPDs (which is the point of using CUPS IPP),
+  // set every single known PPD color setting and hope that one of them sticks.
+  const bool is_color = IsColorModelSelected(color_model).value_or(false);
+  for (const auto& setting : kKnownPpdColorSettings) {
+    const base::StringPiece& color_setting_name = setting.name;
+    const base::StringPiece& color_value =
+        is_color ? setting.color : setting.bw;
+    if (!SetKeyValue(color_setting_name, color_value))
+      return false;
+  }
+
+  return true;
 }
 
 bool PrintingContextMac::SetResolution(const gfx::Size& dpi_size) {
@@ -428,6 +469,18 @@
                                       &resolution) == noErr;
 }
 
+bool PrintingContextMac::SetKeyValue(base::StringPiece key,
+                                     base::StringPiece value) {
+  PMPrintSettings print_settings =
+      static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
+  base::ScopedCFTypeRef<CFStringRef> cf_key(base::SysUTF8ToCFStringRef(key));
+  base::ScopedCFTypeRef<CFStringRef> cf_value(
+      base::SysUTF8ToCFStringRef(value));
+
+  return PMPrintSettingsSetValue(print_settings, cf_key.get(), cf_value.get(),
+                                 /*locked=*/false) == noErr;
+}
+
 PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
   PageRanges page_ranges;
   NSDictionary* print_info_dict = [print_info_.get() dictionary];
diff --git a/remoting/codec/frame_processing_time_estimator.cc b/remoting/codec/frame_processing_time_estimator.cc
index e79a35c..37f5ad50 100644
--- a/remoting/codec/frame_processing_time_estimator.cc
+++ b/remoting/codec/frame_processing_time_estimator.cc
@@ -68,7 +68,13 @@
 
 void FrameProcessingTimeEstimator::FinishFrame(
     const WebrtcVideoEncoder::EncodedFrame& frame) {
-  DCHECK(!start_time_.is_null());
+  // TODO(crbug.com/1192865): Extract the start time from |frame| itself,
+  // instead of relying on the latest StartFrame() call matching this
+  // particular frame.
+  if (start_time_.is_null()) {
+    return;
+  }
+
   base::TimeTicks now = Now();
   if (frame_finish_ticks_.size() == kFrameFinishTicksCount) {
     frame_finish_ticks_.pop_front();
diff --git a/remoting/codec/frame_processing_time_estimator.h b/remoting/codec/frame_processing_time_estimator.h
index 273d5362..a896f0d 100644
--- a/remoting/codec/frame_processing_time_estimator.h
+++ b/remoting/codec/frame_processing_time_estimator.h
@@ -24,8 +24,8 @@
   // calling FinishFrame() will deprecate last record.
   void StartFrame();
 
-  // Marks the finish of encoding a frame. FinishFrame() should only be called
-  // after a StartFrame() call.
+  // Marks the finish of encoding a frame. Ignored if there is no
+  // corresponding StartFrame() call.
   void FinishFrame(const WebrtcVideoEncoder::EncodedFrame& frame);
 
   // Sets the estimated network bandwidth. Negative |bandwidth_kbps| will be
diff --git a/services/viz/public/cpp/gpu/client_gpu_memory_buffer_manager.cc b/services/viz/public/cpp/gpu/client_gpu_memory_buffer_manager.cc
index d850948..cc34bf6 100644
--- a/services/viz/public/cpp/gpu/client_gpu_memory_buffer_manager.cc
+++ b/services/viz/public/cpp/gpu/client_gpu_memory_buffer_manager.cc
@@ -177,6 +177,15 @@
     gfx::GpuMemoryBufferHandle buffer_handle,
     base::UnsafeSharedMemoryRegion memory_region,
     base::OnceCallback<void(bool)> callback) {
+  if (!thread_.task_runner()->BelongsToCurrentThread()) {
+    thread_.task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ClientGpuMemoryBufferManager::CopyGpuMemoryBufferAsync,
+                       base::Unretained(this), std::move(buffer_handle),
+                       std::move(memory_region), std::move(callback)));
+    return;
+  }
+
   gpu_->CopyGpuMemoryBuffer(std::move(buffer_handle), std::move(memory_region),
                             std::move(callback));
 }
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index f566544..7c23328 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -37546,7 +37546,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37596,7 +37596,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37646,7 +37646,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37696,7 +37696,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37746,7 +37746,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37796,7 +37796,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37846,7 +37846,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37896,7 +37896,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37946,7 +37946,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37996,7 +37996,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38046,7 +38046,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38096,7 +38096,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38146,7 +38146,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38196,7 +38196,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38247,7 +38247,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38298,7 +38298,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38349,7 +38349,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38400,7 +38400,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38451,7 +38451,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38503,7 +38503,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38555,7 +38555,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38607,7 +38607,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38659,7 +38659,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38711,7 +38711,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38763,7 +38763,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38815,7 +38815,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38867,7 +38867,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38918,7 +38918,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38969,7 +38969,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39020,7 +39020,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39071,7 +39071,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39122,7 +39122,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39173,7 +39173,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39224,7 +39224,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39275,7 +39275,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39327,7 +39327,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39379,7 +39379,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39431,7 +39431,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39482,7 +39482,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39532,7 +39532,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39582,7 +39582,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39633,7 +39633,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39684,7 +39684,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39735,7 +39735,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39786,7 +39786,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39836,7 +39836,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39886,7 +39886,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39936,7 +39936,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -39987,7 +39987,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40038,7 +40038,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40089,7 +40089,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40140,7 +40140,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40190,7 +40190,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40240,7 +40240,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40290,7 +40290,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40340,7 +40340,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40391,7 +40391,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40442,7 +40442,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40493,7 +40493,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40544,7 +40544,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40594,7 +40594,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40644,7 +40644,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40694,7 +40694,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40744,7 +40744,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40794,7 +40794,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40844,7 +40844,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40894,7 +40894,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40944,7 +40944,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -40994,7 +40994,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41044,7 +41044,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41094,7 +41094,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41144,7 +41144,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41194,7 +41194,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41244,7 +41244,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41294,7 +41294,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41344,7 +41344,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41394,7 +41394,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41444,7 +41444,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41494,7 +41494,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41551,7 +41551,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41601,7 +41601,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41651,7 +41651,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41701,7 +41701,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41751,7 +41751,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41801,7 +41801,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41851,7 +41851,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41901,7 +41901,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -41951,7 +41951,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42001,7 +42001,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42051,7 +42051,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42102,7 +42102,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42153,7 +42153,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42204,7 +42204,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42255,7 +42255,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42307,7 +42307,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42359,7 +42359,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42411,7 +42411,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42463,7 +42463,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42515,7 +42515,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42567,7 +42567,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42618,7 +42618,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42669,7 +42669,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42720,7 +42720,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42771,7 +42771,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42822,7 +42822,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42873,7 +42873,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42925,7 +42925,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -42977,7 +42977,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43028,7 +43028,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43078,7 +43078,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43129,7 +43129,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43180,7 +43180,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43231,7 +43231,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43281,7 +43281,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43331,7 +43331,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43381,7 +43381,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43432,7 +43432,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43483,7 +43483,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43534,7 +43534,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43584,7 +43584,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43634,7 +43634,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43684,7 +43684,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43735,7 +43735,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43786,7 +43786,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43837,7 +43837,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43887,7 +43887,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43937,7 +43937,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43987,7 +43987,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44037,7 +44037,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44087,7 +44087,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44137,7 +44137,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44187,7 +44187,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44237,7 +44237,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44287,7 +44287,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44337,7 +44337,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44387,7 +44387,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44437,7 +44437,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44487,7 +44487,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44537,7 +44537,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index dedf7ab..f443b4a1 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2932,7 +2932,7 @@
         ],
         'mixins': [
           'enable_resultdb',
-          'mac_10.15',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
@@ -2950,7 +2950,7 @@
         ],
         'mixins': [
           'enable_resultdb',
-          'mac_10.15',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index cf460eb2..34d828d 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -567,6 +567,21 @@
             ]
         }
     ],
+    "AndroidMessagesPopupBlocked": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "MessagesForAndroidPopupBlocked"
+                    ]
+                }
+            ]
+        }
+    ],
     "AndroidMessagesSavePassword": [
         {
             "platforms": [
@@ -5409,6 +5424,25 @@
             ]
         }
     ],
+    "OmniboxPedalsBatch2": [
+        {
+            "platforms": [
+                "windows",
+                "mac",
+                "chromeos",
+                "linux"
+            ],
+            "experiments": [
+                {
+                    "name": "Batch2Colored",
+                    "enable_features": [
+                        "OmniboxPedalsBatch2",
+                        "OmniboxPedalsDefaultIconColored"
+                    ]
+                }
+            ]
+        }
+    ],
     "OmniboxUpdatedConnectionSecurityIndicatorsIPH": [
         {
             "platforms": [
@@ -5538,7 +5572,8 @@
                         "mode": "always-new-tab"
                     },
                     "enable_features": [
-                        "AdaptiveButtonInTopToolbar"
+                        "AdaptiveButtonInTopToolbar",
+                        "AdaptiveButtonInTopToolbarCustomization"
                     ]
                 },
                 {
@@ -5547,7 +5582,8 @@
                         "mode": "always-share"
                     },
                     "enable_features": [
-                        "AdaptiveButtonInTopToolbar"
+                        "AdaptiveButtonInTopToolbar",
+                        "AdaptiveButtonInTopToolbarCustomization"
                     ]
                 },
                 {
@@ -5556,7 +5592,8 @@
                         "mode": "always-voice"
                     },
                     "enable_features": [
-                        "AdaptiveButtonInTopToolbar"
+                        "AdaptiveButtonInTopToolbar",
+                        "AdaptiveButtonInTopToolbarCustomization"
                     ]
                 },
                 {
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index dbb7278..36576de 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -973,5 +973,10 @@
     "MinimizeAudioProcessingForUnusedOutput",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Makes autofill look across shadow boundaries when collecting form controls to
+// fill.
+const base::Feature kAutofillShadowDOM{"AutofillShadowDOM",
+                                       base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace blink
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 810be99..3f6c476 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -407,6 +407,10 @@
 BLINK_COMMON_EXPORT extern const base::Feature
     kMinimizeAudioProcessingForUnusedOutput;
 
+// Makes autofill look across shadow boundaries when collecting form controls to
+// fill.
+BLINK_COMMON_EXPORT extern const base::Feature kAutofillShadowDOM;
+
 }  // namespace features
 }  // namespace blink
 
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index a2336d6..bd3f53d 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -8758,7 +8758,7 @@
 
   # Stages of the request to handle. Request will intercept before the request is
   # sent. Response will intercept after the response is received (but before response
-  # body is received.
+  # body is received).
   type RequestStage extends string
     enum
       Request
diff --git a/third_party/blink/public/web/web_element.h b/third_party/blink/public/web/web_element.h
index 1835164..940937d6 100644
--- a/third_party/blink/public/web/web_element.h
+++ b/third_party/blink/public/web/web_element.h
@@ -85,6 +85,9 @@
   // Returns true if this is an autonomous custom element.
   bool IsAutonomousCustomElement() const;
 
+  // Returns the owning shadow host for this element, if there is one.
+  WebElement OwnerShadowHost() const;
+
   // Returns an author ShadowRoot attached to this element, regardless
   // of open or closed.  This returns null WebNode if this
   // element has no ShadowRoot or has a UA ShadowRoot.
diff --git a/third_party/blink/public/web/web_frame.h b/third_party/blink/public/web/web_frame.h
index 75f0535..a70289aff 100644
--- a/third_party/blink/public/web/web_frame.h
+++ b/third_party/blink/public/web/web_frame.h
@@ -75,8 +75,10 @@
 
   virtual bool IsWebLocalFrame() const = 0;
   virtual WebLocalFrame* ToWebLocalFrame() = 0;
+  virtual const WebLocalFrame* ToWebLocalFrame() const = 0;
   virtual bool IsWebRemoteFrame() const = 0;
   virtual WebRemoteFrame* ToWebRemoteFrame() = 0;
+  virtual const WebRemoteFrame* ToWebRemoteFrame() const = 0;
 
   bool Swap(WebFrame*);
 
diff --git a/third_party/blink/public/web/web_view.h b/third_party/blink/public/web/web_view.h
index 26aafb1..7ea8039 100644
--- a/third_party/blink/public/web/web_view.h
+++ b/third_party/blink/public/web/web_view.h
@@ -179,6 +179,7 @@
   // Frames --------------------------------------------------------------
 
   virtual WebFrame* MainFrame() = 0;
+  virtual const WebFrame* MainFrame() const = 0;
 
   // Focus ---------------------------------------------------------------
 
@@ -426,7 +427,7 @@
 
   virtual void SetRendererPreferences(
       const RendererPreferences& preferences) = 0;
-  virtual const RendererPreferences& GetRendererPreferences() = 0;
+  virtual const RendererPreferences& GetRendererPreferences() const = 0;
 
   // Web preferences ---------------------------------------------------
 
diff --git a/third_party/blink/renderer/core/aom/computed_accessible_node.cc b/third_party/blink/renderer/core/aom/computed_accessible_node.cc
index 2f4218b..dd746a6b 100644
--- a/third_party/blink/renderer/core/aom/computed_accessible_node.cc
+++ b/third_party/blink/renderer/core/aom/computed_accessible_node.cc
@@ -100,17 +100,15 @@
   AXID ax_id = cache.GetAXID(element_);
 
   ComputedAccessibleNode* accessible_node =
-      document.GetOrCreateComputedAccessibleNode(ax_id, tree);
+      document.GetOrCreateComputedAccessibleNode(ax_id);
   resolver_->Resolve(accessible_node);
 }
 
 // ComputedAccessibleNode ------------------------------------------------------
 
 ComputedAccessibleNode::ComputedAccessibleNode(AXID ax_id,
-                                               WebComputedAXTree* tree,
                                                Document* document)
     : ax_id_(ax_id),
-      tree_(tree),
       document_(document),
       ax_context_(std::make_unique<AXContext>(*document)) {}
 
@@ -220,7 +218,8 @@
 
 const String ComputedAccessibleNode::checked() const {
   WebString out;
-  if (tree_->GetCheckedStateForAXNode(ax_id_, &out)) {
+
+  if (GetTree() && GetTree()->GetCheckedStateForAXNode(ax_id_, &out)) {
     return out;
   }
   return String();
@@ -238,7 +237,7 @@
 
 const String ComputedAccessibleNode::role() const {
   WebString out;
-  if (tree_->GetRoleForAXNode(ax_id_, &out)) {
+  if (GetTree() && GetTree()->GetRoleForAXNode(ax_id_, &out)) {
     return out;
   }
   return String();
@@ -253,49 +252,79 @@
 }
 
 ComputedAccessibleNode* ComputedAccessibleNode::parent() const {
-  int32_t parent_ax_id;
-  if (!tree_->GetParentIdForAXNode(ax_id_, &parent_ax_id)) {
+  WebComputedAXTree* tree = GetTree();
+  if (!tree) {
     return nullptr;
   }
-  return document_->GetOrCreateComputedAccessibleNode(parent_ax_id, tree_);
+  int32_t parent_ax_id;
+  if (!tree->GetParentIdForAXNode(ax_id_, &parent_ax_id)) {
+    return nullptr;
+  }
+  return document_->GetOrCreateComputedAccessibleNode(parent_ax_id);
 }
 
 ComputedAccessibleNode* ComputedAccessibleNode::firstChild() const {
-  int32_t child_ax_id;
-  if (!tree_->GetFirstChildIdForAXNode(ax_id_, &child_ax_id)) {
+  WebComputedAXTree* tree = GetTree();
+  if (!tree) {
     return nullptr;
   }
-  return document_->GetOrCreateComputedAccessibleNode(child_ax_id, tree_);
+  int32_t child_ax_id;
+  if (!tree->GetFirstChildIdForAXNode(ax_id_, &child_ax_id)) {
+    return nullptr;
+  }
+  return document_->GetOrCreateComputedAccessibleNode(child_ax_id);
 }
 
 ComputedAccessibleNode* ComputedAccessibleNode::lastChild() const {
-  int32_t child_ax_id;
-  if (!tree_->GetLastChildIdForAXNode(ax_id_, &child_ax_id)) {
+  WebComputedAXTree* tree = GetTree();
+  if (!tree) {
     return nullptr;
   }
-  return document_->GetOrCreateComputedAccessibleNode(child_ax_id, tree_);
+  int32_t child_ax_id;
+  if (!tree->GetLastChildIdForAXNode(ax_id_, &child_ax_id)) {
+    return nullptr;
+  }
+  return document_->GetOrCreateComputedAccessibleNode(child_ax_id);
 }
 
 ComputedAccessibleNode* ComputedAccessibleNode::previousSibling() const {
-  int32_t sibling_ax_id;
-  if (!tree_->GetPreviousSiblingIdForAXNode(ax_id_, &sibling_ax_id)) {
+  WebComputedAXTree* tree = GetTree();
+  if (!tree) {
     return nullptr;
   }
-  return document_->GetOrCreateComputedAccessibleNode(sibling_ax_id, tree_);
+  int32_t sibling_ax_id;
+  if (!tree->GetPreviousSiblingIdForAXNode(ax_id_, &sibling_ax_id)) {
+    return nullptr;
+  }
+  return document_->GetOrCreateComputedAccessibleNode(sibling_ax_id);
 }
 
 ComputedAccessibleNode* ComputedAccessibleNode::nextSibling() const {
-  int32_t sibling_ax_id;
-  if (!tree_->GetNextSiblingIdForAXNode(ax_id_, &sibling_ax_id)) {
+  WebComputedAXTree* tree = GetTree();
+  if (!tree) {
     return nullptr;
   }
-  return document_->GetOrCreateComputedAccessibleNode(sibling_ax_id, tree_);
+  int32_t sibling_ax_id;
+  if (!tree->GetNextSiblingIdForAXNode(ax_id_, &sibling_ax_id)) {
+    return nullptr;
+  }
+  return document_->GetOrCreateComputedAccessibleNode(sibling_ax_id);
+}
+
+WebComputedAXTree* ComputedAccessibleNode::GetTree() const {
+  LocalFrame* local_frame = document_->GetFrame();
+  if (!local_frame)
+    return nullptr;
+
+  WebLocalFrameClient* client =
+      WebLocalFrameImpl::FromFrame(local_frame)->Client();
+  return client->GetOrCreateWebComputedAXTree();
 }
 
 absl::optional<bool> ComputedAccessibleNode::GetBoolAttribute(
     WebAOMBoolAttribute attr) const {
   bool value;
-  if (tree_->GetBoolAttributeForAXNode(ax_id_, attr, &value))
+  if (GetTree() && GetTree()->GetBoolAttributeForAXNode(ax_id_, attr, &value))
     return value;
   return absl::nullopt;
 }
@@ -303,7 +332,7 @@
 absl::optional<int32_t> ComputedAccessibleNode::GetIntAttribute(
     WebAOMIntAttribute attr) const {
   int32_t value;
-  if (tree_->GetIntAttributeForAXNode(ax_id_, attr, &value))
+  if (GetTree() && GetTree()->GetIntAttributeForAXNode(ax_id_, attr, &value))
     return value;
   return absl::nullopt;
 }
@@ -311,7 +340,7 @@
 absl::optional<float> ComputedAccessibleNode::GetFloatAttribute(
     WebAOMFloatAttribute attr) const {
   float value;
-  if (tree_->GetFloatAttributeForAXNode(ax_id_, attr, &value))
+  if (GetTree() && GetTree()->GetFloatAttributeForAXNode(ax_id_, attr, &value))
     return value;
   return absl::nullopt;
 }
@@ -319,7 +348,7 @@
 const String ComputedAccessibleNode::GetStringAttribute(
     WebAOMStringAttribute attr) const {
   WebString out;
-  if (tree_->GetStringAttributeForAXNode(ax_id_, attr, &out)) {
+  if (GetTree() && GetTree()->GetStringAttributeForAXNode(ax_id_, attr, &out)) {
     return out;
   }
   return String();
diff --git a/third_party/blink/renderer/core/aom/computed_accessible_node.h b/third_party/blink/renderer/core/aom/computed_accessible_node.h
index e33355f..b08ce94 100644
--- a/third_party/blink/renderer/core/aom/computed_accessible_node.h
+++ b/third_party/blink/renderer/core/aom/computed_accessible_node.h
@@ -46,7 +46,7 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  ComputedAccessibleNode(AXID, WebComputedAXTree*, Document*);
+  ComputedAccessibleNode(AXID, Document*);
   ~ComputedAccessibleNode() override = default;
 
   void Trace(Visitor*) const override;
@@ -95,6 +95,7 @@
   ScriptPromise ensureUpToDate(ScriptState*);
 
  private:
+  WebComputedAXTree* GetTree() const;
   absl::optional<bool> GetBoolAttribute(WebAOMBoolAttribute) const;
   absl::optional<int32_t> GetIntAttribute(WebAOMIntAttribute) const;
   absl::optional<float> GetFloatAttribute(WebAOMFloatAttribute) const;
@@ -103,7 +104,6 @@
   AXID ax_id_;
 
   // This tree is owned by the RenderFrame.
-  blink::WebComputedAXTree* tree_;
   Member<Document> document_;
   std::unique_ptr<AXContext> ax_context_;
 };
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index a20093f..16cf484ce 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -8034,11 +8034,9 @@
 }
 
 ComputedAccessibleNode* Document::GetOrCreateComputedAccessibleNode(
-    AXID ax_id,
-    WebComputedAXTree* tree) {
+    AXID ax_id) {
   if (computed_node_mapping_.find(ax_id) == computed_node_mapping_.end()) {
-    auto* node =
-        MakeGarbageCollected<ComputedAccessibleNode>(ax_id, tree, this);
+    auto* node = MakeGarbageCollected<ComputedAccessibleNode>(ax_id, this);
     computed_node_mapping_.insert(ax_id, node);
   }
   return computed_node_mapping_.at(ax_id);
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 083cd83..a5043680 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -210,7 +210,6 @@
 class V8UnionElementCreationOptionsOrString;
 class ViewportData;
 class VisitedLinkState;
-class WebComputedAXTree;
 class WebMouseEvent;
 class WorkletAnimationController;
 enum class CSSPropertyID;
@@ -1580,9 +1579,7 @@
   // associated Web App Manifest, it will return false.
   bool IsInWebAppScope() const;
 
-  ComputedAccessibleNode* GetOrCreateComputedAccessibleNode(
-      AXID ax_id,
-      WebComputedAXTree* tree);
+  ComputedAccessibleNode* GetOrCreateComputedAccessibleNode(AXID ax_id);
 
   bool HaveRenderBlockingResourcesLoaded() const;
 
diff --git a/third_party/blink/renderer/core/exported/web_element.cc b/third_party/blink/renderer/core/exported/web_element.cc
index 1829c4f8..7d6faf2 100644
--- a/third_party/blink/renderer/core/exported/web_element.cc
+++ b/third_party/blink/renderer/core/exported/web_element.cc
@@ -148,6 +148,13 @@
   return WebNode(root);
 }
 
+WebElement WebElement::OwnerShadowHost() const {
+  if (auto* host = ConstUnwrap<Element>()->OwnerShadowHost()) {
+    return WebElement(host);
+  }
+  return WebElement();
+}
+
 WebNode WebElement::OpenOrClosedShadowRoot() {
   if (IsNull())
     return WebNode();
diff --git a/third_party/blink/renderer/core/exported/web_form_element.cc b/third_party/blink/renderer/core/exported/web_form_element.cc
index 3940883..a0bd1676 100644
--- a/third_party/blink/renderer/core/exported/web_form_element.cc
+++ b/third_party/blink/renderer/core/exported/web_form_element.cc
@@ -66,7 +66,8 @@
     const {
   const HTMLFormElement* form = ConstUnwrap<HTMLFormElement>();
   Vector<WebFormControlElement> form_control_elements;
-  for (const auto& element : form->ListedElements()) {
+  for (const auto& element :
+       form->ListedElements(/*include_shadow_trees=*/true)) {
     if (auto* form_control =
             blink::DynamicTo<HTMLFormControlElement>(element.Get())) {
       form_control_elements.push_back(form_control);
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index a30dd1ab..3583015 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1907,6 +1907,11 @@
   return WebFrame::FromCoreFrame(page ? page->MainFrame() : nullptr);
 }
 
+const WebFrame* WebViewImpl::MainFrame() const {
+  Page* page = page_.Get();
+  return WebFrame::FromCoreFrame(page ? page->MainFrame() : nullptr);
+}
+
 WebLocalFrameImpl* WebViewImpl::MainFrameImpl() const {
   Page* page = page_.Get();
   if (!page)
@@ -3132,7 +3137,7 @@
   UpdateRendererPreferences(preferences);
 }
 
-const RendererPreferences& WebViewImpl::GetRendererPreferences() {
+const RendererPreferences& WebViewImpl::GetRendererPreferences() const {
   return renderer_preferences_;
 }
 
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index e536aea..1e80d82d 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -148,6 +148,7 @@
   void SetWindowFeatures(const WebWindowFeatures&) override;
   void SetOpenedByDOM() override;
   WebFrame* MainFrame() override;
+  const WebFrame* MainFrame() const override;
   WebLocalFrame* FocusedFrame() override;
   void SetFocusedFrame(WebFrame*) override;
   void SmoothScroll(int target_x,
@@ -201,7 +202,7 @@
       CrossVariantMojoRemote<mojom::RendererPreferenceWatcherInterfaceBase>
           watcher) override;
   void SetRendererPreferences(const RendererPreferences& preferences) override;
-  const RendererPreferences& GetRendererPreferences() override;
+  const RendererPreferences& GetRendererPreferences() const override;
   void SetWebPreferences(const web_pref::WebPreferences& preferences) override;
   const web_pref::WebPreferences& GetWebPreferences() override;
   void SetHistoryListFromNavigation(
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index c065d4a..6ccbeca2 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -689,6 +689,10 @@
   return this;
 }
 
+const WebLocalFrame* WebLocalFrameImpl::ToWebLocalFrame() const {
+  return this;
+}
+
 bool WebLocalFrameImpl::IsWebRemoteFrame() const {
   return false;
 }
@@ -698,6 +702,11 @@
   return nullptr;
 }
 
+const WebRemoteFrame* WebLocalFrameImpl::ToWebRemoteFrame() const {
+  NOTREACHED();
+  return nullptr;
+}
+
 void WebLocalFrameImpl::Close() {
   WebLocalFrame::Close();
 
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.h b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
index 415bd0ea..07f6946 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
@@ -509,8 +509,10 @@
   // to call these on a WebLocalFrameImpl.
   bool IsWebLocalFrame() const override;
   WebLocalFrame* ToWebLocalFrame() override;
+  const WebLocalFrame* ToWebLocalFrame() const override;
   bool IsWebRemoteFrame() const override;
   WebRemoteFrame* ToWebRemoteFrame() override;
+  const WebRemoteFrame* ToWebRemoteFrame() const override;
   void CreateFrameWidgetInternal(
       base::PassKey<WebLocalFrame> pass_key,
       CrossVariantMojoAssociatedRemote<
diff --git a/third_party/blink/renderer/core/frame/web_remote_frame_impl.cc b/third_party/blink/renderer/core/frame/web_remote_frame_impl.cc
index e73a828e..999fb678 100644
--- a/third_party/blink/renderer/core/frame/web_remote_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_remote_frame_impl.cc
@@ -153,6 +153,11 @@
   return nullptr;
 }
 
+const WebLocalFrame* WebRemoteFrameImpl::ToWebLocalFrame() const {
+  NOTREACHED();
+  return nullptr;
+}
+
 bool WebRemoteFrameImpl::IsWebRemoteFrame() const {
   return true;
 }
@@ -161,6 +166,10 @@
   return this;
 }
 
+const WebRemoteFrame* WebRemoteFrameImpl::ToWebRemoteFrame() const {
+  return this;
+}
+
 void WebRemoteFrameImpl::Close() {
   WebRemoteFrame::Close();
 
diff --git a/third_party/blink/renderer/core/frame/web_remote_frame_impl.h b/third_party/blink/renderer/core/frame/web_remote_frame_impl.h
index 189e781b..de4ed32 100644
--- a/third_party/blink/renderer/core/frame/web_remote_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_remote_frame_impl.h
@@ -138,8 +138,10 @@
   // to call these on a WebRemoteFrameImpl.
   bool IsWebLocalFrame() const override;
   WebLocalFrame* ToWebLocalFrame() override;
+  const WebLocalFrame* ToWebLocalFrame() const override;
   bool IsWebRemoteFrame() const override;
   WebRemoteFrame* ToWebRemoteFrame() override;
+  const WebRemoteFrame* ToWebRemoteFrame() const override;
 
   WebRemoteFrameClient* client_;
   // TODO(dcheng): Inline this field directly rather than going through Member.
diff --git a/third_party/blink/renderer/core/html/forms/html_form_element.cc b/third_party/blink/renderer/core/html/forms/html_form_element.cc
index 5a4f06cc..d7ba308 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_form_element.cc
@@ -73,9 +73,26 @@
 
 namespace blink {
 
+namespace {
+
+bool HasFormInBetween(const Node* root, const Node* descendant) {
+  DCHECK(descendant->IsDescendantOf(root));
+  DCHECK(!IsA<HTMLFormElement>(descendant));
+  for (ContainerNode* parent = descendant->parentNode(); parent != root;
+       parent = parent->parentNode()) {
+    if (DynamicTo<HTMLFormElement>(parent)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace
+
 HTMLFormElement::HTMLFormElement(Document& document)
     : HTMLElement(html_names::kFormTag, document),
       listed_elements_are_dirty_(false),
+      listed_elements_including_shadow_trees_are_dirty_(false),
       image_elements_are_dirty_(false),
       has_elements_associated_by_parser_(false),
       has_elements_associated_by_form_attribute_(false),
@@ -93,6 +110,7 @@
   visitor->Trace(past_names_map_);
   visitor->Trace(radio_button_group_scope_);
   visitor->Trace(listed_elements_);
+  visitor->Trace(listed_elements_including_shadow_trees_);
   visitor->Trace(image_elements_);
   HTMLElement::Trace(visitor);
 }
@@ -636,6 +654,8 @@
 void HTMLFormElement::Associate(ListedElement& e) {
   listed_elements_are_dirty_ = true;
   listed_elements_.clear();
+  listed_elements_including_shadow_trees_are_dirty_ = true;
+  listed_elements_including_shadow_trees_.clear();
   if (e.ToHTMLElement().FastHasAttribute(html_names::kFormAttr))
     has_elements_associated_by_form_attribute_ = true;
 }
@@ -643,6 +663,8 @@
 void HTMLFormElement::Disassociate(ListedElement& e) {
   listed_elements_are_dirty_ = true;
   listed_elements_.clear();
+  listed_elements_including_shadow_trees_are_dirty_ = true;
+  listed_elements_including_shadow_trees_.clear();
   RemoveFromPastNamesMap(e.ToHTMLElement());
 }
 
@@ -679,31 +701,66 @@
 }
 
 void HTMLFormElement::CollectListedElements(
-    Node& root,
-    ListedElement::List& elements) const {
+    const Node& root,
+    ListedElement::List& elements,
+    ListedElement::List* elements_including_shadow_trees,
+    bool in_shadow_tree) const {
+  DCHECK(!in_shadow_tree || elements_including_shadow_trees);
   elements.clear();
   for (HTMLElement& element : Traversal<HTMLElement>::StartsAfter(root)) {
-    ListedElement* listed_element = ListedElement::From(element);
-    if (listed_element && listed_element->Form() == this)
-      elements.push_back(listed_element);
+    if (ListedElement* listed_element = ListedElement::From(element)) {
+      // If there is a <form> in between |root| and |listed_element|, then we
+      // shouldn't include it in |elements_including_shadow_trees| in order to
+      // prevent multiple forms from "owning" the same |listed_element| as shown
+      // by their |elements_including_shadow_trees|. |elements| doesn't have
+      // this problem because it can check |listed_element->Form()|.
+      if (in_shadow_tree && !HasFormInBetween(&root, &element)) {
+        elements_including_shadow_trees->push_back(listed_element);
+      } else if (listed_element->Form() == this) {
+        elements.push_back(listed_element);
+        if (elements_including_shadow_trees)
+          elements_including_shadow_trees->push_back(listed_element);
+      }
+    }
+    if (elements_including_shadow_trees && element.AuthorShadowRoot() &&
+        !HasFormInBetween(&root, &element)) {
+      const Node& shadow = *element.AuthorShadowRoot();
+      CollectListedElements(shadow, elements, elements_including_shadow_trees,
+                            /*in_shadow_tree=*/true);
+    }
   }
 }
 
 // This function should be const conceptually. However we update some fields
 // because of lazy evaluation.
-const ListedElement::List& HTMLFormElement::ListedElements() const {
-  if (!listed_elements_are_dirty_)
-    return listed_elements_;
-  HTMLFormElement* mutable_this = const_cast<HTMLFormElement*>(this);
-  Node* scope = mutable_this;
-  if (has_elements_associated_by_parser_)
-    scope = &NodeTraversal::HighestAncestorOrSelf(*mutable_this);
-  if (isConnected() && has_elements_associated_by_form_attribute_)
-    scope = &GetTreeScope().RootNode();
-  DCHECK(scope);
-  CollectListedElements(*scope, mutable_this->listed_elements_);
-  mutable_this->listed_elements_are_dirty_ = false;
-  return listed_elements_;
+const ListedElement::List& HTMLFormElement::ListedElements(
+    bool include_shadow_trees) const {
+  if (!RuntimeEnabledFeatures::AutofillShadowDOMEnabled())
+    include_shadow_trees = false;
+  bool collect_shadow_inputs =
+      include_shadow_trees && listed_elements_including_shadow_trees_are_dirty_;
+
+  if (listed_elements_are_dirty_ || collect_shadow_inputs) {
+    HTMLFormElement* mutable_this = const_cast<HTMLFormElement*>(this);
+    Node* scope = mutable_this;
+    if (has_elements_associated_by_parser_)
+      scope = &NodeTraversal::HighestAncestorOrSelf(*mutable_this);
+    if (isConnected() && has_elements_associated_by_form_attribute_)
+      scope = &GetTreeScope().RootNode();
+    DCHECK(scope);
+    mutable_this->listed_elements_.clear();
+    mutable_this->listed_elements_including_shadow_trees_.clear();
+    CollectListedElements(
+        *scope, mutable_this->listed_elements_,
+        collect_shadow_inputs
+            ? &mutable_this->listed_elements_including_shadow_trees_
+            : nullptr);
+    mutable_this->listed_elements_are_dirty_ = false;
+    mutable_this->listed_elements_including_shadow_trees_are_dirty_ =
+        !collect_shadow_inputs;
+  }
+  return include_shadow_trees ? listed_elements_including_shadow_trees_
+                              : listed_elements_;
 }
 
 void HTMLFormElement::CollectImageElements(
@@ -949,4 +1006,8 @@
   }
 }
 
+void HTMLFormElement::InvalidateListedElementsIncludingShadowTrees() {
+  listed_elements_including_shadow_trees_are_dirty_ = true;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/forms/html_form_element.h b/third_party/blink/renderer/core/html/forms/html_form_element.h
index 7c89b71..c29d418 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_form_element.h
@@ -103,7 +103,8 @@
     return radio_button_group_scope_;
   }
 
-  const ListedElement::List& ListedElements() const;
+  const ListedElement::List& ListedElements(
+      bool include_shadow_trees = false) const;
   const HeapVector<Member<HTMLImageElement>>& ImageElements();
 
 #if defined(USE_BLINK_V8_BINDING_NEW_IDL_UNION)
@@ -120,6 +121,8 @@
 
   unsigned UniqueRendererFormId() const { return unique_renderer_form_id_; }
 
+  void InvalidateListedElementsIncludingShadowTrees();
+
  private:
   InsertionNotificationRequest InsertedInto(ContainerNode&) override;
   void RemovedFrom(ContainerNode&) override;
@@ -139,7 +142,11 @@
   void ScheduleFormSubmission(const Event*,
                               HTMLFormControlElement* submit_button);
 
-  void CollectListedElements(Node& root, ListedElement::List&) const;
+  void CollectListedElements(
+      const Node& root,
+      ListedElement::List& elements,
+      ListedElement::List* elements_including_shadow_trees = nullptr,
+      bool in_shadow_tree = false) const;
   void CollectImageElements(Node& root, HeapVector<Member<HTMLImageElement>>&);
 
   // Returns true if the submission should proceed.
@@ -163,6 +170,9 @@
 
   // Do not access listed_elements_ directly. Use ListedElements() instead.
   ListedElement::List listed_elements_;
+  // Do not access listed_elements_including_shadow_trees_ directly. Use
+  // ListedElements(true) instead.
+  ListedElement::List listed_elements_including_shadow_trees_;
   // Do not access image_elements_ directly. Use ImageElements() instead.
   HeapVector<Member<HTMLImageElement>> image_elements_;
 
@@ -173,6 +183,7 @@
   bool is_constructing_entry_list_ = false;
 
   bool listed_elements_are_dirty_ : 1;
+  bool listed_elements_including_shadow_trees_are_dirty_ : 1;
   bool image_elements_are_dirty_ : 1;
   bool has_elements_associated_by_parser_ : 1;
   bool has_elements_associated_by_form_attribute_ : 1;
diff --git a/third_party/blink/renderer/core/html/forms/html_form_element_test.cc b/third_party/blink/renderer/core/html/forms/html_form_element_test.cc
index 1f79394e..e9863afd6 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_element_test.cc
+++ b/third_party/blink/renderer/core/html/forms/html_form_element_test.cc
@@ -5,6 +5,9 @@
 #include "third_party/blink/renderer/core/html/forms/html_form_element.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/html_body_element.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 
 namespace blink {
@@ -31,4 +34,224 @@
   EXPECT_EQ(first_id + 2, form3->UniqueRendererFormId());
 }
 
+// This tree is created manually because the HTML parser removes nested forms.
+// The created tree looks like this:
+// <body>
+//   <form id=form1>
+//     <form id=form2>
+//       <input>
+TEST_F(HTMLFormElementTest, ListedElementsNestedForms) {
+  HTMLBodyElement* body = GetDocument().FirstBodyElement();
+
+  HTMLFormElement* form1 = MakeGarbageCollected<HTMLFormElement>(GetDocument());
+  body->AppendChild(form1);
+
+  HTMLFormElement* form2 = MakeGarbageCollected<HTMLFormElement>(GetDocument());
+  form1->AppendChild(form2);
+
+  HTMLInputElement* input = MakeGarbageCollected<HTMLInputElement>(
+      GetDocument(), CreateElementFlags::ByCreateElement());
+  form2->AppendChild(input);
+
+  ListedElement::List form1elements = form1->ListedElements();
+  ListedElement::List form2elements = form2->ListedElements();
+  EXPECT_EQ(form1elements.size(), 0u);
+  ASSERT_EQ(form2elements.size(), 1u);
+  EXPECT_EQ(form2elements.at(0)->ToHTMLElement(), input);
+}
+
+TEST_F(HTMLFormElementTest, ListedElementsDetachedForm) {
+  HTMLBodyElement* body = GetDocument().FirstBodyElement();
+
+  HTMLFormElement* form = MakeGarbageCollected<HTMLFormElement>(GetDocument());
+  body->AppendChild(form);
+
+  HTMLInputElement* input = MakeGarbageCollected<HTMLInputElement>(
+      GetDocument(), CreateElementFlags::ByCreateElement());
+  form->AppendChild(input);
+
+  ListedElement::List listed_elements = form->ListedElements();
+  ASSERT_EQ(listed_elements.size(), 1u);
+  EXPECT_EQ(listed_elements.at(0)->ToHTMLElement(), input);
+
+  form->remove();
+  listed_elements = form->ListedElements();
+  ASSERT_EQ(listed_elements.size(), 1u);
+  EXPECT_EQ(listed_elements.at(0)->ToHTMLElement(), input);
+}
+
+// This tree is created manually because the HTML parser removes nested forms.
+// The created tree looks like this:
+// <body>
+//   <form id=form1>
+//     <div id=form1div>
+//       <template shadowroot=open>
+//         <form id=form2>
+//           <form id=form3>
+//             <div id=form3div>
+//               <template shadowroot=open>
+//
+// An <input> element is appended at the bottom and moved up one node at a time
+// in this tree, and each step of the way, ListedElements is checked on all
+// forms.
+TEST_F(HTMLFormElementTest, ListedElementsIncludeShadowTrees) {
+  HTMLBodyElement* body = GetDocument().FirstBodyElement();
+
+  HTMLFormElement* form1 = MakeGarbageCollected<HTMLFormElement>(GetDocument());
+  body->AppendChild(form1);
+
+  HTMLDivElement* form1div =
+      MakeGarbageCollected<HTMLDivElement>(GetDocument());
+  form1->AppendChild(form1div);
+  ShadowRoot& form1root =
+      form1div->AttachShadowRootInternal(ShadowRootType::kOpen);
+
+  HTMLFormElement* form2 = MakeGarbageCollected<HTMLFormElement>(GetDocument());
+  form1root.AppendChild(form2);
+
+  HTMLFormElement* form3 = MakeGarbageCollected<HTMLFormElement>(GetDocument());
+  form2->AppendChild(form3);
+
+  HTMLDivElement* form3div =
+      MakeGarbageCollected<HTMLDivElement>(GetDocument());
+  form3->AppendChild(form3div);
+  ShadowRoot& form3root =
+      form3div->AttachShadowRootInternal(ShadowRootType::kOpen);
+
+  HTMLInputElement* input = MakeGarbageCollected<HTMLInputElement>(
+      GetDocument(), CreateElementFlags::ByCreateElement());
+
+  form3root.AppendChild(input);
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{input});
+
+  input->remove();
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+
+  form3div->AppendChild(input);
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{input});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{input});
+
+  form3->AppendChild(input);
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{input});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{input});
+
+  input->remove();
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+
+  form2->AppendChild(input);
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{input});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{input});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+
+  input->remove();
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+
+  form1root.AppendChild(input);
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{input});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+
+  input->remove();
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+
+  form1div->AppendChild(input);
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{input});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{input});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+
+  form1->AppendChild(input);
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{input});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{input});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+
+  input->remove();
+  EXPECT_EQ(form1->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form1->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form2->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(), ListedElement::List{});
+  EXPECT_EQ(form3->ListedElements(/*include_shadow_trees=*/true),
+            ListedElement::List{});
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/forms/listed_element.cc b/third_party/blink/renderer/core/html/forms/listed_element.cc
index a55a83f6..f416080 100644
--- a/third_party/blink/renderer/core/html/forms/listed_element.cc
+++ b/third_party/blink/renderer/core/html/forms/listed_element.cc
@@ -51,6 +51,27 @@
 
 namespace blink {
 
+namespace {
+
+void InvalidateShadowIncludingAncestorForms(ContainerNode& insertion_point) {
+  if (!RuntimeEnabledFeatures::AutofillShadowDOMEnabled())
+    return;
+
+  // Let any forms in the shadow including ancestors know that this
+  // ListedElement has changed. Don't include any forms inside the same
+  // TreeScope know because that relationship isn't tracked by listed elements
+  // including shadow trees.
+  for (ContainerNode* parent = insertion_point.OwnerShadowHost(); parent;
+       parent = parent->ParentOrShadowHostNode()) {
+    if (HTMLFormElement* form = DynamicTo<HTMLFormElement>(parent)) {
+      form->InvalidateListedElementsIncludingShadowTrees();
+      return;
+    }
+  }
+}
+
+}  // namespace
+
 class FormAttributeTargetObserver : public IdTargetObserver {
  public:
   FormAttributeTargetObserver(const AtomicString& id, ListedElement*);
@@ -123,6 +144,8 @@
   // Trigger for elements outside of forms.
   if (!form_ && insertion_point.isConnected())
     element.GetDocument().DidAssociateFormControl(&element);
+
+  InvalidateShadowIncludingAncestorForms(insertion_point);
 }
 
 void ListedElement::RemovedFrom(ContainerNode& insertion_point) {
@@ -156,6 +179,8 @@
         .GetFormController()
         .InvalidateStatefulFormControlList();
   }
+
+  InvalidateShadowIncludingAncestorForms(insertion_point);
 }
 
 HTMLFormElement* ListedElement::FindAssociatedForm(
diff --git a/third_party/blink/renderer/core/html/media/html_video_element.cc b/third_party/blink/renderer/core/html/media/html_video_element.cc
index 4c67780..0b551aa 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_video_element.cc
@@ -347,10 +347,6 @@
     return;
   }
 
-  // TODO(mustaq): This is problematic, see https://crbug.com/1082258.
-  LocalFrame::NotifyUserActivation(
-      GetDocument().GetFrame(),
-      mojom::blink::UserActivationNotificationType::kMedia);
   webkitEnterFullscreen();
 }
 
diff --git a/third_party/blink/renderer/core/html/media/video_auto_fullscreen_test.cc b/third_party/blink/renderer/core/html/media/video_auto_fullscreen_test.cc
index 93fb7c5..017df0e 100644
--- a/third_party/blink/renderer/core/html/media/video_auto_fullscreen_test.cc
+++ b/third_party/blink/renderer/core/html/media/video_auto_fullscreen_test.cc
@@ -169,7 +169,9 @@
   EXPECT_FALSE(Video()->paused());
 }
 
-TEST_F(VideoAutoFullscreen, OnPlayTriggersFullscreenWithoutGesture) {
+// This test is disabled because it requires adding a fake activation in
+// production code (crbug.com/1082258).
+TEST_F(VideoAutoFullscreen, DISABLED_OnPlayTriggersFullscreenWithoutGesture) {
   Video()->SetSrc("http://example.com/foo.mp4");
 
   LocalFrame::NotifyUserActivation(
diff --git a/third_party/blink/renderer/core/paint/background_image_geometry.cc b/third_party/blink/renderer/core/paint/background_image_geometry.cc
index 6f0e990e..863e82c 100644
--- a/third_party/blink/renderer/core/paint/background_image_geometry.cc
+++ b/third_party/blink/renderer/core/paint/background_image_geometry.cc
@@ -929,8 +929,6 @@
             : computed_x_position;
     SetNoRepeatX(fill_layer, unsnapped_box_offset.left + x_offset,
                  snapped_box_offset.left + snapped_x_offset);
-    if (OffsetInBackground(fill_layer).left > tile_size_.width)
-      unsnapped_dest_rect_ = snapped_dest_rect_ = PhysicalRect();
   }
 
   if (background_repeat_y == EFillRepeat::kRepeatFill) {
@@ -960,8 +958,6 @@
             : computed_y_position;
     SetNoRepeatY(fill_layer, unsnapped_box_offset.top + y_offset,
                  snapped_box_offset.top + snapped_y_offset);
-    if (OffsetInBackground(fill_layer).top > tile_size_.height)
-      unsnapped_dest_rect_ = snapped_dest_rect_ = PhysicalRect();
   }
 
   if (ShouldUseFixedAttachment(fill_layer))
diff --git a/third_party/blink/renderer/modules/storage/storage_namespace.cc b/third_party/blink/renderer/modules/storage/storage_namespace.cc
index 3827a9a..234567d1 100644
--- a/third_party/blink/renderer/modules/storage/storage_namespace.cc
+++ b/third_party/blink/renderer/modules/storage/storage_namespace.cc
@@ -30,7 +30,7 @@
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_functions.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -86,10 +86,12 @@
                                           : CacheMetrics::kUnused;
     result = cache_it->value;
   }
-  if (IsSessionStorage())
-    LOCAL_HISTOGRAM_ENUMERATION("SessionStorage.RendererAreaCacheHit", metric);
-  else
-    UMA_HISTOGRAM_ENUMERATION("LocalStorage.RendererAreaCacheHit", metric);
+  if (IsSessionStorage()) {
+    base::UmaHistogramEnumeration("Storage.SessionStorage.RendererAreaCacheHit",
+                                  metric);
+  } else {
+    base::UmaHistogramEnumeration("LocalStorage.RendererAreaCacheHit", metric);
+  }
 
   if (result)
     return result;
diff --git a/third_party/blink/renderer/modules/webdatabase/database_authorizer.cc b/third_party/blink/renderer/modules/webdatabase/database_authorizer.cc
index 13a75e4b..fcf809de 100644
--- a/third_party/blink/renderer/modules/webdatabase/database_authorizer.cc
+++ b/third_party/blink/renderer/modules/webdatabase/database_authorizer.cc
@@ -84,6 +84,8 @@
           // SQLite ICU functions
           // like(), lower() and upper() are already in the list
           "regexp",
+          // Used internally by ALTER TABLE ADD COLUMN.
+          "printf",
       }));
   return list;
 }
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc
index f2a9081..27f09a4 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc
+++ b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc
@@ -346,32 +346,38 @@
   }
   const int width = static_cast<int>(image->width);
   const int height = static_cast<int>(image->height);
+  // Picture center of the image is at pcX and pcY, defined as follows:
+  //   pcX = horizOff + (width - 1)/2
+  //   pcY = vertOff + (height - 1)/2
+  //
   // pcX * 2
   const int64_t center_x_2 = horiz_off_2 + (width - 1);
   // pcY * 2
   const int64_t center_y_2 = vert_off_2 + (height - 1);
-  if (center_x_2 < 0 || center_x_2 > 2 * (width - 1)) {
-    DVLOG(1) << "Clean aperture horizontal offset is too big";
-    return false;
-  }
-  if (center_y_2 < 0 || center_y_2 > 2 * (height - 1)) {
-    DVLOG(1) << "Clean aperture vertical offset is too big";
-    return false;
-  }
+  // The leftmost and rightmost pixels of the clean aperture fall at:
+  //   pcX - (cleanApertureWidth - 1)/2
+  //   pcX + (cleanApertureWidth - 1)/2
+  //
   // Leftmost pixel * 2
   const int64_t leftmost_2 = center_x_2 - (clap_width - 1);
   // Rightmost pixel * 2
   const int64_t rightmost_2 = center_x_2 + (clap_width - 1);
   if (leftmost_2 < 0 || rightmost_2 > 2 * (width - 1)) {
-    DVLOG(1) << "Clean aperture width is too big";
+    DVLOG(1) << "Leftmost or rightmost pixel of clean aperture is out of the "
+                "image's bounds";
     return false;
   }
+  // The topmost and bottommost lines of the clean aperture fall at:
+  //   pcY - (cleanApertureHeight - 1)/2
+  //   pcY + (cleanApertureHeight - 1)/2
+  //
   // Topmost line * 2
   const int64_t topmost_2 = center_y_2 - (clap_height - 1);
   // Bottommost line * 2
   const int64_t bottommost_2 = center_y_2 + (clap_height - 1);
   if (topmost_2 < 0 || bottommost_2 > 2 * (height - 1)) {
-    DVLOG(1) << "Clean aperture height is too big";
+    DVLOG(1) << "Topmost or bottommost line of clean aperture is out of the "
+                "image's bounds";
     return false;
   }
   if (leftmost_2 % 2 != 0) {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 07b64723..cf74ce4 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -204,6 +204,10 @@
       status: "experimental",
     },
     {
+      name: "AutofillShadowDOM",
+      status: "experimental",
+    },
+    {
       name: "AutoLazyLoadOnReloads",
       depends_on: ["LazyFrameLoading"],
     },
diff --git a/third_party/blink/tools/blinkpy/web_tests/merge_results.py b/third_party/blink/tools/blinkpy/web_tests/merge_results.py
index 2a8c4354..c68c1fd8 100644
--- a/third_party/blink/tools/blinkpy/web_tests/merge_results.py
+++ b/third_party/blink/tools/blinkpy/web_tests/merge_results.py
@@ -413,9 +413,7 @@
         other non-JSON data.
         """
         fd.write(before)
-        fd.write(
-            re.subn('\\s+\n', '\n',
-                    json.dumps(json_data, indent=2, sort_keys=True))[0])
+        fd.write(json.dumps(json_data, separators=(",", ":"), sort_keys=True))
         fd.write(after)
 
 
diff --git a/third_party/blink/tools/blinkpy/web_tests/merge_results_unittest.py b/third_party/blink/tools/blinkpy/web_tests/merge_results_unittest.py
index b47360db..5d8a872 100644
--- a/third_party/blink/tools/blinkpy/web_tests/merge_results_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/merge_results_unittest.py
@@ -6,6 +6,7 @@
 # pylint complains about the assertXXX methods and the usage of short variables
 # m/a/b/d in the tests.
 
+import json
 import types
 import unittest
 
@@ -623,10 +624,24 @@
         self.assertDictEqual(expected_json, json_data)
         self.assertEqual(expected_after, after)
 
-    def assertDump(self, before, json, after, expected_data):
+    def assertDump(self, before, json_data, after):
         fd = StringIO.StringIO()
-        merge_results.MergeFilesJSONP.dump_jsonp(fd, before, json, after)
-        self.assertMultiLineEqual(fd.getvalue(), expected_data)
+        merge_results.MergeFilesJSONP.dump_jsonp(fd, before, json_data, after)
+        merged_str = fd.getvalue()
+        self.assertTrue(self.check_before_after(merged_str, before, after))
+        json_str = self.remove_before_after(merged_str, before, after)
+        self.assertEqual(json_data, json.loads(json_str))
+
+    @staticmethod
+    def check_before_after(json_str, before, after):
+        return json_str.startswith(before) and json_str.endswith(after)
+
+    @staticmethod
+    def remove_before_after(full_json_str, before, after):
+        json_str = full_json_str[len(before):]
+        if after:
+            json_str = json_str[:-len(after)]
+        return json_str
 
     def test_load(self):
         fdcls = StringIO.StringIO
@@ -638,31 +653,18 @@
         self.assertLoad(fdcls('/* {"a": 1} */'), '/* ', {'a': 1}, ' */')
 
     def test_dump(self):
-        self.assertDump('', {}, '', '{}')
-        self.assertDump('f(', {}, ');', 'f({});')
-        self.assertDump('var o = ', {}, '', 'var o = {}')
-        self.assertDump('while(1); // ', {}, '', 'while(1); // {}')
-        self.assertDump('/* ', {}, ' */', '/* {} */')
+        self.assertDump('', {}, '')
+        self.assertDump('f(', {}, ');')
+        self.assertDump('var o = ', {}, '')
+        self.assertDump('while(1); // ', {}, '')
+        self.assertDump('/* ', {}, ' */')
 
-        self.assertDump('', {'a': 1}, '', """\
-{
-  "a": 1
-}""")
-        self.assertDump(
-            '', {
-                'a': [1, 'c', 3],
-                'b': 2
-            }, '', """\
-{
-  "a": [
-    1,
-    "c",
-    3
-  ],
-  "b": 2
-}""")
+        self.assertDump('', {'a': 1}, '')
+        self.assertDump('', {'a': [1, 'c', 3], 'b': 2}, '')
 
     def assertMergeResults(self,
+                           before,
+                           after,
                            mock_filesystem_contents,
                            inputargs,
                            filesystem_contains,
@@ -672,8 +674,23 @@
 
         file_merger = merge_results.MergeFilesJSONP(mock_filesystem,
                                                     json_data_merger)
-        with self.assertFilesAdded(mock_filesystem, filesystem_contains):
-            file_merger(*inputargs)
+        file_merger(*inputargs)
+        files = mock_filesystem.files_under('/output')
+        self.assertTrue(len(files) == 1)
+        expected_mock_filesystem = MockFileSystem(filesystem_contains)
+        expected_files = expected_mock_filesystem.files_under('/output')
+        actual_output = mock_filesystem.read_text_file(files[0])
+        expected_output = expected_mock_filesystem.read_text_file(
+            expected_files[0])
+        self.assertTrue(self.check_before_after(actual_output, before, after))
+        self.assertTrue(self.check_before_after(expected_output, before,
+                                                after))
+        actual_json_str = self.remove_before_after(actual_output, before,
+                                                   after)
+        expected_json_str = self.remove_before_after(expected_output, before,
+                                                     after)
+        self.assertEqual(json.loads(actual_json_str),
+                         json.loads(expected_json_str))
 
     def assertMergeRaises(self, mock_filesystem_contents, inputargs):
         mock_filesystem = MockFileSystem(
@@ -684,41 +701,29 @@
             file_merger(*inputargs)
 
     def test_single_file(self):
-        self.assertMergeResults({
-            '/s/filea': '{"a": 1}'
-        }, ('/output/out1', ['/s/filea']),
+        self.assertMergeResults('', '', {'/s/filea': '{"a": 1}'},
+                                ('/output/out1', ['/s/filea']),
                                 {'/output/out1': """\
-{
-  "a": 1
-}"""})
+{"a":1}"""})
 
-        self.assertMergeResults({
-            '/s/filef1a': 'f1({"a": 1})'
-        }, ('/output/outf1', ['/s/filef1a']),
+        self.assertMergeResults('f1(', ')', {'/s/filef1a': 'f1({"a": 1})'},
+                                ('/output/outf1', ['/s/filef1a']),
                                 {'/output/outf1': """\
-f1({
-  "a": 1
-})"""})
+f1({"a":1})"""})
 
-        self.assertMergeResults({
-            '/s/fileb1': '{"b": 2}'
-        }, ('/output/out2', ['/s/fileb1']),
+        self.assertMergeResults('', '', {'/s/fileb1': '{"b": 2}'},
+                                ('/output/out2', ['/s/fileb1']),
                                 {'/output/out2': """\
-{
-  "b": 2
-}"""})
+{"b":2}"""})
 
-        self.assertMergeResults({
-            '/s/filef1b1': 'f1({"b": 2})'
-        }, ('/output/outf2', ['/s/filef1b1']),
+        self.assertMergeResults('f1(', ')', {'/s/filef1b1': 'f1({"b": 2})'},
+                                ('/output/outf2', ['/s/filef1b1']),
                                 {'/output/outf2': """\
-f1({
-  "b": 2
-})"""})
+f1({"b":2})"""})
 
     def test_two_files_nonconflicting_values(self):
         self.assertMergeResults(
-            {
+            '', '', {
                 '/s/filea': '{"a": 1}',
                 '/s/fileb1': '{"b": 2}',
             }, ('/output/out3', ['/s/filea', '/s/fileb1']),
@@ -729,7 +734,7 @@
 }"""})
 
         self.assertMergeResults(
-            {
+            'f1(', ')', {
                 '/s/filef1a': 'f1({"a": 1})',
                 '/s/filef1b1': 'f1({"b": 2})',
             }, ('/output/outf3', ['/s/filef1a', '/s/filef1b1']),
@@ -754,25 +759,29 @@
         json_data_merger = merge_results.JSONMerger()
         json_data_merger.fallback_matcher = json_data_merger.merge_equal
 
-        self.assertMergeResults({
-            '/s/fileb1': '{"b": 2}',
-            '/s/fileb2': '{"b": 2}',
-        }, ('/output/out4', ['/s/fileb1', '/s/fileb2']),
+        self.assertMergeResults('',
+                                '', {
+                                    '/s/fileb1': '{"b": 2}',
+                                    '/s/fileb2': '{"b": 2}',
+                                },
+                                ('/output/out4', ['/s/fileb1', '/s/fileb2']),
                                 {'/output/out4': """\
 {
   "b": 2
 }"""},
                                 json_data_merger=json_data_merger)
 
-        self.assertMergeResults({
-            '/s/filef1b1': 'f1({"b": 2})',
-            '/s/filef1b2': 'f1({"b": 2})',
-        }, ('/output/outf4', ['/s/filef1b1', '/s/filef1b2']),
-                                {'/output/outf4': """\
+        self.assertMergeResults(
+            'f1(',
+            ')', {
+                '/s/filef1b1': 'f1({"b": 2})',
+                '/s/filef1b2': 'f1({"b": 2})',
+            }, ('/output/outf4', ['/s/filef1b1', '/s/filef1b2']),
+            {'/output/outf4': """\
 f1({
   "b": 2
 })"""},
-                                json_data_merger=json_data_merger)
+            json_data_merger=json_data_merger)
 
     def test_two_files_conflicting_values(self):
         self.assertMergeRaises({
@@ -1477,9 +1486,28 @@
             fs, results_json_value_overrides={'layout_tests_dir': 'src'})
         merger.merge('/out', ['/shards/0', '/shards/1'])
 
-        for fname, contents in self.web_test_output_filesystem.items():
+        for fname, expected_contents in self.web_test_output_filesystem.items(
+        ):
             self.assertIn(fname, fs.files)
-            self.assertMultiLineEqual(contents, fs.files[fname])
+            if fname.endswith(".json"):
+                actual_json_str = fs.files[fname]
+                expected_json_str = expected_contents
+                if "archived_results" in fname or "failing_results" in fname:
+                    self.assertTrue(
+                        MergeFilesJSONPTests.check_before_after(
+                            fs.files[fname], 'ADD_RESULTS(', ");"))
+                    self.assertTrue(
+                        MergeFilesJSONPTests.check_before_after(
+                            expected_contents, 'ADD_RESULTS(', ");"))
+                    actual_json_str = MergeFilesJSONPTests.remove_before_after(
+                        fs.files[fname], 'ADD_RESULTS(', ");")
+                    expected_json_str = MergeFilesJSONPTests.remove_before_after(
+                        expected_contents, 'ADD_RESULTS(', ");")
+
+                self.assertEqual(json.loads(actual_json_str),
+                                 json.loads(expected_json_str))
+            else:
+                self.assertMultiLineEqual(expected_contents, fs.files[fname])
 
 
 class MarkMissingShardsTest(unittest.TestCase):
@@ -1638,4 +1666,5 @@
             '/out/output.json',
             fs)
         final_merged_output_json = fs.files['/out/output.json']
-        self.assertEqual(final_merged_output_json, self.final_output_json)
+        self.assertEqual(json.loads(final_merged_output_json),
+                         json.loads(self.final_output_json))
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 21afafa..fb8a906 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -196,7 +196,7 @@
 crbug.com/1124352 external/wpt/picture-in-picture/picture-in-picture-element.html [ Crash Pass ]
 crbug.com/1124352 external/wpt/picture-in-picture/shadow-dom.html [ Crash Pass ]
 
-crbug.com/1174966 [ Mac ] external/wpt/webrtc/RTCPeerConnection-restartIce.https.html [ Pass Failure ]
+crbug.com/1174966 [ Mac ] external/wpt/webrtc/RTCPeerConnection-restartIce.https.html [ Pass Failure Timeout ]
 crbug.com/1174965 [ Mac ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCDtlsTransport-state.html [ Pass Failure ]
 
 # These tests require portals, and some require cross-origin portals.
@@ -6234,9 +6234,6 @@
 
 crbug.com/1185051 fast/canvas/OffscreenCanvas-Bitmaprenderer-toBlob-worker.html [ Pass Failure ]
 
-# Sheriff 2021-03-09
-crbug.com/1185121 fast/scroll-snap/animate-fling-to-snap-points-1.html [ Pass Failure ]
-
 # Sheriff 2021-03-11
 crbug.com/1186901 [ Linux ] http/tests/devtools/elements/styles-3/style-autocomplete.js [ Pass Failure ]
 
@@ -6311,6 +6308,7 @@
 crbug.com/476553 virtual/scroll-unification/fast/scrolling/overflow-scrollability.html [ Pass Failure Timeout Crash ]
 crbug.com/476553 virtual/scroll-unification/fast/events/touch/gesture/gesture-tap-paragraph-end.html [ Pass Failure Timeout Crash ]
 crbug.com/476553 virtual/scroll-unification/fast/events/drag-and-drop-autoscroll-mainframe.html [ Pass Failure Timeout Crash ]
+crbug.com/476553 virtual/scroll-unification/fast/scroll-snap/animate-fling-to-snap-points-1.html [ Pass Failure ]
 
 # Sheriff 2021-04-01
 crbug.com/1167679 accessibility/aom-focus-action.html [ Pass Failure Timeout Crash ]
diff --git a/third_party/blink/web_tests/external/wpt/accessibility/crashtests/aom-in-destroyed-iframe.html b/third_party/blink/web_tests/external/wpt/accessibility/crashtests/aom-in-destroyed-iframe.html
new file mode 100644
index 0000000..d4128929
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/accessibility/crashtests/aom-in-destroyed-iframe.html
@@ -0,0 +1,27 @@
+<html class="test-wait">
+<body></body>
+<script>
+  const frameElem = document.createElement('iframe');
+
+  frameElem.srcdoc = '<html><head><title>X</title></head><body><div>-</div></body></html>';
+  frameElem.onload = function() {
+    const frameDoc = frameElem.contentWindow.document;
+
+    const divElem = frameDoc.querySelector('div');
+
+    getComputedAccessibleNode(divElem).then(function(divAccessible) {
+      // Close window.
+      frameElem.remove();
+
+      requestAnimationFrame(() => {
+        // Window removed, but we try to access DOM of non-existent window.
+        const isChecked = divAccessible.checked;
+        // Test is complete.
+        document.documentElement.className = '';
+      });
+    });
+  };
+
+  document.body.appendChild(frameElem);
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-image-table-cells-straddling-no-repeat.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-image-table-cells-straddling-no-repeat.html
new file mode 100644
index 0000000..67f7937
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-image-table-cells-straddling-no-repeat.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>'no-repeat' background on table row straddling table cells</title>
+<link rel="help" href="https://www.w3.org/TR/CSS21/tables.html#table-layers">
+<link rel="help" href="https://drafts.csswg.org/css-tables/#drawing-cell-backgrounds">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<style>
+.green {
+  background-image: linear-gradient(green, green);
+  background-position: 100px 0px;
+  background-repeat: no-repeat;
+  background-size: 100px 100px;
+}
+</style>
+<p>Test passes if there is a filled green square.</p>
+<table style="border-spacing: 0; margin-left: -100px">
+  <tr class="green">
+    <td style="padding: 0; height: 100px; width: 150px"></td>
+    <td style="padding: 0; height: 100px; width: 50px"></td>
+  </tr>
+</table>
diff --git a/third_party/blink/web_tests/storage/websql/alter-table-1208856.html b/third_party/blink/web_tests/storage/websql/alter-table-1208856.html
new file mode 100644
index 0000000..89c4abca
--- /dev/null
+++ b/third_party/blink/web_tests/storage/websql/alter-table-1208856.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>WebSQL: ALTER TABLE breakage in bug 1208856</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+async_test(testCase => {
+  const database = openDatabase(
+      'AlterTableTest', '1.0', 'Database for ALTER TABLE test',
+      1024 * 1024);
+  assert_not_equals(database, null, 'openDatabase should not fail');
+
+  database.transaction(testCase.step_func(transaction => {
+    transaction.executeSql(
+        'DROP TABLE IF EXISTS main;', [], () => {},
+        testCase.unreached_func('DROP TABLE should not fail'));
+    transaction.executeSql(
+        'CREATE TABLE main(id INTEGER PRIMARY KEY);', [], () => {},
+        testCase.unreached_func('CREATE TABLE should not fail'));
+    transaction.executeSql(
+        'ALTER TABLE main ADD name TEXT;', [],
+        testCase.step_func_done(() => {}),
+        testCase.unreached_func('ALTER TABLE should not fail'));
+  }));
+}, `ALTER TABLE should not fail`);
+</script>
diff --git a/third_party/closure_compiler/externs/accessibility_private.js b/third_party/closure_compiler/externs/accessibility_private.js
index 16a3c1f9..f308294 100644
--- a/third_party/closure_compiler/externs/accessibility_private.js
+++ b/third_party/closure_compiler/externs/accessibility_private.js
@@ -557,7 +557,7 @@
 chrome.accessibilityPrivate.onCustomSpokenFeedbackToggled;
 
 /**
- * Fired when ChromeVox should show its tutorial
+ * Fired when ChromeVox should show its tutorial.
  * @type {!ChromeEvent}
  */
 chrome.accessibilityPrivate.onShowChromeVoxTutorial;
diff --git a/third_party/google_benchmark/BUILD.gn b/third_party/google_benchmark/BUILD.gn
index 9e0bb14..9595a68 100644
--- a/third_party/google_benchmark/BUILD.gn
+++ b/third_party/google_benchmark/BUILD.gn
@@ -35,7 +35,7 @@
       "src/src/counter.cc",
       "src/src/counter.h",
       "src/src/csv_reporter.cc",
-      "src/src/cycle_clock.h",
+      "src/src/cycleclock.h",
       "src/src/internal_macros.h",
       "src/src/json_reporter.cc",
       "src/src/log.h",
diff --git a/third_party/tflite-support/BUILD.gn b/third_party/tflite-support/BUILD.gn
index 053e358..5d9aa10 100644
--- a/third_party/tflite-support/BUILD.gn
+++ b/third_party/tflite-support/BUILD.gn
@@ -103,7 +103,6 @@
     ":metadata_schema",
     ":tflite-support-proto",
     "//base",
-    "//third_party/abseil-cpp:absl",
     "//third_party/flatbuffers",
     "//third_party/libyuv",
     "//third_party/libzip",
@@ -115,7 +114,10 @@
     "//third_party/utf",
   ]
 
-  public_deps = [ "//third_party/re2" ]
+  public_deps = [
+    "//third_party/abseil-cpp:absl",
+    "//third_party/re2",
+  ]
 
   configs -= [ "//build/config/compiler:chromium_code" ]
 
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 1b6b54e..0c00d9a6 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -15819,6 +15819,14 @@
   </description>
 </action>
 
+<action name="MobileTabGridSelectTabs">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>michaeldo@chromium.org</owner>
+  <description>
+    User tapped the 'Select' button in the iOS tab grid.
+  </description>
+</action>
+
 <action name="MobileTabGridUndoCloseAllRegularTabs">
   <owner>edchin@chromium.org</owner>
   <owner>marq@chromium.org</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 04649c6..cb9cf0d3 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -45119,6 +45119,8 @@
   <int value="-1632828661" label="PrivacyReorderedAndroid:disabled"/>
   <int value="-1631329950" label="ssl-version-max"/>
   <int value="-1630419335" label="enable-download-notification"/>
+  <int value="-1629518055"
+      label="AdaptiveButtonInTopToolbarCustomization:enabled"/>
   <int value="-1626110605" label="WebBundles:disabled"/>
   <int value="-1625881240" label="FullscreenExitUI:disabled"/>
   <int value="-1625777277"
@@ -45165,6 +45167,7 @@
   <int value="-1596559650" label="max-tiles-for-interest-area"/>
   <int value="-1596489715" label="AutoScreenBrightness:enabled"/>
   <int value="-1594298767" label="FullscreenToolbarReveal:enabled"/>
+  <int value="-1591450227" label="enable-microphone-mute-switch-device"/>
   <int value="-1588702520" label="WebOTPCrossDevice:disabled"/>
   <int value="-1586642651" label="MaterialDesignExtensions:disabled"/>
   <int value="-1585733447" label="ContextualSearchUnityIntegration:disabled"/>
@@ -47356,6 +47359,7 @@
   <int value="301536977" label="DetailedLanguageSettings:enabled"/>
   <int value="301630312" label="ForcedColors:disabled"/>
   <int value="301869874" label="NTPPhysicalWebPageSuggestions:disabled"/>
+  <int value="302754902" label="MicMuteNotifications:disabled"/>
   <int value="303058039" label="AccountConsistency:disabled"/>
   <int value="303252119" label="AutofillExpandedPopupViews:disabled"/>
   <int value="303325901" label="NearbySharingWebRtc:disabled"/>
@@ -47547,6 +47551,8 @@
   <int value="458410433" label="disable-views-rect-based-targeting"/>
   <int value="460136092" label="MidiManagerAndroid:disabled"/>
   <int value="460475728" label="wake-on-wifi-packet"/>
+  <int value="461306840"
+      label="AdaptiveButtonInTopToolbarCustomization:disabled"/>
   <int value="461641589" label="WebAppWindowControlsOverlay:disabled"/>
   <int value="463582989" label="CompositorThreadedScrollbarScrolling:disabled"/>
   <int value="464226051" label="CrOSComponent:enabled"/>
@@ -48095,6 +48101,7 @@
   <int value="939603162" label="BackgroundLoadingForDownloads:disabled"/>
   <int value="941036016" label="ContentSuggestionsSettings:disabled"/>
   <int value="941883332" label="ProactiveTabFreezeAndDiscard:disabled"/>
+  <int value="941948340" label="PlaybackSpeedButton:enabled"/>
   <int value="942807728" label="NewTabstripAnimation:disabled"/>
   <int value="943319566" label="enable-intent-picker"/>
   <int value="943447234" label="NtpDriveModule:disabled"/>
@@ -48185,6 +48192,7 @@
   <int value="1022992701" label="enable-origin-chip-always"/>
   <int value="1023668536" label="PrintServerUi:enabled"/>
   <int value="1024560563" label="EnableFuzzyAppSearch:enabled"/>
+  <int value="1026169964" label="MicMuteNotifications:enabled"/>
   <int value="1026981579" label="TLS13HardeningForLocalAnchors:enabled"/>
   <int value="1027252926" label="SyncSupportSecondaryAccount:enabled"/>
   <int value="1028817487"
@@ -48552,6 +48560,7 @@
   <int value="1319725131" label="enable-distance-field-text"/>
   <int value="1320201920" label="enable-touchpad-three-finger-click"/>
   <int value="1320450434" label="ArcUsbStorageUI:disabled"/>
+  <int value="1323808967" label="PlaybackSpeedButton:disabled"/>
   <int value="1324623677"
       label="SessionRestorePrioritizesBackgroundUseCases:enabled"/>
   <int value="1325459977" label="HandwritingGestureEditing:disabled"/>
@@ -48563,6 +48572,7 @@
   <int value="1332120969" label="ChromeDuet:disabled"/>
   <int value="1333847867" label="NoScriptPreviews:enabled"/>
   <int value="1338356182" label="AutofillAssistantChromeEntry:disabled"/>
+  <int value="1338510325" label="lacros-selection"/>
   <int value="1338864675" label="EnableTouchableAppContextMenu:disabled"/>
   <int value="1339191800" label="OmniboxMaxZeroSuggestMatches:disabled"/>
   <int value="1339426771" label="TopSitesFromSiteEngagement:disabled"/>
diff --git a/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml b/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
index 8be66ac4..c218b07 100644
--- a/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
@@ -276,6 +276,17 @@
   </summary>
 </histogram>
 
+<histogram name="Accessibility.CrosDictation.Language" enum="CLD3LanguageCode"
+    expires_after="2021-11-20">
+  <owner>katie@chromium.org</owner>
+  <owner>dtseng@chromium.org</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    The language used for speech recognition in dictation on ChromeOS. This is
+    logged each time Dictation is toggled on.
+  </summary>
+</histogram>
+
 <histogram name="Accessibility.CrosDictation.ToggleDictationMethod"
     enum="CrosDictationToggleDictationMethod" expires_after="2021-09-19">
   <owner>anastasi@google.com</owner>
diff --git a/tools/metrics/histograms/histograms_xml/cookie/histograms.xml b/tools/metrics/histograms/histograms_xml/cookie/histograms.xml
index 3688a1d..b4c247f 100644
--- a/tools/metrics/histograms/histograms_xml/cookie/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/cookie/histograms.xml
@@ -53,6 +53,18 @@
   </summary>
 </histogram>
 
+<histogram name="Cookie.ControlCharacterTruncation" enum="Boolean"
+    expires_after="2022-05-31">
+  <owner>bingler@chromium.org</owner>
+  <owner>chlily@chromium.org</owner>
+  <summary>
+    Whether or not a cookie's name or value was truncated due to a terminating
+    control chracter (0x00 NUL, 0x0A LF, or 0x0D CR). E.x.
+    &quot;foo=ba\nr&quot;. This metric is recorded whenever a canonical cookie
+    is created via CanonicalCookie::Create().
+  </summary>
+</histogram>
+
 <histogram name="Cookie.CookiePrefix" enum="CookiePrefix"
     expires_after="2021-10-24">
   <owner>estark@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/extensions/histograms.xml b/tools/metrics/histograms/histograms_xml/extensions/histograms.xml
index 686bb775..6cc6a0d8 100644
--- a/tools/metrics/histograms/histograms_xml/extensions/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/extensions/histograms.xml
@@ -459,6 +459,17 @@
   </summary>
 </histogram>
 
+<histogram name="Extensions.CorruptedExtensionLocation"
+    enum="ExtensionLocation" expires_after="2022-01-01">
+  <owner>ydago@chromium.org</owner>
+  <owner>extensions-core@chromium.org</owner>
+  <summary>
+    Records the manifest location of a corrupted extension that will be
+    reinstalled silently. Fires when we suspect corruption in an extension and
+    begin the process of reinstalling it.
+  </summary>
+</histogram>
+
 <histogram name="Extensions.CorruptExtensionBecameDisabled" units="units"
     expires_after="M85">
   <owner>lazyboy@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
index be057d5..83f6522 100644
--- a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
@@ -8623,6 +8623,8 @@
   <suffix name="PreviewsHintCacheStore" label="Databases for Previews Hints"/>
   <suffix name="PrintJobDatabase" label="Database for print job metadata."/>
   <suffix name="SharedDb" label="Shared database"/>
+  <suffix name="ShareHistoryDatabase"
+      label="Database for third-party share history."/>
   <suffix name="StrikeService" label="Database for strike service."/>
   <suffix name="TabStateDatabase"
       label="Database for NonCriticalPersistedTabData">
diff --git a/tools/metrics/histograms/histograms_xml/local/histograms.xml b/tools/metrics/histograms/histograms_xml/local/histograms.xml
index cb8914c..cf229e11 100644
--- a/tools/metrics/histograms/histograms_xml/local/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/local/histograms.xml
@@ -262,8 +262,9 @@
 </histogram>
 
 <histogram name="LocalStorage.RendererAreaCacheHit"
-    enum="LocalStorageRendererAreaCacheHitEnum" expires_after="M77">
-  <owner>ssid@chromium.org</owner>
+    enum="LocalStorageRendererAreaCacheHitEnum" expires_after="M97">
+  <owner>mek@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The renderer side cache hit rate metrics for new HTML5 LocalStorage DB
     opened.
diff --git a/tools/metrics/histograms/histograms_xml/nearby/histograms.xml b/tools/metrics/histograms/histograms_xml/nearby/histograms.xml
index 6f5bec1..f87ac98 100644
--- a/tools/metrics/histograms/histograms_xml/nearby/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/nearby/histograms.xml
@@ -322,6 +322,17 @@
   </summary>
 </histogram>
 
+<histogram name="Nearby.Share.Certificates.Storage.InitializeSuccessDuration"
+    units="ms" expires_after="2021-10-25">
+  <owner>cvandermerwe@chromium.org</owner>
+  <owner>nearby-share-chromeos-eng@google.com</owner>
+  <summary>
+    Records the time necessary to successfully initialize the Nearby Share
+    public certificates database. Emitted once after sign-in when the Nearby
+    Share service starts.
+  </summary>
+</histogram>
+
 <histogram name="Nearby.Share.Certificates.Storage.{Operation}SuccessRate"
     enum="BooleanSuccess" expires_after="2021-08-19">
   <owner>cclem@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index d13f132c..703dcd5 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -13867,18 +13867,21 @@
 </histogram>
 
 <histogram name="SBMultipartUploader.FailedUploadDuration" units="s"
-    expires_after="M90">
+    expires_after="M95">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     Measures the total duration (including delay due to retries) of a failed
     upload request. This is recorded after the last retry on every failed
     upload.
+
+    Warning: This histogram was expired in M91 and restored in M92. Data may be
+    missing in M91.
   </summary>
 </histogram>
 
 <histogram name="SBMultipartUploader.NetworkRequestResponseCodeOrError"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="M90">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="M95">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -13886,48 +13889,63 @@
     recorded once for each network request made as part of an upload, i.e. if an
     upload fails the first time, but a retry succeeds, this metric is recorded
     twice.
+
+    Warning: This histogram was expired in M91 and restored in M92. Data may be
+    missing in M91.
   </summary>
 </histogram>
 
 <histogram name="SBMultipartUploader.RetriesNeeded" units="retries"
-    expires_after="M90">
+    expires_after="M95">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     Counts how many retries were needed to complete the upload request. This is
     emitted for every successful upload.
+
+    Warning: This histogram was expired in M91 and restored in M92. Data may be
+    missing in M91.
   </summary>
 </histogram>
 
 <histogram name="SBMultipartUploader.SuccessfulUploadDuration" units="s"
-    expires_after="2021-09-19">
+    expires_after="M95">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     Measures the total duration (including delay due to retries) of a successful
     upload request. This is recorded for every successful upload.
+
+    Warning: This histogram was expired in M91 and restored in M92. Data may be
+    missing in M91.
   </summary>
 </histogram>
 
 <histogram name="SBMultipartUploader.UploadSize" units="bytes"
-    expires_after="M90">
+    expires_after="M95">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     The size of the upload request body. This is a combination of the upload
     data and metadata. This is recorded once for each upload, regardless of
     whether it was successful.
+
+    Warning: This histogram was expired in M91 and restored in M92. Data may be
+    missing in M91.
   </summary>
 </histogram>
 
 <histogram name="SBMultipartUploader.UploadSuccess" enum="BooleanSuccess"
-    expires_after="M90">
+    expires_after="M95">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     Whether the overall upload request succeeded or not. This is recorded once
     for an upload request, regardless of how many retries were needed to
     complete the upload.
+
+    Warning: This histogram was expired in M91 and restored in M92. Data may be
+    missing in M91.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml b/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml
index 33d5f80c..11d2690 100644
--- a/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml
@@ -2061,45 +2061,57 @@
 </histogram>
 
 <histogram name="SafeBrowsingFCMService.IncomingMessageHasKey" enum="Boolean"
-    expires_after="M90">
+    expires_after="M95">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     Whether the incoming message has the expected key. Recorded on every FCM
     message received by the Safe Browsing FCM handler.
+
+    Warning: This histogram was expired in M91 and restored in M92. Data may be
+    missing in M91.
   </summary>
 </histogram>
 
 <histogram name="SafeBrowsingFCMService.IncomingMessageHasValidToken"
-    enum="Boolean" expires_after="M90">
+    enum="Boolean" expires_after="M95">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     Whether the incoming message has a token that was previously registered with
     the FCM handler. Recorded on every FCM message received by the Safe Browsing
     FCM handler.
+
+    Warning: This histogram was expired in M91 and restored in M92. Data may be
+    missing in M91.
   </summary>
 </histogram>
 
 <histogram name="SafeBrowsingFCMService.IncomingMessageParsedBase64"
-    enum="BooleanSuccess" expires_after="M90">
+    enum="BooleanSuccess" expires_after="M95">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     Whether the incoming message was successfully parsed as valid base64-encoded
     data. Recorded on every FCM message received by the Safe Browsing FCM
     handler.
+
+    Warning: This histogram was expired in M91 and restored in M92. Data may be
+    missing in M91.
   </summary>
 </histogram>
 
 <histogram name="SafeBrowsingFCMService.IncomingMessageParsedProto"
-    enum="BooleanSuccess" expires_after="M90">
+    enum="BooleanSuccess" expires_after="M95">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     Whether the incoming message was successfully parsed as a valid
     base64-encoded serialized proto. Recorded on every FCM message received by
     the Safe Browsing FCM handler.
+
+    Warning: This histogram was expired in M91 and restored in M92. Data may be
+    missing in M91.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/histograms_xml/sb_client/histograms.xml b/tools/metrics/histograms/histograms_xml/sb_client/histograms.xml
index a0f0c47a..271babe 100644
--- a/tools/metrics/histograms/histograms_xml/sb_client/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/sb_client/histograms.xml
@@ -40,6 +40,9 @@
 
 <histogram name="SBClientDownload.CheckWhitelistResult"
     enum="WhitelistedDownloadType" expires_after="M90">
+  <obsolete>
+    Removed 05-2021 due to lack of use
+  </obsolete>
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -61,6 +64,9 @@
 
 <histogram name="SBClientDownload.DmgFileSuccessByType"
     enum="SBClientDownloadExtensions" expires_after="M90">
+  <obsolete>
+    Removed 05-2021 due to lack of use
+  </obsolete>
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -198,7 +204,7 @@
 </histogram>
 
 <histogram name="SBClientDownload.MalwareDeepScanResult.{trigger}"
-    enum="SBClientDownloadCheckResult" expires_after="2021-04-28">
+    enum="SBClientDownloadCheckResult" expires_after="2022-04-28">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -206,6 +212,9 @@
     uploaded for scanning. This is logged only for succesful scans. The overall
     rate of successful scans is logged in
     SafeBrowsingBinaryUploadRequest.MalwareResult.
+
+    Warning: this histogram was expired from 2021-04-29 to 2021-05-19; data may
+    be missing.
   </summary>
   <token key="trigger">
     <variant name="AdvancedProtectionPrompt"
diff --git a/tools/metrics/histograms/histograms_xml/storage/histograms.xml b/tools/metrics/histograms/histograms_xml/storage/histograms.xml
index 3635a97..5568a79 100644
--- a/tools/metrics/histograms/histograms_xml/storage/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/storage/histograms.xml
@@ -451,6 +451,16 @@
   </summary>
 </histogram>
 
+<histogram name="Storage.SessionStorage.RendererAreaCacheHit"
+    enum="LocalStorageRendererAreaCacheHitEnum" expires_after="M97">
+  <owner>mek@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
+  <summary>
+    The renderer side cache hit rate metrics for new HTML5 SessionStorage DB
+    opened.
+  </summary>
+</histogram>
+
 <histogram name="Storage.StoragePressure.Bubble"
     enum="StoragePressureBubbleUserAction" expires_after="2022-03-12">
   <owner>jarrydg@chromium.org</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 62bf38ba..5d51f57 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -10,7 +10,7 @@
         },
         "linux": {
             "hash": "dd23313e9aabc34dcaed1743f3bb1938be1c63b2",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/3baa021c258ba7670ab363a7ef73b3fd24fee97a/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/9a1689eeaa281b21c5bcedcad5d54ddaeeb7889c/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index a7fd3c7..65d557f 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -106,6 +106,8 @@
     "interaction/element_identifier.h",
     "interaction/element_tracker.cc",
     "interaction/element_tracker.h",
+    "interaction/interaction_sequence.cc",
+    "interaction/interaction_sequence.h",
     "l10n/formatter.cc",
     "l10n/formatter.h",
     "l10n/l10n_font_util.cc",
@@ -696,6 +698,8 @@
 static_library("test_support") {
   testonly = true
   sources = [
+    "interaction/element_test_util.cc",
+    "interaction/element_test_util.h",
     "resource/mock_resource_bundle_delegate.cc",
     "resource/mock_resource_bundle_delegate.h",
   ]
@@ -890,6 +894,7 @@
     "clipboard/file_info_unittest.cc",
     "ime/utf_offset_unittest.cc",
     "interaction/element_tracker_unittest.cc",
+    "interaction/interaction_sequence_unittest.cc",
     "l10n/l10n_util_unittest.cc",
     "l10n/time_format_unittest.cc",
     "layout_unittest.cc",
diff --git a/ui/base/cocoa/secure_password_input.mm b/ui/base/cocoa/secure_password_input.mm
index c9d00321..b3283a4 100644
--- a/ui/base/cocoa/secure_password_input.mm
+++ b/ui/base/cocoa/secure_password_input.mm
@@ -41,7 +41,7 @@
 namespace ui {
 
 ScopedPasswordInputEnabler::ScopedPasswordInputEnabler() {
-  if (!g_password_input_counter) {
+  if (!g_password_input_counter && !IsSecureEventInputEnabled()) {
     SetPasswordInputEnabled(true);
   }
   ++g_password_input_counter;
diff --git a/ui/base/interaction/README.md b/ui/base/interaction/README.md
index 5e3bc4af..26d9614 100644
--- a/ui/base/interaction/README.md
+++ b/ui/base/interaction/README.md
@@ -114,14 +114,14 @@
 * Register for callbacks when the user interacts with an element with a given
   identifier and context (known as _activation_)
 
-`ElementTrackerElement`, a polymorphic class defined in
+`TrackedElement`, a polymorphic class defined in
 [`element_tracker.h`](/ui/base/interaction/element_tracker.h), represents a
 platform-agnostic UI element with an identifier and context. There must be a 1:1
 correspondence between a visible named UI element and an
-`ElementTrackerElement`; the `ElementTrackerElement` is what is passed to
+`TrackedElement`; the `TrackedElement` is what is passed to
 callbacks when the UI element is shown, hidden, or activated.
 
-Each framework has its own derived version of `ElementTrackerElement` that may
+Each framework has its own derived version of `TrackedElement` that may
 provide additional information about the element. If you know what platform the
 element is from you may use the `AsA()` template method to dynamically downcast
 to the platform-specific element type. If you are working in an environment with
@@ -129,7 +129,7 @@
 the element is of the expected type.
 
 Here is an example that shows some of the functionality of `ElementTracker` and
-`ElementTrackerElement`. Note that you must specify the `ElementContext` in
+`TrackedElement`. Note that you must specify the `ElementContext` in
 which you are listening:
 ``` cpp
 void ListenForShowEvent(ui::ElementIdentifier id, ui::ElementContext context) {
@@ -139,26 +139,159 @@
       ->AddElementShownCallback(id, context, callback);
 }
 
-void OnElementShown(ui::ElementTrackerElement* element) {
+void OnElementShown(ui::TrackedElement* element) {
 // Technically you don't need the IsA() call here, since AsA() returns null if
 // the object is the wrong type.
-if (element->IsA<views::ElementTrackerElementViews>()) {
+if (element->IsA<views::TrackedElementViews>()) {
   views::View* const view =
-      element->AsA<views::ElementTrackerElementViews>()->view();
+      element->AsA<views::TrackedElementViews>()->view();
   // Do something with the view that was shown here.
 }
 ```
 ## Defining and following user interaction sequences
 
-*(Add documentation for `InteractionSequence` here)*
+The `InteractionSequence` class provides a way to describe a sequence of
+interactions between the application and the user (real or simulated). Sequences
+are useful for e.g. creating user education tutorials guiding the user through
+the steps of using a new feature, or for simulating user input during
+interaction testing.
 
-...
+Each sequence consists of a series of steps of the following types:
+* **Shown** - a UI element becomes visible to the user
+* **Activated** - the user clics on or otherwise interacts with the UI element
+* **Hidden** - a UI element stops being visible to the user
+
+These are equivalent to the corresponding events provided by `ElementTracker`.
+All of the steps must be followed in order, or the sequence is _aborted_.
+Callbacks may be registered at the start and end of each step, and for when the
+sequence completes or aborts.
+
+Events not in the sequence (other UI elements appearing, focus changes, mouse
+hover, scroll) are ignored unless they result in a required UI element being
+dismissed (such as if a dialog or menu is closed).
+
+To create an interaction sequence, use a `InteractionSequence::Builder`. To add
+steps to the builder, use an `InteractionSequence::StepBuilder`, or call a
+convenience method like `InitialElement()`. Here is an example that expects the
+user to interact with a feature entry point and then displays a help bubble on
+the resulting dialog:
+
+``` cpp
+initial_element =
+    ElementTracker::GetFirstMatchingElement(kFeatureEntryPointID, context());
+sequence_ = InteractionSequence::Builder()
+    .SetCompletedCallback(base::BindOnce(
+        &MyClass::OnSequenceComplete,
+        base::Unretained(this)))
+    .AddStep(InteractionSequence::InitialElement(initial_element))
+    .AddStep(InteractionSequence::StepBuilder()
+        .SetElementID(initial_element->identifier())
+        .SetType(StepType::kActivated)
+        .Build())
+    .AddStep(InteractionSequence::StepBuilder()
+        .SetElementID(kFeatureDialogID)
+        .SetType(StepType::kShown)
+        .SetStartCallback(&MyClass::ShowHelpBubble, base::Unretained(this))
+        .Build())
+    .Build();
+sequence_->Start();
+```
+
+### Interaction steps
+
+Each `Step` has properties that can be set via its `StepBuilder`:
+* **Type** - whether this is a show, activate, or hide step
+* **Element ID** - the identifier of the element involved in the step
+* **Must be visible at start** - the element must be visible at the start of the
+  step or else the sequence is aborted
+* **Must remain visible** - the element must remain visible until the next step
+  begins; not compatible with **hidden** steps
+* **Start callback** - called as soon as the step is started
+* **End callback** - called as soon as the next step is started, or the sequence
+  aborts or completes
+
+Of these, only **Type** and **Element ID** are required. If you do not specify
+whether the element must be visible at start or remain visible, default values
+will be assigned according to the type of step. All callbacks are optional.
+
+Instead of using `StepBuilder`, for the initial step you can call
+`InteractionSequence::InitialElement()`. This creates a default **shown** step
+for an element that is already visible; it expects the element to be visible
+when `Start()` is called or the sequence will abort. You may pass optional
+step start and end callbacks to `InitialElement()`; these are useful for
+displaying an initial prompt to the user (in the case of a tutorial).
+
+There is an additional method on `StepBuilder`, `SetContext()`, but it is only
+used by helper methods and for testing. You should instead use
+`Builder::SetContext()` or `InteractionSequence::InitialElement()`. There is
+currently no support for cross-context sequences and setting conflicting
+contexts in a sequence is an error and will crash if DCHECK is enabled.
+
+### Step callbacks
+
+Each step callback (start or end) has three parameters:
+* **Element** - the element involved in the step; null if the element is not
+  available (i.e. was hidden before the callback could be called)
+* **Identifier** - the `ElementIdentifier` associated with the step, which is
+  always valid even if the element is null
+* **Type** - the step type; **shown**, **activated**, or **hidden**
+
+The **element** can be used to retrieve the UI element in your framework by
+downcasting via `AsA()` - see
+[UI Elements and element events](#UI%20Elements%20and%20element%20events) above.
+
+Typically, when using a sequence to run a tutorial, this will be the code that
+shows or hides a tutorial dialog or prompt. When using the sequence for
+interaction testing, the callback will contain the code to simulate the next
+input to the UI.
+
+### Best practices
+
+In general, it will be pretty obvious how to construct your sequence, because
+you know the steps you need to perform in the UI to get where you want to go.
+However, keep the following in mind:
+* Try to start the sequence with a step generated by `InitialElement()`, keyed
+  to a UI element you know will be visible when the sequence starts.
+* Do not assume the order in which elements will become visible when a surface
+  is shown.
+* Do not assume that interacting with a button or menu item will bring up a
+  resulting surface (another menu, a dialog) _before_ the initial button or
+  menu item disappears.
+
+To elaborate on the third point: it is better to have the following steps in the
+case where a menu item brings up a dialog:
+1. Menu item shown
+2. Menu item activated (does not need to remain visible; default)
+3. Dialog element shown (does not need to be visible at start; default)
+
+If you specify that the menu item must stay visible or that the dialog element
+must be visible at step start, the sequence could fail depending on the order in
+which the presentation framework dismisses the menu and displays the dialog.
+
+However, in the case where you want the user to navigate a series of submenus,
+if the platform supports menu-open-via-hover you may not receive the
+**activated** signal and a sequence like the following might work better:
+1. Menu item shown
+2. Submenu item shown (triggers as soon as the submenu is opened, regardless of
+   how)
+3. Submenu item activated
+4. ...
+
+### Known limitations
+
+* Cannot nest sequences (might be able to in some cases via callbacks)
+* Cannot provide alternate sets of steps in the same sequence
+* Cannot skip ahead (e.g. if the user uses a shortcut key to bypass a menu)
+* Cannot restart steps (e.g. if the user hovers a submenu containing the next
+  element, then un-hovers it, then hovers it again)
+
+All of these can be addressed if a relevant, concrete need is found.
 
 ## Supporting additional UI frameworks
 
 If you want to use ElementTracker with a framework that isn't supported yet, you
 must at minimum do the following:
-1. Derive a class from `ElementTrackerElement` representing visual elements in
+1. Derive a class from `TrackedElement` representing visual elements in
    your framework.
 2. Determine how `ElementContext`s are defined in your framework.
 3. Implement code to create and register your derived element objects with
@@ -172,20 +305,20 @@
 When you are done, please add the folder containing the implementation code to
 the [Supported Frameworks](#Supported%20Frameworks) section below.
 
-### 1. Derive a class from `ElementTrackerElement`
+### 1. Derive a class from `TrackedElement`
 
-When you derive a class from `ElementTrackerElement` to use for your UI
+When you derive a class from `TrackedElement` to use for your UI
 framework, you are obliged to declare specific metadata in order to support
 `IsA()` and `AsA()`. To do this, add the following to the class definition:
 ``` cpp
-class ElementTrackerElementMyPlatform {
+class TrackedElementMyPlatform {
  public:
-  // This provides the required ElementTrackerElement metadata support.
+  // This provides the required TrackedElement metadata support.
   DECLARE_ELEMENT_TRACKER_METADATA();
 }
 
 // In the corresponding .cc file:
-DEFINE_ELEMENT_TRACKER_METADATA(ElementTrackerElementMyPlatform)
+DEFINE_ELEMENT_TRACKER_METADATA(TrackedElementMyPlatform)
 ```
 You will also be expected to pass an immutable identifier and context into the
 constructor, and if the element object stores a pointer or handle to the
@@ -214,11 +347,11 @@
 ### 3. Managing the lifetime of your elements and sending events
 
 How your platform manages the lifetime of elements is entirely up to you. You
-could create an `ElementTrackerElement` whenever a named UI element in your
+could create an `TrackedElement` whenever a named UI element in your
 framework becomes visible to the user, or you could have every UI element
-with an associated `ElementIdentifier` hold a permanent `ElementTrackerElement`.
+with an associated `ElementIdentifier` hold a permanent `TrackedElement`.
 
-The one requirement is that a single `ElementTrackerElement` must be associated
+The one requirement is that a single `TrackedElement` must be associated
 with any named UI element, and must remain associated with that implementation
 as long as the element remains visible.
 
@@ -255,7 +388,7 @@
 `ElementTrackerFrameworkDelegate` is entirely up to you. In Views we go through
 a mediator object -
 [`ElementTrackerViews`](ui/views/interaction/element_tracker_views.h) - which
-first maps the `Button`, `MenuItem`, etc. to an `ElementTrackerElementViews`
+first maps the `Button`, `MenuItem`, etc. to an `TrackedElementViews`
 before passing that object to the appropriate delegate method.
 
 ## Supported Frameworks
@@ -265,3 +398,4 @@
 
 * Views:
   * [`ElementTrackerViews`](/ui/views/interaction/element_tracker_views.h)
+  * [`InteractionSequenceViews`](/ui/views/interaction/interaction_sequence_views.h)
diff --git a/ui/base/interaction/element_test_util.cc b/ui/base/interaction/element_test_util.cc
new file mode 100644
index 0000000..2670056
--- /dev/null
+++ b/ui/base/interaction/element_test_util.cc
@@ -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.
+
+#include "ui/base/interaction/element_test_util.h"
+
+#include "base/test/bind.h"
+
+namespace ui {
+
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestFrameworkIdentifier);
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kOtherFrameworkIdentifier);
+
+TestElementBase::TestElementBase(ElementIdentifier id, ElementContext context)
+    : TrackedElement(id, context) {}
+
+TestElementBase::~TestElementBase() {
+  Hide();
+}
+
+TestElement::TestElement(ElementIdentifier id, ElementContext context)
+    : TestElementBase(id, context) {}
+
+TestElementOtherFramework::TestElementOtherFramework(ElementIdentifier id,
+                                                     ElementContext context)
+    : TestElementBase(id, context) {}
+
+void TestElementBase::Show() {
+  if (visible_)
+    return;
+  visible_ = true;
+  ElementTracker::GetFrameworkDelegate()->NotifyElementShown(this);
+}
+
+void TestElementBase::Activate() {
+  DCHECK(visible_);
+  ElementTracker::GetFrameworkDelegate()->NotifyElementActivated(this);
+}
+
+void TestElementBase::Hide() {
+  if (!visible_)
+    return;
+  visible_ = false;
+  ElementTracker::GetFrameworkDelegate()->NotifyElementHidden(this);
+}
+
+// static
+TrackedElement::FrameworkIdentifier TestElement::GetFrameworkIdentifier() {
+  return kTestFrameworkIdentifier;
+}
+
+TrackedElement::FrameworkIdentifier
+TestElement::GetInstanceFrameworkIdentifier() const {
+  return kTestFrameworkIdentifier;
+}
+
+// static
+TrackedElement::FrameworkIdentifier
+TestElementOtherFramework::GetFrameworkIdentifier() {
+  return kOtherFrameworkIdentifier;
+}
+
+TrackedElement::FrameworkIdentifier
+TestElementOtherFramework::GetInstanceFrameworkIdentifier() const {
+  return kOtherFrameworkIdentifier;
+}
+
+}  // namespace ui
diff --git a/ui/base/interaction/element_test_util.h b/ui/base/interaction/element_test_util.h
new file mode 100644
index 0000000..52a9f35
--- /dev/null
+++ b/ui/base/interaction/element_test_util.h
@@ -0,0 +1,88 @@
+// 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 UI_BASE_INTERACTION_ELEMENT_TEST_UTIL_H_
+#define UI_BASE_INTERACTION_ELEMENT_TEST_UTIL_H_
+
+#include "base/test/mock_callback.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/base/interaction/element_identifier.h"
+#include "ui/base/interaction/element_tracker.h"
+
+namespace ui {
+
+// Provides a platform-less pseudoelement for use in ElementTracker and
+// InteractionSequence tests.
+class TestElementBase : public TrackedElement {
+ public:
+  TestElementBase(ElementIdentifier id, ElementContext context);
+  ~TestElementBase() override;
+
+  // Simulate the element shown event.
+  void Show();
+
+  // Simulate the element activated event.
+  void Activate();
+
+  // Simulate the element hidden event.
+  void Hide();
+
+ private:
+  bool visible_ = false;
+};
+
+// Provides a platform-less test element in a fictional UI framework.
+class TestElement : public TestElementBase {
+ public:
+  TestElement(ElementIdentifier id, ElementContext context);
+  static FrameworkIdentifier GetFrameworkIdentifier();
+  FrameworkIdentifier GetInstanceFrameworkIdentifier() const override;
+};
+
+// Provides a platform-less test element in a fictional UI framework distinct
+// from `TestElement`.
+class TestElementOtherFramework : public TestElementBase {
+ public:
+  TestElementOtherFramework(ElementIdentifier id, ElementContext context);
+  static FrameworkIdentifier GetFrameworkIdentifier();
+  FrameworkIdentifier GetInstanceFrameworkIdentifier() const override;
+};
+
+// Convenience typedef for unique pointers to test elements.
+using TestElementPtr = std::unique_ptr<TestElementBase>;
+
+// Require that a specific base::MockCallback (or callbacks) is called in a
+// specific order, inside a specific block of code.
+
+#define EXPECT_CALL_IN_SCOPE(Name, Call, Block) \
+  EXPECT_CALL(Name, Call).Times(1);             \
+  Block;                                        \
+  EXPECT_CALL(Name, Run).Times(0)
+
+#define EXPECT_CALLS_IN_SCOPE_2(Name1, Call1, Name2, Call2, Block) \
+  {                                                                \
+    testing::InSequence in_sequence;                               \
+    EXPECT_CALL(Name1, Call1).Times(1);                            \
+    EXPECT_CALL(Name2, Call2).Times(1);                            \
+  }                                                                \
+  Block;                                                           \
+  EXPECT_CALL(Name1, Run).Times(0);                                \
+  EXPECT_CALL(Name2, Run).Times(0)
+
+#define EXPECT_CALLS_IN_SCOPE_3(Name1, Call1, Name2, Call2, Name3, Call3, \
+                                Block)                                    \
+  {                                                                       \
+    testing::InSequence in_sequence;                                      \
+    EXPECT_CALL(Name1, Call1).Times(1);                                   \
+    EXPECT_CALL(Name2, Call2).Times(1);                                   \
+    EXPECT_CALL(Name3, Call3).Times(1);                                   \
+  }                                                                       \
+  Block;                                                                  \
+  EXPECT_CALL(Name1, Run).Times(0);                                       \
+  EXPECT_CALL(Name2, Run).Times(0);                                       \
+  EXPECT_CALL(Name3, Run).Times(0)
+
+}  // namespace ui
+
+#endif  // UI_BASE_INTERACTION_ELEMENT_TEST_UTIL_H_
diff --git a/ui/base/interaction/element_tracker.cc b/ui/base/interaction/element_tracker.cc
index 3f1e739..d32d7de 100644
--- a/ui/base/interaction/element_tracker.cc
+++ b/ui/base/interaction/element_tracker.cc
@@ -45,11 +45,7 @@
     return elements_.size();
   }
 
-  bool processing_removal() const { return processing_removal_; }
-
-  const std::list<ElementTrackerElement*>& elements() const {
-    return elements_;
-  }
+  const std::list<TrackedElement*>& elements() const { return elements_; }
 
   Subscription AddElementShownCallback(Callback callback) {
     return shown_callbacks_.Add(callback);
@@ -63,7 +59,7 @@
     return hidden_callbacks_.Add(callback);
   }
 
-  void NotifyElementShown(ElementTrackerElement* element) {
+  void NotifyElementShown(TrackedElement* element) {
     DCHECK_EQ(identifier().raw_value(), element->identifier().raw_value());
     DCHECK_EQ(static_cast<intptr_t>(context()),
               static_cast<intptr_t>(element->context()));
@@ -73,15 +69,12 @@
     shown_callbacks_.Notify(element);
   }
 
-  void NotifyElementActivated(ElementTrackerElement* element) {
+  void NotifyElementActivated(TrackedElement* element) {
     DCHECK(base::Contains(element_lookup_, element));
     activated_callbacks_.Notify(element);
   }
 
-  void NotifyElementHidden(ElementTrackerElement* element) {
-    // We don't want to delete this object during a callback while we're in the
-    // middle of cleaning up the object, so put a guard around this operation.
-    base::AutoReset<bool> guard(&processing_removal_, true);
+  void NotifyElementHidden(TrackedElement* element) {
     const auto it = element_lookup_.find(element);
     DCHECK(it != element_lookup_.end());
     elements_.erase(it->second);
@@ -92,30 +85,80 @@
  private:
   const ElementIdentifier identifier_;
   const ElementContext context_;
-  bool processing_removal_ = false;
 
   // Holds elements in the order they were added to this data block, so that the
   // first element or the first element that matches some criterion can be
   // easily found.
-  std::list<ElementTrackerElement*> elements_;
+  std::list<TrackedElement*> elements_;
 
   // Provides a fast lookup into `elements_` by element for checking and
   // removal. Since there could be many elements (e.g. tabs in a browser) we
   // don't want removing a series of them to turn into an O(n^2) operation.
-  std::map<ElementTrackerElement*, std::list<ElementTrackerElement*>::iterator>
+  std::map<TrackedElement*, std::list<TrackedElement*>::iterator>
       element_lookup_;
 
-  base::RepeatingCallbackList<void(ElementTrackerElement*)> shown_callbacks_;
-  base::RepeatingCallbackList<void(ElementTrackerElement*)>
-      activated_callbacks_;
-  base::RepeatingCallbackList<void(ElementTrackerElement*)> hidden_callbacks_;
+  base::RepeatingCallbackList<void(TrackedElement*)> shown_callbacks_;
+  base::RepeatingCallbackList<void(TrackedElement*)> activated_callbacks_;
+  base::RepeatingCallbackList<void(TrackedElement*)> hidden_callbacks_;
 };
 
-ElementTrackerElement::ElementTrackerElement(ElementIdentifier id,
-                                             ElementContext context)
+// Ensures that ElementData objects get cleaned up, but only after all callbacks
+// have returned. Otherwise a subscription could be canceled during a callback,
+// resulting in the ElementData and the callback list being deleted before the
+// callback has returned.
+class ElementTracker::GarbageCollector {
+ public:
+  // Represents a call stack frame in which garbage collection can happen.
+  // Garbage collection doesn't actually occur until all nested Frames are
+  // destructed.
+  class Frame {
+   public:
+    explicit Frame(GarbageCollector* gc) : gc_(gc) {
+      gc_->IncrementFrameCount();
+    }
+
+    ~Frame() { gc_->DecrementFrameCount(); }
+
+    void Add(ElementData* data) { gc_->AddCandidate(data); }
+
+   private:
+    GarbageCollector* const gc_;
+  };
+
+  explicit GarbageCollector(ElementTracker* tracker) : tracker_(tracker) {}
+
+ private:
+  void AddCandidate(ElementData* data) {
+    DCHECK_GE(frame_count_, 0);
+    candidates_.insert(data);
+  }
+
+  void IncrementFrameCount() { ++frame_count_; }
+
+  void DecrementFrameCount() {
+    DCHECK_GE(frame_count_, 0);
+    if (--frame_count_ > 0)
+      return;
+
+    for (ElementData* data : candidates_) {
+      if (data->empty()) {
+        const auto result = tracker_->element_data_.erase(
+            LookupKey(data->identifier(), data->context()));
+        DCHECK(result);
+      }
+    }
+    candidates_.clear();
+  }
+
+  ElementTracker* const tracker_;
+  std::set<ElementData*> candidates_;
+  int frame_count_ = 0;
+};
+
+TrackedElement::TrackedElement(ElementIdentifier id, ElementContext context)
     : identifier_(id), context_(context) {}
 
-ElementTrackerElement::~ElementTrackerElement() = default;
+TrackedElement::~TrackedElement() = default;
 
 // static
 ElementTracker* ElementTracker::GetElementTracker() {
@@ -128,9 +171,8 @@
   return static_cast<ElementTrackerFrameworkDelegate*>(GetElementTracker());
 }
 
-ElementTrackerElement* ElementTracker::GetUniqueElement(
-    ElementIdentifier id,
-    ElementContext context) {
+TrackedElement* ElementTracker::GetUniqueElement(ElementIdentifier id,
+                                                 ElementContext context) {
   const auto it = element_data_.find(LookupKey(id, context));
   if (it == element_data_.end() || it->second->num_elements() == 0)
     return nullptr;
@@ -138,7 +180,7 @@
   return it->second->elements().front();
 }
 
-ElementTrackerElement* ElementTracker::GetFirstMatchingElement(
+TrackedElement* ElementTracker::GetFirstMatchingElement(
     ElementIdentifier id,
     ElementContext context) {
   const auto it = element_data_.find(LookupKey(id, context));
@@ -187,30 +229,37 @@
   return GetOrAddElementData(id, context)->AddElementHiddenCallback(callback);
 }
 
-ElementTracker::ElementTracker() = default;
-ElementTracker::~ElementTracker() = default;
+ElementTracker::ElementTracker()
+    : gc_(std::make_unique<GarbageCollector>(this)) {}
 
-void ElementTracker::NotifyElementShown(ElementTrackerElement* element) {
+ElementTracker::~ElementTracker() {
+  NOTREACHED();
+}
+
+void ElementTracker::NotifyElementShown(TrackedElement* element) {
+  GarbageCollector::Frame gc_frame(gc_.get());
   DCHECK(!base::Contains(element_to_data_lookup_, element));
   ElementData* const element_data =
       GetOrAddElementData(element->identifier(), element->context());
-  element_data->NotifyElementShown(element);
   element_to_data_lookup_.emplace(element, element_data);
+  element_data->NotifyElementShown(element);
 }
 
-void ElementTracker::NotifyElementActivated(ElementTrackerElement* element) {
+void ElementTracker::NotifyElementActivated(TrackedElement* element) {
+  GarbageCollector::Frame gc_frame(gc_.get());
   const auto it = element_to_data_lookup_.find(element);
   DCHECK(it != element_to_data_lookup_.end());
   it->second->NotifyElementActivated(element);
 }
 
-void ElementTracker::NotifyElementHidden(ElementTrackerElement* element) {
+void ElementTracker::NotifyElementHidden(TrackedElement* element) {
+  GarbageCollector::Frame gc_frame(gc_.get());
   const auto it = element_to_data_lookup_.find(element);
   DCHECK(it != element_to_data_lookup_.end());
   ElementData* const data = it->second;
-  data->NotifyElementHidden(element);
   element_to_data_lookup_.erase(it);
-  MaybeCleanup(data);
+  data->NotifyElementHidden(element);
+  gc_frame.Add(data);
 }
 
 ElementTracker::ElementData* ElementTracker::GetOrAddElementData(
@@ -228,14 +277,57 @@
 }
 
 void ElementTracker::MaybeCleanup(ElementData* data) {
-  // If there is still data, or we're in the middle of processing an element
-  // being removed, do not clean up this data block.
-  if (!data->empty() || data->processing_removal())
+  GarbageCollector::Frame gc_frame(gc_.get());
+  gc_frame.Add(data);
+}
+
+SafeElementReference::SafeElementReference() = default;
+
+SafeElementReference::SafeElementReference(TrackedElement* element)
+    : element_(element) {
+  Subscribe();
+}
+
+SafeElementReference::SafeElementReference(SafeElementReference&& other)
+    : element_(other.element_) {
+  // Have to rebind instead of moving the subscription since the other
+  // reference's this pointer is bound.
+  Subscribe();
+  other.subscription_ = ElementTracker::Subscription();
+  other.element_ = nullptr;
+}
+
+SafeElementReference& SafeElementReference::operator=(
+    SafeElementReference&& other) {
+  if (&other != this) {
+    element_ = other.element_;
+    // Have to rebind instead of moving the subscription since the other
+    // reference's this pointer is bound.
+    Subscribe();
+    other.subscription_ = ElementTracker::Subscription();
+    other.element_ = nullptr;
+  }
+  return *this;
+}
+
+SafeElementReference::~SafeElementReference() = default;
+
+void SafeElementReference::Subscribe() {
+  if (!element_)
     return;
 
-  const auto result =
-      element_data_.erase(LookupKey(data->identifier(), data->context()));
-  DCHECK(result);
+  subscription_ = ElementTracker::GetElementTracker()->AddElementHiddenCallback(
+      element_->identifier(), element_->context(),
+      base::BindRepeating(&SafeElementReference::OnElementHidden,
+                          base::Unretained(this)));
+}
+
+void SafeElementReference::OnElementHidden(TrackedElement* element) {
+  if (element != element_)
+    return;
+
+  subscription_ = ElementTracker::Subscription();
+  element_ = nullptr;
 }
 
 }  // namespace ui
diff --git a/ui/base/interaction/element_tracker.h b/ui/base/interaction/element_tracker.h
index 817c6a37..9be3626 100644
--- a/ui/base/interaction/element_tracker.h
+++ b/ui/base/interaction/element_tracker.h
@@ -6,6 +6,7 @@
 #define UI_BASE_INTERACTION_ELEMENT_TRACKER_H_
 
 #include <map>
+#include <memory>
 #include <vector>
 
 #include "base/callback_list.h"
@@ -19,34 +20,35 @@
 
 namespace ui {
 
-// Represents a UI element in a platform-agnostic manner.
+// Represents a visible UI element in a platform-agnostic manner.
 //
 // A pointer to this object may be stored after the element becomes visible, but
 // is only valid until the "element hidden" event is called for this element;
-// see `ElementTracker` below.
+// see `ElementTracker` below. If you want to hold a pointer that will be valid
+// only as long as the element is visible, use a SafeElementReference.
 //
 // You should derive a class for each UI framework whose elements you wish to
 // track. See README.md for information on how to create your own framework
 // implementations.
-class COMPONENT_EXPORT(UI_BASE) ElementTrackerElement {
+class COMPONENT_EXPORT(UI_BASE) TrackedElement {
  public:
   // Used by IsA() and AsA() methods to do runtime type-checking.
   using FrameworkIdentifier = ElementIdentifier;
 
-  virtual ~ElementTrackerElement();
+  virtual ~TrackedElement();
 
   ElementIdentifier identifier() const { return identifier_; }
   ElementContext context() const { return context_; }
 
   // Returns whether this element is a specific subtype - for example, a
-  // views::ViewsElementTrackerElement.
+  // views::ViewsTrackedElement.
   template <typename T>
   bool IsA() const {
     return AsA<T>();
   }
 
   // Dynamically casts this element to a specific subtype, such as a
-  // views::ViewsElementTrackerElement, returning null if the element is the
+  // views::ViewsTrackedElement, returning null if the element is the
   // wrong type.
   template <typename T>
   T* AsA() {
@@ -56,7 +58,7 @@
   }
 
   // Dynamically casts this element to a specific subtype, such as a
-  // views::ViewsElementTrackerElement, returning null if the element is the
+  // views::ViewsTrackedElement, returning null if the element is the
   // wrong type. This version converts const objects.
   template <typename T>
   const T* AsA() const {
@@ -66,7 +68,7 @@
   }
 
  protected:
-  ElementTrackerElement(ElementIdentifier identifier, ElementContext context);
+  TrackedElement(ElementIdentifier identifier, ElementContext context);
 
   // Override this in derived classes with a unique FrameworkIdentifier.
   // You must also define a static GetFrameworkIdentifier() method that returns
@@ -86,17 +88,17 @@
 };
 
 // These macros can be used to help define platform-specific subclasses of
-// `ElementTrackerElement`.
+// `TrackedElement`.
 #define DECLARE_ELEMENT_TRACKER_METADATA()             \
   static FrameworkIdentifier GetFrameworkIdentifier(); \
   FrameworkIdentifier GetInstanceFrameworkIdentifier() const override;
 #define DEFINE_ELEMENT_TRACKER_METADATA(ClassName)                   \
-  ui::ElementTrackerElement::FrameworkIdentifier                     \
+  ui::TrackedElement::FrameworkIdentifier                            \
   ClassName::GetFrameworkIdentifier() {                              \
     DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(k##ClassName##Identifier); \
     return k##ClassName##Identifier;                                 \
   }                                                                  \
-  ui::ElementTrackerElement::FrameworkIdentifier                     \
+  ui::TrackedElement::FrameworkIdentifier                            \
   ClassName::GetInstanceFrameworkIdentifier() const {                \
     return GetFrameworkIdentifier();                                 \
   }
@@ -109,9 +111,9 @@
 // changes context or identifier.
 class COMPONENT_EXPORT(UI_BASE) ElementTrackerFrameworkDelegate {
  public:
-  virtual void NotifyElementShown(ElementTrackerElement* element) = 0;
-  virtual void NotifyElementActivated(ElementTrackerElement* element) = 0;
-  virtual void NotifyElementHidden(ElementTrackerElement* element) = 0;
+  virtual void NotifyElementShown(TrackedElement* element) = 0;
+  virtual void NotifyElementActivated(TrackedElement* element) = 0;
+  virtual void NotifyElementHidden(TrackedElement* element) = 0;
 };
 
 // Tracks elements as they become visible, are activated by the user, and
@@ -121,9 +123,9 @@
 class COMPONENT_EXPORT(UI_BASE) ElementTracker
     : ElementTrackerFrameworkDelegate {
  public:
-  using Callback = base::RepeatingCallback<void(ElementTrackerElement*)>;
+  using Callback = base::RepeatingCallback<void(TrackedElement*)>;
   using Subscription = base::CallbackListSubscription;
-  using ElementList = std::vector<ElementTrackerElement*>;
+  using ElementList = std::vector<TrackedElement*>;
 
   // Gets the element tracker to be used by clients to subscribe to and receive
   // events.
@@ -138,16 +140,16 @@
   //
   // Use when you want to verify that there's only one matching element in the
   // given context.
-  ElementTrackerElement* GetUniqueElement(ElementIdentifier id,
-                                          ElementContext context);
+  TrackedElement* GetUniqueElement(ElementIdentifier id,
+                                   ElementContext context);
 
   // Returns the same result as GetUniqueElement() except that no error is
   // generated if there is more than one matching element.
   //
   // Use when you just need *an* element in the given context, and don't care if
   // there's more than one.
-  ElementTrackerElement* GetFirstMatchingElement(ElementIdentifier id,
-                                                 ElementContext context);
+  TrackedElement* GetFirstMatchingElement(ElementIdentifier id,
+                                          ElementContext context);
 
   // Returns a list of all visible elements with identifier `id` in `context`.
   // The list may be empty.
@@ -172,9 +174,9 @@
   // Adds a callback that will be called whenever an element with identifier
   // `id` in `context` is hidden.
   //
-  // Note: the ElementTrackerElement* passed to the callback may not remain
+  // Note: the TrackedElement* passed to the callback may not remain
   // valid after the call, even if the same element object in its UI framework
-  // is re-shown (a new ElementTrackerElement may be generated).
+  // is re-shown (a new TrackedElement may be generated).
   Subscription AddElementHiddenCallback(ElementIdentifier id,
                                         ElementContext context,
                                         Callback callback);
@@ -182,17 +184,19 @@
  private:
   friend class base::NoDestructor<ElementTracker>;
   class ElementData;
+  class GarbageCollector;
   using LookupKey = std::pair<ElementIdentifier, ElementContext>;
   FRIEND_TEST_ALL_PREFIXES(ElementTrackerTest, CleanupAfterElementHidden);
   FRIEND_TEST_ALL_PREFIXES(ElementTrackerTest, CleanupAfterCallbacksRemoved);
+  FRIEND_TEST_ALL_PREFIXES(ElementTrackerTest, HideDuringShowCallback);
 
   ElementTracker();
   ~ElementTracker();
 
   // ElementTrackerFrameworkDelegate:
-  void NotifyElementShown(ElementTrackerElement* element) override;
-  void NotifyElementActivated(ElementTrackerElement* element) override;
-  void NotifyElementHidden(ElementTrackerElement* element) override;
+  void NotifyElementShown(TrackedElement* element) override;
+  void NotifyElementActivated(TrackedElement* element) override;
+  void NotifyElementHidden(TrackedElement* element) override;
 
   ElementData* GetOrAddElementData(ElementIdentifier id,
                                    ElementContext context);
@@ -200,7 +204,30 @@
   void MaybeCleanup(ElementData* data);
 
   std::map<LookupKey, std::unique_ptr<ElementData>> element_data_;
-  std::map<ElementTrackerElement*, ElementData*> element_to_data_lookup_;
+  std::map<TrackedElement*, ElementData*> element_to_data_lookup_;
+  std::unique_ptr<GarbageCollector> gc_;
+};
+
+// Holds an TrackedElement reference and nulls it out if the element goes
+// away. In other words, acts as a weak reference for TrackedElements.
+class COMPONENT_EXPORT(UI_BASE) SafeElementReference {
+ public:
+  SafeElementReference();
+  explicit SafeElementReference(TrackedElement* element);
+  SafeElementReference(SafeElementReference&& other);
+  SafeElementReference& operator=(SafeElementReference&& other);
+  ~SafeElementReference();
+
+  TrackedElement* get() { return element_; }
+  explicit operator bool() const { return element_; }
+  bool operator!() const { return !element_; }
+
+ private:
+  void Subscribe();
+  void OnElementHidden(TrackedElement* element);
+
+  ElementTracker::Subscription subscription_;
+  TrackedElement* element_ = nullptr;
 };
 
 }  // namespace ui
diff --git a/ui/base/interaction/element_tracker_unittest.cc b/ui/base/interaction/element_tracker_unittest.cc
index d3c36de..2a07369 100644
--- a/ui/base/interaction/element_tracker_unittest.cc
+++ b/ui/base/interaction/element_tracker_unittest.cc
@@ -4,111 +4,38 @@
 
 #include "ui/base/interaction/element_tracker.h"
 
-#include <memory>
-
 #include "base/callback_forward.h"
+#include "base/logging.h"
 #include "base/test/bind.h"
+#include "base/test/mock_callback.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/interaction/element_identifier.h"
+#include "ui/base/interaction/element_test_util.h"
 
 namespace ui {
 
 namespace {
 
-DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestFrameworkIdentifier);
-DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kOtherFrameworkIdentifier);
+#define DECLARE_STRICT_CALLBACK(Name)                \
+  base::MockCallback<ElementTracker::Callback> Name; \
+  EXPECT_CALL(Name, Run).Times(0)
+
+#define DECLARE_LAX_CALLBACK(Name)                   \
+  base::MockCallback<ElementTracker::Callback> Name; \
+  EXPECT_CALL(Name, Run).Times(testing::AnyNumber())
+
 DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kElementIdentifier1);
 DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kElementIdentifier2);
 const ElementContext kElementContext1(1);
 const ElementContext kElementContext2(2);
 
-class TestElementBase : public ElementTrackerElement {
- public:
-  TestElementBase(ElementIdentifier id, ElementContext context)
-      : ElementTrackerElement(id, context) {}
-  ~TestElementBase() override { Hide(); }
-
-  void Show() {
-    if (visible_)
-      return;
-    visible_ = true;
-    ElementTracker::GetFrameworkDelegate()->NotifyElementShown(this);
-  }
-
-  void Activate() {
-    DCHECK(visible_);
-    ElementTracker::GetFrameworkDelegate()->NotifyElementActivated(this);
-  }
-
-  void Hide() {
-    if (!visible_)
-      return;
-    visible_ = false;
-    ElementTracker::GetFrameworkDelegate()->NotifyElementHidden(this);
-  }
-
- private:
-  bool visible_ = false;
-};
-
-using ElementPtr = std::unique_ptr<TestElementBase>;
-
-class TestElement : public TestElementBase {
- public:
-  using TestElementBase::TestElementBase;
-
-  static FrameworkIdentifier GetFrameworkIdentifier() {
-    return kTestFrameworkIdentifier;
-  }
-
-  FrameworkIdentifier GetInstanceFrameworkIdentifier() const override {
-    return kTestFrameworkIdentifier;
-  }
-};
-
-class TestElementOtherFramework : public TestElementBase {
- public:
-  using TestElementBase::TestElementBase;
-
-  static FrameworkIdentifier GetFrameworkIdentifier() {
-    return kOtherFrameworkIdentifier;
-  }
-
-  FrameworkIdentifier GetInstanceFrameworkIdentifier() const override {
-    return kOtherFrameworkIdentifier;
-  }
-};
-
-class TestCallback {
- public:
-  ElementTracker::Callback GetCallback() {
-    return base::BindLambdaForTesting([&](ElementTrackerElement* element) {
-      ++count_;
-      last_element_ = element;
-      if (on_callback_)
-        on_callback_.Run();
-    });
-  }
-
-  void SetCallback(base::RepeatingClosure on_callback) {
-    on_callback_ = on_callback;
-  }
-
-  size_t count() const { return count_; }
-  const ElementTrackerElement* last_element() const { return last_element_; }
-
- private:
-  size_t count_ = 0;
-  const ElementTrackerElement* last_element_ = nullptr;
-  base::RepeatingClosure on_callback_;
-};
-
 }  // namespace
 
-TEST(ElementTrackerElementTest, IsATest) {
-  ElementPtr e1 =
+TEST(TrackedElementTest, IsATest) {
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  ElementPtr e2 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e2 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier1, kElementContext1);
 
   EXPECT_TRUE(e1->IsA<TestElement>());
@@ -117,10 +44,10 @@
   EXPECT_TRUE(e2->IsA<TestElementOtherFramework>());
 }
 
-TEST(ElementTrackerElementTest, AsATest) {
-  ElementPtr e1 =
+TEST(TrackedElementTest, AsATest) {
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier2, kElementContext2);
-  ElementPtr e2 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e2 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier2, kElementContext2);
 
   EXPECT_EQ(e1.get(), e1->AsA<TestElement>());
@@ -130,9 +57,9 @@
 }
 
 TEST(ElementTrackerTest, GetUniqueElement) {
-  ElementPtr e1 =
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  ElementPtr e2 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e2 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier2, kElementContext1);
   EXPECT_EQ(nullptr, ElementTracker::GetElementTracker()->GetUniqueElement(
                          kElementIdentifier1, kElementContext1));
@@ -161,9 +88,9 @@
 }
 
 TEST(ElementTrackerTest, GetFirstMatchingElement) {
-  ElementPtr e1 =
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  ElementPtr e2 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e2 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier2, kElementContext1);
   EXPECT_EQ(nullptr,
             ElementTracker::GetElementTracker()->GetFirstMatchingElement(
@@ -202,9 +129,9 @@
 }
 
 TEST(ElementTrackerTest, GetFirstMatchingElementWithMultipleElements) {
-  ElementPtr e1 =
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  ElementPtr e2 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e2 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier1, kElementContext1);
   EXPECT_EQ(nullptr,
             ElementTracker::GetElementTracker()->GetFirstMatchingElement(
@@ -236,9 +163,9 @@
 }
 
 TEST(ElementTrackerTest, GetAllMatchingElements) {
-  ElementPtr e1 =
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  ElementPtr e2 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e2 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier1, kElementContext1);
   ElementTracker::ElementList expected;
   EXPECT_EQ(expected,
@@ -277,11 +204,11 @@
 }
 
 TEST(ElementTrackerTest, IsElementVisible) {
-  ElementPtr e1 =
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  ElementPtr e2 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e2 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier2, kElementContext1);
-  ElementPtr e3 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e3 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier1, kElementContext2);
   EXPECT_FALSE(ElementTracker::GetElementTracker()->IsElementVisible(
       kElementIdentifier1, kElementContext1));
@@ -334,132 +261,88 @@
 }
 
 TEST(ElementTrackerTest, AddElementShownCallback) {
-  TestCallback callback;
+  DECLARE_STRICT_CALLBACK(callback);
   auto subscription =
       ElementTracker::GetElementTracker()->AddElementShownCallback(
-          kElementIdentifier1, kElementContext1, callback.GetCallback());
-  ElementPtr e1 =
+          kElementIdentifier1, kElementContext1, callback.Get());
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  ElementPtr e2 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e2 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier2, kElementContext1);
-  ElementPtr e3 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e3 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier1, kElementContext2);
-  ElementPtr e4 =
+  TestElementPtr e4 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  EXPECT_EQ(0U, callback.count());
-  e1->Show();
-  EXPECT_EQ(1U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e1.get()), e1->Show());
   e2->Show();
-  EXPECT_EQ(1U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
   e3->Show();
-  EXPECT_EQ(1U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
   e1->Activate();
   e1->Hide();
-  EXPECT_EQ(1U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
-  e4->Show();
-  EXPECT_EQ(2U, callback.count());
-  EXPECT_EQ(e4.get(), callback.last_element());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e4.get()), e4->Show());
 }
 
 TEST(ElementTrackerTest, AddElementActivatedCallback) {
-  TestCallback callback;
+  DECLARE_STRICT_CALLBACK(callback);
   auto subscription =
       ElementTracker::GetElementTracker()->AddElementActivatedCallback(
-          kElementIdentifier1, kElementContext1, callback.GetCallback());
-  ElementPtr e1 =
+          kElementIdentifier1, kElementContext1, callback.Get());
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  ElementPtr e2 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e2 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier2, kElementContext1);
-  ElementPtr e3 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e3 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier1, kElementContext2);
-  ElementPtr e4 =
+  TestElementPtr e4 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  EXPECT_EQ(0U, callback.count());
   e1->Show();
-  EXPECT_EQ(0U, callback.count());
   e2->Show();
-  EXPECT_EQ(0U, callback.count());
   e3->Show();
-  EXPECT_EQ(0U, callback.count());
   e4->Show();
-  EXPECT_EQ(0U, callback.count());
-  e1->Activate();
-  EXPECT_EQ(1U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
-  e4->Activate();
-  EXPECT_EQ(2U, callback.count());
-  EXPECT_EQ(e4.get(), callback.last_element());
-  e1->Activate();
-  EXPECT_EQ(3U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e1.get()), e1->Activate());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e4.get()), e4->Activate());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e1.get()), e1->Activate());
   e2->Activate();
-  EXPECT_EQ(3U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
   e3->Activate();
-  EXPECT_EQ(3U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
 }
 
 TEST(ElementTrackerTest, AddElementHiddenCallback) {
-  TestCallback callback;
+  DECLARE_STRICT_CALLBACK(callback);
   auto subscription =
       ElementTracker::GetElementTracker()->AddElementHiddenCallback(
-          kElementIdentifier1, kElementContext1, callback.GetCallback());
-  ElementPtr e1 =
+          kElementIdentifier1, kElementContext1, callback.Get());
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  ElementPtr e2 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e2 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier2, kElementContext1);
-  ElementPtr e3 = std::make_unique<TestElementOtherFramework>(
+  TestElementPtr e3 = std::make_unique<TestElementOtherFramework>(
       kElementIdentifier1, kElementContext2);
-  ElementPtr e4 =
+  TestElementPtr e4 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  EXPECT_EQ(0U, callback.count());
   e1->Show();
-  EXPECT_EQ(0U, callback.count());
   e2->Show();
-  EXPECT_EQ(0U, callback.count());
   e3->Show();
-  EXPECT_EQ(0U, callback.count());
   e4->Show();
-  EXPECT_EQ(0U, callback.count());
   e1->Activate();
-  EXPECT_EQ(0U, callback.count());
   e4->Activate();
-  EXPECT_EQ(0U, callback.count());
   e2->Hide();
-  EXPECT_EQ(0U, callback.count());
-  e1->Hide();
-  EXPECT_EQ(1U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e1.get()), e1->Hide());
   e3->Hide();
-  EXPECT_EQ(1U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
-  e4->Hide();
-  EXPECT_EQ(2U, callback.count());
-  EXPECT_EQ(e4.get(), callback.last_element());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e4.get()), e4->Hide());
   e1->Show();
-  EXPECT_EQ(2U, callback.count());
-  EXPECT_EQ(e4.get(), callback.last_element());
-  e1->Hide();
-  EXPECT_EQ(3U, callback.count());
-  EXPECT_EQ(e1.get(), callback.last_element());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e1.get()), e1->Hide());
 }
 
 TEST(ElementTrackerTest, CleanupAfterElementHidden) {
   EXPECT_TRUE(ElementTracker::GetElementTracker()->element_data_.empty());
-  ElementPtr e1 =
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
   e1->Show();
   EXPECT_EQ(1U, ElementTracker::GetElementTracker()->element_data_.size());
   {
-    TestCallback callback;
+    DECLARE_STRICT_CALLBACK(callback);
     auto subscription =
         ElementTracker::GetElementTracker()->AddElementShownCallback(
-            kElementIdentifier1, kElementContext1, callback.GetCallback());
+            kElementIdentifier1, kElementContext1, callback.Get());
     EXPECT_EQ(1U, ElementTracker::GetElementTracker()->element_data_.size());
   }
   e1->Hide();
@@ -468,16 +351,16 @@
 
 TEST(ElementTrackerTest, CleanupAfterCallbacksRemoved) {
   EXPECT_TRUE(ElementTracker::GetElementTracker()->element_data_.empty());
-  ElementPtr e1 =
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
 
   // Add element shown callback. An element will be shown transiently during the
   // subscription.
   {
-    TestCallback callback;
+    DECLARE_LAX_CALLBACK(callback);
     auto subscription =
         ElementTracker::GetElementTracker()->AddElementShownCallback(
-            kElementIdentifier1, kElementContext1, callback.GetCallback());
+            kElementIdentifier1, kElementContext1, callback.Get());
     EXPECT_EQ(1U, ElementTracker::GetElementTracker()->element_data_.size());
     e1->Show();
     EXPECT_EQ(1U, ElementTracker::GetElementTracker()->element_data_.size());
@@ -488,34 +371,34 @@
 
   // Add element activated callback.
   {
-    TestCallback callback;
+    DECLARE_LAX_CALLBACK(callback);
     auto subscription =
         ElementTracker::GetElementTracker()->AddElementActivatedCallback(
-            kElementIdentifier1, kElementContext1, callback.GetCallback());
+            kElementIdentifier1, kElementContext1, callback.Get());
     EXPECT_EQ(1U, ElementTracker::GetElementTracker()->element_data_.size());
   }
   EXPECT_TRUE(ElementTracker::GetElementTracker()->element_data_.empty());
 
   // Add element hidden callback.
   {
-    TestCallback callback;
+    DECLARE_LAX_CALLBACK(callback);
     auto subscription =
         ElementTracker::GetElementTracker()->AddElementHiddenCallback(
-            kElementIdentifier1, kElementContext1, callback.GetCallback());
+            kElementIdentifier1, kElementContext1, callback.Get());
     EXPECT_EQ(1U, ElementTracker::GetElementTracker()->element_data_.size());
   }
   EXPECT_TRUE(ElementTracker::GetElementTracker()->element_data_.empty());
 
   // Add and remove multiple callbacks.
   {
-    TestCallback callback;
+    DECLARE_LAX_CALLBACK(callback);
     auto sub1 = ElementTracker::GetElementTracker()->AddElementShownCallback(
-        kElementIdentifier1, kElementContext1, callback.GetCallback());
+        kElementIdentifier1, kElementContext1, callback.Get());
     auto sub2 =
         ElementTracker::GetElementTracker()->AddElementActivatedCallback(
-            kElementIdentifier1, kElementContext1, callback.GetCallback());
+            kElementIdentifier1, kElementContext1, callback.Get());
     auto sub3 = ElementTracker::GetElementTracker()->AddElementHiddenCallback(
-        kElementIdentifier1, kElementContext1, callback.GetCallback());
+        kElementIdentifier1, kElementContext1, callback.Get());
     EXPECT_EQ(1U, ElementTracker::GetElementTracker()->element_data_.size());
   }
   EXPECT_TRUE(ElementTracker::GetElementTracker()->element_data_.empty());
@@ -527,53 +410,148 @@
 // implemented incorrectly.
 
 TEST(ElementTrackerTest, RemoveCallbackDuringRemove) {
-  ElementPtr e1 =
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  TestCallback callback;
+  DECLARE_STRICT_CALLBACK(callback);
   ElementTracker::Subscription subscription =
       ElementTracker::GetElementTracker()->AddElementHiddenCallback(
-          e1->identifier(), e1->context(), callback.GetCallback());
-  callback.SetCallback(base::BindLambdaForTesting(
-      [&]() { subscription = ElementTracker::Subscription(); }));
+          e1->identifier(), e1->context(), callback.Get());
+
+  ON_CALL(callback, Run).WillByDefault([&](TrackedElement*) {
+    subscription = ElementTracker::Subscription();
+  });
+
   e1->Show();
-  EXPECT_EQ(0U, callback.count());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e1.get()), e1->Hide());
+  e1->Show();
   e1->Hide();
-  EXPECT_EQ(1U, callback.count());
 }
 
 TEST(ElementTrackerTest, RemoveAndThenAddCallbackDuringRemove) {
-  ElementPtr e1 =
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  TestCallback callback;
+  DECLARE_STRICT_CALLBACK(callback);
   ElementTracker::Subscription subscription =
       ElementTracker::GetElementTracker()->AddElementHiddenCallback(
-          e1->identifier(), e1->context(), callback.GetCallback());
-  callback.SetCallback(base::BindLambdaForTesting([&]() {
+          e1->identifier(), e1->context(), callback.Get());
+
+  ON_CALL(callback, Run).WillByDefault([&](TrackedElement*) {
     subscription = ElementTracker::Subscription();
     subscription =
         ElementTracker::GetElementTracker()->AddElementHiddenCallback(
-            e1->identifier(), e1->context(), callback.GetCallback());
-  }));
+            e1->identifier(), e1->context(), callback.Get());
+  });
   e1->Show();
-  EXPECT_EQ(0U, callback.count());
-  e1->Hide();
+  EXPECT_CALL_IN_SCOPE(callback, Run(e1.get()), e1->Hide());
+  e1->Show();
+  EXPECT_CALL_IN_SCOPE(callback, Run(e1.get()), e1->Hide());
 }
 
 TEST(ElementTrackerTest, RemoveAndThenAddDifferentCallbackDuringRemove) {
-  ElementPtr e1 =
+  TestElementPtr e1 =
       std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
-  TestCallback callback;
+  DECLARE_STRICT_CALLBACK(callback);
   ElementTracker::Subscription subscription =
       ElementTracker::GetElementTracker()->AddElementHiddenCallback(
-          e1->identifier(), e1->context(), callback.GetCallback());
-  callback.SetCallback(base::BindLambdaForTesting([&]() {
+          e1->identifier(), e1->context(), callback.Get());
+
+  ON_CALL(callback, Run).WillByDefault([&](TrackedElement*) {
     subscription = ElementTracker::Subscription();
     subscription = ElementTracker::GetElementTracker()->AddElementShownCallback(
-        e1->identifier(), e1->context(), callback.GetCallback());
-  }));
+        e1->identifier(), e1->context(), callback.Get());
+  });
+
   e1->Show();
-  EXPECT_EQ(0U, callback.count());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e1.get()), e1->Hide());
+  EXPECT_CALL_IN_SCOPE(callback, Run(e1.get()), e1->Show());
   e1->Hide();
 }
 
+TEST(ElementTrackerTest, MultipleCallbacksForSameEvent) {
+  TestElementPtr e1 =
+      std::make_unique<TestElement>(kElementIdentifier1, kElementContext1);
+  DECLARE_STRICT_CALLBACK(callback);
+  DECLARE_STRICT_CALLBACK(callback2);
+  ElementTracker::Subscription subscription =
+      ElementTracker::GetElementTracker()->AddElementHiddenCallback(
+          e1->identifier(), e1->context(), callback.Get());
+  ElementTracker::Subscription subscription2 =
+      ElementTracker::GetElementTracker()->AddElementHiddenCallback(
+          e1->identifier(), e1->context(), callback2.Get());
+
+  e1->Show();
+
+  // Note: these calls are not ordered.
+  EXPECT_CALL(callback, Run(e1.get())).Times(1);
+  EXPECT_CALL(callback2, Run(e1.get())).Times(1);
+  e1->Hide();
+}
+
+TEST(ElementTrackerTest, HideDuringShowCallback) {
+  TestElement e1(kElementIdentifier1, kElementContext1);
+  ElementTracker::Subscription subscription;
+  auto callback = base::BindLambdaForTesting([&](TrackedElement* element) {
+    subscription = ElementTracker::Subscription();
+    e1.Hide();
+  });
+  subscription = ElementTracker::GetElementTracker()->AddElementShownCallback(
+      e1.identifier(), e1.context(), callback);
+  e1.Show();
+
+  // Verify that cleanup still happens after all callbacks return.
+  EXPECT_TRUE(ElementTracker::GetElementTracker()->element_data_.empty());
+}
+
+TEST(SafeElementReferenceTest, ElementRemainsVisible) {
+  TestElement e1(kElementIdentifier1, kElementContext1);
+  e1.Show();
+  SafeElementReference ref(&e1);
+  EXPECT_TRUE(ref);
+  EXPECT_FALSE(!ref);
+  EXPECT_EQ(&e1, ref.get());
+  e1.Activate();
+  EXPECT_TRUE(ref);
+  EXPECT_FALSE(!ref);
+  EXPECT_EQ(&e1, ref.get());
+}
+
+TEST(SafeElementReferenceTest, ElementHidden) {
+  TestElement e1(kElementIdentifier1, kElementContext1);
+  e1.Show();
+  SafeElementReference ref(&e1);
+  EXPECT_TRUE(ref);
+  EXPECT_FALSE(!ref);
+  EXPECT_EQ(&e1, ref.get());
+  e1.Hide();
+  EXPECT_FALSE(ref);
+  EXPECT_TRUE(!ref);
+  EXPECT_EQ(nullptr, ref.get());
+}
+
+TEST(SafeElementReferenceTest, MoveConstructor) {
+  TestElement e1(kElementIdentifier1, kElementContext1);
+  e1.Show();
+  std::unique_ptr<SafeElementReference> ref;
+  {
+    SafeElementReference ref2(&e1);
+    ref = std::make_unique<SafeElementReference>(std::move(ref2));
+  }
+  EXPECT_EQ(&e1, ref->get());
+  e1.Hide();
+  EXPECT_EQ(nullptr, ref->get());
+}
+
+TEST(SafeElementReferenceTest, MoveOperator) {
+  TestElement e1(kElementIdentifier1, kElementContext1);
+  e1.Show();
+  SafeElementReference ref;
+  {
+    SafeElementReference ref2(&e1);
+    ref = std::move(ref2);
+  }
+  EXPECT_EQ(&e1, ref.get());
+  e1.Hide();
+  EXPECT_EQ(nullptr, ref.get());
+}
+
 }  // namespace ui
diff --git a/ui/base/interaction/interaction_sequence.cc b/ui/base/interaction/interaction_sequence.cc
new file mode 100644
index 0000000..1980a82
--- /dev/null
+++ b/ui/base/interaction/interaction_sequence.cc
@@ -0,0 +1,515 @@
+// 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 "ui/base/interaction/interaction_sequence.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "ui/base/interaction/element_tracker.h"
+
+namespace ui {
+
+namespace {
+
+// Runs |callback| if it is valid.
+// We have a lot of callbacks that can be null, so calling through this method
+// prevents accidentally trying to run a null callback.
+template <typename Signature, typename... Args>
+void RunIfValid(base::OnceCallback<Signature> callback, Args... args) {
+  if (callback)
+    std::move(callback).Run(args...);
+}
+
+// Version of AutoReset that takes a pointer-to-member and a weak reference in
+// case the object that owns the value goes away before the AutoReset does.
+template <class T, class U>
+class SafeAutoReset {
+ public:
+  SafeAutoReset(base::WeakPtr<T> ptr, U T::*ref, U new_value)
+      : ptr_(ptr), ref_(ref), old_value_(ptr.get()->*ref) {
+    ptr.get()->*ref = new_value;
+  }
+
+  SafeAutoReset(SafeAutoReset<T, U>&& other)
+      : ptr_(std::move(other.ptr_)),
+        ref_(other.ref_),
+        old_value_(other.old_value_) {}
+
+  SafeAutoReset& operator=(SafeAutoReset<T, U>&& other) {
+    if (this != &other) {
+      Reset();
+      ptr_ = std::move(other.ptr_);
+      ref_ = other.ref_;
+      old_value_ = other.old_value_;
+    }
+    return *this;
+  }
+
+  ~SafeAutoReset() { Reset(); }
+
+ private:
+  void Reset() {
+    if (ptr_)
+      ptr_.get()->*ref_ = old_value_;
+  }
+
+  base::WeakPtr<T> ptr_;
+  U T::*ref_ = nullptr;
+  U old_value_ = U();
+};
+
+// Convenience method to create a SafeAutoReset with less boilerplate.
+template <class T, class U>
+static SafeAutoReset<T, U> MakeSafeAutoReset(base::WeakPtr<T> ptr,
+                                             U T::*ref,
+                                             U new_value) {
+  return SafeAutoReset<T, U>(ptr, ref, new_value);
+}
+
+}  // anonymous namespace
+
+InteractionSequence::Step::Step() = default;
+InteractionSequence::Step::~Step() = default;
+
+struct InteractionSequence::Configuration {
+  Configuration() = default;
+  ~Configuration() = default;
+
+  std::list<std::unique_ptr<Step>> steps;
+  ElementContext context;
+  AbortedCallback aborted_callback;
+  CompletedCallback completed_callback;
+};
+
+InteractionSequence::Builder::Builder()
+    : configuration_(std::make_unique<Configuration>()) {}
+
+InteractionSequence::Builder::~Builder() {
+  DCHECK(!configuration_);
+}
+
+InteractionSequence::Builder& InteractionSequence::Builder::SetAbortedCallback(
+    AbortedCallback callback) {
+  DCHECK(!configuration_->aborted_callback);
+  configuration_->aborted_callback = std::move(callback);
+  return *this;
+}
+
+InteractionSequence::Builder&
+InteractionSequence::Builder::SetCompletedCallback(CompletedCallback callback) {
+  DCHECK(!configuration_->completed_callback);
+  configuration_->completed_callback = std::move(callback);
+  return *this;
+}
+
+InteractionSequence::Builder& InteractionSequence::Builder::AddStep(
+    std::unique_ptr<Step> step) {
+  DCHECK(step->id);
+  DCHECK(configuration_->steps.empty() || !step->element)
+      << " Only the initial step of a sequence may have a pre-set element.";
+  DCHECK(!step->element || step->must_be_visible)
+      << " Initial step with associated element must be visible from start.";
+  step->must_be_visible =
+      step->must_be_visible.value_or(step->type == StepType::kActivated);
+  step->must_remain_visible =
+      step->must_remain_visible.value_or(step->type == StepType::kShown);
+  DCHECK(step->type != StepType::kHidden || !step->must_remain_visible.value());
+  if (!configuration_->context)
+    configuration_->context = step->context;
+  else
+    DCHECK(!step->context || step->context == configuration_->context);
+  configuration_->steps.emplace_back(std::move(step));
+  return *this;
+}
+
+InteractionSequence::Builder& InteractionSequence::Builder::SetContext(
+    ElementContext context) {
+  configuration_->context = context;
+  return *this;
+}
+
+std::unique_ptr<InteractionSequence> InteractionSequence::Builder::Build() {
+  DCHECK(!configuration_->steps.empty());
+  DCHECK(configuration_->context)
+      << "If no view is provided, Builder::SetContext() must be called.";
+  return base::WrapUnique(new InteractionSequence(std::move(configuration_)));
+}
+
+InteractionSequence::StepBuilder::StepBuilder()
+    : step_(std::make_unique<Step>()) {}
+InteractionSequence::StepBuilder::~StepBuilder() = default;
+
+InteractionSequence::StepBuilder&
+InteractionSequence::StepBuilder::SetElementID(ElementIdentifier element_id) {
+  DCHECK(element_id);
+  step_->id = element_id;
+  return *this;
+}
+
+InteractionSequence::StepBuilder& InteractionSequence::StepBuilder::SetContext(
+    ElementContext context) {
+  DCHECK(context);
+  step_->context = context;
+  return *this;
+}
+
+InteractionSequence::StepBuilder&
+InteractionSequence::StepBuilder::SetMustBeVisibleAtStart(
+    bool must_be_visible) {
+  step_->must_be_visible = must_be_visible;
+  return *this;
+}
+
+InteractionSequence::StepBuilder&
+InteractionSequence::StepBuilder::SetMustRemainVisible(
+    bool must_remain_visible) {
+  step_->must_remain_visible = must_remain_visible;
+  return *this;
+}
+
+InteractionSequence::StepBuilder& InteractionSequence::StepBuilder::SetType(
+    StepType step_type) {
+  step_->type = step_type;
+  return *this;
+}
+
+InteractionSequence::StepBuilder&
+InteractionSequence::StepBuilder::SetStartCallback(
+    StepCallback start_callback) {
+  step_->start_callback = std::move(start_callback);
+  return *this;
+}
+
+InteractionSequence::StepBuilder&
+InteractionSequence::StepBuilder::SetEndCallback(StepCallback end_callback) {
+  step_->end_callback = std::move(end_callback);
+  return *this;
+}
+
+std::unique_ptr<InteractionSequence::Step>
+InteractionSequence::StepBuilder::Build() {
+  return std::move(step_);
+}
+
+InteractionSequence::InteractionSequence(
+    std::unique_ptr<Configuration> configuration)
+    : configuration_(std::move(configuration)) {
+  TrackedElement* const first_element = next_step()->element;
+  if (first_element) {
+    DCHECK(first_element->identifier() == next_step()->id);
+    DCHECK(first_element->context() == context());
+    next_step()->subscription =
+        ElementTracker::GetElementTracker()->AddElementHiddenCallback(
+            first_element->identifier(), first_element->context(),
+            base::BindRepeating(&InteractionSequence::OnElementHidden,
+                                base::Unretained(this)));
+  }
+}
+
+// static
+std::unique_ptr<InteractionSequence::Step>
+InteractionSequence::WithInitialElement(TrackedElement* element,
+                                        StepCallback start_callback,
+                                        StepCallback end_callback) {
+  StepBuilder step;
+  step.step_->element = element;
+  step.SetType(StepType::kShown)
+      .SetElementID(element->identifier())
+      .SetContext(element->context())
+      .SetMustBeVisibleAtStart(true)
+      .SetMustRemainVisible(true)
+      .SetStartCallback(std::move(start_callback))
+      .SetEndCallback(std::move(end_callback));
+  return step.Build();
+}
+
+InteractionSequence::~InteractionSequence() {
+  // We can abort during a step callback, but we cannot destroy this object.
+  if (started_)
+    Abort();
+}
+
+void InteractionSequence::Start() {
+  // Ensure we're not already started.
+  DCHECK(!started_);
+  started_ = true;
+  if (missing_first_element_) {
+    Abort();
+    return;
+  }
+  StageNextStep();
+}
+
+void InteractionSequence::OnElementShown(TrackedElement* element) {
+  DCHECK_EQ(StepType::kShown, next_step()->type);
+  DCHECK(element->identifier() == next_step()->id);
+  DoStepTransition(element);
+}
+
+void InteractionSequence::OnElementActivated(TrackedElement* element) {
+  DCHECK_EQ(StepType::kActivated, next_step()->type);
+  DCHECK(element->identifier() == next_step()->id);
+  DoStepTransition(element);
+}
+
+void InteractionSequence::OnElementHidden(TrackedElement* element) {
+  if (!started_) {
+    DCHECK_EQ(next_step()->element, element);
+    missing_first_element_ = true;
+    next_step()->subscription = ElementTracker::Subscription();
+    next_step()->element = nullptr;
+    return;
+  }
+
+  if (current_step_->element == element) {
+    // If the current step is marked as needing to remain visible and we haven't
+    // seen the triggering event for the next step, abort.
+    if (current_step_->must_remain_visible.value() &&
+        !activated_during_callback_) {
+      Abort();
+      return;
+    }
+
+    // This element pointer is no longer valid and we can stop watching.
+    current_step_->subscription = ElementTracker::Subscription();
+    current_step_->element = nullptr;
+  }
+
+  // If we got a hidden callback and it wasn't to abort the current step, it
+  // must be because we're waiting on the next step to start.
+  if (next_step() && next_step()->id == element->identifier() &&
+      next_step()->type == StepType::kHidden) {
+    DoStepTransition(element);
+  }
+}
+
+void InteractionSequence::OnElementActivatedDuringStepTransition(
+    TrackedElement* element) {
+  if (!next_step())
+    return;
+
+  DCHECK(element->identifier() == next_step()->id);
+  next_step()->element = element;
+  next_step()->subscription =
+      ElementTracker::GetElementTracker()->AddElementHiddenCallback(
+          next_step()->id, context(),
+          base::BindRepeating(
+              &InteractionSequence::OnElementHiddenDuringStepTransition,
+              base::Unretained(this)));
+
+  activated_during_callback_ = true;
+}
+
+void InteractionSequence::OnElementHiddenDuringStepTransition(
+    TrackedElement* element) {
+  if (!next_step() || element != next_step()->element)
+    return;
+
+  next_step()->element = nullptr;
+  next_step()->subscription = ElementTracker::Subscription();
+}
+
+void InteractionSequence::DoStepTransition(TrackedElement* element) {
+  // There are a number of callbacks during this method that could potentially
+  // result in this InteractionSequence being destructed, so maintain a weak
+  // pointer we can check to see if we need to bail out early.
+  base::WeakPtr<InteractionSequence> delete_guard = weak_factory_.GetWeakPtr();
+  auto* const tracker = ElementTracker::GetElementTracker();
+  {
+    // This block is non-re-entrant.
+    DCHECK(!processing_step_);
+    auto processing =
+        MakeSafeAutoReset(weak_factory_.GetWeakPtr(),
+                          &InteractionSequence::processing_step_, true);
+
+    // End the current step.
+    if (current_step_) {
+      // Unsubscribe from any events during the step-end process. Since the step
+      // has ended, conditions like "must remain visible" no longer apply.
+      current_step_->subscription = ElementTracker::Subscription();
+      RunIfValid(std::move(current_step_->end_callback), current_step_->element,
+                 current_step_->id, current_step_->type);
+      if (!delete_guard || AbortedDuringCallback())
+        return;
+    }
+
+    // Set up the new current step.
+    current_step_ = std::move(configuration_->steps.front());
+    configuration_->steps.pop_front();
+    DCHECK(!current_step_->element || current_step_->element == element);
+    current_step_->element =
+        current_step_->type == StepType::kHidden ? nullptr : element;
+    if (current_step_->element) {
+      current_step_->subscription = tracker->AddElementHiddenCallback(
+          current_step_->id, context(),
+          base::BindRepeating(&InteractionSequence::OnElementHidden,
+                              base::Unretained(this)));
+    } else {
+      current_step_->subscription = ElementTracker::Subscription();
+    }
+
+    // Special care must be taken here, because theoretically *anything* could
+    // happen as a result of this callback. If the next step is a shown or
+    // hidden step and the element becomes shown or hidden (or it's a step that
+    // requires the element to be visible and it is not), then the appropriate
+    // transition (or Abort()) will happen in StageNextStep() below.
+    //
+    // If, however, the callback *activates* the next target element, and the
+    // next element is of type kActivated, then the activation will not
+    // register unless we explicitly listen for it. But we still don't want to
+    if (next_step() && next_step()->type == StepType::kActivated) {
+      next_step()->subscription = tracker->AddElementActivatedCallback(
+          next_step()->id, context(),
+          base::BindRepeating(
+              &InteractionSequence::OnElementActivatedDuringStepTransition,
+              base::Unretained((this))));
+    }
+
+    // Start the step. Like all callbacks, this could abort the sequence, or
+    // cause `element` to become invalid. Because of this we use the element
+    // field of the current step from here forward, because we've installed a
+    // callback above that will null it out if it becomes invalid.
+    RunIfValid(std::move(current_step_->start_callback), current_step_->element,
+               current_step_->id, current_step_->type);
+    if (!delete_guard || AbortedDuringCallback())
+      return;
+  }
+
+  if (configuration_->steps.empty()) {
+    // Reset anything that might cause state change during the final callback.
+    // After this, Abort() will have basically no effect, since by the time it
+    // gets called, both the aborted and step end callbacks will be null.
+    current_step_->subscription = ElementTracker::Subscription();
+    configuration_->aborted_callback.Reset();
+    // Last step end callback needs to be run before sequence completed.
+    // Because the InteractionSequence could conceivably be destroyed during
+    // one of these callbacks, make local copies of the callbacks and data.
+    CompletedCallback completed_callback =
+        std::move(configuration_->completed_callback);
+    std::unique_ptr<Step> last_step = std::move(current_step_);
+    RunIfValid(std::move(last_step->end_callback), last_step->element,
+               last_step->id, last_step->type);
+    RunIfValid(std::move(completed_callback));
+    return;
+  }
+
+  // Since we're not done, load up the next step.
+  StageNextStep();
+}
+
+void InteractionSequence::StageNextStep() {
+  auto* const tracker = ElementTracker::GetElementTracker();
+
+  Step* const next = next_step();
+  DCHECK(!activated_during_callback_ || next->type == StepType::kActivated);
+
+  // Note that if the target element for the next step was activated and then
+  // hidden during the previous step transition, `next_element` could be null.
+  TrackedElement* const next_element =
+      (activated_during_callback_ || next->element)
+          ? next->element
+          : tracker->GetFirstMatchingElement(next->id, context());
+
+  if (!activated_during_callback_ && next->must_be_visible.value() &&
+      !next_element) {
+    // Fast forward to the next step before aborting so we get the correct
+    // information on the failed step in the abort callback.
+    current_step_ = std::move(configuration_->steps.front());
+    configuration_->steps.pop_front();
+    // We don't want to call the step-end callback during Abort() since we
+    // didn't technically start the step.
+    current_step_->end_callback = StepCallback();
+    Abort();
+    return;
+  }
+
+  switch (next_step()->type) {
+    case StepType::kShown:
+      if (next_element) {
+        DoStepTransition(next_element);
+      } else {
+        next_step()->subscription = tracker->AddElementShownCallback(
+            next_step()->id, context(),
+            base::BindRepeating(&InteractionSequence::OnElementShown,
+                                base::Unretained(this)));
+      }
+      break;
+    case StepType::kHidden:
+      if (!next_element) {
+        DoStepTransition(nullptr);
+      } else {
+        next_step()->subscription = tracker->AddElementHiddenCallback(
+            next_step()->id, context(),
+            base::BindRepeating(&InteractionSequence::OnElementHidden,
+                                base::Unretained(this)));
+      }
+      break;
+    case StepType::kActivated:
+      if (activated_during_callback_) {
+        activated_during_callback_ = false;
+        DoStepTransition(next_element);
+      } else {
+        next_step()->subscription = tracker->AddElementActivatedCallback(
+            next_step()->id, context(),
+            base::BindRepeating(&InteractionSequence::OnElementActivated,
+                                base::Unretained(this)));
+      }
+      break;
+  }
+}
+
+void InteractionSequence::Abort() {
+  DCHECK(started_);
+  configuration_->steps.clear();
+  if (current_step_) {
+    // Stop listening for events; we don't want additional callbacks during
+    // teardown.
+    current_step_->subscription = ElementTracker::Subscription();
+    // The current step's element could go away during a callback, so hedge our
+    // bets by using a safe reference.
+    SafeElementReference element(current_step_->element);
+    // The entire InteractionSequence could also go away during a callback, so
+    // save anything we need locally so that we don't have to access any class
+    // members as we finish terminating the sequence.
+    std::unique_ptr<Step> last_step = std::move(current_step_);
+    AbortedCallback aborted_callback =
+        std::move(configuration_->aborted_callback);
+    RunIfValid(std::move(last_step->end_callback), element.get(), last_step->id,
+               last_step->type);
+    RunIfValid(std::move(aborted_callback), element.get(), last_step->id,
+               last_step->type);
+  } else {
+    // Aborted before any steps were run. Pass default values.
+    // Note that if the sequence has already been aborted, this is a no-op, the
+    // callback will already be null.
+    RunIfValid(std::move(configuration_->aborted_callback), nullptr,
+               ElementIdentifier(), StepType::kShown);
+  }
+}
+
+bool InteractionSequence::AbortedDuringCallback() const {
+  // All step callbacks are sourced from the current step. If the current step
+  // is null, then the sequence must have aborted (which clears out the current
+  // step). Completion can only happen after step callbacks are finished
+  if (current_step_)
+    return false;
+
+  DCHECK(configuration_->steps.empty());
+  DCHECK(!configuration_->aborted_callback);
+  return true;
+}
+
+InteractionSequence::Step* InteractionSequence::next_step() {
+  return configuration_->steps.empty() ? nullptr
+                                       : configuration_->steps.front().get();
+}
+
+ElementContext InteractionSequence::context() const {
+  return configuration_->context;
+}
+
+}  // namespace ui
diff --git a/ui/base/interaction/interaction_sequence.h b/ui/base/interaction/interaction_sequence.h
new file mode 100644
index 0000000..90d2a14b
--- /dev/null
+++ b/ui/base/interaction/interaction_sequence.h
@@ -0,0 +1,278 @@
+// 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 UI_BASE_INTERACTION_INTERACTION_SEQUENCE_H_
+#define UI_BASE_INTERACTION_INTERACTION_SEQUENCE_H_
+
+#include <list>
+
+#include "base/callback_forward.h"
+#include "base/component_export.h"
+#include "base/memory/weak_ptr.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/base/interaction/element_identifier.h"
+#include "ui/base/interaction/element_tracker.h"
+
+namespace ui {
+
+// Follows an expected sequence of user-UI interactions and provides callbacks
+// at each step. Useful for creating interaction tests and user tutorials.
+//
+// An interaction sequence consists of an ordered series of steps, each of which
+// refers to an interface element tagged with a ElementIdentifier and each of
+// which represents that element being either shown, activated, or hidden. Other
+// unrelated events such as element hover or focus are ignored (but could be
+// supported in the future).
+//
+// Each step has an optional callback that is triggered when the expected
+// interaction happens, and an optional callback that is triggered when the step
+// ends - either because the next step has started or because the user has
+// aborted the sequence (typically by dismissing UI such as a dialog or menu,
+// resulting in the element from the current step being hidden/destroyed). Once
+// the first callback is called/the step starts, the second callback will always
+// be called.
+//
+// Furthermore, when the last step in the sequence completes, in addition to its
+// end callback, an optional sequence-completed callback will be called. If the
+// user aborts the sequence or if this object is destroyed, then an optional
+// sequence-aborted callback is called instead.
+//
+// To use a InteractionSequence, start with a builder:
+//
+//  sequence_ = InteractionSequence::Builder()
+//      .SetCompletedCallback(base::BindOnce(...))
+//      .AddStep(InteractionSequence::WithInitialElement(initial_element))
+//      .AddStep(InteractionSequence::StepBuilder()
+//          .SetElementID(kDialogElementID)
+//          .SetType(StepType::kShown)
+//          .SetStartCallback(...)
+//          .Build())
+//      .AddStep(...)
+//      .Build();
+//  sequence_->Start();
+//
+// For more detailed instructions on using the ui/base/interaction library, see
+// README.md in this folder.
+//
+class COMPONENT_EXPORT(UI_BASE) InteractionSequence {
+ public:
+  // The type of event that is expected to happen next in the sequence.
+  enum class StepType {
+    // Represents the element with the specified ID becoming visible to the
+    // user.
+    kShown,
+    // Represents an element with the specified ID becoming activated by the
+    // user (for buttons or menu items, being clicked).
+    kActivated,
+    // Represents an element with the specified ID becoming hidden or destroyed.
+    kHidden
+  };
+
+  // Callback when a step happens in the sequence, or when a step ends. If
+  // |element| is no longer available, it will be null.
+  using StepCallback = base::OnceCallback<void(TrackedElement* element,
+                                               ElementIdentifier element_id,
+                                               StepType step_type)>;
+
+  // Callback for when the user aborts the sequence by failing to follow the
+  // sequence of steps, or if this object is deleted after the sequence starts.
+  // The most recent event is described by the parameters; if the target element
+  // is no longer available it will be null.
+  using AbortedCallback = base::OnceCallback<void(TrackedElement* last_element,
+                                                  ElementIdentifier last_id,
+                                                  StepType last_step_type)>;
+
+  using CompletedCallback = base::OnceClosure;
+
+  struct Configuration;
+  class StepBuilder;
+
+  struct COMPONENT_EXPORT(UI_BASE) Step {
+    Step();
+    Step(const Step& other) = delete;
+    void operator=(const Step& other) = delete;
+    ~Step();
+
+    StepType type = StepType::kShown;
+    ElementIdentifier id;
+    ElementContext context;
+
+    // These will always have values when the sequence is built, but can be
+    // unspecified during construction. If unspecified, they will be set to
+    // appropriate defaults for `type`.
+    absl::optional<bool> must_be_visible;
+    absl::optional<bool> must_remain_visible;
+
+    StepCallback start_callback;
+    StepCallback end_callback;
+    ElementTracker::Subscription subscription;
+
+    // Tracks the element associated with the step, if known. We could use a
+    // SafeElementReference here, but there are cases where we want to do
+    // additional processing if this element goes away, so we'll add the
+    // listeners manually instead.
+    TrackedElement* element = nullptr;
+  };
+
+  // Use a Builder to specify parameters when creating an InteractionSequence.
+  class COMPONENT_EXPORT(UI_BASE) Builder {
+   public:
+    Builder();
+    Builder(const Builder& other) = delete;
+    void operator=(const Builder& other) = delete;
+    ~Builder();
+
+    // Sets the callback if the user exits the sequence early.
+    Builder& SetAbortedCallback(AbortedCallback callback);
+
+    // Sets the callback if the user completes the sequence.
+    // Convenience method so that the last step's end callback doesn't need to
+    // have special logic in it.
+    Builder& SetCompletedCallback(CompletedCallback callback);
+
+    // Adds an expected step in the sequence. All sequences must have at least
+    // one step.
+    Builder& AddStep(std::unique_ptr<Step> step);
+
+    // Sets the context for this sequence. Must be called if no step is added
+    // by element or has had SetContext() called. Typically the initial step of
+    // a sequence will use WithInitialElement() so it won't be necessary to call
+    // this method.
+    Builder& SetContext(ElementContext context);
+
+    // Creates the InteractionSequence. You must call Start() to initiate the
+    // sequence; sequences cannot be re-used, and a Builder is no longer valid
+    // after Build() is called.
+    std::unique_ptr<InteractionSequence> Build();
+
+   private:
+    std::unique_ptr<Configuration> configuration_;
+  };
+
+  // Used inline in calls to Builder::AddStep to specify step parameters.
+  class COMPONENT_EXPORT(UI_BASE) StepBuilder {
+   public:
+    StepBuilder();
+    ~StepBuilder();
+    StepBuilder(const StepBuilder& other) = delete;
+    void operator=(StepBuilder& other) = delete;
+
+    // Sets the unique identifier for this step. Required.
+    StepBuilder& SetElementID(ElementIdentifier element_id);
+
+    // Sets the context for the element; useful for setting up the initial
+    // element of the sequence if you do not know the context ahead of time.
+    // Prefer to use Builder::SetContext() if possible.
+    StepBuilder& SetContext(ElementContext context);
+
+    // Sets the type of step. Required.
+    StepBuilder& SetType(StepType step_type);
+
+    // Indicates that the specified element must be visible at the start of the
+    // step. Defaults to true for StepType::kActivated, false otherwise. Failure
+    // To meet this condition will abort the sequence.
+    StepBuilder& SetMustBeVisibleAtStart(bool must_be_visible);
+
+    // Indicates that the specified element must remain visible throughout the
+    // step once it has been shown. Defaults to true for StepType::kShown, false
+    // otherwise (and incompatible with StepType::kHidden). Failure to meet this
+    // condition will abort the sequence.
+    StepBuilder& SetMustRemainVisible(bool must_remain_visible);
+
+    // Sets the callback called at the start of the step.
+    StepBuilder& SetStartCallback(StepCallback start_callback);
+
+    // Sets the callback called at the end of the step. Guaranteed to be called
+    // if the start callback is called, before the start callback of the next
+    // step or the sequence aborted or completed callback. Also called if this
+    // object is destroyed while the step is still in-process.
+    StepBuilder& SetEndCallback(StepCallback end_callback);
+
+    // Builds the step. The builder will not be valid after calling Build().
+    std::unique_ptr<Step> Build();
+
+   private:
+    friend class InteractionSequence;
+    std::unique_ptr<Step> step_;
+  };
+
+  // Returns a step with the following values already set, typically used as the
+  // first step in a sequence (because the first element is usually present):
+  //   ElementID: element->identifier()
+  //   MustBeVisibleAtStart: true
+  //   MustRemainVisible: true
+  //
+  // This is a convenience method and also removes the need to call
+  // Builder::SetContext(). Specific framework implementations may provide
+  // wrappers around this method that allow direct conversion from framework UI
+  // elements (e.g. a views::View) to the target element.
+  static std::unique_ptr<Step> WithInitialElement(
+      TrackedElement* element,
+      StepCallback start_callback = StepCallback(),
+      StepCallback end_callback = StepCallback());
+
+  ~InteractionSequence();
+
+  // Starts the sequence. All of the elements in the sequence must belong to the
+  // same top-level application window (which includes menus, bubbles, etc.
+  // associated with that window).
+  void Start();
+
+ private:
+  explicit InteractionSequence(std::unique_ptr<Configuration> configuration);
+
+  // Callbacks from the ElementTracker.
+  void OnElementShown(TrackedElement* element);
+  void OnElementActivated(TrackedElement* element);
+  void OnElementHidden(TrackedElement* element);
+
+  // Callbacks used only during step transitions to cache certain events.
+  void OnElementActivatedDuringStepTransition(TrackedElement* element);
+  void OnElementHiddenDuringStepTransition(TrackedElement* element);
+
+  // A note on the next three methods - DoStepTransition(), StageNextStep(), and
+  // Abort(): To prevent re-entrancy issues, they must always be the final call
+  // in any method before it returns. This greatly simplifies the consistency
+  // checks and safeguards that need to be put into place to make sure we aren't
+  // making contradictory changes to state or calling callbacks in the wrong
+  // order.
+
+  // Perform the transition from the current step to the next step.
+  void DoStepTransition(TrackedElement* element);
+
+  // Looks at the next step to determine what needs to be done. Called at the
+  // start of the sequence and after each subsequent step starts.
+  void StageNextStep();
+
+  // Cancels the sequence and cleans up.
+  void Abort();
+
+  // Returns true (and does some sanity checking) if the sequence was aborted
+  // during the most recent callback.
+  bool AbortedDuringCallback() const;
+
+  // The following would be inline if not for the fact that the data that holds
+  // the values is an implementation detail.
+
+  // Returns the next step, or null if none.
+  Step* next_step();
+
+  // Returns the context for the current sequence.
+  ElementContext context() const;
+
+  bool missing_first_element_ = false;
+  bool started_ = false;
+  bool activated_during_callback_ = false;
+  bool processing_step_ = false;
+  std::unique_ptr<Step> current_step_;
+  std::unique_ptr<Configuration> configuration_;
+
+  // This is necessary because this object could be deleted during any callback,
+  // and we don't want to risk a UAF if that happens.
+  base::WeakPtrFactory<InteractionSequence> weak_factory_{this};
+};
+
+}  // namespace ui
+
+#endif  // UI_BASE_INTERACTION_INTERACTION_SEQUENCE_H_
diff --git a/ui/base/interaction/interaction_sequence_unittest.cc b/ui/base/interaction/interaction_sequence_unittest.cc
new file mode 100644
index 0000000..0bf772c
--- /dev/null
+++ b/ui/base/interaction/interaction_sequence_unittest.cc
@@ -0,0 +1,1413 @@
+// 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 "ui/base/interaction/interaction_sequence.h"
+
+#include "base/callback_forward.h"
+#include "base/test/bind.h"
+#include "base/test/mock_callback.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/interaction/element_identifier.h"
+#include "ui/base/interaction/element_test_util.h"
+#include "ui/base/interaction/element_tracker.h"
+
+namespace ui {
+
+namespace {
+
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestIdentifier1);
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestIdentifier2);
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestIdentifier3);
+const ElementContext kTestContext1(1);
+const ElementContext kTestContext2(2);
+
+#define DECLARE_STEP_CALLBACK(Name)                           \
+  base::MockCallback<InteractionSequence::StepCallback> Name; \
+  EXPECT_CALL(Name, Run).Times(0)
+
+#define DECLARE_COMPLETED_CALLBACK(Name)                           \
+  base::MockCallback<InteractionSequence::CompletedCallback> Name; \
+  EXPECT_CALL(Name, Run).Times(0)
+
+#define DECLARE_ABORTED_CALLBACK(Name)                           \
+  base::MockCallback<InteractionSequence::AbortedCallback> Name; \
+  EXPECT_CALL(Name, Run).Times(0)
+
+}  // namespace
+
+TEST(InteractionSequenceTest, ConstructAndDestructContext) {
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetContext(kTestContext1)
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(kTestIdentifier1)
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  tracker.reset();
+}
+
+TEST(InteractionSequenceTest, ConstructAndDestructWithWithInitialElement) {
+  TestElement element(kTestIdentifier1, kTestContext1);
+  element.Show();
+  auto tracker = InteractionSequence::Builder()
+                     .AddStep(InteractionSequence::WithInitialElement(&element))
+                     .Build();
+  tracker.reset();
+}
+
+TEST(InteractionSequenceTest, StartAndDestruct) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElement element(kTestIdentifier1, kTestContext1);
+  element.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(aborted, Run, tracker.reset());
+}
+
+TEST(InteractionSequenceTest, StartFailsIfWithInitialElementNotVisible) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElement element(kTestIdentifier1, kTestContext1);
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  EXPECT_CALL_IN_SCOPE(aborted, Run, tracker->Start());
+}
+
+TEST(InteractionSequenceTest,
+     StartFailsIfWithInitialElementNotVisibleIdentifierOnly) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElement element(kTestIdentifier1, kTestContext1);
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .SetContext(element.context())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetMustBeVisibleAtStart(true)
+                       .SetMustRemainVisible(true)
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  EXPECT_CALL_IN_SCOPE(aborted, Run, tracker->Start());
+}
+
+TEST(InteractionSequenceTest, AbortIfWithInitialElementHiddenBeforeStart) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElementPtr element =
+      std::make_unique<TestElement>(kTestIdentifier1, kTestContext1);
+  element->Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(element.get()))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element->identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  element.reset();
+  EXPECT_CALL_IN_SCOPE(aborted, Run, tracker->Start());
+}
+
+TEST(InteractionSequenceTest,
+     AbortIfWithInitialElementHiddenBeforeStartIdentifierOnly) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElementPtr element =
+      std::make_unique<TestElement>(kTestIdentifier1, kTestContext1);
+  element->Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .SetContext(element->context())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element->identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetMustBeVisibleAtStart(true)
+                       .SetMustRemainVisible(true)
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element->identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  element.reset();
+  EXPECT_CALL_IN_SCOPE(aborted, Run, tracker->Start());
+}
+
+TEST(InteractionSequenceTest, HideWithInitialElementAborts) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElement element(kTestIdentifier1, kTestContext1);
+  element.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(aborted, Run, element.Hide());
+}
+
+TEST(InteractionSequenceTest,
+     HideWithInitialElementDoesNotAbortIfMustRemainVisibleIsFalse) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElement element(kTestIdentifier1, kTestContext1);
+  element.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .SetContext(element.context())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetMustBeVisibleAtStart(true)
+                       .SetMustRemainVisible(false)
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  tracker->Start();
+  element.Hide();
+  EXPECT_CALL_IN_SCOPE(aborted, Run, tracker.reset());
+}
+
+TEST(InteractionSequenceTest, TransitionOnActivated) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  TestElement element(kTestIdentifier1, kTestContext1);
+  element.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .Build();
+  tracker->Start();
+  EXPECT_CALL(step, Run(&element, element.identifier(),
+                        InteractionSequence::StepType::kActivated))
+      .Times(1);
+  EXPECT_CALL(completed, Run).Times(1);
+  element.Activate();
+}
+
+TEST(InteractionSequenceTest, TransitionOnElementShown) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .Build();
+  tracker->Start();
+  EXPECT_CALL(step, Run(&element2, element2.identifier(),
+                        InteractionSequence::StepType::kShown))
+      .Times(1);
+  EXPECT_CALL(completed, Run).Times(1);
+  element2.Show();
+}
+
+TEST(InteractionSequenceTest, TransitionFailsOnElementShownIfMustBeVisible) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step.Get())
+                       .SetMustBeVisibleAtStart(true)
+                       .Build())
+          .Build();
+  EXPECT_CALL(aborted, Run(nullptr, element2.identifier(),
+                           InteractionSequence::StepType::kShown))
+      .Times(1);
+  tracker->Start();
+}
+
+TEST(InteractionSequenceTest, TransitionOnSameElementHidden) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetMustRemainVisible(false)
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kHidden)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .Build();
+  tracker->Start();
+  element2.Show();
+  EXPECT_CALL(step, Run(nullptr, element2.identifier(),
+                        InteractionSequence::StepType::kHidden))
+      .Times(1);
+  EXPECT_CALL(completed, Run).Times(1);
+  element2.Hide();
+}
+
+TEST(InteractionSequenceTest, TransitionOnOtherElementHidden) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+  element2.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kHidden)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .Build();
+  tracker->Start();
+  EXPECT_CALL(step, Run(nullptr, element2.identifier(),
+                        InteractionSequence::StepType::kHidden))
+      .Times(1);
+  EXPECT_CALL(completed, Run).Times(1);
+  element2.Hide();
+}
+
+TEST(InteractionSequenceTest, TransitionOnOtherElementAlreadyHidden) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kHidden)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .Build();
+  EXPECT_CALL(step, Run(testing::_, element2.identifier(),
+                        InteractionSequence::StepType::kHidden))
+      .Times(1);
+  EXPECT_CALL(completed, Run).Times(1);
+  tracker->Start();
+}
+
+TEST(InteractionSequenceTest, FailOnOtherElementAlreadyHiddenIfMustBeVisible) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kHidden)
+                       .SetMustBeVisibleAtStart(true)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .Build();
+  EXPECT_CALL(aborted, Run(nullptr, element2.identifier(),
+                           InteractionSequence::StepType::kHidden))
+      .Times(1);
+  tracker->Start();
+}
+
+TEST(InteractionSequenceTest, NoWithInitialElementTransitionsOnActivation) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  TestElement element(kTestIdentifier1, kTestContext1);
+  element.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .SetContext(element.context())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetMustBeVisibleAtStart(false)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .Build();
+  tracker->Start();
+  EXPECT_CALL(step, Run(&element, element.identifier(),
+                        InteractionSequence::StepType::kActivated))
+      .Times(1);
+  EXPECT_CALL(completed, Run).Times(1);
+  element.Activate();
+}
+
+TEST(InteractionSequenceTest, NoWithInitialElementTransitionsOnShown) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  TestElement element(kTestIdentifier1, kTestContext1);
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .SetContext(element.context())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .Build();
+  tracker->Start();
+  EXPECT_CALL(step, Run(&element, element.identifier(),
+                        InteractionSequence::StepType::kShown))
+      .Times(1);
+  EXPECT_CALL(completed, Run).Times(1);
+  element.Show();
+}
+
+TEST(InteractionSequenceTest, StepEndCallbackCalled) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step_start);
+  DECLARE_STEP_CALLBACK(step_end);
+  TestElement element(kTestIdentifier1, kTestContext1);
+  element.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step_start.Get())
+                       .SetEndCallback(step_end.Get())
+                       .Build())
+          .Build();
+  tracker->Start();
+  EXPECT_CALLS_IN_SCOPE_3(step_start,
+                          Run(&element, element.identifier(),
+                              InteractionSequence::StepType::kActivated),
+                          step_end,
+                          Run(&element, element.identifier(),
+                              InteractionSequence::StepType::kActivated),
+                          completed, Run, element.Activate());
+}
+
+TEST(InteractionSequenceTest, StepEndCallbackCalledForInitialStep) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step_start);
+  DECLARE_STEP_CALLBACK(step_end);
+  DECLARE_STEP_CALLBACK(step2);
+  TestElement element(kTestIdentifier1, kTestContext1);
+  element.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(
+              &element, step_start.Get(), step_end.Get()))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step2.Get())
+                       .Build())
+          .Build();
+  EXPECT_CALL_IN_SCOPE(step_start, Run, tracker->Start());
+  EXPECT_CALLS_IN_SCOPE_3(step_end,
+                          Run(&element, element.identifier(),
+                              InteractionSequence::StepType::kShown),
+                          step2,
+                          Run(&element, element.identifier(),
+                              InteractionSequence::StepType::kActivated),
+                          completed, Run, element.Activate());
+}
+
+TEST(InteractionSequenceTest, MultipleStepsComplete) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_start);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  TestElement element3(kTestIdentifier3, kTestContext1);
+  element1.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step1_start.Get())
+                       .SetEndCallback(step1_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step2_start.Get())
+                       .SetEndCallback(step2_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element3.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .Build())
+          .Build();
+
+  tracker->Start();
+
+  EXPECT_CALL_IN_SCOPE(step1_start, Run, element2.Show());
+
+  EXPECT_CALLS_IN_SCOPE_2(step1_end, Run, step2_start, Run,
+                          element2.Activate());
+
+  EXPECT_CALLS_IN_SCOPE_2(step2_end, Run, completed, Run, element3.Show());
+}
+
+TEST(InteractionSequenceTest, MultipleStepsWithImmediateTransition) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_start);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  TestElement element3(kTestIdentifier3, kTestContext1);
+  element1.Show();
+  element3.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step1_start.Get())
+                       .SetEndCallback(step1_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step2_start.Get())
+                       .SetEndCallback(step2_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element3.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .Build())
+          .Build();
+
+  tracker->Start();
+
+  EXPECT_CALL_IN_SCOPE(step1_start, Run, element2.Show());
+
+  // Since element3 is already visible, we skip straight to the end.
+  {
+    testing::InSequence in_sequence;
+    EXPECT_CALL(step1_end, Run).Times(1);
+    EXPECT_CALL(step2_start, Run).Times(1);
+    EXPECT_CALL(step2_end, Run).Times(1);
+    EXPECT_CALL(completed, Run).Times(1);
+  }
+  element2.Activate();
+}
+
+TEST(InteractionSequenceTest, CancelMidSequenceWhenViewHidden) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_start);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  TestElement element3(kTestIdentifier3, kTestContext1);
+  element1.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step1_start.Get())
+                       .SetEndCallback(step1_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       // Specify that this element must remain visible:
+                       .SetMustRemainVisible(true)
+                       .SetStartCallback(step2_start.Get())
+                       .SetEndCallback(step2_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element3.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .Build())
+          .Build();
+
+  tracker->Start();
+
+  EXPECT_CALL_IN_SCOPE(step1_start, Run, element2.Show());
+
+  EXPECT_CALLS_IN_SCOPE_2(step1_end, Run, step2_start, Run,
+                          element2.Activate());
+
+  EXPECT_CALLS_IN_SCOPE_2(step2_end, Run, aborted,
+                          Run(testing::_, element2.identifier(),
+                              InteractionSequence::StepType::kActivated),
+                          element2.Hide());
+}
+
+TEST(InteractionSequenceTest, DontCancelIfViewDoesNotNeedToRemainVisible) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_start);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  TestElement element3(kTestIdentifier3, kTestContext1);
+  element1.Show();
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step1_start.Get())
+                       .SetEndCallback(step1_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       // Specify that this element need not remain visible:
+                       .SetMustRemainVisible(false)
+                       .SetStartCallback(step2_start.Get())
+                       .SetEndCallback(step2_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element3.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .Build())
+          .Build();
+
+  tracker->Start();
+
+  EXPECT_CALL_IN_SCOPE(step1_start, Run, element2.Show());
+
+  EXPECT_CALLS_IN_SCOPE_2(step1_end, Run, step2_start, Run,
+                          element2.Activate());
+
+  element2.Hide();
+
+  EXPECT_CALLS_IN_SCOPE_2(step2_end, Run, completed, Run, element3.Show());
+}
+
+TEST(InteractionSequenceTest,
+     MultipleSequencesInDifferentContextsOneCompletes) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  DECLARE_ABORTED_CALLBACK(aborted2);
+  DECLARE_COMPLETED_CALLBACK(completed2);
+  DECLARE_STEP_CALLBACK(step2);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier1, kTestContext2);
+  element1.Show();
+  element2.Show();
+
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element1.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .Build();
+
+  auto tracker2 =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted2.Get())
+          .SetCompletedCallback(completed2.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element2))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step2.Get())
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  tracker2->Start();
+
+  EXPECT_CALLS_IN_SCOPE_2(step,
+                          Run(&element1, element1.identifier(),
+                              InteractionSequence::StepType::kActivated),
+                          completed, Run, element1.Activate());
+
+  EXPECT_CALL_IN_SCOPE(aborted2, Run, element2.Hide());
+}
+
+TEST(InteractionSequenceTest,
+     MultipleSequencesInDifferentContextsBothComplete) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  DECLARE_ABORTED_CALLBACK(aborted2);
+  DECLARE_COMPLETED_CALLBACK(completed2);
+  DECLARE_STEP_CALLBACK(step2);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier1, kTestContext2);
+  element1.Show();
+  element2.Show();
+
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element1.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .Build();
+
+  auto tracker2 =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted2.Get())
+          .SetCompletedCallback(completed2.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element2))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step2.Get())
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  tracker2->Start();
+
+  EXPECT_CALLS_IN_SCOPE_2(step,
+                          Run(&element1, element1.identifier(),
+                              InteractionSequence::StepType::kActivated),
+                          completed, Run, element1.Activate());
+
+  EXPECT_CALLS_IN_SCOPE_2(step2,
+                          Run(&element2, element2.identifier(),
+                              InteractionSequence::StepType::kActivated),
+                          completed2, Run, element2.Activate());
+}
+
+// These tests verify that events sent during callbacks (as might be used by an
+// interactive UI test powered by an InteractionSequence) do not break the
+// sequence.
+
+TEST(InteractionSequenceTest, ShowDuringCallback) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { element2.Show(); };
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element1.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(
+                           base::BindLambdaForTesting(std::move(callback)))
+                       .SetEndCallback(step1_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step2_start.Get())
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALLS_IN_SCOPE_3(step1_end, Run, step2_start, Run, completed, Run,
+                          element1.Activate());
+}
+
+TEST(InteractionSequenceTest, HideDuringCallback) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+  element2.Show();
+
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { element2.Hide(); };
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element1.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(
+                           base::BindLambdaForTesting(std::move(callback)))
+                       .SetEndCallback(step1_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kHidden)
+                       .SetStartCallback(step2_start.Get())
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALLS_IN_SCOPE_3(step1_end, Run, step2_start, Run, completed, Run,
+                          element1.Activate());
+}
+
+TEST(InteractionSequenceTest, ActivateDuringCallbackDifferentView) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+  element2.Show();
+
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { element2.Activate(); };
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element1.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(
+                           base::BindLambdaForTesting(std::move(callback)))
+                       .SetEndCallback(step1_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step2_start.Get())
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALLS_IN_SCOPE_3(step1_end, Run, step2_start, Run, completed, Run,
+                          element1.Activate());
+}
+
+TEST(InteractionSequenceTest, ActivateDuringCallbackSameView) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { element2.Activate(); };
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(
+                           base::BindLambdaForTesting(std::move(callback)))
+                       .SetEndCallback(step1_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step2_start.Get())
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALLS_IN_SCOPE_3(step1_end, Run, step2_start, Run, completed, Run,
+                          element2.Show());
+}
+
+TEST(InteractionSequenceTest, HideAfterActivateDoesntAbort) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  TestElement element3(kTestIdentifier3, kTestContext1);
+  element1.Show();
+  element3.Show();
+
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) {
+    element2.Activate();
+    element2.Hide();
+  };
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(
+                           base::BindLambdaForTesting(std::move(callback)))
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element3.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(completed, Run, element2.Show());
+}
+
+TEST(InteractionSequenceTest, HideDuringStepStartedCallbackAborts) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { element2.Hide(); };
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(
+                           base::BindLambdaForTesting(std::move(callback)))
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(aborted, Run, element2.Show());
+}
+
+TEST(InteractionSequenceTest, HideDuringStepEndedCallbackAborts) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { element2.Hide(); };
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(
+              &element1, InteractionSequence::StepCallback(),
+              base::BindLambdaForTesting(std::move(callback))))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetMustBeVisibleAtStart(true)
+                       .SetStartCallback(step2_start.Get())
+                       .SetEndCallback(step2_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+
+  EXPECT_CALL_IN_SCOPE(aborted, Run, tracker->Start());
+}
+
+TEST(InteractionSequenceTest, ElementHiddenDuringFinalStepStart) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { element2.Hide(); };
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetMustRemainVisible(false)
+                       .SetStartCallback(base::BindLambdaForTesting(callback))
+                       .SetEndCallback(step_end.Get())
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALLS_IN_SCOPE_2(step_end,
+                          Run(nullptr, element2.identifier(),
+                              InteractionSequence::StepType::kShown),
+                          completed, Run, element2.Show());
+}
+
+TEST(InteractionSequenceTest, ElementHiddenDuringFinalStepEnd) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { element2.Hide(); };
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetMustRemainVisible(false)
+                       .SetEndCallback(base::BindLambdaForTesting(callback))
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(completed, Run, element2.Show());
+}
+
+TEST(InteractionSequenceTest, ElementHiddenDuringStepEndDuringAbort) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { element2.Hide(); };
+  auto tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetEndCallback(base::BindLambdaForTesting(callback))
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  element2.Show();
+
+  // First parameter will be null because during the delete the step end
+  // callback will hide the element, which happens before the abort callback is
+  // called.
+  EXPECT_CALL_IN_SCOPE(aborted,
+                       Run(nullptr, element2.identifier(),
+                           InteractionSequence::StepType::kShown),
+                       tracker.reset());
+}
+
+TEST(InteractionSequenceTest, SequenceDestroyedDuringInitialStepStartCallback) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  element1.Show();
+
+  std::unique_ptr<InteractionSequence> tracker;
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { tracker.reset(); };
+  tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(
+              &element1, base::BindLambdaForTesting(callback), step1_end.Get()))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element1.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+
+  EXPECT_CALLS_IN_SCOPE_2(step1_end, Run, aborted, Run, tracker->Start());
+}
+
+TEST(InteractionSequenceTest, SequenceDestroyedDuringInitialStepEndCallback) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  element1.Show();
+
+  std::unique_ptr<InteractionSequence> tracker;
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { tracker.reset(); };
+  tracker = InteractionSequence::Builder()
+                .SetAbortedCallback(aborted.Get())
+                .SetCompletedCallback(completed.Get())
+                .AddStep(InteractionSequence::WithInitialElement(
+                    &element1, InteractionSequence::StepCallback(),
+                    base::BindLambdaForTesting(callback)))
+                .AddStep(InteractionSequence::StepBuilder()
+                             .SetElementID(element1.identifier())
+                             .SetType(InteractionSequence::StepType::kActivated)
+                             .SetStartCallback(step2_start.Get())
+                             .SetEndCallback(step2_end.Get())
+                             .Build())
+                .Build();
+
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(aborted, Run, element1.Activate());
+}
+
+TEST(InteractionSequenceTest, SequenceDestroyedDuringInitialStepAbort) {
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_start);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  element1.Show();
+
+  std::unique_ptr<InteractionSequence> tracker;
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { tracker.reset(); };
+  tracker = InteractionSequence::Builder()
+                .SetAbortedCallback(base::BindLambdaForTesting(callback))
+                .SetCompletedCallback(completed.Get())
+                .AddStep(InteractionSequence::WithInitialElement(
+                    &element1, step1_start.Get(), step1_end.Get()))
+                .AddStep(InteractionSequence::StepBuilder()
+                             .SetElementID(element1.identifier())
+                             .SetType(InteractionSequence::StepType::kActivated)
+                             .SetStartCallback(step2_start.Get())
+                             .SetEndCallback(step2_end.Get())
+                             .Build())
+                .Build();
+
+  EXPECT_CALL_IN_SCOPE(step1_start, Run, tracker->Start());
+  EXPECT_CALL_IN_SCOPE(step1_end, Run, element1.Hide());
+  EXPECT_FALSE(tracker);
+}
+
+TEST(InteractionSequenceTest, SequenceDestroyedDuringMidSequenceStepStart) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  std::unique_ptr<InteractionSequence> tracker;
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { tracker.reset(); };
+  tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(base::BindLambdaForTesting(callback))
+                       .SetEndCallback(step1_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step2_start.Get())
+                       .SetEndCallback(step2_end.Get())
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALLS_IN_SCOPE_2(step1_end, Run, aborted, Run, element2.Show());
+  EXPECT_FALSE(tracker);
+}
+
+TEST(InteractionSequenceTest, SequenceDestroyedDuringMidSequenceStepEnd) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_start);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  std::unique_ptr<InteractionSequence> tracker;
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { tracker.reset(); };
+  tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step1_start.Get())
+                       .SetEndCallback(base::BindLambdaForTesting(callback))
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step2_start.Get())
+                       .SetEndCallback(step2_end.Get())
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(step1_start, Run, element2.Show());
+  EXPECT_CALL_IN_SCOPE(aborted, Run, element2.Activate());
+  EXPECT_FALSE(tracker);
+}
+
+TEST(InteractionSequenceTest, SequenceDestroyedDuringMidSequenceAbort) {
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_start);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  std::unique_ptr<InteractionSequence> tracker;
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { tracker.reset(); };
+  tracker = InteractionSequence::Builder()
+                .SetAbortedCallback(base::BindLambdaForTesting(callback))
+                .SetCompletedCallback(completed.Get())
+                .AddStep(InteractionSequence::WithInitialElement(&element1))
+                .AddStep(InteractionSequence::StepBuilder()
+                             .SetElementID(element2.identifier())
+                             .SetType(InteractionSequence::StepType::kShown)
+                             .SetStartCallback(step1_start.Get())
+                             .SetEndCallback(step1_end.Get())
+                             .Build())
+                .AddStep(InteractionSequence::StepBuilder()
+                             .SetElementID(element2.identifier())
+                             .SetType(InteractionSequence::StepType::kActivated)
+                             .SetStartCallback(step2_start.Get())
+                             .SetEndCallback(step2_end.Get())
+                             .Build())
+                .Build();
+
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(step1_start, Run, element2.Show());
+  EXPECT_CALL_IN_SCOPE(step1_end, Run, element2.Hide());
+  EXPECT_FALSE(tracker);
+}
+
+TEST(InteractionSequenceTest, SequenceDestroyedDuringFinalStepEnd) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step1_start);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  std::unique_ptr<InteractionSequence> tracker;
+  auto callback = [&](TrackedElement*, ElementIdentifier,
+                      InteractionSequence::StepType) { tracker.reset(); };
+  tracker =
+      InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequence::WithInitialElement(&element1))
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step1_start.Get())
+                       .SetEndCallback(step1_end.Get())
+                       .Build())
+          .AddStep(InteractionSequence::StepBuilder()
+                       .SetElementID(element2.identifier())
+                       .SetType(InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step2_start.Get())
+                       .SetEndCallback(base::BindLambdaForTesting(callback))
+                       .Build())
+          .Build();
+
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(step1_start, Run, element2.Show());
+  EXPECT_CALLS_IN_SCOPE_3(step1_end, Run, step2_start, Run, completed, Run,
+                          element2.Activate());
+  EXPECT_FALSE(tracker);
+}
+
+TEST(InteractionSequenceTest, SequenceDestroyedDuringCompleted) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_STEP_CALLBACK(step1_start);
+  DECLARE_STEP_CALLBACK(step1_end);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  TestElement element1(kTestIdentifier1, kTestContext1);
+  TestElement element2(kTestIdentifier2, kTestContext1);
+  element1.Show();
+
+  std::unique_ptr<InteractionSequence> tracker;
+  auto callback = [&]() { tracker.reset(); };
+  tracker = InteractionSequence::Builder()
+                .SetAbortedCallback(aborted.Get())
+                .SetCompletedCallback(base::BindLambdaForTesting(callback))
+                .AddStep(InteractionSequence::WithInitialElement(&element1))
+                .AddStep(InteractionSequence::StepBuilder()
+                             .SetElementID(element2.identifier())
+                             .SetType(InteractionSequence::StepType::kShown)
+                             .SetStartCallback(step1_start.Get())
+                             .SetEndCallback(step1_end.Get())
+                             .Build())
+                .AddStep(InteractionSequence::StepBuilder()
+                             .SetElementID(element2.identifier())
+                             .SetType(InteractionSequence::StepType::kActivated)
+                             .SetStartCallback(step2_start.Get())
+                             .SetEndCallback(step2_end.Get())
+                             .Build())
+                .Build();
+
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(step1_start, Run, element2.Show());
+  EXPECT_CALLS_IN_SCOPE_3(step1_end, Run, step2_start, Run, step2_end, Run,
+                          element2.Activate());
+  EXPECT_FALSE(tracker);
+}
+
+}  // namespace ui
diff --git a/ui/base/x/visual_picker_glx.h b/ui/base/x/visual_picker_glx.h
index cd4772c..39b6d670 100644
--- a/ui/base/x/visual_picker_glx.h
+++ b/ui/base/x/visual_picker_glx.h
@@ -54,8 +54,8 @@
 
   x11::Connection* const connection_;
 
-  x11::VisualId system_visual_;
-  x11::VisualId rgba_visual_;
+  x11::VisualId system_visual_{};
+  x11::VisualId rgba_visual_{};
 
   std::unique_ptr<base::flat_map<gfx::BufferFormat, x11::Glx::FbConfig>>
       config_map_;
diff --git a/ui/events/ozone/evdev/event_device_info.cc b/ui/events/ozone/evdev/event_device_info.cc
index 10b196e5..03628ff 100644
--- a/ui/events/ozone/evdev/event_device_info.cc
+++ b/ui/events/ozone/evdev/event_device_info.cc
@@ -498,8 +498,7 @@
 }
 
 bool EventDeviceInfo::IsMicrophoneMuteSwitchDevice() const {
-  return HasSwEvent(SW_MUTE_DEVICE) && device_type_ == INPUT_DEVICE_INTERNAL &&
-         name_ == "mic_mute_switch";
+  return HasSwEvent(SW_MUTE_DEVICE) && device_type_ == INPUT_DEVICE_INTERNAL;
 }
 
 bool IsInKeyboardBlockList(input_id input_id_) {
diff --git a/ui/ozone/platform/wayland/host/wayland_event_source.cc b/ui/ozone/platform/wayland/host/wayland_event_source.cc
index 385f9fb2..6e23105 100644
--- a/ui/ozone/platform/wayland/host/wayland_event_source.cc
+++ b/ui/ozone/platform/wayland/host/wayland_event_source.cc
@@ -217,10 +217,18 @@
 
   int flags = pointer_flags_ | keyboard_modifiers_;
 
+  static constexpr bool supports_trackpad_kinetic_scrolling =
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+      true;
+#else
+      false;
+#endif
+
   // Dispatch Fling event if pointer.axis_stop is notified and the recent
   // pointer.axis events meets the criteria to start fling scroll.
   if (current_pointer_frame_.dx == 0 && current_pointer_frame_.dy == 0 &&
-      current_pointer_frame_.is_axis_stop) {
+      current_pointer_frame_.is_axis_stop &&
+      supports_trackpad_kinetic_scrolling) {
     gfx::Vector2dF initial_velocity = ComputeFlingVelocity();
     float vx = initial_velocity.x();
     float vy = initial_velocity.y();
diff --git a/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc b/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc
index 56030e2..ce0ef4b 100644
--- a/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc
@@ -308,6 +308,7 @@
   Mock::VerifyAndClearExpectations(pointer_);
 }
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
 TEST_P(WaylandPointerTest, FlingVertical) {
   uint32_t serial = 0;
   uint32_t time = 1001;
@@ -539,6 +540,7 @@
   EXPECT_GT(std::abs(scroll_event->x_offset_ordinal()),
             std::abs(scroll_event->y_offset_ordinal()));
 }
+#endif
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandPointerTest,
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 58c72f1..00a2bde 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -198,6 +198,7 @@
     "image_model_utils.h",
     "input_event_activation_protector.h",
     "interaction/element_tracker_views.h",
+    "interaction/interaction_sequence_views.h",
     "layout/animating_layout_manager.h",
     "layout/box_layout.h",
     "layout/box_layout_view.h",
@@ -402,6 +403,7 @@
     "image_model_utils.cc",
     "input_event_activation_protector.cc",
     "interaction/element_tracker_views.cc",
+    "interaction/interaction_sequence_views.cc",
     "layout/animating_layout_manager.cc",
     "layout/box_layout.cc",
     "layout/box_layout_view.cc",
@@ -1157,6 +1159,7 @@
     "focus/focus_traversal_unittest.cc",
     "image_model_utils_unittest.cc",
     "interaction/element_tracker_views_unittest.cc",
+    "interaction/interaction_sequence_views_unittest.cc",
     "layout/animating_layout_manager_unittest.cc",
     "layout/box_layout_unittest.cc",
     "layout/box_layout_view_unittest.cc",
diff --git a/ui/views/interaction/element_tracker_views.cc b/ui/views/interaction/element_tracker_views.cc
index c37e3eac..7757b892 100644
--- a/ui/views/interaction/element_tracker_views.cc
+++ b/ui/views/interaction/element_tracker_views.cc
@@ -14,6 +14,7 @@
 #include "ui/base/interaction/element_identifier.h"
 #include "ui/base/interaction/element_tracker.h"
 #include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
 #include "ui/views/view_observer.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/widget/widget_observer.h"
@@ -37,15 +38,14 @@
 
 }  // namespace
 
-ElementTrackerElementViews::ElementTrackerElementViews(
-    View* view,
-    ui::ElementIdentifier identifier,
-    ui::ElementContext context)
-    : ElementTrackerElement(identifier, context), view_(view) {}
+TrackedElementViews::TrackedElementViews(View* view,
+                                         ui::ElementIdentifier identifier,
+                                         ui::ElementContext context)
+    : TrackedElement(identifier, context), view_(view) {}
 
-ElementTrackerElementViews::~ElementTrackerElementViews() = default;
+TrackedElementViews::~TrackedElementViews() = default;
 
-DEFINE_ELEMENT_TRACKER_METADATA(ElementTrackerElementViews)
+DEFINE_ELEMENT_TRACKER_METADATA(TrackedElementViews)
 
 class ElementTrackerViews::ElementDataViews : public ViewObserver,
                                               public WidgetObserver {
@@ -82,6 +82,12 @@
       tracker_->element_data_.erase(id_);
   }
 
+  TrackedElementViews* GetElementForView(View* view) {
+    const auto it = view_data_lookup_.find(view);
+    DCHECK(it != view_data_lookup_.end());
+    return it->second->element.get();
+  }
+
   void NotifyViewActivated(View* view) {
     const auto it = view_data_lookup_.find(view);
     DCHECK(it != view_data_lookup_.end());
@@ -114,7 +120,7 @@
     bool visible() const { return static_cast<bool>(element); }
     View* const view;
     ui::ElementContext context;
-    std::unique_ptr<ElementTrackerElementViews> element;
+    std::unique_ptr<TrackedElementViews> element;
   };
 
   using ViewDataList = std::list<ViewData>;
@@ -151,7 +157,7 @@
         it->second->context && IsViewVisibleToUser(view, force_widget_visible);
     if (visible && !was_visible) {
       data.element =
-          std::make_unique<ElementTrackerElementViews>(view, id_, data.context);
+          std::make_unique<TrackedElementViews>(view, id_, data.context);
       ui::ElementTracker::GetFrameworkDelegate()->NotifyElementShown(
           data.element.get());
     } else if (!visible && was_visible) {
@@ -188,6 +194,16 @@
                 : ui::ElementContext();
 }
 
+TrackedElementViews* ElementTrackerViews::GetElementForView(View* view) {
+  const auto identifier = view->GetProperty(kElementIdentifierKey);
+  if (!identifier)
+    return nullptr;
+  const auto it = element_data_.find(identifier);
+  if (it == element_data_.end())
+    return nullptr;
+  return it->second->GetElementForView(view);
+}
+
 void ElementTrackerViews::RegisterView(ui::ElementIdentifier element_id,
                                        View* view) {
   auto it = element_data_.find(element_id);
diff --git a/ui/views/interaction/element_tracker_views.h b/ui/views/interaction/element_tracker_views.h
index 389172d..7da7fad 100644
--- a/ui/views/interaction/element_tracker_views.h
+++ b/ui/views/interaction/element_tracker_views.h
@@ -20,14 +20,13 @@
 
 class View;
 
-// Wraps a View in an ui::ElementTrackerElement.
-class VIEWS_EXPORT ElementTrackerElementViews
-    : public ui::ElementTrackerElement {
+// Wraps a View in an ui::TrackedElement.
+class VIEWS_EXPORT TrackedElementViews : public ui::TrackedElement {
  public:
-  ElementTrackerElementViews(View* view,
-                             ui::ElementIdentifier identifier,
-                             ui::ElementContext context);
-  ~ElementTrackerElementViews() override;
+  TrackedElementViews(View* view,
+                      ui::ElementIdentifier identifier,
+                      ui::ElementContext context);
+  ~TrackedElementViews() override;
 
   View* view() { return view_; }
   const View* view() const { return view_; }
@@ -38,7 +37,7 @@
   View* const view_;
 };
 
-// Manages ElementTrackerElements associated with View objects.
+// Manages TrackedElements associated with View objects.
 class VIEWS_EXPORT ElementTrackerViews : private WidgetObserver {
  public:
   using ViewList = std::vector<View*>;
@@ -51,6 +50,13 @@
   // application window).
   static ui::ElementContext GetContextForView(View* view);
 
+  // Returns the corresponding TrackedElementViews for the given view, or
+  // null if none exists. Note that views which are not visible or not added to
+  // a Widget may not have associated elements, and that the returned object
+  // may be transient.
+  TrackedElementViews* GetElementForView(View* view);
+  const TrackedElementViews* GetElementForView(const View* view) const;
+
   // Called by View after the kUniqueElementKey property is set.
   void RegisterView(ui::ElementIdentifier element_id, View* view);
 
diff --git a/ui/views/interaction/element_tracker_views_unittest.cc b/ui/views/interaction/element_tracker_views_unittest.cc
index 79f5c29..d5ba875 100644
--- a/ui/views/interaction/element_tracker_views_unittest.cc
+++ b/ui/views/interaction/element_tracker_views_unittest.cc
@@ -33,8 +33,21 @@
 
 enum ElementEventType { kShown, kActivated, kHidden };
 
+View* ElementToView(ui::TrackedElement* element) {
+  auto* const view_element = element->AsA<TrackedElementViews>();
+  return view_element ? view_element->view() : nullptr;
+}
+
+// Watches events on the ElementTracker and converts the resulting values back
+// into Views from the original ui::TrackedElement objects. Monitoring
+// callbacks in this way could be done with gmock but the boilerplate would be
+// unfortunately complicated (for some events, the correct parameters are not
+// known until after the call is made, since the call itself might create the
+// element in question). So instead we use this helper class.
 class ElementEventWatcher {
  public:
+  // Watches the specified `event_type` on Views with identifier `id` in
+  // `context`.
   ElementEventWatcher(ui::ElementIdentifier id,
                       ui::ElementContext context,
                       ElementEventType event_type)
@@ -61,9 +74,9 @@
   View* last_view() { return last_view_; }
 
  private:
-  void OnEvent(ui::ElementTrackerElement* element) {
+  void OnEvent(ui::TrackedElement* element) {
     EXPECT_EQ(id_.raw_value(), element->identifier().raw_value());
-    last_view_ = element->AsA<ElementTrackerElementViews>()->view();
+    last_view_ = ElementToView(element);
     ++event_count_;
   }
 
@@ -73,17 +86,12 @@
   View* last_view_ = nullptr;
 };
 
-View* ElementToView(ui::ElementTrackerElement* element) {
-  auto* const view_element = element->AsA<ElementTrackerElementViews>();
-  return view_element ? view_element->view() : nullptr;
-}
-
 ElementTrackerViews::ViewList ElementsToViews(
     ui::ElementTracker::ElementList elements) {
   ElementTrackerViews::ViewList result;
   std::transform(elements.begin(), elements.end(), std::back_inserter(result),
-                 [](ui::ElementTrackerElement* element) {
-                   return element->AsA<ElementTrackerElementViews>()->view();
+                 [](ui::TrackedElement* element) {
+                   return element->AsA<TrackedElementViews>()->view();
                  });
   return result;
 }
@@ -381,7 +389,7 @@
   auto* button = widget_->SetContentsView(std::move(button_ptr));
   EXPECT_EQ(button, ui::ElementTracker::GetElementTracker()
                         ->GetUniqueElement(kTestElementID, context())
-                        ->AsA<ElementTrackerElementViews>()
+                        ->AsA<TrackedElementViews>()
                         ->view());
 
   // Hiding the view will make the view not findable through the base element
@@ -394,7 +402,7 @@
   button->SetVisible(true);
   EXPECT_EQ(button, ui::ElementTracker::GetElementTracker()
                         ->GetUniqueElement(kTestElementID, context())
-                        ->AsA<ElementTrackerElementViews>()
+                        ->AsA<TrackedElementViews>()
                         ->view());
 
   // Once the view is destroyed, however, the result should be null again.
@@ -408,7 +416,7 @@
   button->SetProperty(kElementIdentifierKey, kTestElementID);
   EXPECT_EQ(button, ui::ElementTracker::GetElementTracker()
                         ->GetUniqueElement(kTestElementID, context())
-                        ->AsA<ElementTrackerElementViews>()
+                        ->AsA<TrackedElementViews>()
                         ->view());
 
   // When the view is deleted, the result once again becomes null.
@@ -468,7 +476,7 @@
   // The first button should be returned.
   EXPECT_EQ(button, ui::ElementTracker::GetElementTracker()
                         ->GetFirstMatchingElement(kTestElementID, context())
-                        ->AsA<ElementTrackerElementViews>()
+                        ->AsA<TrackedElementViews>()
                         ->view());
 
   // Set the buttons' visibility; this should change whether the element tracker
@@ -476,7 +484,7 @@
   button->SetVisible(false);
   EXPECT_EQ(button2, ui::ElementTracker::GetElementTracker()
                          ->GetFirstMatchingElement(kTestElementID, context())
-                         ->AsA<ElementTrackerElementViews>()
+                         ->AsA<TrackedElementViews>()
                          ->view());
   button2->SetVisible(false);
   EXPECT_EQ(nullptr,
@@ -488,14 +496,14 @@
   button->SetVisible(true);
   EXPECT_EQ(button2, ui::ElementTracker::GetElementTracker()
                          ->GetFirstMatchingElement(kTestElementID, context())
-                         ->AsA<ElementTrackerElementViews>()
+                         ->AsA<TrackedElementViews>()
                          ->view());
 
   // Remove the second button. The first should now be returned.
   contents->RemoveChildViewT(button2);
   EXPECT_EQ(button, ui::ElementTracker::GetElementTracker()
                         ->GetFirstMatchingElement(kTestElementID, context())
-                        ->AsA<ElementTrackerElementViews>()
+                        ->AsA<TrackedElementViews>()
                         ->view());
 
   // Remove the first button. There will be no matching views.
@@ -648,6 +656,49 @@
       kTestElementID, context()));
 }
 
+TEST_F(ElementTrackerViewsTest, CanLookupElementByView) {
+  // Should initially be false.
+  EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->IsElementVisible(
+      kTestElementID, context()));
+
+  auto button_ptr = std::make_unique<LabelButton>();
+  button_ptr->SetProperty(kElementIdentifierKey, kTestElementID);
+
+  // The button is not attached to a widget so there is no associated element
+  // object.
+  EXPECT_EQ(nullptr, ElementTrackerViews::GetInstance()->GetElementForView(
+                         button_ptr.get()));
+
+  // Adding the (visible) view to a widget will cause an element to be
+  // generated.
+  auto* button = widget_->SetContentsView(std::move(button_ptr));
+  EXPECT_NE(nullptr,
+            ElementTrackerViews::GetInstance()->GetElementForView(button));
+
+  // Once the view is destroyed, however, the result should be false again.
+  widget_->GetRootView()->RemoveChildView(button);
+  EXPECT_EQ(nullptr,
+            ElementTrackerViews::GetInstance()->GetElementForView(button));
+  delete button;
+
+  // Create a second view with the same ID but start it as not visible.
+  button = widget_->SetContentsView(std::make_unique<LabelButton>());
+  button->SetVisible(false);
+  button->SetProperty(kElementIdentifierKey, kTestElementID);
+  EXPECT_EQ(nullptr,
+            ElementTrackerViews::GetInstance()->GetElementForView(button));
+
+  // Now set the visibility to true.
+  button->SetVisible(true);
+  EXPECT_NE(nullptr,
+            ElementTrackerViews::GetInstance()->GetElementForView(button));
+
+  // Set visibility to false again.
+  button->SetVisible(false);
+  EXPECT_EQ(nullptr,
+            ElementTrackerViews::GetInstance()->GetElementForView(button));
+}
+
 // The following tests ensure conformity with the different platforms' Views
 // implementation to ensure that Views are reported as visible to the user at
 // the correct times, including during Widget close/delete.
diff --git a/ui/views/interaction/interaction_sequence_views.cc b/ui/views/interaction/interaction_sequence_views.cc
new file mode 100644
index 0000000..99323124
--- /dev/null
+++ b/ui/views/interaction/interaction_sequence_views.cc
@@ -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.
+
+#include "ui/views/interaction/interaction_sequence_views.h"
+
+#include <utility>
+
+#include "ui/base/interaction/element_identifier.h"
+#include "ui/views/interaction/element_tracker_views.h"
+#include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
+
+namespace views {
+
+// static
+std::unique_ptr<ui::InteractionSequence::Step>
+InteractionSequenceViews::WithInitialView(
+    View* view,
+    ui::InteractionSequence::StepCallback start_callback,
+    ui::InteractionSequence::StepCallback end_callback) {
+  // If there's already an element associated with this view, then explicitly
+  // key off of that element.
+  auto* const element =
+      ElementTrackerViews::GetInstance()->GetElementForView(view);
+  if (element)
+    return ui::InteractionSequence::WithInitialElement(
+        element, std::move(start_callback), std::move(end_callback));
+
+  // Otherwise, use the element's identifier and context.
+  ui::ElementContext context = ElementTrackerViews::GetContextForView(view);
+  ui::ElementIdentifier identifier = view->GetProperty(kElementIdentifierKey);
+  return ui::InteractionSequence::StepBuilder()
+      .SetContext(context)
+      .SetElementID(identifier)
+      .SetType(ui::InteractionSequence::StepType::kShown)
+      .SetMustBeVisibleAtStart(true)
+      .SetMustRemainVisible(true)
+      .SetStartCallback(std::move(start_callback))
+      .SetEndCallback(std::move(end_callback))
+      .Build();
+}
+
+}  // namespace views
diff --git a/ui/views/interaction/interaction_sequence_views.h b/ui/views/interaction/interaction_sequence_views.h
new file mode 100644
index 0000000..4442b38
--- /dev/null
+++ b/ui/views/interaction/interaction_sequence_views.h
@@ -0,0 +1,33 @@
+// 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 UI_VIEWS_INTERACTION_INTERACTION_SEQUENCE_VIEWS_H_
+#define UI_VIEWS_INTERACTION_INTERACTION_SEQUENCE_VIEWS_H_
+
+#include <memory>
+
+#include "ui/base/interaction/interaction_sequence.h"
+#include "ui/views/views_export.h"
+
+namespace views {
+
+class View;
+
+// Provides utility methods for using ui::InteractionsSequence with Views.
+class VIEWS_EXPORT InteractionSequenceViews {
+ public:
+  // Not constructible.
+  InteractionSequenceViews() = delete;
+
+  static std::unique_ptr<ui::InteractionSequence::Step> WithInitialView(
+      View* view,
+      ui::InteractionSequence::StepCallback start_callback =
+          ui::InteractionSequence::StepCallback(),
+      ui::InteractionSequence::StepCallback end_callback =
+          ui::InteractionSequence::StepCallback());
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_INTERACTION_INTERACTION_SEQUENCE_VIEWS_H_
diff --git a/ui/views/interaction/interaction_sequence_views_unittest.cc b/ui/views/interaction/interaction_sequence_views_unittest.cc
new file mode 100644
index 0000000..115430b
--- /dev/null
+++ b/ui/views/interaction/interaction_sequence_views_unittest.cc
@@ -0,0 +1,630 @@
+// 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 "ui/views/interaction/interaction_sequence_views.h"
+
+// This suite contains tests which integrate the functionality of
+// ui::InteractionSequence with Views elements like Widgets and menus.
+// Similar suites should be created for other platforms.
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/test/mock_callback.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/interaction/element_test_util.h"
+#include "ui/base/interaction/element_tracker.h"
+#include "ui/base/interaction/interaction_sequence.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/events/event.h"
+#include "ui/events/test/event_generator.h"
+#include "ui/events/types/event_type.h"
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/button/menu_button.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/interaction/element_tracker_views.h"
+#include "ui/views/layout/flex_layout.h"
+#include "ui/views/layout/flex_layout_types.h"
+#include "ui/views/layout/layout_types.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/test/widget_test.h"
+#include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
+#include "ui/views/widget/widget.h"
+
+namespace views {
+
+namespace {
+
+DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsElementID);
+DECLARE_ELEMENT_IDENTIFIER_VALUE(kTestElementID);
+DECLARE_ELEMENT_IDENTIFIER_VALUE(kTestElementID2);
+DECLARE_ELEMENT_IDENTIFIER_VALUE(kTestElementID3);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsElementID);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kTestElementID);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kTestElementID2);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kTestElementID3);
+const char16_t kMenuItem1[] = u"Menu item";
+const char16_t kMenuItem2[] = u"Menu item 2";
+constexpr int kMenuID1 = 1;
+constexpr int kMenuID2 = 2;
+
+#define DECLARE_STEP_CALLBACK(Name)                               \
+  base::MockCallback<ui::InteractionSequence::StepCallback> Name; \
+  EXPECT_CALL(Name, Run).Times(0)
+
+#define DECLARE_COMPLETED_CALLBACK(Name)                               \
+  base::MockCallback<ui::InteractionSequence::CompletedCallback> Name; \
+  EXPECT_CALL(Name, Run).Times(0)
+
+#define DECLARE_ABORTED_CALLBACK(Name)                               \
+  base::MockCallback<ui::InteractionSequence::AbortedCallback> Name; \
+  EXPECT_CALL(Name, Run).Times(0)
+
+}  // namespace
+
+class InteractionSequenceViewsTest : public ViewsTestBase {
+ public:
+  InteractionSequenceViewsTest() = default;
+  ~InteractionSequenceViewsTest() override = default;
+
+  static View* ElementToView(ui::TrackedElement* element) {
+    return element ? element->AsA<TrackedElementViews>()->view() : nullptr;
+  }
+
+  static ui::TrackedElement* ViewToElement(View* view) {
+    return view ? ElementTrackerViews::GetInstance()->GetElementForView(view)
+                : nullptr;
+  }
+
+  std::unique_ptr<Widget> CreateWidget() {
+    auto widget = std::make_unique<Widget>();
+    Widget::InitParams params =
+        CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+    params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+    params.bounds = gfx::Rect(0, 0, 650, 650);
+    widget->Init(std::move(params));
+    auto* contents = widget->SetContentsView(std::make_unique<View>());
+    auto* layout = contents->SetLayoutManager(std::make_unique<FlexLayout>());
+    layout->SetOrientation(LayoutOrientation::kHorizontal);
+    layout->SetDefault(kFlexBehaviorKey,
+                       FlexSpecification(MinimumFlexSizeRule::kPreferred,
+                                         MaximumFlexSizeRule::kUnbounded));
+    test::WidgetVisibleWaiter visible_waiter(widget.get());
+    widget->Show();
+    visible_waiter.Wait();
+    return widget;
+  }
+
+  void ShowMenu(ui::ElementIdentifier id) {
+    CreateAndRunMenu(id);
+
+    View* const view = ElementToView(
+        ui::ElementTracker::GetElementTracker()->GetFirstMatchingElement(
+            id, ElementTrackerViews::GetContextForView(contents_)));
+    Widget* const menu_widget = view->GetWidget();
+    test::WidgetVisibleWaiter visible_waiter(menu_widget);
+    visible_waiter.Wait();
+    DCHECK(strstr(view->GetClassName(), "MenuItemView"));
+    menu_item_ = static_cast<MenuItemView*>(view);
+    EXPECT_TRUE(menu_item_->GetVisible());
+    EXPECT_TRUE(menu_item_->GetWidget()->IsVisible());
+  }
+
+  void CloseMenu() {
+    menu_runner_.reset();
+    menu_model_.reset();
+    menu_item_ = nullptr;
+  }
+
+  void ShowBubble(ui::ElementIdentifier id) {
+    auto delegate = std::make_unique<BubbleDialogDelegateView>(
+        contents_, BubbleBorder::Arrow::TOP_LEFT);
+    bubble_view_ = delegate->AddChildView(std::make_unique<LabelButton>());
+    bubble_view_->SetProperty(kElementIdentifierKey, id);
+    bubble_widget_ =
+        BubbleDialogDelegateView::CreateBubble(std::move(delegate));
+    test::WidgetVisibleWaiter visible_waiter(bubble_widget_);
+    bubble_widget_->Show();
+    visible_waiter.Wait();
+  }
+
+  void CloseBubble() {
+    DCHECK(bubble_widget_);
+    bubble_widget_->CloseNow();
+    bubble_widget_ = nullptr;
+    bubble_view_ = nullptr;
+  }
+
+  void Activate(View* view) {
+    ui::ElementTracker::GetFrameworkDelegate()->NotifyElementActivated(
+        ElementTrackerViews::GetInstance()->GetElementForView(view));
+  }
+
+  void SetUp() override {
+    ViewsTestBase::SetUp();
+    widget_ = CreateWidget();
+    contents_ = widget_->GetContentsView();
+    contents_->SetProperty(kElementIdentifierKey, kContentsElementID);
+  }
+
+  void TearDown() override {
+    if (bubble_widget_)
+      CloseBubble();
+    if (menu_runner_)
+      CloseMenu();
+    widget_.reset();
+    contents_ = nullptr;
+    ViewsTestBase::TearDown();
+  }
+
+ protected:
+  ui::ElementContext context() const {
+    return ui::ElementContext(widget_.get());
+  }
+
+  virtual void CreateAndRunMenu(ui::ElementIdentifier id) {
+    menu_model_ = std::make_unique<ui::SimpleMenuModel>(nullptr);
+    menu_model_->AddItem(kMenuID1, kMenuItem1);
+    menu_model_->AddItem(kMenuID2, kMenuItem2);
+    menu_model_->SetElementIdentifierAt(
+        menu_model_->GetIndexOfCommandId(kMenuID2), id);
+
+    menu_runner_ =
+        std::make_unique<MenuRunner>(menu_model_.get(), MenuRunner::NO_FLAGS);
+    menu_runner_->RunMenuAt(
+        widget_.get(), nullptr, gfx::Rect(gfx::Point(), gfx::Size(200, 200)),
+        MenuAnchorPosition::kTopLeft, ui::MENU_SOURCE_MOUSE);
+  }
+
+  std::unique_ptr<Widget> widget_;
+  View* contents_ = nullptr;
+  Widget* bubble_widget_ = nullptr;
+  View* bubble_view_ = nullptr;
+  std::unique_ptr<ui::SimpleMenuModel> menu_model_;
+  std::unique_ptr<MenuRunner> menu_runner_;
+  MenuItemView* menu_item_ = nullptr;
+};
+
+TEST_F(InteractionSequenceViewsTest, DestructWithInitialViewAborts) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  auto* const starting_view = contents_->AddChildView(std::make_unique<View>());
+  starting_view->SetProperty(kElementIdentifierKey, kTestElementID);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(starting_view))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  tracker->Start();
+  EXPECT_CALL_IN_SCOPE(aborted, Run,
+                       contents_->RemoveChildViewT(starting_view));
+}
+
+TEST_F(InteractionSequenceViewsTest, DestructWithInitialViewBeforeStartAborts) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  auto* const starting_view = contents_->AddChildView(std::make_unique<View>());
+  starting_view->SetProperty(kElementIdentifierKey, kTestElementID);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(starting_view))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  contents_->RemoveChildViewT(starting_view);
+  EXPECT_CALL_IN_SCOPE(aborted, Run, tracker->Start());
+}
+
+TEST_F(InteractionSequenceViewsTest, WrongWithInitialViewDoesNotStartSequence) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  auto* const starting_view = contents_->AddChildView(std::make_unique<View>());
+  starting_view->SetProperty(kElementIdentifierKey, kTestElementID);
+  auto* const other_view = contents_->AddChildView(std::make_unique<View>());
+  other_view->SetProperty(kElementIdentifierKey, kTestElementID);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(starting_view))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .Build())
+          .Build();
+  starting_view->SetVisible(false);
+  EXPECT_CALL_IN_SCOPE(aborted, Run, tracker->Start());
+}
+
+TEST_F(InteractionSequenceViewsTest,
+       SequenceNotCanceledDueToViewDestroyedIfRequirementChanged) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step2_start);
+  DECLARE_STEP_CALLBACK(step2_end);
+  DECLARE_STEP_CALLBACK(step3_start);
+  auto* const starting_view = contents_->AddChildView(std::make_unique<View>());
+  starting_view->SetProperty(kElementIdentifierKey, kTestElementID);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(starting_view))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kShown)
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       // Specify that this step doesn't abort on the view
+                       // becoming hidden.
+                       .SetMustRemainVisible(false)
+                       .SetStartCallback(step2_start.Get())
+                       .SetEndCallback(step2_end.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID3)
+                       .SetType(ui::InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step3_start.Get())
+                       .Build())
+          .Build();
+  tracker->Start();
+  auto* const second_view = contents_->AddChildView(std::make_unique<View>());
+  second_view->SetProperty(kElementIdentifierKey, kTestElementID2);
+  auto* const third_view = contents_->AddChildView(std::make_unique<View>());
+  third_view->SetProperty(kElementIdentifierKey, kTestElementID3);
+  third_view->SetVisible(false);
+
+  // Simulate the view being activated to do the second step.
+  EXPECT_CALL_IN_SCOPE(step2_start,
+                       Run(ViewToElement(second_view), kTestElementID2,
+                           ui::InteractionSequence::StepType::kActivated),
+                       Activate(second_view));
+
+  // Destroying the second view should NOT break the sequence.
+  contents_->RemoveChildViewT(second_view);
+
+  // Showing the third view at this point continues the sequence.
+  EXPECT_CALLS_IN_SCOPE_3(step2_end, Run, step3_start, Run, completed, Run,
+                          third_view->SetVisible(true));
+}
+
+TEST_F(InteractionSequenceViewsTest, TransitionToBubble) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  DECLARE_STEP_CALLBACK(step2);
+  DECLARE_STEP_CALLBACK(step3);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(contents_))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step2.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step3.Get())
+                       .Build())
+          .Build();
+  auto* const button = contents_->AddChildView(
+      std::make_unique<LabelButton>(Button::PressedCallback(
+          base::BindRepeating(&InteractionSequenceViewsTest::ShowBubble,
+                              base::Unretained(this), kTestElementID2))));
+  button->SetProperty(kElementIdentifierKey, kTestElementID);
+  tracker->Start();
+
+  EXPECT_CALLS_IN_SCOPE_2(step, Run, step2, Run, {
+    button->OnKeyPressed(ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE,
+                                      ui::EF_NONE, ui::EventTimeForNow()));
+    button->OnKeyReleased(ui::KeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_SPACE,
+                                       ui::EF_NONE, ui::EventTimeForNow()));
+  });
+
+  EXPECT_CALLS_IN_SCOPE_2(step3, Run, completed, Run, {
+    bubble_view_->OnKeyPressed(ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE,
+                                            ui::EF_NONE,
+                                            ui::EventTimeForNow()));
+    bubble_view_->OnKeyReleased(ui::KeyEvent(ui::ET_KEY_RELEASED,
+                                             ui::VKEY_SPACE, ui::EF_NONE,
+                                             ui::EventTimeForNow()));
+  });
+}
+
+TEST_F(InteractionSequenceViewsTest, TransitionToBubbleThenAbort) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  DECLARE_STEP_CALLBACK(step2);
+  DECLARE_STEP_CALLBACK(step3);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(contents_))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step2.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step3.Get())
+                       .Build())
+          .Build();
+  auto* const button = contents_->AddChildView(
+      std::make_unique<LabelButton>(Button::PressedCallback(
+          base::BindRepeating(&InteractionSequenceViewsTest::ShowBubble,
+                              base::Unretained(this), kTestElementID2))));
+  button->SetProperty(kElementIdentifierKey, kTestElementID);
+  tracker->Start();
+
+  EXPECT_CALLS_IN_SCOPE_2(step, Run, step2, Run, {
+    button->OnKeyPressed(ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE,
+                                      ui::EF_NONE, ui::EventTimeForNow()));
+    button->OnKeyReleased(ui::KeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_SPACE,
+                                       ui::EF_NONE, ui::EventTimeForNow()));
+  });
+
+  EXPECT_CALL_IN_SCOPE(aborted, Run, CloseBubble());
+}
+
+TEST_F(InteractionSequenceViewsTest, TransitionToMenuAndViewMenuItem) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  DECLARE_STEP_CALLBACK(step2);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(contents_))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step2.Get())
+                       .Build())
+          .Build();
+  auto* const button = contents_->AddChildView(
+      std::make_unique<LabelButton>(Button::PressedCallback(
+          base::BindRepeating(&InteractionSequenceViewsTest::ShowMenu,
+                              base::Unretained(this), kTestElementID2))));
+  button->SetProperty(kElementIdentifierKey, kTestElementID);
+  tracker->Start();
+
+  EXPECT_CALLS_IN_SCOPE_3(step, Run, step2, Run, completed, Run, {
+    button->OnKeyPressed(ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE,
+                                      ui::EF_NONE, ui::EventTimeForNow()));
+    button->OnKeyReleased(ui::KeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_SPACE,
+                                       ui::EF_NONE, ui::EventTimeForNow()));
+  });
+}
+
+TEST_F(InteractionSequenceViewsTest, TransitionToMenuThenCloseMenuToCancel) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  DECLARE_STEP_CALLBACK(step2);
+  DECLARE_STEP_CALLBACK(step3);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(contents_))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step2.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step3.Get())
+                       .Build())
+          .Build();
+  auto* const button = contents_->AddChildView(
+      std::make_unique<LabelButton>(Button::PressedCallback(
+          base::BindRepeating(&InteractionSequenceViewsTest::ShowMenu,
+                              base::Unretained(this), kTestElementID2))));
+  button->SetProperty(kElementIdentifierKey, kTestElementID);
+  tracker->Start();
+
+  EXPECT_CALLS_IN_SCOPE_2(step, Run, step2, Run, {
+    button->OnKeyPressed(ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE,
+                                      ui::EF_NONE, ui::EventTimeForNow()));
+    button->OnKeyReleased(ui::KeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_SPACE,
+                                       ui::EF_NONE, ui::EventTimeForNow()));
+  });
+
+  EXPECT_CALL_IN_SCOPE(aborted, Run, CloseMenu());
+}
+
+// Menu button uses different event-handling architecture than standard Button,
+// so test it separately here.
+TEST_F(InteractionSequenceViewsTest, TransitionToMenuWithMenuButton) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  DECLARE_STEP_CALLBACK(step2);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(contents_))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step2.Get())
+                       .Build())
+          .Build();
+
+  auto* const button = contents_->AddChildView(
+      std::make_unique<MenuButton>(Button::PressedCallback(
+          base::BindRepeating(&InteractionSequenceViewsTest::ShowMenu,
+                              base::Unretained(this), kTestElementID2))));
+  button->SetProperty(kElementIdentifierKey, kTestElementID);
+  tracker->Start();
+
+  EXPECT_CALLS_IN_SCOPE_3(step, Run, step2, Run, completed, Run, {
+    button->OnKeyPressed(ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE,
+                                      ui::EF_NONE, ui::EventTimeForNow()));
+    button->OnKeyReleased(ui::KeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_SPACE,
+                                       ui::EF_NONE, ui::EventTimeForNow()));
+  });
+}
+
+#if !defined(OS_MAC)
+// Because Mac does not use Aura, mouse event delivery to Views menus on Mac
+// doesn't work the same as on other platforms. We still test that activation of
+// Mac menus is correctly communicated through ui::ElementTracker in the
+// following test.
+TEST_F(InteractionSequenceViewsTest, TransitionToMenuAndActivateMenuItem) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  DECLARE_STEP_CALLBACK(step2);
+  DECLARE_STEP_CALLBACK(step3);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(contents_))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step2.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step3.Get())
+                       .Build())
+          .Build();
+  auto* const button = contents_->AddChildView(
+      std::make_unique<LabelButton>(Button::PressedCallback(
+          base::BindRepeating(&InteractionSequenceViewsTest::ShowMenu,
+                              base::Unretained(this), kTestElementID2))));
+  button->SetProperty(kElementIdentifierKey, kTestElementID);
+  tracker->Start();
+
+  EXPECT_CALLS_IN_SCOPE_2(step, Run, step2, Run, {
+    button->OnKeyPressed(ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE,
+                                      ui::EF_NONE, ui::EventTimeForNow()));
+    button->OnKeyReleased(ui::KeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_SPACE,
+                                       ui::EF_NONE, ui::EventTimeForNow()));
+  });
+
+  EXPECT_CALLS_IN_SCOPE_2(step3, Run, completed, Run, {
+    ui::test::EventGenerator generator(GetContext(),
+                                       widget_->GetNativeWindow());
+    generator.MoveMouseTo(menu_item_->GetBoundsInScreen().CenterPoint());
+    generator.ClickLeftButton();
+  });
+}
+
+#endif  // !defined(OS_MAC)
+
+TEST_F(InteractionSequenceViewsTest, TransitionOnKeyboardMenuActivation) {
+  DECLARE_ABORTED_CALLBACK(aborted);
+  DECLARE_COMPLETED_CALLBACK(completed);
+  DECLARE_STEP_CALLBACK(step);
+  DECLARE_STEP_CALLBACK(step2);
+  DECLARE_STEP_CALLBACK(step3);
+  auto tracker =
+      ui::InteractionSequence::Builder()
+          .SetAbortedCallback(aborted.Get())
+          .SetCompletedCallback(completed.Get())
+          .AddStep(InteractionSequenceViews::WithInitialView(contents_))
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kShown)
+                       .SetStartCallback(step2.Get())
+                       .Build())
+          .AddStep(ui::InteractionSequence::StepBuilder()
+                       .SetElementID(kTestElementID2)
+                       .SetType(ui::InteractionSequence::StepType::kActivated)
+                       .SetStartCallback(step3.Get())
+                       .Build())
+          .Build();
+  auto* const button = contents_->AddChildView(
+      std::make_unique<LabelButton>(Button::PressedCallback(
+          base::BindRepeating(&InteractionSequenceViewsTest::ShowMenu,
+                              base::Unretained(this), kTestElementID2))));
+  button->SetProperty(kElementIdentifierKey, kTestElementID);
+  tracker->Start();
+
+  EXPECT_CALLS_IN_SCOPE_2(step, Run, step2, Run, {
+    button->OnKeyPressed(ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE,
+                                      ui::EF_NONE, ui::EventTimeForNow()));
+    button->OnKeyReleased(ui::KeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_SPACE,
+                                       ui::EF_NONE, ui::EventTimeForNow()));
+  });
+
+  EXPECT_CALLS_IN_SCOPE_2(step3, Run, completed, Run, {
+    ui::test::EventGenerator generator(GetContext(),
+                                       widget_->GetNativeWindow());
+    generator.PressKey(ui::VKEY_DOWN, 0);
+    generator.PressKey(ui::VKEY_DOWN, 0);
+    generator.PressKey(ui::VKEY_RETURN, 0);
+  });
+}
+
+}  // namespace views
diff --git a/url/DIR_METADATA b/url/DIR_METADATA
index fb07a25..6d266b0 100644
--- a/url/DIR_METADATA
+++ b/url/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Core"
diff --git a/url/ipc/OWNERS b/url/ipc/OWNERS
index 0a42834..146c3c3c 100644
--- a/url/ipc/OWNERS
+++ b/url/ipc/OWNERS
@@ -1,2 +1,2 @@
 per-file *_param_traits*.*=set noparent
-per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
\ No newline at end of file
+per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/url/mojom/DIR_METADATA b/url/mojom/DIR_METADATA
index 06992d0..c080aa1 100644
--- a/url/mojom/DIR_METADATA
+++ b/url/mojom/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Mojo"
diff --git a/url/mojom/OWNERS b/url/mojom/OWNERS
index d970307..1feb514 100644
--- a/url/mojom/OWNERS
+++ b/url/mojom/OWNERS
@@ -1,4 +1,4 @@
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
 per-file *_mojom_traits*.*=set noparent
-per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
\ No newline at end of file
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/weblayer/DIR_METADATA b/weblayer/DIR_METADATA
index bb8ad53a..49848114 100644
--- a/weblayer/DIR_METADATA
+++ b/weblayer/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>WebLayer"
diff --git a/weblayer/README.md b/weblayer/README.md
index d69aaf2..e14c53c146 100644
--- a/weblayer/README.md
+++ b/weblayer/README.md
@@ -50,7 +50,7 @@
 If you haven't done this already, you first need to set up an Android build. If
 you are a Google employee, reach out to weblayer-team@google.com for internal
 instructions. Otherwise follow the
-[Android build instructions](https://source.chromium.org/chromium/chromium/src/+/master:docs/android_build_instructions.md).
+[Android build instructions](https://source.chromium.org/chromium/chromium/src/+/main:docs/android_build_instructions.md).
 
 ## Building and Testing
 
@@ -145,7 +145,7 @@
 Passing in `-vvvv` may be useful if you want to see loads of information about
 test execution.
 
-A list of known test failures is in [`WeblayerWPTExpectations`](https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/web_tests/android/WeblayerWPTExpectations).
+A list of known test failures is in [`WeblayerWPTExpectations`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/android/WeblayerWPTExpectations).
 The values between the brackets at the end of each line list the expected
 result types that test can have. For example, a test marked as "[ Failure ]" is
 expected to fail, while a test marked as "[ Failure Pass ]" is expected to be
@@ -160,9 +160,9 @@
 some may fail due to Features that aren't getting enabled or switches that
 aren't getting passed to the test as they would be for Clank. If a test is
 failing due to a missing Feature, check the test FieldTrial configuration
-in [`fieldtrial_testing_config.json`](https://source.chromium.org/chromium/chromium/src/+/master:testing/variations/fieldtrial_testing_config.json).
+in [`fieldtrial_testing_config.json`](https://source.chromium.org/chromium/chromium/src/+/main:testing/variations/fieldtrial_testing_config.json).
 A missing switch could have several causes, but the flags that get passed
-to the test originate from [`third_party/wpt_tools/wpt/tools/wpt/run.py`](https://source.chromium.org/chromium/chromium/src/+/master:third_party/wpt_tools/wpt/tools/wpt/run.py).
+to the test originate from [`third_party/wpt_tools/wpt/tools/wpt/run.py`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/wpt_tools/wpt/tools/wpt/run.py).
 
 
 ## Telemetry
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java
index c6f07e4..8660d6e 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java
@@ -10,6 +10,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.view.PixelCopy;
@@ -143,7 +144,7 @@
                 Runtime.getRuntime().gc();
                 throw new CriteriaNotSatisfiedException("No enqueued reference");
             }
-            Criteria.checkThat(reference, Matchers.is(enqueuedReference));
+            Assert.assertEquals(reference, enqueuedReference);
         });
     }
 
@@ -199,7 +200,28 @@
                 Runtime.getRuntime().gc();
                 throw new CriteriaNotSatisfiedException("No enqueued reference");
             }
-            Criteria.checkThat(reference, Matchers.is(enqueuedReference));
+            Assert.assertEquals(reference, enqueuedReference);
+        });
+    }
+
+    // Verifies recreating the Activity destroys the original Fragment when using ViewModel.
+    @Test
+    @SmallTest
+    public void testRecreateActivityDestroysFragmentUseViewModel() throws Throwable {
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_USE_VIEW_MODEL, true);
+        mActivityTestRule.launchShellWithUrl("about:blank", extras);
+        ReferenceQueue<Fragment> referenceQueue = new ReferenceQueue<>();
+        PhantomReference<Fragment> reference =
+                new PhantomReference<>(mActivityTestRule.getFragment(), referenceQueue);
+        mActivityTestRule.recreateActivity();
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Reference enqueuedReference = referenceQueue.poll();
+            if (enqueuedReference == null) {
+                Runtime.getRuntime().gc();
+                throw new CriteriaNotSatisfiedException("No enqueued reference");
+            }
+            Assert.assertEquals(reference, enqueuedReference);
         });
     }
 }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/FragmentHostingRemoteFragmentImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/FragmentHostingRemoteFragmentImpl.java
index ea42dacb..13f836f 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/FragmentHostingRemoteFragmentImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/FragmentHostingRemoteFragmentImpl.java
@@ -197,7 +197,7 @@
         super.onDetach();
         mContext = null;
 
-        // If the Fragment is retained, onDestory won't be called during configuration changes. We
+        // If the Fragment is retained, onDestroy won't be called during configuration changes. We
         // have to create a new FragmentController that's attached to the correct Context when
         // reattaching this Fragment, so destroy the existing one here.
         if (!mFragmentController.getSupportFragmentManager().isDestroyed()) {
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
index a818f0a..57e40303 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
@@ -50,7 +50,6 @@
         } catch (RemoteException e) {
             throw new APICallException(e);
         }
-        NavigationImplJni.get().setJavaNavigation(mNativeNavigationImpl, NavigationImpl.this);
     }
 
     public IClientNavigation getClientNavigation() {
@@ -299,7 +298,6 @@
 
     @NativeMethods
     interface Natives {
-        void setJavaNavigation(long nativeNavigationImpl, NavigationImpl caller);
         int getState(long nativeNavigationImpl);
         String getUri(long nativeNavigationImpl);
         String[] getRedirectChain(long nativeNavigationImpl);
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/README.md b/weblayer/browser/java/org/chromium/weblayer_private/README.md
index 35e0aed..3a262df 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/README.md
+++ b/weblayer/browser/java/org/chromium/weblayer_private/README.md
@@ -21,7 +21,7 @@
 is what's returned by [`BrowserImpl.getContext()`][link2]. Use this when referencing WebLayer specific
 resources. This is expected to be the most common use case.
 
-[link2]: https://source.chromium.org/chromium/chromium/src/+/master:weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java?q=f:browserimpl%20getContext&ss=chromium%2Fchromium%2Fsrc
+[link2]: https://source.chromium.org/chromium/chromium/src/+/main:weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java?q=f:browserimpl%20getContext&ss=chromium%2Fchromium%2Fsrc
 
 ## Embedder's Application Context
 
@@ -33,4 +33,4 @@
 It shouldn't be downcast to Application (or any subclass thereof) since it's wrapped in a
 ContextWrapper.
 
-[link3]: https://source.chromium.org/chromium/chromium/src/+/master:base/android/java/src/org/chromium/base/ContextUtils.java?q=f:base%2FContextUtils%20getApplicationContext()&ss=chromium%2Fchromium%2Fsrc
+[link3]: https://source.chromium.org/chromium/chromium/src/+/main:base/android/java/src/org/chromium/base/ContextUtils.java?q=f:base%2FContextUtils%20getApplicationContext()&ss=chromium%2Fchromium%2Fsrc
diff --git a/weblayer/browser/navigation_controller_impl.cc b/weblayer/browser/navigation_controller_impl.cc
index a5a64de..58d96a3 100644
--- a/weblayer/browser/navigation_controller_impl.cc
+++ b/weblayer/browser/navigation_controller_impl.cc
@@ -428,8 +428,11 @@
     {
       TRACE_EVENT0("weblayer",
                    "Java_NavigationControllerImpl_createNavigation");
-      Java_NavigationControllerImpl_createNavigation(
-          env, java_controller_, reinterpret_cast<jlong>(navigation));
+      ScopedJavaLocalRef<jobject> java_navigation =
+          Java_NavigationControllerImpl_createNavigation(
+              env, java_controller_, reinterpret_cast<jlong>(navigation));
+      navigation->SetJavaNavigation(
+          base::android::ScopedJavaGlobalRef<jobject>(java_navigation));
     }
     TRACE_EVENT0("weblayer", "Java_NavigationControllerImpl_navigationStarted");
     Java_NavigationControllerImpl_navigationStarted(
diff --git a/weblayer/browser/navigation_impl.cc b/weblayer/browser/navigation_impl.cc
index 9426e57f..6b326f7 100644
--- a/weblayer/browser/navigation_impl.cc
+++ b/weblayer/browser/navigation_impl.cc
@@ -57,12 +57,6 @@
 }
 
 #if defined(OS_ANDROID)
-void NavigationImpl::SetJavaNavigation(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& java_navigation) {
-  java_navigation_ = java_navigation;
-}
-
 ScopedJavaLocalRef<jstring> NavigationImpl::GetUri(JNIEnv* env) {
   return ScopedJavaLocalRef<jstring>(
       base::android::ConvertUTF8ToJavaString(env, GetURL().spec()));
@@ -156,6 +150,13 @@
   return std::move(response_);
 }
 
+void NavigationImpl::SetJavaNavigation(
+    const base::android::ScopedJavaGlobalRef<jobject>& java_navigation) {
+  // SetJavaNavigation() should only be called once.
+  DCHECK(!java_navigation_);
+  java_navigation_ = java_navigation;
+}
+
 #endif
 
 bool NavigationImpl::IsPageInitiated() {
diff --git a/weblayer/browser/navigation_impl.h b/weblayer/browser/navigation_impl.h
index fb8135f..a80632d 100644
--- a/weblayer/browser/navigation_impl.h
+++ b/weblayer/browser/navigation_impl.h
@@ -69,9 +69,6 @@
   TakeParamsToLoadWhenSafe();
 
 #if defined(OS_ANDROID)
-  void SetJavaNavigation(
-      JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& java_navigation);
   int GetState(JNIEnv* env) { return static_cast<int>(GetState()); }
   base::android::ScopedJavaLocalRef<jstring> GetUri(JNIEnv* env);
   base::android::ScopedJavaLocalRef<jobjectArray> GetRedirectChain(JNIEnv* env);
@@ -106,6 +103,8 @@
       std::unique_ptr<embedder_support::WebResourceResponse> response);
   std::unique_ptr<embedder_support::WebResourceResponse> TakeResponse();
 
+  void SetJavaNavigation(
+      const base::android::ScopedJavaGlobalRef<jobject>& java_navigation);
   base::android::ScopedJavaGlobalRef<jobject> java_navigation() {
     return java_navigation_;
   }