diff --git a/DEPS b/DEPS
index 43351a40..fb1d3bb 100644
--- a/DEPS
+++ b/DEPS
@@ -235,15 +235,15 @@
   # 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': 'f32ad08ac4a7047e04706be97191ad0a976e6b27',
+  'skia_revision': '87a0078b890928828c08790bb2c846724234edcb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '2324d83cc4a144a49c83c533dd6a80931ec166b9',
+  'v8_revision': '35eaec5cf5c322ec9211699a68c465f6d3415bbb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'fbf8d1ea1b5c55c9616df2ab9935b8660dc8bb8a',
+  'angle_revision': 'bcc8970347f1a51b650e4a79f5e4ddec11047e79',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -310,7 +310,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': '113abecd98b302a7cc970666277c9f4bb7aed47a',
+  'devtools_frontend_revision': '0404c7edc03c2ded9787ba7d00f821414ce4285a',
   # 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.
@@ -350,11 +350,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '6fd28ba4cb990953dc97ff22d3fbbd9b19a5f504',
+  'dawn_revision': 'a4c8c8d5bffb66900d5bc76b143ce956f4fa6a13',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '003dffca332f2b9a4f288cb983230c8783029e27',
+  'quiche_revision': 'd9e16940859d20084e9de2b47450ca64d179d017',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -1010,7 +1010,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '9d5f4194e9c0ce319f67bf44e84568d4b1ea32cb',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '6b1011520cfe00a073b0062740b25d45f47ec9d9',
       'condition': 'checkout_chromeos',
   },
 
@@ -1297,7 +1297,7 @@
   },
 
   'src/third_party/libunwindstack': {
-      'url': Var('chromium_git') + '/chromium/src/third_party/libunwindstack.git' + '@' + 'b34a0059a648f179ef05da2c0927f564bdaea2b3',
+      'url': Var('chromium_git') + '/chromium/src/third_party/libunwindstack.git' + '@' + '6868358481bb1e5e20d155c1084dc436c88b5e6b',
       'condition': 'checkout_android',
   },
 
@@ -1399,7 +1399,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '3dd5b80bc4f172dd82925bb259cb7c82348409c5',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '5467ac6e5c120051f44720dcd0a0de9be9a52b87',
+    Var('chromium_git') + '/openscreen' + '@' + '0c8028efc2bb92a9fc2311ef992420948617e3f6',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + 'bf21ccb1007bb531b45d9978919a56ea5059c245',
@@ -1416,7 +1416,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '9709a3d80cc9e2ac3e6d808638334f7510ddc59a',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '251c5dd034fdc7ce2578f2b5fd112e2fc0fa0e8e',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1637,7 +1637,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'e42f534de7afea5942ecae5993d9211e258918aa',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '56ed4680c457935f813ae36ce24324df7780b210',
+    Var('webrtc_git') + '/src.git' + '@' + '339965f93cdaaee297fe0764f1765de0ae1bd9f3',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1695,7 +1695,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6eae9bf238ce787d3c8d27c22742cabb894d79e1',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@646a59e1f51fa8237c7104a6c7805d3d7581d021',
     'condition': 'checkout_src_internal',
   },
 
@@ -1714,7 +1714,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'ZFyhw4M_ljvsXoXOC9LIHZPnXMU94atIC8w0bZf3jbsC',
+        'version': 'Pyw0C0bOEiWBFK01kBwB_jv5JJ1x-e5LcCpdRNLhgZsC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1725,7 +1725,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'CsYiCAVozIrhRckMiklfbqKUIsz-6Kls5fI6G_ERblkC',
+        'version': '-gZnSSL4IDU9raGy7Srz8vSLkUuWd9JMY1Gqe0PqPbAC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 9e55724..af3899d2 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -4269,7 +4269,8 @@
             input_api, output_api, full_path,
             files_to_check=[r'^PRESUBMIT_test\.py$'],
             run_on_python2=not use_python3,
-            run_on_python3=use_python3))
+            run_on_python3=use_python3,
+            skip_shebang_check=True))
   return results
 
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 1d16cae..e0548c1 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -902,6 +902,8 @@
     "shutdown_reason.h",
     "style/ash_color_provider.cc",
     "style/ash_color_provider.h",
+    "style/button_style.cc",
+    "style/button_style.h",
     "style/default_color_constants.h",
     "style/default_colors.cc",
     "style/default_colors.h",
@@ -1503,8 +1505,6 @@
     "system/unified/quiet_mode_feature_pod_controller.h",
     "system/unified/rounded_label_button.cc",
     "system/unified/rounded_label_button.h",
-    "system/unified/sign_out_button.cc",
-    "system/unified/sign_out_button.h",
     "system/unified/top_shortcut_button.cc",
     "system/unified/top_shortcut_button.h",
     "system/unified/top_shortcuts_view.cc",
@@ -1648,6 +1648,8 @@
     "wm/desks/root_window_desk_switch_animator.h",
     "wm/desks/scroll_arrow_button.cc",
     "wm/desks/scroll_arrow_button.h",
+    "wm/desks/templates/desks_templates_delete_button.cc",
+    "wm/desks/templates/desks_templates_delete_button.h",
     "wm/desks/templates/desks_templates_dialog_controller.cc",
     "wm/desks/templates/desks_templates_dialog_controller.h",
     "wm/desks/templates/desks_templates_grid_view.cc",
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index a89a451..903c5102 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -705,22 +705,6 @@
     client_->OnItemUpdated(profile_id_, item->CloneMetadata());
 }
 
-void AppListControllerImpl::OnAppListStateChanged(AppListState new_state,
-                                                  AppListState old_state) {
-  UpdateLauncherContainer();
-
-  if (new_state == AppListState::kStateEmbeddedAssistant) {
-    // ShowUi() will be no-op if the Assistant UI is already visible.
-    AssistantUiController::Get()->ShowUi(AssistantEntryPoint::kUnspecified);
-    return;
-  }
-
-  if (old_state == AppListState::kStateEmbeddedAssistant) {
-    // CloseUi() will be no-op if the Assistant UI is already closed.
-    AssistantUiController::Get()->CloseUi(AssistantExitPoint::kBackInLauncher);
-  }
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Methods used in Ash
 
@@ -919,10 +903,6 @@
   return true;
 }
 
-AppListViewState AppListControllerImpl::GetAppListViewState() {
-  return model_->state_fullscreen();
-}
-
 bool AppListControllerImpl::ShouldHomeLauncherBeVisible() const {
   if (!IsTabletMode())
     return false;
@@ -1681,7 +1661,13 @@
     close_assistant_ui_runner_.RunAndReset();
 }
 
+AppListViewState AppListControllerImpl::GetAppListViewState() const {
+  return app_list_view_state_;
+}
+
 void AppListControllerImpl::OnViewStateChanged(AppListViewState state) {
+  app_list_view_state_ = state;
+
   auto* notifier = GetNotifier();
   if (notifier)
     notifier->NotifyUIStateChanged(state);
@@ -1751,6 +1737,34 @@
   return screen_util::SnapBoundsToDisplayEdge(bounds, window);
 }
 
+AppListState AppListControllerImpl::GetCurrentAppListPage() const {
+  return app_list_page_;
+}
+
+void AppListControllerImpl::OnAppListPageChanged(AppListState page) {
+  const AppListState old_page = app_list_page_;
+  if (old_page == page)
+    return;
+
+  app_list_page_ = page;
+
+  if (!fullscreen_presenter_)
+    return;
+
+  UpdateLauncherContainer();
+
+  if (page == AppListState::kStateEmbeddedAssistant) {
+    // ShowUi() will be no-op if the Assistant UI is already visible.
+    AssistantUiController::Get()->ShowUi(AssistantEntryPoint::kUnspecified);
+    return;
+  }
+
+  if (old_page == AppListState::kStateEmbeddedAssistant) {
+    // CloseUi() will be no-op if the Assistant UI is already closed.
+    AssistantUiController::Get()->CloseUi(AssistantExitPoint::kBackInLauncher);
+  }
+}
+
 int AppListControllerImpl::GetShelfSize() {
   return ShelfConfig::Get()->system_shelf_size();
 }
@@ -2142,7 +2156,7 @@
 
 bool AppListControllerImpl::ShouldLauncherShowBehindApps() const {
   return IsTabletMode() &&
-         model_->state() != AppListState::kStateEmbeddedAssistant;
+         app_list_page_ != AppListState::kStateEmbeddedAssistant;
 }
 
 int AppListControllerImpl::GetLastQueryLength() {
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index b1ce50a..b590ed9c 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -147,8 +147,6 @@
   void OnAppListItemAdded(AppListItem* item) override;
   void OnAppListItemWillBeDeleted(AppListItem* item) override;
   void OnAppListItemUpdated(AppListItem* item) override;
-  void OnAppListStateChanged(AppListState new_state,
-                             AppListState old_state) override;
 
   // SessionObserver:
   void OnActiveUserPrefServiceChanged(PrefService* pref_service) override;
@@ -179,7 +177,7 @@
   ShelfAction ToggleAppList(int64_t display_id,
                             AppListShowSource show_source,
                             base::TimeTicks event_time_stamp);
-  AppListViewState GetAppListViewState();
+
   // Returns whether the home launcher should be visible.
   bool ShouldHomeLauncherBeVisible() const;
 
@@ -235,13 +233,16 @@
   void OnStateTransitionAnimationCompleted(
       AppListViewState state,
       bool was_animation_interrupted) override;
-  void OnViewStateChanged(AppListViewState state) override;
   int AdjustAppListViewScrollOffset(int offset, ui::EventType type) override;
   void LoadIcon(const std::string& app_id) override;
 
   void GetAppLaunchedMetricParams(
       AppLaunchedMetricParams* metric_params) override;
   gfx::Rect SnapBoundsToDisplayEdge(const gfx::Rect& bounds) override;
+  AppListState GetCurrentAppListPage() const override;
+  void OnAppListPageChanged(AppListState page) override;
+  AppListViewState GetAppListViewState() const override;
+  void OnViewStateChanged(AppListViewState state) override;
   int GetShelfSize() override;
   bool IsInTabletMode() override;
   AppListColorProviderImpl* GetColorProvider();
@@ -505,6 +506,14 @@
   // is disabled.
   std::unique_ptr<AppListBubblePresenter> bubble_presenter_;
 
+  // Tracks the current page shown in the app list view (tracked for the
+  // fullscreen presenter).
+  AppListState app_list_page_ = AppListState::kInvalidState;
+
+  // Tracks the current state of `AppListView` (tracked for the fullscreen
+  // presenter)
+  AppListViewState app_list_view_state_ = AppListViewState::kClosed;
+
   // True if the on-screen keyboard is shown.
   bool onscreen_keyboard_shown_ = false;
 
diff --git a/ash/app_list/app_list_metrics.cc b/ash/app_list/app_list_metrics.cc
index ea23d67..59e3769 100644
--- a/ash/app_list/app_list_metrics.cc
+++ b/ash/app_list/app_list_metrics.cc
@@ -11,8 +11,6 @@
 #include "ash/app_list/model/app_list_folder_item.h"
 #include "ash/app_list/model/app_list_item.h"
 #include "ash/app_list/model/app_list_item_list.h"
-#include "ash/app_list/model/app_list_model.h"
-#include "ash/app_list/model/search/search_model.h"
 #include "ash/app_list/model/search/search_result.h"
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/app_menu_constants.h"
@@ -137,15 +135,14 @@
 }
 
 void RecordSearchResultOpenSource(const SearchResult* result,
-                                  const AppListModel* model,
-                                  const SearchModel* search_model) {
+                                  AppListViewState state,
+                                  bool is_tablet_mode) {
   // Record the search metric if the SearchResult is not a suggested app.
   if (result->is_recommendation())
     return;
 
   ApplistSearchResultOpenedSource source;
-  AppListViewState state = model->state_fullscreen();
-  if (search_model->tablet_mode()) {
+  if (is_tablet_mode) {
     source = ApplistSearchResultOpenedSource::kFullscreenTablet;
   } else {
     source = state == AppListViewState::kHalf
diff --git a/ash/app_list/app_list_metrics.h b/ash/app_list/app_list_metrics.h
index 1697e58..ae83544 100644
--- a/ash/app_list/app_list_metrics.h
+++ b/ash/app_list/app_list_metrics.h
@@ -12,8 +12,6 @@
 
 namespace ash {
 
-class AppListModel;
-class SearchModel;
 class SearchResult;
 
 // The UMA histogram that logs how the app list transitions from peeking to
@@ -212,8 +210,8 @@
 void RecordPeriodicAppListMetrics();
 
 ASH_EXPORT void RecordSearchResultOpenSource(const SearchResult* result,
-                                             const AppListModel* model,
-                                             const SearchModel* search_model);
+                                             AppListViewState state,
+                                             bool is_tablet_mode);
 
 ASH_EXPORT void RecordSearchLaunchIndexAndQueryLength(
     SearchResultLaunchLocation launch_location,
diff --git a/ash/app_list/app_list_test_view_delegate.cc b/ash/app_list/app_list_test_view_delegate.cc
index 13347e50..a038534 100644
--- a/ash/app_list/app_list_test_view_delegate.cc
+++ b/ash/app_list/app_list_test_view_delegate.cc
@@ -196,7 +196,21 @@
     AppListViewState state,
     bool was_animation_interrupted) {}
 
-void AppListTestViewDelegate::OnViewStateChanged(AppListViewState state) {}
+AppListState AppListTestViewDelegate::GetCurrentAppListPage() const {
+  return app_list_page_;
+}
+
+void AppListTestViewDelegate::OnAppListPageChanged(AppListState page) {
+  app_list_page_ = page;
+}
+
+AppListViewState AppListTestViewDelegate::GetAppListViewState() const {
+  return app_list_view_state_;
+}
+
+void AppListTestViewDelegate::OnViewStateChanged(AppListViewState state) {
+  app_list_view_state_ = state;
+}
 
 void AppListTestViewDelegate::GetAppLaunchedMetricParams(
     AppLaunchedMetricParams* metric_params) {}
@@ -226,7 +240,7 @@
 
 void AppListTestViewDelegate::RecordAppLaunched(
     ash::AppListLaunchedFrom launched_from) {
-  RecordAppListAppLaunched(launched_from, model_->state_fullscreen(),
+  RecordAppListAppLaunched(launched_from, app_list_view_state_,
                            false /*tablet mode*/,
                            false /*home launcher shown*/);
 }
diff --git a/ash/app_list/app_list_test_view_delegate.h b/ash/app_list/app_list_test_view_delegate.h
index 4ac50a5..9b58eea 100644
--- a/ash/app_list/app_list_test_view_delegate.h
+++ b/ash/app_list/app_list_test_view_delegate.h
@@ -110,6 +110,9 @@
   void OnStateTransitionAnimationCompleted(
       AppListViewState state,
       bool was_animation_interrupted) override;
+  AppListState GetCurrentAppListPage() const override;
+  void OnAppListPageChanged(AppListState page) override;
+  AppListViewState GetAppListViewState() const override;
   void OnViewStateChanged(AppListViewState state) override;
   void GetAppLaunchedMetricParams(
       AppLaunchedMetricParams* metric_params) override;
@@ -142,6 +145,8 @@
   int open_assistant_ui_count_ = 0;
   int next_profile_app_count_ = 0;
   int show_wallpaper_context_menu_count_ = 0;
+  AppListState app_list_page_ = AppListState::kInvalidState;
+  AppListViewState app_list_view_state_ = AppListViewState::kClosed;
   bool is_tablet_mode_ = false;
   bool should_show_suggested_content_info_ = false;
   std::map<size_t, int> open_search_result_counts_;
diff --git a/ash/app_list/app_list_view_delegate.h b/ash/app_list/app_list_view_delegate.h
index 4214530..d02bd102 100644
--- a/ash/app_list/app_list_view_delegate.h
+++ b/ash/app_list/app_list_view_delegate.h
@@ -176,6 +176,17 @@
   // pressed to indicate not to show the view any more.
   virtual void MarkSuggestedContentInfoDismissed() = 0;
 
+  // Gets the app list page currently shown in the fullscreen app list, as
+  // reported from the app list view using `OnAppListPageChanged()`.
+  virtual AppListState GetCurrentAppListPage() const = 0;
+
+  // Called when the page shown in the app list contents view is updated.
+  virtual void OnAppListPageChanged(AppListState page) = 0;
+
+  // Gets the current app list view state, as reported by app list view using
+  // `OnViewStateChanged()`. Tracked for fullscreen app list view only.
+  virtual AppListViewState GetAppListViewState() const = 0;
+
   // Called when the app list view state is updated.
   virtual void OnViewStateChanged(AppListViewState state) = 0;
 
diff --git a/ash/app_list/model/app_list_model.cc b/ash/app_list/model/app_list_model.cc
index f544c241..2a82614e 100644
--- a/ash/app_list/model/app_list_model.cc
+++ b/ash/app_list/model/app_list_model.cc
@@ -42,20 +42,6 @@
     observer.OnAppListModelStatusChanged();
 }
 
-void AppListModel::SetState(AppListState state) {
-  if (state_ == state)
-    return;
-
-  auto old_state = state_;
-  state_ = state;
-  for (auto& observer : observers_)
-    observer.OnAppListStateChanged(state_, old_state);
-}
-
-void AppListModel::SetStateFullscreen(AppListViewState state) {
-  state_fullscreen_ = state;
-}
-
 AppListItem* AppListModel::FindItem(const std::string& id) {
   AppListItem* item = top_level_item_list_->FindItem(id);
   if (item)
diff --git a/ash/app_list/model/app_list_model.h b/ash/app_list/model/app_list_model.h
index f4cfcf29..199b27ea 100644
--- a/ash/app_list/model/app_list_model.h
+++ b/ash/app_list/model/app_list_model.h
@@ -46,13 +46,6 @@
 
   void SetStatus(AppListModelStatus status);
 
-  void SetState(AppListState state);
-  AppListState state() const { return state_; }
-
-  // The current state of the AppListView. Controlled by AppListView.
-  void SetStateFullscreen(AppListViewState state);
-  AppListViewState state_fullscreen() const { return state_fullscreen_; }
-
   // Finds the item matching |id|.
   AppListItem* FindItem(const std::string& id);
 
@@ -178,9 +171,7 @@
   std::unique_ptr<AppListItemList> top_level_item_list_;
 
   AppListModelStatus status_ = AppListModelStatus::kStatusNormal;
-  AppListState state_ = AppListState::kInvalidState;
-  // The AppListView state. Controlled by the AppListView.
-  AppListViewState state_fullscreen_ = AppListViewState::kClosed;
+
   base::ObserverList<AppListModelObserver, true> observers_;
   base::ScopedMultiSourceObservation<AppListItemList, AppListItemListObserver>
       item_list_scoped_observations_{this};
diff --git a/ash/app_list/model/app_list_model_observer.h b/ash/app_list/model/app_list_model_observer.h
index 2a6042a..87804c3 100644
--- a/ash/app_list/model/app_list_model_observer.h
+++ b/ash/app_list/model/app_list_model_observer.h
@@ -33,10 +33,6 @@
   // Triggered after |item| has moved, changed folders, or changed properties.
   virtual void OnAppListItemUpdated(AppListItem* item) {}
 
-  // Triggered after AppListState has changed.
-  virtual void OnAppListStateChanged(AppListState new_state,
-                                     AppListState old_state) {}
-
   // Triggered when the custom launcher page enabled state is changed.
   virtual void OnCustomLauncherPageEnabledStateChanged(bool enabled) {}
 
diff --git a/ash/app_list/model/search/search_box_model.cc b/ash/app_list/model/search/search_box_model.cc
index 2f3633f..3ee0e60 100644
--- a/ash/app_list/model/search/search_box_model.cc
+++ b/ash/app_list/model/search/search_box_model.cc
@@ -16,10 +16,6 @@
 
 SearchBoxModel::~SearchBoxModel() = default;
 
-void SearchBoxModel::SetTabletMode(bool is_tablet_mode) {
-  is_tablet_mode_ = is_tablet_mode;
-}
-
 void SearchBoxModel::SetShowAssistantButton(bool show) {
   if (show_assistant_button_ == show)
     return;
diff --git a/ash/app_list/model/search/search_box_model.h b/ash/app_list/model/search/search_box_model.h
index faf7239..32e56b0 100644
--- a/ash/app_list/model/search/search_box_model.h
+++ b/ash/app_list/model/search/search_box_model.h
@@ -25,9 +25,6 @@
   SearchBoxModel& operator=(const SearchBoxModel&) = delete;
   ~SearchBoxModel();
 
-  void SetTabletMode(bool is_tablet_mode);
-  bool is_tablet_mode() const { return is_tablet_mode_; }
-
   void SetShowAssistantButton(bool show);
   bool show_assistant_button() const { return show_assistant_button_; }
 
@@ -45,7 +42,6 @@
  private:
   std::u16string text_;
   bool search_engine_is_google_ = false;
-  bool is_tablet_mode_ = false;
   bool show_assistant_button_ = false;
 
   base::ObserverList<SearchBoxModelObserver> observers_;
diff --git a/ash/app_list/model/search/search_model.cc b/ash/app_list/model/search/search_model.cc
index cd970744..01939aa 100644
--- a/ash/app_list/model/search/search_model.cc
+++ b/ash/app_list/model/search/search_model.cc
@@ -18,10 +18,6 @@
 
 SearchModel::~SearchModel() {}
 
-void SearchModel::SetTabletMode(bool is_tablet_mode) {
-  search_box_->SetTabletMode(is_tablet_mode);
-}
-
 void SearchModel::SetSearchEngineIsGoogle(bool is_google) {
   search_box_->SetSearchEngineIsGoogle(is_google);
 }
diff --git a/ash/app_list/model/search/search_model.h b/ash/app_list/model/search/search_model.h
index ebc1bf5f..22a3099 100644
--- a/ash/app_list/model/search/search_model.h
+++ b/ash/app_list/model/search/search_model.h
@@ -34,10 +34,6 @@
 
   ~SearchModel();
 
-  // Whether tablet mode is active. Controlled by AppListView.
-  void SetTabletMode(bool is_tablet_mode);
-  bool tablet_mode() const { return search_box_->is_tablet_mode(); }
-
   void SetSearchEngineIsGoogle(bool is_google);
   bool search_engine_is_google() const {
     return search_box_->search_engine_is_google();
diff --git a/ash/app_list/views/app_list_main_view.cc b/ash/app_list/views/app_list_main_view.cc
index bad09f4..aba3611 100644
--- a/ash/app_list/views/app_list_main_view.cc
+++ b/ash/app_list/views/app_list_main_view.cc
@@ -14,7 +14,6 @@
 #include "ash/app_list/app_list_view_delegate.h"
 #include "ash/app_list/model/app_list_folder_item.h"
 #include "ash/app_list/model/app_list_item.h"
-#include "ash/app_list/model/app_list_model.h"
 #include "ash/app_list/views/app_list_folder_view.h"
 #include "ash/app_list/views/app_list_item_view.h"
 #include "ash/app_list/views/app_list_view.h"
@@ -52,20 +51,15 @@
 AppListMainView::AppListMainView(AppListViewDelegate* delegate,
                                  AppListView* app_list_view)
     : delegate_(delegate),
-      model_(delegate->GetModel()),
       search_model_(delegate->GetSearchModel()),
       app_list_view_(app_list_view) {
   // We need a layer to apply transform to in small display so that the apps
   // grid fits in the display.
   SetPaintToLayer();
   layer()->SetFillsBoundsOpaquely(false);
-
-  model_->AddObserver(this);
 }
 
-AppListMainView::~AppListMainView() {
-  model_->RemoveObserver(this);
-}
+AppListMainView::~AppListMainView() = default;
 
 void AppListMainView::Init(int initial_apps_page,
                            SearchBoxView* search_box_view) {
@@ -81,7 +75,7 @@
 void AppListMainView::AddContentsViews() {
   DCHECK(search_box_view_);
   auto contents_view = std::make_unique<ContentsView>(app_list_view_);
-  contents_view->Init(model_);
+  contents_view->Init();
   contents_view->SetPaintToLayer(ui::LAYER_NOT_DRAWN);
   contents_view->layer()->SetMasksToBounds(true);
   contents_view_ = AddChildView(std::move(contents_view));
@@ -105,9 +99,6 @@
 }
 
 void AppListMainView::ModelChanged() {
-  model_->RemoveObserver(this);
-  model_ = delegate_->GetModel();
-  model_->AddObserver(this);
   search_model_ = delegate_->GetSearchModel();
   search_box_view_->ModelChanged();
   delete contents_view_;
@@ -145,16 +136,6 @@
     contents_view_->SetBoundsRect(rect);
 }
 
-// AppListModelObserver overrides:
-void AppListMainView::OnAppListStateChanged(AppListState new_state,
-                                            AppListState old_state) {
-  if (new_state == AppListState::kStateEmbeddedAssistant) {
-    search_box_view_->SetVisible(false);
-  } else {
-    search_box_view_->SetVisible(true);
-  }
-}
-
 void AppListMainView::QueryChanged(SearchBoxViewBase* sender) {
   std::u16string raw_query = search_model_->search_box()->text();
   std::u16string query;
diff --git a/ash/app_list/views/app_list_main_view.h b/ash/app_list/views/app_list_main_view.h
index 6dbc26e..ae5655a 100644
--- a/ash/app_list/views/app_list_main_view.h
+++ b/ash/app_list/views/app_list_main_view.h
@@ -5,7 +5,6 @@
 #ifndef ASH_APP_LIST_VIEWS_APP_LIST_MAIN_VIEW_H_
 #define ASH_APP_LIST_VIEWS_APP_LIST_MAIN_VIEW_H_
 
-#include "ash/app_list/model/app_list_model_observer.h"
 #include "ash/app_list/model/search/search_model.h"
 #include "ash/ash_export.h"
 #include "ash/search_box/search_box_view_delegate.h"
@@ -16,7 +15,6 @@
 
 namespace ash {
 
-class AppListModel;
 class AppListView;
 class AppListViewDelegate;
 class ApplicationDragAndDropHost;
@@ -28,7 +26,6 @@
 // AppListMainView contains the normal view of the app list, which is shown
 // when the user is signed in.
 class ASH_EXPORT AppListMainView : public views::View,
-                                   public AppListModelObserver,
                                    public SearchBoxViewDelegate {
  public:
   AppListMainView(AppListViewDelegate* delegate, AppListView* app_list_view);
@@ -52,7 +49,6 @@
       ApplicationDragAndDropHost* drag_and_drop_host);
 
   ContentsView* contents_view() const { return contents_view_; }
-  AppListModel* model() { return model_; }
   SearchModel* search_model() { return search_model_; }
   AppListViewDelegate* view_delegate() { return delegate_; }
 
@@ -63,10 +59,6 @@
   const char* GetClassName() const override;
   void Layout() override;
 
-  // AppListModelObserver overrides:
-  void OnAppListStateChanged(AppListState new_state,
-                             AppListState old_state) override;
-
  private:
   // Adds the ContentsView.
   void AddContentsViews();
@@ -84,7 +76,6 @@
   bool CanSelectSearchResults() override;
 
   AppListViewDelegate* delegate_;  // Owned by parent view (AppListView).
-  AppListModel* model_;        // Unowned; ownership is handled by |delegate_|.
   SearchModel* search_model_;  // Unowned; ownership is handled by |delegate_|.
 
   // Created by AppListView. Owned by views hierarchy.
diff --git a/ash/app_list/views/app_list_view.cc b/ash/app_list/views/app_list_view.cc
index 9f1f80d..8f59eb27 100644
--- a/ash/app_list/views/app_list_view.cc
+++ b/ash/app_list/views/app_list_view.cc
@@ -12,7 +12,6 @@
 
 #include "ash/app_list/app_list_metrics.h"
 #include "ash/app_list/app_list_util.h"
-#include "ash/app_list/model/app_list_model.h"
 #include "ash/app_list/views/app_list_a11y_announcer.h"
 #include "ash/app_list/views/app_list_folder_view.h"
 #include "ash/app_list/views/app_list_main_view.h"
@@ -635,8 +634,6 @@
 
 AppListView::AppListView(AppListViewDelegate* delegate)
     : delegate_(delegate),
-      model_(delegate->GetModel()),
-      search_model_(delegate->GetSearchModel()),
       is_background_blur_enabled_(features::IsBackgroundBlurEnabled()),
       state_transition_notifier_(
           std::make_unique<StateTransitionNotifier>(this)),
@@ -1044,7 +1041,8 @@
   }
 
   if (!search_box_view_->is_search_box_active() &&
-      model_->state() != AppListState::kStateEmbeddedAssistant) {
+      delegate_->GetCurrentAppListPage() !=
+          AppListState::kStateEmbeddedAssistant) {
     if (!delegate_->IsInTabletMode())
       Dismiss();
     return;
@@ -1546,7 +1544,6 @@
 
 void AppListView::OnTabletModeChanged(bool started) {
   search_box_view_->OnTabletModeChanged(started);
-  search_model_->SetTabletMode(started);
   app_list_main_view_->contents_view()->OnTabletModeChanged(started);
 
   if (is_in_drag_) {
@@ -1728,7 +1725,6 @@
   StartAnimationForState(new_state_override);
   MaybeIncreasePrivacyInfoRowShownCounts(new_state_override);
   RecordStateTransitionForUma(new_state_override);
-  model_->SetStateFullscreen(new_state_override);
   app_list_state_ = new_state_override;
   if (delegate_)
     delegate_->OnViewStateChanged(new_state_override);
@@ -1762,7 +1758,7 @@
   if (!GetWidget())
     return;
   gfx::NativeView window = GetWidget()->GetNativeView();
-  AppListState contents_view_state = app_list_main_view_->model()->state();
+  AppListState contents_view_state = delegate_->GetCurrentAppListPage();
   if (window) {
     if (contents_view_state == AppListState::kStateSearchResults ||
         contents_view_state == AppListState::kStateEmbeddedAssistant) {
@@ -2415,8 +2411,7 @@
   int number_of_apps_in_folders = 0;
   int number_of_folders = 0;
   int non_system_folders = 0;
-  AppListItemList* item_list =
-      app_list_main_view_->model()->top_level_item_list();
+  AppListItemList* item_list = delegate_->GetModel()->top_level_item_list();
   for (size_t i = 0; i < item_list->item_count(); ++i) {
     AppListItem* item = item_list->item_at(i);
     if (item->GetItemType() != AppListFolderItem::kItemType)
diff --git a/ash/app_list/views/app_list_view.h b/ash/app_list/views/app_list_view.h
index 29400a1f..a780222 100644
--- a/ash/app_list/views/app_list_view.h
+++ b/ash/app_list/views/app_list_view.h
@@ -44,12 +44,10 @@
 class ApplicationDragAndDropHost;
 class AppListBackgroundShieldView;
 class AppListMainView;
-class AppListModel;
 class AppsGridView;
 class PagedAppsGridView;
 class PaginationModel;
 class SearchBoxView;
-class SearchModel;
 class StateTransitionNotifier;
 
 FORWARD_DECLARE_TEST(AppListControllerImplTest,
@@ -553,8 +551,6 @@
   void ResetSubpixelPositionOffset(ui::Layer* layer);
 
   AppListViewDelegate* const delegate_;
-  AppListModel* const model_;        // Not Owned.
-  SearchModel* const search_model_;  // Not Owned.
 
   // Keeps track of the number of locks that prevent the app list view
   // from creating app list transition accessibility events. This is used to
diff --git a/ash/app_list/views/app_list_view_unittest.cc b/ash/app_list/views/app_list_view_unittest.cc
index 641d2c3..ae8286ef 100644
--- a/ash/app_list/views/app_list_view_unittest.cc
+++ b/ash/app_list/views/app_list_view_unittest.cc
@@ -251,7 +251,7 @@
       }
     }
 
-    if (state != delegate_->GetModel()->state()) {
+    if (state != delegate_->GetCurrentAppListPage()) {
       ADD_FAILURE() << "Model state does not match state "
                     << static_cast<int>(state);
       success = false;
@@ -2262,7 +2262,7 @@
 
   ash::AppListState expected = ash::AppListState::kStateApps;
   EXPECT_TRUE(main_view->contents_view()->IsStateActive(expected));
-  EXPECT_EQ(expected, delegate_->GetModel()->state());
+  EXPECT_EQ(expected, delegate_->GetCurrentAppListPage());
 }
 
 // Tests switching rapidly between multiple pages of the launcher.
@@ -2820,12 +2820,12 @@
   Initialize(false /*is_tablet_mode*/);
   Show();
 
-  EXPECT_TRUE(search_box_view()->GetVisible());
+  EXPECT_TRUE(search_box_view()->GetWidget()->IsVisible());
 
   contents_view()->ShowEmbeddedAssistantUI(true);
 
   EXPECT_TRUE(contents_view()->IsShowingEmbeddedAssistantUI());
-  EXPECT_FALSE(search_box_view()->GetVisible());
+  EXPECT_FALSE(search_box_view()->GetWidget()->IsVisible());
 }
 
 // Tests that the expand arrow cannot be seen when opening the app list with
diff --git a/ash/app_list/views/contents_view.cc b/ash/app_list/views/contents_view.cc
index a4366de..65c0d64 100644
--- a/ash/app_list/views/contents_view.cc
+++ b/ash/app_list/views/contents_view.cc
@@ -117,15 +117,12 @@
              : kDefaultSearchBoxTopMarginInPeekingState;
 }
 
-void ContentsView::Init(AppListModel* model) {
-  DCHECK(model);
-  model_ = model;
-
+void ContentsView::Init() {
   AppListViewDelegate* view_delegate = GetAppListMainView()->view_delegate();
 
-  apps_container_view_ =
-      AddLauncherPage(std::make_unique<AppsContainerView>(this, model),
-                      AppListState::kStateApps);
+  apps_container_view_ = AddLauncherPage(
+      std::make_unique<AppsContainerView>(this, view_delegate->GetModel()),
+      AppListState::kStateApps);
 
   // Search results UI.
   auto search_result_page_view =
@@ -335,7 +332,7 @@
 
   app_list_pages_[GetActivePageIndex()]->OnWillBeShown();
 
-  GetAppListMainView()->model()->SetState(state);
+  GetAppListMainView()->view_delegate()->OnAppListPageChanged(state);
   UpdateSearchBoxVisibility(state);
   app_list_view_->UpdateWindowTitle();
 }
diff --git a/ash/app_list/views/contents_view.h b/ash/app_list/views/contents_view.h
index 7110697..7d4f893 100644
--- a/ash/app_list/views/contents_view.h
+++ b/ash/app_list/views/contents_view.h
@@ -84,7 +84,7 @@
 
   // Initialize the pages of the launcher. Should be called after
   // set_contents_switcher_view().
-  void Init(AppListModel* model);
+  void Init();
 
   // Resets the state of the view so it is ready to be shown.
   void ResetForShow();
@@ -267,9 +267,6 @@
   int GetSearchBoxTopForViewState(AppListState state,
                                   AppListViewState view_state) const;
 
-  // Unowned pointer to application list model.
-  AppListModel* model_ = nullptr;
-
   // Sub-views of the ContentsView. All owned by the views hierarchy.
   AssistantPageView* assistant_page_view_ = nullptr;
   AppsContainerView* apps_container_view_ = nullptr;
diff --git a/ash/app_list/views/search_box_view_unittest.cc b/ash/app_list/views/search_box_view_unittest.cc
index b929663..92d63ba2 100644
--- a/ash/app_list/views/search_box_view_unittest.cc
+++ b/ash/app_list/views/search_box_view_unittest.cc
@@ -112,7 +112,7 @@
         std::make_unique<KeyPressCounterView>(app_list_view_));
 
     widget_->Show();
-    counter_view_->Init(view_delegate_.GetModel());
+    counter_view_->Init();
     SetContentsView(counter_view_);
   }
 
@@ -655,7 +655,7 @@
   view_delegate()->SetShouldShowSuggestedContentInfo(true);
   auto* contents_view = widget()->GetContentsView()->AddChildView(
       std::make_unique<KeyPressCounterView>(app_list_view()));
-  contents_view->Init(view_delegate()->GetModel());
+  contents_view->Init();
   SetContentsView(contents_view);
 
   PrivacyContainerView* const privacy_container_view =
@@ -710,7 +710,7 @@
   view_delegate()->SetShouldShowSuggestedContentInfo(true);
   auto* contents_view = widget()->GetContentsView()->AddChildView(
       std::make_unique<KeyPressCounterView>(app_list_view()));
-  contents_view->Init(view_delegate()->GetModel());
+  contents_view->Init();
   SetContentsView(contents_view);
 
   PrivacyContainerView* const privacy_container_view =
@@ -741,7 +741,7 @@
   view_delegate()->SetShouldShowSuggestedContentInfo(true);
   auto* contents_view = widget()->GetContentsView()->AddChildView(
       std::make_unique<KeyPressCounterView>(app_list_view()));
-  contents_view->Init(view_delegate()->GetModel());
+  contents_view->Init();
   SetContentsView(contents_view);
 
   PrivacyContainerView* const privacy_container_view =
@@ -784,7 +784,7 @@
   view_delegate()->SetShouldShowSuggestedContentInfo(true);
   auto* contents_view = widget()->GetContentsView()->AddChildView(
       std::make_unique<KeyPressCounterView>(app_list_view()));
-  contents_view->Init(view_delegate()->GetModel());
+  contents_view->Init();
   SetContentsView(contents_view);
 
   PrivacyContainerView* const privacy_container_view =
diff --git a/ash/app_list/views/search_result_list_view.cc b/ash/app_list/views/search_result_list_view.cc
index b0f911a6..6cf986f 100644
--- a/ash/app_list/views/search_result_list_view.cc
+++ b/ash/app_list/views/search_result_list_view.cc
@@ -233,8 +233,8 @@
 
   auto* result = view->result();
 
-  RecordSearchResultOpenSource(result, view_delegate_->GetModel(),
-                               view_delegate_->GetSearchModel());
+  RecordSearchResultOpenSource(result, view_delegate_->GetAppListViewState(),
+                               view_delegate_->IsInTabletMode());
   view_delegate_->NotifySearchResultsForLogging(
       view_delegate_->GetSearchModel()->search_box()->text(),
       GetSearchResultsForLogging(search_result_views_),
diff --git a/ash/app_list/views/search_result_suggestion_chip_view.cc b/ash/app_list/views/search_result_suggestion_chip_view.cc
index 4c34d37..187497b 100644
--- a/ash/app_list/views/search_result_suggestion_chip_view.cc
+++ b/ash/app_list/views/search_result_suggestion_chip_view.cc
@@ -255,8 +255,8 @@
 void SearchResultSuggestionChipView::OnButtonPressed(const ui::Event& event) {
   DCHECK(result());
   LogAppLaunch(index_in_container());
-  RecordSearchResultOpenSource(result(), view_delegate_->GetModel(),
-                               view_delegate_->GetSearchModel());
+  RecordSearchResultOpenSource(result(), view_delegate_->GetAppListViewState(),
+                               view_delegate_->IsInTabletMode());
   view_delegate_->OpenSearchResult(
       result()->id(), result()->result_type(), event.flags(),
       AppListLaunchedFrom::kLaunchedFromSuggestionChip,
diff --git a/ash/app_list/views/search_result_tile_item_view.cc b/ash/app_list/views/search_result_tile_item_view.cc
index 9d72632..1a8e0fb 100644
--- a/ash/app_list/views/search_result_tile_item_view.cc
+++ b/ash/app_list/views/search_result_tile_item_view.cc
@@ -363,7 +363,7 @@
       metric_params, GetAppType(),
       base::BindOnce(&SearchResultTileItemView::OnMenuClosed,
                      weak_ptr_factory_.GetWeakPtr()),
-      view_delegate_->GetSearchModel()->tablet_mode());
+      view_delegate_->IsInTabletMode());
   context_menu_->Run(anchor_rect, views::MenuAnchorPosition::kBubbleRight,
                      views::MenuRunner::HAS_MNEMONICS |
                          views::MenuRunner::USE_TOUCHABLE_LAYOUT |
@@ -409,8 +409,8 @@
 
   LogAppLaunchForSuggestedApp();
 
-  RecordSearchResultOpenSource(result(), view_delegate_->GetModel(),
-                               view_delegate_->GetSearchModel());
+  RecordSearchResultOpenSource(result(), view_delegate_->GetAppListViewState(),
+                               view_delegate_->IsInTabletMode());
   view_delegate_->OpenSearchResult(result()->id(), result()->result_type(),
                                    event_flags,
                                    AppListLaunchedFrom::kLaunchedFromSearchBox,
@@ -505,17 +505,15 @@
 AppListMenuModelAdapter::AppListViewAppType
 SearchResultTileItemView::GetAppType() const {
   if (IsSuggestedAppTile()) {
-    if (view_delegate_->GetModel()->state_fullscreen() ==
-        AppListViewState::kPeeking) {
+    if (view_delegate_->GetAppListViewState() == AppListViewState::kPeeking) {
       return AppListMenuModelAdapter::PEEKING_SUGGESTED;
     } else {
       return AppListMenuModelAdapter::FULLSCREEN_SUGGESTED;
     }
   } else {
-    if (view_delegate_->GetModel()->state_fullscreen() ==
-        AppListViewState::kHalf) {
+    if (view_delegate_->GetAppListViewState() == AppListViewState::kHalf) {
       return AppListMenuModelAdapter::HALF_SEARCH_RESULT;
-    } else if (view_delegate_->GetModel()->state_fullscreen() ==
+    } else if (view_delegate_->GetAppListViewState() ==
                AppListViewState::kFullscreenSearch) {
       return AppListMenuModelAdapter::FULLSCREEN_SEARCH_RESULT;
     }
diff --git a/ash/app_list/views/search_result_view.cc b/ash/app_list/views/search_result_view.cc
index 5cd1a43..09d1830 100644
--- a/ash/app_list/views/search_result_view.cc
+++ b/ash/app_list/views/search_result_view.cc
@@ -667,7 +667,7 @@
       metric_params, AppListMenuModelAdapter::SEARCH_RESULT,
       base::BindOnce(&SearchResultView::OnMenuClosed,
                      weak_ptr_factory_.GetWeakPtr()),
-      view_delegate_->GetSearchModel()->tablet_mode());
+      view_delegate_->IsInTabletMode());
   context_menu_->Run(gfx::Rect(point, gfx::Size()),
                      views::MenuAnchorPosition::kTopLeft,
                      views::MenuRunner::HAS_MNEMONICS);
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 2885db1..6f78ec4 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1694,6 +1694,12 @@
       <message name="IDS_ASH_NETWORK_CELLULAR_SETUP_NOTIFICATION_MESSAGE" desc="Message displayed in the system notification shown post-OOBE if a user has no activated mobile networks.">
         Tap to complete set up
       </message>
+      <message name="IDS_ASH_NETWORK_RESET_EUICC_NOTIFICATION_TITLE" desc="Title used for the system notification shown when the reset euicc operation is initiated.">
+        eSIM connection unavailable
+      </message>
+      <message name="IDS_ASH_NETWORK_RESET_EUICC_NOTIFICATION_MESSAGE" desc="Message displayed in the system notification shown when the reset euicc operation is initiated.">
+        Your administrator has removed all eSIM profiles. Contact your administrator for more info.
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_LABEL" desc="The label text used when network is not connected. [CHAR_LIMIT=14]">
         Not connected
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_NETWORK_RESET_EUICC_NOTIFICATION_MESSAGE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_NETWORK_RESET_EUICC_NOTIFICATION_MESSAGE.png.sha1
new file mode 100644
index 0000000..5f372094f
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_NETWORK_RESET_EUICC_NOTIFICATION_MESSAGE.png.sha1
@@ -0,0 +1 @@
+79228bebb6a5d609393df1218a8f393c05d72a81
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_NETWORK_RESET_EUICC_NOTIFICATION_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_NETWORK_RESET_EUICC_NOTIFICATION_TITLE.png.sha1
new file mode 100644
index 0000000..70ffde1
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_NETWORK_RESET_EUICC_NOTIFICATION_TITLE.png.sha1
@@ -0,0 +1 @@
+7e55cb34a65f3d50948987c050973264a7168ec0
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_controller.cc b/ash/capture_mode/capture_mode_controller.cc
index 433aa02..357b78b 100644
--- a/ash/capture_mode/capture_mode_controller.cc
+++ b/ash/capture_mode/capture_mode_controller.cc
@@ -1263,6 +1263,22 @@
     return;
   }
 
+  // During the 3-second count down, screen content might have changed such that
+  // admin-restricted or HDCP content became present. We must check again.
+  const CaptureAllowance allowance =
+      IsCaptureAllowedByEnterprisePolicies(*capture_params);
+  if (allowance != CaptureAllowance::kAllowed) {
+    Stop();
+    ShowDisabledNotification(allowance);
+    return;
+  }
+
+  if (ShouldBlockRecordingForContentProtection(capture_params->window)) {
+    Stop();
+    ShowDisabledNotification(CaptureAllowance::kDisallowedByHdcp);
+    return;
+  }
+
   // In Projector mode, the creation of the DriveFS folder that will host the
   // video is asynchronous. We don't want the user to be able to bail out of the
   // session at this point, since we don't want to create that folder in vain.
@@ -1315,24 +1331,14 @@
       capture_mode_session_->ReleaseLayer();
   session_layer->set_delegate(nullptr);
 
+  // At this point, recording is guaranteed to start, and cannot be prevented by
+  // DLP or user cancellation.
+  capture_mode_session_->set_is_stopping_to_start_video_recording(true);
+
   // Stop the capture session now, so the bar doesn't show up in the captured
   // video.
   Stop();
 
-  // During the 3-second count down, screen content might have changed such that
-  // admin-restricted or HDCP content became present. We must check again.
-  const CaptureAllowance allowance =
-      IsCaptureAllowedByEnterprisePolicies(capture_params);
-  if (allowance != CaptureAllowance::kAllowed) {
-    ShowDisabledNotification(allowance);
-    return;
-  }
-
-  if (ShouldBlockRecordingForContentProtection(capture_params.window)) {
-    ShowDisabledNotification(CaptureAllowance::kDisallowedByHdcp);
-    return;
-  }
-
   mojo::PendingRemote<viz::mojom::FrameSinkVideoCaptureOverlay>
       cursor_capture_overlay;
   auto cursor_overlay_receiver =
diff --git a/ash/capture_mode/capture_mode_session.cc b/ash/capture_mode/capture_mode_session.cc
index b8171e0f..d53c7952e 100644
--- a/ash/capture_mode/capture_mode_session.cc
+++ b/ash/capture_mode/capture_mode_session.cc
@@ -21,6 +21,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/display/mouse_cursor_event_filter.h"
 #include "ash/display/screen_orientation_controller.h"
+#include "ash/projector/projector_controller_impl.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
@@ -632,6 +633,11 @@
         IDS_ASH_SCREEN_CAPTURE_ALERT_CLOSE);
   }
   UpdateAutoclickMenuBoundsIfNeeded();
+
+  // Stopping the session for any reason other than starting video recording
+  // means a cancellation to an ongoing projector session (if any).
+  if (is_in_projector_mode_ && !is_stopping_to_start_video_recording_)
+    ProjectorControllerImpl::Get()->OnRecordingStartAborted();
 }
 
 aura::Window* CaptureModeSession::GetSelectedWindow() const {
diff --git a/ash/capture_mode/capture_mode_session.h b/ash/capture_mode/capture_mode_session.h
index ee31d69..684c237 100644
--- a/ash/capture_mode/capture_mode_session.h
+++ b/ash/capture_mode/capture_mode_session.h
@@ -84,6 +84,9 @@
     a11y_alert_on_session_exit_ = value;
   }
   bool is_shutting_down() const { return is_shutting_down_; }
+  void set_is_stopping_to_start_video_recording(bool value) {
+    is_stopping_to_start_video_recording_ = value;
+  }
 
   // Initializes the capture mode session. This should be called right after the
   // object is created.
@@ -406,6 +409,11 @@
   // True once Shutdown() is called.
   bool is_shutting_down_ = false;
 
+  // True when the session is being stopped to start video recording, at which
+  // point, it's guaranteed that recording will start and will not be blocked by
+  // any errors, DLP restrictions, or any user cancellation.
+  bool is_stopping_to_start_video_recording_ = false;
+
   // The object which handles tab focus while in a capture session.
   std::unique_ptr<CaptureModeSessionFocusCycler> focus_cycler_;
 
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index 7de55dd..1cba0dea 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -460,9 +460,9 @@
 
   void WaitForCountDownToFinish() {
     auto* controller = CaptureModeController::Get();
-    DCHECK(controller->IsActive());
     DCHECK_EQ(controller->type(), CaptureModeType::kVideo);
-    while (!controller->is_recording_in_progress()) {
+    while (controller->IsActive() &&
+           controller->capture_mode_session()->IsInCountDownAnimation()) {
       base::RunLoop run_loop;
       base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
           FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(100));
@@ -4503,6 +4503,8 @@
     window_ = CreateTestWindow(gfx::Rect(20, 30, 200, 200));
     CaptureModeController::Get()->SetUserCaptureRegion(kUserRegion,
                                                        /*by_user=*/true);
+    EXPECT_CALL(projector_client_, IsDriveFsMounted())
+        .WillRepeatedly(testing::Return(true));
   }
 
   void TearDown() override {
@@ -4514,8 +4516,6 @@
     auto* projector_session = ProjectorSession::Get();
     EXPECT_FALSE(projector_session->is_active());
     auto* projector_controller = ProjectorController::Get();
-    EXPECT_CALL(projector_client_, IsDriveFsMounted())
-        .WillRepeatedly(testing::Return(true));
     projector_controller->StartProjectorSession("projector_data");
     EXPECT_TRUE(projector_session->is_active());
   }
@@ -4656,6 +4656,111 @@
   controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
 }
 
+TEST_F(ProjectorCaptureModeIntegrationTests,
+       ProjectorSessionNeverStartsWhenCaptureModeIsBlocked) {
+  auto* controller = CaptureModeController::Get();
+  controller->SetSource(CaptureModeSource::kFullscreen);
+
+  auto* test_delegate =
+      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
+  test_delegate->set_is_allowed_by_policy(false);
+  ProjectorController::Get()->StartProjectorSession("projector_data");
+
+  // Both sessions will never start.
+  EXPECT_FALSE(controller->IsActive());
+  EXPECT_FALSE(ProjectorSession::Get()->is_active());
+  EXPECT_FALSE(controller->is_recording_in_progress());
+}
+
+namespace {
+
+enum AbortReason {
+  kBlockedByDlp,
+  kBlockedByPolicy,
+  kUserPressedEsc,
+};
+
+struct {
+  const std::string scope_trace;
+  const AbortReason reason;
+} kTestCases[] = {
+    {"Blocked by DLP", kBlockedByDlp},
+    {"Blocked by policy", kBlockedByPolicy},
+    {"User Pressed Esc", kUserPressedEsc},
+};
+
+}  // namespace
+
+TEST_F(ProjectorCaptureModeIntegrationTests,
+       ProjectorSessionAbortedBeforeCountDownStarts) {
+  auto* controller = CaptureModeController::Get();
+  controller->SetSource(CaptureModeSource::kFullscreen);
+
+  for (const auto& test_case : kTestCases) {
+    SCOPED_TRACE(test_case.scope_trace);
+    StartProjectorModeSession();
+    auto* test_delegate = static_cast<TestCaptureModeDelegate*>(
+        controller->delegate_for_testing());
+
+    switch (test_case.reason) {
+      case kBlockedByDlp:
+        test_delegate->set_is_allowed_by_dlp(false);
+        PressAndReleaseKey(ui::VKEY_RETURN);
+        break;
+      case kBlockedByPolicy:
+        test_delegate->set_is_allowed_by_policy(false);
+        PressAndReleaseKey(ui::VKEY_RETURN);
+        break;
+      case kUserPressedEsc:
+        PressAndReleaseKey(ui::VKEY_ESCAPE);
+        break;
+    }
+
+    // The session will end immediately without a count down.
+    EXPECT_FALSE(controller->IsActive());
+    EXPECT_FALSE(ProjectorSession::Get()->is_active());
+    EXPECT_FALSE(controller->is_recording_in_progress());
+
+    // Prepare for next iteration by resetting things back to default.
+    test_delegate->ResetAllowancesToDefault();
+  }
+}
+
+TEST_F(ProjectorCaptureModeIntegrationTests,
+       ProjectorSessionAbortedAfterCountDownStarts) {
+  ui::ScopedAnimationDurationScaleMode animation_scale(
+      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
+  auto* controller = CaptureModeController::Get();
+  controller->SetSource(CaptureModeSource::kFullscreen);
+
+  for (const auto& test_case : kTestCases) {
+    SCOPED_TRACE(test_case.scope_trace);
+    StartProjectorModeSession();
+    PressAndReleaseKey(ui::VKEY_RETURN);
+    auto* test_delegate = static_cast<TestCaptureModeDelegate*>(
+        controller->delegate_for_testing());
+
+    switch (test_case.reason) {
+      case kBlockedByDlp:
+        test_delegate->set_is_allowed_by_dlp(false);
+        break;
+      case kBlockedByPolicy:
+        test_delegate->set_is_allowed_by_policy(false);
+        break;
+      case kUserPressedEsc:
+        PressAndReleaseKey(ui::VKEY_ESCAPE);
+        break;
+    }
+
+    WaitForCountDownToFinish();
+    EXPECT_FALSE(ProjectorSession::Get()->is_active());
+    EXPECT_FALSE(controller->is_recording_in_progress());
+
+    // Prepare for next iteration by resetting things back to default.
+    test_delegate->ResetAllowancesToDefault();
+  }
+}
+
 TEST_F(ProjectorCaptureModeIntegrationTests, RecordingOverlayWidget) {
   auto* controller = CaptureModeController::Get();
   controller->SetSource(CaptureModeSource::kFullscreen);
diff --git a/ash/capture_mode/test_capture_mode_delegate.cc b/ash/capture_mode/test_capture_mode_delegate.cc
index 6cc7a7b..73d15b0 100644
--- a/ash/capture_mode/test_capture_mode_delegate.cc
+++ b/ash/capture_mode/test_capture_mode_delegate.cc
@@ -36,6 +36,11 @@
 
 TestCaptureModeDelegate::~TestCaptureModeDelegate() = default;
 
+void TestCaptureModeDelegate::ResetAllowancesToDefault() {
+  is_allowed_by_dlp_ = true;
+  is_allowed_by_policy_ = true;
+}
+
 viz::FrameSinkId TestCaptureModeDelegate::GetCurrentFrameSinkId() const {
   return recording_service_ ? recording_service_->GetCurrentFrameSinkId()
                             : viz::FrameSinkId();
@@ -86,11 +91,11 @@
 bool TestCaptureModeDelegate::IsCaptureAllowedByDlp(const aura::Window* window,
                                                     const gfx::Rect& bounds,
                                                     bool for_video) const {
-  return true;
+  return is_allowed_by_dlp_;
 }
 
 bool TestCaptureModeDelegate::IsCaptureAllowedByPolicy() const {
-  return true;
+  return is_allowed_by_policy_;
 }
 
 void TestCaptureModeDelegate::StartObservingRestrictedContent(
diff --git a/ash/capture_mode/test_capture_mode_delegate.h b/ash/capture_mode/test_capture_mode_delegate.h
index 6655f98..8fd3d59 100644
--- a/ash/capture_mode/test_capture_mode_delegate.h
+++ b/ash/capture_mode/test_capture_mode_delegate.h
@@ -34,6 +34,11 @@
   void set_on_recording_started_callback(base::OnceClosure callback) {
     on_recording_started_callback_ = std::move(callback);
   }
+  void set_is_allowed_by_dlp(bool value) { is_allowed_by_dlp_ = value; }
+  void set_is_allowed_by_policy(bool value) { is_allowed_by_policy_ = value; }
+
+  // Resets |is_allowed_by_policy_| and |is_allowed_by_dlp_| back to true.
+  void ResetAllowancesToDefault();
 
   // Gets the current frame sink id being captured by the service.
   viz::FrameSinkId GetCurrentFrameSinkId() const;
@@ -81,6 +86,8 @@
   std::unique_ptr<recording::RecordingServiceTestApi> recording_service_;
   base::FilePath fake_downloads_dir_;
   base::OnceClosure on_recording_started_callback_;
+  bool is_allowed_by_dlp_ = true;
+  bool is_allowed_by_policy_ = true;
 };
 
 }  // namespace ash
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 9714885..d71619d 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -862,6 +862,10 @@
 const base::Feature kPhoneHubCameraRoll{"PhoneHubCameraRoll",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables the incoming/ongoing call notification feature in Phone Hub.
+const base::Feature kPhoneHubCallNotification{
+    "PhoneHubCallNotification", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables the Recent Apps feature in Phone Hub, which allows users to relaunch
 // the streamed app.
 const base::Feature kPhoneHubRecentApps{"PhoneHubRecentApps",
@@ -1575,6 +1579,10 @@
   return base::FeatureList::IsEnabled(kPhoneHub);
 }
 
+bool IsPhoneHubCallNotificationEnabled() {
+  return base::FeatureList::IsEnabled(kPhoneHubCallNotification);
+}
+
 bool IsPhoneHubRecentAppsEnabled() {
   return base::FeatureList::IsEnabled(kPhoneHubRecentApps);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 24babae..d512de5 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -326,6 +326,8 @@
 extern const base::Feature kPerformantSplitViewResizing;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kPhoneHub;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kPhoneHubCameraRoll;
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kPhoneHubCallNotification;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kPhoneHubRecentApps;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kPinSetupForManagedUsers;
@@ -547,6 +549,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPhoneHubCameraRollEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPerformantSplitViewResizingEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPhoneHubEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPhoneHubCallNotificationEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPhoneHubRecentAppsEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPinAutosubmitBackfillFeatureEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPinAutosubmitFeatureEnabled();
diff --git a/ash/projector/projector_controller_impl.cc b/ash/projector/projector_controller_impl.cc
index d0ce925..9ee9cbba 100644
--- a/ash/projector/projector_controller_impl.cc
+++ b/ash/projector/projector_controller_impl.cc
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/projector/projector_client.h"
 #include "ash/public/cpp/projector/projector_session.h"
 #include "ash/shell.h"
+#include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/strings/stringprintf.h"
@@ -77,8 +78,12 @@
 
   auto* controller = CaptureModeController::Get();
   if (!controller->is_recording_in_progress()) {
-    projector_session_->Start(storage_dir);
+    // A capture mode session can be blocked by many factors, such as policy,
+    // DLP, ... etc. We don't start a Projector session until we're sure a
+    // capture session started.
     controller->Start(CaptureModeEntryType::kProjector);
+    if (controller->IsActive())
+      projector_session_->Start(storage_dir);
   }
 }
 
@@ -203,6 +208,21 @@
   client_->OpenProjectorApp();
 }
 
+void ProjectorControllerImpl::OnRecordingStartAborted() {
+  DCHECK(projector_session_->is_active());
+
+  // Delete the DriveFS path that might have been created for this aborted
+  // session if any.
+  if (projector_session_->screencast_container_path()) {
+    base::ThreadPool::PostTask(
+        FROM_HERE, {base::MayBlock()},
+        base::BindOnce(base::GetDeletePathRecursivelyCallback(),
+                       *projector_session_->screencast_container_path()));
+  }
+
+  projector_session_->Stop();
+}
+
 void ProjectorControllerImpl::OnLaserPointerPressed() {
   ui_controller_->OnLaserPointerPressed();
 }
diff --git a/ash/projector/projector_controller_impl.h b/ash/projector/projector_controller_impl.h
index 813aaff..1ada094 100644
--- a/ash/projector/projector_controller_impl.h
+++ b/ash/projector/projector_controller_impl.h
@@ -76,6 +76,11 @@
   void OnRecordingStarted();
   void OnRecordingEnded();
 
+  // Called by Capture Mode to notify us that a Projector-initiated recording
+  // session was aborted (i.e. recording was never started) due to e.g. user
+  // cancellation, an error, or a DLP/HDCP restriction.
+  void OnRecordingStartAborted();
+
   // Invoked when laser pointer button is pressed.
   void OnLaserPointerPressed();
   // Invoked when marker button is pressed.
diff --git a/ash/style/button_style.cc b/ash/style/button_style.cc
new file mode 100644
index 0000000..8873caf
--- /dev/null
+++ b/ash/style/button_style.cc
@@ -0,0 +1,192 @@
+// 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/style/button_style.h"
+
+#include "ash/style/ash_color_provider.h"
+#include "base/bind.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
+#include "ui/views/animation/ink_drop.h"
+#include "ui/views/animation/ink_drop_highlight.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/highlight_path_generator.h"
+
+namespace ash {
+
+namespace {
+
+constexpr gfx::Size kPillButtonSize(32, 32);
+constexpr int kIconSize = 20;
+constexpr int kIconPillButtonImageLabelSpacingDp = 8;
+
+constexpr gfx::Insets kInkDropInsets(4);
+
+SkColor GetPillButtonBackgroundColor(PillButton::Type type) {
+  AshColorProvider::ControlsLayerType color_id =
+      AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive;
+  switch (type) {
+    case PillButton::Type::kIcon:
+    case PillButton::Type::kIconless:
+    case PillButton::Type::kIconlessAccent:
+      break;
+    case PillButton::Type::kIconlessAlert:
+      color_id =
+          AshColorProvider::ControlsLayerType::kControlBackgroundColorAlert;
+      break;
+    case PillButton::Type::kIconlessProminent:
+      color_id =
+          AshColorProvider::ControlsLayerType::kControlBackgroundColorActive;
+      break;
+  }
+  return AshColorProvider::Get()->GetControlsLayerColor(color_id);
+}
+
+SkColor GetPillButtonTextColor(PillButton::Type type) {
+  AshColorProvider::ContentLayerType color_id =
+      AshColorProvider::ContentLayerType::kButtonLabelColor;
+  switch (type) {
+    case PillButton::Type::kIcon:
+    case PillButton::Type::kIconless:
+    case PillButton::Type::kIconlessProminent:
+      break;
+    case PillButton::Type::kIconlessAlert:
+      color_id = AshColorProvider::ContentLayerType::kButtonLabelColorPrimary;
+      break;
+    case PillButton::Type::kIconlessAccent:
+      color_id = AshColorProvider::ContentLayerType::kButtonLabelColorBlue;
+      break;
+  }
+  return AshColorProvider::Get()->GetContentLayerColor(color_id);
+}
+
+gfx::Insets GetInkDropInsets(TrayPopupInkDropStyle ink_drop_style) {
+  if (ink_drop_style == TrayPopupInkDropStyle::HOST_CENTERED ||
+      ink_drop_style == TrayPopupInkDropStyle::INSET_BOUNDS) {
+    return kInkDropInsets;
+  }
+  return gfx::Insets();
+}
+
+gfx::Size GetPillButtonSize(bool has_icon) {
+  gfx::Size button_size(kPillButtonSize);
+  if (has_icon) {
+    button_size.set_width(button_size.width() + kIconSize +
+                          kIconPillButtonImageLabelSpacingDp);
+  }
+  return button_size;
+}
+
+std::unique_ptr<views::InkDrop> CreateInkDrop(views::Button* host,
+                                              bool highlight_on_hover,
+                                              bool highlight_on_focus) {
+  return views::InkDrop::CreateInkDropForFloodFillRipple(
+      views::InkDrop::Get(host), highlight_on_hover, highlight_on_focus);
+}
+
+std::unique_ptr<views::InkDropRipple> CreateInkDropRipple(
+    TrayPopupInkDropStyle ink_drop_style,
+    const views::Button* host) {
+  const AshColorProvider::RippleAttributes ripple_attributes =
+      AshColorProvider::Get()->GetRippleAttributes();
+  return std::make_unique<views::FloodFillInkDropRipple>(
+      host->size(), GetInkDropInsets(ink_drop_style),
+      views::InkDrop::Get(host)->GetInkDropCenterBasedOnLastEvent(),
+      ripple_attributes.base_color, ripple_attributes.inkdrop_opacity);
+}
+
+std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight(
+    const views::View* host) {
+  const AshColorProvider::RippleAttributes ripple_attributes =
+      AshColorProvider::Get()->GetRippleAttributes();
+  auto highlight = std::make_unique<views::InkDropHighlight>(
+      gfx::SizeF(host->size()), ripple_attributes.base_color);
+  highlight->set_visible_opacity(ripple_attributes.highlight_opacity);
+  return highlight;
+}
+
+}  // namespace
+
+// static
+void PillButton::ConfigureInkDrop(views::Button* button,
+                                  TrayPopupInkDropStyle ink_drop_style,
+                                  bool highlight_on_hover,
+                                  bool highlight_on_focus) {
+  button->SetInstallFocusRingOnFocus(true);
+  views::InkDropHost* const ink_drop = views::InkDrop::Get(button);
+  ink_drop->SetMode(views::InkDropHost::InkDropMode::ON);
+  button->SetHasInkDropActionOnClick(true);
+  ink_drop->SetCreateInkDropCallback(base::BindRepeating(
+      &CreateInkDrop, button, highlight_on_hover, highlight_on_focus));
+  ink_drop->SetCreateRippleCallback(
+      base::BindRepeating(&CreateInkDropRipple, ink_drop_style, button));
+  ink_drop->SetCreateHighlightCallback(
+      base::BindRepeating(&CreateInkDropHighlight, button));
+}
+
+PillButton::PillButton(PressedCallback callback,
+                       const std::u16string& text,
+                       PillButton::Type type,
+                       const gfx::VectorIcon* icon)
+    : views::LabelButton(std::move(callback), text),
+      type_(type),
+      button_size_(GetPillButtonSize(type_ == PillButton::Type::kIcon)),
+      icon_(icon) {
+  SetHorizontalAlignment(gfx::ALIGN_CENTER);
+  SetBorder(views::CreateEmptyBorder(gfx::Insets()));
+  label()->SetElideBehavior(gfx::NO_ELIDE);
+  label()->SetSubpixelRenderingEnabled(false);
+  // TODO: Unify the font size, weight under ash/style as well.
+  label()->SetFontList(views::Label::GetDefaultFontList().Derive(
+      1, gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM));
+  ConfigureInkDrop(this, TrayPopupInkDropStyle::FILL_BOUNDS,
+                   /*highlight_on_hover=*/false, /*highlight_on_focus=*/false);
+  views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
+                                                button_size_.height() / 2.f);
+  SetBackground(views::CreateRoundedRectBackground(
+      GetPillButtonBackgroundColor(type), button_size_.height() / 2.f));
+}
+
+PillButton::~PillButton() = default;
+
+gfx::Size PillButton::CalculatePreferredSize() const {
+  return gfx::Size(label()->GetPreferredSize().width() + button_size_.width(),
+                   button_size_.height());
+}
+
+int PillButton::GetHeightForWidth(int width) const {
+  return button_size_.height();
+}
+
+void PillButton::OnThemeChanged() {
+  views::LabelButton::OnThemeChanged();
+
+  auto* color_provider = AshColorProvider::Get();
+  if (type_ == PillButton::Type::kIcon) {
+    DCHECK(icon_);
+    const SkColor enabled_icon_color = color_provider->GetContentLayerColor(
+        AshColorProvider::ContentLayerType::kButtonIconColor);
+    SetImage(views::Button::STATE_NORMAL,
+             gfx::CreateVectorIcon(*icon_, kIconSize, enabled_icon_color));
+    SetImage(views::Button::STATE_DISABLED,
+             gfx::CreateVectorIcon(
+                 *icon_, kIconSize,
+                 AshColorProvider::GetDisabledColor(enabled_icon_color)));
+    SetImageLabelSpacing(kIconPillButtonImageLabelSpacingDp);
+  }
+
+  const SkColor enabled_text_color = GetPillButtonTextColor(type_);
+  SetEnabledTextColors(enabled_text_color);
+  SetTextColor(views::Button::STATE_DISABLED,
+               AshColorProvider::GetDisabledColor(enabled_text_color));
+  views::FocusRing::Get(this)->SetColor(color_provider->GetControlsLayerColor(
+      AshColorProvider::ControlsLayerType::kFocusRingColor));
+  background()->SetNativeControlColor(GetPillButtonBackgroundColor(type_));
+}
+
+BEGIN_METADATA(PillButton, views::LabelButton)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/style/button_style.h b/ash/style/button_style.h
new file mode 100644
index 0000000..ea0c7c2a1
--- /dev/null
+++ b/ash/style/button_style.h
@@ -0,0 +1,68 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_STYLE_BUTTON_STYLE_H_
+#define ASH_STYLE_BUTTON_STYLE_H_
+
+#include "ash/system/tray/tray_popup_ink_drop_style.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/controls/button/label_button.h"
+
+namespace ash {
+
+// A label button with a rounded rectangle background. It can have an icon
+// inside as well, and its text and background colors will be different based on
+// the type of the button.
+class PillButton : public views::LabelButton {
+ public:
+  METADATA_HEADER(PillButton);
+
+  // Types of the PillButton.
+  enum class Type {
+    // PillButton with an icon, default text and background colors.
+    kIcon,
+    // PillButton without an icon, default text and background colors.
+    kIconless,
+    // PillButton without an icon, `kButtonLabelColorPrimary` as the text color
+    // and `kControlBackgroundColorAlert` as the background color.
+    kIconlessAlert,
+    // PillButton without an icon, `kButtonLabelColorBlue` as the text color and
+    // default background color.
+    kIconlessAccent,
+    // PillButton without an icon, default text color and
+    // `kControlBackgroundColorActive` as the background color.
+    kIconlessProminent
+  };
+
+  // TODO: Move this function outside of PillButton after we built up more
+  // button styles under this file. And remove all the corresponding functions
+  // from TrayPopupUtils to make all the clients get the ink drop styles from
+  // ash/style.
+  static void ConfigureInkDrop(views::Button* button,
+                               TrayPopupInkDropStyle style,
+                               bool highlight_on_hover,
+                               bool highlight_on_focus);
+
+  PillButton(PressedCallback callback,
+             const std::u16string& text,
+             Type type,
+             const gfx::VectorIcon* icon);
+  PillButton(const PillButton&) = delete;
+  PillButton& operator=(const PillButton&) = delete;
+  ~PillButton() override;
+
+  // views::LabelButton:
+  gfx::Size CalculatePreferredSize() const override;
+  int GetHeightForWidth(int width) const override;
+  void OnThemeChanged() override;
+
+ private:
+  const Type type_;
+  gfx::Size button_size_;
+  const gfx::VectorIcon* const icon_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_STYLE_BUTTON_STYLE_H_
diff --git a/ash/system/night_light/night_light_controller_unittest.cc b/ash/system/night_light/night_light_controller_unittest.cc
index a299a027..6a2e07b 100644
--- a/ash/system/night_light/night_light_controller_unittest.cc
+++ b/ash/system/night_light/night_light_controller_unittest.cc
@@ -1919,7 +1919,7 @@
   }
 
   // Turn color temperature down.
-  float ambient_temperature = SimulateAmbientColorFromPowerd(8000, 7350.0f);
+  SimulateAmbientColorFromPowerd(8000, 7350.0f);
   auto internal_rgb = GetDisplayCompositorRGBScaleFactors(kInternalDisplayId);
   auto external_rgb = GetDisplayCompositorRGBScaleFactors(kExternalDisplayId);
 
@@ -1932,7 +1932,7 @@
   EXPECT_TRUE((external_rgb - gfx::Vector3dF(1.0f, 1.0f, 1.0f)).IsZero());
 
   // Turn color temperature up.
-  ambient_temperature = SimulateAmbientColorFromPowerd(2700, 5800.0f);
+  SimulateAmbientColorFromPowerd(2700, 5800.0f);
   internal_rgb = GetDisplayCompositorRGBScaleFactors(kInternalDisplayId);
   external_rgb = GetDisplayCompositorRGBScaleFactors(kExternalDisplayId);
 
diff --git a/ash/system/unified/sign_out_button.cc b/ash/system/unified/sign_out_button.cc
deleted file mode 100644
index 4818178..0000000
--- a/ash/system/unified/sign_out_button.cc
+++ /dev/null
@@ -1,25 +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.
-
-#include "ash/system/unified/sign_out_button.h"
-
-#include "ash/session/session_controller_impl.h"
-#include "ash/shell.h"
-#include "ash/system/user/login_status.h"
-
-namespace ash {
-
-SignOutButton::SignOutButton(PressedCallback callback)
-    : RoundedLabelButton(std::move(callback),
-                         user::GetLocalizedSignOutStringForStatus(
-                             Shell::Get()->session_controller()->login_status(),
-                             false /* multiline */)) {}
-
-SignOutButton::~SignOutButton() = default;
-
-const char* SignOutButton::GetClassName() const {
-  return "SignOutButton";
-}
-
-}  // namespace ash
diff --git a/ash/system/unified/sign_out_button.h b/ash/system/unified/sign_out_button.h
deleted file mode 100644
index 8aac6dc..0000000
--- a/ash/system/unified/sign_out_button.h
+++ /dev/null
@@ -1,31 +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.
-
-#ifndef ASH_SYSTEM_UNIFIED_SIGN_OUT_BUTTON_H_
-#define ASH_SYSTEM_UNIFIED_SIGN_OUT_BUTTON_H_
-
-#include "ash/system/unified/rounded_label_button.h"
-#include "base/macros.h"
-
-namespace ash {
-
-// Sign out button to be shown in TopShortcutView with TopShortcutButtons.
-// Shows the label like "Sign out", "Exit guest", etc. depending on the session
-// status.
-class SignOutButton : public RoundedLabelButton {
- public:
-  explicit SignOutButton(PressedCallback callback);
-
-  SignOutButton(const SignOutButton&) = delete;
-  SignOutButton& operator=(const SignOutButton&) = delete;
-
-  ~SignOutButton() override;
-
-  // views::RoundedLabelButton:
-  const char* GetClassName() const override;
-};
-
-}  // namespace ash
-
-#endif  // ASH_SYSTEM_UNIFIED_SIGN_OUT_BUTTON_H_
diff --git a/ash/system/unified/top_shortcuts_view.cc b/ash/system/unified/top_shortcuts_view.cc
index 0da1adc..574a056 100644
--- a/ash/system/unified/top_shortcuts_view.cc
+++ b/ash/system/unified/top_shortcuts_view.cc
@@ -15,14 +15,16 @@
 #include "ash/shutdown_controller_impl.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/button_style.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/unified/collapse_button.h"
-#include "ash/system/unified/sign_out_button.h"
+#include "ash/system/unified/rounded_label_button.h"
 #include "ash/system/unified/top_shortcut_button.h"
 #include "ash/system/unified/unified_system_tray_controller.h"
 #include "ash/system/unified/user_chooser_detailed_view_controller.h"
 #include "ash/system/unified/user_chooser_view.h"
+#include "ash/system/user/login_status.h"
 #include "base/bind.h"
 #include "base/numerics/ranges.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -198,9 +200,14 @@
         UserChooserDetailedViewController::IsUserChooserEnabled());
     container_->AddUserAvatarButton(user_avatar_button_);
 
-    sign_out_button_ = new SignOutButton(
+    sign_out_button_ = new PillButton(
         base::BindRepeating(&UnifiedSystemTrayController::HandleSignOutAction,
-                            base::Unretained(controller)));
+                            base::Unretained(controller)),
+        user::GetLocalizedSignOutStringForStatus(
+            Shell::Get()->session_controller()->login_status(),
+            /*multiline=*/false),
+        PillButton::Type::kIconless,
+        /*icon=*/nullptr);
     container_->AddSignOutButton(sign_out_button_);
   }
 
diff --git a/ash/system/unified/top_shortcuts_view.h b/ash/system/unified/top_shortcuts_view.h
index 579a417..74ef741 100644
--- a/ash/system/unified/top_shortcuts_view.h
+++ b/ash/system/unified/top_shortcuts_view.h
@@ -19,7 +19,6 @@
 namespace ash {
 
 class CollapseButton;
-class SignOutButton;
 class TopShortcutButton;
 class TopShortcutsViewTest;
 class UnifiedSystemTrayController;
@@ -75,7 +74,7 @@
 
   // Owned by views hierarchy.
   views::Button* user_avatar_button_ = nullptr;
-  SignOutButton* sign_out_button_ = nullptr;
+  views::Button* sign_out_button_ = nullptr;
   TopShortcutButtonContainer* container_ = nullptr;
   TopShortcutButton* lock_button_ = nullptr;
   TopShortcutButton* settings_button_ = nullptr;
diff --git a/ash/system/unified/top_shortcuts_view_unittest.cc b/ash/system/unified/top_shortcuts_view_unittest.cc
index d2ec5faf5..ad2a8a3 100644
--- a/ash/system/unified/top_shortcuts_view_unittest.cc
+++ b/ash/system/unified/top_shortcuts_view_unittest.cc
@@ -7,7 +7,6 @@
 #include "ash/constants/ash_pref_names.h"
 #include "ash/session/test_session_controller_client.h"
 #include "ash/system/unified/collapse_button.h"
-#include "ash/system/unified/sign_out_button.h"
 #include "ash/system/unified/top_shortcut_button.h"
 #include "ash/system/unified/unified_system_tray_controller.h"
 #include "ash/system/unified/unified_system_tray_model.h"
diff --git a/ash/webui/diagnostics_ui/diagnostics_ui.cc b/ash/webui/diagnostics_ui/diagnostics_ui.cc
index ece4396..f3b5f67 100644
--- a/ash/webui/diagnostics_ui/diagnostics_ui.cc
+++ b/ash/webui/diagnostics_ui/diagnostics_ui.cc
@@ -328,13 +328,7 @@
     const diagnostics::SessionLogHandler::SelectFilePolicyCreator&
         select_file_policy_creator,
     HoldingSpaceClient* holding_space_client)
-    : ui::MojoWebDialogUI(web_ui),
-      session_log_handler_(std::make_unique<diagnostics::SessionLogHandler>(
-          select_file_policy_creator,
-          holding_space_client)) {
-  diagnostics_manager_ = std::make_unique<diagnostics::DiagnosticsManager>(
-      session_log_handler_.get());
-
+    : ui::MojoWebDialogUI(web_ui) {
   auto html_source = base::WrapUnique(
       content::WebUIDataSource::Create(kChromeUIDiagnosticsAppHost));
   html_source->OverrideContentSecurityPolicy(
diff --git a/ash/webui/diagnostics_ui/diagnostics_ui.h b/ash/webui/diagnostics_ui/diagnostics_ui.h
index f2d6f95..ce52f2d 100644
--- a/ash/webui/diagnostics_ui/diagnostics_ui.h
+++ b/ash/webui/diagnostics_ui/diagnostics_ui.h
@@ -58,7 +58,6 @@
   // metrics.
   base::Time open_timestamp_;
 
-  std::unique_ptr<diagnostics::SessionLogHandler> session_log_handler_;
   std::unique_ptr<diagnostics::DiagnosticsManager> diagnostics_manager_;
 };
 
diff --git a/ash/webui/diagnostics_ui/resources/overview_card.html b/ash/webui/diagnostics_ui/resources/overview_card.html
index d9cfc0f..006e924 100644
--- a/ash/webui/diagnostics_ui/resources/overview_card.html
+++ b/ash/webui/diagnostics_ui/resources/overview_card.html
@@ -5,13 +5,14 @@
   }
 
   #overviewCardContainer {
+    align-items: center;
     display: flex;
     height: 48px;
     justify-content: center;
   }
 
   #overviewChip {
-    margin-top: 14px;
+    margin-top: 8px;
   }
 </style>
 <div id="overviewCardContainer" tabindex="0"
diff --git a/ash/webui/scanning/resources/scan_preview.js b/ash/webui/scanning/resources/scan_preview.js
index 8f465a87..f1caa35 100644
--- a/ash/webui/scanning/resources/scan_preview.js
+++ b/ash/webui/scanning/resources/scan_preview.js
@@ -61,6 +61,15 @@
   /** @private {number} */
   actionToolbarWidth_: 0,
 
+  /**
+   * Stores the y value of the bottom of each scanned image in the preview div.
+   * The values are stored in the array in scanned image order so the value at
+   * [0] refers to the first scanned image, the value at [1] the second scanned
+   * image, etc.
+   * @private {!Array<number>}
+   */
+  scrollingIntervals_: [],
+
   properties: {
     /** @type {!AppState} */
     appState: {
@@ -338,7 +347,7 @@
     }
 
     // If the current page in view stays the same, do nothing.
-    const pageIndexInView = this.getCurrentPageInView_(scannedImages);
+    const pageIndexInView = this.getCurrentPageInView_();
     if (pageIndexInView === this.currentPageIndexInView_) {
       return;
     }
@@ -347,28 +356,32 @@
   },
 
   /**
-   * Calculates the current page in view. Returns the page index of the highest
-   * page in the viewport unless that page is scrolled halfway outside the
-   * viewport, then it'll return the following page number. Assumes each scanned
-   * image is the same height.
-   * @param {!HTMLCollection} scannedImages
+   * Calculates the index of the current page in view based on where the current
+   * position of the scroll bar is found in |scrollingIntervals_|.
    * @return {number}
    * @private
    */
-  getCurrentPageInView_(scannedImages) {
+  getCurrentPageInView_() {
     assert(this.isMultiPageScan);
 
-    const imageHeight = scannedImages[0].height;
-    const scrollTop = this.$$('#previewDiv').scrollTop - (imageHeight * .5);
-
-    // This is a special case for the first page since there is no margin or
-    // previous page above it.
-    if (scrollTop < 0) {
+    if (this.objectUrls.length === 1) {
       return 0;
     }
 
-    return 1 +
-        Math.floor(scrollTop / (imageHeight + SCANNED_IMG_MARGIN_BOTTOM_PX));
+    // |scrollTop| is a height value that ranges from 0 to the height at the
+    // bottom of div with all the scanned images. The height intervals in
+    // |scrollingIntervals_| are in order so the value at [0] refers to the
+    // first scanned image, at [1] the second scanned image, etc.
+    const scrollTop = this.$$('#previewDiv').scrollTop;
+    for (let i = 0; i < this.scrollingIntervals_.length; i++) {
+      // TODO(gavinwill): Convert from linear search to binary search.
+      if (scrollTop <= this.scrollingIntervals_[i]) {
+        return i;
+      }
+    }
+
+    // The last image is the catch-all interval.
+    return this.objectUrls.length - 1;
   },
 
   /**
@@ -425,8 +438,7 @@
 
     const scannedImages =
         this.$$('#scannedImages').getElementsByClassName('scanned-image');
-    this.setFocusedScannedImage_(
-        scannedImages, this.getCurrentPageInView_(scannedImages));
+    this.setFocusedScannedImage_(scannedImages, this.getCurrentPageInView_());
 
     this.updatePreviewElements_();
 
@@ -584,7 +596,7 @@
     }
 
     assert(pageIndex >= 0 && pageIndex < scannedImages.length);
-    const imageHeight = scannedImages[0].height;
+    const imageHeight = scannedImages[0].offsetHeight;
 
     // Use |pageIndex| to calculate the number of pages needed to scroll by to
     // get to our desired page. Ex: If we want to scroll to the page with
@@ -638,6 +650,7 @@
    * @private
    */
   updatePreviewElements_() {
+    this.buildScrollingIntervals_();
     this.setMultiPageScanProgressHeight_();
     this.setActionToolbarPosition_();
   },
@@ -676,4 +689,34 @@
     return this.i18n(
         'multiPageImageAriaLabel', index + 1, this.objectUrls.length);
   },
+
+  /**
+   * Divides the total scroll area of the scanned images into proportionate
+   * intervals based on each individual image's height. These intervals are used
+   * to determine which image should be focused on based on the position of the
+   * scroll bar.
+   * @private
+   */
+  buildScrollingIntervals_() {
+    const scannedImages =
+        this.$$('#scannedImages').getElementsByClassName('scanned-image');
+    if (scannedImages.length === 0) {
+      return;
+    }
+
+    const totalImagesHeight = this.$$('#previewDiv').scrollHeight;
+    const maxScrollTop =
+        totalImagesHeight - this.$$('#previewDiv').offsetHeight;
+    this.scrollingIntervals_ = [];
+    let currentIntervalHeight = 0;
+
+    // Only the first n - 1 images require intervals since the last image is the
+    // catch-all interval.
+    for (let i = 0; i < scannedImages.length - 1; i++) {
+      const scrollHeightProportion =
+          scannedImages[i].offsetHeight / totalImagesHeight;
+      currentIntervalHeight += maxScrollTop * scrollHeightProportion;
+      this.scrollingIntervals_.push(currentIntervalHeight);
+    }
+  },
 });
diff --git a/ash/wm/desks/templates/desks_templates_delete_button.cc b/ash/wm/desks/templates/desks_templates_delete_button.cc
new file mode 100644
index 0000000..9860e748
--- /dev/null
+++ b/ash/wm/desks/templates/desks_templates_delete_button.cc
@@ -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.
+
+#include "ash/wm/desks/templates/desks_templates_delete_button.h"
+
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/style/element_style.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/strings/grit/ui_strings.h"
+
+namespace ash {
+
+DesksTemplatesDeleteButton::DesksTemplatesDeleteButton() {
+  views::Builder<DesksTemplatesDeleteButton>(this)
+      .SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER)
+      .SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE)
+      .SetTooltipText(l10n_util::GetStringUTF16(IDS_APP_DELETE))
+      .BuildChildren();
+}
+
+DesksTemplatesDeleteButton::~DesksTemplatesDeleteButton() = default;
+
+void DesksTemplatesDeleteButton::OnThemeChanged() {
+  views::ImageButton::OnThemeChanged();
+  element_style::DecorateMediumCloseButton(this, kTrashCanIcon);
+}
+
+BEGIN_METADATA(DesksTemplatesDeleteButton, views::ImageButton)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/wm/desks/templates/desks_templates_delete_button.h b/ash/wm/desks/templates/desks_templates_delete_button.h
new file mode 100644
index 0000000..261b444
--- /dev/null
+++ b/ash/wm/desks/templates/desks_templates_delete_button.h
@@ -0,0 +1,38 @@
+// 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_WM_DESKS_TEMPLATES_DESKS_TEMPLATES_DELETE_BUTTON_H_
+#define ASH_WM_DESKS_TEMPLATES_DESKS_TEMPLATES_DELETE_BUTTON_H_
+
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/controls/button/image_button.h"
+
+namespace ash {
+
+// A button view that shows up when hovering over the associated grid item.
+// Allows the user to delete the template.
+class DesksTemplatesDeleteButton : public views::ImageButton {
+ public:
+  METADATA_HEADER(DesksTemplatesDeleteButton);
+
+  DesksTemplatesDeleteButton();
+  DesksTemplatesDeleteButton(const DesksTemplatesDeleteButton&) = delete;
+  DesksTemplatesDeleteButton& operator=(const DesksTemplatesDeleteButton&) =
+      delete;
+  ~DesksTemplatesDeleteButton() override;
+
+  // views::ImageButton:
+  void OnThemeChanged() override;
+};
+
+BEGIN_VIEW_BUILDER(/* no export */,
+                   DesksTemplatesDeleteButton,
+                   views::ImageButton)
+END_VIEW_BUILDER
+
+}  // namespace ash
+
+DEFINE_VIEW_BUILDER(/* no export */, ash::DesksTemplatesDeleteButton)
+
+#endif  // ASH_WM_DESKS_TEMPLATES_DESKS_TEMPLATES_DELETE_BUTTON_H_
diff --git a/ash/wm/desks/templates/desks_templates_grid_view.cc b/ash/wm/desks/templates/desks_templates_grid_view.cc
index 13db15f5..afcb7be 100644
--- a/ash/wm/desks/templates/desks_templates_grid_view.cc
+++ b/ash/wm/desks/templates/desks_templates_grid_view.cc
@@ -58,9 +58,11 @@
       layout->StartRowWithPadding(fixed_size, kColumnSetId, fixed_size,
                                   kGridPaddingDp);
     }
-
-    for (int j = 0; j < kNumColumns; ++j)
-      layout->AddView(std::make_unique<DesksTemplatesItemView>());
+    for (int j = 0; j < kNumColumns; ++j) {
+      DesksTemplatesItemView* grid_item =
+          layout->AddView(std::make_unique<DesksTemplatesItemView>());
+      grid_items_.push_back(grid_item);
+    }
   }
 }
 
@@ -100,6 +102,45 @@
   return widget;
 }
 
+void DesksTemplatesGridView::OnMouseEvent(ui::MouseEvent* event) {
+  OnLocatedEvent(event, /*is_touch=*/false);
+}
+
+void DesksTemplatesGridView::OnGestureEvent(ui::GestureEvent* event) {
+  OnLocatedEvent(event, /*is_touch=*/true);
+}
+
+void DesksTemplatesGridView::AddedToWidget() {
+  // Adding a pre-target handler to ensure that events are not accidentally
+  // captured by the child views. Also, `this` is added as the pre-target
+  // handler to the window as opposed to `Env` to ensure that we only get events
+  // that are on this window.
+  widget_window_ = GetWidget()->GetNativeWindow();
+  widget_window_->AddPreTargetHandler(this);
+}
+
+void DesksTemplatesGridView::RemovedFromWidget() {
+  DCHECK(widget_window_);
+  widget_window_->RemovePreTargetHandler(this);
+  widget_window_ = nullptr;
+}
+
+void DesksTemplatesGridView::OnLocatedEvent(ui::LocatedEvent* event,
+                                            bool is_touch) {
+  switch (event->type()) {
+    case ui::ET_MOUSE_MOVED:
+    case ui::ET_MOUSE_ENTERED:
+    case ui::ET_MOUSE_EXITED:
+    case ui::ET_GESTURE_LONG_PRESS:
+    case ui::ET_GESTURE_LONG_TAP:
+      for (auto* grid_item : grid_items_)
+        grid_item->UpdateDeleteButtonVisibility();
+      return;
+    default:
+      return;
+  }
+}
+
 BEGIN_METADATA(DesksTemplatesGridView, views::View)
 END_METADATA
 
diff --git a/ash/wm/desks/templates/desks_templates_grid_view.h b/ash/wm/desks/templates/desks_templates_grid_view.h
index 88b4d66..2e55dbf4 100644
--- a/ash/wm/desks/templates/desks_templates_grid_view.h
+++ b/ash/wm/desks/templates/desks_templates_grid_view.h
@@ -11,8 +11,10 @@
 
 namespace ash {
 
+class DesksTemplatesItemView;
+
 // A view that acts as the content view of the desks templates widget.
-// TODO(sammiequon): Add details and ASCII.
+// TODO(richui): Add details and ASCII.
 class DesksTemplatesGridView : public views::View {
  public:
   METADATA_HEADER(DesksTemplatesGridView);
@@ -29,6 +31,22 @@
   static views::UniqueWidgetPtr CreateDesksTemplatesGridWidget(
       aura::Window* root,
       const gfx::Rect& grid_bounds);
+
+  // views::View:
+  void OnMouseEvent(ui::MouseEvent* event) override;
+  void OnGestureEvent(ui::GestureEvent* event) override;
+  void AddedToWidget() override;
+  void RemovedFromWidget() override;
+
+ private:
+  // Helper to unify mouse/touch events.
+  void OnLocatedEvent(ui::LocatedEvent* event, bool is_touch);
+
+  // The views representing templates. They're owned by views hierarchy.
+  std::vector<DesksTemplatesItemView*> grid_items_;
+
+  // The underlying window of the templates grid widget.
+  aura::Window* widget_window_ = nullptr;
 };
 
 }  // namespace ash
diff --git a/ash/wm/desks/templates/desks_templates_item_view.cc b/ash/wm/desks/templates/desks_templates_item_view.cc
index 287c15ab..31c9cbe 100644
--- a/ash/wm/desks/templates/desks_templates_item_view.cc
+++ b/ash/wm/desks/templates/desks_templates_item_view.cc
@@ -4,8 +4,13 @@
 
 #include "ash/wm/desks/templates/desks_templates_item_view.h"
 
+#include "ash/accessibility/accessibility_controller_impl.h"
+#include "ash/shell.h"
+#include "ash/wm/desks/templates/desks_templates_delete_button.h"
+#include "base/notreached.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/views/border.h"
+#include "ui/views/layout/box_layout_view.h"
 
 namespace ash {
 
@@ -18,42 +23,74 @@
 constexpr gfx::Size kPreferredSize(250, 150);
 constexpr int kIconSpacingDp = 10;
 constexpr gfx::Size kPreviewIconSize(40, 40);
+constexpr int kDeleteButtonMargin = 8;
+constexpr int kDeleteButtonSize = 24;
 
 }  // namespace
 
 DesksTemplatesItemView::DesksTemplatesItemView() {
   // TODO(richui): Remove all the borders. It is only used for visualizing
   // bounds while it is a placeholder.
+  auto delete_button_callback = base::BindRepeating(
+      &DesksTemplatesItemView::OnDeleteButtonPressed, base::Unretained(this));
+
   views::View* spacer;
+  views::BoxLayoutView* container;
   views::Builder<DesksTemplatesItemView>(this)
-      .SetOrientation(views::BoxLayout::Orientation::kVertical)
-      .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kStart)
       .SetPreferredSize(kPreferredSize)
+      .SetUseDefaultFillLayout(true)
       .SetBorder(views::CreateSolidBorder(/*thickness=*/2, SK_ColorDKGRAY))
       .AddChildren(
-          views::Builder<views::View>()
-              .CopyAddressTo(&name_view_)
-              .SetPreferredSize(kViewSize)
-              .SetBorder(views::CreateSolidBorder(
-                  /*thickness=*/2, SK_ColorGRAY)),
-          views::Builder<views::View>()
-              .CopyAddressTo(&time_view_)
-              .SetPreferredSize(kViewSize)
-              .SetBorder(views::CreateSolidBorder(
-                  /*thickness=*/2, SK_ColorGRAY)),
-          views::Builder<views::View>().CopyAddressTo(&spacer),
           views::Builder<views::BoxLayoutView>()
-              .CopyAddressTo(&preview_view_)
-              .SetOrientation(views::BoxLayout::Orientation::kHorizontal)
-              .SetBetweenChildSpacing(kIconSpacingDp))
+              .CopyAddressTo(&container)
+              .SetOrientation(views::BoxLayout::Orientation::kVertical)
+              .SetCrossAxisAlignment(
+                  views::BoxLayout::CrossAxisAlignment::kStart)
+              .AddChildren(views::Builder<views::View>()
+                               .CopyAddressTo(&name_view_)
+                               .SetPreferredSize(kViewSize)
+                               .SetBorder(views::CreateSolidBorder(
+                                   /*thickness=*/2, SK_ColorGRAY)),
+                           views::Builder<views::View>()
+                               .CopyAddressTo(&time_view_)
+                               .SetPreferredSize(kViewSize)
+                               .SetBorder(views::CreateSolidBorder(
+                                   /*thickness=*/2, SK_ColorGRAY)),
+                           views::Builder<views::View>().CopyAddressTo(&spacer),
+                           views::Builder<views::BoxLayoutView>()
+                               .CopyAddressTo(&preview_view_)
+                               .SetOrientation(
+                                   views::BoxLayout::Orientation::kHorizontal)
+                               .SetBetweenChildSpacing(kIconSpacingDp)),
+          views::Builder<DesksTemplatesDeleteButton>()
+              .CopyAddressTo(&delete_button_)
+              .SetCallback(delete_button_callback))
       .BuildChildren();
 
-  SetFlexForView(spacer, 1);
+  container->SetFlexForView(spacer, 1);
+  UpdateDeleteButtonVisibility();
   SetIcons();
 }
 
 DesksTemplatesItemView::~DesksTemplatesItemView() = default;
 
+void DesksTemplatesItemView::UpdateDeleteButtonVisibility() {
+  // For switch access, setting the delete button to visible allows users to
+  // navigate to it.
+  // TODO(richui): update `force_show_delete_button_` based on touch events.
+  delete_button_->SetVisible(
+      (IsMouseHovered() || force_show_delete_button_ ||
+       Shell::Get()->accessibility_controller()->IsSwitchAccessRunning()));
+}
+
+void DesksTemplatesItemView::Layout() {
+  views::View::Layout();
+
+  delete_button_->SetBoundsRect(
+      gfx::Rect(width() - kDeleteButtonSize - kDeleteButtonMargin,
+                kDeleteButtonMargin, kDeleteButtonSize, kDeleteButtonSize));
+}
+
 void DesksTemplatesItemView::SetIcons() {
   for (int i = 0; i < kMaxIcons; ++i) {
     preview_view_->AddChildView(views::Builder<views::View>()
@@ -64,7 +101,12 @@
   }
 }
 
-BEGIN_METADATA(DesksTemplatesItemView, views::BoxLayoutView)
+void DesksTemplatesItemView::OnDeleteButtonPressed() {
+  // TODO(richui): Hook this up to the presenter.
+  NOTIMPLEMENTED();
+}
+
+BEGIN_METADATA(DesksTemplatesItemView, views::View)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/wm/desks/templates/desks_templates_item_view.h b/ash/wm/desks/templates/desks_templates_item_view.h
index 496ddf85..a2b5f27 100644
--- a/ash/wm/desks/templates/desks_templates_item_view.h
+++ b/ash/wm/desks/templates/desks_templates_item_view.h
@@ -6,13 +6,19 @@
 #define ASH_WM_DESKS_TEMPLATES_DESKS_TEMPLATES_ITEM_VIEW_H_
 
 #include "ui/base/metadata/metadata_header_macros.h"
-#include "ui/views/layout/box_layout_view.h"
+#include "ui/views/view.h"
+
+namespace views {
+class BoxLayoutView;
+}
 
 namespace ash {
 
+class DesksTemplatesDeleteButton;
+
 // A view that represents each individual template item in the desks templates
 // grid.
-class DesksTemplatesItemView : public views::BoxLayoutView {
+class DesksTemplatesItemView : public views::View {
  public:
   METADATA_HEADER(DesksTemplatesItemView);
 
@@ -21,19 +27,31 @@
   DesksTemplatesItemView& operator=(const DesksTemplatesItemView&) = delete;
   ~DesksTemplatesItemView() override;
 
+  // Updates the visibility state of the delete button depending on whether this
+  // view is mouse hovered, or if switch access is enabled.
+  void UpdateDeleteButtonVisibility();
+
+  // views::View:
+  void Layout() override;
+
  private:
   // TODO(richui): Pass a list of icons as the parameter.
   void SetIcons();
 
+  void OnDeleteButtonPressed();
+
   // Owned by the views hierarchy.
   views::View* name_view_ = nullptr;
   views::View* time_view_ = nullptr;
   views::BoxLayoutView* preview_view_ = nullptr;
+  DesksTemplatesDeleteButton* delete_button_ = nullptr;
+
+  // We force showing the delete button when `this` is long pressed or tapped
+  // using touch gestures.
+  bool force_show_delete_button_ = false;
 };
 
-BEGIN_VIEW_BUILDER(/* no export */,
-                   DesksTemplatesItemView,
-                   views::BoxLayoutView)
+BEGIN_VIEW_BUILDER(/* no export */, DesksTemplatesItemView, views::View)
 END_VIEW_BUILDER
 
 }  // namespace ash
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc
index 18738bb..2707a37 100644
--- a/ash/wm/overview/overview_controller.cc
+++ b/ash/wm/overview/overview_controller.cc
@@ -202,8 +202,9 @@
     std::unique_ptr<DelayedAnimationObserver> animation_observer) {
   // No delayed animations should be created when overview mode is set to exit
   // immediately.
-  DCHECK_NE(overview_session_->enter_exit_overview_type(),
-            OverviewEnterExitType::kImmediateExit);
+  DCHECK(IsCompletingShutdownAnimations() ||
+         overview_session_->enter_exit_overview_type() !=
+             OverviewEnterExitType::kImmediateExit);
 
   animation_observer->SetOwner(this);
   delayed_animations_.push_back(std::move(animation_observer));
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index a3942ef7..3a1a462 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -463,8 +463,8 @@
 
   window_list_.clear();
 
-  if (desks_templates_grid_)
-    desks_templates_grid_->CloseNow();
+  if (desks_templates_grid_widget_)
+    desks_templates_grid_widget_->CloseNow();
 
   overview_session_ = nullptr;
 
@@ -857,17 +857,18 @@
 }
 
 void OverviewGrid::ShowDesksTemplatesGrid() {
-  if (!desks_templates_grid_) {
-    desks_templates_grid_ =
+  if (!desks_templates_grid_widget_) {
+    desks_templates_grid_widget_ =
         DesksTemplatesGridView::CreateDesksTemplatesGridWidget(
             root_window_, GetGridEffectiveBounds());
   }
 
-  desks_templates_grid_->Show();
+  desks_templates_grid_widget_->Show();
 }
 
 bool OverviewGrid::IsShowingDesksTemplatesGrid() const {
-  return desks_templates_grid_ && desks_templates_grid_->IsVisible();
+  return desks_templates_grid_widget_ &&
+         desks_templates_grid_widget_->IsVisible();
 }
 
 void OverviewGrid::UpdateNoWindowsWidget(bool no_items) {
@@ -1060,8 +1061,8 @@
 
   UpdateCannotSnapWarningVisibility();
 
-  if (desks_templates_grid_)
-    desks_templates_grid_->SetBounds(GetGridEffectiveBounds());
+  if (desks_templates_grid_widget_)
+    desks_templates_grid_widget_->SetBounds(GetGridEffectiveBounds());
 
   // In case of split view mode, the grid bounds and item positions will be
   // updated in |OnSplitViewDividerPositionChanged|.
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index f3f7df9..79835301 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -550,7 +550,7 @@
   aura::Window* dragged_window_ = nullptr;
 
   // The widget that contains the view for all the existing templates.
-  views::UniqueWidgetPtr desks_templates_grid_;
+  views::UniqueWidgetPtr desks_templates_grid_widget_;
 
   // A widget that contains a button which creates a new desk template when
   // pressed.
diff --git a/ash/wm/splitview/split_view_constants.h b/ash/wm/splitview/split_view_constants.h
index aaf1221..8768096 100644
--- a/ash/wm/splitview/split_view_constants.h
+++ b/ash/wm/splitview/split_view_constants.h
@@ -74,6 +74,15 @@
 constexpr float kPreviewAreaHighlightOpacity = 0.18f;
 constexpr float kDarkLightPreviewAreaHighlightOpacity = 0.2f;
 
+// In portrait mode split view, if the caret in the bottom window is less than
+// `kMinCaretKeyboardDist` dip above the upper bounds of the virtual keyboard,
+// then we push up the bottom window above the virtual keyboard to avoid the
+// input field being occluded by the virtual keyboard. The upper bounds of the
+// bottom window after being pushed up cannot exceeds 1 -
+// `kMinDividerPositionRatio` of screen height.
+constexpr int kMinCaretKeyboardDist = 16;
+constexpr float kMinDividerPositionRatio = 0.15f;
+
 }  // namespace ash
 
 #endif  // ASH_WM_SPLITVIEW_SPLIT_VIEW_CONSTANTS_H_
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index e4f0426..c410430d 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -24,9 +24,11 @@
 #include "ash/style/default_colors.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/overview/delayed_animation_observer_impl.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_item.h"
+#include "ash/wm/overview/overview_types.h"
 #include "ash/wm/overview/overview_utils.h"
 #include "ash/wm/splitview/split_view_constants.h"
 #include "ash/wm/splitview/split_view_divider.h"
@@ -97,15 +99,6 @@
 constexpr float kBlackScrimFadeInRatio = 0.1f;
 constexpr float kBlackScrimOpacity = 0.4f;
 
-// In portrait mode split view, if the caret in the bottom window is less than
-// `kMinCaretKeyboardDist` dip above the upper bounds of the virtual keyboard,
-// then we push up the bottom window above the virtual keyboard to avoid the
-// input field being occluded by the virtual keyboard. The upper bounds of the
-// bottom window after being pushed up cannot exceeds 1 -
-// `kMinDividerPositionRatio` of screen height.
-constexpr int kMinCaretKeyboardDist = 16;
-constexpr float kMinDividerPositionRatio = 0.15f;
-
 // If performant split view resizing is enabled, the speed at which the divider
 // is moved controls whether windows are scaled or translated. If the divider is
 // moved more than this many pixels per second, the "fast" mode is enabled.
@@ -887,7 +880,6 @@
   OverviewSession* overview_session = GetOverviewSession();
   RemoveSnappingWindowFromOverviewIfApplicable(overview_session, window);
 
-  bool do_divider_spawn_animation = false;
   if (state_ == State::kNoSnap) {
     // Add observers when the split view mode starts.
     Shell::Get()->AddShellObserver(this);
@@ -908,20 +900,6 @@
     // There is no divider bar in clamshell splitview mode.
     if (split_view_type_ == SplitViewType::kTabletType) {
       split_view_divider_ = std::make_unique<SplitViewDivider>(this);
-      // The divider spawn animation adds a finishing touch to the |window|
-      // animation that generally accommodates snapping by dragging, but if
-      // |window| is currently minimized then it will undergo the unminimizing
-      // animation instead. Therefore skip the divider spawn animation if
-      // |window| is minimized.
-      if (!WindowState::Get(window)->IsMinimized() &&
-          !window->transform().IsIdentity()) {
-        // For the divider spawn animation, at the end of the delay, the divider
-        // shall be visually aligned with an edge of |window|. This effect will
-        // be more easily achieved after |window| has been snapped and the
-        // corresponding transform animation has begun. So for now, just set a
-        // flag to indicate that the divider spawn animation should be done.
-        do_divider_spawn_animation = true;
-      }
     }
 
     splitview_start_time_ = base::Time::Now();
@@ -2510,12 +2488,33 @@
     if (new_start_transform != window_iter->layer()->GetTargetTransform())
       window_iter->SetTransform(new_start_transform);
 
-    DoSplitviewTransformAnimation(
-        window_iter->layer(), SPLITVIEW_ANIMATION_SET_WINDOW_TRANSFORM,
-        new_target_transform,
-        window_iter == window
-            ? std::make_unique<WindowTransformAnimationObserver>(window)
-            : nullptr);
+    std::vector<ui::ImplicitAnimationObserver*> animation_observers;
+    if (window_iter == window) {
+      animation_observers.push_back(
+          new WindowTransformAnimationObserver(window));
+
+      // If the overview exit animation is in progress or is about to start, add
+      // the |window| snap animation as one of the animations to be completed
+      // before |OverviewController::OnEndingAnimationComplete| should be called
+      // to unpause occlusion tracking, unblur the wallpaper, etc.
+      OverviewController* overview_controller =
+          Shell::Get()->overview_controller();
+      if (overview_controller->IsCompletingShutdownAnimations() ||
+          (overview_controller->overview_session() &&
+           overview_controller->overview_session()->is_shutting_down() &&
+           overview_controller->overview_session()
+                   ->enter_exit_overview_type() !=
+               OverviewEnterExitType::kImmediateExit)) {
+        auto overview_exit_animation_observer =
+            std::make_unique<ExitAnimationObserver>();
+        animation_observers.push_back(overview_exit_animation_observer.get());
+        overview_controller->AddExitAnimationObserver(
+            std::move(overview_exit_animation_observer));
+      }
+    }
+    DoSplitviewTransformAnimation(window_iter->layer(),
+                                  SPLITVIEW_ANIMATION_SET_WINDOW_TRANSFORM,
+                                  new_target_transform, animation_observers);
   }
 }
 
diff --git a/ash/wm/splitview/split_view_controller.h b/ash/wm/splitview/split_view_controller.h
index 255d3ec0c..fe125df6 100644
--- a/ash/wm/splitview/split_view_controller.h
+++ b/ash/wm/splitview/split_view_controller.h
@@ -7,6 +7,7 @@
 
 #include <limits>
 #include <memory>
+#include <vector>
 
 #include "ash/accessibility/accessibility_observer.h"
 #include "ash/ash_export.h"
diff --git a/ash/wm/splitview/split_view_controller_unittest.cc b/ash/wm/splitview/split_view_controller_unittest.cc
index 5ba35f1..a416bdc 100644
--- a/ash/wm/splitview/split_view_controller_unittest.cc
+++ b/ash/wm/splitview/split_view_controller_unittest.cc
@@ -13,8 +13,11 @@
 #include "ash/accessibility/magnifier/docked_magnifier_controller.h"
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/constants/app_types.h"
+#include "ash/constants/ash_features.h"
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/display/screen_orientation_controller_test_api.h"
+#include "ash/keyboard/ui/keyboard_ui_controller.h"
+#include "ash/keyboard/ui/test/keyboard_test_util.h"
 #include "ash/public/cpp/fps_counter.h"
 #include "ash/public/cpp/presentation_time_recorder.h"
 #include "ash/public/cpp/shelf_config.h"
@@ -31,6 +34,7 @@
 #include "ash/system/status_area_widget_test_helper.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test/test_window_builder.h"
+#include "ash/wallpaper/wallpaper_widget_controller.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/drag_window_resizer.h"
 #include "ash/wm/mru_window_tracker.h"
@@ -39,6 +43,7 @@
 #include "ash/wm/overview/overview_item.h"
 #include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/overview/overview_test_util.h"
+#include "ash/wm/splitview/split_view_constants.h"
 #include "ash/wm/splitview/split_view_divider.h"
 #include "ash/wm/splitview/split_view_drag_indicators.h"
 #include "ash/wm/splitview/split_view_metrics_controller.h"
@@ -62,6 +67,7 @@
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/aura/test/test_windows.h"
 #include "ui/base/hit_test.h"
+#include "ui/base/ime/dummy_text_input_client.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/compositor/test/test_utils.h"
 #include "ui/compositor_extra/shadow.h"
@@ -77,6 +83,8 @@
 
 namespace {
 
+constexpr int kCaretHeightForTest = 8;
+
 // The observer to observe the overview states in |root_window_|.
 class OverviewStatesObserver : public OverviewObserver {
  public:
@@ -126,6 +134,57 @@
   ~TestBubbleDialogDelegateView() override {}
 };
 
+// Helper class to simulate the text input field in a window. When the text
+// input field is focused, the attached window will also be focused and show the
+// virtual keyboard. If the text input field is unfocused, it will hide the
+// virtual keyboard.
+class TestTextInputClient : public ui::DummyTextInputClient {
+ public:
+  explicit TestTextInputClient(aura::Window* window)
+      : ui::DummyTextInputClient(ui::TEXT_INPUT_TYPE_TEXT), window_(window) {
+    DCHECK(window_);
+  }
+  TestTextInputClient(const TestTextInputClient&) = delete;
+  TestTextInputClient& operator=(const TestTextInputClient&) = delete;
+  ~TestTextInputClient() override {
+    auto* ime = keyboard::KeyboardUIController::Get()->GetInputMethodForTest();
+    ime->DetachTextInputClient(this);
+  }
+
+  // ui::DummyTextInputClient:
+  gfx::Rect GetCaretBounds() const override { return caret_bounds_; }
+
+  void set_caret_bounds(gfx::Rect caret_bounds) {
+    caret_bounds_ = caret_bounds;
+  }
+
+  // When the text client is focused, the attached window will also be focused
+  // and the virtual keyboard is enabled.
+  void Focus() {
+    auto* ime = keyboard::KeyboardUIController::Get()->GetInputMethodForTest();
+    ime->SetFocusedTextInputClient(this);
+
+    if (window_)
+      window_->Focus();
+
+    ime->ShowVirtualKeyboardIfEnabled();
+    ASSERT_TRUE(keyboard::WaitUntilShown());
+  }
+
+  // When the text client is unfocused, hide the virtual keyboard.
+  void UnFocus() {
+    auto* ime = keyboard::KeyboardUIController::Get()->GetInputMethodForTest();
+    ime->DetachTextInputClient(this);
+    keyboard::KeyboardUIController::Get()->HideKeyboardExplicitlyBySystem();
+  }
+
+ private:
+  // The window to which the text client attaches to.
+  aura::Window* window_;
+  // The bounds of the caret.
+  gfx::Rect caret_bounds_;
+};
+
 bool IsTabletMode() {
   return Shell::Get()->tablet_mode_controller()->InTabletMode();
 }
@@ -628,6 +687,34 @@
   CheckOverviewEnterExitHistogram("ExitInSplitView", {1, 0}, {0, 1});
 }
 
+// Tests that in split view with a single overview window, when overview is
+// ended, the wallpaper stays blurred until the window finishes animating.
+TEST_F(SplitViewControllerTest,
+       WallpaperUnblurredAfterLoneOverviewWindowSnapAnimationCompleted) {
+  const gfx::Rect bounds(400, 400);
+  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
+  ToggleOverview();
+  split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT);
+
+  WallpaperWidgetController* wallpaper_widget_controller =
+      Shell::GetPrimaryRootWindowController()->wallpaper_widget_controller();
+  EXPECT_GT(wallpaper_widget_controller->GetWallpaperBlur(), 0);
+  EXPECT_FALSE(wallpaper_widget_controller->IsAnimating());
+
+  ui::ScopedAnimationDurationScaleMode animation_scale(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+  ToggleOverview();
+  EXPECT_GT(wallpaper_widget_controller->GetWallpaperBlur(), 0);
+  EXPECT_FALSE(wallpaper_widget_controller->IsAnimating());
+
+  WaitForOverviewExitAnimation();
+  // The wallpaper is unblurred without animation, because the wallpaper is
+  // covered by the windows and the split view divider.
+  EXPECT_EQ(wallpaper_widget_controller->GetWallpaperBlur(), 0);
+  EXPECT_FALSE(wallpaper_widget_controller->IsAnimating());
+}
+
 // Tests that if split view mode is active when entering overview, the overview
 // windows grid should show in the non-default side of the screen, and the
 // default snapped window should not be shown in the overview window grid.
@@ -5414,4 +5501,287 @@
   EXPECT_EQ(backdrop_window->bounds(), active_desk_container->bounds());
 }
 
+// The test class that enables the feature flag of portrait mode split view
+// virtual keyboard improvement and the virtual keyboard.
+class SplitViewKeyboardTest : public SplitViewControllerTest {
+ public:
+  SplitViewKeyboardTest() = default;
+
+  SplitViewKeyboardTest(const SplitViewKeyboardTest&) = delete;
+  SplitViewKeyboardTest& operator=(const SplitViewKeyboardTest&) = delete;
+
+  ~SplitViewKeyboardTest() override = default;
+
+  // SplitViewControllerTest:
+  void SetUp() override {
+    SplitViewControllerTest::SetUp();
+    scoped_feature_list_.InitAndEnableFeature(features::kAdjustSplitViewForVK);
+    SetVirtualKeyboardEnabled(true);
+  }
+
+  keyboard::KeyboardUIController* keyboard_controller() {
+    return keyboard::KeyboardUIController::Get();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that when the input field in the bottom window is blocked by the
+// virtual keyboard (the bottom of the caret is less than
+// `kMinCaretKeyboardDist` above the virtual keyboard), the bottom window will
+// be pushed above the virtual keyboard.
+TEST_F(SplitViewKeyboardTest, PushUpBottomWindow) {
+  UpdateDisplay("1200x800");
+
+  int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
+  display::DisplayManager* display_manager = Shell::Get()->display_manager();
+  display::test::ScopedSetInternalDisplayId set_internal(display_manager,
+                                                         display_id);
+  ScreenOrientationControllerTestApi test_api(
+      Shell::Get()->screen_orientation_controller());
+  ASSERT_EQ(chromeos::OrientationType::kLandscapePrimary,
+            test_api.GetCurrentOrientation());
+
+  gfx::Rect bounds(0, 0, 400, 400);
+  std::unique_ptr<aura::Window> bottom_window(CreateWindow(bounds));
+  auto bottom_client =
+      std::make_unique<TestTextInputClient>(bottom_window.get());
+  split_view_controller()->SnapWindow(bottom_window.get(),
+                                      SplitViewController::RIGHT);
+
+  test_api.SetDisplayRotation(display::Display::ROTATE_270,
+                              display::Display::RotationSource::ACTIVE);
+  EXPECT_EQ(chromeos::OrientationType::kPortraitPrimary,
+            test_api.GetCurrentOrientation());
+  EXPECT_FALSE(split_view_controller()->IsPhysicalLeftOrTop(
+      SplitViewController::RIGHT, bottom_window.get()));
+
+  const gfx::Rect keyboard_bounds =
+      keyboard_controller()->GetKeyboardWindow()->GetBoundsInScreen();
+  const gfx::Rect orig_bottom_bounds = bottom_window->GetBoundsInScreen();
+  const gfx::Rect orig_divider_bounds = split_view_controller()
+                                            ->split_view_divider()
+                                            ->divider_widget()
+                                            ->GetWindowBoundsInScreen();
+
+  // Set the caret position in bottom window above the upper bounds of the
+  // virtual keyboard. When the virtual keyboard is enabled, the bottom window
+  // will not shift.
+  bottom_client->set_caret_bounds(gfx::Rect(
+      keyboard_bounds.top_center() +
+          gfx::Vector2d(0, -kMinCaretKeyboardDist - kCaretHeightForTest - 10),
+      gfx::Size(0, kCaretHeightForTest)));
+  bottom_client->Focus();
+  EXPECT_TRUE(keyboard_controller()->IsKeyboardVisible());
+  EXPECT_EQ(orig_bottom_bounds, bottom_window->GetBoundsInScreen());
+  // The split view divider is adjustable and not moved.
+  EXPECT_EQ(orig_divider_bounds, split_view_controller()
+                                     ->split_view_divider()
+                                     ->divider_widget()
+                                     ->GetWindowBoundsInScreen());
+  EXPECT_TRUE(split_view_controller()->split_view_divider()->IsAdjustable());
+
+  // Disable the keyboard.
+  bottom_client->UnFocus();
+  EXPECT_FALSE(keyboard_controller()->IsKeyboardVisible());
+
+  const gfx::Rect shift_bottom_bounds(
+      keyboard_bounds.origin() + gfx::Vector2d(0, -orig_bottom_bounds.height()),
+      orig_bottom_bounds.size());
+  const gfx::Rect shift_divider_bounds(
+      shift_bottom_bounds.origin() +
+          gfx::Vector2d(0, -orig_divider_bounds.height()),
+      orig_divider_bounds.size());
+  // Set the caret position in bottom window below the upper bounds of the
+  // virtual keyboard. When the virtual keyboard is enabled, the bottom window
+  // will shift above the virtual keyboard.
+  bottom_client->set_caret_bounds(
+      gfx::Rect(keyboard_bounds.top_center() + gfx::Vector2d(0, 10),
+                gfx::Size(0, kCaretHeightForTest)));
+  bottom_client->Focus();
+  EXPECT_TRUE(keyboard_controller()->IsKeyboardVisible());
+  EXPECT_EQ(shift_bottom_bounds, bottom_window->GetBoundsInScreen());
+  // The split view divider will also be shifted and become unadjustable.
+  EXPECT_EQ(shift_divider_bounds, split_view_controller()
+                                      ->split_view_divider()
+                                      ->divider_widget()
+                                      ->GetWindowBoundsInScreen());
+  EXPECT_FALSE(split_view_controller()->split_view_divider()->IsAdjustable());
+
+  // Disable the keyboard. The bottom window will restore to original bounds.
+  // The split view divider will also be adjustable and restore to original
+  // bounds.
+  bottom_client->UnFocus();
+  EXPECT_FALSE(keyboard_controller()->IsKeyboardVisible());
+  EXPECT_EQ(orig_bottom_bounds, bottom_window->GetBoundsInScreen());
+  EXPECT_EQ(orig_divider_bounds, split_view_controller()
+                                     ->split_view_divider()
+                                     ->divider_widget()
+                                     ->GetWindowBoundsInScreen());
+  EXPECT_TRUE(split_view_controller()->split_view_divider()->IsAdjustable());
+}
+
+// When the bottom window is pushed up due to the virtual keyboard and the
+// shifted window position cannot exceed `1 - kMinDividerPositionRatio` of the
+// screen height.
+TEST_F(SplitViewKeyboardTest, PushUpBottomWindowLimitHeight) {
+  UpdateDisplay("1200x800");
+
+  int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
+  display::DisplayManager* display_manager = Shell::Get()->display_manager();
+  display::test::ScopedSetInternalDisplayId set_internal(display_manager,
+                                                         display_id);
+  ScreenOrientationControllerTestApi test_api(
+      Shell::Get()->screen_orientation_controller());
+  ASSERT_EQ(chromeos::OrientationType::kLandscapePrimary,
+            test_api.GetCurrentOrientation());
+
+  gfx::Rect bounds(0, 0, 200, 200);
+  std::unique_ptr<aura::Window> bottom_window(CreateWindow(bounds));
+  auto bottom_client =
+      std::make_unique<TestTextInputClient>(bottom_window.get());
+  split_view_controller()->SnapWindow(bottom_window.get(),
+                                      SplitViewController::RIGHT);
+
+  test_api.SetDisplayRotation(display::Display::ROTATE_270,
+                              display::Display::RotationSource::ACTIVE);
+  EXPECT_EQ(chromeos::OrientationType::kPortraitPrimary,
+            test_api.GetCurrentOrientation());
+  EXPECT_FALSE(split_view_controller()->IsPhysicalLeftOrTop(
+      SplitViewController::RIGHT, bottom_window.get()));
+
+  const gfx::Rect keyboard_bounds =
+      keyboard_controller()->GetKeyboardWindow()->GetBoundsInScreen();
+  const gfx::Rect divider_bounds =
+      split_view_divider()->GetDividerBoundsInScreen(false /* is_dragging */);
+  const gfx::Rect screen_bounds =
+      screen_util::GetDisplayWorkAreaBoundsInParent(bottom_window.get());
+  const int screen_height = screen_bounds.height();
+  const int limit_y = screen_height * kMinDividerPositionRatio;
+
+  // Resize divider to a position that when the bottom window is pushed up, its
+  // position will exceeds `1-kMinDividerPositionRatio` of screen height.
+  split_view_controller()->StartResize(divider_bounds.CenterPoint());
+  split_view_controller()->Resize(gfx::Point(0, screen_height * 0.15f));
+
+  const gfx::Rect orig_bottom_bounds = bottom_window->GetBoundsInScreen();
+  EXPECT_LT(keyboard_bounds.y() - orig_bottom_bounds.height(), limit_y);
+
+  const gfx::Rect orig_divider_bounds = split_view_controller()
+                                            ->split_view_divider()
+                                            ->divider_widget()
+                                            ->GetWindowBoundsInScreen();
+
+  // Set the caret position in bottom window below the upper bounds of the
+  // virtual keyboard. When the virtual keyboard is enabled, the bottom window
+  // will shift above the virtual keyboard but the upper bounds will be limited
+  // to `kMinDividerPositionRatio` of the screen height.
+  const gfx::Rect shift_bottom_bounds(0, limit_y, keyboard_bounds.width(),
+                                      keyboard_bounds.y() - limit_y);
+  const gfx::Rect shift_divider_bounds(
+      shift_bottom_bounds.origin() +
+          gfx::Vector2d(0, -orig_divider_bounds.height()),
+      orig_divider_bounds.size());
+
+  bottom_client->set_caret_bounds(gfx::Rect(keyboard_bounds.top_center(),
+                                            gfx::Size(0, kCaretHeightForTest)));
+  bottom_client->Focus();
+  EXPECT_TRUE(keyboard_controller()->IsKeyboardVisible());
+  EXPECT_EQ(shift_bottom_bounds, bottom_window->GetBoundsInScreen());
+  // The split view divider will also be shifted and become unadjustable.
+  EXPECT_EQ(shift_divider_bounds, split_view_controller()
+                                      ->split_view_divider()
+                                      ->divider_widget()
+                                      ->GetWindowBoundsInScreen());
+  EXPECT_FALSE(split_view_controller()->split_view_divider()->IsAdjustable());
+
+  // Disable the keyboard. The bottom window will restore to original bounds.
+  // The split view divider will also be adjustable and restore to original
+  // bounds.
+  bottom_client->UnFocus();
+  EXPECT_FALSE(keyboard_controller()->IsKeyboardVisible());
+  EXPECT_EQ(orig_bottom_bounds, bottom_window->GetBoundsInScreen());
+  EXPECT_EQ(orig_divider_bounds, split_view_controller()
+                                     ->split_view_divider()
+                                     ->divider_widget()
+                                     ->GetWindowBoundsInScreen());
+  EXPECT_TRUE(split_view_controller()->split_view_divider()->IsAdjustable());
+}
+
+// Tests that when the bottom window is pushed up due to the virtual keyboard
+// and the top window is activated, then the bottom window should restore to the
+// original layout.
+TEST_F(SplitViewKeyboardTest, RestoreByActivatingTopWindow) {
+  UpdateDisplay("1200x800");
+
+  int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
+  display::DisplayManager* display_manager = Shell::Get()->display_manager();
+  display::test::ScopedSetInternalDisplayId set_internal(display_manager,
+                                                         display_id);
+  ScreenOrientationControllerTestApi test_api(
+      Shell::Get()->screen_orientation_controller());
+  ASSERT_EQ(chromeos::OrientationType::kLandscapePrimary,
+            test_api.GetCurrentOrientation());
+
+  gfx::Rect bounds(0, 0, 400, 400);
+  std::unique_ptr<aura::Window> top_window(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> bottom_window(CreateWindow(bounds));
+  auto top_client = std::make_unique<TestTextInputClient>(top_window.get());
+  auto bottom_client =
+      std::make_unique<TestTextInputClient>(bottom_window.get());
+  split_view_controller()->SnapWindow(top_window.get(),
+                                      SplitViewController::LEFT);
+  split_view_controller()->SnapWindow(bottom_window.get(),
+                                      SplitViewController::RIGHT);
+
+  test_api.SetDisplayRotation(display::Display::ROTATE_270,
+                              display::Display::RotationSource::ACTIVE);
+  EXPECT_EQ(chromeos::OrientationType::kPortraitPrimary,
+            test_api.GetCurrentOrientation());
+  EXPECT_TRUE(split_view_controller()->IsPhysicalLeftOrTop(
+      SplitViewController::LEFT, top_window.get()));
+
+  const gfx::Rect keyboard_bounds =
+      keyboard_controller()->GetKeyboardWindow()->GetBoundsInScreen();
+  const gfx::Rect orig_bottom_bounds = bottom_window->GetBoundsInScreen();
+  const gfx::Rect shift_bottom_bounds(
+      keyboard_bounds.origin() + gfx::Vector2d(0, -orig_bottom_bounds.height()),
+      orig_bottom_bounds.size());
+  const gfx::Rect orig_divider_bounds = split_view_controller()
+                                            ->split_view_divider()
+                                            ->divider_widget()
+                                            ->GetWindowBoundsInScreen();
+  const gfx::Rect shift_divider_bounds(
+      shift_bottom_bounds.origin() +
+          gfx::Vector2d(0, -orig_divider_bounds.height()),
+      orig_divider_bounds.size());
+
+  // Set the caret position in bottom window below the upper bounds of the
+  // virtual keyboard. When the virtual keyboard is enabled, the bottom window
+  // will shift.
+  bottom_client->set_caret_bounds(
+      gfx::Rect(keyboard_bounds.top_center() + gfx::Vector2d(0, 10),
+                gfx::Size(0, kCaretHeightForTest)));
+  bottom_client->Focus();
+  EXPECT_TRUE(keyboard_controller()->IsKeyboardVisible());
+  EXPECT_EQ(shift_bottom_bounds, bottom_window->GetBoundsInScreen());
+  // The split view divider will also be shifted and become unadjustable.
+  EXPECT_EQ(shift_divider_bounds, split_view_controller()
+                                      ->split_view_divider()
+                                      ->divider_widget()
+                                      ->GetWindowBoundsInScreen());
+  EXPECT_FALSE(split_view_controller()->split_view_divider()->IsAdjustable());
+
+  // Activate the top window. The bottom window will restore to original bounds.
+  top_client->Focus();
+  EXPECT_TRUE(keyboard_controller()->IsKeyboardVisible());
+  EXPECT_EQ(orig_bottom_bounds, bottom_window->GetBoundsInScreen());
+  EXPECT_EQ(orig_divider_bounds, split_view_controller()
+                                     ->split_view_divider()
+                                     ->divider_widget()
+                                     ->GetWindowBoundsInScreen());
+  EXPECT_TRUE(split_view_controller()->split_view_divider()->IsAdjustable());
+}
+
 }  // namespace ash
diff --git a/ash/wm/splitview/split_view_drag_indicators.cc b/ash/wm/splitview/split_view_drag_indicators.cc
index 264bd04..8461154 100644
--- a/ash/wm/splitview/split_view_drag_indicators.cc
+++ b/ash/wm/splitview/split_view_drag_indicators.cc
@@ -5,6 +5,7 @@
 #include "ash/wm/splitview/split_view_drag_indicators.h"
 
 #include <utility>
+#include <vector>
 
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/public/cpp/shell_window_ids.h"
@@ -583,11 +584,11 @@
         DoSplitviewTransformAnimation(
             preview_label_layer,
             SPLITVIEW_ANIMATION_PREVIEW_AREA_TEXT_SLIDE_OUT,
-            preview_label_transform, /*animation_observer=*/nullptr);
+            preview_label_transform, /*animation_observers=*/{});
         DoSplitviewTransformAnimation(
             other_highlight_label_layer,
             SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_TEXT_SLIDE_OUT,
-            other_highlight_label_transform, /*animation_observer=*/nullptr);
+            other_highlight_label_transform, /*animation_observers=*/{});
       } else {
         // Put the labels where they belong.
         preview_label_layer->SetTransform(preview_label_transform);
@@ -606,11 +607,11 @@
         // Animate the labels sliding in.
         DoSplitviewTransformAnimation(
             preview_label_layer, SPLITVIEW_ANIMATION_PREVIEW_AREA_TEXT_SLIDE_IN,
-            gfx::Transform(), /*animation_observer=*/nullptr);
+            gfx::Transform(), /*animation_observers=*/{});
         DoSplitviewTransformAnimation(
             other_highlight_label_layer,
             SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_TEXT_SLIDE_IN, gfx::Transform(),
-            /*animation_observer=*/nullptr);
+            /*animation_observers=*/{});
       } else {
         // Put the labels where they belong.
         preview_label_layer->SetTransform(gfx::Transform());
diff --git a/ash/wm/splitview/split_view_utils.cc b/ash/wm/splitview/split_view_utils.cc
index 2933315..7ebefa0c7 100644
--- a/ash/wm/splitview/split_view_utils.cc
+++ b/ash/wm/splitview/split_view_utils.cc
@@ -245,7 +245,7 @@
     ui::Layer* layer,
     SplitviewAnimationType type,
     const gfx::Transform& target_transform,
-    std::unique_ptr<ui::ImplicitAnimationObserver> animation_observer) {
+    const std::vector<ui::ImplicitAnimationObserver*>& animation_observers) {
   if (layer->GetTargetTransform() == target_transform)
     return;
 
@@ -271,8 +271,8 @@
 
   ui::LayerAnimator* animator = layer->GetAnimator();
   ui::ScopedLayerAnimationSettings settings(animator);
-  if (animation_observer.get())
-    settings.AddObserver(animation_observer.release());
+  for (ui::ImplicitAnimationObserver* animation_observer : animation_observers)
+    settings.AddObserver(animation_observer);
   ApplyAnimationSettings(&settings, animator,
                          ui::LayerAnimationElement::TRANSFORM, duration, tween,
                          preemption_strategy, delay);
diff --git a/ash/wm/splitview/split_view_utils.h b/ash/wm/splitview/split_view_utils.h
index 80b5a6c..15b1754 100644
--- a/ash/wm/splitview/split_view_utils.h
+++ b/ash/wm/splitview/split_view_utils.h
@@ -5,6 +5,8 @@
 #ifndef ASH_WM_SPLITVIEW_SPLIT_VIEW_UTILS_H_
 #define ASH_WM_SPLITVIEW_SPLIT_VIEW_UTILS_H_
 
+#include <vector>
+
 #include "ash/ash_export.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -113,7 +115,7 @@
     ui::Layer* layer,
     SplitviewAnimationType type,
     const gfx::Transform& target_transform,
-    std::unique_ptr<ui::ImplicitAnimationObserver> animation_observer);
+    const std::vector<ui::ImplicitAnimationObserver*>& animation_observers);
 
 // Animates |layer|'s clip rect based on |type|.
 void DoSplitviewClipRectAnimation(
diff --git a/ash/wm/workspace/workspace_window_resizer.cc b/ash/wm/workspace/workspace_window_resizer.cc
index 5ef51e7e..e96a2eb 100644
--- a/ash/wm/workspace/workspace_window_resizer.cc
+++ b/ash/wm/workspace/workspace_window_resizer.cc
@@ -1043,7 +1043,6 @@
   // TODO: figure out how to deal with window going off the edge.
 
   // Calculate sizes so that we can maintain the ratios if we need to resize.
-  int total_available = 0;
   for (size_t i = 0; i < attached_windows_.size(); ++i) {
     gfx::Size min(attached_windows_[i]->delegate()
                       ? attached_windows_[i]->delegate()->GetMinimumSize()
@@ -1056,7 +1055,6 @@
                             std::max(PrimaryAxisSize(min), kMinOnscreenSize));
     total_min_ += min_size;
     total_initial_size_ += initial_size;
-    total_available += std::max(min_size, initial_size) - min_size;
   }
   instance = this;
 
diff --git a/base/android/library_loader/library_prefetcher.cc b/base/android/library_loader/library_prefetcher.cc
index 2a64694..88dd068a 100644
--- a/base/android/library_loader/library_prefetcher.cc
+++ b/base/android/library_loader/library_prefetcher.cc
@@ -183,6 +183,7 @@
     // loop.
     dummy ^= *static_cast<volatile unsigned char*>(ptr);
   }
+  ALLOW_UNUSED_LOCAL(dummy);
 }
 
 // These values were used in the past for recording
diff --git a/base/command_line.h b/base/command_line.h
index 706726a..ad028128 100644
--- a/base/command_line.h
+++ b/base/command_line.h
@@ -19,6 +19,7 @@
 #include <stddef.h>
 #include <functional>
 #include <map>
+#include <memory>
 #include <string>
 #include <vector>
 
diff --git a/base/task/sequence_manager/task_queue_impl.cc b/base/task/sequence_manager/task_queue_impl.cc
index f1c4ff5..31039cc 100644
--- a/base/task/sequence_manager/task_queue_impl.cc
+++ b/base/task/sequence_manager/task_queue_impl.cc
@@ -957,12 +957,9 @@
   // immediate tasks inside UpdateDelayedWakeUp().
   UpdateDelayedWakeUp(&lazy_now);
 
-  bool has_pending_immediate_work = false;
-
   {
     base::internal::CheckedAutoLock lock(any_thread_lock_);
     UpdateCrossThreadQueueStateLocked();
-    has_pending_immediate_work = HasTaskToRunImmediatelyLocked();
 
     // Copy over the task-reporting related state.
     any_thread_.tracing_only.is_enabled = enabled;
diff --git a/base/timer/timer.cc b/base/timer/timer.cc
index 20f95b10..c770a47 100644
--- a/base/timer/timer.cc
+++ b/base/timer/timer.cc
@@ -12,11 +12,9 @@
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/tick_clock.h"
-#include "build/build_config.h"
 
 namespace base {
 namespace internal {
@@ -30,25 +28,6 @@
 constexpr Feature kAlwaysAbandonScheduledTask{"AlwaysAbandonScheduledTask",
                                               FEATURE_DISABLED_BY_DEFAULT};
 
-// The reason for which the timer's scheduled task was invoked.
-enum ScheduledTaskInvokedReason {
-  kStopped,      // The timer fired for a stopped timer so nothing was done.
-  kRescheduled,  // The timer fired before the desired run time so the user task
-                 // was rescheduled for later. This can happens when the timer
-                 // is restarted while it is already running.
-  kReady,        // The timer fired at the desired run time so the task is ready
-                 // to be invoked.
-  kMaxValue
-};
-
-void RecordScheduledTaskInvokedReason(ScheduledTaskInvokedReason reason) {
-  // Recording this histogram breaks a fuchsia test.
-#if !defined(OS_FUCHSIA)
-  UMA_HISTOGRAM_ENUMERATION("Scheduler.TimerBase.ScheduledTaskInvokedReason",
-                            reason);
-#endif
-}
-
 }  // namespace
 
 // TaskDestructionDetector's role is to detect when the scheduled task is
@@ -234,7 +213,6 @@
   // The timer may have been stopped.
   if (!is_running_) {
     DCHECK(!FeatureList::IsEnabled(kAlwaysAbandonScheduledTask));
-    RecordScheduledTaskInvokedReason(ScheduledTaskInvokedReason::kStopped);
     return;
   }
 
@@ -247,15 +225,12 @@
     // task if the |desired_run_time_| is in the future.
     if (desired_run_time_ > now) {
       DCHECK(!FeatureList::IsEnabled(kAlwaysAbandonScheduledTask));
-      RecordScheduledTaskInvokedReason(
-          ScheduledTaskInvokedReason::kRescheduled);
       // Post a new task to span the remaining time.
       ScheduleNewTask(desired_run_time_ - now);
       return;
     }
   }
 
-  RecordScheduledTaskInvokedReason(ScheduledTaskInvokedReason::kReady);
   RunUserTask();
   // No more member accesses here: |this| could be deleted at this point.
 }
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 8638ce942..f40a502 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -104,6 +104,7 @@
 FEATURE_ANNOTATION = 'Feature'
 RENDER_TEST_FEATURE_ANNOTATION = 'RenderTest'
 WPR_ARCHIVE_FILE_PATH_ANNOTATION = 'WPRArchiveDirectory'
+WPR_ARCHIVE_NAME_ANNOTATION = 'WPRArchiveDirectory$ArchiveName'
 WPR_RECORD_REPLAY_TEST_FEATURE_ANNOTATION = 'WPRRecordReplayTest'
 
 _DEVICE_GOLD_DIR = 'skia_gold'
@@ -663,10 +664,12 @@
                                wpr_archive_path,
                                os.path.exists(wpr_archive_path)))
 
+      file_name = _GetWPRArchiveFileName(
+          test) or self._GetUniqueTestName(test) + '.wprgo'
+
       # Some linux version does not like # in the name. Replaces it with __.
-      archive_path = os.path.join(
-          wpr_archive_path,
-          _ReplaceUncommonChars(self._GetUniqueTestName(test)) + '.wprgo')
+      archive_path = os.path.join(wpr_archive_path,
+                                  _ReplaceUncommonChars(file_name))
 
       if not os.path.exists(_WPR_GO_LINUX_X86_64_PATH):
         # If we got to this stage, then we should have
@@ -1372,6 +1375,13 @@
                                  {}).get('value', ())
 
 
+def _GetWPRArchiveFileName(test):
+  """Retrieves the WPRArchiveDirectory.ArchiveName annotation."""
+  value = test['annotations'].get(WPR_ARCHIVE_NAME_ANNOTATION,
+                                  {}).get('value', None)
+  return value[0] if value else None
+
+
 def _ReplaceUncommonChars(original):
   """Replaces uncommon characters with __."""
   if not original:
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 02e814c9..d858e6b 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2604,6 +2604,8 @@
           resources_config_paths += invoker.resources_config_paths
         }
       }
+    } else {
+      not_needed(invoker, [ "resources_config_paths" ])
     }
 
     if (!_is_bundle_module) {
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 3ec6479d..8ece1b2 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-6.20211012.0.1
+6.20211012.1.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 3ec6479d..1290fe0 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-6.20211012.0.1
+6.20211012.2.1
diff --git a/build/rust/rust_source_set.gni b/build/rust/rust_source_set.gni
index 5887a62..146eb7e 100644
--- a/build/rust/rust_source_set.gni
+++ b/build/rust/rust_source_set.gni
@@ -149,7 +149,7 @@
          "rustflags not supported by rust_source_set")
   _deps_for_rust_targets = []
   if (defined(invoker.mutually_dependent_target)) {
-    _deps_for_rust_targets += [ invoker.mutually_depependent_peer ]
+    _deps_for_rust_targets += [ invoker.mutually_dependent_target ]
   }
   _deps = []
   if (defined(invoker.deps)) {
@@ -191,7 +191,7 @@
                            # binary by the C++ linker.
     ]
     if (defined(invoker.mutually_dependent_target)) {
-      visibility = invoker.mutually_dependent_target
+      visibility = [ invoker.mutually_dependent_target ]
     }
   }
 
diff --git a/build/rust/std/remap_alloc.cc b/build/rust/std/remap_alloc.cc
index 799cf8b0..ab85638 100644
--- a/build/rust/std/remap_alloc.cc
+++ b/build/rust/std/remap_alloc.cc
@@ -43,6 +43,11 @@
 // redirect these symbols to that crate instead. The advantage of the latter
 // is that it would work equally well for those cases where rustc is doing
 // the final linking.
+//
+// They're weak symbols, because this file will sometimes end up in targets
+// which are linked by rustc, and thus we would otherwise get duplicate
+// definitions. The following definitions will therefore only end up being
+// used in targets which are linked by our C++ toolchain.
 
 extern "C" {
 
@@ -51,23 +56,24 @@
 void* __rdl_realloc(void*, size_t, size_t, size_t);
 void* __rdl_alloc_zeroed(size_t, size_t);
 
-void* __rust_alloc(size_t a, size_t b) {
+void* __attribute__((weak)) __rust_alloc(size_t a, size_t b) {
   return __rdl_alloc(a, b);
 }
 
-void __rust_dealloc(void* a) {
+void __attribute__((weak)) __rust_dealloc(void* a) {
   __rdl_dealloc(a);
 }
 
-void* __rust_realloc(void* a, size_t b, size_t c, size_t d) {
+void* __attribute__((weak))
+__rust_realloc(void* a, size_t b, size_t c, size_t d) {
   return __rdl_realloc(a, b, c, d);
 }
 
-void* __rust_alloc_zeroed(size_t a, size_t b) {
+void* __attribute__((weak)) __rust_alloc_zeroed(size_t a, size_t b) {
   return __rdl_alloc_zeroed(a, b);
 }
 
-void __rust_alloc_error_handler(size_t a, size_t b) {
+void __attribute__((weak)) __rust_alloc_error_handler(size_t a, size_t b) {
   IMMEDIATE_CRASH();
 }
 
diff --git a/build/rust/tests/BUILD.gn b/build/rust/tests/BUILD.gn
index 4ccd89b2a..f111229 100644
--- a/build/rust/tests/BUILD.gn
+++ b/build/rust/tests/BUILD.gn
@@ -11,12 +11,15 @@
   if (toolchain_has_rust) {
     deps = [
       "test_cpp_including_rust",
+      "test_mixed_source_set",
       "test_rust_source_set",
     ]
     if (build_rust_unit_tests) {
       deps += [
         "test_cpp_including_rust:test_cpp_including_rust_unittests",
+        "test_mixed_source_set:test_mixed_source_set_rs_unittests",
         "test_rust_source_set:test_rust_source_set_unittests",
+        "test_rust_unittests",
       ]
     }
     if (rustc_can_link) {
diff --git a/build/rust/tests/test_cpp_including_rust/BUILD.gn b/build/rust/tests/test_cpp_including_rust/BUILD.gn
index afe5ff4..43e8ef1 100644
--- a/build/rust/tests/test_cpp_including_rust/BUILD.gn
+++ b/build/rust/tests/test_cpp_including_rust/BUILD.gn
@@ -8,6 +8,7 @@
 executable("test_cpp_including_rust") {
   sources = [ "main.cc" ]
   deps = [
+    "//build/rust/tests/test_mixed_source_set",
     "//build/rust/tests/test_rust_source_set:test_rust_source_set_cpp_bindings",
   ]
 }
@@ -18,6 +19,7 @@
     "//base",
     "//base/allocator:buildflags",
     "//base/test:run_all_unittests",
+    "//build/rust/tests/test_mixed_source_set",
     "//build/rust/tests/test_rust_source_set:test_rust_source_set_cpp_bindings",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/build/rust/tests/test_cpp_including_rust/main.cc b/build/rust/tests/test_cpp_including_rust/main.cc
index c1de55a..2718a8e 100644
--- a/build/rust/tests/test_cpp_including_rust/main.cc
+++ b/build/rust/tests/test_cpp_including_rust/main.cc
@@ -2,10 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "build/rust/tests/test_mixed_source_set/test_mixed_source_set.h"
+
 // Defined in Rust.
 extern "C" void say_hello_from_cpp();
 
 int main(int argc, char* argv[]) {
   say_hello_from_cpp();
+  say_hello_via_callbacks();
   return 0;
 }
diff --git a/build/rust/tests/test_cpp_including_rust/unittests.cc b/build/rust/tests/test_cpp_including_rust/unittests.cc
index a9bca8d..b100e536 100644
--- a/build/rust/tests/test_cpp_including_rust/unittests.cc
+++ b/build/rust/tests/test_cpp_including_rust/unittests.cc
@@ -28,3 +28,8 @@
             base::IsManagedByPartitionAlloc(cpp_allocated_int.get()));
   deallocate_via_rust(rust_allocated_ptr);
 }
+
+extern "C" int32_t add_two_ints_via_rust_then_cpp(int32_t x, int32_t y);
+TEST(RustTest, CppCallingIntoRustAndBack_BasicFFI) {
+  EXPECT_EQ(10, add_two_ints_via_rust_then_cpp(6, 4));
+}
diff --git a/build/rust/tests/test_mixed_source_set/BUILD.gn b/build/rust/tests/test_mixed_source_set/BUILD.gn
new file mode 100644
index 0000000..45840940
--- /dev/null
+++ b/build/rust/tests/test_mixed_source_set/BUILD.gn
@@ -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.
+
+import("//build/rust/rust_source_set.gni")
+
+rust_source_set("test_mixed_source_set_rs") {
+  sources = [ "src/lib.rs" ]
+  mutually_dependent_target = ":test_mixed_source_set"
+}
+
+source_set("test_mixed_source_set") {
+  sources = [
+    "test_mixed_source_set.cc",
+    "test_mixed_source_set.h",
+  ]
+  deps = [ ":test_mixed_source_set_rs_cpp_bindings" ]
+}
diff --git a/build/rust/tests/test_mixed_source_set/src/lib.rs b/build/rust/tests/test_mixed_source_set/src/lib.rs
new file mode 100644
index 0000000..a06e141
--- /dev/null
+++ b/build/rust/tests/test_mixed_source_set/src/lib.rs
@@ -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.
+
+extern "C" {
+    fn cpp_callback();
+    fn cpp_addition(a: u32, b: u32) -> u32;
+}
+
+#[no_mangle]
+pub extern "C" fn rust_code() {
+    say_hello_from_a_cpp_callback_from_rust()
+}
+
+pub fn say_hello_from_a_cpp_callback_from_rust() {
+    unsafe { cpp_callback() }; // we'll have cxx to remove the need
+    // for unsafe in future for these simple cases
+}
+
+#[no_mangle]
+pub extern "C" fn add_two_ints_via_rust_then_cpp(a: u32, b: u32) -> u32 {
+    add_two_ints_using_cpp(a, b)
+}
+
+pub fn add_two_ints_using_cpp(a: u32, b: u32) -> u32 {
+    unsafe { cpp_addition(a, b) }
+}
+
+#[test]
+fn test_callback_to_cpp() {
+    say_hello_from_a_cpp_callback_from_rust();
+    assert_eq!(add_two_ints_via_rust_then_cpp(4u32, 4u32), 8u32);
+}
diff --git a/build/rust/tests/test_mixed_source_set/test_mixed_source_set.cc b/build/rust/tests/test_mixed_source_set/test_mixed_source_set.cc
new file mode 100644
index 0000000..6e2a5f5
--- /dev/null
+++ b/build/rust/tests/test_mixed_source_set/test_mixed_source_set.cc
@@ -0,0 +1,21 @@
+// 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 "test_mixed_source_set.h"
+#include <stdint.h>
+#include <iostream>
+
+extern "C" void rust_code();
+
+void say_hello_via_callbacks() {
+  rust_code();
+}
+
+extern "C" void cpp_callback() {
+  std::cout << "Hello from C++ callback from Rust" << std::endl;
+}
+
+extern "C" uint32_t cpp_addition(uint32_t a, uint32_t b) {
+  return a + b;
+}
diff --git a/build/rust/tests/test_mixed_source_set/test_mixed_source_set.h b/build/rust/tests/test_mixed_source_set/test_mixed_source_set.h
new file mode 100644
index 0000000..189e564
--- /dev/null
+++ b/build/rust/tests/test_mixed_source_set/test_mixed_source_set.h
@@ -0,0 +1,10 @@
+// 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 BUILD_RUST_TESTS_TEST_MIXED_SOURCE_SET_TEST_MIXED_SOURCE_SET_H_
+#define BUILD_RUST_TESTS_TEST_MIXED_SOURCE_SET_TEST_MIXED_SOURCE_SET_H_
+
+void say_hello_via_callbacks();
+
+#endif  // BUILD_RUST_TESTS_TEST_MIXED_SOURCE_SET_TEST_MIXED_SOURCE_SET_H_
diff --git a/build/rust/tests/test_rust_exe/BUILD.gn b/build/rust/tests/test_rust_exe/BUILD.gn
index 0ce2e4a..28d81fa 100644
--- a/build/rust/tests/test_rust_exe/BUILD.gn
+++ b/build/rust/tests/test_rust_exe/BUILD.gn
@@ -4,7 +4,10 @@
 
 executable("test_rust_exe") {
   crate_root = "main.rs"
-  deps = [ "//build/rust/tests/test_rust_source_set" ]
+  deps = [
+    "//build/rust/tests/test_mixed_source_set:test_mixed_source_set_rs",
+    "//build/rust/tests/test_rust_source_set",
+  ]
   rustflags = [
     "--edition",
     "2018",
diff --git a/build/rust/tests/test_rust_exe/main.rs b/build/rust/tests/test_rust_exe/main.rs
index c107205..8514d832 100644
--- a/build/rust/tests/test_rust_exe/main.rs
+++ b/build/rust/tests/test_rust_exe/main.rs
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use test_mixed_source_set_rs::say_hello_from_a_cpp_callback_from_rust;
 use test_rust_source_set::say_hello;
 
 fn main() {
     say_hello();
+    say_hello_from_a_cpp_callback_from_rust();
 }
diff --git a/build/rust/tests/test_rust_unittests/BUILD.gn b/build/rust/tests/test_rust_unittests/BUILD.gn
new file mode 100644
index 0000000..fa6e7e5
--- /dev/null
+++ b/build/rust/tests/test_rust_unittests/BUILD.gn
@@ -0,0 +1,15 @@
+# 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.
+
+executable("test_rust_unittests") {
+  crate_root = "main.rs"
+  deps = [ "//build/rust/tests/test_mixed_source_set:test_mixed_source_set_rs" ]
+  rustflags = [
+    "--edition",
+    "2018",
+    "--cfg",
+    "test",
+    "--test",
+  ]
+}
diff --git a/build/rust/tests/test_rust_unittests/main.rs b/build/rust/tests/test_rust_unittests/main.rs
new file mode 100644
index 0000000..f004fbd
--- /dev/null
+++ b/build/rust/tests/test_rust_unittests/main.rs
@@ -0,0 +1,10 @@
+// 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.
+
+use test_mixed_source_set_rs::add_two_ints_using_cpp;
+
+#[test]
+fn test_call_into_mixed_source_set() {
+    assert_eq!(add_two_ints_using_cpp(5, 7), 12)
+}
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index 5ed1e35..8f483b1 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -185,7 +185,7 @@
       strip_resource_names = !is_java_debug
 
       # Shortens resource file names in apk eg: res/drawable/button.xml -> res/a.xml
-      short_resource_paths = true
+      short_resource_paths = !is_java_debug
 
       # Removes unused resources from the apk. Only enabled on official builds
       # since it adds a slow step and serializes the build graph causing fewer
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 e5a7180d..801f8206 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
@@ -169,8 +169,9 @@
                 headerList.get(index).set(SectionHeaderProperties.OPTIONS_INDICATOR_VISIBILITY_KEY,
                         ViewVisibility.VISIBLE);
             }
-
-            bindStream(newStream);
+            if (!mSettingUpStreams) {
+                bindStream(newStream);
+            }
         }
 
         @Override
@@ -299,6 +300,9 @@
 
     private final HashMap<Integer, Stream> mTabToStreamMap = new HashMap<>();
     private Stream mCurrentStream;
+    // Whether we're currently adding the streams. If this is true, streams should not be bound yet.
+    // This avoids automatically binding the first stream when it's added.
+    private boolean mSettingUpStreams;
 
     /**
      * @param coordinator The {@link FeedSurfaceCoordinator} that interacts with this class.
@@ -459,6 +463,8 @@
      */
     private void initializePropertiesForStream() {
         if (mHasHeader) {
+            assert !mSettingUpStreams;
+            mSettingUpStreams = true;
             mSectionHeaderModel.set(SectionHeaderListProperties.ON_TAB_SELECTED_CALLBACK_KEY,
                     new FeedSurfaceHeaderSelectedCallback());
 
@@ -488,6 +494,7 @@
             if (mTabToStreamMap.size() <= mRestoreTabId) mRestoreTabId = 0;
             mSectionHeaderModel.set(
                     SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY, mRestoreTabId);
+            mSettingUpStreams = false;
         } else {
             // Show feed if there is no header that would allow user to hide feed.
             // This is currently only relevant for the two panes start surface.
@@ -499,8 +506,7 @@
             mSectionHeaderModel.set(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY, 0);
         }
 
-        if (mCoordinator.isActive()
-                && mSectionHeaderModel.get(SectionHeaderListProperties.IS_SECTION_ENABLED_KEY)) {
+        if (mSectionHeaderModel.get(SectionHeaderListProperties.IS_SECTION_ENABLED_KEY)) {
             bindStream(mTabToStreamMap.get(
                     mSectionHeaderModel.get(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY)));
         } else {
@@ -600,6 +606,11 @@
         if (mCurrentStream != null) {
             unbindStream(/* shouldPlaceSpacer = */ true);
         }
+        // Don't bind before the coordinator is active, or when the feed should not show.
+        if (!mCoordinator.isActive()
+                || !mSectionHeaderModel.get(SectionHeaderListProperties.IS_SECTION_ENABLED_KEY)) {
+            return;
+        }
         mCurrentStream = stream;
         mCurrentStream.addOnContentChangedListener(mStreamContentChangedListener);
 
@@ -669,8 +680,6 @@
     private void rebindStream() {
         // If a stream is already bound, then do nothing.
         if (mCurrentStream != null) return;
-        // If feed shouldn't be shown, do nothing.
-        if (!mSectionHeaderModel.get(SectionHeaderListProperties.IS_SECTION_ENABLED_KEY)) return;
         // Find the stream that should be bound and bind it. If no stream matches, then we haven't
         // fully set up yet. This will be taken care of by setup.
         Stream stream = mTabToStreamMap.get(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
index da43239..1cfb672 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -117,6 +117,7 @@
 import org.chromium.components.browser_ui.widget.TouchEventObserver;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.components.messages.MessageDispatcherProvider;
+import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.ActivityWindowAndroid;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.IntentRequestTracker;
@@ -632,9 +633,19 @@
                         ChromeFeatureList.DARKEN_WEBSITES_CHECKBOX_IN_THEMES_SETTING)) {
             // TODO(crbug.com/1252965): Investigate locking feature engagement system during
             // "second run promos" to avoid !didTriggerPromo check.
-            WebContentsDarkModeMessageController.attemptToSendMessage(mActivity,
-                    Profile.fromWebContents(mActivityTabProvider.get().getWebContents()),
-                    new SettingsLauncherImpl(), mMessageDispatcher);
+            Tab tab;
+            WebContents webContents;
+
+            Profile profile;
+            if ((tab = mActivityTabProvider.get()) != null
+                    && (webContents = tab.getWebContents()) != null) {
+                profile = Profile.fromWebContents(webContents);
+            } else {
+                profile = Profile.getLastUsedRegularProfile();
+            }
+
+            WebContentsDarkModeMessageController.attemptToSendMessage(
+                    mActivity, profile, new SettingsLauncherImpl(), mMessageDispatcher);
         }
 
         if (FeedFeatures.isWebFeedUIEnabled()) {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceMediatorTest.java
index 12c02385..0121ec9 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceMediatorTest.java
@@ -7,7 +7,6 @@
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.eq;
@@ -32,6 +31,7 @@
 import org.mockito.junit.MockitoRule;
 import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLog;
 
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
@@ -101,7 +101,9 @@
     @Mock
     private TemplateUrlService mUrlService;
     @Mock
-    private FeedStream mStream;
+    private FeedStream mForYouStream;
+    @Mock
+    private FeedStream mFollowingStream;
     @Mock
     private FeedLaunchReliabilityLogger mLaunchReliabilityLogger;
     @Mock
@@ -116,6 +118,9 @@
 
     @Before
     public void setUp() {
+        // Print logs to stdout.
+        ShadowLog.stream = System.out;
+
         mActivity = Robolectric.buildActivity(Activity.class).get();
         mocker.mock(FeedServiceBridgeJni.TEST_HOOKS, mFeedServiceBridgeJniMock);
         mocker.mock(WebFeedBridge.getTestHooksForTesting(), mWebFeedBridgeJniMock);
@@ -125,9 +130,11 @@
         when(mIdentityService.getSigninManager(any(Profile.class))).thenReturn(mSigninManager);
         when(mSigninManager.getIdentityManager()).thenReturn(mIdentityManager);
         when(mIdentityManager.hasPrimaryAccount(anyInt())).thenReturn(true);
+        when(mFeedSurfaceCoordinator.isActive()).thenReturn(true);
         when(mFeedSurfaceCoordinator.getRecyclerView()).thenReturn(new RecyclerView(mActivity));
-        when(mFeedSurfaceCoordinator.getStream()).thenReturn(mStream);
-        when(mFeedSurfaceCoordinator.createFeedStream(anyBoolean())).thenReturn(mStream);
+        when(mFeedSurfaceCoordinator.getStream()).thenReturn(mForYouStream);
+        when(mFeedSurfaceCoordinator.createFeedStream(eq(false))).thenReturn(mFollowingStream);
+        when(mFeedSurfaceCoordinator.createFeedStream(eq(true))).thenReturn(mForYouStream);
         when(mFeedSurfaceCoordinator.getLaunchReliabilityLogger())
                 .thenReturn(mLaunchReliabilityLogger);
         when(mFeedSurfaceCoordinator.getHybridListRenderer()).thenReturn(mHybridListRenderer);
@@ -135,7 +142,8 @@
                 .thenReturn(mFeedSurfaceLifecycleManager);
         ObservableSupplierImpl<Boolean> hasUnreadContent = new ObservableSupplierImpl<>();
         hasUnreadContent.set(false);
-        when(mStream.hasUnreadContent()).thenReturn(hasUnreadContent);
+        when(mForYouStream.hasUnreadContent()).thenReturn(hasUnreadContent);
+        when(mFollowingStream.hasUnreadContent()).thenReturn(hasUnreadContent);
 
         FeedSurfaceMediator.setPrefForTest(mPrefChangeRegistrar, mPrefService);
         FeedFeatures.setFakePrefsForTest(mPrefService);
@@ -198,30 +206,37 @@
 
     @Test
     public void updateContent_openingTabIdFollowing() {
+        when(mPrefService.getBoolean(Pref.ARTICLES_LIST_VISIBLE)).thenReturn(true);
         PropertyModel sectionHeaderModel = SectionHeaderListProperties.create();
         mFeedSurfaceMediator =
                 createMediator(FeedSurfaceCoordinator.StreamTabId.FOLLOWING, sectionHeaderModel);
         mFeedSurfaceMediator.updateContent();
 
+        verify(mForYouStream, never()).bind(any(), any(), any(), any(), any(), any(), anyInt());
+        verify(mFollowingStream, times(1)).bind(any(), any(), any(), any(), any(), any(), anyInt());
         assertEquals(FeedSurfaceCoordinator.StreamTabId.FOLLOWING,
                 sectionHeaderModel.get(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY));
     }
 
     @Test
     public void updateContent_openingTabIdForYou() {
+        when(mPrefService.getBoolean(Pref.ARTICLES_LIST_VISIBLE)).thenReturn(true);
         PropertyModel sectionHeaderModel = SectionHeaderListProperties.create();
         mFeedSurfaceMediator =
                 createMediator(FeedSurfaceCoordinator.StreamTabId.FOR_YOU, sectionHeaderModel);
         mFeedSurfaceMediator.updateContent();
 
+        verify(mForYouStream, times(1)).bind(any(), any(), any(), any(), any(), any(), anyInt());
+        verify(mFollowingStream, never()).bind(any(), any(), any(), any(), any(), any(), anyInt());
         assertEquals(FeedSurfaceCoordinator.StreamTabId.FOR_YOU,
                 sectionHeaderModel.get(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY));
     }
 
     @Test
     public void testOnSurfaceClosed_launchInProgress() {
+        when(mPrefService.getBoolean(Pref.ARTICLES_LIST_VISIBLE)).thenReturn(true);
         mFeedSurfaceMediator = createMediator();
-        mFeedSurfaceMediator.bindStream(mStream);
+        mFeedSurfaceMediator.updateContent();
 
         when(mLaunchReliabilityLogger.isLaunchInProgress()).thenReturn(true);
         mFeedSurfaceMediator.onSurfaceClosed();
@@ -232,8 +247,9 @@
 
     @Test
     public void testOnSurfaceClosed_noLaunchInProgress() {
+        when(mPrefService.getBoolean(Pref.ARTICLES_LIST_VISIBLE)).thenReturn(true);
         mFeedSurfaceMediator = createMediator();
-        mFeedSurfaceMediator.bindStream(mStream);
+        mFeedSurfaceMediator.updateContent();
 
         when(mLaunchReliabilityLogger.isLaunchInProgress()).thenReturn(false);
         mFeedSurfaceMediator.onSurfaceClosed();
@@ -388,11 +404,11 @@
                 .add(SectionHeaderProperties.createSectionHeader("Following"));
         mFeedSurfaceMediator = createMediator(FeedSurfaceCoordinator.StreamTabId.FOLLOWING, model);
 
-        when(mStream.getOptionsView()).thenReturn(mView);
+        when(mForYouStream.getOptionsView()).thenReturn(mView);
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mForYouStream);
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mForYouStream);
 
         OnSectionHeaderSelectedListener listener =
                 mFeedSurfaceMediator.getOrCreateSectionHeaderListenerForTesting();
@@ -417,9 +433,9 @@
         mFeedSurfaceMediator = createMediator(FeedSurfaceCoordinator.StreamTabId.FOLLOWING, model);
 
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mForYouStream);
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mForYouStream);
 
         OnSectionHeaderSelectedListener listener =
                 mFeedSurfaceMediator.getOrCreateSectionHeaderListenerForTesting();
@@ -444,11 +460,11 @@
                 .add(SectionHeaderProperties.createSectionHeader("Following"));
         mFeedSurfaceMediator = createMediator(FeedSurfaceCoordinator.StreamTabId.FOLLOWING, model);
 
-        when(mStream.getOptionsView()).thenReturn(mView);
+        when(mForYouStream.getOptionsView()).thenReturn(mView);
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mForYouStream);
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mForYouStream);
 
         OnSectionHeaderSelectedListener listener =
                 mFeedSurfaceMediator.getOrCreateSectionHeaderListenerForTesting();
@@ -472,9 +488,9 @@
         mFeedSurfaceMediator = createMediator(FeedSurfaceCoordinator.StreamTabId.FOLLOWING, model);
 
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mForYouStream);
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mForYouStream);
 
         OnSectionHeaderSelectedListener listener =
                 mFeedSurfaceMediator.getOrCreateSectionHeaderListenerForTesting();
@@ -494,11 +510,11 @@
                 .add(SectionHeaderProperties.createSectionHeader("Following"));
         mFeedSurfaceMediator = createMediator(FeedSurfaceCoordinator.StreamTabId.FOLLOWING, model);
 
-        when(mStream.getOptionsView()).thenReturn(mView);
+        when(mForYouStream.getOptionsView()).thenReturn(mView);
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mForYouStream);
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mForYouStream);
 
         OnSectionHeaderSelectedListener listener =
                 mFeedSurfaceMediator.getOrCreateSectionHeaderListenerForTesting();
@@ -520,9 +536,9 @@
         mFeedSurfaceMediator = createMediator(FeedSurfaceCoordinator.StreamTabId.FOLLOWING, model);
 
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOLLOWING, mForYouStream);
         mFeedSurfaceMediator.setStreamForTesting(
-                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mStream);
+                FeedSurfaceCoordinator.StreamTabId.FOR_YOU, mForYouStream);
 
         OnSectionHeaderSelectedListener listener =
                 mFeedSurfaceMediator.getOrCreateSectionHeaderListenerForTesting();
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 24cca08..25ffdd30 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5146,6 +5146,12 @@
       <message name="IDS_WEB_APP_DISABLE_WINDOW_CONTROLS_OVERLAY_TOOLTIP">
         Show title bar
       </message>
+      <message name="IDS_WEB_APP_WINDOW_CONTROLS_OVERLAY_ENABLED_ALERT" desc="Status change notification that the title bar is hidden after window controls overlay is enabled">
+        Title bar is now hidden
+      </message>
+      <message name="IDS_WEB_APP_WINDOW_CONTROLS_OVERLAY_DISABLED_ALERT" desc="Status change notification that the title bar is showing after window controls overlay is disabled">
+        Title bar is now showing
+      </message>
 
       <!-- Components -->
       <message name="IDS_COMPONENTS_TITLE" desc="Title for the chrome://components page.">
@@ -7233,6 +7239,9 @@
         <message name="IDS_TAB_CXMENU_CLOSETABSTORIGHT" desc="The label of the 'Close Tabs to the Right' Tab context menu item.">
           Close tabs to the right
         </message>
+        <message name="IDS_TAB_CXMENU_CLOSETABSTOLEFT" desc="The label of the 'Close Tabs to the Left' Tab context menu item.">
+          Close tabs to the left
+        </message>
         <message name="IDS_TAB_CXMENU_FOCUS_THIS_TAB" desc="The label of the 'Focus this tab' Tab context menu item.">
           Focus this tab
         </message>
@@ -7313,6 +7322,9 @@
         <message name="IDS_TAB_CXMENU_CLOSETABSTORIGHT" desc="In Title Case: The label of the 'Close Tabs to the Right' Tab context menu item.">
           Close Tabs to the Right
         </message>
+        <message name="IDS_TAB_CXMENU_CLOSETABSTOLEFT" desc="In Title Case: The label of the 'Close Tabs to the Left' Tab context menu item.">
+          Close Tabs to the Left
+        </message>
         <message name="IDS_TAB_CXMENU_FOCUS_THIS_TAB" desc="In Title Case: The label of the 'Focus This Tab' Tab context menu item.">
           Focus This Tab
         </message>
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_CXMENU_CLOSETABSTOLEFT.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_CXMENU_CLOSETABSTOLEFT.png.sha1
new file mode 100644
index 0000000..4954573
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_CXMENU_CLOSETABSTOLEFT.png.sha1
@@ -0,0 +1 @@
+cc11fe8891b6c2bb71a98ef553ef6057c20aa256
\ No newline at end of file
diff --git a/chrome/app/global_media_controls_strings.grdp b/chrome/app/global_media_controls_strings.grdp
index 52751fd..2871c4ed 100644
--- a/chrome/app/global_media_controls_strings.grdp
+++ b/chrome/app/global_media_controls_strings.grdp
@@ -1,15 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- Global Media Controls-specific strings (included from generated_resources.grd). -->
 <grit-part>
-  <message name="IDS_GLOBAL_MEDIA_CONTROLS_BACK_TO_TAB" desc="A11y text for the Global Media Controls container describing how clicking it takes you back to the tab.">
-   Back to tab
-  </message>
   <message name="IDS_GLOBAL_MEDIA_CONTROLS_ICON_TOOLTIP_TEXT" desc="Tooltip for the Global Media Controls icon, which appears in the toolbar. The tooltip appears on mouseover of the icon.">
    Control your music, videos, and more
   </message>
-  <message name="IDS_GLOBAL_MEDIA_CONTROLS_DISMISS_ICON_TOOLTIP_TEXT" desc="Tooltip for the dismiss button within the Global Media Controls dialog. The tooltip appears on mouseover of the icon.">
-   Dismiss
-  </message>
   <message name="IDS_GLOBAL_MEDIA_CONTROLS_DEVICES_BUTTON_LABEL" desc="Label for a button that opens a menu for picking an audio or cast device to play from.">
    Select a device
   </message>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 2c7581e..3ce275f 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2444,7 +2444,6 @@
       "//chrome/browser/ui/webui/chromeos/crostini_installer:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/crostini_upgrader:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/emoji:mojo_bindings",
-      "//chrome/browser/ui/webui/chromeos/enterprise_casting:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/launcher_internals:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/parent_access:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/vm:mojo_bindings",
@@ -4413,6 +4412,7 @@
       "//chrome/browser/share/proto:proto",
       "//chrome/browser/ui/color:color_headers",
       "//chrome/browser/ui/webui/tab_strip:mojo_bindings",
+      "//chrome/browser/ui/webui/enterprise_casting:mojo_bindings",
       "//chrome/browser/web_applications",
       "//chrome/browser/web_applications/app_service",
       "//chrome/common/apps/platform_apps",
@@ -5025,6 +5025,8 @@
       "lacros/client_cert_store_lacros.h",
       "lacros/crosapi_pref_observer.cc",
       "lacros/crosapi_pref_observer.h",
+      "lacros/device_settings_lacros.cc",
+      "lacros/device_settings_lacros.h",
       "lacros/download_controller_client_lacros.cc",
       "lacros/download_controller_client_lacros.h",
       "lacros/drivefs_cache.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 84b04cba..2adf132 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4201,6 +4201,15 @@
      kOsCrOS,
      FEATURE_VALUE_TYPE(
          features::kExperimentalAccessibilitySwitchAccessSetupGuide)},
+    {"enable-experimental-accessibility-switch-access-multistep-automation",
+     flag_descriptions::
+         kExperimentalAccessibilitySwitchAccessMultistepAutomationName,
+     flag_descriptions::
+         kExperimentalAccessibilitySwitchAccessMultistepAutomationDescription,
+     kOsCrOS,
+     SINGLE_VALUE_TYPE(
+         ::switches::
+             kEnableExperimentalAccessibilitySwitchAccessMultistepAutomation)},
     {"enable-experimental-kernel-vm-support",
      flag_descriptions::kKernelnextVMsName,
      flag_descriptions::kKernelnextVMsDescription, kOsCrOS,
@@ -7461,6 +7470,11 @@
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+    {"enable-phone-hub-call-notification",
+     flag_descriptions::kPhoneHubCallNotificationName,
+     flag_descriptions::kPhoneHubCallNotificationDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kPhoneHubCallNotification)},
+
     {"enable-phone-hub-camera-roll", flag_descriptions::kPhoneHubCameraRollName,
      flag_descriptions::kPhoneHubCameraRollDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kPhoneHubCameraRoll)},
diff --git a/chrome/browser/android/explore_sites/explore_sites_fetcher_unittest.cc b/chrome/browser/android/explore_sites/explore_sites_fetcher_unittest.cc
index 92392033..455e2fd 100644
--- a/chrome/browser/android/explore_sites/explore_sites_fetcher_unittest.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_fetcher_unittest.cc
@@ -366,9 +366,8 @@
 
   net::HttpRequestHeaders headers = last_resource_request.headers;
   std::string header_text;
-  bool success;
 
-  success = headers.GetHeader("X-Goog-Chrome-Experiment-Tag", &header_text);
+  headers.GetHeader("X-Goog-Chrome-Experiment-Tag", &header_text);
   EXPECT_EQ(std::string(kExperimentData), header_text);
 }
 
diff --git a/chrome/browser/android/oom_intervention/oom_intervention_config.cc b/chrome/browser/android/oom_intervention/oom_intervention_config.cc
index 2390fe9..d678c96 100644
--- a/chrome/browser/android/oom_intervention/oom_intervention_config.cc
+++ b/chrome/browser/android/oom_intervention/oom_intervention_config.cc
@@ -132,11 +132,8 @@
   use_components_callback_ = base::GetFieldTrialParamByFeatureAsBool(
       features::kOomIntervention, kUseComponentCallbacks, true);
 
-  OomInterventionBrowserMonitorStatus status =
-      OomInterventionBrowserMonitorStatus::kEnabledWithValidConfig;
   if (!GetSwapFreeThreshold(&swapfree_threshold_)) {
     is_swap_monitor_enabled_ = false;
-    status = OomInterventionBrowserMonitorStatus::kEnabledWithNoSwap;
   }
   // If no threshold is specified, set blink_workload_threshold to 10% of the
   // RAM size.
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 97749b5..5d98086c 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -302,7 +302,6 @@
   // TODO(davidben): To limit those cases, consider preferentially
   // deminiaturizing a window on the current space.
   NSWindow* frontmost_window = nil;
-  NSWindow* frontmost_window_all_spaces = nil;
   NSWindow* frontmost_miniaturized_window = nil;
   bool all_miniaturized = true;
   for (NSWindow* win in [[NSApp orderedWindows] reverseObjectEnumerator]) {
@@ -312,7 +311,6 @@
       frontmost_miniaturized_window = win;
     } else if ([win isVisible]) {
       all_miniaturized = false;
-      frontmost_window_all_spaces = win;
       if ([win isOnActiveSpace]) {
         // Raise the old |frontmost_window| (if any). The topmost |win| will be
         // raised with makeKeyAndOrderFront: below.
diff --git a/chrome/browser/ash/app_restore/app_restore_arc_task_handler.cc b/chrome/browser/ash/app_restore/app_restore_arc_task_handler.cc
index c297d35..5d9d49a 100644
--- a/chrome/browser/ash/app_restore/app_restore_arc_task_handler.cc
+++ b/chrome/browser/ash/app_restore/app_restore_arc_task_handler.cc
@@ -115,6 +115,8 @@
     if (handler)
       handler->OnArcPlayStoreEnabledChanged(enabled);
   }
+
+  GetAppRestoreArcInfo()->NotifyPlayStoreEnabledChanged(enabled);
 }
 
 void AppRestoreArcTaskHandler::OnShelfReady() {
diff --git a/chrome/browser/ash/app_restore/full_restore_app_launch_handler_browsertest.cc b/chrome/browser/ash/app_restore/full_restore_app_launch_handler_browsertest.cc
index e3f755f..49aade5 100644
--- a/chrome/browser/ash/app_restore/full_restore_app_launch_handler_browsertest.cc
+++ b/chrome/browser/ash/app_restore/full_restore_app_launch_handler_browsertest.cc
@@ -1418,6 +1418,14 @@
     return &test_full_restore_info_observer_;
   }
 
+  const std::map<int32_t, std::string>& task_id_to_app_id() {
+    auto* arc_save_handler =
+        ::full_restore::FullRestoreSaveHandler::GetInstance()
+            ->arc_save_handler_.get();
+    DCHECK(arc_save_handler);
+    return arc_save_handler->task_id_to_app_id_;
+  }
+
  protected:
   app_restore::ArcAppLaunchHandler* arc_app_launch_handler_ = nullptr;
 
@@ -1928,6 +1936,89 @@
   StopInstance();
 }
 
+// Test ARC apps restore data is removed, when Play Store is disabled.
+IN_PROC_BROWSER_TEST_F(FullRestoreAppLaunchHandlerArcAppBrowserTest,
+                       DisablePlayStore) {
+  SetProfile();
+  InstallTestApps(kTestAppPackage, true);
+
+  const std::string app_id1 = GetTestApp1Id(kTestAppPackage);
+  const std::string app_id2 = GetTestApp2Id(kTestAppPackage);
+  int32_t session_id1 =
+      ::full_restore::FullRestoreSaveHandler::GetInstance()->GetArcSessionId();
+  int32_t session_id2 =
+      ::full_restore::FullRestoreSaveHandler::GetInstance()->GetArcSessionId();
+  ::full_restore::FullRestoreInfo::GetInstance()->AddObserver(
+      test_full_restore_info_observer());
+
+  SaveAppLaunchInfo(app_id1, session_id1);
+  SaveAppLaunchInfo(app_id2, session_id2);
+
+  // Simulate creating kTaskId1.
+  int32_t kTaskId1 = 100;
+  CreateTask(app_id1, kTaskId1, session_id1);
+
+  // Create the window for the app1 and store its bounds.
+  views::Widget* widget1 = CreateExoWindow("org.chromium.arc.100");
+  aura::Window* window1 = widget1->GetNativeWindow();
+
+  // Create the window for the app2 and store its bounds.
+  int32_t kTaskId2 = 101;
+  views::Widget* widget2 = CreateExoWindow("org.chromium.arc.101");
+  aura::Window* window2 = widget2->GetNativeWindow();
+
+  // Simulate creating kTaskId2.
+  CreateTask(app_id2, kTaskId2, session_id2);
+  VerifyObserver(window1, /*launch_count=*/1, /*init_count=*/0);
+  VerifyObserver(window2, /*launch_count=*/1, /*init_count=*/0);
+
+  VerifyWindowProperty(window1, kTaskId1, /*restore_window_id*/ 0,
+                       /*hidden=*/false);
+  VerifyWindowProperty(window2, kTaskId2, /*restore_window_id*/ 0,
+                       /*hidden=*/false);
+
+  WaitForAppLaunchInfoSaved();
+
+  int32_t activation_index1 = 11;
+  int32_t activation_index2 = 12;
+  SaveWindowInfo(window1, activation_index1,
+                 chromeos::WindowStateType::kMaximized);
+  SaveWindowInfo(window2, activation_index2,
+                 chromeos::WindowStateType::kMinimized);
+
+  WaitForAppLaunchInfoSaved();
+
+  // Verify ARC app launch info is saved in `restore_data`.
+  const auto* restore_data =
+      ::full_restore::FullRestoreSaveHandler::GetInstance()->GetRestoreData(
+          profile()->GetPath());
+  ASSERT_TRUE(restore_data);
+  ASSERT_FALSE(restore_data->app_id_to_launch_list().empty());
+  ASSERT_FALSE(task_id_to_app_id().empty());
+
+  // Simulate Play Store is disabled.
+  app_restore::AppRestoreArcTaskHandler::GetForProfile(profile())
+      ->OnArcPlayStoreEnabledChanged(/*enabled=*/false);
+  widget1->CloseNow();
+
+  // Verify ARC app launch info is removed from `restore_data`.
+  ASSERT_TRUE(restore_data->app_id_to_launch_list().empty());
+  ASSERT_TRUE(task_id_to_app_id().empty());
+
+  widget2->CloseNow();
+
+  // Simulate Play Store is enabled and `app_id1` is launched.
+  int32_t session_id3 =
+      ::full_restore::FullRestoreSaveHandler::GetInstance()->GetArcSessionId();
+  int32_t kTaskId3 = 201;
+  SaveAppLaunchInfo(app_id1, session_id3);
+  CreateTask(app_id1, kTaskId3, session_id3);
+  ASSERT_FALSE(restore_data->app_id_to_launch_list().empty());
+  ASSERT_TRUE(base::Contains(restore_data->app_id_to_launch_list(), app_id1));
+
+  StopInstance();
+}
+
 // Test restoration when the ARC window is created before OnTaskCreated is
 // called.
 IN_PROC_BROWSER_TEST_F(FullRestoreAppLaunchHandlerArcAppBrowserTest,
diff --git a/chrome/browser/ash/crosapi/BUILD.gn b/chrome/browser/ash/crosapi/BUILD.gn
index b827416..ad18063 100644
--- a/chrome/browser/ash/crosapi/BUILD.gn
+++ b/chrome/browser/ash/crosapi/BUILD.gn
@@ -43,6 +43,8 @@
     "crosapi_manager.h",
     "device_attributes_ash.cc",
     "device_attributes_ash.h",
+    "device_settings_ash.cc",
+    "device_settings_ash.h",
     "download_controller_ash.cc",
     "download_controller_ash.h",
     "drive_integration_service_ash.cc",
diff --git a/chrome/browser/ash/crosapi/browser_util.cc b/chrome/browser/ash/crosapi/browser_util.cc
index 1b78279..739bb2b 100644
--- a/chrome/browser/ash/crosapi/browser_util.cc
+++ b/chrome/browser/ash/crosapi/browser_util.cc
@@ -58,6 +58,7 @@
 #include "chromeos/crosapi/mojom/content_protection.mojom.h"
 #include "chromeos/crosapi/mojom/crosapi.mojom.h"
 #include "chromeos/crosapi/mojom/device_attributes.mojom.h"
+#include "chromeos/crosapi/mojom/device_settings_service.mojom.h"
 #include "chromeos/crosapi/mojom/download_controller.mojom.h"
 #include "chromeos/crosapi/mojom/drive_integration_service.mojom.h"
 #include "chromeos/crosapi/mojom/feedback.mojom.h"
@@ -262,72 +263,6 @@
   return result;
 }
 
-// Returns the device policy data needed for Lacros.
-mojom::DeviceSettingsPtr GetDeviceSettings() {
-  mojom::DeviceSettingsPtr result = mojom::DeviceSettings::New();
-
-  result->attestation_for_content_protection_enabled = MojoOptionalBool::kUnset;
-  if (ash::CrosSettings::IsInitialized()) {
-    // It's expected that the CrosSettings values are trusted. The only
-    // theoretical exception is when device ownership is taken on consumer
-    // device. Then there's no settings to be passed to Lacros anyway.
-    auto trusted_result =
-        ash::CrosSettings::Get()->PrepareTrustedValues(base::DoNothing());
-    if (trusted_result == ash::CrosSettingsProvider::TRUSTED) {
-      const auto* cros_settings = ash::CrosSettings::Get();
-      bool attestation_enabled = false;
-      if (cros_settings->GetBoolean(
-              ash::kAttestationForContentProtectionEnabled,
-              &attestation_enabled)) {
-        result->attestation_for_content_protection_enabled =
-            attestation_enabled ? MojoOptionalBool::kTrue
-                                : MojoOptionalBool::kFalse;
-      }
-
-      const base::ListValue* usb_detachable_allow_list;
-      if (cros_settings->GetList(ash::kUsbDetachableAllowlist,
-                                 &usb_detachable_allow_list)) {
-        mojom::UsbDetachableAllowlistPtr allow_list =
-            mojom::UsbDetachableAllowlist::New();
-        for (const auto& entry : usb_detachable_allow_list->GetList()) {
-          mojom::UsbDeviceIdPtr usb_device_id = mojom::UsbDeviceId::New();
-          absl::optional<int> vid =
-              entry.FindIntKey(ash::kUsbDetachableAllowlistKeyVid);
-          if (vid) {
-            usb_device_id->has_vendor_id = true;
-            usb_device_id->vendor_id = vid.value();
-          }
-          absl::optional<int> pid =
-              entry.FindIntKey(ash::kUsbDetachableAllowlistKeyPid);
-          if (pid) {
-            usb_device_id->has_product_id = true;
-            usb_device_id->product_id = pid.value();
-          }
-          allow_list->usb_device_ids.push_back(std::move(usb_device_id));
-        }
-        result->usb_detachable_allow_list = std::move(allow_list);
-      }
-    } else {
-      LOG(WARNING) << "Unexpected crossettings trusted values status: "
-                   << trusted_result;
-    }
-  }
-
-  result->device_system_wide_tracing_enabled = MojoOptionalBool::kUnset;
-  auto* local_state = g_browser_process->local_state();
-  if (local_state) {
-    auto* pref = local_state->FindPreference(
-        ash::prefs::kDeviceSystemWideTracingEnabled);
-    if (pref && pref->IsManaged()) {
-      result->device_system_wide_tracing_enabled =
-          pref->GetValue()->GetBool() ? MojoOptionalBool::kTrue
-                                      : MojoOptionalBool::kFalse;
-    }
-  }
-
-  return result;
-}
-
 struct InterfaceVersionEntry {
   base::Token uuid;
   uint32_t version;
@@ -355,6 +290,7 @@
     MakeInterfaceVersionEntry<crosapi::mojom::ContentProtection>(),
     MakeInterfaceVersionEntry<crosapi::mojom::Crosapi>(),
     MakeInterfaceVersionEntry<crosapi::mojom::DeviceAttributes>(),
+    MakeInterfaceVersionEntry<crosapi::mojom::DeviceSettingsService>(),
     MakeInterfaceVersionEntry<crosapi::mojom::DownloadController>(),
     MakeInterfaceVersionEntry<crosapi::mojom::DriveIntegrationService>(),
     MakeInterfaceVersionEntry<crosapi::mojom::Feedback>(),
@@ -458,7 +394,7 @@
 }
 
 static_assert(
-    crosapi::mojom::Crosapi::Version_ == 54,
+    crosapi::mojom::Crosapi::Version_ == 55,
     "if you add a new crosapi, please add it to kInterfaceVersionEntries");
 static_assert(!HasDuplicatedUuid(),
               "Each Crosapi Mojom interface should have unique UUID.");
@@ -1094,6 +1030,72 @@
   }
 }
 
+// Returns the device policy data needed for Lacros.
+mojom::DeviceSettingsPtr GetDeviceSettings() {
+  mojom::DeviceSettingsPtr result = mojom::DeviceSettings::New();
+
+  result->attestation_for_content_protection_enabled = MojoOptionalBool::kUnset;
+  if (ash::CrosSettings::IsInitialized()) {
+    // It's expected that the CrosSettings values are trusted. The only
+    // theoretical exception is when device ownership is taken on consumer
+    // device. Then there's no settings to be passed to Lacros anyway.
+    auto trusted_result =
+        ash::CrosSettings::Get()->PrepareTrustedValues(base::DoNothing());
+    if (trusted_result == ash::CrosSettingsProvider::TRUSTED) {
+      const auto* cros_settings = ash::CrosSettings::Get();
+      bool attestation_enabled = false;
+      if (cros_settings->GetBoolean(
+              ash::kAttestationForContentProtectionEnabled,
+              &attestation_enabled)) {
+        result->attestation_for_content_protection_enabled =
+            attestation_enabled ? MojoOptionalBool::kTrue
+                                : MojoOptionalBool::kFalse;
+      }
+
+      const base::ListValue* usb_detachable_allow_list;
+      if (cros_settings->GetList(ash::kUsbDetachableAllowlist,
+                                 &usb_detachable_allow_list)) {
+        mojom::UsbDetachableAllowlistPtr allow_list =
+            mojom::UsbDetachableAllowlist::New();
+        for (const auto& entry : usb_detachable_allow_list->GetList()) {
+          mojom::UsbDeviceIdPtr usb_device_id = mojom::UsbDeviceId::New();
+          absl::optional<int> vid =
+              entry.FindIntKey(ash::kUsbDetachableAllowlistKeyVid);
+          if (vid) {
+            usb_device_id->has_vendor_id = true;
+            usb_device_id->vendor_id = vid.value();
+          }
+          absl::optional<int> pid =
+              entry.FindIntKey(ash::kUsbDetachableAllowlistKeyPid);
+          if (pid) {
+            usb_device_id->has_product_id = true;
+            usb_device_id->product_id = pid.value();
+          }
+          allow_list->usb_device_ids.push_back(std::move(usb_device_id));
+        }
+        result->usb_detachable_allow_list = std::move(allow_list);
+      }
+    } else {
+      LOG(WARNING) << "Unexpected crossettings trusted values status: "
+                   << trusted_result;
+    }
+  }
+
+  result->device_system_wide_tracing_enabled = MojoOptionalBool::kUnset;
+  auto* local_state = g_browser_process->local_state();
+  if (local_state) {
+    auto* pref = local_state->FindPreference(
+        ash::prefs::kDeviceSystemWideTracingEnabled);
+    if (pref && pref->IsManaged()) {
+      result->device_system_wide_tracing_enabled =
+          pref->GetValue()->GetBool() ? MojoOptionalBool::kTrue
+                                      : MojoOptionalBool::kFalse;
+    }
+  }
+
+  return result;
+}
+
 LacrosLaunchSwitch GetLaunchSwitchForTesting() {
   return GetLaunchSwitch();
 }
diff --git a/chrome/browser/ash/crosapi/browser_util.h b/chrome/browser/ash/crosapi/browser_util.h
index 6e9f2d4c..9d82b4c 100644
--- a/chrome/browser/ash/crosapi/browser_util.h
+++ b/chrome/browser/ash/crosapi/browser_util.h
@@ -283,6 +283,9 @@
 version_info::Channel GetLacrosSelectionUpdateChannel(
     LacrosSelection selection);
 
+// Returns the device settings needed for Lacros.
+mojom::DeviceSettingsPtr GetDeviceSettings();
+
 // Exposed for testing. Returns the lacros integration suggested by the policy
 // lacros-availability, modified by Finch flags and user flags as appropriate.
 LacrosLaunchSwitch GetLaunchSwitchForTesting();
diff --git a/chrome/browser/ash/crosapi/browser_util_unittest.cc b/chrome/browser/ash/crosapi/browser_util_unittest.cc
index 81c6aa0..cef0dc5 100644
--- a/chrome/browser/ash/crosapi/browser_util_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_util_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/values.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -23,6 +24,7 @@
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/crosapi/mojom/crosapi.mojom.h"
 #include "chromeos/crosapi/mojom/keystore_service.mojom.h"
+#include "chromeos/settings/cros_settings_names.h"
 #include "components/account_id/account_id.h"
 #include "components/policy/policy_constants.h"
 #include "components/user_manager/scoped_user_manager.h"
@@ -700,4 +702,50 @@
   cmdline->RemoveSwitch(browser_util::kLacrosStabilitySwitch);
 }
 
+TEST_F(BrowserUtilTest, EmptyDeviceSettings) {
+  auto settings = browser_util::GetDeviceSettings();
+  EXPECT_EQ(settings->attestation_for_content_protection_enabled,
+            crosapi::mojom::DeviceSettings::OptionalBool::kUnset);
+  EXPECT_EQ(settings->device_system_wide_tracing_enabled,
+            crosapi::mojom::DeviceSettings::OptionalBool::kUnset);
+}
+
+TEST_F(BrowserUtilTest, DeviceSettingsWithData) {
+  testing_profile_.ScopedCrosSettingsTestHelper()
+      ->ReplaceDeviceSettingsProviderWithStub();
+  testing_profile_.ScopedCrosSettingsTestHelper()->SetTrustedStatus(
+      ash::CrosSettingsProvider::TRUSTED);
+  base::RunLoop().RunUntilIdle();
+  testing_profile_.ScopedCrosSettingsTestHelper()
+      ->GetStubbedProvider()
+      ->SetBoolean(chromeos::kAttestationForContentProtectionEnabled, true);
+
+  base::Value allowlist(base::Value::Type::LIST);
+  base::Value ids(base::Value::Type::DICTIONARY);
+  ids.SetIntKey(ash::kUsbDetachableAllowlistKeyVid, 2);
+  ids.SetIntKey(ash::kUsbDetachableAllowlistKeyPid, 3);
+  allowlist.Append(std::move(ids));
+
+  testing_profile_.ScopedCrosSettingsTestHelper()->GetStubbedProvider()->Set(
+      chromeos::kUsbDetachableAllowlist, std::move(allowlist));
+
+  auto settings = browser_util::GetDeviceSettings();
+  testing_profile_.ScopedCrosSettingsTestHelper()
+      ->RestoreRealDeviceSettingsProvider();
+
+  EXPECT_EQ(settings->attestation_for_content_protection_enabled,
+            crosapi::mojom::DeviceSettings::OptionalBool::kTrue);
+  EXPECT_EQ(settings->usb_detachable_allow_list->usb_device_ids.size(), 1);
+  EXPECT_EQ(
+      settings->usb_detachable_allow_list->usb_device_ids[0]->has_vendor_id,
+      true);
+  EXPECT_EQ(settings->usb_detachable_allow_list->usb_device_ids[0]->vendor_id,
+            2);
+  EXPECT_EQ(
+      settings->usb_detachable_allow_list->usb_device_ids[0]->has_product_id,
+      true);
+  EXPECT_EQ(settings->usb_detachable_allow_list->usb_device_ids[0]->product_id,
+            3);
+}
+
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/crosapi_ash.cc b/chrome/browser/ash/crosapi/crosapi_ash.cc
index daa94f1733..e8983b84 100644
--- a/chrome/browser/ash/crosapi/crosapi_ash.cc
+++ b/chrome/browser/ash/crosapi/crosapi_ash.cc
@@ -29,6 +29,7 @@
 #include "chrome/browser/ash/crosapi/clipboard_history_ash.h"
 #include "chrome/browser/ash/crosapi/content_protection_ash.h"
 #include "chrome/browser/ash/crosapi/device_attributes_ash.h"
+#include "chrome/browser/ash/crosapi/device_settings_ash.h"
 #include "chrome/browser/ash/crosapi/download_controller_ash.h"
 #include "chrome/browser/ash/crosapi/drive_integration_service_ash.h"
 #include "chrome/browser/ash/crosapi/feedback_ash.h"
@@ -124,6 +125,7 @@
       clipboard_history_ash_(std::make_unique<ClipboardHistoryAsh>()),
       content_protection_ash_(std::make_unique<ContentProtectionAsh>()),
       device_attributes_ash_(std::make_unique<DeviceAttributesAsh>()),
+      device_settings_ash_(std::make_unique<DeviceSettingsAsh>()),
       download_controller_ash_(std::make_unique<DownloadControllerAsh>()),
       drive_integration_service_ash_(
           std::make_unique<DriveIntegrationServiceAsh>()),
@@ -421,6 +423,11 @@
   device_attributes_ash_->BindReceiver(std::move(receiver));
 }
 
+void CrosapiAsh::BindDeviceSettingsService(
+    mojo::PendingReceiver<mojom::DeviceSettingsService> receiver) {
+  device_settings_ash_->BindReceiver(std::move(receiver));
+}
+
 void CrosapiAsh::BindDownloadController(
     mojo::PendingReceiver<mojom::DownloadController> receiver) {
   download_controller_ash_->BindReceiver(std::move(receiver));
diff --git a/chrome/browser/ash/crosapi/crosapi_ash.h b/chrome/browser/ash/crosapi/crosapi_ash.h
index 22add6e..fa5e725 100644
--- a/chrome/browser/ash/crosapi/crosapi_ash.h
+++ b/chrome/browser/ash/crosapi/crosapi_ash.h
@@ -32,6 +32,7 @@
 class ClipboardHistoryAsh;
 class ContentProtectionAsh;
 class DeviceAttributesAsh;
+class DeviceSettingsAsh;
 class DownloadControllerAsh;
 class DriveIntegrationServiceAsh;
 class FeedbackAsh;
@@ -114,6 +115,8 @@
       mojo::PendingReceiver<mojom::ContentProtection> receiver) override;
   void BindDeviceAttributes(
       mojo::PendingReceiver<mojom::DeviceAttributes> receiver) override;
+  void BindDeviceSettingsService(
+      mojo::PendingReceiver<mojom::DeviceSettingsService> receiver) override;
   void BindHoldingSpaceService(
       mojo::PendingReceiver<mojom::HoldingSpaceService> receiver) override;
   void BindDownloadController(
@@ -260,6 +263,7 @@
   std::unique_ptr<ClipboardHistoryAsh> clipboard_history_ash_;
   std::unique_ptr<ContentProtectionAsh> content_protection_ash_;
   std::unique_ptr<DeviceAttributesAsh> device_attributes_ash_;
+  std::unique_ptr<DeviceSettingsAsh> device_settings_ash_;
   std::unique_ptr<DownloadControllerAsh> download_controller_ash_;
   std::unique_ptr<DriveIntegrationServiceAsh> drive_integration_service_ash_;
   std::unique_ptr<FeedbackAsh> feedback_ash_;
diff --git a/chrome/browser/ash/crosapi/device_settings_ash.cc b/chrome/browser/ash/crosapi/device_settings_ash.cc
new file mode 100644
index 0000000..bc8ad9b
--- /dev/null
+++ b/chrome/browser/ash/crosapi/device_settings_ash.cc
@@ -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.
+
+#include "chrome/browser/ash/crosapi/device_settings_ash.h"
+
+#include <utility>
+
+#include "chrome/browser/ash/crosapi/browser_util.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace crosapi {
+
+DeviceSettingsAsh::DeviceSettingsAsh() {
+  if (ash::DeviceSettingsService::IsInitialized())
+    ash::DeviceSettingsService::Get()->AddObserver(this);
+}
+
+DeviceSettingsAsh::~DeviceSettingsAsh() {
+  if (ash::DeviceSettingsService::IsInitialized())
+    ash::DeviceSettingsService::Get()->RemoveObserver(this);
+}
+
+void DeviceSettingsAsh::BindReceiver(
+    mojo::PendingReceiver<mojom::DeviceSettingsService> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
+void DeviceSettingsAsh::DeviceSettingsUpdated() {
+  for (auto& observer : observers_)
+    observer->UpdateDeviceSettings(browser_util::GetDeviceSettings());
+}
+
+void DeviceSettingsAsh::AddDeviceSettingsObserver(
+    mojo::PendingRemote<mojom::DeviceSettingsObserver> observer) {
+  mojo::Remote<mojom::DeviceSettingsObserver> remote(std::move(observer));
+  observers_.Add(std::move(remote));
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/device_settings_ash.h b/chrome/browser/ash/crosapi/device_settings_ash.h
new file mode 100644
index 0000000..bf4bff0
--- /dev/null
+++ b/chrome/browser/ash/crosapi/device_settings_ash.h
@@ -0,0 +1,46 @@
+// 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_ASH_CROSAPI_DEVICE_SETTINGS_ASH_H_
+#define CHROME_BROWSER_ASH_CROSAPI_DEVICE_SETTINGS_ASH_H_
+
+#include "chrome/browser/ash/settings/device_settings_service.h"
+#include "chromeos/crosapi/mojom/device_settings_service.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+
+namespace crosapi {
+
+// Implements the crosapi device settings interface. Lives in ash-chrome.
+// Allows lacros-chrome to access device settings that live in ash.
+class DeviceSettingsAsh : public mojom::DeviceSettingsService,
+                          public ash::DeviceSettingsService::Observer {
+ public:
+  DeviceSettingsAsh();
+  DeviceSettingsAsh(const DeviceSettingsAsh&) = delete;
+  DeviceSettingsAsh& operator=(const DeviceSettingsAsh&) = delete;
+  ~DeviceSettingsAsh() override;
+
+  void BindReceiver(
+      mojo::PendingReceiver<mojom::DeviceSettingsService> receiver);
+
+  // ash::DeviceSettingsService::Observer
+  void DeviceSettingsUpdated() override;
+
+  // crosapi::mojom::DeviceSettingsService:
+  void AddDeviceSettingsObserver(
+      mojo::PendingRemote<mojom::DeviceSettingsObserver> observer) override;
+
+ private:
+  mojo::ReceiverSet<mojom::DeviceSettingsService> receivers_;
+
+  // Support any number of device settings observers.
+  mojo::RemoteSet<mojom::DeviceSettingsObserver> observers_;
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_ASH_CROSAPI_DEVICE_SETTINGS_ASH_H_
diff --git a/chrome/browser/ash/login/enrollment/enrollment_local_policy_server_browsertest.cc b/chrome/browser/ash/login/enrollment/enrollment_local_policy_server_browsertest.cc
index ecfc28b..c11ff0d 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_local_policy_server_browsertest.cc
+++ b/chrome/browser/ash/login/enrollment/enrollment_local_policy_server_browsertest.cc
@@ -9,7 +9,6 @@
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/test/gtest_util.h"
-#include "base/test/metrics/histogram_tester.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "chrome/browser/ash/app_mode/kiosk_app_manager.h"
@@ -1022,37 +1021,31 @@
 INSTANTIATE_TEST_SUITE_P(All, OobeGuestButtonPolicy, ::testing::Bool());
 
 IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase, SwitchToViews) {
-  base::HistogramTester histogram_tester;
   TriggerEnrollmentAndSignInSuccessfully();
   enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepSuccess);
   ConfirmAndWaitLoginScreen();
   EXPECT_TRUE(LoginScreenTestApi::IsOobeDialogVisible());
-  histogram_tester.ExpectTotalCount("OOBE.WebUIToViewsSwitch.Duration", 1);
 }
 
 IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
                        SwitchToViewsLocalUsers) {
   AddPublicUser("test_user");
-  base::HistogramTester histogram_tester;
   TriggerEnrollmentAndSignInSuccessfully();
   enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepSuccess);
   ConfirmAndWaitLoginScreen();
   EXPECT_FALSE(LoginScreenTestApi::IsOobeDialogVisible());
   EXPECT_EQ(LoginScreenTestApi::GetUsersCount(), 1);
-  histogram_tester.ExpectTotalCount("OOBE.WebUIToViewsSwitch.Duration", 1);
 }
 
 IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase, SwitchToViewsLocales) {
   auto initial_label = LoginScreenTestApi::GetShutDownButtonLabel();
 
   SetLoginScreenLocale("ru-RU");
-  base::HistogramTester histogram_tester;
   TriggerEnrollmentAndSignInSuccessfully();
   enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepSuccess);
   ConfirmAndWaitLoginScreen();
   EXPECT_TRUE(LoginScreenTestApi::IsOobeDialogVisible());
   EXPECT_NE(LoginScreenTestApi::GetShutDownButtonLabel(), initial_label);
-  histogram_tester.ExpectTotalCount("OOBE.WebUIToViewsSwitch.Duration", 1);
 }
 
 class KioskEnrollmentTest : public EnrollmentLocalPolicyServerBase {
diff --git a/chrome/browser/ash/login/ui/login_display_host_webui.cc b/chrome/browser/ash/login/ui/login_display_host_webui.cc
index 3c086fc2..d3c01d4 100644
--- a/chrome/browser/ash/login/ui/login_display_host_webui.cc
+++ b/chrome/browser/ash/login/ui/login_display_host_webui.cc
@@ -26,9 +26,6 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/string_split.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/task/single_thread_task_runner_forward.h"
-#include "base/threading/thread_restrictions.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
 #include "base/trace_event/trace_event.h"
@@ -1286,38 +1283,10 @@
   TriggerShowLoginWizardFinish(locale, std::move(data));
 }
 
-class WebUIToViewsSwitchMetricsReporter
-    : public session_manager::SessionManagerObserver {
- public:
-  WebUIToViewsSwitchMetricsReporter() {
-    session_observation_.Observe(session_manager::SessionManager::Get());
-  }
-
-  // session_manager::SessionManagerObserver:
-  void OnLoginOrLockScreenVisible() override {
-    if (LoginDisplayHost::default_host()->GetOobeUI()) {
-      DCHECK_EQ(OobeUI::kGaiaSigninDisplay,
-                LoginDisplayHost::default_host()->GetOobeUI()->display_type());
-    }
-    base::UmaHistogramTimes("OOBE.WebUIToViewsSwitch.Duration",
-                            timer_.Elapsed());
-    base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
-  }
-
- private:
-  base::ScopedObservation<session_manager::SessionManager,
-                          session_manager::SessionManagerObserver>
-      session_observation_{this};
-  base::ElapsedTimer timer_;
-};
-
 void SwitchWebUItoMojo() {
   DCHECK_EQ(LoginDisplayHost::default_host()->GetOobeUI()->display_type(),
             OobeUI::kOobeDisplay);
 
-  // The object deletes itself.
-  new WebUIToViewsSwitchMetricsReporter();
-
   // This replaces WebUI host with the Mojo (views) host.
   ShowLoginWizard(OobeScreen::SCREEN_UNKNOWN);
 }
diff --git a/chrome/browser/ash/net/network_portal_detector_impl.cc b/chrome/browser/ash/net/network_portal_detector_impl.cc
index a677e67..f620929 100644
--- a/chrome/browser/ash/net/network_portal_detector_impl.cc
+++ b/chrome/browser/ash/net/network_portal_detector_impl.cc
@@ -362,7 +362,6 @@
   attempt_timeout_.Cancel();
 
   CaptivePortalStatus status = CAPTIVE_PORTAL_STATUS_UNKNOWN;
-  bool no_response_since_portal = false;
   switch (result) {
     case captive_portal::RESULT_NO_RESPONSE:
       if (response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) {
@@ -370,7 +369,6 @@
       } else if (network && network->IsShillCaptivePortal()) {
         // Take into account shill's detection results.
         status = CAPTIVE_PORTAL_STATUS_PORTAL;
-        no_response_since_portal = true;
       } else {
         status = CAPTIVE_PORTAL_STATUS_OFFLINE;
       }
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.cc b/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.cc
index e36d9102..408275d 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.cc
+++ b/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.cc
@@ -6,19 +6,35 @@
 
 #include <utility>
 
+#include "ash/public/cpp/notification_utils.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/syslog_logging.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/notifications/system_notification_helper.h"
 #include "chromeos/dbus/hermes/hermes_euicc_client.h"
 #include "chromeos/network/cellular_inhibitor.h"
 #include "chromeos/network/cellular_utils.h"
 #include "chromeos/network/network_handler.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/image/image.h"
+#include "ui/message_center/public/cpp/notification.h"
 
 namespace policy {
 
+namespace {
+
+constexpr char kNotifierESimPolicy[] = "policy.esim-policy";
+
+}  // namespace
+
+// static
+const char DeviceCommandResetEuiccJob::kResetEuiccNotificationId[] =
+    "cros_reset_euicc";
+
 DeviceCommandResetEuiccJob::DeviceCommandResetEuiccJob()
     : DeviceCommandResetEuiccJob(
           chromeos::NetworkHandler::Get()->cellular_inhibitor()) {}
@@ -50,8 +66,7 @@
     return;
   }
 
-  // TODO(crbug.com/1231305) Trigger a notification if an eSIM network is
-  // active.
+  ShowResetEuiccNotification();
   SYSLOG(INFO) << "Executing EUICC reset memory remote command";
   cellular_inhibitor_->InhibitCellularScanning(
       chromeos::CellularInhibitor::InhibitReason::kResettingEuiccMemory,
@@ -106,4 +121,24 @@
       base::BindOnce(std::move(callback), /*result_payload=*/nullptr));
 }
 
+void DeviceCommandResetEuiccJob::ShowResetEuiccNotification() {
+  std::unique_ptr<message_center::Notification> notification =
+      ash::CreateSystemNotification(
+          message_center::NOTIFICATION_TYPE_SIMPLE, kResetEuiccNotificationId,
+          l10n_util::GetStringUTF16(
+              IDS_ASH_NETWORK_RESET_EUICC_NOTIFICATION_TITLE),
+          l10n_util::GetStringUTF16(
+              IDS_ASH_NETWORK_RESET_EUICC_NOTIFICATION_MESSAGE),
+          /*display_source=*/std::u16string(), /*origin_url=*/GURL(),
+          message_center::NotifierId(
+              message_center::NotifierType::SYSTEM_COMPONENT,
+              kNotifierESimPolicy),
+          message_center::RichNotificationData(),
+          base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
+              base::DoNothingAs<void()>()),
+          /*small_image=*/gfx::VectorIcon(),
+          message_center::SystemNotificationWarningLevel::NORMAL);
+  SystemNotificationHelper::GetInstance()->Display(*notification);
+}
+
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.h b/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.h
index 70abb81..6ff373b1 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.h
+++ b/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.h
@@ -36,6 +36,8 @@
   static std::unique_ptr<DeviceCommandResetEuiccJob> CreateForTesting(
       chromeos::CellularInhibitor* cellular_inhbitor);
 
+  static const char kResetEuiccNotificationId[];
+
   // RemoteCommandJob:
   enterprise_management::RemoteCommand_Type GetType() const override;
 
@@ -58,6 +60,7 @@
       std::unique_ptr<chromeos::CellularInhibitor::InhibitLock> inhibit_lock,
       chromeos::HermesResponseStatus status);
   void RunResultCallback(CallbackWithResult callback);
+  void ShowResetEuiccNotification();
 
   chromeos::CellularInhibitor* const cellular_inhibitor_;
   base::WeakPtrFactory<DeviceCommandResetEuiccJob> weak_ptr_factory_{this};
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job_unittest.cc b/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job_unittest.cc
index fa8b54b5..63c726e 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job_unittest.cc
+++ b/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job_unittest.cc
@@ -4,7 +4,10 @@
 
 #include "chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.h"
 
+#include "chrome/browser/notifications/notification_display_service_tester.h"
+#include "chrome/browser/notifications/system_notification_helper.h"
 #include "chrome/test/base/chrome_ash_test_base.h"
+#include "chrome/test/base/testing_browser_process.h"
 #include "chromeos/dbus/hermes/hermes_clients.h"
 #include "chromeos/dbus/hermes/hermes_euicc_client.h"
 #include "chromeos/dbus/hermes/hermes_manager_client.h"
@@ -13,6 +16,7 @@
 #include "chromeos/network/network_state_test_helper.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/message_center/public/cpp/notification.h"
 
 namespace policy {
 
@@ -109,6 +113,10 @@
 
 TEST_F(DeviceCommandResetEuiccJobTest, ResetEuicc) {
   base::RunLoop run_loop;
+  TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
+      std::make_unique<SystemNotificationHelper>());
+  NotificationDisplayServiceTester tester(/*profile=*/nullptr);
+
   std::unique_ptr<RemoteCommandJob> job = CreateResetEuiccJob(test_start_time_);
   EXPECT_TRUE(
       job->Run(base::Time::Now(), base::TimeTicks::Now(),
@@ -117,10 +125,16 @@
                               RemoteCommandJob::Status::SUCCEEDED,
                               /*expected_profile_count=*/0)));
   run_loop.Run();
+  // Verify that the notification should be displayed.
+  EXPECT_TRUE(tester.GetNotification(
+      DeviceCommandResetEuiccJob::kResetEuiccNotificationId));
 }
 
 TEST_F(DeviceCommandResetEuiccJobTest, ResetEuiccInhibitFailure) {
   chromeos::ShillManagerClient::Get()->GetTestInterface()->ClearDevices();
+  TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
+      std::make_unique<SystemNotificationHelper>());
+  NotificationDisplayServiceTester tester(/*profile=*/nullptr);
   base::RunLoop run_loop;
   std::unique_ptr<RemoteCommandJob> job = CreateResetEuiccJob(test_start_time_);
   EXPECT_TRUE(
@@ -130,6 +144,9 @@
                               RemoteCommandJob::Status::FAILED,
                               /*expected_profile_count=*/2)));
   run_loop.Run();
+  // Verify that the notification should be displayed.
+  EXPECT_TRUE(tester.GetNotification(
+      DeviceCommandResetEuiccJob::kResetEuiccNotificationId));
 }
 
 }  // namespace policy
diff --git a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
index 3e9bf0b7..6255045 100644
--- a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
@@ -11,6 +11,7 @@
 #include <string>
 #include <vector>
 
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/cpp/wallpaper/online_wallpaper_params.h"
 #include "ash/public/cpp/wallpaper/wallpaper_controller.h"
 #include "ash/public/cpp/wallpaper/wallpaper_info.h"
@@ -238,8 +239,13 @@
   }
 }
 
+void ChromePersonalizationAppUiDelegate::OnWallpaperPreviewEnded() {
+  ChromePersonalizationAppUiDelegate::OnWallpaperChanged();
+}
+
 void ChromePersonalizationAppUiDelegate::SelectWallpaper(
     uint64_t image_asset_id,
+    bool preview_mode,
     SelectWallpaperCallback callback) {
   const auto& it = image_asset_id_map_.find(image_asset_id);
 
@@ -259,8 +265,8 @@
       ash::OnlineWallpaperParams(
           GetAccountId(), absl::make_optional(image_asset_id),
           GURL(it->second.image_url.spec()), it->second.collection_id,
-          ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-          /*preview_mode=*/false, /*from_user=*/true,
+          ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED, preview_mode,
+          /*from_user=*/true,
           /*daily_refresh_enabled=*/false),
       base::BindOnce(
           &ChromePersonalizationAppUiDelegate::OnOnlineWallpaperSelected,
@@ -269,6 +275,7 @@
 
 void ChromePersonalizationAppUiDelegate::SelectLocalImage(
     const base::FilePath& path,
+    bool preview_mode,
     SelectLocalImageCallback callback) {
   if (local_images_.count(path) == 0) {
     mojo::ReportBadMessage("Invalid local image path selected");
@@ -280,8 +287,7 @@
 
   WallpaperController::Get()->SetCustomWallpaper(
       GetAccountId(), path,
-      ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-      /*preview_mode=*/false,
+      ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED, preview_mode,
       base::BindOnce(&ChromePersonalizationAppUiDelegate::OnLocalImageSelected,
                      backend_weak_ptr_factory_.GetWeakPtr()));
 }
@@ -317,6 +323,19 @@
       backend_weak_ptr_factory_.GetWeakPtr()));
 }
 
+void ChromePersonalizationAppUiDelegate::IsInTabletMode(
+    IsInTabletModeCallback callback) {
+  std::move(callback).Run(ash::TabletMode::IsInTabletMode());
+}
+
+void ChromePersonalizationAppUiDelegate::ConfirmPreviewWallpaper() {
+  WallpaperController::Get()->ConfirmPreviewWallpaper();
+}
+
+void ChromePersonalizationAppUiDelegate::CancelPreviewWallpaper() {
+  WallpaperController::Get()->CancelPreviewWallpaper();
+}
+
 void ChromePersonalizationAppUiDelegate::OnFetchCollections(
     bool success,
     const std::vector<backdrop::Collection>& collections) {
diff --git a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.h b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.h
index acf3a9d..cf11164 100644
--- a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.h
+++ b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.h
@@ -91,11 +91,16 @@
   // ash::WallpaperControllerObserver:
   void OnWallpaperChanged() override;
 
+  // ash::WallpaperControllerObserver:
+  void OnWallpaperPreviewEnded() override;
+
   // chromeos::personalization_app::mojom::WallpaperProvider:
   void SelectWallpaper(uint64_t image_asset_id,
+                       bool preview_mode,
                        SelectWallpaperCallback callback) override;
 
   void SelectLocalImage(const base::FilePath& path,
+                        bool preview_mode,
                         SelectLocalImageCallback callback) override;
 
   void SetCustomWallpaperLayout(ash::WallpaperLayout layout) override;
@@ -108,6 +113,12 @@
   void UpdateDailyRefreshWallpaper(
       UpdateDailyRefreshWallpaperCallback callback) override;
 
+  void IsInTabletMode(IsInTabletModeCallback callback) override;
+
+  void ConfirmPreviewWallpaper() override;
+
+  void CancelPreviewWallpaper() override;
+
  private:
   friend class ChromePersonalizationAppUiDelegateTest;
 
diff --git a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate_unittest.cc b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate_unittest.cc
index 2e80d7d..d2d8341 100644
--- a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate_unittest.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate_unittest.cc
@@ -210,7 +210,7 @@
 
   base::RunLoop loop;
   wallpaper_provider_remote()->get()->SelectWallpaper(
-      asset_id,
+      asset_id, /*preview_mode=*/false,
       base::BindLambdaForTesting([quit = loop.QuitClosure()](bool success) {
         EXPECT_TRUE(success);
         std::move(quit).Run();
@@ -229,6 +229,37 @@
       test_wallpaper_controller()->wallpaper_info().value());
 }
 
+TEST_F(ChromePersonalizationAppUiDelegateTest, PreviewWallpaper) {
+  test_wallpaper_controller()->ClearCounts();
+
+  const uint64_t asset_id = 1;
+
+  AddWallpaperImage(asset_id, /*image_info=*/{
+                        GURL("test_url"),
+                        "collection_id",
+                    });
+
+  base::RunLoop loop;
+  wallpaper_provider_remote()->get()->SelectWallpaper(
+      asset_id, /*preview_mode=*/true,
+      base::BindLambdaForTesting([quit = loop.QuitClosure()](bool success) {
+        EXPECT_TRUE(success);
+        std::move(quit).Run();
+      }));
+  wallpaper_provider_remote()->FlushForTesting();
+  loop.Run();
+
+  EXPECT_EQ(1, test_wallpaper_controller()->set_online_wallpaper_count());
+  EXPECT_EQ(
+      ash::WallpaperInfo(
+          {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
+           absl::make_optional(asset_id), GURL("test_url"), "collection_id",
+           ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
+           /*preview_mode=*/true, /*from_user=*/true,
+           /*daily_refresh_enabled=*/false}),
+      test_wallpaper_controller()->wallpaper_info().value());
+}
+
 TEST_F(ChromePersonalizationAppUiDelegateTest, ObserveWallpaperFiresWhenBound) {
   // This will create the data url referenced below in expectation.
   test_wallpaper_controller()->ShowWallpaperImage(
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper.cc b/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
index 590a4bd..a1a26d7e 100644
--- a/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
+++ b/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
@@ -10,7 +10,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "components/browsing_data/content/browsing_data_helper.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
-#include "components/services/storage/public/cpp/buckets/bucket_info.h"
+#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/dom_storage_context.h"
@@ -157,11 +157,11 @@
 }
 
 void SiteDataCountingHelper::GetQuotaBucketsCallback(
-    const std::set<storage::BucketInfo>& buckets,
+    const std::set<storage::BucketLocator>& buckets,
     blink::mojom::StorageType type) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   std::set<GURL> urls;
-  for (const storage::BucketInfo& bucket : buckets)
+  for (const storage::BucketLocator& bucket : buckets)
     urls.insert(bucket.storage_key.origin().GetURL());
   content::GetUIThreadTaskRunner({})->PostTask(
       FROM_HERE,
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper.h b/chrome/browser/browsing_data/counters/site_data_counting_helper.h
index 3ee94de3..e968a4b 100644
--- a/chrome/browser/browsing_data/counters/site_data_counting_helper.h
+++ b/chrome/browser/browsing_data/counters/site_data_counting_helper.h
@@ -25,7 +25,7 @@
 
 namespace storage {
 class SpecialStoragePolicy;
-struct BucketInfo;
+struct BucketLocator;
 }
 
 // Helper class that counts the number of unique origins, that are affected by
@@ -49,7 +49,7 @@
       const scoped_refptr<storage::SpecialStoragePolicy>&
           special_storage_policy,
       const std::vector<content::StorageUsageInfo>& infos);
-  void GetQuotaBucketsCallback(const std::set<storage::BucketInfo>& buckets,
+  void GetQuotaBucketsCallback(const std::set<storage::BucketLocator>& buckets,
                                blink::mojom::StorageType type);
   void SitesWithMediaLicensesCallback(
       const std::list<BrowsingDataMediaLicenseHelper::MediaLicenseInfo>&
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 98f9814f..ecc9ac2 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -130,6 +130,8 @@
 #include "chrome/browser/ui/webui/app_service_internals/app_service_internals_ui.h"
 #include "chrome/browser/ui/webui/downloads/downloads.mojom.h"
 #include "chrome/browser/ui/webui/downloads/downloads_ui.h"
+#include "chrome/browser/ui/webui/enterprise_casting/enterprise_casting.mojom.h"
+#include "chrome/browser/ui/webui/enterprise_casting/enterprise_casting_ui.h"
 #include "chrome/browser/ui/webui/realbox/realbox.mojom.h"
 #if !defined(OFFICIAL_BUILD)
 #include "chrome/browser/ui/webui/new_tab_page/foo/foo.mojom.h"  // nogncheck crbug.com/1125897
@@ -204,8 +206,6 @@
 #include "chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_ui.h"
 #include "chrome/browser/ui/webui/chromeos/emoji/emoji_picker.mojom.h"
 #include "chrome/browser/ui/webui/chromeos/emoji/emoji_ui.h"
-#include "chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting.mojom.h"
-#include "chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_ui.h"
 #include "chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_network_ui.h"
 #include "chrome/browser/ui/webui/chromeos/internet_config_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/internet_detail_dialog.h"
@@ -785,6 +785,10 @@
   RegisterWebUIControllerInterfaceBinder<
       ::mojom::app_service_internals::AppServiceInternalsPageHandler,
       AppServiceInternalsUI>(map);
+
+  RegisterWebUIControllerInterfaceBinder<
+      enterprise_casting::mojom::PageHandlerFactory,
+      EnterpriseCastingUI>(map);
 #endif  // defined(OS_ANDROID)
 
 #if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
@@ -949,10 +953,6 @@
       launcher_internals::mojom::PageHandlerFactory,
       chromeos::LauncherInternalsUI>(map);
 
-  RegisterWebUIControllerInterfaceBinder<
-      enterprise_casting::mojom::PageHandlerFactory,
-      chromeos::EnterpriseCastingUI>(map);
-
   if (ash::features::IsBluetoothRevampEnabled()) {
     RegisterWebUIControllerInterfaceBinder<
         chromeos::bluetooth_config::mojom::CrosBluetoothConfig,
diff --git a/chrome/browser/content_creation/reactions/internal/android/java/res/layout/reaction_layout.xml b/chrome/browser/content_creation/reactions/internal/android/java/res/layout/reaction_layout.xml
index efe7a772..e66f4864 100644
--- a/chrome/browser/content_creation/reactions/internal/android/java/res/layout/reaction_layout.xml
+++ b/chrome/browser/content_creation/reactions/internal/android/java/res/layout/reaction_layout.xml
@@ -15,7 +15,7 @@
         android:id="@+id/reaction_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:padding="24dp"
+        android:padding="@dimen/reaction_padding"
         android:background="@drawable/border_inset"/>
 
     <org.chromium.ui.widget.ChromeImageButton
diff --git a/chrome/browser/content_creation/reactions/internal/android/java/res/values/dimens.xml b/chrome/browser/content_creation/reactions/internal/android/java/res/values/dimens.xml
index b3493dc..667239d4 100644
--- a/chrome/browser/content_creation/reactions/internal/android/java/res/values/dimens.xml
+++ b/chrome/browser/content_creation/reactions/internal/android/java/res/values/dimens.xml
@@ -8,6 +8,7 @@
     <dimen name="button_size">24dp</dimen>
     <dimen name="border_inset">12dp</dimen>
     <dimen name="delete_icon_padding">3dp</dimen>
+    <dimen name="reaction_padding">24dp</dimen>
     <dimen name="rtl_auto_mirror_scale">1</dimen>
     <dimen name="toolbar_row_height">56dp</dimen>
     <dimen name="toolbar_total_height">112dp</dimen>
diff --git a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/ReactionLayout.java b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/ReactionLayout.java
index 198a05a..be386a2 100644
--- a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/ReactionLayout.java
+++ b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/ReactionLayout.java
@@ -8,6 +8,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
 
@@ -16,13 +17,19 @@
 import org.chromium.ui.widget.ChromeImageButton;
 
 class ReactionLayout extends RelativeLayout {
+    private final int mReactionPadding;
+
     private ChromeImageButton mCopyButton;
+    private ChromeImageButton mDeleteButton;
+    private ChromeImageButton mAdjustButton;
     private Drawable mDrawable;
     private ImageView mReaction;
     private SceneEditorDelegate mSceneEditorDelegate;
+    private boolean mIsActive;
 
     public ReactionLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mReactionPadding = context.getResources().getDimensionPixelSize(R.dimen.reaction_padding);
     }
 
     /**
@@ -33,9 +40,8 @@
     void init(Drawable drawable, SceneEditorDelegate sceneEditorDelegate) {
         mDrawable = drawable;
         mSceneEditorDelegate = sceneEditorDelegate;
-        if (mReaction != null) {
-            mReaction.setImageDrawable(mDrawable);
-        }
+        mIsActive = true;
+        setUpReactionView();
     }
 
     Drawable getReaction() {
@@ -46,10 +52,32 @@
     public void onFinishInflate() {
         super.onFinishInflate();
         mReaction = findViewById(R.id.reaction_view);
-        if (mDrawable != null) {
-            mReaction.setImageDrawable(mDrawable);
-        }
         setUpCopyButton();
+        mDeleteButton = findViewById(R.id.delete_button);
+        mAdjustButton = findViewById(R.id.adjust_button);
+    }
+
+    void setActive(boolean isActive) {
+        if (mIsActive == isActive) return;
+
+        mIsActive = isActive;
+        int visibility = mIsActive ? View.VISIBLE : View.GONE;
+        mCopyButton.setVisibility(visibility);
+        mDeleteButton.setVisibility(visibility);
+        mAdjustButton.setVisibility(visibility);
+        if (!mIsActive) {
+            mReaction.setBackground(null);
+        } else {
+            mReaction.setBackgroundResource(R.drawable.border_inset);
+            mReaction.setPadding(
+                    mReactionPadding, mReactionPadding, mReactionPadding, mReactionPadding);
+        }
+    }
+
+    private void setUpReactionView() {
+        mReaction.setImageDrawable(mDrawable);
+        mReaction.setOnClickListener(
+                view -> mSceneEditorDelegate.markActiveStatus(this, !mIsActive));
     }
 
     private void setUpCopyButton() {
diff --git a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/SceneCoordinator.java b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/SceneCoordinator.java
index 8d32bb3..73a00de 100644
--- a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/SceneCoordinator.java
+++ b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/SceneCoordinator.java
@@ -28,6 +28,7 @@
     private final Activity mActivity;
     private final Set<ReactionLayout> mReactionLayouts;
 
+    private ReactionLayout mActiveReaction;
     private RelativeLayout mSceneBackground;
 
     /**
@@ -42,6 +43,8 @@
 
     public void setSceneBackground(RelativeLayout sceneBackground) {
         mSceneBackground = sceneBackground;
+        mSceneBackground.setOnClickListener(
+                (view) -> { markActiveStatus(mActiveReaction, false); });
     }
 
     public void addInitialReaction() {
@@ -65,8 +68,14 @@
                 - res.getDimensionPixelSize(R.dimen.toolbar_total_height);
         lp.setMargins(leftPx, topPx, 0, 0);
 
-        mSceneBackground.addView(reactionLayout, lp);
+        addReaction(reactionLayout, lp);
+    }
+
+    private void addReaction(
+            ReactionLayout reactionLayout, RelativeLayout.LayoutParams layoutParams) {
+        mSceneBackground.addView(reactionLayout, layoutParams);
         mReactionLayouts.add(reactionLayout);
+        markActiveStatus(reactionLayout, true);
     }
 
     // SceneEditorCallback implementation.
@@ -89,10 +98,8 @@
         int offsetPx = ViewUtils.dpToPx(mActivity, REACTION_OFFSET_DP);
         newLayoutParams.leftMargin = oldLayoutParams.leftMargin + offsetPx;
         newLayoutParams.topMargin = oldLayoutParams.topMargin + offsetPx;
-        newReactionLayout.setLayoutParams(newLayoutParams);
 
-        mSceneBackground.addView(newReactionLayout);
-        mReactionLayouts.add(newReactionLayout);
+        addReaction(newReactionLayout, newLayoutParams);
     }
 
     @Override
@@ -103,6 +110,15 @@
 
     @Override
     public void markActiveStatus(ReactionLayout reactionLayout, boolean isActive) {
-        // no-op for now
+        if (isActive) {
+            if (mActiveReaction != null) {
+                mActiveReaction.setActive(false);
+            }
+            reactionLayout.setActive(true);
+            mActiveReaction = reactionLayout;
+        } else if (mActiveReaction != null) {
+            mActiveReaction.setActive(false);
+            mActiveReaction = null;
+        }
     }
 }
diff --git a/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc b/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc
index 02265a1..c8cd2f5 100644
--- a/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc
+++ b/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc
@@ -64,8 +64,8 @@
          host == chrome::kChromeUIWebApksHost ||
          host == chrome::kChromeUIWebRtcLogsHost ||
          host == content::kChromeUIAppCacheInternalsHost ||
+         host == content::kChromeUIAttributionInternalsHost ||
          host == content::kChromeUIBlobInternalsHost ||
-         host == content::kChromeUIConversionInternalsHost ||
          host == content::kChromeUIGpuHost ||
          host == content::kChromeUIHistogramHost ||
          host == content::kChromeUIIndexedDBInternalsHost ||
diff --git a/chrome/browser/devtools/devtools_window.cc b/chrome/browser/devtools/devtools_window.cc
index ff31db1..699f1ef 100644
--- a/chrome/browser/devtools/devtools_window.cc
+++ b/chrome/browser/devtools/devtools_window.cc
@@ -80,6 +80,11 @@
 #include "ui/events/keycodes/keyboard_code_conversion.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 
+// This should be after all other #includes.
+#if defined(_WINDOWS_)  // Detect whether windows.h was included.
+#include "base/win/windows_h_disallowed.h"
+#endif  // defined(_WINDOWS_)
+
 using base::DictionaryValue;
 using blink::WebInputEvent;
 using content::BrowserThread;
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/common/proto/device_trust_attestation_ca.proto b/chrome/browser/enterprise/connectors/device_trust/attestation/common/proto/device_trust_attestation_ca.proto
index 8521c48d..3e679df 100644
--- a/chrome/browser/enterprise/connectors/device_trust/attestation/common/proto/device_trust_attestation_ca.proto
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/common/proto/device_trust_attestation_ca.proto
@@ -124,9 +124,9 @@
   // OS version (e.g. macOS 10.15.7)
   optional string os_version = 8;
   // IMEI
-  optional string imei = 9;
+  repeated string imei = 9;
   // MEID
-  optional string meid = 10;
+  repeated string meid = 10;
   // Hash of the EKPub certificate of the TPM on the device, if available.
   optional string tpm_hash = 11;
   // Is the disk encrypted
diff --git a/chrome/browser/enterprise/reporting/browser_report_generator_android.cc b/chrome/browser/enterprise/reporting/browser_report_generator_android.cc
index 3d023e8..efea90a 100644
--- a/chrome/browser/enterprise/reporting/browser_report_generator_android.cc
+++ b/chrome/browser/enterprise/reporting/browser_report_generator_android.cc
@@ -36,7 +36,7 @@
 }
 
 std::vector<BrowserReportGenerator::ReportedProfileData>
-BrowserReportGeneratorAndroid::GetReportedProfiles(ReportType report_type) {
+BrowserReportGeneratorAndroid::GetReportedProfiles() {
   std::vector<BrowserReportGenerator::ReportedProfileData> reportedProfileData;
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   for (const auto* entry : profile_manager->GetProfileAttributesStorage()
@@ -69,9 +69,4 @@
   std::move(callback).Run(std::move(report));
 }
 
-void BrowserReportGeneratorAndroid::OnProfileInfoGenerated(
-    ReportType report_type) {
-  // Not used on Android because there is no throttling profiles.
-}
-
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/browser_report_generator_android.h b/chrome/browser/enterprise/reporting/browser_report_generator_android.h
index d609e618..bd653bc 100644
--- a/chrome/browser/enterprise/reporting/browser_report_generator_android.h
+++ b/chrome/browser/enterprise/reporting/browser_report_generator_android.h
@@ -33,15 +33,14 @@
   // BrowserReportGenerator::Delegate implementation
   std::string GetExecutablePath() override;
   version_info::Channel GetChannel() override;
-  std::vector<BrowserReportGenerator::ReportedProfileData> GetReportedProfiles(
-      ReportType report_type) override;
+  std::vector<BrowserReportGenerator::ReportedProfileData> GetReportedProfiles()
+      override;
   bool IsExtendedStableChannel() override;
   void GenerateBuildStateInfo(
       enterprise_management::BrowserReport* report) override;
   void GeneratePluginsIfNeeded(
       ReportCallback callback,
       std::unique_ptr<enterprise_management::BrowserReport> report) override;
-  void OnProfileInfoGenerated(ReportType report_type) override;
 };
 
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/browser_report_generator_desktop.cc b/chrome/browser/enterprise/reporting/browser_report_generator_desktop.cc
index eeb46395..0d1c467e 100644
--- a/chrome/browser/enterprise/reporting/browser_report_generator_desktop.cc
+++ b/chrome/browser/enterprise/reporting/browser_report_generator_desktop.cc
@@ -12,7 +12,6 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/enterprise/reporting/extension_request/extension_request_report_throttler.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -51,19 +50,9 @@
 }
 
 std::vector<BrowserReportGenerator::ReportedProfileData>
-BrowserReportGeneratorDesktop::GetReportedProfiles(ReportType report_type) {
+BrowserReportGeneratorDesktop::GetReportedProfiles() {
   std::vector<BrowserReportGenerator::ReportedProfileData> reportedProfileData;
 
-  bool is_extension_request_report =
-      (report_type == ReportType::kExtensionRequest);
-
-  auto* throttler = ExtensionRequestReportThrottler::Get();
-  if (is_extension_request_report && !throttler->IsEnabled())
-    return reportedProfileData;
-
-  base::flat_set<base::FilePath> extension_request_profile_paths =
-      throttler->GetProfiles();
-
   for (const auto* entry : g_browser_process->profile_manager()
                                ->GetProfileAttributesStorage()
                                .GetAllProfilesAttributes()) {
@@ -76,10 +65,6 @@
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
     base::FilePath profile_path = entry->GetPath();
-    if (is_extension_request_report &&
-        !extension_request_profile_paths.contains(profile_path)) {
-      continue;
-    }
 
     reportedProfileData.push_back(
         {profile_path.AsUTF8Unsafe(), base::UTF16ToUTF8(entry->GetName())});
@@ -116,14 +101,6 @@
 #endif
 }
 
-void BrowserReportGeneratorDesktop::OnProfileInfoGenerated(
-    ReportType report_type) {
-  auto* throttler = ExtensionRequestReportThrottler::Get();
-  if (throttler->IsEnabled() && (report_type == ReportType::kExtensionRequest ||
-                                 report_type == ReportType::kFull))
-    throttler->ResetProfiles();
-}
-
 void BrowserReportGeneratorDesktop::OnPluginsReady(
     ReportCallback callback,
     std::unique_ptr<em::BrowserReport> report,
diff --git a/chrome/browser/enterprise/reporting/browser_report_generator_desktop.h b/chrome/browser/enterprise/reporting/browser_report_generator_desktop.h
index fcb68e53..1f4449b 100644
--- a/chrome/browser/enterprise/reporting/browser_report_generator_desktop.h
+++ b/chrome/browser/enterprise/reporting/browser_report_generator_desktop.h
@@ -35,8 +35,8 @@
 
   std::string GetExecutablePath() override;
   version_info::Channel GetChannel() override;
-  std::vector<BrowserReportGenerator::ReportedProfileData> GetReportedProfiles(
-      ReportType report_type) override;
+  std::vector<BrowserReportGenerator::ReportedProfileData> GetReportedProfiles()
+      override;
   bool IsExtendedStableChannel() override;
   // Adds the auto-updated version to the given report instance.
   void GenerateBuildStateInfo(
@@ -44,7 +44,6 @@
   void GeneratePluginsIfNeeded(
       ReportCallback callback,
       std::unique_ptr<enterprise_management::BrowserReport> report) override;
-  void OnProfileInfoGenerated(ReportType report_type) override;
 
   void OnPluginsReady(
       ReportCallback callback,
diff --git a/chrome/browser/enterprise/reporting/browser_report_generator_unittest.cc b/chrome/browser/enterprise/reporting/browser_report_generator_unittest.cc
index 22738dd..5ace7d3 100644
--- a/chrome/browser/enterprise/reporting/browser_report_generator_unittest.cc
+++ b/chrome/browser/enterprise/reporting/browser_report_generator_unittest.cc
@@ -27,7 +27,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if !defined(OS_ANDROID)
-#include "chrome/browser/enterprise/reporting/extension_request/extension_request_report_throttler_test.h"
 #include "chrome/browser/upgrade_detector/build_state.h"
 #endif  // !defined(OS_ANDROID)
 
@@ -203,13 +202,6 @@
   }
 
 #if !defined(OS_ANDROID)
-  void InitializeExtensionRequest() {
-    throttler()->AddProfile(
-        profile_manager()->profiles_dir().AppendASCII(kProfileId));
-    throttler()->AddProfile(
-        profile_manager()->profiles_dir().AppendASCII("deleted_profile"));
-  }
-
   void InitializeUpdate() {
     auto* build_state = g_browser_process->GetBuildState();
     build_state->SetUpdate(BuildState::UpdateType::kNormalUpdate,
@@ -220,7 +212,6 @@
   void GenerateAndVerify() {
     base::RunLoop run_loop;
     generator_.Generate(
-        ReportType::kFull,
         base::BindLambdaForTesting(
             [&run_loop](std::unique_ptr<em::BrowserReport> report) {
               EXPECT_TRUE(report.get());
@@ -237,38 +228,6 @@
     run_loop.Run();
   }
 
-#if !defined(OS_ANDROID)
-  void GenerateExtensinRequestReportAndVerify(
-      const std::vector<std::string>& expected_request_profile_ids) {
-    base::RunLoop run_loop;
-    generator_.Generate(
-        ReportType::kExtensionRequest,
-        base::BindLambdaForTesting(
-            [&run_loop, &expected_request_profile_ids](
-                std::unique_ptr<em::BrowserReport> report) {
-              EXPECT_TRUE(report.get());
-              EXPECT_NE(std::string(), report->executable_path());
-              EXPECT_FALSE(report->has_browser_version());
-              EXPECT_FALSE(report->has_channel());
-              EXPECT_FALSE(report->has_installed_browser_version());
-              EXPECT_EQ(expected_request_profile_ids.size(),
-                        static_cast<size_t>(
-                            report->chrome_user_profile_infos_size()));
-              if (expected_request_profile_ids.size() > 0 &&
-                  report->chrome_user_profile_infos_size() > 0) {
-                EXPECT_EQ(expected_request_profile_ids[0],
-                          report->chrome_user_profile_infos(0).id());
-              }
-              EXPECT_EQ(0, report->plugins_size());
-              run_loop.Quit();
-              ExtensionRequestReportThrottler::Get()->Disable();
-            }));
-    run_loop.Run();
-  }
-
-  ExtensionRequestReportThrottler* throttler() { return throttler_.Get(); }
-#endif  // !defined(OS_ANDROID)
-
   TestingProfileManager* profile_manager() { return &profile_manager_; }
 
  private:
@@ -277,9 +236,6 @@
   TestingProfileManager profile_manager_;
   BrowserReportGenerator generator_;
 
-#if !defined(OS_ANDROID)
-  ScopedExtensionRequestReportThrottler throttler_;
-#endif  // !defined(OS_ANDROID)
 };
 
 TEST_F(BrowserReportGeneratorTest, GenerateBasicReport) {
@@ -314,30 +270,4 @@
 #endif  // !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH) &&
         // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
-#if !defined(OS_ANDROID)
-TEST_F(BrowserReportGeneratorTest, ExtensionRequestOnly) {
-  InitializeUpdate();
-  InitializeProfile();
-  InitializeIrregularProfiles();
-  InitializePlugin();
-  InitializeExtensionRequest();
-  GenerateExtensinRequestReportAndVerify({profile_manager()
-                                              ->profiles_dir()
-                                              .AppendASCII(kProfileId)
-                                              .AsUTF8Unsafe()});
-}
-
-// It's possible that the extension request report is delayed and by the time
-// report is generated, the extension request report throttler is disabled.
-TEST_F(BrowserReportGeneratorTest, ExtensionRequestOnlyWithoutThrottler) {
-  InitializeUpdate();
-  InitializeProfile();
-  InitializeIrregularProfiles();
-  InitializePlugin();
-  InitializeExtensionRequest();
-  throttler()->Disable();
-  GenerateExtensinRequestReportAndVerify({});
-}
-#endif  // !defined(OS_ANDROID)
-
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc b/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc
index 8cdd742..83edc60 100644
--- a/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc
+++ b/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc
@@ -119,7 +119,7 @@
       const base::FilePath& path,
       const std::string& name) {
     std::unique_ptr<em::ChromeUserProfileInfo> report =
-        generator_.MaybeGenerate(path, name, ReportType::kFull);
+        generator_.MaybeGenerate(path, name);
     return report;
   }
 
@@ -190,7 +190,7 @@
   profile_manager()->profile_attributes_storage()->AddProfile(
       std::move(params));
   std::unique_ptr<em::ChromeUserProfileInfo> response =
-      generator_.MaybeGenerate(profile_path, kIdleProfile, ReportType::kFull);
+      generator_.MaybeGenerate(profile_path, kIdleProfile);
   ASSERT_FALSE(response.get());
 }
 
@@ -319,80 +319,6 @@
               report2->extension_requests(id).id());
 }
 
-TEST_F(ProfileReportGeneratorTest, ExtensionRequestOnlyReport) {
-  profile()->GetTestingPrefService()->SetManagedPref(
-      prefs::kCloudExtensionRequestEnabled,
-      std::make_unique<base::Value>(true));
-  std::vector<std::string> ids = {kExtensionId};
-  SetExtensionToPendingList(ids);
-
-  IdentityTestEnvironmentProfileAdaptor identity_test_env_adaptor(profile());
-  auto expected_info =
-      identity_test_env_adaptor.identity_test_env()->SetPrimaryAccount(
-          "test@mail.com", signin::ConsentLevel::kSync);
-
-  auto report = generator_.MaybeGenerate(profile()->GetPath(),
-                                         profile()->GetProfileUserName(),
-                                         ReportType::kExtensionRequest);
-
-  // Extension request and profile id are included. Profile name and sign in
-  // users info are included on CrOS only.
-  EXPECT_TRUE(report);
-  EXPECT_EQ(profile()->GetPath().AsUTF8Unsafe(), report->id());
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  EXPECT_EQ(profile()->GetProfileUserName(), report->name());
-  EXPECT_TRUE(report->has_chrome_signed_in_user());
-#else
-  EXPECT_FALSE(report->has_name());
-  EXPECT_FALSE(report->has_chrome_signed_in_user());
-#endif
-  ASSERT_EQ(1, report->extension_requests_size());
-  EXPECT_EQ(kExtensionId, report->extension_requests(0).id());
-  EXPECT_EQ(kFakeTime, report->extension_requests(0).request_timestamp());
-
-  // Policies and extensions info should not be added.
-  EXPECT_EQ(0, report->chrome_policies_size());
-  EXPECT_EQ(0, report->extensions_size());
-  EXPECT_EQ(0, report->policy_fetched_timestamps_size());
-  EXPECT_TRUE(report->is_detail_available());
-}
-
-TEST_F(ProfileReportGeneratorTest, ExtensionRequestOnlyReportWithoutPolicy) {
-  profile()->GetTestingPrefService()->SetManagedPref(
-      prefs::kCloudExtensionRequestEnabled,
-      std::make_unique<base::Value>(false));
-  IdentityTestEnvironmentProfileAdaptor identity_test_env_adaptor(profile());
-  auto expected_info =
-      identity_test_env_adaptor.identity_test_env()->SetPrimaryAccount(
-          "test@mail.com", signin::ConsentLevel::kSync);
-
-  auto report = generator_.MaybeGenerate(profile()->GetPath(),
-                                         profile()->GetProfileUserName(),
-                                         ReportType::kExtensionRequest);
-  EXPECT_TRUE(report);
-  EXPECT_EQ(0, report->extension_requests_size());
-}
-
-TEST_F(ProfileReportGeneratorTest,
-       ExtensionRequestOnlyReportWithoutAnyRequest) {
-  profile()->GetTestingPrefService()->SetManagedPref(
-      prefs::kCloudExtensionRequestEnabled,
-      std::make_unique<base::Value>(true));
-  std::vector<std::string> ids;
-  SetExtensionToPendingList(ids);
-
-  IdentityTestEnvironmentProfileAdaptor identity_test_env_adaptor(profile());
-  auto expected_info =
-      identity_test_env_adaptor.identity_test_env()->SetPrimaryAccount(
-          "test@mail.com", signin::ConsentLevel::kSync);
-
-  auto report = generator_.MaybeGenerate(profile()->GetPath(),
-                                         profile()->GetProfileUserName(),
-                                         ReportType::kExtensionRequest);
-
-  EXPECT_TRUE(report);
-  EXPECT_EQ(0, report->extension_requests_size());
-}
 #endif  // !defined(OS_ANDROID)
 
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/report_generator_unittest.cc b/chrome/browser/enterprise/reporting/report_generator_unittest.cc
index 8ecdd60..343cb4d8bc 100644
--- a/chrome/browser/enterprise/reporting/report_generator_unittest.cc
+++ b/chrome/browser/enterprise/reporting/report_generator_unittest.cc
@@ -21,6 +21,7 @@
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/account_id/account_id.h"
 #include "components/enterprise/browser/reporting/report_request_definition.h"
+#include "components/enterprise/browser/reporting/report_type.h"
 #include "components/policy/core/common/cloud/cloud_policy_util.h"
 #include "content/public/common/webplugininfo.h"
 #include "content/public/test/browser_task_environment.h"
@@ -463,30 +464,6 @@
                       profile_names, browser_report);
 }
 
-TEST_F(ReportGeneratorTest, ExtensionRequestOnly) {
-  auto profile_names = CreateProfiles(/*number*/ 2, kActive);
-  CreatePlugin();
-
-  auto requests = GenerateRequests(ReportType::kExtensionRequest);
-  EXPECT_EQ(1u, requests.size());
-
-  auto* basic_request = requests[0].get();
-
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
-  EXPECT_FALSE(basic_request->has_computer_name());
-  EXPECT_FALSE(basic_request->has_os_user_name());
-  EXPECT_FALSE(basic_request->has_os_report());
-  EXPECT_FALSE(basic_request->has_serial_number());
-#else
-  EXPECT_EQ(0, basic_request->android_app_infos_size());
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
-  EXPECT_TRUE(basic_request->has_browser_report());
-
-  EXPECT_EQ(1, basic_request->partial_report_types_size());
-  EXPECT_EQ(em::PartialReportType::EXTENSION_REQUEST,
-            basic_request->partial_report_types(0));
-}
-
 #endif  // defined(OS_ANDROID)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc b/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc
index 5105faa9..8db2c34a 100644
--- a/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc
+++ b/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc
@@ -131,7 +131,6 @@
     base::RunLoop run_loop;
 
     browser_report_generator_.Generate(
-        ReportType::kFull,
         base::BindLambdaForTesting(
             [&run_loop, &request](std::unique_ptr<em::BrowserReport> report) {
               request->set_allocated_browser_report(report.release()),
@@ -146,7 +145,7 @@
       const ReportRequest& request) {
     histogram_tester_ = std::make_unique<base::HistogramTester>();
     std::queue<std::unique_ptr<ReportRequest>> requests =
-        report_request_queue_generator_.Generate(ReportType::kFull, request);
+        report_request_queue_generator_.Generate(request);
     std::vector<std::unique_ptr<ReportRequest>> result;
     while (!requests.empty()) {
       result.push_back(std::move(requests.front()));
diff --git a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
index e73d07a3..93a37c3 100644
--- a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
+++ b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
@@ -730,11 +730,9 @@
         current_ruleset_count + expected_extensions_with_rulesets_count_change);
 
     size_t expected_enabled_rulesets_count = has_dynamic_ruleset ? 1 : 0;
-    size_t expected_manifest_rules_count = 0;
     size_t expected_manifest_enabled_rules_count = 0;
     for (const TestRulesetInfo& info : rulesets) {
       size_t rules_count = info.rules_value.GetList().size();
-      expected_manifest_rules_count += rules_count;
 
       if (info.enabled) {
         expected_enabled_rulesets_count++;
diff --git a/chrome/browser/extensions/external_install_manager.cc b/chrome/browser/extensions/external_install_manager.cc
index a09c9c5..4e791878 100644
--- a/chrome/browser/extensions/external_install_manager.cc
+++ b/chrome/browser/extensions/external_install_manager.cc
@@ -10,9 +10,13 @@
 #include "base/containers/contains.h"
 #include "base/metrics/histogram_macros.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_management.h"
 #include "chrome/browser/extensions/external_install_error.h"
+#include "chrome/browser/profiles/profile.h"
 #include "components/version_info/version_info.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/feature_switch.h"
@@ -66,6 +70,9 @@
   DCHECK(browser_context_);
   extension_registry_observation_.Observe(
       ExtensionRegistry::Get(browser_context_));
+  Profile* profile = Profile::FromBrowserContext(browser_context_);
+  registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_REMOVED,
+                 content::Source<Profile>(profile));
   // Populate the set of unacknowledged external extensions now. We can't just
   // rely on IsUnacknowledgedExternalExtension() for cases like
   // OnExtensionLoaded(), since we need to examine the disable reasons, which
@@ -260,9 +267,6 @@
     content::BrowserContext* browser_context,
     const Extension* extension,
     extensions::UninstallReason reason) {
-  if (base::Contains(errors_, extension->id()))
-    RemoveExternalInstallError(extension->id());
-
   ExtensionManagement* settings =
       ExtensionManagementFactory::GetForBrowserContext(browser_context_);
   if (unacknowledged_ids_.erase(extension->id())) {
@@ -289,4 +293,19 @@
          !extension_prefs_->IsExternalExtensionAcknowledged(extension.id());
 }
 
+void ExternalInstallManager::Observe(
+    int type,
+    const content::NotificationSource& source,
+    const content::NotificationDetails& details) {
+  DCHECK_EQ(type, extensions::NOTIFICATION_EXTENSION_REMOVED);
+  // The error is invalidated if the extension has been loaded or removed.
+  // It's a shame we have to use the notification system (instead of the
+  // registry observer) for this, but the ExtensionUnloaded notification is
+  // not sent out if the extension is disabled (which it is here).
+  const std::string& extension_id =
+      content::Details<const Extension>(details).ptr()->id();
+  if (base::Contains(errors_, extension_id))
+    RemoveExternalInstallError(extension_id);
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/external_install_manager.h b/chrome/browser/extensions/external_install_manager.h
index e0b56ef..5d98b6a 100644
--- a/chrome/browser/extensions/external_install_manager.h
+++ b/chrome/browser/extensions/external_install_manager.h
@@ -10,12 +10,16 @@
 
 #include "base/macros.h"
 #include "base/scoped_observation.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension_id.h"
 
 namespace content {
 class BrowserContext;
+class NotificationDetails;
+class NotificationSource;
 }
 
 namespace extensions {
@@ -23,7 +27,8 @@
 class ExtensionPrefs;
 class ExternalInstallError;
 
-class ExternalInstallManager : public ExtensionRegistryObserver {
+class ExternalInstallManager : public ExtensionRegistryObserver,
+                               public content::NotificationObserver {
  public:
   ExternalInstallManager(content::BrowserContext* browser_context,
                          bool is_first_run);
@@ -80,6 +85,11 @@
                               const Extension* extension,
                               extensions::UninstallReason reason) override;
 
+  // content::NotificationObserver implementation.
+  void Observe(int type,
+               const content::NotificationSource& source,
+               const content::NotificationDetails& details) override;
+
   // Adds a global error informing the user that an external extension was
   // installed. If |is_new_profile| is true, then this error is from the first
   // time our profile checked for new extensions.
@@ -112,6 +122,8 @@
   // The error that is currently showing an alert dialog/bubble.
   ExternalInstallError* currently_visible_install_alert_;
 
+  content::NotificationRegistrar registrar_;
+
   base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
       extension_registry_observation_{this};
 };
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 3531adb1..a68d111 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1889,6 +1889,11 @@
     "expiry_milestone": 98
   },
   {
+    "name": "enable-experimental-accessibility-switch-access-multistep-automation",
+    "owners": [ "akihiroota@google.com", "mschillaci@google.com" ],
+    "expiry_milestone": 98
+  },
+  {
     "name": "enable-experimental-accessibility-switch-access-setup-guide",
     "owners": ["ansatasi@google.com", "josiahk@google.com", "//ui/accessibility/OWNERS"],
     "expiry_milestone": 97
@@ -2386,6 +2391,11 @@
     "expiry_milestone": 95
   },
   {
+    "name": "enable-phone-hub-call-notification",
+    "owners": [ "dhnishi", "andychou", "samchiu" ],
+    "expiry_milestone": 102
+  },
+  {
     "name": "enable-phone-hub-camera-roll",
     "owners": [ "mattwalliser", "jasonsun", "cros-cameraroll@google.com" ],
     "expiry_milestone": 97
@@ -3263,7 +3273,7 @@
   {
     "name": "global-media-controls-modern-ui",
     "owners": [ "steimel", "media-dev" ],
-    "expiry_milestone": 95
+    "expiry_milestone": 100
   },
   {
     "name": "global-media-controls-picture-in-picture",
@@ -4994,12 +5004,12 @@
   {
     "name" : "side-panel",
     "owners": [ "chrome-desktop-ui-sea@google.com", "pbos" ],
-    "expiry_milestone" : 96
+    "expiry_milestone" : 101
   },
   {
     "name" : "side-panel-drag-and-drop",
     "owners": [ "chrome-desktop-ui-sea@google.com", "johntlee" ],
-    "expiry_milestone" : 96
+    "expiry_milestone" : 101
   },
   {
     "name" : "side-search",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index c3aea1d..240a3fbd 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4657,6 +4657,13 @@
     "Enable a setup guide to walk through the steps of initially configuring "
     "Switch Access.";
 
+const char kExperimentalAccessibilitySwitchAccessMultistepAutomationName[] =
+    "Enable multistep automation for Switch Access.";
+const char
+    kExperimentalAccessibilitySwitchAccessMultistepAutomationDescription[] =
+        "Enable multistep automation for Switch Access, which is a project for "
+        "the 2021 accessibility sprint.";
+
 const char kMagnifierContinuousMouseFollowingModeSettingName[] =
     "Enable ability to choose continuous mouse following mode in Magnifier "
     "settings";
@@ -4936,6 +4943,11 @@
     "If enabled, windows may be moved instead of scaled when resizing split "
     "view in tablet mode.";
 
+const char kPhoneHubCallNotificationName[] =
+    "Incoming call notification in Phone Hub";
+const char kPhoneHubCallNotificationDescription[] =
+    "Enables the incoming/ongoing call feature in Phone Hub.";
+
 const char kPhoneHubCameraRollName[] = "Camera Roll in Phone Hub";
 const char kPhoneHubCameraRollDescription[] =
     "Enables the Camera Roll feature in Phone Hub, which allows users to "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index d0af920..283d6b2 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2668,6 +2668,11 @@
 extern const char kExperimentalAccessibilitySwitchAccessSetupGuideName[];
 extern const char kExperimentalAccessibilitySwitchAccessSetupGuideDescription[];
 
+extern const char
+    kExperimentalAccessibilitySwitchAccessMultistepAutomationName[];
+extern const char
+    kExperimentalAccessibilitySwitchAccessMultistepAutomationDescription[];
+
 extern const char kMagnifierContinuousMouseFollowingModeSettingName[];
 extern const char kMagnifierContinuousMouseFollowingModeSettingDescription[];
 
@@ -2842,6 +2847,9 @@
 extern const char kPerformantSplitViewResizing[];
 extern const char kPerformantSplitViewResizingDescription[];
 
+extern const char kPhoneHubCallNotificationName[];
+extern const char kPhoneHubCallNotificationDescription[];
+
 extern const char kPhoneHubCameraRollName[];
 extern const char kPhoneHubCameraRollDescription[];
 
diff --git a/chrome/browser/lacros/device_settings_lacros.cc b/chrome/browser/lacros/device_settings_lacros.cc
new file mode 100644
index 0000000..08753563
--- /dev/null
+++ b/chrome/browser/lacros/device_settings_lacros.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 "chrome/browser/lacros/device_settings_lacros.h"
+
+#include <utility>
+
+#include "chromeos/crosapi/mojom/device_settings_service.mojom.h"
+#include "chromeos/lacros/lacros_service.h"
+
+DeviceSettingsLacros::DeviceSettingsLacros() {
+  auto* lacros_service = chromeos::LacrosService::Get();
+  device_settings_ = lacros_service->init_params()->device_settings.Clone();
+
+  // DeviceSettingsService is not available yet at the time when this is
+  // constructed. So, we post it as a task to be executed later.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&DeviceSettingsLacros::Init,
+                                weak_ptr_factory_.GetWeakPtr()));
+}
+
+DeviceSettingsLacros::~DeviceSettingsLacros() = default;
+
+void DeviceSettingsLacros::Init() {
+  auto* lacros_service = chromeos::LacrosService::Get();
+  if (!lacros_service->IsAvailable<crosapi::mojom::DeviceSettingsService>()) {
+    LOG(ERROR) << "DeviceSettingsService not available.";
+    return;
+  }
+
+  lacros_service->GetRemote<crosapi::mojom::DeviceSettingsService>()
+      ->AddDeviceSettingsObserver(
+          receiver_.BindNewPipeAndPassRemoteWithVersion());
+}
+
+crosapi::mojom::DeviceSettings* DeviceSettingsLacros::GetDeviceSettings() {
+  return device_settings_.get();
+}
+
+void DeviceSettingsLacros::UpdateDeviceSettings(
+    crosapi::mojom::DeviceSettingsPtr device_settings) {
+  device_settings_ = std::move(device_settings);
+}
diff --git a/chrome/browser/lacros/device_settings_lacros.h b/chrome/browser/lacros/device_settings_lacros.h
new file mode 100644
index 0000000..cd09bb6
--- /dev/null
+++ b/chrome/browser/lacros/device_settings_lacros.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 CHROME_BROWSER_LACROS_DEVICE_SETTINGS_LACROS_H_
+#define CHROME_BROWSER_LACROS_DEVICE_SETTINGS_LACROS_H_
+
+#include "base/memory/weak_ptr.h"
+#include "chromeos/crosapi/mojom/device_settings_service.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+// The keeper of device settings needed for Lacros. Initializes with current
+// value at startup and receives the updates from ash when the settings are
+// changed. Lacros should use the device settings provided by this class when
+// needs to use any device settings.
+class DeviceSettingsLacros : public crosapi::mojom::DeviceSettingsObserver {
+ public:
+  DeviceSettingsLacros();
+  DeviceSettingsLacros(const DeviceSettingsLacros&) = delete;
+  DeviceSettingsLacros& operator=(const DeviceSettingsLacros&) = delete;
+  ~DeviceSettingsLacros() override;
+
+  crosapi::mojom::DeviceSettings* GetDeviceSettings();
+
+  // crosapi::mojom::DeviceSettingsObserver:
+  void UpdateDeviceSettings(
+      crosapi::mojom::DeviceSettingsPtr device_settings) override;
+
+ private:
+  void Init();
+
+  crosapi::mojom::DeviceSettingsPtr device_settings_;
+  mojo::Receiver<crosapi::mojom::DeviceSettingsObserver> receiver_{this};
+  base::WeakPtrFactory<DeviceSettingsLacros> weak_ptr_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_LACROS_DEVICE_SETTINGS_LACROS_H_
diff --git a/chrome/browser/page_info/chrome_about_this_site_service_client.cc b/chrome/browser/page_info/chrome_about_this_site_service_client.cc
index d78c39e..7475f5e 100644
--- a/chrome/browser/page_info/chrome_about_this_site_service_client.cc
+++ b/chrome/browser/page_info/chrome_about_this_site_service_client.cc
@@ -23,7 +23,6 @@
 ChromeAboutThisSiteServiceClient::CanApplyOptimization(
     const GURL& url,
     optimization_guide::OptimizationMetadata* optimization_metadata) {
-  // TODO(crbug.com/1250653): Call CanApplyOptimization method of
-  // `optimization_guide_decider_` after proto is added.
-  return optimization_guide::OptimizationGuideDecision::kUnknown;
+  return optimization_guide_decider_->CanApplyOptimization(
+      url, optimization_guide::proto::ABOUT_THIS_SITE, optimization_metadata);
 }
diff --git a/chrome/browser/password_manager/android/BUILD.gn b/chrome/browser/password_manager/android/BUILD.gn
index 960a4a9..88d2aa7a 100644
--- a/chrome/browser/password_manager/android/BUILD.gn
+++ b/chrome/browser/password_manager/android/BUILD.gn
@@ -93,11 +93,18 @@
     "junit/src/org/chromium/chrome/browser/password_manager/settings/PasswordReauthenticationFragmentTest.java",
     "junit/src/org/chromium/chrome/browser/password_manager/settings/ReauthenticationManagerTest.java",
   ]
+  if (enable_chrome_android_internal) {
+    sources += [ "junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java" ]
+  }
 
   deps = [
     ":java",
+    "//base:base_java",
+    "//base:base_java_test_support",
     "//base:base_junit_test_support",
     "//chrome/test/android:chrome_java_test_support",
+    "//components/sync/protocol:protocol_java",
+    "//third_party/android_deps:protobuf_lite_runtime_java",
     "//third_party/androidx:androidx_fragment_fragment_java",
     "//third_party/junit",
   ]
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java
new file mode 100644
index 0000000..d05d860e
--- /dev/null
+++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java
@@ -0,0 +1,97 @@
+// 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.password_manager;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.Callback;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.components.sync.protocol.ListPasswordsResult;
+import org.chromium.components.sync.protocol.PasswordSpecificsData;
+import org.chromium.components.sync.protocol.PasswordWithLocalData;
+
+/**
+ * Tests that bridge calls as invoked by the password store reach the backend and return correctly.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+@Batch(Batch.PER_CLASS)
+public class PasswordStoreAndroidBackendBridgeTest {
+    private static final PasswordSpecificsData.Builder sTestProfile =
+            PasswordSpecificsData.newBuilder()
+                    .setUsernameValue("Todd Tester")
+                    .setUsernameElement("name")
+                    .setPasswordElement("pwd")
+                    .setOrigin("https://www.google.com/")
+                    .setSignonRealm("https://accounts.google.com/signin")
+                    .setPasswordValue("S3cr3t");
+    private static final ListPasswordsResult.Builder sTestLogins =
+            ListPasswordsResult.newBuilder().addPasswordData(
+                    PasswordWithLocalData.newBuilder().setPasswordSpecificsData(sTestProfile));
+    private static final long sDummyNativePointer = 4;
+
+    @Rule
+    public JniMocker mJniMocker = new JniMocker();
+
+    @Mock
+    private PasswordStoreAndroidBackendBridgeImpl.Natives mBridgeJniMock;
+    @Mock
+    private PasswordStoreAndroidBackend mBackendMock;
+
+    private PasswordStoreAndroidBackendBridgeImpl mBackendBridge;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mJniMocker.mock(PasswordStoreAndroidBackendBridgeImplJni.TEST_HOOKS, mBridgeJniMock);
+        mBackendBridge =
+                new PasswordStoreAndroidBackendBridgeImpl(sDummyNativePointer, mBackendMock);
+    }
+
+    @Test
+    public void testGetAllLoginsCallsBridgeOnSuccess() {
+        final int kTestTaskId = 1337;
+
+        // Ensure the backend is called with a valid success callback.
+        mBackendBridge.getAllLogins(kTestTaskId);
+        ArgumentCaptor<Callback<byte[]>> successCallback = ArgumentCaptor.forClass(Callback.class);
+        verify(mBackendMock).getAllLogins(successCallback.capture(), any());
+        assertNotNull(successCallback.getValue());
+
+        byte[] kExpectedList = sTestLogins.build().toByteArray();
+        successCallback.getValue().onResult(kExpectedList);
+        verify(mBridgeJniMock)
+                .onCompleteWithLogins(sDummyNativePointer, kTestTaskId, kExpectedList);
+    }
+
+    @Test
+    public void testGetAllLoginsCallsBridgeOnFailure() {
+        final int kTestTaskId = 42069;
+
+        // Ensure the backend is called with a valid failure callback.
+        mBackendBridge.getAllLogins(kTestTaskId);
+        ArgumentCaptor<Callback<Exception>> failureCallback =
+                ArgumentCaptor.forClass(Callback.class);
+        verify(mBackendMock).getAllLogins(any(), failureCallback.capture());
+        assertNotNull(failureCallback.getValue());
+
+        Exception kExpectedException = new Exception("Sample failure");
+        failureCallback.getValue().onResult(kExpectedException);
+        verify(mBridgeJniMock).onError(sDummyNativePointer, kTestTaskId);
+    }
+}
diff --git a/chrome/browser/performance_monitor/resource_coalition_mac_unittest.mm b/chrome/browser/performance_monitor/resource_coalition_mac_unittest.mm
index f01f721..d3dfbe2 100644
--- a/chrome/browser/performance_monitor/resource_coalition_mac_unittest.mm
+++ b/chrome/browser/performance_monitor/resource_coalition_mac_unittest.mm
@@ -9,6 +9,7 @@
 #include <limits>
 #include <memory>
 
+#include "base/compiler_specific.h"
 #include "base/containers/contains.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
@@ -84,12 +85,13 @@
 
   base::TimeTicks begin = base::TimeTicks::Now();
   constexpr base::TimeDelta busy_time = base::Seconds(1);
-  double number = 1;
+  volatile double number = 1;
   while (base::TimeTicks::Now() < (begin + busy_time)) {
     for (int i = 0; i < 10000; ++i) {
       number *= base::RandDouble() / std::numeric_limits<double>::max() * 2;
     }
   }
+  ALLOW_UNUSED_LOCAL(number);
 
   auto sample = coalition.GetDataRate();
   EXPECT_TRUE(sample.has_value());
diff --git a/chrome/browser/policy/chrome_browser_policy_connector.cc b/chrome/browser/policy/chrome_browser_policy_connector.cc
index 78c8677..a76118a 100644
--- a/chrome/browser/policy/chrome_browser_policy_connector.cc
+++ b/chrome/browser/policy/chrome_browser_policy_connector.cc
@@ -71,6 +71,7 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/lacros/device_settings_lacros.h"
 #include "components/policy/core/common/policy_loader_lacros.h"
 #endif
 
@@ -125,6 +126,11 @@
 bool ChromeBrowserPolicyConnector::IsMainUserManaged() const {
   return PolicyLoaderLacros::IsMainUserManaged();
 }
+
+crosapi::mojom::DeviceSettings*
+ChromeBrowserPolicyConnector::GetDeviceSettings() const {
+  return device_settings_->GetDeviceSettings();
+}
 #endif
 
 bool ChromeBrowserPolicyConnector::IsDeviceEnterpriseManaged() const {
@@ -226,6 +232,10 @@
   MaybeCreateCloudPolicyManager(&providers);
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  device_settings_ = std::make_unique<DeviceSettingsLacros>();
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
   std::unique_ptr<CommandLinePolicyProvider> command_line_provider =
       CommandLinePolicyProvider::CreateIfAllowed(
           *base::CommandLine::ForCurrentProcess(), chrome::GetChannel());
diff --git a/chrome/browser/policy/chrome_browser_policy_connector.h b/chrome/browser/policy/chrome_browser_policy_connector.h
index f659492..22d3a922 100644
--- a/chrome/browser/policy/chrome_browser_policy_connector.h
+++ b/chrome/browser/policy/chrome_browser_policy_connector.h
@@ -23,6 +23,10 @@
 #include "components/policy/core/browser/android/policy_cache_updater_android.h"
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/lacros/device_settings_lacros.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
 class PrefService;
 
 namespace policy {
@@ -110,6 +114,9 @@
   // Checks if the main / primary user is managed or not.
   // TODO(crbug/1245077): Remove once Lacros handles all profiles the same way.
   bool IsMainUserManaged() const;
+
+  // The device settings used in Lacros.
+  crosapi::mojom::DeviceSettings* GetDeviceSettings() const;
 #endif
 
  protected:
@@ -167,6 +174,10 @@
   // Owned by base class.
   ConfigurationPolicyProvider* command_line_provider_ = nullptr;
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  std::unique_ptr<DeviceSettingsLacros> device_settings_ = nullptr;
+#endif
+
   // Holds a callback to |ChromeBrowserCloudManagementController::Init| so that
   // its execution can be deferred until an enrollment token is available.
   base::OnceClosure deferred_init_callback_;
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index d721794..4230f5a 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -993,6 +993,8 @@
   crosapi::browser_util::RegisterLocalStatePrefs(registry);
   chromeos::CupsPrintersManager::RegisterLocalStatePrefs(registry);
   ash::BrowserDataMigrator::RegisterLocalStatePrefs(registry);
+  chromeos::bluetooth_config::DeviceNameManagerImpl::RegisterLocalStatePrefs(
+      registry);
   ash::DemoModeDetector::RegisterPrefs(registry);
   ash::DemoModeResourcesRemover::RegisterLocalStatePrefs(registry);
   ash::DemoSession::RegisterLocalStatePrefs(registry);
@@ -1320,7 +1322,6 @@
   ash::ClientAppMetadataProviderService::RegisterProfilePrefs(registry);
   chromeos::CupsPrintersManager::RegisterProfilePrefs(registry);
   chromeos::device_sync::RegisterProfilePrefs(registry);
-  chromeos::bluetooth_config::DeviceNameManagerImpl::RegisterPrefs(registry);
   ash::FamilyUserChromeActivityMetrics::RegisterProfilePrefs(registry);
   ash::FamilyUserMetricsService::RegisterProfilePrefs(registry);
   ash::FamilyUserSessionMetrics::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/printing/print_browsertest.cc b/chrome/browser/printing/print_browsertest.cc
index a6cec98..b030a50 100644
--- a/chrome/browser/printing/print_browsertest.cc
+++ b/chrome/browser/printing/print_browsertest.cc
@@ -1394,7 +1394,7 @@
   const char kScript[] = R"(
     const button = document.getElementsByTagName('print-preview-app')[0]
                        .$['previewArea']
-                       .$$('iframe')
+                       .shadowRoot.querySelector('iframe')
                        .contentDocument.querySelector('pdf-viewer-pp')
                        .shadowRoot.querySelector('#zoom-toolbar')
                        .$['zoom-out-button'];
diff --git a/chrome/browser/privacy_review/android/BUILD.gn b/chrome/browser/privacy_review/android/BUILD.gn
index 9272f679..94eb86f 100644
--- a/chrome/browser/privacy_review/android/BUILD.gn
+++ b/chrome/browser/privacy_review/android/BUILD.gn
@@ -15,11 +15,15 @@
 
 android_resources("java_resources") {
   sources = [
+    "java/res/drawable/privacy_review_illustration.xml",
     "java/res/layout/privacy_review_dialog.xml",
+    "java/res/layout/privacy_review_welcome.xml",
     "java/res/menu/privacy_review_toolbar_menu.xml",
+    "java/res/values/dimens.xml",
   ]
   deps = [
     "//chrome/browser/ui/android/strings:ui_strings_grd",
     "//components/browser_ui/styles/android:java_resources",
+    "//components/browser_ui/widget/android:java_resources",
   ]
 }
diff --git a/chrome/browser/privacy_review/android/java/res/drawable/privacy_review_illustration.xml b/chrome/browser/privacy_review/android/java/res/drawable/privacy_review_illustration.xml
new file mode 100644
index 0000000..c1792d3
--- /dev/null
+++ b/chrome/browser/privacy_review/android/java/res/drawable/privacy_review_illustration.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:targetApi="21"
+    tools:ignore="VectorRaster"
+    android:width="258dp"
+    android:height="144dp"
+    android:viewportWidth="258"
+    android:viewportHeight="144">
+  <path
+      android:pathData="M0,141.822H257.759"
+      android:strokeWidth="3.00427"
+      android:fillColor="#00000000"
+      android:strokeColor="#E8EAED"/>
+  <path
+      android:pathData="M185.577,54.163L175.521,45.238C174.426,44.266 174.329,42.598 175.3,41.503C176.271,40.408 177.94,40.311 179.035,41.283L189.09,50.208C190.185,51.179 190.282,52.848 189.311,53.943C188.34,55.037 186.671,55.134 185.577,54.163Z"
+      android:fillColor="#CF8D66"/>
+  <path
+      android:pathData="M204.549,136.463C217.49,136.463 227.98,125.973 227.98,113.032C227.98,100.092 217.49,89.601 204.549,89.601C191.609,89.601 181.118,100.092 181.118,113.032C181.118,125.973 191.609,136.463 204.549,136.463Z"
+      android:strokeWidth="7.00996"
+      android:fillColor="#00000000"
+      android:strokeColor="#DADCE0"/>
+  <path
+      android:pathData="M204.549,113.032L191.739,57.659H183.714"
+      android:strokeLineJoin="round"
+      android:strokeWidth="5.00711"
+      android:fillColor="#00000000"
+      android:strokeColor="#FBBC04"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M130.477,19.908L150.236,9.959L128.014,2.746C127.405,3.673 126.902,4.688 126.531,5.791C124.809,11.018 126.549,16.545 130.477,19.908Z"
+      android:fillColor="#3C4043"/>
+  <path
+      android:pathData="M140.163,63.054L160.971,79.74L147.729,104.698"
+      android:strokeLineJoin="round"
+      android:strokeWidth="20.0285"
+      android:fillColor="#00000000"
+      android:strokeColor="#174EA6"/>
+  <path
+      android:pathData="M129.674,113.032H68.024"
+      android:strokeWidth="5.00711"
+      android:fillColor="#00000000"
+      android:strokeColor="#DADCE0"/>
+  <path
+      android:pathData="M86.652,77.206H29.779V114.824H86.652V77.206Z"
+      android:fillColor="#5BB974"/>
+  <path
+      android:pathData="M119.821,136.463C132.762,136.463 143.252,125.973 143.252,113.032C143.252,100.092 132.762,89.601 119.821,89.601C106.881,89.601 96.39,100.092 96.39,113.032C96.39,125.973 106.881,136.463 119.821,136.463Z"
+      android:strokeWidth="7.00996"
+      android:fillColor="#00000000"
+      android:strokeColor="#DADCE0"/>
+  <path
+      android:pathData="M58.216,136.463C66.987,136.463 74.098,129.352 74.098,120.581C74.098,111.809 66.987,104.698 58.216,104.698C49.444,104.698 42.333,111.809 42.333,120.581C42.333,129.352 49.444,136.463 58.216,136.463Z"
+      android:strokeWidth="7.00996"
+      android:fillColor="#00000000"
+      android:strokeColor="#DADCE0"/>
+  <path
+      android:pathData="M147.367,113.032H119.822L144.515,72.191H194.811"
+      android:strokeLineJoin="round"
+      android:strokeWidth="5.00711"
+      android:fillColor="#00000000"
+      android:strokeColor="#FBBC04"/>
+  <path
+      android:pathData="M196.48,78.124L156.787,113.032"
+      android:strokeLineJoin="round"
+      android:strokeWidth="5.00711"
+      android:fillColor="#00000000"
+      android:strokeColor="#FBBC04"/>
+  <path
+      android:pathData="M152.346,112.344L157.14,111.514C157.14,111.514 157.043,115.61 160.353,117.005C162.198,117.782 164.838,117.941 166.816,117.923C168.405,117.906 169.765,119.027 170.038,120.572L170.126,121.102L151.816,121.022L152.346,112.344Z"
+      android:fillColor="#DADCE0"/>
+  <path
+      android:pathData="M153.591,14.108C157.486,14.108 160.645,10.95 160.645,7.054C160.645,3.158 157.486,0 153.591,0C149.695,0 146.537,3.158 146.537,7.054C146.537,10.95 149.695,14.108 153.591,14.108Z"
+      android:fillColor="#CF8D66"/>
+  <path
+      android:pathData="M146.546,20.235L175.918,46.059"
+      android:strokeLineJoin="round"
+      android:strokeWidth="16.0228"
+      android:fillColor="#00000000"
+      android:strokeColor="#5BB974"/>
+  <path
+      android:pathData="M131.316,53.907L143.261,114.895H158.367L147.04,55.867L131.316,53.907Z"
+      android:fillColor="#1967D2"/>
+  <path
+      android:pathData="M155.807,0.362C152.108,-0.856 148.126,1.148 146.899,4.847C146.166,7.072 146.607,9.402 147.879,11.177L160.309,4.926C159.647,2.843 158.04,1.095 155.807,0.362Z"
+      android:fillColor="#3C4043"/>
+  <path
+      android:pathData="M150.571,15.891C149.838,16.757 118.877,46.721 118.877,46.721C118.877,46.721 123.9,53.766 134.212,57.174C145.292,60.838 155.392,60.838 155.392,60.838L150.571,15.891Z"
+      android:fillColor="#5BB974"/>
+  <path
+      android:pathData="M71.344,72.191H45.079C40.806,72.191 37.336,68.73 37.336,64.449V38.192C37.336,33.919 40.797,30.45 45.079,30.45H71.344C75.617,30.45 79.087,33.91 79.087,38.192V64.457C79.087,68.73 75.617,72.191 71.344,72.191Z"
+      android:fillColor="#D2E3FC"/>
+</vector>
diff --git a/chrome/browser/privacy_review/android/java/res/layout/privacy_review_dialog.xml b/chrome/browser/privacy_review/android/java/res/layout/privacy_review_dialog.xml
index 5433f090..1861ccf 100644
--- a/chrome/browser/privacy_review/android/java/res/layout/privacy_review_dialog.xml
+++ b/chrome/browser/privacy_review/android/java/res/layout/privacy_review_dialog.xml
@@ -12,13 +12,18 @@
     <com.google.android.material.appbar.AppBarLayout
         android:id="@+id/app_bar_layout"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:fitsSystemWindows="true"
-        app:liftOnScroll="true">
+        android:layout_height="wrap_content">
 
         <com.google.android.material.appbar.MaterialToolbar
             android:id="@+id/toolbar"
             android:layout_width="match_parent"
             android:layout_height="@dimen/toolbar_height_no_shadow"/>
     </com.google.android.material.appbar.AppBarLayout>
+
+    <FrameLayout
+        android:id="@+id/dialog_content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior">
+    </FrameLayout>
 </androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/chrome/browser/privacy_review/android/java/res/layout/privacy_review_welcome.xml b/chrome/browser/privacy_review/android/java/res/layout/privacy_review_welcome.xml
new file mode 100644
index 0000000..8e25a08
--- /dev/null
+++ b/chrome/browser/privacy_review/android/java/res/layout/privacy_review_welcome.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <ScrollView
+        android:id="@+id/scroll_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <LinearLayout
+            android:id="@+id/welcome_main_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:animateLayoutChanges="true"
+            android:gravity="center_horizontal"
+            android:orientation="vertical">
+
+            <ImageView
+                android:id="@+id/image"
+                android:layout_marginTop="32dp"
+                android:layout_marginBottom="32dp"
+                android:layout_height="@dimen/privacy_review_illustration_height"
+                android:layout_width="@dimen/privacy_review_illustration_width"
+                android:src="@drawable/privacy_review_illustration"
+                android:importantForAccessibility="no" />
+
+            <TextView
+                android:id="@+id/welcome_title"
+                android:layout_margin="16dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/privacy_review_welcome_title"
+                style="@style/TextAppearance.Headline.Primary" />
+
+            <TextView
+                android:id="@+id/welcome_description"
+                android:layout_margin="16dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/privacy_review_welcome_description"
+                style="@style/TextAppearance.TextLarge.Secondary" />
+
+        </LinearLayout>
+
+    </ScrollView>
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="bottom|center">
+
+        <org.chromium.ui.widget.ButtonCompat
+            android:id="@+id/start_button"
+            android:focusable="true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="16dp"
+            android:layout_marginLeft="16dp"
+            android:layout_marginRight="16dp"
+            android:text="@string/privacy_review_start_button"
+            style="@style/FilledButton.Flat" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/chrome/browser/privacy_review/android/java/res/values/dimens.xml b/chrome/browser/privacy_review/android/java/res/values/dimens.xml
new file mode 100644
index 0000000..34dd5ab
--- /dev/null
+++ b/chrome/browser/privacy_review/android/java/res/values/dimens.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<resources>
+  <dimen name="privacy_review_illustration_height">120dp</dimen>
+  <dimen name="privacy_review_illustration_width">215dp</dimen>
+</resources>
diff --git a/chrome/browser/privacy_review/android/java/src/org/chromium/chrome/browser/privacy_review/PrivacyReviewDialog.java b/chrome/browser/privacy_review/android/java/src/org/chromium/chrome/browser/privacy_review/PrivacyReviewDialog.java
index d0c7459..b741d37c 100644
--- a/chrome/browser/privacy_review/android/java/src/org/chromium/chrome/browser/privacy_review/PrivacyReviewDialog.java
+++ b/chrome/browser/privacy_review/android/java/src/org/chromium/chrome/browser/privacy_review/PrivacyReviewDialog.java
@@ -4,18 +4,19 @@
 
 package org.chromium.chrome.browser.privacy_review;
 
+import android.app.Dialog;
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.widget.FrameLayout;
 
-import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.widget.Toolbar;
 
 /**
  * UI for the Privacy Review dialog in Privacy and security settings.
  */
-public class PrivacyReviewDialog extends AlertDialog {
+public class PrivacyReviewDialog extends Dialog {
     public PrivacyReviewDialog(Context context) {
         super(context, R.style.ThemeOverlay_BrowserUI_Fullscreen);
         View view = LayoutInflater.from(context).inflate(R.layout.privacy_review_dialog, null);
@@ -24,7 +25,10 @@
         toolbar.inflateMenu(R.menu.privacy_review_toolbar_menu);
         toolbar.setOnMenuItemClickListener(this::onMenuItemClick);
 
-        setView(view);
+        FrameLayout content = view.findViewById(R.id.dialog_content);
+        LayoutInflater.from(context).inflate(R.layout.privacy_review_welcome, content);
+
+        setContentView(view);
     }
 
     private boolean onMenuItemClick(MenuItem menuItem) {
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 7c563a81b..35b58c18 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -601,9 +601,10 @@
     user_policy_provider_->Init(schema_registry_service_->registry());
     policy_provider = user_policy_provider_.get();
     user_cloud_policy_manager = nullptr;
-  } else
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+  } else {
+#else
   {
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
     user_cloud_policy_manager_ = CreateUserCloudPolicyManager(
         GetPath(), GetPolicySchemaRegistryService()->registry(),
         force_immediate_policy_load, io_task_runner_);
@@ -876,13 +877,17 @@
 
   // Destroy all OTR profiles and their profile services first.
   std::vector<Profile*> raw_otr_profiles;
+#if BUILDFLAG(ENABLE_EXTENSIONS)
   bool primary_otr_available = false;
+#endif
 
   // Get a list of existing OTR profiles since |off_the_record_profile_| might
   // be modified after the call to |DestroyProfileNow|.
   for (auto& otr_profile : otr_profiles_) {
     raw_otr_profiles.push_back(otr_profile.second.get());
+#if BUILDFLAG(ENABLE_EXTENSIONS)
     primary_otr_available |= (otr_profile.first == OTRProfileID::PrimaryID());
+#endif
   }
 
   for (Profile* otr_profile : raw_otr_profiles)
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 312b52e..4d890ed 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -30,6 +30,7 @@
       "commander:resources",
       "download_shelf:resources",
       "downloads:resources",
+      "enterprise_casting:resources",
       "federated_learning:resources",
       "feedback_webui:resources",
       "gaia_auth_host:resources",
@@ -59,7 +60,6 @@
       "chromeos/accessibility:build",
       "chromeos/audio:resources",
       "chromeos/emoji_picker:resources",
-      "chromeos/enterprise_casting:resources",
       "chromeos/launcher_internals:resources",
       "chromeos/login:conditional_resources",
       "chromeos/login:unconditional_resources",
diff --git a/chrome/browser/resources/chromeos/enterprise_casting/tsconfig_base.json b/chrome/browser/resources/chromeos/enterprise_casting/tsconfig_base.json
deleted file mode 100644
index 5502828..0000000
--- a/chrome/browser/resources/chromeos/enterprise_casting/tsconfig_base.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "extends": "../../../../../tools/typescript/tsconfig_base.json",
-  "compilerOptions": {
-    "allowJs": true
-  }
-}
diff --git a/chrome/browser/resources/chromeos/enterprise_casting/BUILD.gn b/chrome/browser/resources/enterprise_casting/BUILD.gn
similarity index 87%
rename from chrome/browser/resources/chromeos/enterprise_casting/BUILD.gn
rename to chrome/browser/resources/enterprise_casting/BUILD.gn
index 6454fc2..86e4be3 100644
--- a/chrome/browser/resources/chromeos/enterprise_casting/BUILD.gn
+++ b/chrome/browser/resources/enterprise_casting/BUILD.gn
@@ -7,7 +7,7 @@
 import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 
-assert(is_chromeos, "enterprise_casting is Chrome OS only.")
+assert(!is_android, "enterprise_casting is not for android.")
 
 resources_grd_file = "$target_gen_dir/resources.grd"
 
@@ -21,8 +21,10 @@
 }
 
 copy("copy_mojo") {
-  deps = [ "//chrome/browser/ui/webui/chromeos/enterprise_casting:mojo_bindings_webui_js" ]
-  mojom_folder = "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/chromeos/enterprise_casting/"
+  deps =
+      [ "//chrome/browser/ui/webui/enterprise_casting:mojo_bindings_webui_js" ]
+  mojom_folder =
+      "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/enterprise_casting/"
   sources = [ "$mojom_folder/enterprise_casting.mojom-webui.js" ]
   outputs = [ "$target_gen_dir/{{source_file_part}}" ]
 }
diff --git a/chrome/browser/resources/chromeos/enterprise_casting/OWNERS b/chrome/browser/resources/enterprise_casting/OWNERS
similarity index 100%
rename from chrome/browser/resources/chromeos/enterprise_casting/OWNERS
rename to chrome/browser/resources/enterprise_casting/OWNERS
diff --git a/chrome/browser/resources/chromeos/enterprise_casting/browser_proxy.ts b/chrome/browser/resources/enterprise_casting/browser_proxy.ts
similarity index 100%
rename from chrome/browser/resources/chromeos/enterprise_casting/browser_proxy.ts
rename to chrome/browser/resources/enterprise_casting/browser_proxy.ts
diff --git a/chrome/browser/resources/chromeos/enterprise_casting/enterprise_casting.html b/chrome/browser/resources/enterprise_casting/enterprise_casting.html
similarity index 100%
rename from chrome/browser/resources/chromeos/enterprise_casting/enterprise_casting.html
rename to chrome/browser/resources/enterprise_casting/enterprise_casting.html
diff --git a/chrome/browser/resources/chromeos/enterprise_casting/enterprise_casting.ts b/chrome/browser/resources/enterprise_casting/enterprise_casting.ts
similarity index 100%
rename from chrome/browser/resources/chromeos/enterprise_casting/enterprise_casting.ts
rename to chrome/browser/resources/enterprise_casting/enterprise_casting.ts
diff --git a/chrome/browser/resources/chromeos/enterprise_casting/index.html b/chrome/browser/resources/enterprise_casting/index.html
similarity index 100%
rename from chrome/browser/resources/chromeos/enterprise_casting/index.html
rename to chrome/browser/resources/enterprise_casting/index.html
diff --git a/chrome/browser/resources/enterprise_casting/tsconfig_base.json b/chrome/browser/resources/enterprise_casting/tsconfig_base.json
new file mode 100644
index 0000000..99a81eca
--- /dev/null
+++ b/chrome/browser/resources/enterprise_casting/tsconfig_base.json
@@ -0,0 +1,6 @@
+{
+  "extends": "../../../../tools/typescript/tsconfig_base.json",
+  "compilerOptions": {
+    "allowJs": true
+  }
+}
diff --git a/chrome/browser/resources/print_preview/data/document_info.ts b/chrome/browser/resources/print_preview/data/document_info.ts
index 754b83a..ece9937 100644
--- a/chrome/browser/resources/print_preview/data/document_info.ts
+++ b/chrome/browser/resources/print_preview/data/document_info.ts
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
-import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Coordinate2d} from './coordinate2d.js';
 import {CustomMarginsOrientation, Margins} from './margins.js';
@@ -34,9 +34,7 @@
   printableAreaHeight: number,
 };
 
-const PrintPreviewDocumentInfoElementBase =
-    mixinBehaviors([WebUIListenerBehavior], PolymerElement) as
-    {new (): PolymerElement & WebUIListenerBehavior};
+const PrintPreviewDocumentInfoElementBase = WebUIListenerMixin(PolymerElement);
 
 export class PrintPreviewDocumentInfoElement extends
     PrintPreviewDocumentInfoElementBase {
diff --git a/chrome/browser/resources/print_preview/data/user_manager.ts b/chrome/browser/resources/print_preview/data/user_manager.ts
index b9f461c..fd8e412d 100644
--- a/chrome/browser/resources/print_preview/data/user_manager.ts
+++ b/chrome/browser/resources/print_preview/data/user_manager.ts
@@ -4,8 +4,8 @@
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
-import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {CloudPrintInterface, CloudPrintInterfaceErrorEventDetail, CloudPrintInterfaceEventType} from '../cloud_print_interface.js';
 import {CloudPrintInterfaceImpl} from '../cloud_print_interface_impl.js';
@@ -18,11 +18,8 @@
   users?: string[],
 };
 
-const PrintPreviewUserManagerElementBase =
-    mixinBehaviors([WebUIListenerBehavior], PolymerElement) as
-    {new (): PolymerElement & WebUIListenerBehavior};
+const PrintPreviewUserManagerElementBase = WebUIListenerMixin(PolymerElement);
 
-/** @polymer */
 class PrintPreviewUserManagerElement extends
     PrintPreviewUserManagerElementBase {
   static get is() {
diff --git a/chrome/browser/resources/print_preview/ui/advanced_settings_dialog.ts b/chrome/browser/resources/print_preview/ui/advanced_settings_dialog.ts
index ed2b40fc..a673b41 100644
--- a/chrome/browser/resources/print_preview/ui/advanced_settings_dialog.ts
+++ b/chrome/browser/resources/print_preview/ui/advanced_settings_dialog.ts
@@ -12,15 +12,15 @@
 import '../strings.m.js';
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {removeHighlights} from 'chrome://resources/js/search_highlight_utils.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination} from '../data/destination.js';
 import {MetricsContext, PrintSettingsUiBucket} from '../metrics.js';
 
 import {PrintPreviewSearchBoxElement} from './print_preview_search_box.js';
-import {SettingsMixin, SettingsMixinInterface} from './settings_mixin.js';
+import {SettingsMixin} from './settings_mixin.js';
 
 export interface PrintPreviewAdvancedSettingsDialogElement {
   $: {
@@ -30,8 +30,7 @@
 }
 
 const PrintPreviewAdvancedSettingsDialogElementBase =
-    mixinBehaviors([I18nBehavior], SettingsMixin(PolymerElement)) as
-    {new (): PolymerElement & SettingsMixinInterface & I18nBehavior};
+    I18nMixin(SettingsMixin(PolymerElement));
 
 export class PrintPreviewAdvancedSettingsDialogElement extends
     PrintPreviewAdvancedSettingsDialogElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/app.ts b/chrome/browser/resources/print_preview/ui/app.ts
index d763dd3f..b778b47 100644
--- a/chrome/browser/resources/print_preview/ui/app.ts
+++ b/chrome/browser/resources/print_preview/ui/app.ts
@@ -14,8 +14,8 @@
 import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
 import {hasKeyModifiers} from 'chrome://resources/js/util.m.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {CloudPrintInterface, CloudPrintInterfaceErrorEventDetail, CloudPrintInterfaceEventType} from '../cloud_print_interface.js';
 import {CloudPrintInterfaceImpl} from '../cloud_print_interface_impl.js';
@@ -37,7 +37,7 @@
 
 import {DestinationState} from './destination_settings.js';
 import {PreviewAreaState, PrintPreviewPreviewAreaElement} from './preview_area.js';
-import {SettingsMixin, SettingsMixinInterface} from './settings_mixin.js';
+import {SettingsMixin} from './settings_mixin.js';
 import {PrintPreviewSidebarElement} from './sidebar.js';
 
 export interface PrintPreviewAppElement {
@@ -51,8 +51,7 @@
 }
 
 const PrintPreviewAppElementBase =
-    mixinBehaviors([WebUIListenerBehavior], SettingsMixin(PolymerElement)) as
-    {new (): PolymerElement & WebUIListenerBehavior & SettingsMixinInterface};
+    WebUIListenerMixin(SettingsMixin(PolymerElement));
 
 export class PrintPreviewAppElement extends PrintPreviewAppElementBase {
   static get is() {
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
index 7d33607..177d96d 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
@@ -28,7 +28,7 @@
 import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
 import {ListPropertyUpdateBehavior} from 'chrome://resources/js/list_property_update_behavior.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
+import {WebUIListenerMixin, WebUIListenerMixinInterface} from 'chrome://resources/js/web_ui_listener_mixin.js';
 import {beforeNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination, GooglePromotedDestinationId} from '../data/destination.js';
@@ -57,10 +57,9 @@
 
 const PrintPreviewDestinationDialogCrosElementBase =
     mixinBehaviors(
-        [ListPropertyUpdateBehavior, WebUIListenerBehavior], PolymerElement) as
-    {
-      new ():
-          PolymerElement & WebUIListenerBehavior & ListPropertyUpdateBehavior
+        [ListPropertyUpdateBehavior], WebUIListenerMixin(PolymerElement)) as {
+      new (): PolymerElement & WebUIListenerMixinInterface &
+      ListPropertyUpdateBehavior
     };
 
 export class PrintPreviewDestinationDialogCrosElement extends
diff --git a/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.ts b/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.ts
index efb5d34..9d5de91 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.ts
@@ -10,8 +10,8 @@
 
 import './print_preview_vars_css.js';
 
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination, DestinationOrigin} from '../data/destination.js';
 import {ERROR_STRING_KEY_MAP, getPrinterStatusIcon, PrinterStatusReason} from '../data/printer_status_cros.js';
@@ -24,8 +24,7 @@
 }
 
 const PrintPreviewDestinationDropdownCrosElementBase =
-    mixinBehaviors([I18nBehavior], PolymerElement) as
-    {new (): PolymerElement & I18nBehavior};
+    I18nMixin(PolymerElement);
 
 export class PrintPreviewDestinationDropdownCrosElement extends
     PrintPreviewDestinationDropdownCrosElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/destination_list_item.ts b/chrome/browser/resources/print_preview/ui/destination_list_item.ts
index 28887161..d23a752 100644
--- a/chrome/browser/resources/print_preview/ui/destination_list_item.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_list_item.ts
@@ -11,10 +11,10 @@
 import '../strings.m.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {removeHighlights} from 'chrome://resources/js/search_highlight_utils.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination, DestinationOrigin} from '../data/destination.js';
 // <if expr="chromeos or lacros">
@@ -31,9 +31,7 @@
 }
 // </if>
 
-const PrintPreviewDestinationListItemElementBase =
-    mixinBehaviors([I18nBehavior], PolymerElement) as
-    {new (): I18nBehavior & PolymerElement};
+const PrintPreviewDestinationListItemElementBase = I18nMixin(PolymerElement);
 
 export class PrintPreviewDestinationListItemElement extends
     PrintPreviewDestinationListItemElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/destination_select.ts b/chrome/browser/resources/print_preview/ui/destination_select.ts
index 9552aa9b..1743576 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_select.ts
@@ -19,19 +19,18 @@
 import './throbber_css.js';
 import '../strings.m.js';
 
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {IronMeta} from 'chrome://resources/polymer/v3_0/iron-meta/iron-meta.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination, DestinationOrigin, GooglePromotedDestinationId, PDF_DESTINATION_KEY, RecentDestination} from '../data/destination.js';
 import {getSelectDropdownBackground} from '../print_preview_utils.js';
 
-import {SelectMixin, SelectMixinInterface} from './select_mixin.js';
+import {SelectMixin} from './select_mixin.js';
 
 const PrintPreviewDestinationSelectElementBase =
-    mixinBehaviors([I18nBehavior], SelectMixin(PolymerElement)) as
-    {new (): PolymerElement & I18nBehavior & SelectMixinInterface};
+    I18nMixin(SelectMixin(PolymerElement));
 
 export class PrintPreviewDestinationSelectElement extends
     PrintPreviewDestinationSelectElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/destination_select_cros.ts b/chrome/browser/resources/print_preview/ui/destination_select_cros.ts
index b5c87df..699aead 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select_cros.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_select_cros.ts
@@ -15,17 +15,16 @@
 import '../strings.m.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {CloudOrigins, Destination, DestinationOrigin, GooglePromotedDestinationId, PDF_DESTINATION_KEY, RecentDestination, SAVE_TO_DRIVE_CROS_DESTINATION_KEY} from '../data/destination.js';
 import {ERROR_STRING_KEY_MAP, getPrinterStatusIcon, PrinterStatusReason} from '../data/printer_status_cros.js';
 
-import {SelectMixin, SelectMixinInterface} from './select_mixin.js';
+import {SelectMixin} from './select_mixin.js';
 
 const PrintPreviewDestinationSelectCrosElementBase =
-    mixinBehaviors([I18nBehavior], SelectMixin(PolymerElement)) as
-    {new (): I18nBehavior & SelectMixinInterface & PolymerElement};
+    I18nMixin(SelectMixin(PolymerElement));
 
 export class PrintPreviewDestinationSelectCrosElement extends
     PrintPreviewDestinationSelectCrosElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/destination_settings.ts b/chrome/browser/resources/print_preview/ui/destination_settings.ts
index 05b559f..bacd860 100644
--- a/chrome/browser/resources/print_preview/ui/destination_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_settings.ts
@@ -27,10 +27,10 @@
 import {CrLazyRenderElement} from 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.m.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
-import {beforeNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
+import {beforeNextRender, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {CloudPrintInterfaceImpl} from '../cloud_print_interface_impl.js';
 import {CloudOrigins, createDestinationKey, createRecentDestinationKey, Destination, DestinationOrigin, GooglePromotedDestinationId, makeRecentDestination, RecentDestination} from '../data/destination.js';
@@ -53,7 +53,7 @@
 // <if expr="chromeos or lacros">
 import {PrintPreviewDestinationSelectCrosElement} from './destination_select_cros.js';
 // </if>
-import {SettingsMixin, SettingsMixinInterface} from './settings_mixin.js';
+import {SettingsMixin} from './settings_mixin.js';
 
 export enum DestinationState {
   INIT = 0,
@@ -93,12 +93,7 @@
 }
 
 const PrintPreviewDestinationSettingsElementBase =
-    mixinBehaviors(
-        [I18nBehavior, WebUIListenerBehavior], SettingsMixin(PolymerElement)) as
-    {
-      new (): PolymerElement & I18nBehavior & WebUIListenerBehavior &
-      SettingsMixinInterface
-    };
+    I18nMixin(WebUIListenerMixin(SettingsMixin(PolymerElement)));
 
 export class PrintPreviewDestinationSettingsElement extends
     PrintPreviewDestinationSettingsElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/input_mixin.ts b/chrome/browser/resources/print_preview/ui/input_mixin.ts
index d9b9c6f..da904d6 100644
--- a/chrome/browser/resources/print_preview/ui/input_mixin.ts
+++ b/chrome/browser/resources/print_preview/ui/input_mixin.ts
@@ -111,7 +111,7 @@
       return InputMixin;
     });
 
-export interface InputMixinInterface {
+interface InputMixinInterface {
   /**
    * @return The cr-input or input element the behavior should use. Should be
    *     overridden by elements using this behavior.
diff --git a/chrome/browser/resources/print_preview/ui/margin_control.ts b/chrome/browser/resources/print_preview/ui/margin_control.ts
index 4c6ef5d..ed40d9ee 100644
--- a/chrome/browser/resources/print_preview/ui/margin_control.ts
+++ b/chrome/browser/resources/print_preview/ui/margin_control.ts
@@ -7,9 +7,9 @@
 import '../strings.m.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
+import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Coordinate2d} from '../data/coordinate2d.js';
 import {CustomMarginsOrientation} from '../data/margins.js';
@@ -17,7 +17,7 @@
 import {Size} from '../data/size.js';
 import {observerDepsDefined} from '../print_preview_utils.js';
 
-import {InputMixin, InputMixinInterface} from './input_mixin.js';
+import {InputMixin} from './input_mixin.js';
 
 /**
  * Radius of the margin control in pixels. Padding of control + 1 for border.
@@ -32,11 +32,7 @@
 }
 
 const PrintPreviewMarginControlElementBase =
-    mixinBehaviors(
-        [I18nBehavior, WebUIListenerBehavior], InputMixin(PolymerElement)) as {
-      new (): PolymerElement & I18nBehavior & WebUIListenerBehavior &
-      InputMixinInterface
-    };
+    I18nMixin(WebUIListenerMixin(InputMixin(PolymerElement)));
 
 export class PrintPreviewMarginControlElement extends
     PrintPreviewMarginControlElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/margin_control_container.ts b/chrome/browser/resources/print_preview/ui/margin_control_container.ts
index c8780ce..fc71a45 100644
--- a/chrome/browser/resources/print_preview/ui/margin_control_container.ts
+++ b/chrome/browser/resources/print_preview/ui/margin_control_container.ts
@@ -15,7 +15,7 @@
 import {State} from '../data/state.js';
 
 import {PrintPreviewMarginControlElement} from './margin_control.js';
-import {SettingsMixin, SettingsMixinInterface} from './settings_mixin.js';
+import {SettingsMixin} from './settings_mixin.js';
 
 export const MARGIN_KEY_MAP: Map<CustomMarginsOrientation, string> = new Map([
   [CustomMarginsOrientation.TOP, 'marginTop'],
diff --git a/chrome/browser/resources/print_preview/ui/more_settings.ts b/chrome/browser/resources/print_preview/ui/more_settings.ts
index 9ec3e9e..a2a2f43c 100644
--- a/chrome/browser/resources/print_preview/ui/more_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/more_settings.ts
@@ -9,7 +9,7 @@
 
 import {CrExpandButtonElement} from 'chrome://resources/cr_elements/cr_expand_button/cr_expand_button.m.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {MetricsContext, PrintSettingsUiBucket} from '../metrics.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/number_settings_section.ts b/chrome/browser/resources/print_preview/ui/number_settings_section.ts
index a7051c66..af4057e 100644
--- a/chrome/browser/resources/print_preview/ui/number_settings_section.ts
+++ b/chrome/browser/resources/print_preview/ui/number_settings_section.ts
@@ -8,10 +8,10 @@
 import './settings_section.js';
 
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {InputMixin, InputMixinInterface} from './input_mixin.js';
+import {InputMixin} from './input_mixin.js';
 
 export interface PrintPreviewNumberSettingsSectionElement {
   $: {
@@ -20,8 +20,7 @@
 }
 
 const PrintPreviewNumberSettingsSectionElementBase =
-    mixinBehaviors([WebUIListenerBehavior], InputMixin(PolymerElement)) as
-    {new (): PolymerElement & WebUIListenerBehavior & InputMixinInterface};
+    WebUIListenerMixin(InputMixin(PolymerElement));
 
 export class PrintPreviewNumberSettingsSectionElement extends
     PrintPreviewNumberSettingsSectionElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/pages_settings.ts b/chrome/browser/resources/print_preview/ui/pages_settings.ts
index ae6982b..5e82cbd 100644
--- a/chrome/browser/resources/print_preview/ui/pages_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/pages_settings.ts
@@ -12,14 +12,14 @@
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {areRangesEqual, Range} from '../print_preview_utils.js';
 
-import {InputMixin, InputMixinInterface} from './input_mixin.js';
-import {SelectMixin, SelectMixinInterface} from './select_mixin.js';
-import {SettingsMixin, SettingsMixinInterface} from './settings_mixin.js';
+import {InputMixin} from './input_mixin.js';
+import {SelectMixin} from './select_mixin.js';
+import {SettingsMixin} from './settings_mixin.js';
 
 enum PagesInputErrorState {
   NO_ERROR = 0,
@@ -56,12 +56,7 @@
 }
 
 const PrintPreviewPagesSettingsElementBase =
-    mixinBehaviors(
-        [WebUIListenerBehavior],
-        InputMixin(SettingsMixin(SelectMixin(PolymerElement)))) as {
-      new (): PolymerElement & WebUIListenerBehavior & InputMixinInterface &
-      SettingsMixinInterface & SelectMixinInterface
-    };
+    WebUIListenerMixin(InputMixin(SettingsMixin(SelectMixin(PolymerElement))));
 
 export class PrintPreviewPagesSettingsElement extends
     PrintPreviewPagesSettingsElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/pin_settings.ts b/chrome/browser/resources/print_preview/ui/pin_settings.ts
index 257e5efd..b1a83e70 100644
--- a/chrome/browser/resources/print_preview/ui/pin_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/pin_settings.ts
@@ -10,14 +10,14 @@
 
 import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
-import {I18nMixin, I18nMixinInterface} from 'chrome://resources/js/i18n_mixin.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
+import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {State} from '../data/state.js';
 
-import {InputMixin, InputMixinInterface} from './input_mixin.js';
-import {SettingsMixin, SettingsMixinInterface} from './settings_mixin.js';
+import {InputMixin} from './input_mixin.js';
+import {SettingsMixin} from './settings_mixin.js';
 
 interface PrintPreviewPinSettingsElement {
   $: {
@@ -27,12 +27,7 @@
 }
 
 const PrintPreviewPinSettingsElementBase =
-    mixinBehaviors(
-        [WebUIListenerBehavior],
-        InputMixin(SettingsMixin(I18nMixin(PolymerElement)))) as {
-      new (): PolymerElement & I18nMixinInterface & WebUIListenerBehavior &
-      InputMixinInterface & SettingsMixinInterface
-    };
+    WebUIListenerMixin(InputMixin(SettingsMixin(I18nMixin(PolymerElement))));
 
 class PrintPreviewPinSettingsElement extends
     PrintPreviewPinSettingsElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/preview_area.ts b/chrome/browser/resources/print_preview/ui/preview_area.ts
index 64186dd..96de86f 100644
--- a/chrome/browser/resources/print_preview/ui/preview_area.ts
+++ b/chrome/browser/resources/print_preview/ui/preview_area.ts
@@ -9,12 +9,12 @@
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {isMac} from 'chrome://resources/js/cr.m.js';
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {hasKeyModifiers} from 'chrome://resources/js/util.m.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {DarkModeMixin, DarkModeMixinInterface} from '../dark_mode_mixin.js';
+import {DarkModeMixin} from '../dark_mode_mixin.js';
 import {Coordinate2d} from '../data/coordinate2d.js';
 import {Destination} from '../data/destination.js';
 import {getPrinterTypeForDestination} from '../data/destination_match.js';
@@ -31,7 +31,7 @@
 
 import {MARGIN_KEY_MAP, MarginObject, PrintPreviewMarginControlContainerElement} from './margin_control_container.js';
 import {PluginProxy, PluginProxyImpl} from './plugin_proxy.js';
-import {SettingsMixin, SettingsMixinInterface} from './settings_mixin.js';
+import {SettingsMixin} from './settings_mixin.js';
 
 type PreviewTicket = Ticket&{
   headerFooterEnabled: boolean;
@@ -54,12 +54,7 @@
 }
 
 const PrintPreviewPreviewAreaElementBase =
-    mixinBehaviors(
-        [WebUIListenerBehavior, I18nBehavior],
-        SettingsMixin(DarkModeMixin(PolymerElement))) as {
-      new (): PolymerElement & WebUIListenerBehavior & I18nBehavior &
-      SettingsMixinInterface & DarkModeMixinInterface
-    };
+    WebUIListenerMixin(I18nMixin(SettingsMixin(DarkModeMixin(PolymerElement))));
 
 export class PrintPreviewPreviewAreaElement extends
     PrintPreviewPreviewAreaElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/print_preview_search_box.ts b/chrome/browser/resources/print_preview/ui/print_preview_search_box.ts
index bd28c08..b4c574a0 100644
--- a/chrome/browser/resources/print_preview/ui/print_preview_search_box.ts
+++ b/chrome/browser/resources/print_preview/ui/print_preview_search_box.ts
@@ -11,7 +11,7 @@
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import {CrSearchFieldBehavior} from 'chrome://resources/cr_elements/cr_search_field/cr_search_field_behavior.js';
 import {stripDiacritics} from 'chrome://resources/js/search_highlight_utils.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
+import {WebUIListenerMixin, WebUIListenerMixinInterface} from 'chrome://resources/js/web_ui_listener_mixin.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 declare global {
@@ -30,8 +30,10 @@
 
 const PrintPreviewSearchBoxElementBase =
     mixinBehaviors(
-        [CrSearchFieldBehavior, WebUIListenerBehavior], PolymerElement) as
-    {new (): PolymerElement & WebUIListenerBehavior & CrSearchFieldBehavior};
+        [CrSearchFieldBehavior], WebUIListenerMixin(PolymerElement)) as {
+      new ():
+          PolymerElement & WebUIListenerMixinInterface & CrSearchFieldBehavior
+    };
 
 export class PrintPreviewSearchBoxElement extends
     PrintPreviewSearchBoxElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/provisional_destination_resolver.ts b/chrome/browser/resources/print_preview/ui/provisional_destination_resolver.ts
index 9200c3af..36efcea 100644
--- a/chrome/browser/resources/print_preview/ui/provisional_destination_resolver.ts
+++ b/chrome/browser/resources/print_preview/ui/provisional_destination_resolver.ts
@@ -13,9 +13,9 @@
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination} from '../data/destination.js';
 import {DestinationStore} from '../data/destination_store.js';
@@ -47,8 +47,7 @@
 }
 
 const PrintPreviewProvisionalDestinationResolverElementBase =
-    mixinBehaviors([I18nBehavior], PolymerElement) as
-    {new (): PolymerElement & I18nBehavior};
+    I18nMixin(PolymerElement);
 
 export class PrintPreviewProvisionalDestinationResolverElement extends
     PrintPreviewProvisionalDestinationResolverElementBase {
diff --git a/chrome/browser/resources/print_preview/ui/select_mixin.ts b/chrome/browser/resources/print_preview/ui/select_mixin.ts
index 0ada172..0c2a6df 100644
--- a/chrome/browser/resources/print_preview/ui/select_mixin.ts
+++ b/chrome/browser/resources/print_preview/ui/select_mixin.ts
@@ -58,7 +58,7 @@
       return SelectMixin;
     });
 
-export interface SelectMixinInterface {
+interface SelectMixinInterface {
   selectedValue: string;
 
   /**
diff --git a/chrome/browser/resources/print_preview/ui/sidebar.ts b/chrome/browser/resources/print_preview/ui/sidebar.ts
index 2056d6d..712b57f 100644
--- a/chrome/browser/resources/print_preview/ui/sidebar.ts
+++ b/chrome/browser/resources/print_preview/ui/sidebar.ts
@@ -33,7 +33,7 @@
 
 import {CrContainerShadowBehavior} from 'chrome://resources/cr_elements/cr_container_shadow_behavior.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
+import {WebUIListenerMixin, WebUIListenerMixinInterface} from 'chrome://resources/js/web_ui_listener_mixin.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {DarkModeMixin, DarkModeMixinInterface} from '../dark_mode_mixin.js';
@@ -59,11 +59,10 @@
     mixinBehaviors(
         [
           CrContainerShadowBehavior,
-          WebUIListenerBehavior,
         ],
-        SettingsMixin(DarkModeMixin(PolymerElement))) as {
-      new (): PolymerElement & WebUIListenerBehavior & DarkModeMixinInterface &
-      SettingsMixinInterface
+        WebUIListenerMixin(SettingsMixin(DarkModeMixin(PolymerElement)))) as {
+      new (): PolymerElement & WebUIListenerMixinInterface &
+      DarkModeMixinInterface & SettingsMixinInterface
     };
 
 export class PrintPreviewSidebarElement extends PrintPreviewSidebarElementBase {
diff --git a/chrome/browser/safe_browsing/chrome_password_protection_service.cc b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
index fa36955..5e67260 100644
--- a/chrome/browser/safe_browsing/chrome_password_protection_service.cc
+++ b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
@@ -1615,6 +1615,11 @@
       identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSync));
 }
 
+ChromeUserPopulation::UserPopulation
+ChromePasswordProtectionService::GetUserPopulationPref() const {
+  return ::safe_browsing::GetUserPopulationPref(profile_->GetPrefs());
+}
+
 ChromePasswordProtectionService::ChromePasswordProtectionService(
     Profile* profile,
     scoped_refptr<SafeBrowsingUIManager> ui_manager,
diff --git a/chrome/browser/safe_browsing/chrome_password_protection_service.h b/chrome/browser/safe_browsing/chrome_password_protection_service.h
index caf7a0b..07d39fd 100644
--- a/chrome/browser/safe_browsing/chrome_password_protection_service.h
+++ b/chrome/browser/safe_browsing/chrome_password_protection_service.h
@@ -26,6 +26,7 @@
 #include "components/safe_browsing/buildflags.h"
 #include "components/safe_browsing/content/browser/password_protection/password_protection_service.h"
 #include "components/safe_browsing/content/browser/triggers/trigger_manager.h"
+#include "components/safe_browsing/content/browser/user_population.h"
 #include "components/sessions/core/session_id.h"
 #include "components/sync/protocol/gaia_password_reuse.pb.h"
 #include "components/sync/protocol/user_event_specifics.pb.h"
@@ -292,6 +293,9 @@
   // Gets |account_info_| based on |profile_|.
   AccountInfo GetAccountInfo() const override;
 
+  // Gets the UserPopulation value for this profile.
+  ChromeUserPopulation::UserPopulation GetUserPopulationPref() const override;
+
   // KeyedService:
   // Called before the actual deletion of the object.
   void Shutdown() override;
diff --git a/chrome/browser/share/OWNERS b/chrome/browser/share/OWNERS
index 07431be0..8d2032ce 100644
--- a/chrome/browser/share/OWNERS
+++ b/chrome/browser/share/OWNERS
@@ -5,3 +5,5 @@
 sebsg@chromium.org
 skare@chromium.org
 sophey@chromium.org
+
+per-file *.gni=file://components/paint_preview/OWNERS
diff --git a/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc b/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
index 7289964..1208a15a 100644
--- a/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
+++ b/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
@@ -16,6 +16,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
+#include "build/build_config.h"
 #include "chrome/browser/ash/login/test/logged_in_user_mixin.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_key.h"
@@ -221,8 +222,14 @@
 }
 
 // Tests that prerendering fails in supervised user mode.
+#if defined(OS_CHROMEOS)
+// TODO(crbug.com/1259146): Flaky on ChromeOS.
+#define MAYBE_DisallowPrerendering DISABLED_DisallowPrerendering
+#else
+#define MAYBE_DisallowPrerendering DisallowPrerendering
+#endif
 IN_PROC_BROWSER_TEST_F(SupervisedUserNavigationThrottleTest,
-                       DisallowPrerendering) {
+                       MAYBE_DisallowPrerendering) {
   base::HistogramTester histogram_tester;
   const GURL initial_url = embedded_test_server()->GetURL("/simple.html");
   const GURL allowed_url =
diff --git a/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc b/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
index 65f3a90..67a1bba 100644
--- a/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
@@ -122,21 +122,6 @@
   EXPECT_TRUE(send_tab_to_self::IsUserSyncTypeActive(GetProfile(0)));
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientSendTabToSelfSyncTest, HasValidTargetDevice) {
-  ASSERT_TRUE(SetupSync());
-
-  static_cast<send_tab_to_self::SendTabToSelfBridge*>(
-      SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(0))
-          ->GetSendTabToSelfModel())
-      ->SetLocalDeviceNameForTest("device1");
-  static_cast<send_tab_to_self::SendTabToSelfBridge*>(
-      SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(1))
-          ->GetSendTabToSelfModel())
-      ->SetLocalDeviceNameForTest("device2");
-
-  EXPECT_TRUE(send_tab_to_self::HasValidTargetDevice(GetProfile(0)));
-}
-
 IN_PROC_BROWSER_TEST_F(TwoClientSendTabToSelfSyncTest,
                        SendTabToSelfReceivingEnabled) {
   ASSERT_TRUE(SetupSync());
@@ -191,7 +176,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(TwoClientSendTabToSelfSyncTest,
-                       SendTabToSelfTargetDeviceMap) {
+                       SendTabToSelfTargetDeviceInfoList) {
   ASSERT_TRUE(SetupSync());
 
   DeviceInfoSyncServiceFactory::GetForProfile(GetProfile(0))
@@ -209,6 +194,11 @@
   // Explicitly set the two profiles to have different client names to simulate
   // them being on different devices. Otherwise their device infos will get
   // deduped.
+  // TODO(crbug.com/1257573): This is rather misleading. The "device1"/"device2"
+  // strings below are never sent to the server, they just ensure the local
+  // device name is different from the other entry. The same string could even
+  // be used in both calls. The most robust test would be: update the device
+  // info name and wait for the right value of GetTargetDeviceInfoSortedList().
   static_cast<send_tab_to_self::SendTabToSelfBridge*>(
       SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(0))
           ->GetSendTabToSelfModel())
@@ -218,17 +208,27 @@
           ->GetSendTabToSelfModel())
       ->SetLocalDeviceNameForTest("device2");
 
-  std::vector<send_tab_to_self::TargetDeviceInfo> profile1_target_device_map =
+  // Emulate a device info update to force the target device list to refresh.
+  DeviceInfoSyncServiceFactory::GetForProfile(GetProfile(1))
+      ->GetDeviceInfoTracker()
+      ->ForcePulseForTest();
+  DeviceInfoSyncServiceFactory::GetForProfile(GetProfile(0))
+      ->GetDeviceInfoTracker()
+      ->ForcePulseForTest();
+
+  std::vector<send_tab_to_self::TargetDeviceInfo> profile1_target_devices =
       SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(0))
           ->GetSendTabToSelfModel()
           ->GetTargetDeviceInfoSortedList();
-  std::vector<send_tab_to_self::TargetDeviceInfo> profile2_target_device_map =
+  std::vector<send_tab_to_self::TargetDeviceInfo> profile2_target_devices =
       SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(1))
           ->GetSendTabToSelfModel()
           ->GetTargetDeviceInfoSortedList();
 
-  EXPECT_EQ(1u, profile1_target_device_map.size());
-  EXPECT_EQ(1u, profile2_target_device_map.size());
+  EXPECT_EQ(1u, profile1_target_devices.size());
+  EXPECT_EQ(1u, profile2_target_devices.size());
+  EXPECT_TRUE(send_tab_to_self::HasValidTargetDevice(GetProfile(0)));
+  EXPECT_TRUE(send_tab_to_self::HasValidTargetDevice(GetProfile(1)));
 }
 
 IN_PROC_BROWSER_TEST_F(TwoClientSendTabToSelfSyncTest,
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 64480ad..27c768e 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -873,6 +873,7 @@
       "//components/navigation_interception",
       "//components/optimization_guide/proto:optimization_guide_proto",
       "//components/page_info",
+      "//components/page_info:proto",
       "//components/page_info/android:android",
       "//components/query_tiles",
       "//components/resources:android_resources",
@@ -1050,6 +1051,7 @@
       "global_media_controls/cast_media_notification_producer.h",
       "global_media_controls/cast_media_session_controller.cc",
       "global_media_controls/cast_media_session_controller.h",
+      "global_media_controls/media_item_ui_device_selector_delegate.h",
       "global_media_controls/media_notification_device_monitor.cc",
       "global_media_controls/media_notification_device_monitor.h",
       "global_media_controls/media_notification_device_provider.h",
@@ -1423,6 +1425,10 @@
       "webui/downloads/downloads_list_tracker.h",
       "webui/downloads/downloads_ui.cc",
       "webui/downloads/downloads_ui.h",
+      "webui/enterprise_casting/enterprise_casting_handler.cc",
+      "webui/enterprise_casting/enterprise_casting_handler.h",
+      "webui/enterprise_casting/enterprise_casting_ui.cc",
+      "webui/enterprise_casting/enterprise_casting_ui.h",
       "webui/extensions/extensions_internals_source.cc",
       "webui/extensions/extensions_internals_source.h",
       "webui/extensions/extensions_ui.cc",
@@ -1658,6 +1664,7 @@
       "//chrome/browser/ui/commander:fuzzy_finder",
       "//chrome/browser/ui/webui/app_management:mojo_bindings",
       "//chrome/browser/ui/webui/app_service_internals:mojo_bindings",
+      "//chrome/browser/ui/webui/enterprise_casting:mojo_bindings",
       "//chrome/browser/ui/webui/internals/user_education:mojo_bindings",
       "//chrome/browser/ui/webui/tab_strip:mojo_bindings",
       "//chrome/browser/web_applications",
@@ -2406,10 +2413,6 @@
       "webui/chromeos/emoji/emoji_page_handler.h",
       "webui/chromeos/emoji/emoji_ui.cc",
       "webui/chromeos/emoji/emoji_ui.h",
-      "webui/chromeos/enterprise_casting/enterprise_casting_handler.cc",
-      "webui/chromeos/enterprise_casting/enterprise_casting_handler.h",
-      "webui/chromeos/enterprise_casting/enterprise_casting_ui.cc",
-      "webui/chromeos/enterprise_casting/enterprise_casting_ui.h",
       "webui/chromeos/image_source.cc",
       "webui/chromeos/image_source.h",
       "webui/chromeos/in_session_password_change/base_lock_dialog.cc",
@@ -2862,7 +2865,6 @@
       "//chrome/browser/ui/webui/chromeos/add_supervision:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/audio:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/crostini_upgrader:mojo_bindings",
-      "//chrome/browser/ui/webui/chromeos/enterprise_casting:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/launcher_internals:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/parent_access:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/vm:mojo_bindings",
@@ -4066,22 +4068,18 @@
       "views/fullscreen_control/fullscreen_control_host.h",
       "views/global_error_bubble_view.cc",
       "views/global_error_bubble_view.h",
-      "views/global_media_controls/global_media_controls_types.h",
       "views/global_media_controls/media_dialog_view.cc",
       "views/global_media_controls/media_dialog_view.h",
       "views/global_media_controls/media_dialog_view_observer.h",
-      "views/global_media_controls/media_notification_container_impl_view.cc",
-      "views/global_media_controls/media_notification_container_impl_view.h",
+      "views/global_media_controls/media_item_ui_device_selector_observer.h",
+      "views/global_media_controls/media_item_ui_device_selector_view.cc",
+      "views/global_media_controls/media_item_ui_device_selector_view.h",
+      "views/global_media_controls/media_item_ui_footer_view.cc",
+      "views/global_media_controls/media_item_ui_footer_view.h",
+      "views/global_media_controls/media_item_ui_legacy_cast_footer_view.cc",
+      "views/global_media_controls/media_item_ui_legacy_cast_footer_view.h",
       "views/global_media_controls/media_notification_device_entry_ui.cc",
       "views/global_media_controls/media_notification_device_entry_ui.h",
-      "views/global_media_controls/media_notification_device_selector_observer.h",
-      "views/global_media_controls/media_notification_device_selector_view.cc",
-      "views/global_media_controls/media_notification_device_selector_view.h",
-      "views/global_media_controls/media_notification_device_selector_view_delegate.h",
-      "views/global_media_controls/media_notification_footer_view.cc",
-      "views/global_media_controls/media_notification_footer_view.h",
-      "views/global_media_controls/media_notification_list_view.cc",
-      "views/global_media_controls/media_notification_list_view.h",
       "views/global_media_controls/media_toolbar_button_view.cc",
       "views/global_media_controls/media_toolbar_button_view.h",
       "views/hover_button.cc",
@@ -4668,6 +4666,7 @@
       "//components/live_caption",
       "//components/media_message_center",
       "//components/page_info",
+      "//components/page_info:proto",
       "//components/payments/content",
       "//components/payments/content:utils",
       "//components/payments/core",
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index a3c34cc..c42d3a9f 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -1019,6 +1019,15 @@
       <message name="IDS_PREFS_PRIVACY_REVIEW_SUMMARY" desc="The summary of the privacy menu item.">
         Review privacy and security settings
       </message>
+      <message name="IDS_PRIVACY_REVIEW_WELCOME_TITLE" desc="Title on the welcome page of the privacy review.">
+        The most important privacy and security controls in one place
+      </message>
+      <message name="IDS_PRIVACY_REVIEW_WELCOME_DESCRIPTION" desc="Description on the welcome page of the privacy review.">
+        Chrome provides reasonable defaults, but you can customize it to best fit your browsing habit
+      </message>
+      <message name="IDS_PRIVACY_REVIEW_START_BUTTON" desc="Text on the privacy review start button on the welcome screen.">
+        Let’s go
+      </message>
 
       <!-- Safety check -->
       <message name="IDS_PREFS_SAFETY_CHECK" desc="Title of the Safety check element in settings, allowing the user to check multiple areas of browser safety. [CHAR_LIMIT=32]">
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_REVIEW_START_BUTTON.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_REVIEW_START_BUTTON.png.sha1
new file mode 100644
index 0000000..dbfd6e6
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_REVIEW_START_BUTTON.png.sha1
@@ -0,0 +1 @@
+9f8fba0dbbddbe6c5fbe7f569961d2a092d888b1
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_REVIEW_WELCOME_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_REVIEW_WELCOME_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..dbfd6e6
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_REVIEW_WELCOME_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+9f8fba0dbbddbe6c5fbe7f569961d2a092d888b1
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_REVIEW_WELCOME_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_REVIEW_WELCOME_TITLE.png.sha1
new file mode 100644
index 0000000..dbfd6e6
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_REVIEW_WELCOME_TITLE.png.sha1
@@ -0,0 +1 @@
+9f8fba0dbbddbe6c5fbe7f569961d2a092d888b1
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/media_notification_provider_impl.cc b/chrome/browser/ui/ash/media_notification_provider_impl.cc
index e39635b..8f60f4e3 100644
--- a/chrome/browser/ui/ash/media_notification_provider_impl.cc
+++ b/chrome/browser/ui/ash/media_notification_provider_impl.cc
@@ -9,9 +9,9 @@
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ui/global_media_controls/media_notification_service.h"
 #include "chrome/browser/ui/global_media_controls/media_notification_service_factory.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_list_view.h"
 #include "components/global_media_controls/public/media_item_manager.h"
+#include "components/global_media_controls/public/views/media_item_ui_list_view.h"
+#include "components/global_media_controls/public/views/media_item_ui_view.h"
 #include "components/session_manager/core/session_manager.h"
 #include "ui/views/view.h"
 
@@ -57,13 +57,15 @@
     int separator_thickness) {
   DCHECK(item_manager_);
   DCHECK(color_theme_);
-  auto notification_list_view = std::make_unique<MediaNotificationListView>(
-      MediaNotificationListView::SeparatorStyle(color_theme_->separator_color,
-                                                separator_thickness));
+  auto notification_list_view =
+      std::make_unique<global_media_controls::MediaItemUIListView>(
+          global_media_controls::MediaItemUIListView::SeparatorStyle(
+              color_theme_->separator_color, separator_thickness));
   active_session_view_ = notification_list_view.get();
   item_manager_->SetDialogDelegate(this);
-  base::UmaHistogramEnumeration("Media.GlobalMediaControls.EntryPoint",
-                                GlobalMediaControlsEntryPoint::kSystemTray);
+  base::UmaHistogramEnumeration(
+      "Media.GlobalMediaControls.EntryPoint",
+      global_media_controls::GlobalMediaControlsEntryPoint::kSystemTray);
   return std::move(notification_list_view);
 }
 
@@ -88,14 +90,14 @@
   if (!active_session_view_)
     return nullptr;
 
-  auto container = std::make_unique<MediaNotificationContainerImplView>(
-      id, item, service_, GlobalMediaControlsEntryPoint::kSystemTray, profile_,
+  auto item_ui = std::make_unique<global_media_controls::MediaItemUIView>(
+      id, item, /*footer_view=*/nullptr, /*device_selector_view=*/nullptr,
       color_theme_);
-  auto* item_ui_ptr = container.get();
+  auto* item_ui_ptr = item_ui.get();
   item_ui_ptr->AddObserver(this);
   observed_item_uis_[id] = item_ui_ptr;
 
-  active_session_view_->ShowNotification(id, std::move(container));
+  active_session_view_->ShowItem(id, std::move(item_ui));
   for (auto& observer : observers_)
     observer.OnNotificationListViewSizeChanged();
 
@@ -106,7 +108,7 @@
   if (!active_session_view_)
     return;
 
-  active_session_view_->HideNotification(id);
+  active_session_view_->HideItem(id);
   for (auto& observer : observers_)
     observer.OnNotificationListViewSizeChanged();
 }
@@ -132,12 +134,13 @@
 
 void MediaNotificationProviderImpl::OnUserProfileLoaded(
     const AccountId& account_id) {
-  profile_ = chromeos::ProfileHelper::Get()->GetProfileByAccountId(account_id);
+  auto* profile =
+      chromeos::ProfileHelper::Get()->GetProfileByAccountId(account_id);
   user_manager::User* user =
-      chromeos::ProfileHelper::Get()->GetUserByProfile(profile_);
+      chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
 
   if (user_manager::UserManager::Get()->GetPrimaryUser() == user) {
-    service_ = MediaNotificationServiceFactory::GetForProfile(profile_);
+    service_ = MediaNotificationServiceFactory::GetForProfile(profile);
     item_manager_ = service_->media_item_manager();
     item_manager_->AddObserver(this);
   }
diff --git a/chrome/browser/ui/ash/media_notification_provider_impl.h b/chrome/browser/ui/ash/media_notification_provider_impl.h
index 40acd6f2..14340e5f 100644
--- a/chrome/browser/ui/ash/media_notification_provider_impl.h
+++ b/chrome/browser/ui/ash/media_notification_provider_impl.h
@@ -18,11 +18,10 @@
 
 namespace global_media_controls {
 class MediaItemManager;
+class MediaItemUIListView;
 }  // namespace global_media_controls
 
 class MediaNotificationService;
-class MediaNotificationListView;
-class Profile;
 
 class MediaNotificationProviderImpl
     : public ash::MediaNotificationProvider,
@@ -72,9 +71,7 @@
  private:
   base::ObserverList<ash::MediaNotificationProviderObserver> observers_;
 
-  MediaNotificationListView* active_session_view_ = nullptr;
-
-  Profile* profile_ = nullptr;
+  global_media_controls::MediaItemUIListView* active_session_view_ = nullptr;
 
   MediaNotificationService* service_ = nullptr;
 
diff --git a/chrome/browser/ui/ash/media_notification_provider_impl_unittest.cc b/chrome/browser/ui/ash/media_notification_provider_impl_unittest.cc
index 6c78fe70..5480109 100644
--- a/chrome/browser/ui/ash/media_notification_provider_impl_unittest.cc
+++ b/chrome/browser/ui/ash/media_notification_provider_impl_unittest.cc
@@ -12,9 +12,9 @@
 #include "chrome/browser/ui/global_media_controls/media_notification_service.h"
 #include "chrome/browser/ui/global_media_controls/media_notification_service_factory.h"
 #include "chrome/browser/ui/global_media_controls/media_session_notification_producer.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_list_view.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "components/global_media_controls/public/views/media_item_ui_list_view.h"
 #include "components/session_manager/core/session_manager.h"
 #include "content/public/test/browser_task_environment.h"
 #include "media/base/media_switches.h"
@@ -144,12 +144,12 @@
       provider()->GetMediaNotificationListView(1);
 
   auto* notification_list_view =
-      static_cast<MediaNotificationListView*>(view.get());
-  EXPECT_EQ(notification_list_view->notifications_for_testing().size(), 2u);
+      static_cast<global_media_controls::MediaItemUIListView*>(view.get());
+  EXPECT_EQ(notification_list_view->items_for_testing().size(), 2u);
 
   EXPECT_CALL(*observer(), OnNotificationListViewSizeChanged);
   SimulateHideNotification(id_1);
-  EXPECT_EQ(notification_list_view->notifications_for_testing().size(), 1u);
+  EXPECT_EQ(notification_list_view->items_for_testing().size(), 1u);
 
   provider()->OnBubbleClosing();
 }
diff --git a/chrome/browser/ui/ash/projector/projector_app_client_impl.cc b/chrome/browser/ui/ash/projector/projector_app_client_impl.cc
index 59997f3..b9abcf3 100644
--- a/chrome/browser/ui/ash/projector/projector_app_client_impl.cc
+++ b/chrome/browser/ui/ash/projector/projector_app_client_impl.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
+#include "content/public/browser/storage_partition.h"
 
 ProjectorAppClientImpl::ProjectorAppClientImpl() = default;
 ProjectorAppClientImpl::~ProjectorAppClientImpl() = default;
@@ -26,3 +27,12 @@
 void ProjectorAppClientImpl::RemoveObserver(Observer* observer) {
   observers_.RemoveObserver(observer);
 }
+
+network::mojom::URLLoaderFactory*
+ProjectorAppClientImpl::GetUrlLoaderFactory() {
+  Profile* profile = ProfileManager::GetPrimaryUserProfile();
+  DCHECK(chromeos::ProfileHelper::IsPrimaryProfile(profile));
+  return profile->GetDefaultStoragePartition()
+      ->GetURLLoaderFactoryForBrowserProcess()
+      .get();
+}
diff --git a/chrome/browser/ui/ash/projector/projector_app_client_impl.h b/chrome/browser/ui/ash/projector/projector_app_client_impl.h
index 65af0814..8c2f6bc8b 100644
--- a/chrome/browser/ui/ash/projector/projector_app_client_impl.h
+++ b/chrome/browser/ui/ash/projector/projector_app_client_impl.h
@@ -8,6 +8,12 @@
 #include "base/observer_list.h"
 #include "chromeos/components/projector_app/projector_app_client.h"
 
+namespace network {
+namespace mojom {
+class URLLoaderFactory;
+}
+}  // namespace network
+
 // Implements the interface for Projector App.
 class ProjectorAppClientImpl : public chromeos::ProjectorAppClient {
  public:
@@ -18,6 +24,7 @@
 
   // chromeos::ProjectorAppClient:
   signin::IdentityManager* GetIdentityManager() override;
+  network::mojom::URLLoaderFactory* GetUrlLoaderFactory() override;
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
 
diff --git a/chrome/browser/ui/browser_focus_uitest.cc b/chrome/browser/ui/browser_focus_uitest.cc
index 59cca50..4a3012588 100644
--- a/chrome/browser/ui/browser_focus_uitest.cc
+++ b/chrome/browser/ui/browser_focus_uitest.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/find_bar/find_bar_host_unittest_util.h"
+#include "chrome/browser/ui/frame/window_frame_util.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/view_ids.h"
@@ -125,6 +126,14 @@
                                                     reverse, false, false));
       }
 
+      // From the location icon we must traverse backwards one more time to
+      // traverse past the tab search caption button if present.
+      if (WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(browser()) &&
+          reverse) {
+        ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), key, false, true,
+                                                    false, false));
+      }
+
       for (size_t j = 0; j < base::size(kExpectedIDs); ++j) {
         SCOPED_TRACE(base::StringPrintf("focus inner loop %" PRIuS, j));
         const size_t index = reverse ? base::size(kExpectedIDs) - 1 - j : j;
@@ -161,6 +170,13 @@
       }
 #endif
 
+      // Traverse over the tab search frame caption button if present.
+      if (WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(browser()) &&
+          !reverse) {
+        ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), key, false,
+                                                    false, false, false));
+      }
+
       ui_test_utils::WaitForViewFocus(
           browser(), reverse ? VIEW_ID_OMNIBOX : VIEW_ID_LOCATION_ICON, true);
 
diff --git a/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm b/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm
index caaca76..005887a 100644
--- a/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm
+++ b/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm
@@ -168,7 +168,6 @@
   // be nice to fake a click with -performClick: but that doesn't work (see
   // http://www.cocoabuilder.com/archive/cocoa/177610-programmatically-click-column-header-in-nstableview.html).
   bool is_sorted = false;
-  int sorted_col_id = -1;
   for (NSTableColumn* column in tableColumns) {
     if ([column isHidden])
       continue;
@@ -178,7 +177,6 @@
           [[column sortDescriptorPrototype] reversedSortDescriptor];
       [table setSortDescriptors:@[ newSortDescriptor ]];
       is_sorted = true;
-      sorted_col_id = [[column identifier] intValue];
       break;
     }
   }
diff --git a/chrome/browser/ui/frame/window_frame_util.cc b/chrome/browser/ui/frame/window_frame_util.cc
index d50ad176..9b26827 100644
--- a/chrome/browser/ui/frame/window_frame_util.cc
+++ b/chrome/browser/ui/frame/window_frame_util.cc
@@ -4,8 +4,15 @@
 
 #include "chrome/browser/ui/frame/window_frame_util.h"
 
+#include "build/build_config.h"
 #include "ui/gfx/geometry/size.h"
 
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/ui_features.h"
+#endif  // defined(OS_WIN)
+
 // static
 SkAlpha WindowFrameUtil::CalculateWindows10GlassCaptionButtonBackgroundAlpha(
     SkAlpha theme_alpha) {
@@ -14,6 +21,8 @@
 
 // static
 gfx::Size WindowFrameUtil::GetWindows10GlassCaptionButtonAreaSize() {
+  // TODO(crbug.com/1257470): Fix uses of this to dynamically compute the size
+  // of the glass caption button area.
   constexpr int kNumButtons = 3;
 
   return gfx::Size(
@@ -21,3 +30,15 @@
           ((kNumButtons - 1) * kWindows10GlassCaptionButtonVisualSpacing),
       kWindows10GlassCaptionButtonHeightRestored);
 }
+
+// static
+bool WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(
+    const Browser* browser) {
+#if defined(OS_WIN)
+  return browser->is_type_normal() &&
+         base::win::GetVersion() >= base::win::Version::WIN10 &&
+         base::FeatureList::IsEnabled(features::kWin10TabSearchCaptionButton);
+#else
+  return false;
+#endif  // defined(OS_WIN)
+}
diff --git a/chrome/browser/ui/frame/window_frame_util.h b/chrome/browser/ui/frame/window_frame_util.h
index e10a7a6..52203e9 100644
--- a/chrome/browser/ui/frame/window_frame_util.h
+++ b/chrome/browser/ui/frame/window_frame_util.h
@@ -12,6 +12,8 @@
 class Size;
 }
 
+class Browser;
+
 // Static-only class containing values and helper functions for frame classes
 // that need to be accessible outside of /browser/ui/views.
 class WindowFrameUtil {
@@ -32,6 +34,9 @@
   // browser frame view.
   static gfx::Size GetWindows10GlassCaptionButtonAreaSize();
 
+  // Returns true if the windows 10 caption button is enabled.
+  static bool IsWin10TabSearchCaptionButtonEnabled(const Browser* browser);
+
  private:
   WindowFrameUtil() {}
 };
diff --git a/chrome/browser/ui/global_media_controls/cast_media_notification_item.cc b/chrome/browser/ui/global_media_controls/cast_media_notification_item.cc
index 22f0579..810bfd31 100644
--- a/chrome/browser/ui/global_media_controls/cast_media_notification_item.cc
+++ b/chrome/browser/ui/global_media_controls/cast_media_notification_item.cc
@@ -8,11 +8,15 @@
 #include "base/location.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/feature_engagement/tracker_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/global_media_controls/cast_media_session_controller.h"
+#include "components/feature_engagement/public/tracker.h"
 #include "components/global_media_controls/public/media_item_manager.h"
 #include "components/media_message_center/media_notification_view.h"
 #include "components/media_message_center/media_notification_view_impl.h"
+#include "components/media_router/browser/media_router.h"
+#include "components/media_router/browser/media_router_factory.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -268,6 +272,36 @@
     view_->UpdateWithMediaMetadata(metadata_);
 }
 
+void CastMediaNotificationItem::StopCasting(
+    global_media_controls::GlobalMediaControlsEntryPoint entry_point) {
+  media_router::MediaRouterFactory::GetApiForBrowserContext(profile_)
+      ->TerminateRoute(media_route_id_);
+
+  item_manager_->FocusDialog();
+
+  feature_engagement::TrackerFactory::GetForBrowserContext(profile_)
+      ->NotifyEvent("media_route_stopped_from_gmc");
+
+  global_media_controls::GlobalMediaControlsCastActionAndEntryPoint action;
+  switch (entry_point) {
+    case global_media_controls::GlobalMediaControlsEntryPoint::kToolbarIcon:
+      action = global_media_controls::
+          GlobalMediaControlsCastActionAndEntryPoint::kStopViaToolbarIcon;
+      break;
+    case global_media_controls::GlobalMediaControlsEntryPoint::kPresentation:
+      action = global_media_controls::
+          GlobalMediaControlsCastActionAndEntryPoint::kStopViaPresentation;
+      break;
+    case global_media_controls::GlobalMediaControlsEntryPoint::kSystemTray:
+      action = global_media_controls::
+          GlobalMediaControlsCastActionAndEntryPoint::kStopViaSystemTray;
+      break;
+  }
+  base::UmaHistogramEnumeration(
+      media_message_center::MediaNotificationItem::kCastStartStopHistogramName,
+      action);
+}
+
 mojo::PendingRemote<media_router::mojom::MediaStatusObserver>
 CastMediaNotificationItem::GetObserverPendingRemote() {
   return observer_receiver_.BindNewPipeAndPassRemote();
diff --git a/chrome/browser/ui/global_media_controls/cast_media_notification_item.h b/chrome/browser/ui/global_media_controls/cast_media_notification_item.h
index da9799d..d1d58a3 100644
--- a/chrome/browser/ui/global_media_controls/cast_media_notification_item.h
+++ b/chrome/browser/ui/global_media_controls/cast_media_notification_item.h
@@ -8,6 +8,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
 #include "chrome/browser/ui/global_media_controls/cast_media_session_controller.h"
+#include "components/global_media_controls/public/constants.h"
 #include "components/media_message_center/media_notification_item.h"
 #include "components/media_router/common/media_route.h"
 #include "components/media_router/common/mojom/media_status.mojom.h"
@@ -62,6 +63,10 @@
 
   void OnRouteUpdated(const media_router::MediaRoute& route);
 
+  // Stops the cast session and logs UMA about the stop cast action.
+  void StopCasting(
+      global_media_controls::GlobalMediaControlsEntryPoint entry_point);
+
   // Returns a pending remote bound to |this|. This should not be called more
   // than once per instance.
   mojo::PendingRemote<media_router::mojom::MediaStatusObserver>
diff --git a/chrome/browser/ui/global_media_controls/media_item_ui_device_selector_delegate.h b/chrome/browser/ui/global_media_controls/media_item_ui_device_selector_delegate.h
new file mode 100644
index 0000000..e8a206c
--- /dev/null
+++ b/chrome/browser/ui/global_media_controls/media_item_ui_device_selector_delegate.h
@@ -0,0 +1,37 @@
+// 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 CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_DEVICE_SELECTOR_DELEGATE_H_
+#define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_DEVICE_SELECTOR_DELEGATE_H_
+
+#include "base/callback_list.h"
+#include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
+
+class MediaItemUIDeviceSelectorDelegate {
+ public:
+  // Called when the user selects an audio device on the
+  // |MediaItemUIDeviceSelectorView|.
+  virtual void OnAudioSinkChosen(const std::string& id,
+                                 const std::string& sink_id) = 0;
+
+  // Used by a |MediaItemUIDeviceSelectorView| to query the system for connected
+  // audio output devices.
+  virtual base::CallbackListSubscription
+  RegisterAudioOutputDeviceDescriptionsCallback(
+      MediaNotificationDeviceProvider::GetOutputDevicesCallbackList::
+          CallbackType callback) = 0;
+
+  // Used by a |MediaItemUIDeviceSelectorView| to become notified of audio
+  // device switching capabilities. The callback will be immediately run with
+  // the current availability.
+  virtual base::CallbackListSubscription
+  RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
+      const std::string& id,
+      base::RepeatingCallback<void(bool)> callback) = 0;
+
+ protected:
+  virtual ~MediaItemUIDeviceSelectorDelegate() = default;
+};
+
+#endif  // CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_DEVICE_SELECTOR_DELEGATE_H_
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service.cc b/chrome/browser/ui/global_media_controls/media_notification_service.cc
index 8284753d..94537f2 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service.cc
+++ b/chrome/browser/ui/global_media_controls/media_notification_service.cc
@@ -25,7 +25,6 @@
 #include "components/global_media_controls/public/media_item_ui.h"
 #include "components/media_message_center/media_notification_item.h"
 #include "components/media_router/browser/presentation/start_presentation_context.h"
-#include "content/public/browser/audio_service.h"
 #include "content/public/browser/media_session.h"
 #include "content/public/browser/media_session_service.h"
 #include "media/base/media_switches.h"
@@ -142,22 +141,6 @@
              : false;
 }
 
-base::CallbackListSubscription
-MediaNotificationService::RegisterAudioOutputDeviceDescriptionsCallback(
-    MediaNotificationDeviceProvider::GetOutputDevicesCallback callback) {
-  return media_session_notification_producer_
-      ->RegisterAudioOutputDeviceDescriptionsCallback(std::move(callback));
-}
-
-base::CallbackListSubscription
-MediaNotificationService::RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
-    const std::string& id,
-    base::RepeatingCallback<void(bool)> callback) {
-  return media_session_notification_producer_
-      ->RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
-          id, std::move(callback));
-}
-
 void MediaNotificationService::OnStartPresentationContextCreated(
     std::unique_ptr<media_router::StartPresentationContext> context) {
   auto* web_contents = content::WebContents::FromRenderFrameHost(
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service.h b/chrome/browser/ui/global_media_controls/media_notification_service.h
index a124fe7..7e398b1 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service.h
+++ b/chrome/browser/ui/global_media_controls/media_notification_service.h
@@ -14,10 +14,10 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/global_media_controls/cast_media_notification_producer.h"
 #include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
+#include "chrome/browser/ui/global_media_controls/media_session_notification_producer.h"
 #include "chrome/browser/ui/global_media_controls/presentation_request_notification_producer.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/media_router/browser/presentation/web_contents_presentation_manager.h"
-#include "media/audio/audio_device_description.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace content {
@@ -34,8 +34,6 @@
 class CastDialogController;
 }  // namespace media_router
 
-class MediaSessionNotificationProducer;
-
 class MediaNotificationService : public KeyedService {
  public:
   MediaNotificationService(Profile* profile, bool show_from_all_profiles);
@@ -50,6 +48,10 @@
     return item_manager_.get();
   }
 
+  MediaItemUIDeviceSelectorDelegate* device_selector_delegate() {
+    return media_session_notification_producer_.get();
+  }
+
   void SetDialogDelegateForWebContents(
       global_media_controls::MediaDialogDelegate* delegate,
       content::WebContents* contents);
@@ -62,19 +64,6 @@
   // True if there are local cast notifications.
   bool HasLocalCastNotifications() const;
 
-  // Used by a |MediaNotificationDeviceSelectorView| to query the system
-  // for connected audio output devices.
-  base::CallbackListSubscription RegisterAudioOutputDeviceDescriptionsCallback(
-      MediaNotificationDeviceProvider::GetOutputDevicesCallback callback);
-
-  // Used by a |MediaNotificationAudioDeviceSelectorView| to become notified of
-  // audio device switching capabilities. The callback will be immediately run
-  // with the current availability.
-  base::CallbackListSubscription
-  RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
-      const std::string& id,
-      base::RepeatingCallback<void(bool)> callback);
-
   void OnStartPresentationContextCreated(
       std::unique_ptr<media_router::StartPresentationContext> context);
 
diff --git a/chrome/browser/ui/global_media_controls/media_session_notification_producer.cc b/chrome/browser/ui/global_media_controls/media_session_notification_producer.cc
index 058ab24..1121b3ee 100644
--- a/chrome/browser/ui/global_media_controls/media_session_notification_producer.cc
+++ b/chrome/browser/ui/global_media_controls/media_session_notification_producer.cc
@@ -443,14 +443,6 @@
   session->item()->Dismiss();
 }
 
-void MediaSessionNotificationProducer::OnAudioSinkChosen(
-    const std::string& id,
-    const std::string& sink_id) {
-  auto it = sessions_.find(id);
-  DCHECK(it != sessions_.end());
-  it->second.SetAudioSinkId(sink_id);
-}
-
 void MediaSessionNotificationProducer::OnItemShown(
     const std::string& id,
     global_media_controls::MediaItemUI* item_ui) {
@@ -560,6 +552,14 @@
       .Record(recorder);
 }
 
+void MediaSessionNotificationProducer::OnAudioSinkChosen(
+    const std::string& id,
+    const std::string& sink_id) {
+  auto it = sessions_.find(id);
+  DCHECK(it != sessions_.end());
+  it->second.SetAudioSinkId(sink_id);
+}
+
 base::CallbackListSubscription
 MediaSessionNotificationProducer::RegisterAudioOutputDeviceDescriptionsCallback(
     MediaNotificationDeviceProvider::GetOutputDevicesCallback callback) {
diff --git a/chrome/browser/ui/global_media_controls/media_session_notification_producer.h b/chrome/browser/ui/global_media_controls/media_session_notification_producer.h
index 717bf3de..e66f223 100644
--- a/chrome/browser/ui/global_media_controls/media_session_notification_producer.h
+++ b/chrome/browser/ui/global_media_controls/media_session_notification_producer.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_SESSION_NOTIFICATION_PRODUCER_H_
 #define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_SESSION_NOTIFICATION_PRODUCER_H_
 
+#include "chrome/browser/ui/global_media_controls/media_item_ui_device_selector_delegate.h"
 #include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
 #include "chrome/browser/ui/global_media_controls/media_session_notification_item.h"
 #include "components/global_media_controls/public/media_item_manager_observer.h"
@@ -47,6 +48,7 @@
     : public global_media_controls::MediaItemProducer,
       public MediaSessionNotificationItem::Delegate,
       public media_session::mojom::AudioFocusObserver,
+      public MediaItemUIDeviceSelectorDelegate,
       public global_media_controls::MediaItemUIObserver {
  public:
   MediaSessionNotificationProducer(
@@ -82,8 +84,6 @@
   // global_media_controls::MediaItemUIObserver implementation.
   void OnMediaItemUIClicked(const std::string& id) override;
   void OnMediaItemUIDismissed(const std::string& id) override;
-  void OnAudioSinkChosen(const std::string& id,
-                         const std::string& sink_id) override;
 
   bool HasSession(const std::string& id) const;
   std::unique_ptr<media_router::CastDialogController>
@@ -95,18 +95,16 @@
   std::string GetActiveControllableSessionForWebContents(
       content::WebContents* web_contents) const;
 
-  // Used by a |MediaNotificationDeviceSelectorView| to query the system
-  // for connected audio output devices.
+  // MediaItemUIDeviceSelectorDelegate:
+  void OnAudioSinkChosen(const std::string& id,
+                         const std::string& sink_id) override;
   base::CallbackListSubscription RegisterAudioOutputDeviceDescriptionsCallback(
-      MediaNotificationDeviceProvider::GetOutputDevicesCallback callback);
-
-  // Used by a |MediaNotificationAudioDeviceSelectorView| to become notified of
-  // audio device switching capabilities. The callback will be immediately run
-  // with the current availability.
+      MediaNotificationDeviceProvider::GetOutputDevicesCallback callback)
+      override;
   base::CallbackListSubscription
   RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
       const std::string& id,
-      base::RepeatingCallback<void(bool)> callback);
+      base::RepeatingCallback<void(bool)> callback) override;
 
   void OnStartPresentationContextCreated(
       std::unique_ptr<media_router::StartPresentationContext> context);
diff --git a/chrome/browser/ui/global_media_controls/test_helper.cc b/chrome/browser/ui/global_media_controls/test_helper.cc
index 50765fe4..8735269 100644
--- a/chrome/browser/ui/global_media_controls/test_helper.cc
+++ b/chrome/browser/ui/global_media_controls/test_helper.cc
@@ -7,17 +7,8 @@
 #include <memory>
 #include <string>
 
-#include "chrome/browser/ui/global_media_controls/media_notification_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
-MockMediaNotificationItem::MockMediaNotificationItem() = default;
-MockMediaNotificationItem::~MockMediaNotificationItem() = default;
-
-base::WeakPtr<MockMediaNotificationItem>
-MockMediaNotificationItem::GetWeakPtr() {
-  return weak_ptr_factory_.GetWeakPtr();
-}
-
 MockWebContentsPresentationManager::MockWebContentsPresentationManager() =
     default;
 MockWebContentsPresentationManager::~MockWebContentsPresentationManager() =
diff --git a/chrome/browser/ui/global_media_controls/test_helper.h b/chrome/browser/ui/global_media_controls/test_helper.h
index c602d403..6798179 100644
--- a/chrome/browser/ui/global_media_controls/test_helper.h
+++ b/chrome/browser/ui/global_media_controls/test_helper.h
@@ -5,36 +5,12 @@
 #ifndef CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_TEST_HELPER_H_
 #define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_TEST_HELPER_H_
 
-#include "components/media_message_center/media_notification_item.h"
 #include "components/media_router/browser/presentation/web_contents_presentation_manager.h"
 #include "content/public/browser/presentation_request.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
-using media_message_center::MediaNotificationView;
 using media_router::WebContentsPresentationManager;
 
-class MockMediaNotificationItem
-    : public media_message_center::MediaNotificationItem {
- public:
-  MockMediaNotificationItem();
-  ~MockMediaNotificationItem() override;
-
-  base::WeakPtr<MockMediaNotificationItem> GetWeakPtr();
-
-  MOCK_METHOD(void, SetView, (MediaNotificationView*));
-  MOCK_METHOD(void,
-              OnMediaSessionActionButtonPressed,
-              (media_session::mojom::MediaSessionAction));
-  MOCK_METHOD(void, SeekTo, (base::TimeDelta));
-  MOCK_METHOD(void, Dismiss, ());
-  MOCK_METHOD(void, SetVolume, (float));
-  MOCK_METHOD(void, SetMute, (bool));
-  MOCK_METHOD(media_message_center::SourceType, SourceType, ());
-
- private:
-  base::WeakPtrFactory<MockMediaNotificationItem> weak_ptr_factory_{this};
-};
-
 class MockWebContentsPresentationManager
     : public WebContentsPresentationManager {
  public:
diff --git a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.cc b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.cc
index afcd2e4..4e294a5 100644
--- a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.cc
+++ b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h"
 
+#include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/chrome_content_settings_utils.h"
 #include "chrome/browser/page_info/about_this_site_service_factory.h"
@@ -93,12 +94,13 @@
   return std::u16string();
 }
 
-std::u16string ChromePageInfoUiDelegate::GetAboutThisSiteDescription() {
+absl::optional<page_info::proto::SiteInfo>
+ChromePageInfoUiDelegate::GetAboutThisSiteInfo() {
   if (auto* service =
           AboutThisSiteServiceFactory::GetForProfile(GetProfile())) {
-    return service->GetAboutThisSiteDescription(site_url_);
+    return service->GetAboutThisSiteInfo(site_url_);
   }
-  return std::u16string();
+  return absl::nullopt;
 }
 
 bool ChromePageInfoUiDelegate::ShouldShowAsk(ContentSettingsType type) {
diff --git a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h
index 5440717..70fe2e4 100644
--- a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h
+++ b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h
@@ -9,6 +9,7 @@
 
 #include "build/build_config.h"
 #include "components/page_info/page_info_ui_delegate.h"
+#include "components/page_info/proto/about_this_site_metadata.pb.h"
 #include "url/gurl.h"
 
 class Profile;
@@ -33,8 +34,8 @@
   // If "allow" option is not available, return the reason why.
   std::u16string GetAutomaticallyBlockedReason(ContentSettingsType type);
 
-  // Returns "About this site" description for the active page.
-  std::u16string GetAboutThisSiteDescription();
+  // Returns "About this site" info for the active page.
+  absl::optional<page_info::proto::SiteInfo> GetAboutThisSiteInfo();
 
 #if !defined(OS_ANDROID)
   // If PageInfo should show a link to the site or app's settings page, this
diff --git a/chrome/browser/ui/tabs/tab_menu_model.cc b/chrome/browser/ui/tabs/tab_menu_model.cc
index 4149ef02..26c9863 100644
--- a/chrome/browser/ui/tabs/tab_menu_model.cc
+++ b/chrome/browser/ui/tabs/tab_menu_model.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/tabs/tab_menu_model.h"
 
 #include "base/command_line.h"
+#include "base/i18n/rtl.h"
 #include "base/metrics/user_metrics.h"
 #include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
@@ -158,7 +159,8 @@
   AddItemWithStringId(TabStripModel::CommandCloseOtherTabs,
                       IDS_TAB_CXMENU_CLOSEOTHERTABS);
   AddItemWithStringId(TabStripModel::CommandCloseTabsToRight,
-                      IDS_TAB_CXMENU_CLOSETABSTORIGHT);
+                      base::i18n::IsRTL() ? IDS_TAB_CXMENU_CLOSETABSTOLEFT
+                                          : IDS_TAB_CXMENU_CLOSETABSTORIGHT);
 }
 
 DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(TabMenuModel,
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial.cc b/chrome/browser/ui/user_education/tutorial/tutorial.cc
index ec9dad2..894ce64 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial.cc
+++ b/chrome/browser/ui/user_education/tutorial/tutorial.cc
@@ -24,6 +24,7 @@
 Tutorial::StepBuilder::BuildFromDescriptionStep(
     TutorialDescription::Step step,
     absl::optional<std::pair<int, int>> progress,
+    bool is_last_step,
     TutorialService* tutorial_service,
     TutorialBubbleFactoryRegistry* bubble_factory_registry) {
   Tutorial::StepBuilder step_builder;
@@ -33,7 +34,8 @@
       .SetBodyText(step.body_text)
       .SetStepType(step.step_type)
       .SetProgress(progress)
-      .SetArrow(step.arrow);
+      .SetArrow(step.arrow)
+      .SetIsLastStep(is_last_step);
 
   return step_builder.Build(tutorial_service, bubble_factory_registry);
 }
@@ -74,6 +76,12 @@
   return *this;
 }
 
+Tutorial::StepBuilder& Tutorial::StepBuilder::SetIsLastStep(
+    bool is_last_step_) {
+  this->is_last_step = is_last_step_;
+  return *this;
+}
+
 std::unique_ptr<ui::InteractionSequence::Step> Tutorial::StepBuilder::Build(
     TutorialService* tutorial_service,
     TutorialBubbleFactoryRegistry* bubble_factory_registry) {
@@ -94,28 +102,29 @@
          absl::optional<std::u16string> title_text_,
          absl::optional<std::u16string> body_text_,
          TutorialDescription::Step::Arrow arrow_,
-         absl::optional<std::pair<int, int>> progress_,
+         absl::optional<std::pair<int, int>> progress_, bool is_last_step_,
          ui::InteractionSequence* sequence, ui::TrackedElement* element) {
         DCHECK(tutorial_service);
         DCHECK(bubble_factory_registry);
 
+        tutorial_service->HideCurrentBubbleIfShowing();
+
         std::unique_ptr<TutorialBubble> bubble =
             bubble_factory_registry->CreateBubbleForTrackedElement(
-                element, title_text_, body_text_, arrow_, progress_);
+                element, title_text_, body_text_, arrow_, progress_,
+                is_last_step_);
         tutorial_service->SetCurrentBubble(std::move(bubble));
       },
       base::Unretained(tutorial_service),
       base::Unretained(bubble_factory_registry), title_text, body_text, arrow,
-      progress);
+      progress, is_last_step);
 }
 
 ui::InteractionSequence::StepEndCallback
 Tutorial::StepBuilder::BuildHideBubbleCallback(
     TutorialService* tutorial_service) {
   return base::BindOnce(
-      [](TutorialService* tutorial_service, ui::TrackedElement* element) {
-        tutorial_service->HideCurrentBubbleIfShowing();
-      },
+      [](TutorialService* tutorial_service, ui::TrackedElement* element) {},
       base::Unretained(tutorial_service));
 }
 
@@ -142,8 +151,9 @@
   int current_step = 0;
   for (const auto& step : description.steps) {
     builder.AddStep(Tutorial::StepBuilder::BuildFromDescriptionStep(
-        step, std::pair<int, int>(current_step, visible_step_count),
-        tutorial_service, bubble_factory_registry));
+        step, std::pair<int, int>(current_step, visible_step_count - 1),
+        &step == &description.steps.back(), tutorial_service,
+        bubble_factory_registry));
     if (step.ShouldShowBubble())
       current_step++;
   }
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial.h b/chrome/browser/ui/user_education/tutorial/tutorial.h
index 623f6db..99713f5 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial.h
+++ b/chrome/browser/ui/user_education/tutorial/tutorial.h
@@ -41,15 +41,27 @@
  public:
   ~Tutorial();
 
+  // Step Builder provides an interface for constructing an
+  // InteractionSequence::Step from a TutorialDescription::Step.
+  // TutorialDescription is used as the basis for the StepBuilder since all
+  // parameters of the Description will be needed to create the bubble or build
+  // the interaction sequence step. In order to use the The StepBuilder should
+  // only be used by Tutorial::Builder to construct the steps in the tutorial.
   class StepBuilder : public TutorialDescription::Step {
    public:
     StepBuilder();
     ~StepBuilder();
+    StepBuilder(const StepBuilder&) = delete;
+    StepBuilder& operator=(const StepBuilder&) = delete;
 
+    // Constructs the InteractionSequenceStepDirectly from the
+    // TutorialDescriptionStep. This method is used by
+    // Tutorial::Builder::BuildFromDescription to create tutorials.
     static std::unique_ptr<ui::InteractionSequence::Step>
     BuildFromDescriptionStep(
         TutorialDescription::Step step,
         absl::optional<std::pair<int, int>> progress,
+        bool is_last_step,
         TutorialService* tutorial_service,
         TutorialBubbleFactoryRegistry* bubble_factory_registry);
 
@@ -57,8 +69,9 @@
     StepBuilder& SetTitleText(absl::optional<std::u16string> title_text_);
     StepBuilder& SetBodyText(absl::optional<std::u16string> body_text_);
     StepBuilder& SetStepType(ui::InteractionSequence::StepType step_type_);
-    StepBuilder& SetProgress(absl::optional<std::pair<int, int>> progress_);
     StepBuilder& SetArrow(TutorialDescription::Step::Arrow arrow_);
+    StepBuilder& SetProgress(absl::optional<std::pair<int, int>> progress_);
+    StepBuilder& SetIsLastStep(bool is_last_step_);
 
     std::unique_ptr<ui::InteractionSequence::Step> Build(
         TutorialService* tutorial_service,
@@ -66,6 +79,8 @@
 
    private:
     absl::optional<std::pair<int, int>> progress;
+    bool is_last_step = false;
+
     ui::InteractionSequence::StepStartCallback BuildShowBubbleCallback(
         TutorialService* tutorial_service,
         TutorialBubbleFactoryRegistry* bubble_factory_registry);
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.cc b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.cc
index ce0b48e1..67c22af 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.cc
+++ b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.cc
@@ -15,8 +15,10 @@
     absl::optional<std::u16string> title_text,
     absl::optional<std::u16string> body_text,
     TutorialDescription::Step::Arrow arrow,
-    absl::optional<std::pair<int, int>> progress) {
-  if (CanBuildBubbleForTrackedElement(element))
-    return CreateBubble(element, title_text, body_text, arrow, progress);
-  return nullptr;
+    absl::optional<std::pair<int, int>> progress,
+    bool is_last_step) {
+  if (!CanBuildBubbleForTrackedElement(element))
+    return nullptr;
+  return CreateBubble(element, title_text, body_text, arrow, progress,
+                      is_last_step);
 }
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.h b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.h
index e4978bd..a0ceec8 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.h
+++ b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.h
@@ -25,7 +25,8 @@
       absl::optional<std::u16string> title_text,
       absl::optional<std::u16string> body_text,
       TutorialDescription::Step::Arrow arrow,
-      absl::optional<std::pair<int, int>> progress);
+      absl::optional<std::pair<int, int>> progress,
+      bool is_last_step);
 
  private:
   // Called by the Tutorial to show the bubble.
@@ -34,7 +35,8 @@
       absl::optional<std::u16string> title_text,
       absl::optional<std::u16string> body_text,
       TutorialDescription::Step::Arrow arrow,
-      absl::optional<std::pair<int, int>> progress) = 0;
+      absl::optional<std::pair<int, int>> progress,
+      bool is_last_step) = 0;
 
   // Returns true iff the bubble owner can show a bubble for the TrackedElement.
   virtual bool CanBuildBubbleForTrackedElement(ui::TrackedElement* element) = 0;
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.cc b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.cc
index 8adb04f..c67cde1 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.cc
+++ b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.cc
@@ -24,11 +24,12 @@
     absl::optional<std::u16string> title_text,
     absl::optional<std::u16string> body_text,
     TutorialDescription::Step::Arrow arrow,
-    absl::optional<std::pair<int, int>> progress) {
+    absl::optional<std::pair<int, int>> progress,
+    bool is_last_step) {
   for (const auto& bubble_factory : bubble_factories_) {
     std::unique_ptr<TutorialBubble> bubble =
         bubble_factory->CreateBubbleIfElementIsValid(
-            element, title_text, body_text, arrow, progress);
+            element, title_text, body_text, arrow, progress, is_last_step);
     if (bubble)
       return bubble;
   }
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.h b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.h
index 482e90e..70f9485 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.h
+++ b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.h
@@ -34,7 +34,8 @@
       absl::optional<std::u16string> title_text,
       absl::optional<std::u16string> body_text,
       TutorialDescription::Step::Arrow arrow,
-      absl::optional<std::pair<int, int>> progress);
+      absl::optional<std::pair<int, int>> progress,
+      bool is_last_step);
 
  private:
   // the list of registered bubble factories
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial_service.cc b/chrome/browser/ui/user_education/tutorial/tutorial_service.cc
index 53f28c8d..41800dd 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial_service.cc
+++ b/chrome/browser/ui/user_education/tutorial/tutorial_service.cc
@@ -54,7 +54,7 @@
 
 void TutorialService::HideCurrentBubbleIfShowing() {
   if (currently_displayed_bubble_) {
-    currently_displayed_bubble_.release();
+    currently_displayed_bubble_.reset();
   }
 }
 
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 6fe66a440..f5910f1 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -206,6 +206,7 @@
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/scoped_canvas.h"
 #include "ui/gfx/scrollbar_size.h"
+#include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/accessibility/view_accessibility_utils.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/controls/button/menu_button.h"
@@ -1728,6 +1729,13 @@
 
   if (frame_ && frame_->GetFrameView())
     frame_->GetFrameView()->WindowControlsOverlayEnabledChanged();
+
+  GetViewAccessibility().AnnounceText(
+      IsWindowControlsOverlayEnabled()
+          ? l10n_util::GetStringUTF16(
+                IDS_WEB_APP_WINDOW_CONTROLS_OVERLAY_ENABLED_ALERT)
+          : l10n_util::GetStringUTF16(
+                IDS_WEB_APP_WINDOW_CONTROLS_OVERLAY_DISABLED_ALERT));
 }
 
 void BrowserView::UpdateWindowControlsOverlayToggleVisible() {
diff --git a/chrome/browser/ui/views/frame/glass_browser_caption_button_container.cc b/chrome/browser/ui/views/frame/glass_browser_caption_button_container.cc
index 94bf6f8..5c1acd0 100644
--- a/chrome/browser/ui/views/frame/glass_browser_caption_button_container.cc
+++ b/chrome/browser/ui/views/frame/glass_browser_caption_button_container.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "chrome/browser/ui/frame/window_frame_util.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/glass_browser_frame_view.h"
 #include "chrome/browser/ui/views/frame/windows_10_caption_button.h"
@@ -64,8 +65,8 @@
           frame_view_,
           VIEW_ID_CLOSE_BUTTON,
           IDS_APP_ACCNAME_CLOSE))) {
-  if (Windows10TabSearchCaptionButton::IsTabSearchCaptionButtonEnabled(
-          frame_view_)) {
+  if (WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(
+          frame_view_->browser_view()->browser())) {
     tab_search_button_ =
         AddChildViewAt(std::make_unique<Windows10TabSearchCaptionButton>(
                            frame_view_, VIEW_ID_TAB_SEARCH_BUTTON,
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view.cc b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
index 3ffee76..6150e79 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view.cc
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
@@ -8,6 +8,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/browser/themes/theme_properties.h"
+#include "chrome/browser/ui/frame/window_frame_util.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
@@ -127,12 +128,13 @@
     return;
 #endif
 
-#if defined(OS_WIN)
-  if (base::FeatureList::IsEnabled(features::kWin10TabSearchCaptionButton))
-    return;
-#endif
   const Browser* browser = tab_strip_->controller()->GetBrowser();
-  if (browser && browser->is_type_normal()) {
+  if (!browser ||
+      WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(browser)) {
+    return;
+  }
+
+  if (browser->is_type_normal()) {
     auto tab_search_button = std::make_unique<TabSearchButton>(tab_strip_);
     tab_search_button->SetTooltipText(
         l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_SEARCH));
diff --git a/chrome/browser/ui/views/frame/windows_10_caption_button.cc b/chrome/browser/ui/views/frame/windows_10_caption_button.cc
index e1461542..d14e6f7 100644
--- a/chrome/browser/ui/views/frame/windows_10_caption_button.cc
+++ b/chrome/browser/ui/views/frame/windows_10_caption_button.cc
@@ -9,7 +9,6 @@
 #include "chrome/browser/ui/frame/window_frame_util.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/glass_browser_frame_view.h"
-#include "chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h"
 #include "chrome/grit/theme_resources.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/theme_provider.h"
@@ -147,8 +146,8 @@
 int Windows10CaptionButton::GetButtonDisplayOrderIndex() const {
   int button_display_order = 0;
   const bool tab_search_enabled =
-      Windows10TabSearchCaptionButton::IsTabSearchCaptionButtonEnabled(
-          frame_view_);
+      WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(
+          frame_view_->browser_view()->browser());
   switch (button_type_) {
     case VIEW_ID_TAB_SEARCH_BUTTON:
       button_display_order = 0;
diff --git a/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.cc b/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.cc
index 1bb9e4e..23edbdf 100644
--- a/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.cc
+++ b/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.cc
@@ -4,19 +4,11 @@
 
 #include "chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h"
 
-#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/glass_browser_frame_view.h"
 #include "chrome/browser/ui/views/tab_search_bubble_host.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 
-// static.
-bool Windows10TabSearchCaptionButton::IsTabSearchCaptionButtonEnabled(
-    BrowserNonClientFrameView* frame_view) {
-  return frame_view->browser_view()->GetIsNormalType() &&
-         base::FeatureList::IsEnabled(features::kWin10TabSearchCaptionButton);
-}
-
 Windows10TabSearchCaptionButton::Windows10TabSearchCaptionButton(
     GlassBrowserFrameView* frame_view,
     ViewID button_type,
diff --git a/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h b/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h
index 57498c9..d38bf024 100644
--- a/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h
+++ b/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h
@@ -8,17 +8,12 @@
 #include "chrome/browser/ui/views/frame/windows_10_caption_button.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 
-class BrowserNonClientFrameView;
 class GlassBrowserFrameView;
 class TabSearchBubbleHost;
 
 class Windows10TabSearchCaptionButton : public Windows10CaptionButton {
  public:
   METADATA_HEADER(Windows10TabSearchCaptionButton);
-
-  static bool IsTabSearchCaptionButtonEnabled(
-      BrowserNonClientFrameView* frame_view);
-
   Windows10TabSearchCaptionButton(GlassBrowserFrameView* frame_view,
                                   ViewID button_type,
                                   const std::u16string& accessible_name);
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc b/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
index 53797f6..c3023d3 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
@@ -11,15 +11,19 @@
 #include "base/bind.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/global_media_controls/media_notification_service.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/global_media_controls/media_dialog_view_observer.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_list_view.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_legacy_cast_footer_view.h"
 #include "chrome/browser/ui/views/user_education/new_badge_label.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/global_media_controls/public/media_item_manager.h"
+#include "components/global_media_controls/public/views/media_item_ui_list_view.h"
+#include "components/global_media_controls/public/views/media_item_ui_view.h"
 #include "components/live_caption/pref_names.h"
 #include "components/soda/constants.h"
 #include "components/sync_preferences/pref_service_syncable.h"
@@ -61,7 +65,7 @@
     views::View* anchor_view,
     MediaNotificationService* service,
     Profile* profile,
-    GlobalMediaControlsEntryPoint entry_point) {
+    global_media_controls::GlobalMediaControlsEntryPoint entry_point) {
   return ShowDialogForPresentationRequest(anchor_view, service, profile,
                                           nullptr, entry_point);
 }
@@ -72,7 +76,7 @@
     MediaNotificationService* service,
     Profile* profile,
     content::WebContents* contents,
-    GlobalMediaControlsEntryPoint entry_point) {
+    global_media_controls::GlobalMediaControlsEntryPoint entry_point) {
   DCHECK(!instance_);
   DCHECK(service);
   instance_ =
@@ -113,23 +117,22 @@
 global_media_controls::MediaItemUI* MediaDialogView::ShowMediaItem(
     const std::string& id,
     base::WeakPtr<media_message_center::MediaNotificationItem> item) {
-  auto container = std::make_unique<MediaNotificationContainerImplView>(
-      id, item, service_, entry_point_, profile_);
-  MediaNotificationContainerImplView* container_ptr = container.get();
-  container_ptr->AddObserver(this);
-  observed_containers_[id] = container_ptr;
+  auto view = BuildMediaItemUIView(id, item);
+  auto* view_ptr = view.get();
+  view_ptr->AddObserver(this);
+  observed_items_[id] = view_ptr;
 
-  active_sessions_view_->ShowNotification(id, std::move(container));
+  active_sessions_view_->ShowItem(id, std::move(view));
   UpdateBubbleSize();
 
   for (auto& observer : observers_)
     observer.OnMediaSessionShown();
 
-  return container_ptr;
+  return view_ptr;
 }
 
 void MediaDialogView::HideMediaItem(const std::string& id) {
-  active_sessions_view_->HideNotification(id);
+  active_sessions_view_->HideItem(id);
 
   if (active_sessions_view_->empty())
     HideDialog();
@@ -155,7 +158,8 @@
   if (frame) {
     frame->SetCornerRadius(corner_radius);
   }
-  if (entry_point_ == GlobalMediaControlsEntryPoint::kPresentation) {
+  if (entry_point_ ==
+      global_media_controls::GlobalMediaControlsEntryPoint::kPresentation) {
     service_->SetDialogDelegateForWebContents(
         this, web_contents_for_presentation_request_);
   } else {
@@ -200,11 +204,11 @@
 }
 
 void MediaDialogView::OnMediaItemUIDestroyed(const std::string& id) {
-  auto iter = observed_containers_.find(id);
-  DCHECK(iter != observed_containers_.end());
+  auto iter = observed_items_.find(id);
+  DCHECK(iter != observed_items_.end());
 
   iter->second->RemoveObserver(this);
-  observed_containers_.erase(iter);
+  observed_items_.erase(iter);
 }
 
 void MediaDialogView::AddObserver(MediaDialogViewObserver* observer) {
@@ -215,26 +219,27 @@
   observers_.RemoveObserver(observer);
 }
 
-const std::map<const std::string, MediaNotificationContainerImplView*>&
-MediaDialogView::GetNotificationsForTesting() const {
-  return active_sessions_view_->notifications_for_testing();
+const std::map<const std::string, global_media_controls::MediaItemUIView*>&
+MediaDialogView::GetItemsForTesting() const {
+  return active_sessions_view_->items_for_testing();  // IN-TEST
 }
 
-const MediaNotificationListView* MediaDialogView::GetListViewForTesting()
-    const {
+const global_media_controls::MediaItemUIListView*
+MediaDialogView::GetListViewForTesting() const {
   return active_sessions_view_;
 }
 
-MediaDialogView::MediaDialogView(views::View* anchor_view,
-                                 MediaNotificationService* service,
-                                 Profile* profile,
-                                 content::WebContents* contents,
-                                 GlobalMediaControlsEntryPoint entry_point)
+MediaDialogView::MediaDialogView(
+    views::View* anchor_view,
+    MediaNotificationService* service,
+    Profile* profile,
+    content::WebContents* contents,
+    global_media_controls::GlobalMediaControlsEntryPoint entry_point)
     : BubbleDialogDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
       service_(service),
       profile_(profile->GetOriginalProfile()),
-      active_sessions_view_(
-          AddChildView(std::make_unique<MediaNotificationListView>())),
+      active_sessions_view_(AddChildView(
+          std::make_unique<global_media_controls::MediaItemUIListView>())),
       web_contents_for_presentation_request_(contents),
       entry_point_(entry_point) {
   // Enable layer based clipping to ensure children using layers are clipped
@@ -247,8 +252,8 @@
 }
 
 MediaDialogView::~MediaDialogView() {
-  for (auto container_pair : observed_containers_)
-    container_pair.second->RemoveObserver(this);
+  for (auto item_pair : observed_items_)
+    item_pair.second->RemoveObserver(this);
 }
 
 void MediaDialogView::Init() {
@@ -374,5 +379,66 @@
       combined_progress));
 }
 
+std::unique_ptr<global_media_controls::MediaItemUIView>
+MediaDialogView::BuildMediaItemUIView(
+    const std::string& id,
+    base::WeakPtr<media_message_center::MediaNotificationItem> item) {
+  const bool is_cast_item =
+      item->SourceType() == media_message_center::SourceType::kCast;
+  const bool is_local_media_session =
+      item->SourceType() ==
+      media_message_center::SourceType::kLocalMediaSession;
+  const bool gmc_cast_start_stop_enabled =
+      media_router::GlobalMediaControlsCastStartStopEnabled() &&
+      media_router::MediaRouterEnabled(profile_);
+
+  // Show a device selector view for media and supplemental notifications.
+  std::unique_ptr<MediaItemUIDeviceSelectorView> device_selector_view;
+  if (!is_cast_item && (gmc_cast_start_stop_enabled ||
+                        base::FeatureList::IsEnabled(
+                            media::kGlobalMediaControlsSeamlessTransfer))) {
+    const bool show_expand_button =
+        !base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI);
+    std::unique_ptr<media_router::CastDialogController> cast_controller;
+    if (media_router::GlobalMediaControlsCastStartStopEnabled() &&
+        media_router::MediaRouterEnabled(profile_)) {
+      cast_controller =
+          is_local_media_session
+              ? service_->CreateCastDialogControllerForSession(id)
+              : service_->CreateCastDialogControllerForPresentationRequest();
+    }
+    device_selector_view = std::make_unique<MediaItemUIDeviceSelectorView>(
+        id, service_->device_selector_delegate(), std::move(cast_controller),
+        /* has_audio_output */ is_local_media_session, entry_point_,
+        show_expand_button);
+  }
+
+  base::RepeatingClosure stop_casting_closure =
+      is_cast_item ? base::BindRepeating(
+                         &CastMediaNotificationItem::StopCasting,
+                         static_cast<CastMediaNotificationItem*>(item.get())
+                             ->GetWeakPtr(),
+                         entry_point_)
+                   : base::NullCallback();
+
+  std::unique_ptr<global_media_controls::MediaItemUIFooter> footer_view;
+  if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI)) {
+    footer_view = std::make_unique<MediaItemUIFooterView>(stop_casting_closure);
+
+    if (device_selector_view) {
+      auto* modern_footer =
+          static_cast<MediaItemUIFooterView*>(footer_view.get());
+      modern_footer->SetDelegate(device_selector_view.get());
+      device_selector_view->AddObserver(modern_footer);
+    }
+  } else if (is_cast_item && gmc_cast_start_stop_enabled) {
+    footer_view =
+        std::make_unique<MediaItemUILegacyCastFooterView>(stop_casting_closure);
+  }
+
+  return std::make_unique<global_media_controls::MediaItemUIView>(
+      id, item, std::move(footer_view), std::move(device_selector_view));
+}
+
 BEGIN_METADATA(MediaDialogView, views::BubbleDialogDelegateView)
 END_METADATA
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view.h b/chrome/browser/ui/views/global_media_controls/media_dialog_view.h
index ebd9631..60235c6 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view.h
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view.h
@@ -10,7 +10,7 @@
 #include <string>
 
 #include "base/observer_list.h"
-#include "chrome/browser/ui/views/global_media_controls/global_media_controls_types.h"
+#include "components/global_media_controls/public/constants.h"
 #include "components/global_media_controls/public/media_dialog_delegate.h"
 #include "components/global_media_controls/public/media_item_ui_observer.h"
 #include "components/soda/constants.h"
@@ -18,21 +18,24 @@
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
-class MediaDialogViewObserver;
-class MediaNotificationContainerImplView;
-class MediaNotificationListView;
-class MediaNotificationService;
-class NewBadgeLabel;
-class Profile;
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace global_media_controls {
+class MediaItemUIListView;
+class MediaItemUIView;
+}  // namespace global_media_controls
 
 namespace views {
 class Label;
 class ToggleButton;
 }  // namespace views
 
-namespace content {
-class WebContents;
-}  // namespace content
+class MediaDialogViewObserver;
+class MediaNotificationService;
+class NewBadgeLabel;
+class Profile;
 
 // Dialog that shows media controls that control the active media session.
 class MediaDialogView : public views::BubbleDialogDelegateView,
@@ -45,16 +48,17 @@
   MediaDialogView(const MediaDialogView&) = delete;
   MediaDialogView& operator=(const MediaDialogView&) = delete;
 
-  static views::Widget* ShowDialog(views::View* anchor_view,
-                                   MediaNotificationService* service,
-                                   Profile* profile,
-                                   GlobalMediaControlsEntryPoint entry_point);
+  static views::Widget* ShowDialog(
+      views::View* anchor_view,
+      MediaNotificationService* service,
+      Profile* profile,
+      global_media_controls::GlobalMediaControlsEntryPoint entry_point);
   static views::Widget* ShowDialogForPresentationRequest(
       views::View* anchor_view,
       MediaNotificationService* service,
       Profile* profile,
       content::WebContents* contents,
-      GlobalMediaControlsEntryPoint entry_point);
+      global_media_controls::GlobalMediaControlsEntryPoint entry_point);
   static void HideDialog();
   static bool IsShowing();
 
@@ -79,24 +83,24 @@
   void OnMediaItemUIClicked(const std::string& id) override {}
   void OnMediaItemUIDismissed(const std::string& id) override {}
   void OnMediaItemUIDestroyed(const std::string& id) override;
-  void OnAudioSinkChosen(const std::string& id,
-                         const std::string& sink_id) override {}
 
   void AddObserver(MediaDialogViewObserver* observer);
   void RemoveObserver(MediaDialogViewObserver* observer);
 
-  const std::map<const std::string, MediaNotificationContainerImplView*>&
-  GetNotificationsForTesting() const;
+  const std::map<const std::string, global_media_controls::MediaItemUIView*>&
+  GetItemsForTesting() const;
 
-  const MediaNotificationListView* GetListViewForTesting() const;
+  const global_media_controls::MediaItemUIListView* GetListViewForTesting()
+      const;
 
  private:
   friend class MediaDialogViewBrowserTest;
-  MediaDialogView(views::View* anchor_view,
-                  MediaNotificationService* service,
-                  Profile* profile,
-                  content::WebContents* contents,
-                  GlobalMediaControlsEntryPoint entry_point);
+  MediaDialogView(
+      views::View* anchor_view,
+      MediaNotificationService* service,
+      Profile* profile,
+      content::WebContents* contents,
+      global_media_controls::GlobalMediaControlsEntryPoint entry_point);
 
   ~MediaDialogView() override;
 
@@ -126,17 +130,21 @@
                                   speech::LanguageCode language_code) override {
   }
 
+  std::unique_ptr<global_media_controls::MediaItemUIView> BuildMediaItemUIView(
+      const std::string& id,
+      base::WeakPtr<media_message_center::MediaNotificationItem> item);
+
   MediaNotificationService* const service_;
 
   Profile* const profile_;
 
-  MediaNotificationListView* const active_sessions_view_;
+  global_media_controls::MediaItemUIListView* const active_sessions_view_;
 
   base::ObserverList<MediaDialogViewObserver> observers_;
 
   // A map of all containers we're currently observing.
-  std::map<const std::string, MediaNotificationContainerImplView*>
-      observed_containers_;
+  std::map<const std::string, global_media_controls::MediaItemUIView*>
+      observed_items_;
 
   views::View* live_caption_container_ = nullptr;
   // TODO(crbug.com/1055150): Remove live_caption_title_new_badge_ by M93.
@@ -148,7 +156,7 @@
   // opened the dialog for a presentation request. It is nullptr if the dialog
   // is opened from the toolbar.
   content::WebContents* const web_contents_for_presentation_request_ = nullptr;
-  const GlobalMediaControlsEntryPoint entry_point_;
+  const global_media_controls::GlobalMediaControlsEntryPoint entry_point_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_DIALOG_VIEW_H_
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc b/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
index 9853a992..e25aae3 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
@@ -16,8 +16,6 @@
 #include "chrome/browser/ui/global_media_controls/media_toolbar_button_observer.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/global_media_controls/media_dialog_view_observer.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_list_view.h"
 #include "chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/browser/ui/views/user_education/new_badge_label.h"
@@ -26,6 +24,8 @@
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/feature_engagement/public/feature_constants.h"
+#include "components/global_media_controls/public/views/media_item_ui_list_view.h"
+#include "components/global_media_controls/public/views/media_item_ui_view.h"
 #include "components/media_message_center/media_notification_view_impl.h"
 #include "components/media_router/browser/presentation/web_contents_presentation_manager.h"
 #include "components/media_router/browser/test/mock_media_router.h"
@@ -120,12 +120,12 @@
     Wait();
   }
 
-  void WaitForNotificationCount(int count) {
-    if (GetNotificationCount() == count)
+  void WaitForItemCount(int count) {
+    if (GetItemCount() == count)
       return;
 
-    waiting_for_notification_count_ = true;
-    expected_notification_count_ = count;
+    waiting_for_item_count_ = true;
+    expected_item_count_ = count;
     observed_dialog_ = MediaDialogView::GetDialogViewForTesting();
     observed_dialog_->AddObserver(this);
     Wait();
@@ -154,14 +154,14 @@
     MaybeStopWaiting();
   }
 
-  void CheckNotificationCount() {
-    if (!waiting_for_notification_count_)
+  void CheckItemCount() {
+    if (!waiting_for_item_count_)
       return;
 
-    if (GetNotificationCount() != expected_notification_count_)
+    if (GetItemCount() != expected_item_count_)
       return;
 
-    waiting_for_notification_count_ = false;
+    waiting_for_item_count_ = false;
     MaybeStopWaiting();
   }
 
@@ -181,8 +181,7 @@
       return;
 
     if (!waiting_for_dialog_opened_ && !waiting_for_button_shown_ &&
-        !waiting_for_dialog_to_contain_text_ &&
-        !waiting_for_notification_count_ &&
+        !waiting_for_dialog_to_contain_text_ && !waiting_for_item_count_ &&
         !waiting_for_pip_visibility_changed_) {
       run_loop_->Quit();
     }
@@ -194,14 +193,13 @@
     run_loop_->Run();
   }
 
-  // Checks the title and artist of each notification in the dialog to see if
+  // Checks the title and artist of each item in the dialog to see if
   // |text| is contained anywhere in the dialog.
   bool DialogContainsText(const std::u16string& text) {
-    for (const auto& notification_pair :
-         MediaDialogView::GetDialogViewForTesting()
-             ->GetNotificationsForTesting()) {
+    for (const auto& item_pair :
+         MediaDialogView::GetDialogViewForTesting()->GetItemsForTesting()) {
       const media_message_center::MediaNotificationViewImpl* view =
-          notification_pair.second->view_for_testing();
+          item_pair.second->view_for_testing();
       if (view->title_label_for_testing()->GetText().find(text) !=
               std::string::npos ||
           view->artist_label_for_testing()->GetText().find(text) !=
@@ -214,19 +212,19 @@
   }
 
   bool CheckPictureInPictureButtonVisibility(bool visible) {
-    const auto notification_pair = MediaDialogView::GetDialogViewForTesting()
-                                       ->GetNotificationsForTesting()
-                                       .begin();
+    const auto item_pair = MediaDialogView::GetDialogViewForTesting()
+                               ->GetItemsForTesting()
+                               .begin();
     const media_message_center::MediaNotificationViewImpl* view =
-        notification_pair->second->view_for_testing();
+        item_pair->second->view_for_testing();
 
     return view->picture_in_picture_button_for_testing()->GetVisible() ==
            visible;
   }
 
-  int GetNotificationCount() {
+  int GetItemCount() {
     return MediaDialogView::GetDialogViewForTesting()
-        ->GetNotificationsForTesting()
+        ->GetItemsForTesting()
         .size();
   }
 
@@ -235,13 +233,13 @@
 
   bool waiting_for_dialog_opened_ = false;
   bool waiting_for_button_shown_ = false;
-  bool waiting_for_notification_count_ = false;
+  bool waiting_for_item_count_ = false;
   bool waiting_for_pip_visibility_changed_ = false;
 
   MediaDialogView* observed_dialog_ = nullptr;
   bool waiting_for_dialog_to_contain_text_ = false;
   std::u16string expected_text_;
-  int expected_notification_count_ = 0;
+  int expected_item_count_ = 0;
   bool expected_pip_visibility_ = false;
 };
 
@@ -470,8 +468,8 @@
         .WaitForDialogToContainText(text);
   }
 
-  void WaitForNotificationCount(int count) {
-    MediaToolbarButtonWatcher(GetToolbarIcon()).WaitForNotificationCount(count);
+  void WaitForItemCount(int count) {
+    MediaToolbarButtonWatcher(GetToolbarIcon()).WaitForItemCount(count);
   }
 
   void WaitForPictureInPictureButtonVisibility(bool visible) {
@@ -511,12 +509,11 @@
     ClickButton(live_caption_button);
   }
 
-  void ClickNotificationByTitle(const std::u16string& title) {
+  void ClickItemByTitle(const std::u16string& title) {
     ASSERT_TRUE(MediaDialogView::IsShowing());
-    MediaNotificationContainerImplView* notification =
-        GetNotificationByTitle(title);
-    ASSERT_NE(nullptr, notification);
-    ClickButton(notification);
+    global_media_controls::MediaItemUIView* item = GetItemByTitle(title);
+    ASSERT_NE(nullptr, item);
+    ClickButton(item);
   }
 
   content::WebContents* GetActiveWebContents() {
@@ -529,13 +526,13 @@
                                  ->GetListViewForTesting()
                                  ->contents()
                                  ->children()) {
-      MediaNotificationContainerImplView* notification =
-          static_cast<MediaNotificationContainerImplView*>(view);
+      global_media_controls::MediaItemUIView* item =
+          static_cast<global_media_controls::MediaItemUIView*>(view);
 
-      if (seen_paused && notification->is_playing_for_testing())
+      if (seen_paused && item->is_playing_for_testing())
         return false;
 
-      if (!seen_paused && !notification->is_playing_for_testing())
+      if (!seen_paused && !item->is_playing_for_testing())
         seen_paused = true;
     }
 
@@ -612,16 +609,15 @@
     return nullptr;
   }
 
-  // Finds a MediaNotificationContainerImplView by title.
-  MediaNotificationContainerImplView* GetNotificationByTitle(
+  // Finds a global_media_controls::MediaItemUIView by title.
+  global_media_controls::MediaItemUIView* GetItemByTitle(
       const std::u16string& title) {
-    for (const auto& notification_pair :
-         MediaDialogView::GetDialogViewForTesting()
-             ->GetNotificationsForTesting()) {
+    for (const auto& item_pair :
+         MediaDialogView::GetDialogViewForTesting()->GetItemsForTesting()) {
       const media_message_center::MediaNotificationViewImpl* view =
-          notification_pair.second->view_for_testing();
+          item_pair.second->view_for_testing();
       if (view->title_label_for_testing()->GetText() == title)
-        return notification_pair.second;
+        return item_pair.second;
     }
     return nullptr;
   }
@@ -723,7 +719,7 @@
 
   // The view containing playback controls should not be mirrored.
   EXPECT_FALSE(MediaDialogView::GetDialogViewForTesting()
-                   ->GetNotificationsForTesting()
+                   ->GetItemsForTesting()
                    .begin()
                    ->second->view_for_testing()
                    ->playback_button_container_for_testing()
@@ -788,7 +784,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaDialogViewBrowserTest,
-                       ClickingOnNotificationGoesBackToTab) {
+                       ClickingOnItemGoesBackToTab) {
   // Open a tab and play media.
   OpenTestURL();
   StartPlayback();
@@ -818,8 +814,8 @@
   // The second tab should be the active tab.
   EXPECT_EQ(second_web_contents, GetActiveWebContents());
 
-  // Clicking the first notification should make the first tab active.
-  ClickNotificationByTitle(u"Big Buck Bunny");
+  // Clicking the first item should make the first tab active.
+  ClickItemByTitle(u"Big Buck Bunny");
 
   // Allow the MediaSessionNotificationItem to flush its message to the
   // MediaSessionImpl. There isn't currently a clean way for us to access the
@@ -850,7 +846,7 @@
   EXPECT_TRUE(WaitForDialogOpened());
   WaitForDialogToContainText(
       base::UTF8ToUTF16(route_description + " \xC2\xB7 " + sink_name));
-  WaitForNotificationCount(1);
+  WaitForItemCount(1);
 }
 
 #if defined(OS_MAC) && defined(ARCH_CPU_ARM64)
diff --git a/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_observer.h b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_observer.h
new file mode 100644
index 0000000..e32644a
--- /dev/null
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_observer.h
@@ -0,0 +1,22 @@
+// 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_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_DEVICE_SELECTOR_OBSERVER_H_
+#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_DEVICE_SELECTOR_OBSERVER_H_
+
+#include <map>
+
+#include "base/observer_list_types.h"
+
+class DeviceEntryUI;
+
+class MediaItemUIDeviceSelectorObserver : public base::CheckedObserver {
+ public:
+  // Called by MediaNotificationDeviceSelector view when available devices
+  // changed.
+  virtual void OnMediaItemUIDeviceSelectorUpdated(
+      const std::map<int, DeviceEntryUI*>& device_entries_map) = 0;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_DEVICE_SELECTOR_OBSERVER_H_
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.cc b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc
similarity index 74%
rename from chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.cc
rename to chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc
index 792fa7b..fced918 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc
@@ -2,19 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h"
 
 #include "base/bind.h"
 #include "base/containers/contains.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/global_media_controls/media_item_ui_device_selector_delegate.h"
 #include "chrome/browser/ui/media_router/cast_dialog_model.h"
 #include "chrome/browser/ui/media_router/ui_media_sink.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_device_selector_observer.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_delegate.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_observer.h"
 #include "chrome/browser/ui/views/media_router/cast_dialog_sink_button.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/global_media_controls/public/views/media_item_ui_view.h"
 #include "components/media_message_center/media_notification_item.h"
 #include "components/media_router/browser/media_router_metrics.h"
 #include "components/media_router/common/media_sink.h"
@@ -38,7 +39,7 @@
 
 namespace {
 
-// Constants for the MediaNotificationDeviceSelectorView
+// Constants for the MediaItemUIDeviceSelectorView
 constexpr gfx::Insets kExpandButtonStripInsets{6, 15};
 constexpr gfx::Size kExpandButtonStripSize{400, 30};
 constexpr gfx::Insets kExpandButtonBorderInsets{4, 8};
@@ -50,19 +51,20 @@
 const int kAudioDevicesCountHistogramMax = 30;
 
 media_router::MediaRouterDialogOpenOrigin ConvertToOrigin(
-    GlobalMediaControlsEntryPoint entry_point) {
+    global_media_controls::GlobalMediaControlsEntryPoint entry_point) {
   switch (entry_point) {
-    case GlobalMediaControlsEntryPoint::kPresentation:
+    case global_media_controls::GlobalMediaControlsEntryPoint::kPresentation:
       return media_router::MediaRouterDialogOpenOrigin::PAGE;
-    case GlobalMediaControlsEntryPoint::kSystemTray:
+    case global_media_controls::GlobalMediaControlsEntryPoint::kSystemTray:
       return media_router::MediaRouterDialogOpenOrigin::SYSTEM_TRAY;
-    case GlobalMediaControlsEntryPoint::kToolbarIcon:
+    case global_media_controls::GlobalMediaControlsEntryPoint::kToolbarIcon:
       return media_router::MediaRouterDialogOpenOrigin::TOOLBAR;
   }
 }
 
-void RecordCastDeviceCountMetrics(GlobalMediaControlsEntryPoint entry_point,
-                                  std::vector<CastDeviceEntryView*> entries) {
+void RecordCastDeviceCountMetrics(
+    global_media_controls::GlobalMediaControlsEntryPoint entry_point,
+    std::vector<CastDeviceEntryView*> entries) {
   MediaRouterMetrics::RecordDeviceCount(entries.size());
 
   std::map<MediaRouteProviderId, std::map<bool, int>> counts = {
@@ -132,20 +134,14 @@
       delegate_->GetIconLabelBubbleSurroundingForegroundColor()));
 }
 
-MediaNotificationDeviceSelectorView::MediaNotificationDeviceSelectorView(
-    MediaNotificationDeviceSelectorViewDelegate* delegate,
+MediaItemUIDeviceSelectorView::MediaItemUIDeviceSelectorView(
+    const std::string& item_id,
+    MediaItemUIDeviceSelectorDelegate* delegate,
     std::unique_ptr<media_router::CastDialogController> cast_controller,
     bool has_audio_output,
-    const std::string& current_device_id,
-    const SkColor& foreground_color,
-    const SkColor& background_color,
-    GlobalMediaControlsEntryPoint entry_point,
+    global_media_controls::GlobalMediaControlsEntryPoint entry_point,
     bool show_expand_button)
-    : delegate_(delegate),
-      current_device_id_(current_device_id),
-      foreground_color_(foreground_color),
-      background_color_(background_color),
-      entry_point_(entry_point) {
+    : item_id_(item_id), delegate_(delegate), entry_point_(entry_point) {
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical));
 
@@ -162,9 +158,9 @@
 
   expand_button_ = expand_button_strip_->AddChildView(
       std::make_unique<ExpandDeviceSelectorButton>(this));
-  expand_button_->SetCallback(base::BindRepeating(
-      &MediaNotificationDeviceSelectorView::ExpandButtonPressed,
-      base::Unretained(this)));
+  expand_button_->SetCallback(
+      base::BindRepeating(&MediaItemUIDeviceSelectorView::ExpandButtonPressed,
+                          base::Unretained(this)));
 
   if (!show_expand_button)
     expand_button_strip_->SetVisible(false);
@@ -175,7 +171,8 @@
           views::BoxLayout::Orientation::kVertical));
   device_entry_views_container_->SetVisible(false);
 
-  if (entry_point_ == GlobalMediaControlsEntryPoint::kPresentation) {
+  if (entry_point_ ==
+      global_media_controls::GlobalMediaControlsEntryPoint::kPresentation) {
     ShowDevices();
   }
   SetBackground(views::CreateSolidBackground(background_color_));
@@ -197,7 +194,7 @@
   }
 }
 
-void MediaNotificationDeviceSelectorView::UpdateCurrentAudioDevice(
+void MediaItemUIDeviceSelectorView::UpdateCurrentAudioDevice(
     const std::string& current_device_id) {
   if (current_audio_device_entry_view_)
     current_audio_device_entry_view_->SetHighlighted(false);
@@ -222,7 +219,7 @@
   current_audio_device_entry_view_->Layout();
 }
 
-MediaNotificationDeviceSelectorView::~MediaNotificationDeviceSelectorView() {
+MediaItemUIDeviceSelectorView::~MediaItemUIDeviceSelectorView() {
   audio_device_subscription_ = {};
 
   // If this metric has not been recorded during the lifetime of this view, it
@@ -235,7 +232,7 @@
   }
 }
 
-void MediaNotificationDeviceSelectorView::UpdateAvailableAudioDevices(
+void MediaItemUIDeviceSelectorView::UpdateAvailableAudioDevices(
     const media::AudioDeviceDescriptions& device_descriptions) {
   RemoveDevicesOfType(DeviceEntryUIType::kAudio);
   current_audio_device_entry_view_ = nullptr;
@@ -244,8 +241,8 @@
   for (auto description : device_descriptions) {
     auto device_entry_view = std::make_unique<AudioDeviceEntryView>(
         base::BindRepeating(
-            &MediaNotificationDeviceSelectorViewDelegate::OnAudioSinkChosen,
-            base::Unretained(delegate_), description.unique_id),
+            &MediaItemUIDeviceSelectorDelegate::OnAudioSinkChosen,
+            base::Unretained(delegate_), item_id_, description.unique_id),
         foreground_color_, background_color_, description.unique_id,
         description.device_name);
     device_entry_view->set_tag(next_tag_++);
@@ -264,12 +261,16 @@
 
   UpdateVisibility();
   for (auto& observer : observers_)
-    observer.OnMediaNotificationDeviceSelectorUpdated(device_entry_ui_map_);
+    observer.OnMediaItemUIDeviceSelectorUpdated(device_entry_ui_map_);
 }
 
-void MediaNotificationDeviceSelectorView::OnColorsChanged(
-    const SkColor& foreground_color,
-    const SkColor& background_color) {
+void MediaItemUIDeviceSelectorView::SetMediaItemUIView(
+    global_media_controls::MediaItemUIView* view) {
+  media_item_ui_ = view;
+}
+
+void MediaItemUIDeviceSelectorView::OnColorsChanged(SkColor foreground_color,
+                                                    SkColor background_color) {
   foreground_color_ = foreground_color;
   background_color_ = background_color;
 
@@ -283,33 +284,34 @@
   SchedulePaint();
 }
 
-SkColor MediaNotificationDeviceSelectorView::
-    GetIconLabelBubbleSurroundingForegroundColor() const {
+SkColor
+MediaItemUIDeviceSelectorView::GetIconLabelBubbleSurroundingForegroundColor()
+    const {
   return foreground_color_;
 }
 
-SkColor MediaNotificationDeviceSelectorView::GetIconLabelBubbleBackgroundColor()
+SkColor MediaItemUIDeviceSelectorView::GetIconLabelBubbleBackgroundColor()
     const {
   return background_color_;
 }
 
-views::Button*
-MediaNotificationDeviceSelectorView::GetExpandButtonForTesting() {
+views::Button* MediaItemUIDeviceSelectorView::GetExpandButtonForTesting() {
   return expand_button_;
 }
 
-std::string MediaNotificationDeviceSelectorView::GetEntryLabelForTesting(
+std::string MediaItemUIDeviceSelectorView::GetEntryLabelForTesting(
     views::View* entry_view) {
   return GetDeviceEntryUI(entry_view)->device_name();
 }
 
-bool MediaNotificationDeviceSelectorView::GetEntryIsHighlightedForTesting(
+bool MediaItemUIDeviceSelectorView::GetEntryIsHighlightedForTesting(
     views::View* entry_view) {
-  return GetDeviceEntryUI(entry_view)->GetEntryIsHighlightedForTesting();
+  return GetDeviceEntryUI(entry_view)
+      ->GetEntryIsHighlightedForTesting();  // IN-TEST
 }
 
 std::vector<media_router::CastDialogSinkButton*>
-MediaNotificationDeviceSelectorView::GetCastSinkButtonsForTesting() {
+MediaItemUIDeviceSelectorView::GetCastSinkButtonsForTesting() {
   std::vector<media_router::CastDialogSinkButton*> buttons;
   for (auto* view : device_entry_views_container_->children()) {
     if (GetDeviceEntryUI(view)->GetType() == DeviceEntryUIType::kCast) {
@@ -319,7 +321,7 @@
   return buttons;
 }
 
-void MediaNotificationDeviceSelectorView::ShowDevices() {
+void MediaItemUIDeviceSelectorView::ShowDevices() {
   DCHECK(!is_expanded_);
   is_expanded_ = true;
   NotifyAccessibilityEvent(ax::mojom::Event::kExpandedChanged, true);
@@ -340,7 +342,7 @@
   PreferredSizeChanged();
 }
 
-void MediaNotificationDeviceSelectorView::HideDevices() {
+void MediaItemUIDeviceSelectorView::HideDevices() {
   DCHECK(is_expanded_);
   is_expanded_ = false;
   NotifyAccessibilityEvent(ax::mojom::Event::kExpandedChanged, true);
@@ -351,7 +353,7 @@
   PreferredSizeChanged();
 }
 
-void MediaNotificationDeviceSelectorView::UpdateVisibility() {
+void MediaItemUIDeviceSelectorView::UpdateVisibility() {
   SetVisible(ShouldBeVisible());
 
   if (!has_expand_button_been_shown_ && GetVisible()) {
@@ -359,10 +361,11 @@
     has_expand_button_been_shown_ = true;
   }
 
-  delegate_->OnDeviceSelectorViewSizeChanged();
+  if (media_item_ui_)
+    media_item_ui_->OnDeviceSelectorViewSizeChanged();
 }
 
-bool MediaNotificationDeviceSelectorView::ShouldBeVisible() const {
+bool MediaItemUIDeviceSelectorView::ShouldBeVisible() const {
   if (has_cast_device_)
     return true;
   if (!is_audio_device_switching_enabled_)
@@ -385,15 +388,17 @@
   return device_entry_views_container_->children().size() > 2;
 }
 
-void MediaNotificationDeviceSelectorView::ExpandButtonPressed() {
+void MediaItemUIDeviceSelectorView::ExpandButtonPressed() {
   if (is_expanded_)
     HideDevices();
   else
     ShowDevices();
-  delegate_->OnDeviceSelectorViewSizeChanged();
+
+  if (media_item_ui_)
+    media_item_ui_->OnDeviceSelectorViewSizeChanged();
 }
 
-void MediaNotificationDeviceSelectorView::UpdateIsAudioDeviceSwitchingEnabled(
+void MediaItemUIDeviceSelectorView::UpdateIsAudioDeviceSwitchingEnabled(
     bool enabled) {
   if (enabled == is_audio_device_switching_enabled_)
     return;
@@ -402,7 +407,7 @@
   UpdateVisibility();
 }
 
-void MediaNotificationDeviceSelectorView::RemoveDevicesOfType(
+void MediaItemUIDeviceSelectorView::RemoveDevicesOfType(
     DeviceEntryUIType type) {
   std::vector<views::View*> views_to_remove;
   for (auto* view : device_entry_views_container_->children()) {
@@ -417,14 +422,14 @@
   }
 }
 
-DeviceEntryUI* MediaNotificationDeviceSelectorView::GetDeviceEntryUI(
+DeviceEntryUI* MediaItemUIDeviceSelectorView::GetDeviceEntryUI(
     views::View* view) const {
   auto it = device_entry_ui_map_.find(static_cast<views::Button*>(view)->tag());
   DCHECK(it != device_entry_ui_map_.end());
   return it->second;
 }
 
-void MediaNotificationDeviceSelectorView::OnModelUpdated(
+void MediaItemUIDeviceSelectorView::OnModelUpdated(
     const media_router::CastDialogModel& model) {
   RemoveDevicesOfType(DeviceEntryUIType::kCast);
   has_cast_device_ = false;
@@ -435,9 +440,8 @@
     }
     has_cast_device_ = true;
     auto device_entry_view = std::make_unique<CastDeviceEntryView>(
-        base::BindRepeating(
-            &MediaNotificationDeviceSelectorView::StartCastSession,
-            base::Unretained(this)),
+        base::BindRepeating(&MediaItemUIDeviceSelectorView::StartCastSession,
+                            base::Unretained(this)),
         foreground_color_, background_color_, sink);
     device_entry_view->set_tag(next_tag_++);
     device_entry_ui_map_[device_entry_view->tag()] = device_entry_view.get();
@@ -451,37 +455,37 @@
 
   UpdateVisibility();
   for (auto& observer : observers_)
-    observer.OnMediaNotificationDeviceSelectorUpdated(device_entry_ui_map_);
+    observer.OnMediaItemUIDeviceSelectorUpdated(device_entry_ui_map_);
 }
 
-void MediaNotificationDeviceSelectorView::OnControllerInvalidated() {
+void MediaItemUIDeviceSelectorView::OnControllerInvalidated() {
   cast_controller_.reset();
 }
 
-void MediaNotificationDeviceSelectorView::OnDeviceSelected(int tag) {
+void MediaItemUIDeviceSelectorView::OnDeviceSelected(int tag) {
   auto it = device_entry_ui_map_.find(tag);
   DCHECK(it != device_entry_ui_map_.end());
 
   if (it->second->GetType() == DeviceEntryUIType::kAudio)
-    delegate_->OnAudioSinkChosen(it->second->raw_device_id());
+    delegate_->OnAudioSinkChosen(item_id_, it->second->raw_device_id());
   else
     StartCastSession(static_cast<CastDeviceEntryView*>(it->second));
 }
 
-void MediaNotificationDeviceSelectorView::OnDropdownButtonClicked() {
+void MediaItemUIDeviceSelectorView::OnDropdownButtonClicked() {
   ExpandButtonPressed();
 }
 
-bool MediaNotificationDeviceSelectorView::IsDeviceSelectorExpanded() {
+bool MediaItemUIDeviceSelectorView::IsDeviceSelectorExpanded() {
   return is_expanded_;
 }
 
-void MediaNotificationDeviceSelectorView::AddObserver(
-    MediaNotificationDeviceSelectorObserver* observer) {
+void MediaItemUIDeviceSelectorView::AddObserver(
+    MediaItemUIDeviceSelectorObserver* observer) {
   observers_.AddObserver(observer);
 }
 
-void MediaNotificationDeviceSelectorView::StartCastSession(
+void MediaItemUIDeviceSelectorView::StartCastSession(
     CastDeviceEntryView* entry) {
   if (!cast_controller_)
     return;
@@ -513,7 +517,7 @@
     }
   }
 }
-void MediaNotificationDeviceSelectorView::DoStartCastSession(
+void MediaItemUIDeviceSelectorView::DoStartCastSession(
     media_router::UIMediaSink sink) {
   DCHECK(base::Contains(sink.cast_modes,
                         media_router::MediaCastMode::PRESENTATION));
@@ -522,22 +526,24 @@
   RecordStartCastingMetrics(sink.icon_type);
 }
 
-void MediaNotificationDeviceSelectorView::RecordStartCastingMetrics(
+void MediaItemUIDeviceSelectorView::RecordStartCastingMetrics(
     media_router::SinkIconType sink_icon_type) {
   MediaRouterMetrics::RecordMediaSinkTypeForGlobalMediaControls(sink_icon_type);
   RecordStartCastingWithCastAndDialPresent(sink_icon_type);
 
-  GlobalMediaControlsCastActionAndEntryPoint action;
+  global_media_controls::GlobalMediaControlsCastActionAndEntryPoint action;
   switch (entry_point_) {
-    case GlobalMediaControlsEntryPoint::kToolbarIcon:
-      action = GlobalMediaControlsCastActionAndEntryPoint::kStartViaToolbarIcon;
+    case global_media_controls::GlobalMediaControlsEntryPoint::kToolbarIcon:
+      action = global_media_controls::
+          GlobalMediaControlsCastActionAndEntryPoint::kStartViaToolbarIcon;
       break;
-    case GlobalMediaControlsEntryPoint::kPresentation:
-      action =
+    case global_media_controls::GlobalMediaControlsEntryPoint::kPresentation:
+      action = global_media_controls::
           GlobalMediaControlsCastActionAndEntryPoint::kStartViaPresentation;
       break;
-    case GlobalMediaControlsEntryPoint::kSystemTray:
-      action = GlobalMediaControlsCastActionAndEntryPoint::kStartViaSystemTray;
+    case global_media_controls::GlobalMediaControlsEntryPoint::kSystemTray:
+      action = global_media_controls::
+          GlobalMediaControlsCastActionAndEntryPoint::kStartViaSystemTray;
       break;
   }
   base::UmaHistogramEnumeration(
@@ -545,8 +551,8 @@
       action);
 }
 
-void MediaNotificationDeviceSelectorView::
-    RecordStartCastingWithCastAndDialPresent(media_router::SinkIconType type) {
+void MediaItemUIDeviceSelectorView::RecordStartCastingWithCastAndDialPresent(
+    media_router::SinkIconType type) {
   bool has_cast = false;
   bool has_dial = false;
   for (views::View* view : device_entry_views_container_->children()) {
@@ -575,17 +581,20 @@
   }
 }
 
-void MediaNotificationDeviceSelectorView::RecordStopCastingMetrics() {
-  GlobalMediaControlsCastActionAndEntryPoint action;
+void MediaItemUIDeviceSelectorView::RecordStopCastingMetrics() {
+  global_media_controls::GlobalMediaControlsCastActionAndEntryPoint action;
   switch (entry_point_) {
-    case GlobalMediaControlsEntryPoint::kToolbarIcon:
-      action = GlobalMediaControlsCastActionAndEntryPoint::kStopViaToolbarIcon;
+    case global_media_controls::GlobalMediaControlsEntryPoint::kToolbarIcon:
+      action = global_media_controls::
+          GlobalMediaControlsCastActionAndEntryPoint::kStopViaToolbarIcon;
       break;
-    case GlobalMediaControlsEntryPoint::kPresentation:
-      action = GlobalMediaControlsCastActionAndEntryPoint::kStopViaPresentation;
+    case global_media_controls::GlobalMediaControlsEntryPoint::kPresentation:
+      action = global_media_controls::
+          GlobalMediaControlsCastActionAndEntryPoint::kStopViaPresentation;
       break;
-    case GlobalMediaControlsEntryPoint::kSystemTray:
-      action = GlobalMediaControlsCastActionAndEntryPoint::kStopViaSystemTray;
+    case global_media_controls::GlobalMediaControlsEntryPoint::kSystemTray:
+      action = global_media_controls::
+          GlobalMediaControlsCastActionAndEntryPoint::kStopViaSystemTray;
       break;
   }
   base::UmaHistogramEnumeration(
@@ -593,16 +602,15 @@
       action);
 }
 
-void MediaNotificationDeviceSelectorView::RecordCastDeviceCountAfterDelay() {
+void MediaItemUIDeviceSelectorView::RecordCastDeviceCountAfterDelay() {
   content::GetUIThreadTaskRunner({})->PostDelayedTask(
       FROM_HERE,
-      base::BindOnce(
-          &MediaNotificationDeviceSelectorView::RecordCastDeviceCount,
-          weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&MediaItemUIDeviceSelectorView::RecordCastDeviceCount,
+                     weak_ptr_factory_.GetWeakPtr()),
       MediaRouterMetrics::kDeviceCountMetricDelay);
 }
 
-void MediaNotificationDeviceSelectorView::RecordCastDeviceCount() {
+void MediaItemUIDeviceSelectorView::RecordCastDeviceCount() {
   std::vector<CastDeviceEntryView*> entries;
   for (views::View* view : device_entry_views_container_->children()) {
     DeviceEntryUI* entry = GetDeviceEntryUI(view);
@@ -613,21 +621,21 @@
   RecordCastDeviceCountMetrics(entry_point_, entries);
 }
 
-void MediaNotificationDeviceSelectorView::RegisterAudioDeviceCallbacks() {
+void MediaItemUIDeviceSelectorView::RegisterAudioDeviceCallbacks() {
   // Get a list of the connected audio output devices.
   audio_device_subscription_ =
       delegate_->RegisterAudioOutputDeviceDescriptionsCallback(
           base::BindRepeating(
-              &MediaNotificationDeviceSelectorView::UpdateAvailableAudioDevices,
+              &MediaItemUIDeviceSelectorView::UpdateAvailableAudioDevices,
               weak_ptr_factory_.GetWeakPtr()));
 
   // Get the availability of audio output device switching.
   is_device_switching_enabled_subscription_ =
       delegate_->RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
-          base::BindRepeating(&MediaNotificationDeviceSelectorView::
-                                  UpdateIsAudioDeviceSwitchingEnabled,
-                              weak_ptr_factory_.GetWeakPtr()));
+          item_id_, base::BindRepeating(&MediaItemUIDeviceSelectorView::
+                                            UpdateIsAudioDeviceSwitchingEnabled,
+                                        weak_ptr_factory_.GetWeakPtr()));
 }
 
-BEGIN_METADATA(MediaNotificationDeviceSelectorView, views::View)
+BEGIN_METADATA(MediaItemUIDeviceSelectorView, views::View)
 END_METADATA
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h
similarity index 63%
rename from chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h
rename to chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h
index 2a2b3ec..cb996f9 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h
@@ -2,17 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_SELECTOR_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_SELECTOR_VIEW_H_
+#ifndef CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_DEVICE_SELECTOR_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_DEVICE_SELECTOR_VIEW_H_
 
 #include "base/callback_list.h"
 #include "base/gtest_prod_util.h"
 #include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
 #include "chrome/browser/ui/media_router/cast_dialog_controller.h"
-#include "chrome/browser/ui/views/global_media_controls/global_media_controls_types.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.h"
 #include "chrome/browser/ui/views/global_media_controls/media_notification_device_entry_ui.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_footer_view.h"
 #include "chrome/browser/ui/views/location_bar/icon_label_bubble_view.h"
+#include "components/global_media_controls/public/constants.h"
+#include "components/global_media_controls/public/views/media_item_ui_device_selector.h"
 #include "components/media_router/common/media_sink.h"
 #include "media/audio/audio_device_description.h"
 #include "ui/base/metadata/metadata_header_macros.h"
@@ -29,37 +30,42 @@
     "Media.GlobalMediaControls.DeviceSelectorOpened";
 }  // anonymous namespace
 
+namespace global_media_controls {
+class MediaItemUIView;
+}  // namespace global_media_controls
+
 namespace media_router {
 class CastDialogSinkButton;
 }
-class MediaNotificationDeviceSelectorViewDelegate;
-class MediaNotificationDeviceSelectorObserver;
+class MediaItemUIDeviceSelectorDelegate;
+class MediaItemUIDeviceSelectorObserver;
 
-class MediaNotificationDeviceSelectorView
-    : public views::View,
+class MediaItemUIDeviceSelectorView
+    : public global_media_controls::MediaItemUIDeviceSelector,
       public IconLabelBubbleView::Delegate,
       public media_router::CastDialogController::Observer,
-      public MediaNotificationFooterView::Delegate {
+      public MediaItemUIFooterView::Delegate {
  public:
-  METADATA_HEADER(MediaNotificationDeviceSelectorView);
-  MediaNotificationDeviceSelectorView(
-      MediaNotificationDeviceSelectorViewDelegate* delegate,
+  METADATA_HEADER(MediaItemUIDeviceSelectorView);
+  MediaItemUIDeviceSelectorView(
+      const std::string& item_id,
+      MediaItemUIDeviceSelectorDelegate* delegate,
       std::unique_ptr<media_router::CastDialogController> cast_controller,
       bool has_audio_output,
-      const std::string& current_device_id,
-      const SkColor& foreground_color,
-      const SkColor& background_color,
-      GlobalMediaControlsEntryPoint entry_point,
+      global_media_controls::GlobalMediaControlsEntryPoint entry_point,
       bool show_expand_button = true);
-  ~MediaNotificationDeviceSelectorView() override;
+  ~MediaItemUIDeviceSelectorView() override;
 
   // Called when audio output devices are discovered.
   void UpdateAvailableAudioDevices(
       const media::AudioDeviceDescriptions& device_descriptions);
-  // Called when an audio device switch has occurred
-  void UpdateCurrentAudioDevice(const std::string& current_device_id);
-  void OnColorsChanged(const SkColor& foreground_color,
-                       const SkColor& background_color);
+
+  // global_media_controls::MediaItemUIDeviceSelector:
+  void SetMediaItemUIView(
+      global_media_controls::MediaItemUIView* view) override;
+  void OnColorsChanged(SkColor foreground_color,
+                       SkColor background_color) override;
+  void UpdateCurrentAudioDevice(const std::string& current_device_id) override;
 
   // Called when the audio device switching has become enabled or disabled.
   void UpdateIsAudioDeviceSwitchingEnabled(bool enabled);
@@ -72,12 +78,12 @@
   void OnModelUpdated(const media_router::CastDialogModel& model) override;
   void OnControllerInvalidated() override;
 
-  // MediaNotificationFooterview::Delegate
+  // MediaItemUIFooterView::Delegate
   void OnDeviceSelected(int tag) override;
   void OnDropdownButtonClicked() override;
   bool IsDeviceSelectorExpanded() override;
 
-  void AddObserver(MediaNotificationDeviceSelectorObserver* observer);
+  void AddObserver(MediaItemUIDeviceSelectorObserver* observer);
 
   views::Button* GetExpandButtonForTesting();
   std::string GetEntryLabelForTesting(views::View* entry_view);
@@ -86,27 +92,27 @@
   GetCastSinkButtonsForTesting();
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            DeviceButtonsCreated);
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            ExpandButtonOpensEntryContainer);
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            DeviceEntryContainerVisibility);
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            AudioDeviceButtonClickNotifiesContainer);
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            CurrentAudioDeviceHighlighted);
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            AudioDeviceHighlightedOnChange);
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            AudioDeviceButtonsChange);
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            AudioDevicesCountHistogramRecorded);
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            DeviceSelectorOpenedHistogramRecorded);
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            CastDeviceButtonClickStartsCasting);
-  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+  FRIEND_TEST_ALL_PREFIXES(MediaItemUIDeviceSelectorViewTest,
                            CastDeviceButtonClickClearsIssue);
 
   void UpdateVisibility();
@@ -132,10 +138,13 @@
   bool is_expanded_ = false;
   bool is_audio_device_switching_enabled_ = false;
   bool has_cast_device_ = false;
-  MediaNotificationDeviceSelectorViewDelegate* const delegate_;
-  std::string current_device_id_;
-  SkColor foreground_color_, background_color_;
-  GlobalMediaControlsEntryPoint const entry_point_;
+  const std::string item_id_;
+  MediaItemUIDeviceSelectorDelegate* const delegate_;
+  std::string current_device_id_ =
+      media::AudioDeviceDescription::kDefaultDeviceId;
+  SkColor foreground_color_ = global_media_controls::kDefaultForegroundColor;
+  SkColor background_color_ = global_media_controls::kDefaultBackgroundColor;
+  global_media_controls::GlobalMediaControlsEntryPoint const entry_point_;
 
   // Child views
   AudioDeviceEntryView* current_audio_device_entry_view_ = nullptr;
@@ -146,17 +155,18 @@
   base::CallbackListSubscription audio_device_subscription_;
   base::CallbackListSubscription is_device_switching_enabled_subscription_;
 
+  global_media_controls::MediaItemUIView* media_item_ui_ = nullptr;
+
   std::unique_ptr<media_router::CastDialogController> cast_controller_;
 
-  base::ObserverList<MediaNotificationDeviceSelectorObserver> observers_;
+  base::ObserverList<MediaItemUIDeviceSelectorObserver> observers_;
 
   // Each button has a unique tag, which is used to look up DeviceEntryUI* in
   // |device_entry_ui_map_|.
   int next_tag_ = 0;
   std::map<int, DeviceEntryUI*> device_entry_ui_map_;
 
-  base::WeakPtrFactory<MediaNotificationDeviceSelectorView> weak_ptr_factory_{
-      this};
+  base::WeakPtrFactory<MediaItemUIDeviceSelectorView> weak_ptr_factory_{this};
 };
 
-#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_SELECTOR_VIEW_H_
+#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_DEVICE_SELECTOR_VIEW_H_
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_unittest.cc b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view_unittest.cc
similarity index 82%
rename from chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_unittest.cc
rename to chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view_unittest.cc
index a22de69..dc6b351 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_unittest.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view_unittest.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 "chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h"
 
 #include <memory>
 #include <string>
@@ -15,10 +15,10 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
+#include "chrome/browser/ui/global_media_controls/media_item_ui_device_selector_delegate.h"
 #include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
 #include "chrome/browser/ui/global_media_controls/media_notification_service.h"
 #include "chrome/browser/ui/media_router/cast_dialog_model.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_delegate.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "media/audio/audio_device_description.h"
 #include "media/base/media_switches.h"
@@ -34,10 +34,9 @@
 using testing::_;
 using testing::NiceMock;
 
-class MediaNotificationContainerObserver;
-
 namespace {
 
+constexpr char kItemId[] = "item_id";
 constexpr char kSinkId[] = "sink_id";
 constexpr char kSinkFriendlyName[] = "Nest Hub";
 constexpr char16_t kSinkFriendlyName16[] = u"Nest Hub";
@@ -90,18 +89,17 @@
   GetOutputDevicesCallback output_devices_callback_;
 };
 
-class MockMediaNotificationDeviceSelectorViewDelegate
-    : public MediaNotificationDeviceSelectorViewDelegate {
+class MockMediaItemUIDeviceSelectorDelegate
+    : public MediaItemUIDeviceSelectorDelegate {
  public:
-  MockMediaNotificationDeviceSelectorViewDelegate() {
+  MockMediaItemUIDeviceSelectorDelegate() {
     provider_ = std::make_unique<MockMediaNotificationDeviceProvider>();
   }
 
   MOCK_METHOD(void,
               OnAudioSinkChosen,
-              (const std::string& sink_id),
+              (const std::string& item_id, const std::string& sink_id),
               (override));
-  MOCK_METHOD(void, OnDeviceSelectorViewSizeChanged, (), (override));
 
   base::CallbackListSubscription RegisterAudioOutputDeviceDescriptionsCallback(
       MediaNotificationDeviceProvider::GetOutputDevicesCallbackList::
@@ -114,6 +112,7 @@
 
   base::CallbackListSubscription
   RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
+      const std::string& item_id,
       base::RepeatingCallback<void(bool)> callback) override {
     callback.Run(supports_switching);
     supports_switching_callback_ = std::move(callback);
@@ -144,12 +143,13 @@
       void(base::OnceCallback<void(const ui::SelectedFileInfo*)> callback));
   MOCK_METHOD1(ClearIssue, void(const media_router::Issue::Id& issue_id));
 };
+
 }  // anonymous namespace
 
-class MediaNotificationDeviceSelectorViewTest : public ChromeViewsTestBase {
+class MediaItemUIDeviceSelectorViewTest : public ChromeViewsTestBase {
  public:
-  MediaNotificationDeviceSelectorViewTest() = default;
-  ~MediaNotificationDeviceSelectorViewTest() override = default;
+  MediaItemUIDeviceSelectorViewTest() = default;
+  ~MediaItemUIDeviceSelectorViewTest() override = default;
 
   // ChromeViewsTestBase
   void SetUp() override {
@@ -163,8 +163,7 @@
     ChromeViewsTestBase::TearDown();
   }
 
-  void AddAudioDevices(
-      MockMediaNotificationDeviceSelectorViewDelegate& delegate) {
+  void AddAudioDevices(MockMediaItemUIDeviceSelectorDelegate& delegate) {
     auto* provider = delegate.GetProvider();
     provider->AddDevice("Speaker", "1");
     provider->AddDevice("Headphones", "2");
@@ -189,27 +188,29 @@
     return base::UTF16ToUTF8(static_cast<views::LabelButton*>(view)->GetText());
   }
 
-  std::unique_ptr<MediaNotificationDeviceSelectorView> CreateDeviceSelectorView(
-      MockMediaNotificationDeviceSelectorViewDelegate* delegate,
+  std::unique_ptr<MediaItemUIDeviceSelectorView> CreateDeviceSelectorView(
+      MockMediaItemUIDeviceSelectorDelegate* delegate,
       std::unique_ptr<MockCastDialogController> controller =
           std::make_unique<NiceMock<MockCastDialogController>>(),
-      const std::string& device_description = "1",
+      const std::string& current_device = "1",
       bool has_audio_output = true,
-      GlobalMediaControlsEntryPoint entry_point =
-          GlobalMediaControlsEntryPoint::kToolbarIcon) {
-    return std::make_unique<MediaNotificationDeviceSelectorView>(
-        delegate, std::move(controller), has_audio_output, device_description,
-        gfx::kPlaceholderColor, gfx::kPlaceholderColor, entry_point);
+      global_media_controls::GlobalMediaControlsEntryPoint entry_point =
+          global_media_controls::GlobalMediaControlsEntryPoint::kToolbarIcon) {
+    auto device_selector_view = std::make_unique<MediaItemUIDeviceSelectorView>(
+        kItemId, delegate, std::move(controller), has_audio_output,
+        entry_point);
+    device_selector_view->UpdateCurrentAudioDevice(current_device);
+    return device_selector_view;
   }
 
-  std::unique_ptr<MediaNotificationDeviceSelectorView> view_;
+  std::unique_ptr<MediaItemUIDeviceSelectorView> view_;
   base::HistogramTester histogram_tester_;
   base::test::ScopedFeatureList feature_list_;
 };
 
-TEST_F(MediaNotificationDeviceSelectorViewTest, DeviceButtonsCreated) {
+TEST_F(MediaItemUIDeviceSelectorViewTest, DeviceButtonsCreated) {
   // Buttons should be created for every device reported by the provider.
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   AddAudioDevices(delegate);
   view_ = CreateDeviceSelectorView(&delegate);
   view_->OnModelUpdated(CreateModelWithSinks({CreateMediaSink()}));
@@ -225,9 +226,8 @@
   EXPECT_EQ(EntryLabelText(container_children.at(3)), kSinkFriendlyName);
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest,
-       ExpandButtonOpensEntryContainer) {
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+TEST_F(MediaItemUIDeviceSelectorViewTest, ExpandButtonOpensEntryContainer) {
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   AddAudioDevices(delegate);
   view_ = CreateDeviceSelectorView(&delegate);
 
@@ -237,9 +237,8 @@
   EXPECT_TRUE(view_->device_entry_views_container_->GetVisible());
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest,
-       DeviceEntryContainerVisibility) {
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+TEST_F(MediaItemUIDeviceSelectorViewTest, DeviceEntryContainerVisibility) {
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   AddAudioDevices(delegate);
 
   // The device entry container should be collapsed if the media dialog is
@@ -252,30 +251,29 @@
   view_ = CreateDeviceSelectorView(
       &delegate, std::make_unique<NiceMock<MockCastDialogController>>(), "1",
       /* has_audio_output */ true,
-      GlobalMediaControlsEntryPoint::kPresentation);
+      global_media_controls::GlobalMediaControlsEntryPoint::kPresentation);
   EXPECT_TRUE(view_->device_entry_views_container_->GetVisible());
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest,
+TEST_F(MediaItemUIDeviceSelectorViewTest,
        AudioDeviceButtonClickNotifiesContainer) {
   // When buttons are clicked the media notification delegate should be
   // informed.
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   AddAudioDevices(delegate);
   view_ = CreateDeviceSelectorView(&delegate);
 
-  EXPECT_CALL(delegate, OnAudioSinkChosen("1")).Times(1);
-  EXPECT_CALL(delegate, OnAudioSinkChosen("2")).Times(1);
-  EXPECT_CALL(delegate, OnAudioSinkChosen("3")).Times(1);
+  EXPECT_CALL(delegate, OnAudioSinkChosen(kItemId, "1")).Times(1);
+  EXPECT_CALL(delegate, OnAudioSinkChosen(kItemId, "2")).Times(1);
+  EXPECT_CALL(delegate, OnAudioSinkChosen(kItemId, "3")).Times(1);
 
   for (views::View* child : view_->device_entry_views_container_->children()) {
     SimulateButtonClick(child);
   }
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest,
-       CastDeviceButtonClickStartsCasting) {
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+TEST_F(MediaItemUIDeviceSelectorViewTest, CastDeviceButtonClickStartsCasting) {
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   auto cast_controller = std::make_unique<NiceMock<MockCastDialogController>>();
   auto* cast_controller_ptr = cast_controller.get();
   view_ = CreateDeviceSelectorView(&delegate, std::move(cast_controller));
@@ -318,10 +316,10 @@
   }
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest, CurrentAudioDeviceHighlighted) {
+TEST_F(MediaItemUIDeviceSelectorViewTest, CurrentAudioDeviceHighlighted) {
   // The 'current' audio device should be highlighted in the UI and appear
   // before other devices.
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   AddAudioDevices(delegate);
   view_ = CreateDeviceSelectorView(
       &delegate, std::make_unique<NiceMock<MockCastDialogController>>(), "3");
@@ -331,10 +329,9 @@
   EXPECT_TRUE(IsHighlighted(first_entry));
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest,
-       AudioDeviceHighlightedOnChange) {
+TEST_F(MediaItemUIDeviceSelectorViewTest, AudioDeviceHighlightedOnChange) {
   // When the audio output device changes, the UI should highlight that one.
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   AddAudioDevices(delegate);
   view_ = CreateDeviceSelectorView(&delegate);
 
@@ -358,10 +355,10 @@
   EXPECT_EQ(EntryLabelText(container_children.front()), "Earbuds");
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest, AudioDeviceButtonsChange) {
+TEST_F(MediaItemUIDeviceSelectorViewTest, AudioDeviceButtonsChange) {
   // If the device provider reports a change in connect audio devices, the UI
   // should update accordingly.
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   AddAudioDevices(delegate);
   view_ = CreateDeviceSelectorView(&delegate);
 
@@ -400,16 +397,15 @@
   }
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest, VisibilityChanges) {
+TEST_F(MediaItemUIDeviceSelectorViewTest, VisibilityChanges) {
   // The device selector view should become hidden when there is only one
   // unique device, unless there exists a cast device.
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   auto* provider = delegate.GetProvider();
   provider->AddDevice("Speaker", "1");
   provider->AddDevice(media::AudioDeviceDescription::GetDefaultDeviceName(),
                       media::AudioDeviceDescription::kDefaultDeviceId);
 
-  EXPECT_CALL(delegate, OnDeviceSelectorViewSizeChanged).Times(2);
   view_ = CreateDeviceSelectorView(
       &delegate, std::make_unique<NiceMock<MockCastDialogController>>(),
       media::AudioDeviceDescription::kDefaultDeviceId);
@@ -417,7 +413,6 @@
 
   testing::Mock::VerifyAndClearExpectations(&delegate);
 
-  EXPECT_CALL(delegate, OnDeviceSelectorViewSizeChanged);
   view_->OnModelUpdated(CreateModelWithSinks({CreateMediaSink()}));
   EXPECT_TRUE(view_->GetVisible());
 
@@ -427,7 +422,6 @@
   provider->AddDevice("Speaker", "1");
   provider->AddDevice("Headphones",
                       media::AudioDeviceDescription::kDefaultDeviceId);
-  EXPECT_CALL(delegate, OnDeviceSelectorViewSizeChanged).Times(1);
   provider->RunUICallback();
   EXPECT_TRUE(view_->GetVisible());
   testing::Mock::VerifyAndClearExpectations(&delegate);
@@ -437,15 +431,13 @@
   provider->AddDevice("Headphones", "2");
   provider->AddDevice(media::AudioDeviceDescription::GetDefaultDeviceName(),
                       media::AudioDeviceDescription::kDefaultDeviceId);
-  EXPECT_CALL(delegate, OnDeviceSelectorViewSizeChanged).Times(1);
   provider->RunUICallback();
   EXPECT_TRUE(view_->GetVisible());
   testing::Mock::VerifyAndClearExpectations(&delegate);
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest,
-       AudioDeviceChangeIsNotSupported) {
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+TEST_F(MediaItemUIDeviceSelectorViewTest, AudioDeviceChangeIsNotSupported) {
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   AddAudioDevices(delegate);
   delegate.supports_switching = false;
 
@@ -459,9 +451,8 @@
   EXPECT_TRUE(view_->GetVisible());
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest,
-       CastDeviceButtonClickClearsIssue) {
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+TEST_F(MediaItemUIDeviceSelectorViewTest, CastDeviceButtonClickClearsIssue) {
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   auto cast_controller = std::make_unique<NiceMock<MockCastDialogController>>();
   auto* cast_controller_ptr = cast_controller.get();
   view_ = CreateDeviceSelectorView(&delegate, std::move(cast_controller));
@@ -483,9 +474,8 @@
   }
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest,
-       AudioDevicesCountHistogramRecorded) {
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+TEST_F(MediaItemUIDeviceSelectorViewTest, AudioDevicesCountHistogramRecorded) {
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   AddAudioDevices(delegate);
 
   histogram_tester_.ExpectTotalCount(kAudioDevicesCountHistogramName, 0);
@@ -505,9 +495,9 @@
   histogram_tester_.ExpectBucketCount(kAudioDevicesCountHistogramName, 3, 1);
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest,
+TEST_F(MediaItemUIDeviceSelectorViewTest,
        DeviceSelectorAvailableHistogramRecorded) {
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   auto* provider = delegate.GetProvider();
   provider->AddDevice("Speaker",
                       media::AudioDeviceDescription::kDefaultDeviceId);
@@ -546,9 +536,9 @@
   histogram_tester_.ExpectTotalCount(kDeviceSelectorAvailableHistogramName, 2);
 }
 
-TEST_F(MediaNotificationDeviceSelectorViewTest,
+TEST_F(MediaItemUIDeviceSelectorViewTest,
        DeviceSelectorOpenedHistogramRecorded) {
-  NiceMock<MockMediaNotificationDeviceSelectorViewDelegate> delegate;
+  NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
   auto* provider = delegate.GetProvider();
   provider->AddDevice("Speaker",
                       media::AudioDeviceDescription::kDefaultDeviceId);
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_footer_view.cc b/chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.cc
similarity index 83%
rename from chrome/browser/ui/views/global_media_controls/media_notification_footer_view.cc
rename to chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.cc
index 27089a4..b52a157 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_footer_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.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 "chrome/browser/ui/views/global_media_controls/media_notification_footer_view.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.h"
 
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
@@ -15,6 +15,8 @@
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/highlight_path_generator.h"
+#include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/flex_layout.h"
 
 namespace {
@@ -88,9 +90,8 @@
 
 }  // anonymous namespace
 
-MediaNotificationFooterView::MediaNotificationFooterView(
-    bool is_cast_session,
-    views::Button::PressedCallback stop_casting_callback) {
+MediaItemUIFooterView::MediaItemUIFooterView(
+    base::RepeatingClosure stop_casting_callback) {
   SetLayoutManager(std::make_unique<views::FlexLayout>())
       ->SetOrientation(views::LayoutOrientation::kHorizontal)
       .SetIgnoreDefaultMainAxisMargins(true)
@@ -102,16 +103,19 @@
               /*horizontal=*/ChromeLayoutProvider::Get()->GetDistanceMetric(
                   views::DISTANCE_RELATED_BUTTON_HORIZONTAL)));
 
-  if (!is_cast_session)
+  if (stop_casting_callback.is_null())
     return;
 
+  // |this| owns the DeviceEntryButton, so base::Unretained is safe here.
   AddChildView(std::make_unique<DeviceEntryButton>(
       stop_casting_callback, nullptr,
       l10n_util::GetStringUTF16(
           IDS_GLOBAL_MEDIA_CONTROLS_STOP_CASTING_BUTTON_LABEL)));
 }
 
-void MediaNotificationFooterView::OnMediaNotificationDeviceSelectorUpdated(
+MediaItemUIFooterView::~MediaItemUIFooterView() = default;
+
+void MediaItemUIFooterView::OnMediaItemUIDeviceSelectorUpdated(
     const std::map<int, DeviceEntryUI*>& device_entries_map) {
   RemoveAllChildViews();
 
@@ -121,7 +125,7 @@
 
     auto* device_entry_button =
         AddChildView(std::make_unique<DeviceEntryButton>(
-            base::BindRepeating(&MediaNotificationFooterView::OnDeviceSelected,
+            base::BindRepeating(&MediaItemUIFooterView::OnDeviceSelected,
                                 base::Unretained(this), tag),
             device_entry->icon(),
             base::UTF8ToUTF16(device_entry->device_name())));
@@ -134,7 +138,7 @@
   }
 
   overflow_button_ = AddChildView(std::make_unique<DeviceEntryButton>(
-      base::BindRepeating(&MediaNotificationFooterView::OnOverflowButtonClicked,
+      base::BindRepeating(&MediaItemUIFooterView::OnOverflowButtonClicked,
                           base::Unretained(this))));
   overflow_button_->SetProperty(
       views::kFlexBehaviorKey,
@@ -149,7 +153,7 @@
   UpdateButtonsColor();
 }
 
-void MediaNotificationFooterView::Layout() {
+void MediaItemUIFooterView::Layout() {
   if (!overflow_button_) {
     views::View::Layout();
     return;
@@ -161,26 +165,27 @@
   views::View::Layout();
 }
 
-void MediaNotificationFooterView::OnColorChanged(SkColor foreground) {
+void MediaItemUIFooterView::OnColorsChanged(SkColor foreground,
+                                            SkColor background) {
   foreground_color_ = foreground;
   UpdateButtonsColor();
 }
 
-void MediaNotificationFooterView::SetDelegate(Delegate* delegate) {
+void MediaItemUIFooterView::SetDelegate(Delegate* delegate) {
   delegate_ = delegate;
 }
 
-void MediaNotificationFooterView::UpdateButtonsColor() {
+void MediaItemUIFooterView::UpdateButtonsColor() {
   for (auto* view : children())
     static_cast<DeviceEntryButton*>(view)->UpdateColor(foreground_color_);
 }
 
-void MediaNotificationFooterView::OnDeviceSelected(int tag) {
+void MediaItemUIFooterView::OnDeviceSelected(int tag) {
   if (delegate_)
     delegate_->OnDeviceSelected(tag);
 }
 
-void MediaNotificationFooterView::OnOverflowButtonClicked() {
+void MediaItemUIFooterView::OnOverflowButtonClicked() {
   if (!delegate_)
     return;
 
diff --git a/chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.h b/chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.h
new file mode 100644
index 0000000..defa0b2
--- /dev/null
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_FOOTER_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_FOOTER_VIEW_H_
+
+#include "base/callback.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_observer.h"
+#include "components/global_media_controls/public/views/media_item_ui_footer.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/views/controls/button/button.h"
+
+namespace {
+class DeviceEntryButton;
+}  // anonymous namespace
+
+// A footer view attached to MediaItemUIView containing
+// available cast devices and volume controls.
+class MediaItemUIFooterView : public global_media_controls::MediaItemUIFooter,
+                              public MediaItemUIDeviceSelectorObserver {
+ public:
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    virtual void OnDeviceSelected(int tag) = 0;
+    virtual void OnDropdownButtonClicked() = 0;
+    virtual bool IsDeviceSelectorExpanded() = 0;
+  };
+
+  explicit MediaItemUIFooterView(base::RepeatingClosure stop_casting_callback);
+  ~MediaItemUIFooterView() override;
+
+  // global_media_controls::MediaItemUIFooter:
+  void OnColorsChanged(SkColor foreground, SkColor background) override;
+
+  void SetDelegate(Delegate* delegate);
+
+  // MediaItemDeviceSelectorObserver:
+  void OnMediaItemUIDeviceSelectorUpdated(
+      const std::map<int, DeviceEntryUI*>& device_entries_map) override;
+
+  void Layout() override;
+
+ private:
+  void UpdateButtonsColor();
+  void OnDeviceSelected(int tag);
+  void OnOverflowButtonClicked();
+
+  SkColor foreground_color_ = gfx::kPlaceholderColor;
+
+  DeviceEntryButton* overflow_button_ = nullptr;
+
+  Delegate* delegate_ = nullptr;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_FOOTER_VIEW_H_
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_footer_view_unittest.cc b/chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view_unittest.cc
similarity index 83%
rename from chrome/browser/ui/views/global_media_controls/media_notification_footer_view_unittest.cc
rename to chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view_unittest.cc
index 30603ad..1399554 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_footer_view_unittest.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view_unittest.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 "chrome/browser/ui/views/global_media_controls/media_notification_footer_view.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.h"
 
 #include <map>
 #include <memory>
@@ -29,7 +29,7 @@
 };
 
 // A mock class for delegating media notification footer view.
-class MockFooterViewDelegate : public MediaNotificationFooterView::Delegate {
+class MockFooterViewDelegate : public MediaItemUIFooterView::Delegate {
  public:
   MockFooterViewDelegate() = default;
   ~MockFooterViewDelegate() override = default;
@@ -42,10 +42,10 @@
 
 }  // namespace
 
-class MediaNotificationFooterViewTest : public ChromeViewsTestBase {
+class MediaItemUIFooterViewTest : public ChromeViewsTestBase {
  public:
-  MediaNotificationFooterViewTest() = default;
-  ~MediaNotificationFooterViewTest() override = default;
+  MediaItemUIFooterViewTest() = default;
+  ~MediaItemUIFooterViewTest() override = default;
 
   // ChromeViewsTestBase
   void SetUp() override { ChromeViewsTestBase::SetUp(); }
@@ -62,11 +62,13 @@
     handler_ = std::make_unique<StopCastingHandler>();
     delegate_ = std::make_unique<NiceMock<MockFooterViewDelegate>>();
 
-    view_ =
-        widget_->SetContentsView(std::make_unique<MediaNotificationFooterView>(
-            is_cast_session,
-            base::BindRepeating(&StopCastingHandler::StopCasting,
-                                base::Unretained(handler_.get()))));
+    base::RepeatingClosure stop_casting_callback =
+        is_cast_session ? base::BindRepeating(&StopCastingHandler::StopCasting,
+                                              base::Unretained(handler_.get()))
+                        : base::NullCallback();
+
+    view_ = widget_->SetContentsView(
+        std::make_unique<MediaItemUIFooterView>(stop_casting_callback));
     view_->SetDelegate(delegate_.get());
 
     widget_->Show();
@@ -89,7 +91,7 @@
 
   views::Widget* get_widget() { return widget_.get(); }
 
-  MediaNotificationFooterView* get_view() { return view_; }
+  MediaItemUIFooterView* get_view() { return view_; }
 
   MockFooterViewDelegate* delegate() { return delegate_.get(); }
 
@@ -99,10 +101,10 @@
   std::unique_ptr<views::Widget> widget_;
   std::unique_ptr<StopCastingHandler> handler_;
   std::unique_ptr<MockFooterViewDelegate> delegate_;
-  MediaNotificationFooterView* view_ = nullptr;
+  MediaItemUIFooterView* view_ = nullptr;
 };
 
-TEST_F(MediaNotificationFooterViewTest, ViewDuringCast) {
+TEST_F(MediaItemUIFooterViewTest, ViewDuringCast) {
   CreateView(true);
   EXPECT_EQ(get_view()->children().size(), 1u);
 
@@ -110,7 +112,7 @@
   SimulateButtonClicked(get_view()->children()[0]);
 }
 
-TEST_F(MediaNotificationFooterViewTest, DevicesCanFit) {
+TEST_F(MediaItemUIFooterViewTest, DevicesCanFit) {
   CreateView(false);
   get_widget()->SetBounds(gfx::Rect(200, 20));
   EXPECT_EQ(get_view()->children().size(), 0u);
@@ -129,7 +131,7 @@
   devices[0] = &device1;
   devices[1] = &device2;
 
-  get_view()->OnMediaNotificationDeviceSelectorUpdated(devices);
+  get_view()->OnMediaItemUIDeviceSelectorUpdated(devices);
   get_widget()->LayoutRootViewIfNecessary();
   auto visible_items = GetVisibleItems();
   EXPECT_EQ(visible_items.size(), 2u);
@@ -146,7 +148,7 @@
     SimulateButtonClicked(view);
 }
 
-TEST_F(MediaNotificationFooterViewTest, OverflowButton) {
+TEST_F(MediaItemUIFooterViewTest, OverflowButton) {
   CreateView(false);
   get_widget()->SetBounds(gfx::Rect(200, 20));
   EXPECT_EQ(get_view()->children().size(), 0u);
@@ -159,7 +161,7 @@
   devices[0] = &device;
   devices[1] = &device;
 
-  get_view()->OnMediaNotificationDeviceSelectorUpdated(devices);
+  get_view()->OnMediaItemUIDeviceSelectorUpdated(devices);
   get_widget()->LayoutRootViewIfNecessary();
   auto visible_items = GetVisibleItems();
   EXPECT_EQ(visible_items.size(), 2u);
@@ -174,7 +176,7 @@
   SimulateButtonClicked(visible_items[1]);
 }
 
-TEST_F(MediaNotificationFooterViewTest, OverflowButtonFallback) {
+TEST_F(MediaItemUIFooterViewTest, OverflowButtonFallback) {
   CreateView(false);
   get_widget()->SetBounds(gfx::Rect(310, 20));
   EXPECT_EQ(get_view()->children().size(), 0u);
@@ -188,7 +190,7 @@
   devices[1] = &device;
 
   // Two devices with 130px width should fit in the footer view (296px).
-  get_view()->OnMediaNotificationDeviceSelectorUpdated(devices);
+  get_view()->OnMediaItemUIDeviceSelectorUpdated(devices);
   get_widget()->LayoutRootViewIfNecessary();
   auto visible_items = GetVisibleItems();
   EXPECT_EQ(visible_items.size(), 2u);
@@ -203,7 +205,7 @@
   // (130px device button + 8px spacing + 130px device button + 8px spacing +
   // 30px overflow button > 296px maximum footer size)
   devices[2] = &device;
-  get_view()->OnMediaNotificationDeviceSelectorUpdated(devices);
+  get_view()->OnMediaItemUIDeviceSelectorUpdated(devices);
   get_widget()->LayoutRootViewIfNecessary();
   visible_items = GetVisibleItems();
   EXPECT_EQ(visible_items.size(), 2u);
diff --git a/chrome/browser/ui/views/global_media_controls/media_item_ui_legacy_cast_footer_view.cc b/chrome/browser/ui/views/global_media_controls/media_item_ui_legacy_cast_footer_view.cc
new file mode 100644
index 0000000..7e159dc
--- /dev/null
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_legacy_cast_footer_view.cc
@@ -0,0 +1,97 @@
+// 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/ui/views/global_media_controls/media_item_ui_legacy_cast_footer_view.h"
+
+#include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/feature_engagement/tracker_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/global_media_controls/cast_media_notification_item.h"
+#include "chrome/browser/ui/global_media_controls/media_notification_service.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/feature_engagement/public/tracker.h"
+#include "components/global_media_controls/public/media_item_manager.h"
+#include "components/media_message_center/media_notification_item.h"
+#include "components/media_router/browser/media_router.h"
+#include "components/media_router/browser/media_router_factory.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/animation/ink_drop.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/highlight_path_generator.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace {
+
+constexpr gfx::Insets kInsets{6, 15};
+constexpr gfx::Size kSize{400, 30};
+constexpr gfx::Insets kBorderInsets{4, 8};
+
+}  // anonymous namespace
+
+MediaItemUILegacyCastFooterView::MediaItemUILegacyCastFooterView(
+    base::RepeatingClosure stop_casting_callback)
+    : stop_casting_callback_(stop_casting_callback) {
+  DCHECK(!stop_casting_callback_.is_null());
+
+  auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kHorizontal, kInsets));
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
+  layout->set_cross_axis_alignment(
+      views::BoxLayout::CrossAxisAlignment::kCenter);
+  SetPreferredSize(kSize);
+
+  stop_cast_button_ = AddChildView(std::make_unique<views::LabelButton>(
+      base::BindRepeating(&MediaItemUILegacyCastFooterView::StopCasting,
+                          base::Unretained(this)),
+      l10n_util::GetStringUTF16(
+          IDS_GLOBAL_MEDIA_CONTROLS_STOP_CASTING_BUTTON_LABEL)));
+  views::InstallRoundRectHighlightPathGenerator(
+      stop_cast_button_, gfx::Insets(), kSize.height() / 2);
+
+  views::InkDrop::Get(stop_cast_button_)
+      ->SetMode(views::InkDropHost::InkDropMode::ON);
+  stop_cast_button_->SetFocusBehavior(FocusBehavior::ALWAYS);
+  stop_cast_button_->SetBorder(views::CreatePaddedBorder(
+      views::CreateRoundedRectBorder(1, kSize.height() / 2, foreground_color_),
+      kBorderInsets));
+  UpdateColors();
+}
+
+MediaItemUILegacyCastFooterView::~MediaItemUILegacyCastFooterView() = default;
+
+views::Button*
+MediaItemUILegacyCastFooterView::GetStopCastingButtonForTesting() {
+  return stop_cast_button_;
+}
+
+void MediaItemUILegacyCastFooterView::OnColorsChanged(SkColor foreground,
+                                                      SkColor background) {
+  if (foreground == foreground_color_ && background == background_color_)
+    return;
+
+  foreground_color_ = foreground;
+  background_color_ = background;
+
+  UpdateColors();
+}
+
+void MediaItemUILegacyCastFooterView::StopCasting() {
+  stop_cast_button_->SetEnabled(false);
+  stop_casting_callback_.Run();
+}
+
+void MediaItemUILegacyCastFooterView::UpdateColors() {
+  // Update background.
+  SetBackground(views::CreateSolidBackground(background_color_));
+
+  // Update button icon.
+  stop_cast_button_->SetEnabledTextColors(foreground_color_);
+  views::InkDrop::Get(stop_cast_button_)->SetBaseColor(foreground_color_);
+  stop_cast_button_->SetBorder(views::CreatePaddedBorder(
+      views::CreateRoundedRectBorder(1, kSize.height() / 2, foreground_color_),
+      kBorderInsets));
+}
diff --git a/chrome/browser/ui/views/global_media_controls/media_item_ui_legacy_cast_footer_view.h b/chrome/browser/ui/views/global_media_controls/media_item_ui_legacy_cast_footer_view.h
new file mode 100644
index 0000000..05366cd
--- /dev/null
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_legacy_cast_footer_view.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_LEGACY_CAST_FOOTER_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_LEGACY_CAST_FOOTER_VIEW_H_
+
+#include "base/callback.h"
+#include "components/global_media_controls/public/constants.h"
+#include "components/global_media_controls/public/views/media_item_ui_footer.h"
+#include "ui/gfx/color_palette.h"
+
+namespace views {
+class Button;
+class LabelButton;
+}  // namespace views
+
+// A footer view attached to MediaItemUIView for Cast items when modern UI is
+// disabled.
+class MediaItemUILegacyCastFooterView
+    : public global_media_controls::MediaItemUIFooter {
+ public:
+  explicit MediaItemUILegacyCastFooterView(
+      base::RepeatingClosure stop_casting_callback);
+  ~MediaItemUILegacyCastFooterView() override;
+
+  // global_media_controls::MediaItemUIFooter:
+  void OnColorsChanged(SkColor foreground, SkColor background) override;
+
+  views::Button* GetStopCastingButtonForTesting();
+
+ private:
+  void StopCasting();
+
+  void UpdateColors();
+
+  SkColor foreground_color_ = global_media_controls::kDefaultForegroundColor;
+  SkColor background_color_ = global_media_controls::kDefaultBackgroundColor;
+
+  views::LabelButton* stop_cast_button_ = nullptr;
+
+  const base::RepeatingClosure stop_casting_callback_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_ITEM_UI_LEGACY_CAST_FOOTER_VIEW_H_
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.cc b/chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.cc
deleted file mode 100644
index 61f853d..0000000
--- a/chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.cc
+++ /dev/null
@@ -1,574 +0,0 @@
-// Copyright 2019 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/ui/views/global_media_controls/media_notification_container_impl_view.h"
-
-#include "base/bind.h"
-#include "base/containers/contains.h"
-#include "base/feature_list.h"
-#include "base/metrics/field_trial_params.h"
-#include "base/metrics/histogram_functions.h"
-#include "chrome/browser/feature_engagement/tracker_factory.h"
-#include "chrome/browser/media/router/media_router_feature.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/global_media_controls/cast_media_notification_item.h"
-#include "chrome/browser/ui/global_media_controls/media_notification_service.h"
-#include "chrome/browser/ui/global_media_controls/media_toolbar_button_controller.h"
-#include "chrome/browser/ui/views/global_media_controls/media_dialog_view.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_footer_view.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/feature_engagement/public/tracker.h"
-#include "components/global_media_controls/public/media_item_manager.h"
-#include "components/global_media_controls/public/media_item_ui_observer.h"
-#include "components/media_message_center/media_notification_item.h"
-#include "components/media_message_center/media_notification_view_modern_impl.h"
-#include "components/media_router/browser/media_router.h"
-#include "components/media_router/browser/media_router_factory.h"
-#include "components/vector_icons/vector_icons.h"
-#include "media/audio/audio_device_description.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/metadata/metadata_header_macros.h"
-#include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/compositor/canvas_painter.h"
-#include "ui/compositor/layer.h"
-#include "ui/message_center/public/cpp/message_center_constants.h"
-#include "ui/views/animation/ink_drop.h"
-#include "ui/views/animation/slide_out_controller.h"
-#include "ui/views/background.h"
-#include "ui/views/border.h"
-#include "ui/views/controls/button/image_button.h"
-#include "ui/views/controls/button/image_button_factory.h"
-#include "ui/views/controls/highlight_path_generator.h"
-#include "ui/views/controls/image_view.h"
-#include "ui/views/layout/box_layout.h"
-#include "ui/views/layout/fill_layout.h"
-
-namespace {
-
-// TODO(steimel): We need to decide on the correct values here.
-constexpr int kWidth = 400;
-constexpr int kModernUIWidth = 350;
-constexpr gfx::Size kNormalSize = gfx::Size(kWidth, 100);
-constexpr gfx::Size kExpandedSize = gfx::Size(kWidth, 150);
-constexpr gfx::Size kModernUISize = gfx::Size(kModernUIWidth, 168);
-constexpr gfx::Size kDismissButtonSize = gfx::Size(30, 30);
-constexpr int kDismissButtonIconSize = 20;
-constexpr int kDismissButtonBackgroundRadius = 15;
-constexpr SkColor kDefaultForegroundColor = SK_ColorBLACK;
-constexpr SkColor kDefaultBackgroundColor = SK_ColorTRANSPARENT;
-constexpr gfx::Insets kStopCastButtonStripInsets{6, 15};
-constexpr gfx::Size kStopCastButtonStripSize{400, 30};
-constexpr gfx::Insets kStopCastButtonBorderInsets{4, 8};
-constexpr gfx::Size kCrOSDismissButtonSize = gfx::Size(20, 20);
-constexpr int kCrOSDismissButtonIconSize = 12;
-constexpr gfx::Size kModernDismissButtonSize = gfx::Size(14, 14);
-constexpr int kModernDismissButtonIconSize = 10;
-
-// The minimum number of enabled and visible user actions such that we should
-// force the MediaNotificationView to be expanded.
-constexpr int kMinVisibleActionsForExpanding = 4;
-
-}  // anonymous namespace
-
-class MediaNotificationContainerImplView::DismissButton
-    : public views::ImageButton {
- public:
-  METADATA_HEADER(DismissButton);
-
-  explicit DismissButton(PressedCallback callback)
-      : views::ImageButton(std::move(callback)) {
-    views::ConfigureVectorImageButton(this);
-    views::InstallFixedSizeCircleHighlightPathGenerator(
-        this, kDismissButtonBackgroundRadius);
-    SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
-  }
-  DismissButton(const DismissButton&) = delete;
-  DismissButton& operator=(const DismissButton&) = delete;
-  ~DismissButton() override = default;
-};
-
-BEGIN_METADATA(MediaNotificationContainerImplView,
-               DismissButton,
-               views::ImageButton)
-END_METADATA
-
-MediaNotificationContainerImplView::MediaNotificationContainerImplView(
-    const std::string& id,
-    base::WeakPtr<media_message_center::MediaNotificationItem> item,
-    MediaNotificationService* service,
-    GlobalMediaControlsEntryPoint entry_point,
-    Profile* profile,
-    absl::optional<media_message_center::NotificationTheme> theme)
-    : views::Button(base::BindRepeating(
-          &MediaNotificationContainerImplView::ContainerClicked,
-          base::Unretained(this))),
-      id_(id),
-      foreground_color_(kDefaultForegroundColor),
-      background_color_(kDefaultBackgroundColor),
-      service_(service),
-      is_cros_(theme.has_value()),
-      entry_point_(entry_point),
-      profile_(profile) {
-  DCHECK(item);
-  SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kVertical));
-  SetPreferredSize(kNormalSize);
-  SetNotifyEnterExitOnChild(true);
-  SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
-  SetTooltipText(
-      l10n_util::GetStringUTF16(IDS_GLOBAL_MEDIA_CONTROLS_BACK_TO_TAB));
-
-  auto swipeable_container = std::make_unique<views::View>();
-  swipeable_container->SetLayoutManager(std::make_unique<views::FillLayout>());
-  swipeable_container->SetPaintToLayer();
-  swipeable_container->layer()->SetFillsBoundsOpaquely(false);
-  swipeable_container_ = AddChildView(std::move(swipeable_container));
-
-  gfx::Size dismiss_button_size =
-      is_cros_ ? kCrOSDismissButtonSize : kDismissButtonSize;
-  if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI))
-    dismiss_button_size = kModernDismissButtonSize;
-
-  auto dismiss_button_placeholder = std::make_unique<views::View>();
-  dismiss_button_placeholder->SetPreferredSize(dismiss_button_size);
-  dismiss_button_placeholder->SetLayoutManager(
-      std::make_unique<views::FillLayout>());
-  dismiss_button_placeholder_ = dismiss_button_placeholder.get();
-
-  auto dismiss_button_container = std::make_unique<views::View>();
-  dismiss_button_container->SetPreferredSize(dismiss_button_size);
-  dismiss_button_container->SetLayoutManager(
-      std::make_unique<views::FillLayout>());
-  dismiss_button_container->SetVisible(false);
-  dismiss_button_container_ = dismiss_button_placeholder_->AddChildView(
-      std::move(dismiss_button_container));
-
-  auto dismiss_button = std::make_unique<DismissButton>(base::BindRepeating(
-      &MediaNotificationContainerImplView::DismissNotification,
-      base::Unretained(this)));
-  dismiss_button->SetPreferredSize(dismiss_button_size);
-  dismiss_button->SetTooltipText(l10n_util::GetStringUTF16(
-      IDS_GLOBAL_MEDIA_CONTROLS_DISMISS_ICON_TOOLTIP_TEXT));
-  dismiss_button_ =
-      dismiss_button_container_->AddChildView(std::move(dismiss_button));
-  UpdateDismissButtonIcon();
-
-  // Compute a few things related to |item| before the construction of |view|
-  // below moves it.
-  const bool is_cast_notification =
-      item->SourceType() == media_message_center::SourceType::kCast;
-  auto* const cast_item =
-      is_cast_notification ? static_cast<CastMediaNotificationItem*>(item.get())
-                           : nullptr;
-  const bool is_local_media_session =
-      item->SourceType() ==
-      media_message_center::SourceType::kLocalMediaSession;
-
-  std::unique_ptr<media_message_center::MediaNotificationView> view;
-  if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI)) {
-    auto footer_view = std::make_unique<MediaNotificationFooterView>(
-        is_cast_notification,
-        is_cast_notification
-            ? base::BindRepeating(
-                  &MediaNotificationContainerImplView::StopCasting,
-                  base::Unretained(this), base::Unretained(cast_item))
-            : views::Button::PressedCallback());
-    footer_view_ = footer_view.get();
-
-    view =
-        std::make_unique<media_message_center::MediaNotificationViewModernImpl>(
-            this, std::move(item), std::move(dismiss_button_placeholder),
-            std::move(footer_view), kModernUIWidth);
-    SetPreferredSize(kModernUISize);
-  } else {
-    view = std::make_unique<media_message_center::MediaNotificationViewImpl>(
-        this, std::move(item), std::move(dismiss_button_placeholder),
-        std::u16string(), kWidth, /*should_show_icon=*/false, theme);
-    SetPreferredSize(kNormalSize);
-  }
-  view_ = swipeable_container_->AddChildView(std::move(view));
-  bool gmc_cast_start_stop_enabled =
-      media_router::GlobalMediaControlsCastStartStopEnabled() &&
-      media_router::MediaRouterEnabled(profile_);
-  // Show a stop cast button for cast notifications.
-  if (is_cast_notification && gmc_cast_start_stop_enabled &&
-      !base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI)) {
-    AddStopCastButton(cast_item);
-  }
-
-  // Show a device selector view for media and supplemental notifications.
-  if (!is_cast_notification &&
-      (gmc_cast_start_stop_enabled ||
-       base::FeatureList::IsEnabled(
-           media::kGlobalMediaControlsSeamlessTransfer))) {
-    AddDeviceSelectorView(
-        is_local_media_session,
-        /* show_expand_button */ !base::FeatureList::IsEnabled(
-            media::kGlobalMediaControlsModernUI));
-    if (device_selector_view_ && footer_view_) {
-      footer_view_->SetDelegate(device_selector_view_);
-      device_selector_view_->AddObserver(footer_view_);
-    }
-  }
-
-  ForceExpandedState();
-
-  slide_out_controller_ =
-      std::make_unique<views::SlideOutController>(this, this);
-}
-
-MediaNotificationContainerImplView::~MediaNotificationContainerImplView() {
-  for (auto& observer : observers_)
-    observer.OnMediaItemUIDestroyed(id_);
-}
-
-void MediaNotificationContainerImplView::AddedToWidget() {
-  if (GetFocusManager())
-    GetFocusManager()->AddFocusChangeListener(this);
-}
-
-void MediaNotificationContainerImplView::RemovedFromWidget() {
-  if (GetFocusManager())
-    GetFocusManager()->RemoveFocusChangeListener(this);
-}
-
-void MediaNotificationContainerImplView::OnMouseEntered(
-    const ui::MouseEvent& event) {
-  UpdateDismissButtonVisibility();
-}
-
-void MediaNotificationContainerImplView::OnMouseExited(
-    const ui::MouseEvent& event) {
-  UpdateDismissButtonVisibility();
-}
-
-void MediaNotificationContainerImplView::OnDidChangeFocus(
-    views::View* focused_before,
-    views::View* focused_now) {
-  UpdateDismissButtonVisibility();
-}
-
-void MediaNotificationContainerImplView::OnExpanded(bool expanded) {
-  is_expanded_ = expanded;
-  OnSizeChanged();
-}
-
-void MediaNotificationContainerImplView::OnMediaSessionInfoChanged(
-    const media_session::mojom::MediaSessionInfoPtr& session_info) {
-  is_playing_ =
-      session_info && session_info->playback_state ==
-                          media_session::mojom::MediaPlaybackState::kPlaying;
-  if (session_info) {
-    audio_sink_id_ = session_info->audio_sink_id.value_or(
-        media::AudioDeviceDescription::kDefaultDeviceId);
-    if (device_selector_view_ &&
-        base::FeatureList::IsEnabled(
-            media::kGlobalMediaControlsSeamlessTransfer)) {
-      device_selector_view_->UpdateCurrentAudioDevice(audio_sink_id_);
-    }
-  }
-}
-
-void MediaNotificationContainerImplView::OnMediaSessionMetadataChanged(
-    const media_session::MediaMetadata& metadata) {
-  title_ = metadata.title;
-
-  for (auto& observer : observers_)
-    observer.OnMediaItemUIMetadataChanged();
-}
-
-void MediaNotificationContainerImplView::OnVisibleActionsChanged(
-    const base::flat_set<media_session::mojom::MediaSessionAction>& actions) {
-  has_many_actions_ =
-      (actions.size() >= kMinVisibleActionsForExpanding ||
-       base::Contains(
-           actions,
-           media_session::mojom::MediaSessionAction::kEnterPictureInPicture) ||
-       base::Contains(
-           actions,
-           media_session::mojom::MediaSessionAction::kExitPictureInPicture));
-  ForceExpandedState();
-
-  for (auto& observer : observers_)
-    observer.OnMediaItemUIActionsChanged();
-}
-
-void MediaNotificationContainerImplView::OnMediaArtworkChanged(
-    const gfx::ImageSkia& image) {
-  has_artwork_ = !image.isNull();
-
-  UpdateDismissButtonBackground();
-  ForceExpandedState();
-}
-
-void MediaNotificationContainerImplView::OnColorsChanged(SkColor foreground,
-                                                         SkColor background) {
-  if (foreground_color_ != foreground) {
-    foreground_color_ = foreground;
-    UpdateDismissButtonIcon();
-    UpdateStopCastButtonIcon();
-  }
-
-  if (background_color_ != background) {
-    background_color_ = background;
-    UpdateDismissButtonBackground();
-    UpdateStopCastButtonBackground();
-  }
-  if (footer_view_)
-    footer_view_->OnColorChanged(foreground);
-
-  if (device_selector_view_)
-    device_selector_view_->OnColorsChanged(foreground, background);
-}
-
-void MediaNotificationContainerImplView::OnHeaderClicked() {
-  // Since we disable the expand button, nothing happens on the
-  // MediaNotificationView when the header is clicked. Treat the click as if we
-  // were clicked directly.
-  ContainerClicked();
-}
-
-void MediaNotificationContainerImplView::OnAudioSinkChosen(
-    const std::string& sink_id) {
-  for (auto& observer : observers_) {
-    observer.OnAudioSinkChosen(id_, sink_id);
-  }
-}
-
-void MediaNotificationContainerImplView::OnDeviceSelectorViewSizeChanged() {
-  OnSizeChanged();
-}
-
-base::CallbackListSubscription MediaNotificationContainerImplView::
-    RegisterAudioOutputDeviceDescriptionsCallback(
-        MediaNotificationDeviceProvider::GetOutputDevicesCallbackList::
-            CallbackType callback) {
-  return service_->RegisterAudioOutputDeviceDescriptionsCallback(
-      std::move(callback));
-}
-
-base::CallbackListSubscription MediaNotificationContainerImplView::
-    RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
-        base::RepeatingCallback<void(bool)> callback) {
-  return service_->RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
-      id_, std::move(callback));
-}
-
-ui::Layer* MediaNotificationContainerImplView::GetSlideOutLayer() {
-  return swipeable_container_->layer();
-}
-
-void MediaNotificationContainerImplView::OnSlideOut() {
-  DismissNotification();
-}
-
-void MediaNotificationContainerImplView::AddObserver(
-    global_media_controls::MediaItemUIObserver* observer) {
-  observers_.AddObserver(observer);
-}
-
-void MediaNotificationContainerImplView::RemoveObserver(
-    global_media_controls::MediaItemUIObserver* observer) {
-  observers_.RemoveObserver(observer);
-}
-
-const std::u16string& MediaNotificationContainerImplView::GetTitle() const {
-  return title_;
-}
-
-views::ImageButton*
-MediaNotificationContainerImplView::GetDismissButtonForTesting() {
-  return dismiss_button_;
-}
-
-views::Button*
-MediaNotificationContainerImplView::GetStopCastingButtonForTesting() {
-  return stop_cast_button_;
-}
-
-void MediaNotificationContainerImplView::AddStopCastButton(
-    CastMediaNotificationItem* cast_item) {
-  DCHECK(cast_item);
-  stop_button_strip_ = AddChildView(std::make_unique<views::View>());
-  auto* stop_cast_button_strip_layout =
-      stop_button_strip_->SetLayoutManager(std::make_unique<views::BoxLayout>(
-          views::BoxLayout::Orientation::kHorizontal,
-          kStopCastButtonStripInsets));
-  stop_cast_button_strip_layout->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::kStart);
-  stop_cast_button_strip_layout->set_cross_axis_alignment(
-      views::BoxLayout::CrossAxisAlignment::kCenter);
-  stop_button_strip_->SetPreferredSize(kStopCastButtonStripSize);
-  UpdateStopCastButtonBackground();
-
-  stop_cast_button_ =
-      stop_button_strip_->AddChildView(std::make_unique<views::LabelButton>(
-          base::BindRepeating(&MediaNotificationContainerImplView::StopCasting,
-                              base::Unretained(this),
-                              base::Unretained(cast_item)),
-          l10n_util::GetStringUTF16(
-              IDS_GLOBAL_MEDIA_CONTROLS_STOP_CASTING_BUTTON_LABEL)));
-  views::InstallRoundRectHighlightPathGenerator(
-      stop_cast_button_, gfx::Insets(), kStopCastButtonStripSize.height() / 2);
-
-  views::InkDrop::Get(stop_cast_button_)
-      ->SetMode(views::InkDropHost::InkDropMode::ON);
-  stop_cast_button_->SetFocusBehavior(FocusBehavior::ALWAYS);
-  stop_cast_button_->SetBorder(views::CreatePaddedBorder(
-      views::CreateRoundedRectBorder(1, kStopCastButtonStripSize.height() / 2,
-                                     foreground_color_),
-      kStopCastButtonBorderInsets));
-  UpdateStopCastButtonIcon();
-}
-
-void MediaNotificationContainerImplView::AddDeviceSelectorView(
-    bool is_local_media_session,
-    bool show_expand_button) {
-  std::unique_ptr<media_router::CastDialogController> cast_controller;
-  if (media_router::GlobalMediaControlsCastStartStopEnabled() &&
-      media_router::MediaRouterEnabled(profile_)) {
-    cast_controller =
-        is_local_media_session
-            ? service_->CreateCastDialogControllerForSession(id_)
-            : service_->CreateCastDialogControllerForPresentationRequest();
-  }
-  auto device_selector_view =
-      std::make_unique<MediaNotificationDeviceSelectorView>(
-          this, std::move(cast_controller),
-          /* has_audio_output */ is_local_media_session, audio_sink_id_,
-          foreground_color_, background_color_, entry_point_,
-          show_expand_button);
-  device_selector_view_ = AddChildView(std::move(device_selector_view));
-  view_->UpdateCornerRadius(message_center::kNotificationCornerRadius, 0);
-}
-
-void MediaNotificationContainerImplView::StopCasting(
-    CastMediaNotificationItem* cast_item) {
-  stop_cast_button_->SetEnabled(false);
-
-  media_router::MediaRouterFactory::GetApiForBrowserContext(
-      cast_item->profile())
-      ->TerminateRoute(cast_item->route_id());
-
-  // |service_| is nullptr in MediaNotificationContainerImplViewTest.
-  if (service_)
-    service_->media_item_manager()->FocusDialog();
-
-  feature_engagement::TrackerFactory::GetForBrowserContext(profile_)
-      ->NotifyEvent("media_route_stopped_from_gmc");
-
-  GlobalMediaControlsCastActionAndEntryPoint action;
-  switch (entry_point_) {
-    case GlobalMediaControlsEntryPoint::kToolbarIcon:
-      action = GlobalMediaControlsCastActionAndEntryPoint::kStopViaToolbarIcon;
-      break;
-    case GlobalMediaControlsEntryPoint::kPresentation:
-      action = GlobalMediaControlsCastActionAndEntryPoint::kStopViaPresentation;
-      break;
-    case GlobalMediaControlsEntryPoint::kSystemTray:
-      action = GlobalMediaControlsCastActionAndEntryPoint::kStopViaSystemTray;
-      break;
-  }
-  base::UmaHistogramEnumeration(
-      media_message_center::MediaNotificationItem::kCastStartStopHistogramName,
-      action);
-}
-
-void MediaNotificationContainerImplView::UpdateStopCastButtonIcon() {
-  if (!stop_cast_button_)
-    return;
-  stop_cast_button_->SetEnabledTextColors(foreground_color_);
-  views::InkDrop::Get(stop_cast_button_)->SetBaseColor(foreground_color_);
-  stop_cast_button_->SetBorder(views::CreatePaddedBorder(
-      views::CreateRoundedRectBorder(1, kStopCastButtonStripSize.height() / 2,
-                                     foreground_color_),
-      kStopCastButtonBorderInsets));
-}
-
-void MediaNotificationContainerImplView::UpdateStopCastButtonBackground() {
-  if (!stop_button_strip_)
-    return;
-  stop_button_strip_->SetBackground(
-      views::CreateSolidBackground(background_color_));
-}
-
-void MediaNotificationContainerImplView::UpdateDismissButtonIcon() {
-  int icon_size =
-      is_cros_ ? kCrOSDismissButtonIconSize : kDismissButtonIconSize;
-  if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI))
-    icon_size = kModernDismissButtonIconSize;
-
-  views::SetImageFromVectorIconWithColor(dismiss_button_,
-                                         vector_icons::kCloseRoundedIcon,
-                                         icon_size, foreground_color_);
-}
-
-void MediaNotificationContainerImplView::UpdateDismissButtonBackground() {
-  if (!has_artwork_) {
-    dismiss_button_container_->SetBackground(nullptr);
-    return;
-  }
-
-  dismiss_button_container_->SetBackground(views::CreateRoundedRectBackground(
-      background_color_, kDismissButtonBackgroundRadius));
-}
-
-void MediaNotificationContainerImplView::UpdateDismissButtonVisibility() {
-  bool has_focus = false;
-  if (GetFocusManager()) {
-    views::View* focused_view = GetFocusManager()->GetFocusedView();
-    if (focused_view)
-      has_focus = Contains(focused_view);
-  }
-
-  dismiss_button_container_->SetVisible(IsMouseHovered() || has_focus);
-}
-
-void MediaNotificationContainerImplView::DismissNotification() {
-  for (auto& observer : observers_)
-    observer.OnMediaItemUIDismissed(id_);
-}
-
-void MediaNotificationContainerImplView::ForceExpandedState() {
-  if (view_) {
-    bool should_expand = has_many_actions_ || has_artwork_;
-    view_->SetForcedExpandedState(&should_expand);
-  }
-}
-
-void MediaNotificationContainerImplView::ContainerClicked() {
-  for (auto& observer : observers_)
-    observer.OnMediaItemUIClicked(id_);
-}
-
-void MediaNotificationContainerImplView::OnSizeChanged() {
-  gfx::Size new_size;
-  if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI)) {
-    new_size = kModernUISize;
-  } else {
-    new_size = is_expanded_ ? kExpandedSize : kNormalSize;
-  }
-
-  // |new_size| does not contain the height for the device selector view.
-  // If this view is present, we should query it for its preferred height and
-  // include that in |new_size|.
-  if (device_selector_view_) {
-    auto device_selector_view_size = device_selector_view_->GetPreferredSize();
-    DCHECK(device_selector_view_size.width() == kWidth);
-    new_size.set_height(new_size.height() + device_selector_view_size.height());
-    view_->UpdateDeviceSelectorAvailability(
-        device_selector_view_->GetVisible());
-  }
-
-  SetPreferredSize(new_size);
-  PreferredSizeChanged();
-
-  for (auto& observer : observers_)
-    observer.OnMediaItemUISizeChanged();
-}
-
-BEGIN_METADATA(MediaNotificationContainerImplView, views::Button)
-ADD_READONLY_PROPERTY_METADATA(std::u16string, Title)
-END_METADATA
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view_unittest.cc b/chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view_unittest.cc
deleted file mode 100644
index 7bfeb20f..0000000
--- a/chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view_unittest.cc
+++ /dev/null
@@ -1,458 +0,0 @@
-// Copyright 2019 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/ui/views/global_media_controls/media_notification_container_impl_view.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/containers/flat_set.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/test/scoped_feature_list.h"
-#include "build/build_config.h"
-#include "chrome/browser/media/router/media_router_feature.h"
-#include "chrome/browser/ui/global_media_controls/cast_media_notification_item.h"
-#include "chrome/browser/ui/global_media_controls/cast_media_session_controller.h"
-#include "chrome/browser/ui/global_media_controls/test_helper.h"
-#include "chrome/test/base/testing_profile.h"
-#include "chrome/test/views/chrome_views_test_base.h"
-#include "components/global_media_controls/public/test/mock_media_item_manager.h"
-#include "components/global_media_controls/public/test/mock_media_item_ui_observer.h"
-#include "components/media_router/browser/media_router_factory.h"
-#include "components/media_router/browser/test/mock_media_router.h"
-#include "media/base/media_switches.h"
-#include "services/media_session/public/mojom/media_session.mojom.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "ui/display/test/scoped_screen_override.h"
-#include "ui/display/test/test_screen.h"
-#include "ui/events/base_event_utils.h"
-#include "ui/events/test/event_generator.h"
-#include "ui/views/test/button_test_api.h"
-#include "ui/views/test/view_metadata_test_utils.h"
-#include "ui/views/widget/widget_utils.h"
-
-using media_session::mojom::MediaPlaybackState;
-using media_session::mojom::MediaSessionAction;
-using ::testing::_;
-using ::testing::NiceMock;
-
-namespace {
-
-const char kTestNotificationId[] = "testid";
-const char kOtherTestNotificationId[] = "othertestid";
-const char kRouteId[] = "route_id";
-
-media_router::MediaRoute CreateMediaRoute() {
-  media_router::MediaRoute route(kRouteId,
-                                 media_router::MediaSource("source_id"),
-                                 "sink_id", "route_description",
-                                 /* is_local */ true, /* for_display */ true);
-  route.set_media_sink_name("sink_name");
-  return route;
-}
-
-class MockSessionController : public CastMediaSessionController {
- public:
-  MockSessionController(
-      mojo::Remote<media_router::mojom::MediaController> remote)
-      : CastMediaSessionController(std::move(remote)) {}
-
-  MOCK_METHOD1(Send, void(media_session::mojom::MediaSessionAction));
-  MOCK_METHOD1(OnMediaStatusUpdated, void(media_router::mojom::MediaStatusPtr));
-};
-
-}  // anonymous namespace
-
-class MediaNotificationContainerImplViewTest : public ChromeViewsTestBase {
- public:
-  MediaNotificationContainerImplViewTest() : screen_override_(&fake_screen_) {}
-  MediaNotificationContainerImplViewTest(
-      const MediaNotificationContainerImplViewTest&) = delete;
-  MediaNotificationContainerImplViewTest& operator=(
-      const MediaNotificationContainerImplViewTest&) = delete;
-  ~MediaNotificationContainerImplViewTest() override = default;
-
-  // ViewsTestBase:
-  void SetUp() override {
-    ViewsTestBase::SetUp();
-    item_ = std::make_unique<NiceMock<MockMediaNotificationItem>>();
-    SetUpCommon(std::make_unique<MediaNotificationContainerImplView>(
-        kTestNotificationId, item_->GetWeakPtr(), nullptr,
-        GlobalMediaControlsEntryPoint::kToolbarIcon, nullptr));
-  }
-
-  void SetUpCommon(std::unique_ptr<MediaNotificationContainerImplView>
-                       notification_container) {
-    widget_ = CreateTestWidget();
-
-    notification_container_ =
-        widget_->SetContentsView(std::move(notification_container));
-
-    observer_ = std::make_unique<
-        NiceMock<global_media_controls::test::MockMediaItemUIObserver>>();
-    notification_container_->AddObserver(observer_.get());
-
-    SimulateMediaSessionData();
-
-    widget_->Show();
-  }
-
-  void TearDown() override {
-    notification_container_->RemoveObserver(observer_.get());
-    widget_.reset();
-    ViewsTestBase::TearDown();
-  }
-
-  void SimulateNotificationSwipedToDismiss() {
-    // When the notification is swiped, the SlideOutController sends this to the
-    // MediaNotificationContainerImplView.
-    notification_container()->OnSlideOut();
-  }
-
-  bool IsDismissButtonVisible() { return GetDismissButton()->IsDrawn(); }
-
-  void SimulateHoverOverContainer() {
-    fake_screen_.set_cursor_screen_point(
-        notification_container_->GetBoundsInScreen().CenterPoint());
-
-    ui::MouseEvent event(ui::ET_MOUSE_ENTERED, gfx::Point(), gfx::Point(),
-                         ui::EventTimeForNow(), 0, 0);
-    notification_container_->OnMouseEntered(event);
-  }
-
-  void SimulateNotHoveringOverContainer() {
-    gfx::Rect container_bounds = notification_container_->GetBoundsInScreen();
-    gfx::Point point_outside_container =
-        container_bounds.bottom_right() + gfx::Vector2d(1, 1);
-    fake_screen_.set_cursor_screen_point(point_outside_container);
-
-    ui::MouseEvent event(ui::ET_MOUSE_EXITED, gfx::Point(), gfx::Point(),
-                         ui::EventTimeForNow(), 0, 0);
-    notification_container_->OnMouseExited(event);
-  }
-
-  void SimulateContainerClicked() {
-    ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
-                         ui::EventTimeForNow(), 0, 0);
-    views::test::ButtonTestApi(notification_container_).NotifyClick(event);
-  }
-
-  void SimulateHeaderClicked() {
-    ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
-                         ui::EventTimeForNow(), 0, 0);
-    views::test::ButtonTestApi(GetView()->GetHeaderRowForTesting())
-        .NotifyClick(event);
-  }
-
-  void SimulateDismissButtonClicked() {
-    ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
-                         ui::EventTimeForNow(), 0, 0);
-    views::test::ButtonTestApi(GetDismissButton()).NotifyClick(event);
-  }
-
-  void SimulatePressingDismissButtonWithKeyboard() {
-    GetFocusManager()->SetFocusedView(
-        notification_container_->GetDismissButtonForTesting());
-
-// On Mac OS, we need to use the space bar to press a button.
-#if defined(OS_MAC)
-    ui::KeyboardCode button_press_keycode = ui::VKEY_SPACE;
-#else
-    ui::KeyboardCode button_press_keycode = ui::VKEY_RETURN;
-#endif  // defined(OS_MAC)
-
-    ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
-    generator.PressKey(button_press_keycode, 0);
-  }
-
-  void SimulateSessionPlaying() { SimulateSessionInfo(true); }
-
-  void SimulateSessionPaused() { SimulateSessionInfo(false); }
-
-  void SimulateMetadataChanged() {
-    media_session::MediaMetadata metadata;
-    metadata.source_title = u"source_title2";
-    metadata.title = u"title2";
-    metadata.artist = u"artist2";
-    GetView()->UpdateWithMediaMetadata(metadata);
-  }
-
-  void SimulateAllActionsEnabled() {
-    actions_.insert(MediaSessionAction::kPlay);
-    actions_.insert(MediaSessionAction::kPause);
-    actions_.insert(MediaSessionAction::kPreviousTrack);
-    actions_.insert(MediaSessionAction::kNextTrack);
-    actions_.insert(MediaSessionAction::kSeekBackward);
-    actions_.insert(MediaSessionAction::kSeekForward);
-    actions_.insert(MediaSessionAction::kStop);
-
-    NotifyUpdatedActions();
-  }
-
-  void SimulateOnlyPlayPauseEnabled() {
-    actions_.clear();
-    actions_.insert(MediaSessionAction::kPlay);
-    actions_.insert(MediaSessionAction::kPause);
-    NotifyUpdatedActions();
-  }
-
-  void SimulateHasArtwork() {
-    SkBitmap image;
-    image.allocN32Pixels(10, 10);
-    image.eraseColor(SK_ColorMAGENTA);
-    GetView()->UpdateWithMediaArtwork(
-        gfx::ImageSkia::CreateFrom1xBitmap(image));
-  }
-
-  void SimulateHasNoArtwork() {
-    GetView()->UpdateWithMediaArtwork(gfx::ImageSkia());
-  }
-
-  views::FocusManager* GetFocusManager() {
-    return notification_container_->GetFocusManager();
-  }
-
-  global_media_controls::test::MockMediaItemUIObserver& observer() {
-    return *observer_;
-  }
-
-  MediaNotificationContainerImplView* notification_container() {
-    return notification_container_;
-  }
-
-  base::WeakPtr<MockMediaNotificationItem> notification_item() {
-    return item_->GetWeakPtr();
-  }
-
- private:
-  void SimulateSessionInfo(bool playing) {
-    media_session::mojom::MediaSessionInfoPtr session_info(
-        media_session::mojom::MediaSessionInfo::New());
-    session_info->playback_state =
-        playing ? MediaPlaybackState::kPlaying : MediaPlaybackState::kPaused;
-    session_info->is_controllable = true;
-
-    GetView()->UpdateWithMediaSessionInfo(std::move(session_info));
-  }
-
-  void SimulateMediaSessionData() {
-    SimulateSessionInfo(true);
-
-    media_session::MediaMetadata metadata;
-    metadata.source_title = u"source_title";
-    metadata.title = u"title";
-    metadata.artist = u"artist";
-    GetView()->UpdateWithMediaMetadata(metadata);
-
-    SimulateOnlyPlayPauseEnabled();
-  }
-
-  void NotifyUpdatedActions() { GetView()->UpdateWithMediaActions(actions_); }
-
-  media_message_center::MediaNotificationViewImpl* GetView() {
-    return notification_container()->view_for_testing();
-  }
-
-  views::ImageButton* GetDismissButton() {
-    return notification_container()->GetDismissButtonForTesting();
-  }
-
-  std::unique_ptr<views::Widget> widget_;
-  MediaNotificationContainerImplView* notification_container_ = nullptr;
-  std::unique_ptr<global_media_controls::test::MockMediaItemUIObserver>
-      observer_;
-  std::unique_ptr<MockMediaNotificationItem> item_;
-
-  // Set of actions currently enabled.
-  base::flat_set<MediaSessionAction> actions_;
-
-  display::test::TestScreen fake_screen_;
-  display::test::ScopedScreenOverride screen_override_;
-};
-
-// TODO(b/185139027): Remove this class once
-// |media_router::kGlobalMediaControlsCastStartStop| is enabled by default.
-class MediaNotificationContainerImplViewCastTest
-    : public MediaNotificationContainerImplViewTest {
- public:
-  void SetUp() override {
-    ViewsTestBase::SetUp();
-    feature_list_.InitWithFeatures(
-        {media::kGlobalMediaControlsForCast,
-         media_router::kGlobalMediaControlsCastStartStop},
-        {});
-
-    media_router::MediaRouterFactory::GetInstance()->SetTestingFactory(
-        &profile_, base::BindRepeating(&media_router::MockMediaRouter::Create));
-
-    auto session_controller = std::make_unique<MockSessionController>(
-        mojo::Remote<media_router::mojom::MediaController>());
-    session_controller_ = session_controller.get();
-    item_ = std::make_unique<CastMediaNotificationItem>(
-        CreateMediaRoute(), &item_manager_, std::move(session_controller),
-        &profile_);
-
-    SetUpCommon(std::make_unique<MediaNotificationContainerImplView>(
-        kTestNotificationId, item_->GetWeakPtr(), nullptr,
-        GlobalMediaControlsEntryPoint::kToolbarIcon, profile()));
-  }
-
-  void TearDown() override {
-    // Delete |item_| before |item_manager_|.
-    item_.reset();
-    MediaNotificationContainerImplViewTest::TearDown();
-  }
-
-  void SimulateStopCastingButtonClicked() {
-    ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
-                         ui::EventTimeForNow(), 0, 0);
-    views::test::ButtonTestApi(
-        notification_container()->GetStopCastingButtonForTesting())
-        .NotifyClick(event);
-  }
-
-  CastMediaNotificationItem* item() { return item_.get(); }
-  Profile* profile() { return &profile_; }
-  global_media_controls::test::MockMediaItemManager* item_manager() {
-    return &item_manager_;
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-  TestingProfile profile_;
-  std::unique_ptr<CastMediaNotificationItem> item_;
-  NiceMock<global_media_controls::test::MockMediaItemManager> item_manager_;
-  MockSessionController* session_controller_ = nullptr;
-};
-
-TEST_F(MediaNotificationContainerImplViewCastTest, StopCasting) {
-  auto* mock_router = static_cast<media_router::MockMediaRouter*>(
-      media_router::MediaRouterFactory::GetApiForBrowserContext(profile()));
-  EXPECT_CALL(*mock_router, TerminateRoute(kRouteId));
-
-  SimulateStopCastingButtonClicked();
-}
-
-TEST_F(MediaNotificationContainerImplViewTest, SwipeToDismiss) {
-  EXPECT_CALL(observer(), OnMediaItemUIDismissed(kTestNotificationId));
-  SimulateNotificationSwipedToDismiss();
-}
-
-TEST_F(MediaNotificationContainerImplViewTest, ClickToDismiss) {
-  // Ensure that the mouse is not over the container and that nothing is
-  // focused. The dismiss button should not be visible.
-  SimulateNotHoveringOverContainer();
-  ASSERT_EQ(nullptr, GetFocusManager()->GetFocusedView());
-  ASSERT_FALSE(notification_container()->IsMouseHovered());
-  EXPECT_FALSE(IsDismissButtonVisible());
-
-  // Hovering over the notification should show the dismiss button.
-  SimulateHoverOverContainer();
-  EXPECT_TRUE(IsDismissButtonVisible());
-
-  // Moving the mouse away from the notification should hide the dismiss button.
-  SimulateNotHoveringOverContainer();
-  EXPECT_FALSE(IsDismissButtonVisible());
-
-  // Moving the mouse back over the notification should re-show it.
-  SimulateHoverOverContainer();
-  EXPECT_TRUE(IsDismissButtonVisible());
-
-  // Clicking it should inform observers that we've been dismissed.
-  EXPECT_CALL(observer(), OnMediaItemUIDismissed(kTestNotificationId));
-  SimulateDismissButtonClicked();
-  testing::Mock::VerifyAndClearExpectations(&observer());
-}
-
-TEST_F(MediaNotificationContainerImplViewTest, KeyboardToDismiss) {
-  // Ensure that the mouse is not over the container and that nothing is
-  // focused. The dismiss button should not be visible.
-  SimulateNotHoveringOverContainer();
-  ASSERT_EQ(nullptr, GetFocusManager()->GetFocusedView());
-  ASSERT_FALSE(notification_container()->IsMouseHovered());
-  EXPECT_FALSE(IsDismissButtonVisible());
-
-  // When the notification receives keyboard focus, the dismiss button should be
-  // visible.
-  GetFocusManager()->SetFocusedView(notification_container());
-  EXPECT_TRUE(IsDismissButtonVisible());
-
-  // When the notification loses keyboard focus, the dismiss button should be
-  // hidden.
-  GetFocusManager()->SetFocusedView(nullptr);
-  EXPECT_FALSE(IsDismissButtonVisible());
-
-  // If it gets focus again, it should re-show the dismiss button.
-  GetFocusManager()->SetFocusedView(notification_container());
-  EXPECT_TRUE(IsDismissButtonVisible());
-
-  // Clicking it should inform observers that we've been dismissed.
-  EXPECT_CALL(observer(), OnMediaItemUIDismissed(kTestNotificationId));
-  SimulatePressingDismissButtonWithKeyboard();
-  testing::Mock::VerifyAndClearExpectations(&observer());
-}
-
-TEST_F(MediaNotificationContainerImplViewTest, ForceExpandedState) {
-  // When we have many actions enabled, we should be forced into the expanded
-  // state.
-  SimulateAllActionsEnabled();
-  EXPECT_TRUE(notification_container()->is_expanded_for_testing());
-
-  // When we don't have many actions enabled, we should be forced out of the
-  // expanded state.
-  SimulateOnlyPlayPauseEnabled();
-  EXPECT_FALSE(notification_container()->is_expanded_for_testing());
-
-  // We will also be forced into the expanded state when artwork is present.
-  SimulateHasArtwork();
-  EXPECT_TRUE(notification_container()->is_expanded_for_testing());
-
-  // Once the artwork is gone, we should be forced back out of the expanded
-  // state.
-  SimulateHasNoArtwork();
-  EXPECT_FALSE(notification_container()->is_expanded_for_testing());
-}
-
-TEST_F(MediaNotificationContainerImplViewTest, SendsMetadataUpdates) {
-  EXPECT_CALL(observer(), OnMediaItemUIMetadataChanged());
-  SimulateMetadataChanged();
-}
-
-TEST_F(MediaNotificationContainerImplViewTest, SendsDestroyedUpdates) {
-  auto container = std::make_unique<MediaNotificationContainerImplView>(
-      kOtherTestNotificationId, notification_item(), nullptr,
-      GlobalMediaControlsEntryPoint::kToolbarIcon, nullptr);
-  global_media_controls::test::MockMediaItemUIObserver observer;
-  container->AddObserver(&observer);
-
-  // When the container is destroyed, it should notify the observer.
-  EXPECT_CALL(observer, OnMediaItemUIDestroyed(kOtherTestNotificationId));
-  container.reset();
-  testing::Mock::VerifyAndClearExpectations(&observer);
-}
-
-TEST_F(MediaNotificationContainerImplViewTest, SendsClicks) {
-  // When the container is clicked directly, it should notify its observers.
-  EXPECT_CALL(observer(), OnMediaItemUIClicked(kTestNotificationId));
-  SimulateContainerClicked();
-  testing::Mock::VerifyAndClearExpectations(&observer());
-
-  // It should also notify its observers when the header is clicked.
-  EXPECT_CALL(observer(), OnMediaItemUIClicked(kTestNotificationId));
-  SimulateHeaderClicked();
-}
-
-TEST_F(MediaNotificationContainerImplViewTest, SendsSinkUpdates) {
-  // The container should notify its observers when an audio output device has
-  // been chosen.
-  EXPECT_CALL(observer(), OnAudioSinkChosen(kTestNotificationId, "foobar"));
-  notification_container()->OnAudioSinkChosen("foobar");
-}
-
-TEST_F(MediaNotificationContainerImplViewTest, MetadataTest) {
-  auto container_view = std::make_unique<MediaNotificationContainerImplView>(
-      kOtherTestNotificationId, notification_item(), nullptr,
-      GlobalMediaControlsEntryPoint::kToolbarIcon, nullptr);
-  views::test::TestViewMetadata(container_view.get());
-}
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_observer.h b/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_observer.h
deleted file mode 100644
index d9d5824..0000000
--- a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_observer.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_SELECTOR_OBSERVER_H_
-#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_SELECTOR_OBSERVER_H_
-
-#include <map>
-
-#include "base/observer_list_types.h"
-
-class DeviceEntryUI;
-
-class MediaNotificationDeviceSelectorObserver : public base::CheckedObserver {
- public:
-  // Called by MediaNotificationDeviceSelector view when available devices
-  // changed.
-  virtual void OnMediaNotificationDeviceSelectorUpdated(
-      const std::map<int, DeviceEntryUI*>& device_entries_map) = 0;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_SELECTOR_OBSERVER_H_
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_delegate.h b/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_delegate.h
deleted file mode 100644
index 0b94983..0000000
--- a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_delegate.h
+++ /dev/null
@@ -1,23 +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 CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_SELECTOR_VIEW_DELEGATE_H_
-#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_SELECTOR_VIEW_DELEGATE_H_
-
-#include "base/callback_list.h"
-#include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
-
-class MediaNotificationDeviceSelectorViewDelegate {
- public:
-  virtual void OnAudioSinkChosen(const std::string& sink_id) = 0;
-  virtual void OnDeviceSelectorViewSizeChanged() = 0;
-  virtual base::CallbackListSubscription
-  RegisterAudioOutputDeviceDescriptionsCallback(
-      MediaNotificationDeviceProvider::GetOutputDevicesCallbackList::
-          CallbackType callback) = 0;
-  virtual base::CallbackListSubscription
-  RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
-      base::RepeatingCallback<void(bool)> callback) = 0;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_SELECTOR_VIEW_DELEGATE_H_
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_footer_view.h b/chrome/browser/ui/views/global_media_controls/media_notification_footer_view.h
deleted file mode 100644
index 8070ae1..0000000
--- a/chrome/browser/ui/views/global_media_controls/media_notification_footer_view.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_FOOTER_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_FOOTER_VIEW_H_
-
-#include "chrome/browser/ui/views/global_media_controls/media_notification_device_selector_observer.h"
-#include "ui/gfx/color_palette.h"
-#include "ui/views/controls/button/button.h"
-#include "ui/views/view.h"
-
-namespace {
-class DeviceEntryButton;
-}  // anonymous namespace
-
-// A footer view attached to media_notification_view_impl containing
-// available cast devices and volume controls.
-class MediaNotificationFooterView
-    : public views::View,
-      public MediaNotificationDeviceSelectorObserver {
- public:
-  class Delegate {
-   public:
-    virtual ~Delegate() = default;
-
-    virtual void OnDeviceSelected(int tag) = 0;
-    virtual void OnDropdownButtonClicked() = 0;
-    virtual bool IsDeviceSelectorExpanded() = 0;
-  };
-
-  MediaNotificationFooterView(
-      bool is_cast_session,
-      views::Button::PressedCallback stop_casting_callback);
-  ~MediaNotificationFooterView() override = default;
-
-  void OnColorChanged(SkColor foreground);
-  void SetDelegate(Delegate* delegate);
-
-  // MediaNotificationDeviceselectorobserver
-  void OnMediaNotificationDeviceSelectorUpdated(
-      const std::map<int, DeviceEntryUI*>& device_entries_map) override;
-
-  void Layout() override;
-
- private:
-  void UpdateButtonsColor();
-  void OnDeviceSelected(int tag);
-  void OnOverflowButtonClicked();
-
-  SkColor foreground_color_ = gfx::kPlaceholderColor;
-
-  DeviceEntryButton* overflow_button_ = nullptr;
-
-  Delegate* delegate_ = nullptr;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_FOOTER_VIEW_H_
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_list_view.cc b/chrome/browser/ui/views/global_media_controls/media_notification_list_view.cc
deleted file mode 100644
index 86cd1ad9..0000000
--- a/chrome/browser/ui/views/global_media_controls/media_notification_list_view.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2019 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/ui/views/global_media_controls/media_notification_list_view.h"
-
-#include "base/containers/contains.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
-#include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/color/color_id.h"
-#include "ui/color/color_provider.h"
-#include "ui/views/border.h"
-#include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
-#include "ui/views/layout/box_layout.h"
-
-namespace {
-
-constexpr int kMediaListMaxHeight = 478;
-
-// Thickness of separator border.
-constexpr int kMediaListSeparatorThickness = 2;
-
-std::unique_ptr<views::Border> CreateMediaListSeparatorBorder(SkColor color,
-                                                              int thickness) {
-  return views::CreateSolidSidedBorder(/*top=*/thickness,
-                                       /*left=*/0,
-                                       /*bottom=*/0,
-                                       /*right=*/0, color);
-}
-
-}  // anonymous namespace
-
-MediaNotificationListView::SeparatorStyle::SeparatorStyle(
-    SkColor separator_color,
-    int separator_thickness)
-    : separator_color(separator_color),
-      separator_thickness(separator_thickness) {}
-
-MediaNotificationListView::MediaNotificationListView()
-    : MediaNotificationListView(absl::nullopt) {}
-
-MediaNotificationListView::MediaNotificationListView(
-    const absl::optional<SeparatorStyle>& separator_style)
-    : separator_style_(separator_style) {
-  SetBackgroundColor(absl::nullopt);
-  SetContents(std::make_unique<views::View>());
-  contents()->SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kVertical));
-  ClipHeightTo(0, kMediaListMaxHeight);
-
-  SetVerticalScrollBar(
-      std::make_unique<views::OverlayScrollBar>(/*horizontal=*/false));
-  SetHorizontalScrollBar(
-      std::make_unique<views::OverlayScrollBar>(/*horizontal=*/true));
-}
-
-MediaNotificationListView::~MediaNotificationListView() = default;
-
-void MediaNotificationListView::ShowNotification(
-    const std::string& id,
-    std::unique_ptr<MediaNotificationContainerImplView> notification) {
-  DCHECK(!base::Contains(notifications_, id));
-  DCHECK_NE(nullptr, notification.get());
-
-  // If this isn't the first notification, then create a top-sided separator
-  // border.
-  if (!notifications_.empty()) {
-    if (separator_style_.has_value()) {
-      notification->SetBorder(CreateMediaListSeparatorBorder(
-          separator_style_->separator_color,
-          separator_style_->separator_thickness));
-    } else {
-      notification->SetBorder(CreateMediaListSeparatorBorder(
-          GetColorProvider()->GetColor(ui::kColorMenuSeparator),
-          kMediaListSeparatorThickness));
-    }
-  }
-
-  notifications_[id] = contents()->AddChildView(std::move(notification));
-
-  contents()->InvalidateLayout();
-  PreferredSizeChanged();
-}
-
-void MediaNotificationListView::HideNotification(const std::string& id) {
-  RemoveNotification(id);
-}
-
-std::unique_ptr<MediaNotificationContainerImplView>
-MediaNotificationListView::RemoveNotification(const std::string& id) {
-  if (!base::Contains(notifications_, id))
-    return nullptr;
-
-  // If we're removing the topmost notification and there are others, then we
-  // need to remove the top-sided separator border from the new topmost
-  // notification.
-  if (contents()->children().size() > 1 &&
-      contents()->children().at(0) == notifications_[id]) {
-    contents()->children().at(1)->SetBorder(nullptr);
-  }
-
-  // Remove the notification. Note that since |RemoveChildView()| does not
-  // delete the notification, we now have ownership.
-  contents()->RemoveChildView(notifications_[id]);
-  std::unique_ptr<MediaNotificationContainerImplView> notification(
-      notifications_[id]);
-  notifications_.erase(id);
-
-  contents()->InvalidateLayout();
-  PreferredSizeChanged();
-
-  return notification;
-}
-
-BEGIN_METADATA(MediaNotificationListView, views::ScrollView)
-END_METADATA
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_list_view.h b/chrome/browser/ui/views/global_media_controls/media_notification_list_view.h
deleted file mode 100644
index ed08c91..0000000
--- a/chrome/browser/ui/views/global_media_controls/media_notification_list_view.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2019 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_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_LIST_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_LIST_VIEW_H_
-
-#include <map>
-#include <memory>
-
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "ui/base/metadata/metadata_header_macros.h"
-#include "ui/views/controls/scroll_view.h"
-
-class MediaNotificationContainerImplView;
-
-// MediaNotificationListView is a container that holds a list of active media
-// sessions.
-class MediaNotificationListView : public views::ScrollView {
- public:
-  METADATA_HEADER(MediaNotificationListView);
-  struct SeparatorStyle {
-    SeparatorStyle(SkColor separator_color, int separator_thickness);
-
-    const SkColor separator_color;
-    const int separator_thickness;
-  };
-
-  explicit MediaNotificationListView(
-      const absl::optional<SeparatorStyle>& separator_style);
-  MediaNotificationListView();
-  MediaNotificationListView(const MediaNotificationListView&) = delete;
-  MediaNotificationListView& operator=(const MediaNotificationListView&) =
-      delete;
-  ~MediaNotificationListView() override;
-
-  // Adds the given notification into the list.
-  void ShowNotification(
-      const std::string& id,
-      std::unique_ptr<MediaNotificationContainerImplView> notification);
-
-  // Removes the given notification from the list.
-  void HideNotification(const std::string& id);
-
-  bool empty() { return notifications_.empty(); }
-
-  const std::map<const std::string, MediaNotificationContainerImplView*>&
-  notifications_for_testing() const {
-    return notifications_;
-  }
-
- private:
-  std::unique_ptr<MediaNotificationContainerImplView> RemoveNotification(
-      const std::string& id);
-
-  std::map<const std::string, MediaNotificationContainerImplView*>
-      notifications_;
-
-  absl::optional<SeparatorStyle> separator_style_;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_LIST_VIEW_H_
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_list_view_unittest.cc b/chrome/browser/ui/views/global_media_controls/media_notification_list_view_unittest.cc
deleted file mode 100644
index 8d4a678..0000000
--- a/chrome/browser/ui/views/global_media_controls/media_notification_list_view_unittest.cc
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright 2019 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/ui/views/global_media_controls/media_notification_list_view.h"
-
-#include <memory>
-#include <string>
-
-#include "chrome/browser/ui/global_media_controls/test_helper.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
-#include "chrome/test/views/chrome_views_test_base.h"
-
-using testing::NiceMock;
-
-namespace {
-
-// Test IDs for notifications.
-const char kTestNotificationId1[] = "testid1";
-const char kTestNotificationId2[] = "testid2";
-const char kTestNotificationId3[] = "testid3";
-
-}  // anonymous namespace
-
-class MediaNotificationListViewTest : public ChromeViewsTestBase {
- public:
-  MediaNotificationListViewTest() = default;
-  MediaNotificationListViewTest(const MediaNotificationListViewTest&) = delete;
-  MediaNotificationListViewTest& operator=(
-      const MediaNotificationListViewTest&) = delete;
-  ~MediaNotificationListViewTest() override = default;
-
-  // ViewsTestBase:
-  void SetUp() override {
-    ViewsTestBase::SetUp();
-
-    widget_ = CreateTestWidget();
-
-    list_view_ =
-        widget_->SetContentsView(std::make_unique<MediaNotificationListView>());
-
-    item_ = std::make_unique<NiceMock<MockMediaNotificationItem>>();
-    widget_->Show();
-  }
-
-  void TearDown() override {
-    widget_.reset();
-    ViewsTestBase::TearDown();
-  }
-
-  void ShowNotification(const std::string& id) {
-    list_view_->ShowNotification(
-        id, std::make_unique<MediaNotificationContainerImplView>(
-                id, item_->GetWeakPtr(), nullptr,
-                GlobalMediaControlsEntryPoint::kToolbarIcon, nullptr));
-  }
-
-  void HideNotification(const std::string& id) {
-    list_view_->HideNotification(id);
-  }
-
-  MediaNotificationListView* list_view() { return list_view_; }
-
- private:
-  std::unique_ptr<views::Widget> widget_;
-  MediaNotificationListView* list_view_ = nullptr;
-  std::unique_ptr<MockMediaNotificationItem> item_;
-};
-
-TEST_F(MediaNotificationListViewTest, NoSeparatorForOneNotification) {
-  // Show a single notification.
-  ShowNotification(kTestNotificationId1);
-
-  // There should be just one notification.
-  EXPECT_EQ(1u, list_view()->notifications_for_testing().size());
-
-  // Since there's only one, there should be no separator line.
-  EXPECT_EQ(nullptr, list_view()
-                         ->notifications_for_testing()
-                         .at(kTestNotificationId1)
-                         ->GetBorder());
-}
-
-TEST_F(MediaNotificationListViewTest, SeparatorBetweenNotifications) {
-  // Show two notifications.
-  ShowNotification(kTestNotificationId1);
-  ShowNotification(kTestNotificationId2);
-
-  // There should be two notifications.
-  EXPECT_EQ(2u, list_view()->notifications_for_testing().size());
-
-  // There should be a separator between them. Since the separators are
-  // top-sided, the bottom notification should have one.
-  EXPECT_EQ(nullptr, list_view()
-                         ->notifications_for_testing()
-                         .at(kTestNotificationId1)
-                         ->GetBorder());
-  EXPECT_NE(nullptr, list_view()
-                         ->notifications_for_testing()
-                         .at(kTestNotificationId2)
-                         ->GetBorder());
-}
-
-TEST_F(MediaNotificationListViewTest, SeparatorRemovedWhenNotificationRemoved) {
-  // Show three notifications.
-  ShowNotification(kTestNotificationId1);
-  ShowNotification(kTestNotificationId2);
-  ShowNotification(kTestNotificationId3);
-
-  // There should be three notifications.
-  EXPECT_EQ(3u, list_view()->notifications_for_testing().size());
-
-  // There should be separators.
-  EXPECT_EQ(nullptr, list_view()
-                         ->notifications_for_testing()
-                         .at(kTestNotificationId1)
-                         ->GetBorder());
-  EXPECT_NE(nullptr, list_view()
-                         ->notifications_for_testing()
-                         .at(kTestNotificationId2)
-                         ->GetBorder());
-  EXPECT_NE(nullptr, list_view()
-                         ->notifications_for_testing()
-                         .at(kTestNotificationId3)
-                         ->GetBorder());
-
-  // Remove the topmost notification.
-  HideNotification(kTestNotificationId1);
-
-  // There should be two notifications.
-  EXPECT_EQ(2u, list_view()->notifications_for_testing().size());
-
-  // The new top notification should have lost its top separator.
-  EXPECT_EQ(nullptr, list_view()
-                         ->notifications_for_testing()
-                         .at(kTestNotificationId2)
-                         ->GetBorder());
-  EXPECT_NE(nullptr, list_view()
-                         ->notifications_for_testing()
-                         .at(kTestNotificationId3)
-                         ->GetBorder());
-}
diff --git a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.cc b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.cc
index eb43f0f..bd46690 100644
--- a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.cc
@@ -145,8 +145,9 @@
   if (MediaDialogView::IsShowing()) {
     MediaDialogView::HideDialog();
   } else {
-    MediaDialogView::ShowDialog(this, service_, browser_->profile(),
-                                GlobalMediaControlsEntryPoint::kToolbarIcon);
+    MediaDialogView::ShowDialog(
+        this, service_, browser_->profile(),
+        global_media_controls::GlobalMediaControlsEntryPoint::kToolbarIcon);
     ClosePromoBubble();
 
     for (auto& observer : observers_)
diff --git a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
index 7b02719..844c460 100644
--- a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
+++ b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
@@ -81,7 +81,7 @@
   scoped_widget_observations_.AddObservation(
       MediaDialogView::ShowDialogForPresentationRequest(
           media_button, service, profile, initiator(),
-          GlobalMediaControlsEntryPoint::kPresentation));
+          global_media_controls::GlobalMediaControlsEntryPoint::kPresentation));
   return true;
 }
 
diff --git a/chrome/browser/ui/views/page_info/page_info_main_view.cc b/chrome/browser/ui/views/page_info/page_info_main_view.cc
index d96fb23a..77840fd 100644
--- a/chrome/browser/ui/views/page_info/page_info_main_view.cc
+++ b/chrome/browser/ui/views/page_info/page_info_main_view.cc
@@ -88,11 +88,11 @@
       ->SetOrientation(views::LayoutOrientation::kVertical);
 
   if (base::FeatureList::IsEnabled(page_info::kPageInfoAboutThisSite)) {
-    std::u16string description = ui_delegate_->GetAboutThisSiteDescription();
-    if (!description.empty()) {
+    auto info = ui_delegate_->GetAboutThisSiteInfo();
+    if (info) {
       layout->StartRow(views::GridLayout::kFixedSize, kColumnId);
       about_this_site_section_ =
-          layout->AddView(CreateAboutThisSiteSection(description));
+          layout->AddView(CreateAboutThisSiteSection(info));
     }
   }
 
@@ -501,7 +501,7 @@
 }
 
 std::unique_ptr<views::View> PageInfoMainView::CreateAboutThisSiteSection(
-    std::u16string description) {
+    const absl::optional<page_info::proto::SiteInfo> info) {
   auto about_this_site_section = std::make_unique<views::View>();
   about_this_site_section
       ->SetLayoutManager(std::make_unique<views::FlexLayout>())
@@ -509,6 +509,8 @@
   about_this_site_section->AddChildView(PageInfoViewFactory::CreateSeparator());
 
   // TODO(crbug.com/1250653): Update with the actual strings.
+  // TODO(crbug.com/1250653): Update checks for the data being available and
+  // logic to determine short description showed as subtitle.
   auto* about_this_site_button = about_this_site_section->AddChildView(
       std::make_unique<PageInfoHoverButton>(
           base::BindRepeating(
@@ -518,7 +520,7 @@
               this),
           PageInfoViewFactory::GetAboutThisSiteIcon(), 0, std::u16string(),
           PageInfoViewFactory::VIEW_ID_PAGE_INFO_ABOUT_THIS_SITE_BUTTON,
-          std::u16string(), description,
+          std::u16string(), base::ASCIIToUTF16(info->entity_description()),
           PageInfoViewFactory::GetOpenSubpageIcon()));
   about_this_site_button->SetTitleText(u"About this site");
 
diff --git a/chrome/browser/ui/views/page_info/page_info_main_view.h b/chrome/browser/ui/views/page_info/page_info_main_view.h
index a329af5..b13d1e4 100644
--- a/chrome/browser/ui/views/page_info/page_info_main_view.h
+++ b/chrome/browser/ui/views/page_info/page_info_main_view.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/views/page_info/chosen_object_view_observer.h"
 #include "chrome/browser/ui/views/page_info/permission_selector_row_observer.h"
 #include "components/page_info/page_info_ui.h"
+#include "components/page_info/proto/about_this_site_metadata.pb.h"
 #include "device/vr/buildflags/buildflags.h"
 #include "ui/views/view.h"
 
@@ -95,7 +96,7 @@
   // Creates 'About this site' section which contains a button that opens a
   // subpage and two separators.
   std::unique_ptr<views::View> CreateAboutThisSiteSection(
-      std::u16string description) WARN_UNUSED_RESULT;
+      const absl::optional<page_info::proto::SiteInfo> info) WARN_UNUSED_RESULT;
 
   PageInfo* presenter_;
 
diff --git a/chrome/browser/ui/views/sad_tab_view_interactive_uitest.cc b/chrome/browser/ui/views/sad_tab_view_interactive_uitest.cc
index 09025c1..bb5b4f6 100644
--- a/chrome/browser/ui/views/sad_tab_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/sad_tab_view_interactive_uitest.cc
@@ -7,8 +7,10 @@
 #include "build/build_config.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/frame/window_frame_util.h"
 #include "chrome/browser/ui/sad_tab.h"
 #include "chrome/browser/ui/sad_tab_helper.h"
+#include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -155,10 +157,19 @@
   ASSERT_TRUE(IsFocusedViewInsideSadTab());
   ASSERT_FALSE(IsFocusedViewInsideBrowserToolbar());
 
-  // Pressing the Tab key should cycle focus back to the toolbar.
+  // Pressing the Tab key should cycle focus back to the toolbar or the browser
+  // frame if the tab search caption button is enabled.
   PressTab();
-  ASSERT_FALSE(IsFocusedViewInsideSadTab());
-  ASSERT_TRUE(IsFocusedViewInsideBrowserToolbar());
+  if (WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(browser())) {
+    const auto* frame_view = BrowserView::GetBrowserViewForBrowser(browser())
+                                 ->frame()
+                                 ->GetFrameView();
+    ASSERT_FALSE(IsFocusedViewInsideSadTab());
+    ASSERT_TRUE(frame_view->Contains(GetFocusedView()));
+  } else {
+    ASSERT_FALSE(IsFocusedViewInsideSadTab());
+    ASSERT_TRUE(IsFocusedViewInsideBrowserToolbar());
+  }
 
   // Keep pressing the Tab key and make sure we make it back to the sad tab.
   while (!IsFocusedViewInsideSadTab())
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index 3fc2bde..01372c2 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -2535,8 +2535,10 @@
 
   AddTabsAndResetBrowser(browser(), 1);
 
+  // We must ensure that we set the bounds of the browser window such that it is
+  // wide enough to allow the tab strip to expand to accommodate this tab.
   browser()->window()->SetBounds(
-      gfx::Rect(0, 0, TabStyle::GetStandardWidth() * 4, 400));
+      gfx::Rect(0, 0, TabStyle::GetStandardWidth() * 5, 400));
 
   const int tab_strip_width = tab_strip->width();
   const gfx::Point tab_1_center =
diff --git a/chrome/browser/ui/views/tabs/tab_search_button_browsertest.cc b/chrome/browser/ui/views/tabs/tab_search_button_browsertest.cc
index 0d506da..76af028 100644
--- a/chrome/browser/ui/views/tabs/tab_search_button_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/tab_search_button_browsertest.cc
@@ -6,12 +6,14 @@
 
 #include <vector>
 
+#include "base/feature_list.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
+#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/bubble/webui_bubble_dialog_view.h"
 #include "chrome/browser/ui/views/bubble/webui_bubble_manager.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -31,6 +33,15 @@
 
 class TabSearchButtonBrowserTest : public InProcessBrowserTest {
  public:
+#if defined(OS_WIN)
+  // InProcessBrowserTest:
+  void SetUp() override {
+    scoped_feature_list_.InitAndDisableFeature(
+        features::kWin10TabSearchCaptionButton);
+    InProcessBrowserTest::SetUp();
+  }
+#endif  // defined(OS_WIN);
+
   BrowserView* browser_view() {
     return BrowserView::GetBrowserViewForBrowser(browser());
   }
@@ -55,6 +66,11 @@
     run_loop.Run();
     ASSERT_EQ(nullptr, bubble_manager()->GetBubbleWidget());
   }
+
+#if defined(OS_WIN)
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+#endif  // defined(OS_WIN);
 };
 
 IN_PROC_BROWSER_TEST_F(TabSearchButtonBrowserTest, ButtonClickCreatesBubble) {
@@ -71,6 +87,13 @@
 class TabSearchButtonBrowserUITest : public DialogBrowserTest {
  public:
   // DialogBrowserTest:
+#if defined(OS_WIN)
+  void SetUp() override {
+    scoped_feature_list_.InitAndDisableFeature(
+        features::kWin10TabSearchCaptionButton);
+    DialogBrowserTest::SetUp();
+  }
+#endif  // defined(OS_WIN);
   void ShowUi(const std::string& name) override {
     AppendTab(chrome::kChromeUISettingsURL);
     AppendTab(chrome::kChromeUIHistoryURL);
@@ -84,6 +107,11 @@
   void AppendTab(std::string url) {
     chrome::AddTabAt(browser(), GURL(url), -1, true);
   }
+
+#if defined(OS_WIN)
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+#endif  // defined(OS_WIN);
 };
 
 // Invokes a tab search bubble.
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 249acbf..74a6244 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -1355,6 +1355,8 @@
         base::TimeTicks::Now() - new_tab_button_pressed_start_time_.value());
     new_tab_button_pressed_start_time_.reset();
   }
+
+  LogTabWidthsForTabScrolling();
 }
 
 void TabStrip::MoveTab(int from_model_index,
@@ -3367,6 +3369,19 @@
   StartResizeLayoutAnimation();
 }
 
+void TabStrip::LogTabWidthsForTabScrolling() {
+  int active_tab_width = GetActiveTabWidth();
+  int inactive_tab_width = GetInactiveTabWidth();
+
+  if (active_tab_width > 1) {
+    UMA_HISTOGRAM_EXACT_LINEAR("Tabs.ActiveTabWidth", active_tab_width, 257);
+  }
+  if (inactive_tab_width > 1) {
+    UMA_HISTOGRAM_EXACT_LINEAR("Tabs.InactiveTabWidth", inactive_tab_width,
+                               257);
+  }
+}
+
 void TabStrip::ResizeLayoutTabsFromTouch() {
   // Don't resize if the user is interacting with the tabstrip.
   if (!drag_context_->IsDragSessionActive())
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index 8319fac..128c0dff 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -658,6 +658,10 @@
   void AnnounceTabAddedToGroup(tab_groups::TabGroupId group_id);
   void AnnounceTabRemovedFromGroup(tab_groups::TabGroupId group_id);
 
+  // For metrics on the best size for tab scrolling, log if the different
+  // sizes would trigger tab scrolling
+  void LogTabWidthsForTabScrolling();
+
   // -- Member Variables ------------------------------------------------------
 
   base::ObserverList<TabStripObserver>::Unchecked observers_;
diff --git a/chrome/browser/ui/views/user_education/tip_marquee_view.cc b/chrome/browser/ui/views/user_education/tip_marquee_view.cc
index 788d87d..9a80ebd 100644
--- a/chrome/browser/ui/views/user_education/tip_marquee_view.cc
+++ b/chrome/browser/ui/views/user_education/tip_marquee_view.cc
@@ -300,7 +300,7 @@
 }
 
 std::u16string TipMarqueeView::GetTooltipText(const gfx::Point& p) const {
-  if (!IsPointInIcon(p))
+  if (!GetVisible() || !IsPointInIcon(p))
     return View::GetTooltipText(p);
 
   // TODO(pkasting): Localize
diff --git a/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.cc b/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.cc
index 5359f5a..59147d6 100644
--- a/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.cc
+++ b/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.cc
@@ -15,6 +15,17 @@
 #include "ui/base/interaction/element_tracker.h"
 #include "ui/views/interaction/element_tracker_views.h"
 
+namespace {
+
+// The amount of time the tutorial should stay onscreen depending on if the
+// bubble is interacted with it. similar to FeaturePromoBubbleView's timeouts
+constexpr base::TimeDelta kDelayNoInteraction =
+    base::TimeDelta::FromSeconds(10);
+constexpr base::TimeDelta kDelayWithInteraction =
+    base::TimeDelta::FromSeconds(3);
+constexpr base::TimeDelta kDelayZero = base::TimeDelta::FromSeconds(0);
+}  // namespace
+
 TutorialBubbleViews::TutorialBubbleViews(absl::optional<base::Token> bubble_id)
     : bubble_id_(bubble_id) {}
 
@@ -34,7 +45,8 @@
     absl::optional<std::u16string> title_text,
     absl::optional<std::u16string> body_text,
     TutorialDescription::Step::Arrow arrow,
-    absl::optional<std::pair<int, int>> progress) {
+    absl::optional<std::pair<int, int>> progress,
+    bool is_last_step) {
   if (!element->IsA<views::TrackedElementViews>())
     return nullptr;
 
@@ -81,6 +93,11 @@
       break;
   }
 
+  params.timeout_no_interaction =
+      is_last_step ? kDelayNoInteraction : kDelayZero;
+  params.timeout_after_interaction =
+      is_last_step ? kDelayWithInteraction : kDelayZero;
+
   return std::make_unique<TutorialBubbleViews>(
       owner_impl->ShowBubble(std::move(params), base::BindOnce([] {})));
 }
diff --git a/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.h b/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.h
index 26bc785..a929281 100644
--- a/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.h
+++ b/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.h
@@ -29,7 +29,8 @@
       absl::optional<std::u16string> title_text,
       absl::optional<std::u16string> body_text,
       TutorialDescription::Step::Arrow arrow,
-      absl::optional<std::pair<int, int>> progress) override;
+      absl::optional<std::pair<int, int>> progress,
+      bool is_last_step) override;
 
   bool CanBuildBubbleForTrackedElement(ui::TrackedElement* element) override;
 };
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index cd680b58..38d965a 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -152,12 +152,15 @@
   }
 
   AppId InstallPwaForCurrentUrl() {
+    // Depending on the installability criteria, different dialogs can be used.
+    chrome::SetAutoAcceptWebAppDialogForTesting(true, true);
     chrome::SetAutoAcceptPWAInstallConfirmationForTesting(true);
     WebAppTestInstallObserver observer(profile());
     observer.BeginListening();
     CHECK(chrome::ExecuteCommand(browser(), IDC_INSTALL_PWA));
     AppId app_id = observer.Wait();
     chrome::SetAutoAcceptPWAInstallConfirmationForTesting(false);
+    chrome::SetAutoAcceptWebAppDialogForTesting(false, false);
     return app_id;
   }
 
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 7951094..b814191 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -127,6 +127,7 @@
 #include "chrome/browser/ui/webui/devtools_ui.h"
 #include "chrome/browser/ui/webui/download_shelf/download_shelf_ui.h"
 #include "chrome/browser/ui/webui/downloads/downloads_ui.h"
+#include "chrome/browser/ui/webui/enterprise_casting/enterprise_casting_ui.h"
 #include "chrome/browser/ui/webui/feedback/feedback_ui.h"
 #include "chrome/browser/ui/webui/history/history_ui.h"
 #include "chrome/browser/ui/webui/identity_internals_ui.h"
@@ -212,7 +213,6 @@
 #include "chrome/browser/ui/webui/chromeos/cryptohome_ui.h"
 #include "chrome/browser/ui/webui/chromeos/drive_internals_ui.h"
 #include "chrome/browser/ui/webui/chromeos/emoji/emoji_ui.h"
-#include "chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_ui.h"
 #include "chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_network_ui.h"
 #include "chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_start_reauth_ui.h"
 #include "chrome/browser/ui/webui/chromeos/in_session_password_change/password_change_ui.h"
@@ -744,6 +744,10 @@
   // Inline login UI is available on all platforms except Android.
   if (url.host_piece() == chrome::kChromeUIChromeSigninHost)
     return &NewWebUI<InlineLoginUI>;
+  if (base::FeatureList::IsEnabled(features::kEnterpriseCastingUI)) {
+      if (url.host_piece() == chrome::kChromeUIEnterpriseCastingHost)
+        return &NewWebUI<EnterpriseCastingUI>;
+  }
 #endif  // !defined(OS_ANDROID)
 #if defined(OS_WIN)
   if (url.host_piece() == chrome::kChromeUIConflictsHost)
@@ -829,9 +833,6 @@
     return &NewWebUI<chromeos::DriveInternalsUI>;
   if (url.host_piece() == chrome::kChromeUILauncherInternalsHost)
     return &NewWebUI<chromeos::LauncherInternalsUI>;
-  if (base::FeatureList::IsEnabled(features::kEnterpriseCastingUI) &&
-      url.host_piece() == chrome::kChromeUIEnterpriseCastingHost)
-    return &NewWebUI<chromeos::EnterpriseCastingUI>;
   if (url.host_piece() == ash::kChromeUIHelpAppHost)
     return &NewComponentUI<ash::HelpAppUI, ash::ChromeHelpAppUIDelegate>;
   if (url.host_piece() == chrome::kChromeUIMobileSetupHost)
diff --git a/chrome/browser/ui/webui/chromeos/parent_access/parent_access_ui_handler_impl.cc b/chrome/browser/ui/webui/chromeos/parent_access/parent_access_ui_handler_impl.cc
index 6ef9a9b..3204918 100644
--- a/chrome/browser/ui/webui/chromeos/parent_access/parent_access_ui_handler_impl.cc
+++ b/chrome/browser/ui/webui/chromeos/parent_access/parent_access_ui_handler_impl.cc
@@ -31,7 +31,8 @@
 
 void ParentAccessUIHandlerImpl::GetOAuthToken(GetOAuthTokenCallback callback) {
   signin::ScopeSet scopes;
-  scopes.insert(GaiaConstants::kKidsSupervisionSetupChildOAuth2Scope);
+  scopes.insert(GaiaConstants::kParentApprovalOAuth2Scope);
+  scopes.insert(GaiaConstants::kProgrammaticChallengeOAuth2Scope);
 
   if (oauth2_access_token_fetcher_) {
     // Only one GetOAuthToken call can happen at a time.
diff --git a/chrome/browser/ui/webui/connectors_internals/zero_trust_utils.cc b/chrome/browser/ui/webui/connectors_internals/zero_trust_utils.cc
index 461b85b..12247c9 100644
--- a/chrome/browser/ui/webui/connectors_internals/zero_trust_utils.cc
+++ b/chrome/browser/ui/webui/connectors_internals/zero_trust_utils.cc
@@ -4,13 +4,17 @@
 
 #include "chrome/browser/ui/webui/connectors_internals/zero_trust_utils.h"
 
+#include "base/containers/span.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
 
 namespace enterprise_connectors {
 namespace utils {
 
 namespace {
 
+using google::protobuf::RepeatedPtrField;
+
 void TrySetSignal(base::flat_map<std::string, std::string>& map,
                   const std::string& key,
                   bool has_value,
@@ -38,6 +42,20 @@
   }
 }
 
+// Encodes repeated fields into a single string with values separated by commas.
+// TODO(seblalancette): Uncomment once both CrOS and Chrome versions of the
+// DeviceTrustSignals proto have been updated.
+// void TrySetSignal(base::flat_map<std::string, std::string>& map,
+//                   const std::string& key,
+//                   const RepeatedPtrField<std::string>& values) {
+//   if (values.empty()) {
+//     return;
+//   }
+
+//   map[key] = base::JoinString(
+//       std::vector<base::StringPiece>(values.begin(), values.end()), ", ");
+// }
+
 }  // namespace
 
 base::flat_map<std::string, std::string> SignalsToMap(
@@ -64,8 +82,12 @@
                signals->device_manufacturer());
   TrySetSignal(map, "device_model", signals->has_device_model(),
                signals->device_model());
-  TrySetSignal(map, "imei", signals->has_imei(), signals->imei());
-  TrySetSignal(map, "meid", signals->has_meid(), signals->meid());
+
+  // TODO(seblalancette): Uncomment once both CrOS and Chrome versions of the
+  // DeviceTrustSignals proto have been updated.
+  // TrySetSignal(map, "imei", signals->imei());
+  // TrySetSignal(map, "meid", signals->meid());
+
   TrySetSignal(map, "tpm_hash", signals->has_tpm_hash(), signals->tpm_hash());
   TrySetSignal(map, "is_disk_encrypted", signals->has_is_disk_encrypted(),
                signals->is_disk_encrypted());
diff --git a/chrome/browser/ui/webui/chromeos/enterprise_casting/BUILD.gn b/chrome/browser/ui/webui/enterprise_casting/BUILD.gn
similarity index 83%
rename from chrome/browser/ui/webui/chromeos/enterprise_casting/BUILD.gn
rename to chrome/browser/ui/webui/enterprise_casting/BUILD.gn
index 1a73667..78d19e8 100644
--- a/chrome/browser/ui/webui/chromeos/enterprise_casting/BUILD.gn
+++ b/chrome/browser/ui/webui/enterprise_casting/BUILD.gn
@@ -4,7 +4,7 @@
 
 import("//mojo/public/tools/bindings/mojom.gni")
 
-assert(is_chromeos, "EnterpriseCasting is Chrome OS only.")
+assert(!is_android, "EnterpriseCasting is not for android.")
 
 mojom("mojo_bindings") {
   sources = [ "enterprise_casting.mojom" ]
diff --git a/chrome/browser/ui/webui/chromeos/enterprise_casting/OWNERS b/chrome/browser/ui/webui/enterprise_casting/OWNERS
similarity index 85%
rename from chrome/browser/ui/webui/chromeos/enterprise_casting/OWNERS
rename to chrome/browser/ui/webui/enterprise_casting/OWNERS
index 57d67b8..1c4f5ce 100644
--- a/chrome/browser/ui/webui/chromeos/enterprise_casting/OWNERS
+++ b/chrome/browser/ui/webui/enterprise_casting/OWNERS
@@ -1,5 +1,5 @@
 gbj@google.com
-bmalcolm@google.com
+bmalcolm@chromium.org
 bzielinski@google.com
 jacqueli@google.com
 
diff --git a/chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting.mojom b/chrome/browser/ui/webui/enterprise_casting/enterprise_casting.mojom
similarity index 100%
rename from chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting.mojom
rename to chrome/browser/ui/webui/enterprise_casting/enterprise_casting.mojom
diff --git a/chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_handler.cc b/chrome/browser/ui/webui/enterprise_casting/enterprise_casting_handler.cc
similarity index 83%
rename from chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_handler.cc
rename to chrome/browser/ui/webui/enterprise_casting/enterprise_casting_handler.cc
index 96d4154..2405343 100644
--- a/chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_handler.cc
+++ b/chrome/browser/ui/webui/enterprise_casting/enterprise_casting_handler.cc
@@ -2,13 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_handler.h"
+#include "chrome/browser/ui/webui/enterprise_casting/enterprise_casting_handler.h"
 
 #include "base/rand_util.h"
 #include "base/strings/string_number_conversions.h"
 
-namespace chromeos {
-
 EnterpriseCastingHandler::EnterpriseCastingHandler(
     mojo::PendingReceiver<enterprise_casting::mojom::PageHandler> page_handler,
     mojo::PendingRemote<enterprise_casting::mojom::Page> page)
@@ -19,5 +17,3 @@
 void EnterpriseCastingHandler::UpdatePin() {
   page_->SetPin(base::NumberToString(base::RandInt(0, 9999)));
 }
-
-}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_handler.h b/chrome/browser/ui/webui/enterprise_casting/enterprise_casting_handler.h
similarity index 68%
rename from chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_handler.h
rename to chrome/browser/ui/webui/enterprise_casting/enterprise_casting_handler.h
index aba0c316..54c0078f 100644
--- a/chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_handler.h
+++ b/chrome/browser/ui/webui/enterprise_casting/enterprise_casting_handler.h
@@ -2,17 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_ENTERPRISE_CASTING_ENTERPRISE_CASTING_HANDLER_H_
-#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_ENTERPRISE_CASTING_ENTERPRISE_CASTING_HANDLER_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_ENTERPRISE_CASTING_ENTERPRISE_CASTING_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_ENTERPRISE_CASTING_ENTERPRISE_CASTING_HANDLER_H_
 
 #include "base/scoped_observation.h"
 #include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting.mojom.h"
+#include "chrome/browser/ui/webui/enterprise_casting/enterprise_casting.mojom.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
-namespace chromeos {
-
 class EnterpriseCastingHandler : public enterprise_casting::mojom::PageHandler {
  public:
   EnterpriseCastingHandler(
@@ -29,6 +27,4 @@
   mojo::Receiver<enterprise_casting::mojom::PageHandler> receiver_;
 };
 
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_ENTERPRISE_CASTING_ENTERPRISE_CASTING_HANDLER_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_ENTERPRISE_CASTING_ENTERPRISE_CASTING_HANDLER_H_
diff --git a/chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_ui.cc b/chrome/browser/ui/webui/enterprise_casting/enterprise_casting_ui.cc
similarity index 92%
rename from chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_ui.cc
rename to chrome/browser/ui/webui/enterprise_casting/enterprise_casting_ui.cc
index bcea12f..97b7996 100644
--- a/chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_ui.cc
+++ b/chrome/browser/ui/webui/enterprise_casting/enterprise_casting_ui.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 "chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_ui.h"
+#include "chrome/browser/ui/webui/enterprise_casting/enterprise_casting_ui.h"
 
 #include "base/containers/span.h"
 #include "chrome/browser/ui/webui/webui_util.h"
@@ -12,8 +12,6 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui_data_source.h"
 
-namespace chromeos {
-
 EnterpriseCastingUI::EnterpriseCastingUI(content::WebUI* web_ui)
     : MojoWebUIController(web_ui) {
   auto source = base::WrapUnique(
@@ -47,5 +45,3 @@
 }
 
 WEB_UI_CONTROLLER_TYPE_IMPL(EnterpriseCastingUI)
-
-}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_ui.h b/chrome/browser/ui/webui/enterprise_casting/enterprise_casting_ui.h
similarity index 73%
rename from chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_ui.h
rename to chrome/browser/ui/webui/enterprise_casting/enterprise_casting_ui.h
index 3f9abee..dbd81c1 100644
--- a/chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_ui.h
+++ b/chrome/browser/ui/webui/enterprise_casting/enterprise_casting_ui.h
@@ -2,18 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_ENTERPRISE_CASTING_ENTERPRISE_CASTING_UI_H_
-#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_ENTERPRISE_CASTING_ENTERPRISE_CASTING_UI_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_ENTERPRISE_CASTING_ENTERPRISE_CASTING_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_ENTERPRISE_CASTING_ENTERPRISE_CASTING_UI_H_
 
-#include "chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting.mojom.h"
-#include "chrome/browser/ui/webui/chromeos/enterprise_casting/enterprise_casting_handler.h"
+#include "chrome/browser/ui/webui/enterprise_casting/enterprise_casting.mojom.h"
+#include "chrome/browser/ui/webui/enterprise_casting/enterprise_casting_handler.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "ui/webui/mojo_web_ui_controller.h"
 
-namespace chromeos {
-
 // The WebUI controller for chrome://enterprise-casting.
 class EnterpriseCastingUI
     : public ui::MojoWebUIController,
@@ -43,6 +41,4 @@
   WEB_UI_CONTROLLER_TYPE_DECL();
 };
 
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_ENTERPRISE_CASTING_ENTERPRISE_CASTING_UI_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_ENTERPRISE_CASTING_ENTERPRISE_CASTING_UI_H_
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_page_handler.cc b/chrome/browser/ui/webui/tab_strip/tab_strip_page_handler.cc
index 274fccd8..109c68f 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_page_handler.cc
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_page_handler.cc
@@ -56,6 +56,11 @@
 #include "ui/gfx/range/range.h"
 #include "url/gurl.h"
 
+// This should be after all other #includes.
+#if defined(_WINDOWS_)  // Detect whether windows.h was included.
+#include "base/win/windows_h_disallowed.h"
+#endif  // defined(_WINDOWS_)
+
 namespace {
 
 // Delay in milliseconds of when the dragging UI should be shown for touch drag.
diff --git a/chrome/browser/web_applications/manifest_update_manager_browsertest.cc b/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
index cceed3a..1a9fb3d 100644
--- a/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
+++ b/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
@@ -620,33 +620,6 @@
       features::kPwaUpdateDialogForNameAndIcon};
 };
 
-IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerAppIdentityBrowserTest,
-                       VerifyAppIdentityUpdatesWithDlgForUserInstalledApps) {
-  chrome::SetAutoAcceptAppIdentityUpdateForTesting(true);
-
-  constexpr char kManifestTemplate[] = R"(
-    {
-      "name": "$1",
-      "start_url": ".",
-      "scope": "/",
-      "display": "standalone",
-      "icons": $2
-    }
-  )";
-  OverrideManifest(kManifestTemplate, {"Test app name", kInstallableIconList});
-  AppId app_id = InstallWebApp();
-
-  OverrideManifest(kManifestTemplate,
-                   {"Different app name", kAnotherInstallableIconList});
-  EXPECT_EQ(GetResultAfterPageLoad(GetAppURL()),
-            ManifestUpdateResult::kAppUpdated);
-  histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
-                                      ManifestUpdateResult::kAppUpdated, 1);
-  EXPECT_EQ(GetProvider().registrar().GetAppShortName(app_id),
-            "Different app name");
-  CheckShortcutInfoUpdated(app_id, kAnotherInstallableIconTopLeftColor);
-}
-
 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
                        CheckIgnoresStartUrlChange) {
   constexpr char kManifestTemplate[] = R"(
@@ -2641,7 +2614,7 @@
       features::kWebAppManifestIconUpdating};
 };
 
-IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerIconUpdatingBrowserTest,
+IN_PROC_BROWSER_TEST_P(ManifestUpdateManagerBrowserTest_UpdateDialog,
                        CheckFindsIconContentChange) {
   constexpr char kManifest[] = R"(
     {
@@ -2651,39 +2624,52 @@
       "display": "standalone",
       "icons": [
         {
-          "src": "/web_apps/basic-192.png?ignore",
-          "sizes": "192x192",
+          "src": "/banners/256x256-green.png?ignore",
+          "sizes": "256x256",
           "type": "image/png"
         }
       ]
     }
   )";
+
+  if (IsUpdateDialogEnabled())
+    chrome::SetAutoAcceptAppIdentityUpdateForTesting(true);
+
   OverrideManifest(kManifest, {});
   AppId app_id = InstallWebApp();
 
-  // Replace the contents of basic-192.png with blue-192.png without changing
-  // the URL.
+  // Replace the green icon with a red icon without changing the URL.
   content::URLLoaderInterceptor url_interceptor(base::BindLambdaForTesting(
       [this](content::URLLoaderInterceptor::RequestParams* params)
           -> bool /*intercepted*/ {
         if (params->url_request.url ==
-            http_server_.GetURL("/web_apps/basic-192.png?ignore")) {
+            http_server_.GetURL("/banners/256x256-green.png?ignore")) {
           content::URLLoaderInterceptor::WriteResponse(
-              "chrome/test/data/web_apps/blue-192.png", params->client.get());
+              "chrome/test/data/banners/256x256-red.png", params->client.get());
           return true;
         }
         return false;
       }));
 
-  EXPECT_EQ(GetResultAfterPageLoad(GetAppURL()),
-            ManifestUpdateResult::kAppUpdated);
-  histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
-                                      ManifestUpdateResult::kAppUpdated, 1);
-  // The icon should have changed, as the file has been updated (but the url is
-  // the same).
-  CheckShortcutInfoUpdated(app_id, SK_ColorBLUE);
+  if (IsUpdateDialogEnabled()) {
+    EXPECT_EQ(GetResultAfterPageLoad(GetAppURL()),
+              ManifestUpdateResult::kAppUpdated);
+    histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
+                                        ManifestUpdateResult::kAppUpdated, 1);
+    // The icon should have changed, as the file has been updated (but the url
+    // is the same).
+    CheckShortcutInfoUpdated(app_id, SK_ColorRED);
 
-  EXPECT_EQ(ReadAppIconPixel(app_id, /*size=*/192), SK_ColorBLUE);
+    EXPECT_EQ(ReadAppIconPixel(app_id, /*size=*/256), SK_ColorRED);
+  } else {
+    EXPECT_EQ(GetResultAfterPageLoad(GetAppURL()),
+              ManifestUpdateResult::kAppUpToDate);
+    histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
+                                        ManifestUpdateResult::kAppUpdated, 0);
+    CheckShortcutInfoUpdated(app_id, SK_ColorGREEN);
+
+    EXPECT_EQ(ReadAppIconPixel(app_id, /*size=*/256), SK_ColorGREEN);
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerIconUpdatingBrowserTest,
@@ -3444,4 +3430,328 @@
   run_loop.Run();
 }
 
+enum AppIdTestParam {
+  kInvalid = 0,
+  kTypeWebApp = 1 << 1,
+  kTypeDefaultApp = 1 << 2,
+  kTypePolicyApp = 1 << 3,
+  kWithFlagNone = 1 << 4,
+  kWithFlagPolicyAppIdentity = 1 << 5,
+  kWithFlagAppIdDialog = 1 << 6,
+  kActionUpdateTitle = 1 << 7,
+  kActionUpdateSingleIcon = 1 << 8,
+  kActionUpdateTitleAndSingleIcon = 1 << 9,
+  kActionAddSingleIcon = 1 << 10,
+  kActionUpdateMultiIcons = 1 << 11,
+  kActionRemoveSingleIcon = 1 << 12,
+  kActionSwitchIconSize = 1 << 13,
+};
+
+class ManifestUpdateManagerBrowserTest_AppIdentityParameterized
+    : public ManifestUpdateManagerBrowserTest,
+      public testing::WithParamInterface<
+          std::tuple<AppIdTestParam, AppIdTestParam, AppIdTestParam>> {
+ public:
+  ManifestUpdateManagerBrowserTest_AppIdentityParameterized() {
+    std::vector<base::Feature> enabled_features;
+    std::vector<base::Feature> disabled_features;
+    if (IsAppIdentityUpdateDialogEnabled()) {
+      enabled_features.push_back(features::kPwaUpdateDialogForNameAndIcon);
+    } else {
+      disabled_features.push_back(features::kPwaUpdateDialogForNameAndIcon);
+    }
+    if (IsPolicyAppIdentityOverrideEnabled()) {
+      enabled_features.push_back(
+          features::kWebAppManifestPolicyAppIdentityUpdate);
+    } else {
+      disabled_features.push_back(
+          features::kWebAppManifestPolicyAppIdentityUpdate);
+    }
+
+    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  }
+
+  bool IsWebApp() const {
+    return std::get<1>(GetParam()) & AppIdTestParam::kTypeWebApp;
+  }
+  bool IsDefaultApp() const {
+    return std::get<1>(GetParam()) & AppIdTestParam::kTypeDefaultApp;
+  }
+  bool IsPolicyApp() const {
+    return std::get<1>(GetParam()) & AppIdTestParam::kTypePolicyApp;
+  }
+
+  bool IsAppIdentityUpdateDialogEnabled() const {
+    return std::get<2>(GetParam()) & AppIdTestParam::kWithFlagAppIdDialog;
+  }
+  bool IsPolicyAppIdentityOverrideEnabled() const {
+    return std::get<2>(GetParam()) & AppIdTestParam::kWithFlagPolicyAppIdentity;
+  }
+
+  bool TitleUpdateRequested() const {
+    return std::get<0>(GetParam()) & AppIdTestParam::kActionUpdateTitle ||
+           std::get<0>(GetParam()) &
+               AppIdTestParam::kActionUpdateTitleAndSingleIcon;
+  }
+
+  bool AnyIconUpdateRequested() const {
+    return SingleIconAddRequested() || SingleIconRemoveRequested() ||
+           SingleIconUpdateRequested() || MultiIconUpdateRequested() ||
+           IconSwitchUpdateRequested();
+  }
+  bool SingleIconAddRequested() const {
+    return std::get<0>(GetParam()) & AppIdTestParam::kActionAddSingleIcon;
+  }
+  bool SingleIconRemoveRequested() const {
+    return std::get<0>(GetParam()) & AppIdTestParam::kActionRemoveSingleIcon;
+  }
+  bool SingleIconUpdateRequested() const {
+    return std::get<0>(GetParam()) & AppIdTestParam::kActionUpdateSingleIcon ||
+           std::get<0>(GetParam()) &
+               AppIdTestParam::kActionUpdateTitleAndSingleIcon;
+  }
+  bool MultiIconUpdateRequested() const {
+    return std::get<0>(GetParam()) & AppIdTestParam::kActionUpdateMultiIcons;
+  }
+  bool IconSwitchUpdateRequested() const {
+    return std::get<0>(GetParam()) & AppIdTestParam::kActionSwitchIconSize;
+  }
+
+  bool ExpectTitleUpdate() const {
+    if (!TitleUpdateRequested())
+      return false;
+
+    if (IsDefaultApp())
+      return true;
+    if (IsPolicyApp() && IsPolicyAppIdentityOverrideEnabled())
+      return true;
+    return IsAppIdentityUpdateDialogEnabled();
+  }
+
+  bool ExpectIconUpdate() const {
+    // Ideally, this should just check AnyIconUpdateRequested(), but adding and
+    // removing of icons results in kAppNotEligible when updating, even for
+    // Default apps. Therefore, only the supported upgrade paths must be
+    // enumerated here.
+    if (!SingleIconUpdateRequested() && !MultiIconUpdateRequested() &&
+        !IconSwitchUpdateRequested())
+      return false;
+
+    if (IsDefaultApp())
+      return true;
+    if (IsPolicyApp() && IsPolicyAppIdentityOverrideEnabled())
+      return true;
+    if (SingleIconUpdateRequested() && IsAppIdentityUpdateDialogEnabled())
+      return true;
+
+    return false;
+  }
+
+  ManifestUpdateResult ExpectedResultWhenNoUpdate() const {
+    if (SingleIconAddRequested() || SingleIconRemoveRequested())
+      return ManifestUpdateResult::kAppNotEligible;
+    return ManifestUpdateResult::kAppUpToDate;
+  }
+
+  static std::string ParamToString(
+      testing::TestParamInfo<
+          std::tuple<AppIdTestParam, AppIdTestParam, AppIdTestParam>>
+          param_info) {
+    std::string result = "";
+
+    AppIdTestParam action = std::get<0>(param_info.param);
+    if (action & AppIdTestParam::kActionUpdateTitle)
+      result += "UpdateTitle_";
+    if (action & AppIdTestParam::kActionUpdateSingleIcon)
+      result += "UpdateSingleIcon_";
+    if (action & AppIdTestParam::kActionUpdateTitleAndSingleIcon)
+      result += "UpdateTitleAndSingleIcon_";
+    if (action & AppIdTestParam::kActionRemoveSingleIcon)
+      result += "RemoveSingleIcon_";
+    if (action & AppIdTestParam::kActionAddSingleIcon)
+      result += "AddSingleIcon_";
+    if (action & AppIdTestParam::kActionUpdateMultiIcons)
+      result += "UpdateMultiIcons_";
+    if (action & AppIdTestParam::kActionSwitchIconSize)
+      result += "SwitchIcon_";
+
+    AppIdTestParam type = std::get<1>(param_info.param);
+    if (type & AppIdTestParam::kTypeWebApp)
+      result += "WebApp_";
+    if (type & AppIdTestParam::kTypeDefaultApp)
+      result += "DefaultApp_";
+    if (type & AppIdTestParam::kTypePolicyApp)
+      result += "PolicyApp_";
+
+    AppIdTestParam flags = std::get<2>(param_info.param);
+    result += "Flags_";
+    if (flags & AppIdTestParam::kWithFlagNone)
+      result += "None_";
+    if (flags & AppIdTestParam::kWithFlagPolicyAppIdentity)
+      result += "PolicyCanUpdate_";
+    if (flags & AppIdTestParam::kWithFlagAppIdDialog)
+      result += "WithAppIdDlg_";
+
+    return result;
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(
+    ManifestUpdateManagerBrowserTest_AppIdentityParameterized,
+    CheckCombinations) {
+  constexpr char kManifestTemplate[] = R"(
+    {
+      "name": "$1",
+      "start_url": "manifest_test_page.html",
+      "scope": "/",
+      "display": "standalone",
+      "icons": $2
+    }
+  )";
+
+  // Starting icon set always uses solid green icons.
+  constexpr SkColor kOriginalIconTopLeftColor = SkColorSetRGB(0x00, 0xFF, 0x00);
+  // The icons that get updated are all solid red.
+  constexpr SkColor kUpdatedIconTopLeftColor = SkColorSetRGB(0xFF, 0x00, 0x00);
+
+  // This is always the starting set of icons. Please note that some sizes will
+  // be auto-generated (see SizesToGenerate()), so the starting state when
+  // debugging will also consist of sizes 32, 48, 64, 96, 128. Size 256 would be
+  // autogenerated also, if it were not provided.
+  constexpr char kIconList[] = R"(
+    [
+      { "src": "256x256-green.png", "sizes": "256x256", "type": "image/png" },
+      { "src": "512x512-green.png", "sizes": "512x512", "type": "image/png" }
+    ]
+  )";
+
+  // If we are supposed to remove one icon, this is the end state (512 removed),
+  // plus auto-generated sizes (see comment in kIconList).
+  constexpr char kRemovedSingleIconList[] = R"(
+    [
+      { "src": "256x256-green.png", "sizes": "256x256", "type": "image/png" },
+    ]
+  )";
+  // If we are supposed to add one icon, this is the end state (128 added),
+  // plus auto-generated sizes (see comment in kIconList).
+  constexpr char kAddedSingleIconList[] = R"(
+    [
+      { "src": "128x128-red.png", "sizes": "256x256", "type": "image/png" },
+      { "src": "256x256-green.png", "sizes": "256x256", "type": "image/png" },
+      { "src": "512x512-green.png", "sizes": "512x512", "type": "image/png" }
+    ]
+  )";
+  // Updating one icon only changes the bits of size 256 to red.
+  constexpr char kUpdatedSingleIconList[] = R"(
+    [
+      { "src": "256x256-red.png", "sizes": "256x256", "type": "image/png" },
+      { "src": "512x512-green.png", "sizes": "512x512", "type": "image/png" }
+    ]
+  )";
+  // Updating multiple icons changes size 256 and size 512 to red.
+  constexpr char kUpdatedMultiIconList[] = R"(
+    [
+      { "src": "256x256-red.png", "sizes": "256x256", "type": "image/png" },
+      { "src": "512x512-red.png", "sizes": "512x512", "type": "image/png" }
+    ]
+  )";
+  // Icon switch involves removing a size and replacing it with another. Here,
+  // size 256 has been removed and size 128 added. Note that size 256 will still
+  // be found in the end state because it gets auto-generated.
+  constexpr char kIconSwitchList[] = R"(
+    [
+      { "src": "128x128-red.png", "sizes": "128x128", "type": "image/png" },
+      { "src": "512x512-green.png", "sizes": "512x512", "type": "image/png" }
+    ]
+  )";
+
+  testing::TestParamInfo<
+      std::tuple<AppIdTestParam, AppIdTestParam, AppIdTestParam>>
+      param(GetParam(), 0);
+
+  if (IsAppIdentityUpdateDialogEnabled())
+    chrome::SetAutoAcceptAppIdentityUpdateForTesting(true);
+
+  std::string app_name = "Test app name";
+  OverrideManifest(kManifestTemplate, {app_name, kIconList});
+
+  AppId app_id;
+  if (IsDefaultApp()) {
+    app_id = InstallDefaultApp();
+  } else if (IsPolicyApp()) {
+    app_id = InstallPolicyApp();
+  } else if (IsWebApp()) {
+    app_id = InstallWebApp();
+  } else {
+    NOTREACHED();
+  }
+
+  const WebApp* web_app = GetProvider().registrar().GetAppById(app_id);
+  ASSERT_TRUE(web_app);
+
+  if (TitleUpdateRequested())
+    app_name = "Different app name";
+
+  if (SingleIconUpdateRequested()) {
+    OverrideManifest(kManifestTemplate, {app_name, kUpdatedSingleIconList});
+  } else if (SingleIconAddRequested()) {
+    OverrideManifest(kManifestTemplate, {app_name, kAddedSingleIconList});
+  } else if (SingleIconRemoveRequested()) {
+    OverrideManifest(kManifestTemplate, {app_name, kRemovedSingleIconList});
+  } else if (MultiIconUpdateRequested()) {
+    OverrideManifest(kManifestTemplate, {app_name, kUpdatedMultiIconList});
+  } else if (IconSwitchUpdateRequested()) {
+    OverrideManifest(kManifestTemplate, {app_name, kIconSwitchList});
+  } else {
+    OverrideManifest(kManifestTemplate, {app_name, kIconList});
+  }
+
+  bool expectations_match = (TitleUpdateRequested() == ExpectTitleUpdate()) &&
+                            (AnyIconUpdateRequested() == ExpectIconUpdate());
+  if ((TitleUpdateRequested() || AnyIconUpdateRequested()) &&
+      expectations_match) {
+    ASSERT_EQ(ManifestUpdateResult::kAppUpdated,
+              GetResultAfterPageLoad(GetAppURL()));
+    histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
+                                        ManifestUpdateResult::kAppUpdated, 1);
+  } else {
+    ASSERT_EQ(ExpectedResultWhenNoUpdate(),
+              GetResultAfterPageLoad(GetAppURL()));
+    histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
+                                        ManifestUpdateResult::kAppUpdated, 0);
+  }
+
+  EXPECT_EQ(ExpectTitleUpdate() && expectations_match ? "Different app name"
+                                                      : "Test app name",
+            GetProvider().registrar().GetAppShortName(app_id));
+
+  CheckShortcutInfoUpdated(app_id, ExpectIconUpdate() && expectations_match
+                                       ? kUpdatedIconTopLeftColor
+                                       : kOriginalIconTopLeftColor);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    ManifestUpdateManagerBrowserTest_AppIdentityParameterized,
+    testing::Combine(
+        testing::Values(AppIdTestParam::kActionUpdateTitle,
+                        AppIdTestParam::kActionUpdateSingleIcon,
+                        AppIdTestParam::kActionUpdateTitleAndSingleIcon,
+                        AppIdTestParam::kActionUpdateMultiIcons,
+                        AppIdTestParam::kActionAddSingleIcon,
+                        AppIdTestParam::kActionRemoveSingleIcon,
+                        AppIdTestParam::kActionSwitchIconSize),
+        testing::Values(AppIdTestParam::kTypeDefaultApp,
+                        AppIdTestParam::kTypePolicyApp,
+                        AppIdTestParam::kTypeWebApp),
+        testing::Values(AppIdTestParam::kWithFlagNone,
+                        AppIdTestParam::kWithFlagPolicyAppIdentity,
+                        AppIdTestParam::kWithFlagAppIdDialog,
+                        AppIdTestParam::kWithFlagPolicyAppIdentity |
+                            AppIdTestParam::kWithFlagAppIdDialog)),
+    ManifestUpdateManagerBrowserTest_AppIdentityParameterized::ParamToString);
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/manifest_update_task.cc b/chrome/browser/web_applications/manifest_update_task.cc
index fa37150e..4662494 100644
--- a/chrome/browser/web_applications/manifest_update_task.cc
+++ b/chrome/browser/web_applications/manifest_update_task.cc
@@ -34,27 +34,26 @@
 
 namespace web_app {
 
-struct IconDiff {
- public:
-  IconDiff() = default;
-  explicit IconDiff(bool changes) { changes_detected = changes; }
-  IconDiff(const SkBitmap& before_icon, const SkBitmap& after_icon) {
-    changes_detected = true;
-    before = before_icon;
-    after = after_icon;
-  }
-  bool changes_detected = false;
-  SkBitmap before;
-  SkBitmap after;
-};
-
 namespace {
 
-IconDiff HaveIconContentsChanged(
+void HaveIconContentsChanged(
     const std::map<SquareSizePx, SkBitmap>& disk_icon_bitmaps,
-    const std::map<SquareSizePx, SkBitmap>& downloaded_icon_bitmaps) {
-  if (downloaded_icon_bitmaps.size() != disk_icon_bitmaps.size())
-    return IconDiff(true);
+    const std::map<SquareSizePx, SkBitmap>& downloaded_icon_bitmaps,
+    IconDiff* icon_diff,
+    const std::vector<SquareSizePx>& on_disk_sizes,
+    const std::vector<SquareSizePx>& downloaded_sizes,
+    bool end_when_mismatch_detected) {
+  if (downloaded_icon_bitmaps.size() != disk_icon_bitmaps.size()) {
+    icon_diff->diff_results |= MISMATCHED_IMAGE_SIZES;
+    if (end_when_mismatch_detected)
+      return;
+  }
+
+  if (on_disk_sizes != downloaded_sizes) {
+    icon_diff->diff_results |= MISMATCHED_IMAGE_SIZES;
+    if (end_when_mismatch_detected)
+      return;
+  }
 
   for (const std::pair<const SquareSizePx, SkBitmap>& entry :
        downloaded_icon_bitmaps) {
@@ -62,35 +61,42 @@
     const SkBitmap& downloaded_bitmap = entry.second;
 
     auto it = disk_icon_bitmaps.find(size);
-    if (it == disk_icon_bitmaps.end())
-      return IconDiff(true);
+    if (it == disk_icon_bitmaps.end()) {
+      icon_diff->diff_results |= MISMATCHED_IMAGE_SIZES;
+      if (end_when_mismatch_detected)
+        return;
+      continue;
+    }
 
     const SkBitmap& disk_bitmap = it->second;
-    if (!gfx::BitmapsAreEqual(downloaded_bitmap, disk_bitmap))
-      return IconDiff(disk_bitmap, downloaded_bitmap);
+    if (!gfx::BitmapsAreEqual(downloaded_bitmap, disk_bitmap)) {
+      if (end_when_mismatch_detected) {
+        icon_diff->diff_results |= ONE_OR_MORE_ICONS_CHANGED;
+        return;
+      } else {
+        // Icons that are specified in new manifest are of special interest, the
+        // rest is auto-generated.
+        bool important_icon =
+            std::find(downloaded_sizes.begin(), downloaded_sizes.end(), size) !=
+            downloaded_sizes.end();
+        if (!important_icon) {
+          icon_diff->diff_results |= GENERATED_ICON_CHANGED;
+        } else if ((icon_diff->diff_results & SINGLE_ICON_CHANGED) == 0 &&
+                   (icon_diff->diff_results & MULTIPLE_ICONS_CHANGED) == 0) {
+          icon_diff->diff_results |= SINGLE_ICON_CHANGED;
+          icon_diff->before = disk_bitmap;
+          icon_diff->after = downloaded_bitmap;
+        } else if (icon_diff->diff_results & SINGLE_ICON_CHANGED) {
+          icon_diff->diff_results &= ~SINGLE_ICON_CHANGED;
+          icon_diff->diff_results |= MULTIPLE_ICONS_CHANGED;
+          // The UI can only handle showing one image at a time, at the moment.
+          icon_diff->before = SkBitmap();
+          icon_diff->after = SkBitmap();
+          return;
+        }
+      }
+    }
   }
-
-  return IconDiff(false);
-}
-
-IconDiff HaveIconBitmapsChanged(const IconBitmaps& disk_icon_bitmaps,
-                                const IconBitmaps& downloaded_icon_bitmaps) {
-  IconDiff icon_diff = HaveIconContentsChanged(disk_icon_bitmaps.any,
-                                               downloaded_icon_bitmaps.any);
-  if (icon_diff.changes_detected)
-    return icon_diff;
-
-  icon_diff = HaveIconContentsChanged(disk_icon_bitmaps.maskable,
-                                      downloaded_icon_bitmaps.maskable);
-  if (icon_diff.changes_detected)
-    return icon_diff;
-
-  icon_diff = HaveIconContentsChanged(disk_icon_bitmaps.monochrome,
-                                      downloaded_icon_bitmaps.monochrome);
-  if (icon_diff.changes_detected)
-    return icon_diff;
-
-  return IconDiff(false);
 }
 
 // Some apps, such as pre-installed apps, have been vetted and are therefore
@@ -115,8 +121,74 @@
          base::FeatureList::IsEnabled(features::kWebAppManifestIconUpdating);
 }
 
+bool NeedsAppIdentityUpdateDialog(bool title_changing,
+                                  bool icons_changing,
+                                  const AppId& app_id,
+                                  const WebAppRegistrar& registrar) {
+  if (title_changing && !AllowUnpromptedNameUpdate(app_id, registrar))
+    return true;
+  if (icons_changing && !AllowUnpromptedIconUpdate(app_id, registrar))
+    return true;
+  return false;
+}
+
 }  // namespace
 
+IconDiff HaveIconBitmapsChanged(
+    const IconBitmaps& disk_icon_bitmaps,
+    const IconBitmaps& downloaded_icon_bitmaps,
+    const std::vector<apps::IconInfo>& disk_icon_info,
+    const std::vector<apps::IconInfo>& downloaded_icon_info,
+    bool end_when_mismatch_detected) {
+  // The manifest information associated with the icons is a flat vector of
+  // IconInfo types. This needs to be split into vectors and keyed by purpose
+  // (any, masked, monochrome) so that it can be read by the icon diff.
+  std::map<apps::IconInfo::Purpose, std::vector<SquareSizePx>> on_disk_sizes;
+  std::map<apps::IconInfo::Purpose, std::vector<SquareSizePx>> downloaded_sizes;
+  on_disk_sizes[apps::IconInfo::Purpose::kAny] = std::vector<SquareSizePx>();
+  downloaded_sizes[apps::IconInfo::Purpose::kAny] = std::vector<SquareSizePx>();
+  on_disk_sizes[apps::IconInfo::Purpose::kMaskable] =
+      std::vector<SquareSizePx>();
+  downloaded_sizes[apps::IconInfo::Purpose::kMaskable] =
+      std::vector<SquareSizePx>();
+  on_disk_sizes[apps::IconInfo::Purpose::kMonochrome] =
+      std::vector<SquareSizePx>();
+  downloaded_sizes[apps::IconInfo::Purpose::kMonochrome] =
+      std::vector<SquareSizePx>();
+  // Put each entry found into the right map (sort by purpose).
+  for (auto entry : disk_icon_info) {
+    on_disk_sizes[entry.purpose].push_back(entry.square_size_px.value_or(-1));
+  }
+  for (auto entry : downloaded_icon_info) {
+    downloaded_sizes[entry.purpose].push_back(
+        entry.square_size_px.value_or(-1));
+  }
+
+  IconDiff icon_diff;
+  HaveIconContentsChanged(disk_icon_bitmaps.any, downloaded_icon_bitmaps.any,
+                          &icon_diff,
+                          on_disk_sizes[apps::IconInfo::Purpose::kAny],
+                          downloaded_sizes[apps::IconInfo::Purpose::kAny],
+                          end_when_mismatch_detected);
+  if (icon_diff.mismatch() && end_when_mismatch_detected)
+    return icon_diff;
+
+  HaveIconContentsChanged(disk_icon_bitmaps.maskable,
+                          downloaded_icon_bitmaps.maskable, &icon_diff,
+                          on_disk_sizes[apps::IconInfo::Purpose::kMaskable],
+                          downloaded_sizes[apps::IconInfo::Purpose::kMaskable],
+                          end_when_mismatch_detected);
+  if (icon_diff.mismatch() && end_when_mismatch_detected)
+    return icon_diff;
+
+  HaveIconContentsChanged(
+      disk_icon_bitmaps.monochrome, downloaded_icon_bitmaps.monochrome,
+      &icon_diff, on_disk_sizes[apps::IconInfo::Purpose::kMonochrome],
+      downloaded_sizes[apps::IconInfo::Purpose::kMonochrome],
+      end_when_mismatch_detected);
+  return icon_diff;
+}
+
 ManifestUpdateTask::ManifestUpdateTask(
     const GURL& url,
     const AppId& app_id,
@@ -181,9 +253,9 @@
     case Stage::kPendingInstallableData:
     case Stage::kPendingIconDownload:
     case Stage::kPendingIconReadFromDisk:
+    case Stage::kPendingAppIdentityCheck:
       DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
       return;
-    case Stage::kPendingAppIdentityCheck:
     case Stage::kPendingWindowsClosed:
     case Stage::kPendingMaybeReadExistingIcons:
     case Stage::kPendingInstallation:
@@ -234,6 +306,20 @@
   const WebApp* app = registrar_.GetAppById(app_id_);
   DCHECK(app);
 
+  bool title_changing =
+      web_application_info_->title != base::UTF8ToUTF16(app->name());
+  bool icons_changing =
+      web_application_info_->manifest_icons != app->manifest_icons();
+  if (!NeedsAppIdentityUpdateDialog(title_changing, icons_changing, app_id_,
+                                    registrar_)) {
+    if (title_changing && AllowUnpromptedNameUpdate(app_id_, registrar_)) {
+      return true;
+    }
+    if (icons_changing && AllowUnpromptedIconUpdate(app_id_, registrar_)) {
+      return true;
+    }
+  }
+
   // Allows updating start_url and manifest_id when kWebAppEnableManifestId is
   // enabled. Both fields are allowed to change as long as the app_id generated
   // from them doesn't change.
@@ -256,13 +342,6 @@
   if (web_application_info_->display_override != app->display_mode_override())
     return true;
 
-  // Allow app icon updating for certain apps, if the existing icons are
-  // empty - this means the app icon download during install failed.
-  if (AllowUnpromptedIconUpdate(app_id_, registrar_) &&
-      web_application_info_->manifest_icons != app->manifest_icons()) {
-    return true;
-  }
-
   if (web_application_info_->shortcuts_menu_item_infos !=
       app->shortcuts_menu_item_infos()) {
     return true;
@@ -301,11 +380,6 @@
   if (web_application_info_->manifest_url != app->manifest_url())
     return true;
 
-  if (AllowUnpromptedNameUpdate(app_id_, registrar_) &&
-      web_application_info_->title != base::UTF8ToUTF16(app->name())) {
-    return true;
-  }
-
   if (web_application_info_->launch_handler != app->launch_handler())
     return true;
 
@@ -371,81 +445,88 @@
   }
   DCHECK(web_application_info_.has_value());
 
-  content::WebContents* web_contents = WebContentsObserver::web_contents();
   stage_ = Stage::kPendingAppIdentityCheck;
-  Observe(nullptr);
 
-  PopulateOtherIcons(&web_application_info_.value(), downloaded_icons_map);
-
-  // This call populates the |web_application_info_| with all icon bitmap
+  // These calls populate the |web_application_info_| with all icon bitmap
   // data.
   // If this data does not match what we already have on disk, then an update
   // is necessary.
   // TODO(https://crbug.com/1184911): Reuse this data in the web app install
   // task.
+  PopulateOtherIcons(&web_application_info_.value(), downloaded_icons_map);
   PopulateProductIcons(&web_application_info_.value(), &downloaded_icons_map);
 
+  if (!base::FeatureList::IsEnabled(features::kPwaUpdateDialogForNameAndIcon)) {
+    OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
+    return;
+  }
+
   IconDiff icon_diff = IsUpdateNeededForIconContents(disk_icon_bitmaps);
   std::u16string old_title =
       base::UTF8ToUTF16(registrar_.GetAppShortName(app_id_));
   std::u16string new_title = web_application_info_->title;
-  bool title_change = old_title != new_title;
 
-  bool show_dialog_for_name_update =
-      title_change & !AllowUnpromptedNameUpdate(app_id_, registrar_);
-  bool show_dialog_for_icon_update =
-      icon_diff.changes_detected &
-      !AllowUnpromptedIconUpdate(app_id_, registrar_);
+  bool title_change = old_title != new_title;
+  bool icon_change = icon_diff.mismatch();
+
+  if (!title_change && !icon_change) {
+    OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
+    return;
+  }
+
+  if (!NeedsAppIdentityUpdateDialog(title_change, icon_change, app_id_,
+                                    registrar_)) {
+    // The app identity update can be skipped, because any update not requiring
+    // the AppIdentityUpdate dialog should have been triggered already by
+    // running IsUpdateNeededForManifest. It doesn't matter a great deal whether
+    // kSkipped or kAllowed is used here, except that updating should also work
+    // without approval here. So to be safe we return kSkipped.
+    OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
+    return;
+  }
+
+  if (icon_change && !icon_diff.supported_for_app_identity_check()) {
+    OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
+    return;
+  }
 
   // Note: If icon and name changes are to be actually used later and not
   // overridden, then OnPostAppIdentityUpdateCheck must be called with
   // |AppIdentityUpdate::kAllowed| so |app_identity_update_allowed_| is true.
-  if (show_dialog_for_name_update || show_dialog_for_icon_update) {
-    // An identity update with a confirmation dialog is needed. See if it is
-    // available and show it -- or if not, abort the update.
-    if (!base::FeatureList::IsEnabled(
-            features::kPwaUpdateDialogForNameAndIcon)) {
-      OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
-      return;
+  SkBitmap* before_icon = nullptr;
+  SkBitmap* after_icon = nullptr;
+  if (icon_diff.mismatch()) {
+    before_icon = &icon_diff.before;
+    after_icon = &icon_diff.after;
+  } else {
+    auto it = disk_icon_bitmaps.any.find(web_app::kWebAppIconSmall);
+    if (it != disk_icon_bitmaps.any.end()) {
+      before_icon = &it->second;
+      after_icon = &it->second;
     }
+  }
 
-    SkBitmap* before_icon = nullptr;
-    SkBitmap* after_icon = nullptr;
-    if (icon_diff.changes_detected) {
-      before_icon = &icon_diff.before;
-      after_icon = &icon_diff.after;
-    } else {
-      auto it = disk_icon_bitmaps.any.find(web_app::kWebAppIconSmall);
-      if (it != disk_icon_bitmaps.any.end()) {
-        before_icon = &it->second;
-        after_icon = &it->second;
-      }
-    }
-
-    if (before_icon != nullptr && after_icon != nullptr &&
-        !before_icon->drawsNothing() && !after_icon->drawsNothing()) {
-      ui_manager_.ShowWebAppIdentityUpdateDialog(
-          app_id_, title_change, icon_diff.changes_detected, old_title,
-          new_title, *before_icon, *after_icon, web_contents,
-          base::BindOnce(&ManifestUpdateTask::OnPostAppIdentityUpdateCheck,
-                         AsWeakPtr()));
-      return;
-    }
-  } else if (title_change || icon_diff.changes_detected) {
-    // An identity update has been detected, but we've already determined it
-    // doesn't require the confirmation dialog, so the change can be allowed.
-    OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kAllowed);
+  if (before_icon == nullptr || after_icon == nullptr ||
+      before_icon->drawsNothing() || after_icon->drawsNothing()) {
+    OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
     return;
   }
 
-  // No identity change is happening (or we couldn't get the image diffs), so
-  // skip the check.
-  OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
+  content::WebContents* web_contents = WebContentsObserver::web_contents();
+  ui_manager_.ShowWebAppIdentityUpdateDialog(
+      app_id_, title_change, icon_diff.mismatch(), old_title, new_title,
+      *before_icon, *after_icon, web_contents,
+      base::BindOnce(&ManifestUpdateTask::OnPostAppIdentityUpdateCheck,
+                     AsWeakPtr()));
+
+  // Flow continues in OnPostAppIdentityUpdateCheck, once an action has been
+  // taken in the dialog.
 }
 
 void ManifestUpdateTask::OnPostAppIdentityUpdateCheck(
     AppIdentityUpdate app_identity_update_allowed) {
   DCHECK_EQ(stage_, Stage::kPendingAppIdentityCheck);
+  Observe(nullptr);
   app_identity_update_allowed_ =
       app_identity_update_allowed == AppIdentityUpdate::kAllowed;
   if (app_identity_update_allowed_) {
@@ -461,8 +542,13 @@
 IconDiff ManifestUpdateTask::IsUpdateNeededForIconContents(
     const IconBitmaps& disk_icon_bitmaps) const {
   DCHECK(web_application_info_.has_value());
-  return HaveIconBitmapsChanged(disk_icon_bitmaps,
-                                web_application_info_->icon_bitmaps);
+  const WebApp* app = registrar_.GetAppById(app_id_);
+  DCHECK(app);
+
+  return HaveIconBitmapsChanged(
+      disk_icon_bitmaps, web_application_info_->icon_bitmaps,
+      web_application_info_->manifest_icons, app->manifest_icons(),
+      /* end_when_mismatch_detected= */ false);
 }
 
 void ManifestUpdateTask::OnAllShortcutsMenuIconsRead(
@@ -490,12 +576,17 @@
     return true;
   }
 
+  const WebApp* app = registrar_.GetAppById(app_id_);
+  DCHECK(app);
   for (size_t i = 0; i < downloaded_shortcuts_menu_icon_bitmaps.size(); ++i) {
     const IconBitmaps& downloaded_icon_bitmaps =
         downloaded_shortcuts_menu_icon_bitmaps[i];
     const IconBitmaps& disk_icon_bitmaps = disk_shortcuts_menu_icon_bitmaps[i];
-    if (HaveIconBitmapsChanged(disk_icon_bitmaps, downloaded_icon_bitmaps)
-            .changes_detected)
+    if (HaveIconBitmapsChanged(disk_icon_bitmaps, downloaded_icon_bitmaps,
+                               web_application_info_->manifest_icons,
+                               app->manifest_icons(),
+                               /* end_when_mismatch_detected= */ true)
+            .mismatch())
       return true;
   }
 
diff --git a/chrome/browser/web_applications/manifest_update_task.h b/chrome/browser/web_applications/manifest_update_task.h
index 17ed559..53c85e97 100644
--- a/chrome/browser/web_applications/manifest_update_task.h
+++ b/chrome/browser/web_applications/manifest_update_task.h
@@ -58,6 +58,84 @@
   kMaxValue = kAppAssociationsUpdated,
 };
 
+enum IconDiffResult : uint32_t {
+  NO_CHANGE_DETECTED = 0,
+
+  // A mismatch was detected between what was downloaded and what is on disk.
+  // This might mean that a size has been removed or added, and it could mean
+  // both.
+  MISMATCHED_IMAGE_SIZES = 1 << 1,
+
+  // At least one icon was found to have changed. Note: Used only if the diff
+  // process stops when it encounters the first mismatch. If, instead, it is
+  // allowed to continue, a more detailed results will be returned (see flags
+  // below).
+  ONE_OR_MORE_ICONS_CHANGED = 1 << 2,
+
+  // Only one icon is changing. This flag is only set if the diff process is
+  // allowed to continue to the end (doesn't stop as soon as it finds a change).
+  SINGLE_ICON_CHANGED = 1 << 3,
+
+  // Two or more icons are changing. This flag is only set if the diff process
+  // is allowed to continue to the end (doesn't stop as soon as it finds a
+  // change).
+  MULTIPLE_ICONS_CHANGED = 1 << 4,
+
+  // And icon has changed, but it was a generated icon that changed. This flag
+  // is only set if the diff process is allowed to continue to the end (doesn't
+  // stop as soon as it finds a change).
+  GENERATED_ICON_CHANGED = 1 << 5,
+};
+
+// A structure to keep track of the differences found while comparing icons
+// on disk to what has been downloaded.
+struct IconDiff {
+ public:
+  IconDiff() = default;
+  explicit IconDiff(uint32_t results) { diff_results = results; }
+  IconDiff(const SkBitmap& before_icon,
+           const SkBitmap& after_icon,
+           uint32_t results) {
+    before = before_icon;
+    after = after_icon;
+    diff_results = results;
+  }
+
+  // Returns true iff an icon change was detected (not matter how
+  // insignificant).
+  bool mismatch() { return diff_results != NO_CHANGE_DETECTED; }
+
+  // Returns true iff the mismatch should result in app identity dlg being
+  // shown.
+  bool supported_for_app_identity_check() {
+    return diff_results == SINGLE_ICON_CHANGED ||
+           diff_results == (SINGLE_ICON_CHANGED | GENERATED_ICON_CHANGED);
+  }
+
+  // Keeps track of all the differences discovered in the icon set.
+  uint32_t diff_results = NO_CHANGE_DETECTED;
+
+  // The original image. Only valid if a single icon is changing.
+  SkBitmap before;
+
+  // The changed image. Only valid if a single icon is changing.
+  SkBitmap after;
+};
+
+// Returns whether any differences were found in the images on disk and what has
+// been downloaded. The |disk_icon_bitmaps| and |disk_icon_info| parameters
+// represent the bits on disk and the associated size info (respectively). Same
+// with |downloaded_icon_bitmaps| and |downloaded_icon_info|, which covers the
+// downloaded icon set. If |end_when_mismatch_detected| is true, the diff
+// process will stop when it encounters the first mismatch. Otherwise, it the
+// IconDiff returned will cover all the differences found.
+IconDiff HaveIconBitmapsChanged(
+    const IconBitmaps& disk_icon_bitmaps,
+    const IconBitmaps& downloaded_icon_bitmaps,
+    const std::vector<apps::IconInfo>& disk_icon_info,
+    const std::vector<apps::IconInfo>& downloaded_icon_info,
+    bool end_when_mismatch_detected);
+
 // Checks whether the installed web app associated with a given WebContents has
 // out of date manifest data and triggers an update if so.
 // Owned and managed by |ManifestUpdateManager|.
diff --git a/chrome/browser/web_applications/manifest_update_task_unittest.cc b/chrome/browser/web_applications/manifest_update_task_unittest.cc
index eff1a6c..b088ba0 100644
--- a/chrome/browser/web_applications/manifest_update_task_unittest.cc
+++ b/chrome/browser/web_applications/manifest_update_task_unittest.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/web_applications/manifest_update_task.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
+#include "chrome/browser/web_applications/web_app_icon_generator.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
@@ -126,4 +128,359 @@
   EXPECT_NE(old_handlers, new_handlers);
 }
 
+std::vector<apps::IconInfo> GenerateIconInfosFrom(
+    const IconBitmaps& downloaded) {
+  std::vector<apps::IconInfo> result;
+  for (auto entry : downloaded.any) {
+    apps::IconInfo icon_info(GURL(), entry.first);
+    icon_info.purpose = apps::IconInfo::Purpose::kAny;
+    result.push_back(icon_info);
+  }
+  for (auto entry : downloaded.maskable) {
+    apps::IconInfo icon_info(GURL(), entry.first);
+    icon_info.purpose = apps::IconInfo::Purpose::kMaskable;
+    result.push_back(icon_info);
+  }
+  for (auto entry : downloaded.monochrome) {
+    apps::IconInfo icon_info(GURL(), entry.first);
+    icon_info.purpose = apps::IconInfo::Purpose::kMonochrome;
+    result.push_back(icon_info);
+  }
+  return result;
+}
+
+TEST_F(ManifestUpdateTaskTest, TestImageComparison) {
+  {
+    // Test case: Find first difference with two empty IconBitmaps as input
+    // should report no differences.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    IconDiff diff = HaveIconBitmapsChanged(
+        on_disk, downloaded, GenerateIconInfosFrom(on_disk),
+        GenerateIconInfosFrom(downloaded),
+        /* end_when_mismatch_detected= */ true);
+    EXPECT_EQ(NO_CHANGE_DETECTED, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+  {
+    // Test case: Find all differences with two empty IconBitmaps as input
+    // should report no differences.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+
+    IconDiff diff = HaveIconBitmapsChanged(
+        on_disk, downloaded, GenerateIconInfosFrom(on_disk),
+        GenerateIconInfosFrom(downloaded),
+        /* end_when_mismatch_detected= */ false);
+    EXPECT_EQ(NO_CHANGE_DETECTED, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+
+  {
+    // Test case: Find first difference when one new image has been downloaded
+    // should report size mismatch.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&downloaded.any, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(
+        on_disk, downloaded, GenerateIconInfosFrom(on_disk),
+        GenerateIconInfosFrom(downloaded),
+        /* end_when_mismatch_detected= */ true);
+    EXPECT_EQ(MISMATCHED_IMAGE_SIZES, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+  {
+    // Test case: Find all differences when one new image has been downloaded
+    // should report size mismatch.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&downloaded.any, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(
+        on_disk, downloaded, GenerateIconInfosFrom(on_disk),
+        GenerateIconInfosFrom(downloaded),
+        /* end_when_mismatch_detected= */ false);
+    EXPECT_EQ(MISMATCHED_IMAGE_SIZES, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+
+  {
+    // Test case: Find first difference when one image has been removed
+    // should report size mismatch.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.any, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(
+        on_disk, downloaded, GenerateIconInfosFrom(on_disk),
+        GenerateIconInfosFrom(downloaded),
+        /* end_when_mismatch_detected= */ true);
+    EXPECT_EQ(MISMATCHED_IMAGE_SIZES, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+  {
+    // Test case: Find all differences when one new image has been removed
+    // should report size mismatch.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.any, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(
+        on_disk, downloaded, GenerateIconInfosFrom(on_disk),
+        GenerateIconInfosFrom(downloaded),
+        /* end_when_mismatch_detected= */ false);
+    EXPECT_EQ(MISMATCHED_IMAGE_SIZES, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+
+  {
+    // Test case: Find first difference, when one image has been removed and one
+    // added, should report size mismatch.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.any, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.any, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected= */
+                                           true);
+    // First mismatch found will be the added image, then it will stop.
+    EXPECT_EQ(MISMATCHED_IMAGE_SIZES, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+  {
+    // Test case: Find all differences, when one image has been removed and one
+    // added, should report size mismatch.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.any, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.any, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected=
+                                            */
+                                           false);
+    EXPECT_EQ(MISMATCHED_IMAGE_SIZES, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+
+  {
+    // Test case: Find first difference, when one image has been removed and one
+    // added (but across maps), should report size mismatch.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.maskable, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.monochrome, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected=
+                                            */
+                                           true);
+    // First mismatch found will be the fact that one of the maps has changed
+    // size.
+    EXPECT_EQ(MISMATCHED_IMAGE_SIZES, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+  {
+    // Test case: Find all differences, when one image has been removed and one
+    // added (but across maps), should report size mismatch.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.maskable, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.monochrome, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected= */
+                                           false);
+    EXPECT_EQ(MISMATCHED_IMAGE_SIZES, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+
+  {
+    // Test case: Find first difference, when one image has had its bits
+    // updated, should return ONE_OR_MORE_ICONS_CHANGED.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.any, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.any, icon_size::k256, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected= */
+                                           true);
+    EXPECT_EQ(ONE_OR_MORE_ICONS_CHANGED, diff.diff_results);
+    // The expectation here might, at a glance, seem unusual because there *has*
+    // been a change in only a single icon. However, this was detected via the
+    // short pass, which does not provide |before| and |after| images (only the
+    // longer pass will know whether more images changed).
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+  {
+    // Test case: Find all differences, when one image has had its bits
+    // updated, should return SINGLE_ICON_CHANGED.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.any, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.any, icon_size::k256, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(
+        on_disk, downloaded, GenerateIconInfosFrom(on_disk),
+        GenerateIconInfosFrom(downloaded),
+        /* end_when_mismatch_detected= */ false);
+    EXPECT_EQ(SINGLE_ICON_CHANGED, diff.diff_results);
+    // The function has checked all possibilities and is able to provide before
+    // and after images, because it knows only a single image changed.
+    EXPECT_FALSE(diff.before.drawsNothing());
+    EXPECT_FALSE(diff.after.drawsNothing());
+  }
+
+  {
+    // Test case: Find first difference, when two images have had their bits
+    // updated, should return ONE_OR_MORE_ICONS_CHANGED.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.any, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&on_disk.any, icon_size::k512, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.any, icon_size::k256, SK_ColorYELLOW);
+    AddGeneratedIcon(&downloaded.any, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected= */
+                                           true);
+    EXPECT_EQ(ONE_OR_MORE_ICONS_CHANGED, diff.diff_results);
+    // Since more than two images changed, the |before| and |after| isn't
+    // provided.
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+  {
+    // Test case: Find all differences, when two images have had their bits
+    // updated, should return MULTIPLE_ICONS_CHANGED.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.any, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&on_disk.any, icon_size::k512, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.any, icon_size::k256, SK_ColorYELLOW);
+    AddGeneratedIcon(&downloaded.any, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected= */
+                                           false);
+    EXPECT_EQ(MULTIPLE_ICONS_CHANGED, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+
+  {
+    // Test case: Find first difference, when two images have had their bits
+    // updated (across |any| and |maskable|), should return
+    // ONE_OR_MORE_ICONS_CHANGED.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.any, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&on_disk.maskable, icon_size::k512, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.any, icon_size::k256, SK_ColorYELLOW);
+    AddGeneratedIcon(&downloaded.maskable, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected= */
+                                           true);
+    EXPECT_EQ(ONE_OR_MORE_ICONS_CHANGED, diff.diff_results);
+    // Since more than two images changed, the |before| and |after| isn't
+    // provided.
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+  {
+    // Test case: Find all differences, when two images have had their bits
+    // updated (across |any| and |maskable|), should return
+    // MULTIPLE_ICONS_CHANGED.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.any, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&on_disk.maskable, icon_size::k512, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.any, icon_size::k256, SK_ColorYELLOW);
+    AddGeneratedIcon(&downloaded.maskable, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected= */
+                                           false);
+    EXPECT_EQ(MULTIPLE_ICONS_CHANGED, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+
+  {
+    // Test case: Find first difference, when two images have had their bits
+    // updated (across |maskable| and |monochrome|), should return
+    // ONE_OR_MORE_ICON_CHANGED.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.maskable, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&on_disk.monochrome, icon_size::k512, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.maskable, icon_size::k256, SK_ColorYELLOW);
+    AddGeneratedIcon(&downloaded.monochrome, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected= */
+                                           true);
+    EXPECT_EQ(ONE_OR_MORE_ICONS_CHANGED, diff.diff_results);
+    // Since more than two images changed, the |before| and |after| isn't
+    // provided.
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+  {
+    // Test case: Find all differences, when two images have had their bits
+    // updated (across |maskable| and |monochrome|), should return
+    // MULTIPLE_ICONS_CHANGED.
+    IconBitmaps on_disk;
+    IconBitmaps downloaded;
+    AddGeneratedIcon(&on_disk.maskable, icon_size::k256, SK_ColorRED);
+    AddGeneratedIcon(&on_disk.monochrome, icon_size::k512, SK_ColorRED);
+    AddGeneratedIcon(&downloaded.maskable, icon_size::k256, SK_ColorYELLOW);
+    AddGeneratedIcon(&downloaded.monochrome, icon_size::k512, SK_ColorYELLOW);
+
+    IconDiff diff = HaveIconBitmapsChanged(on_disk, downloaded,
+                                           GenerateIconInfosFrom(on_disk),
+                                           GenerateIconInfosFrom(downloaded),
+                                           /* end_when_mismatch_detected= */
+                                           false);
+    EXPECT_EQ(MULTIPLE_ICONS_CHANGED, diff.diff_results);
+    EXPECT_TRUE(diff.before.drawsNothing());
+    EXPECT_TRUE(diff.after.drawsNothing());
+  }
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/xsurface/BUILD.gn b/chrome/browser/xsurface/BUILD.gn
index a2a5b13..15304463 100644
--- a/chrome/browser/xsurface/BUILD.gn
+++ b/chrome/browser/xsurface/BUILD.gn
@@ -12,7 +12,6 @@
     "android/java/src/org/chromium/chrome/browser/xsurface/HybridListRenderer.java",
     "android/java/src/org/chromium/chrome/browser/xsurface/ImageCacheHelper.java",
     "android/java/src/org/chromium/chrome/browser/xsurface/ImageFetchClient.java",
-    "android/java/src/org/chromium/chrome/browser/xsurface/ImagePrefetcher.java",
     "android/java/src/org/chromium/chrome/browser/xsurface/ListContentManager.java",
     "android/java/src/org/chromium/chrome/browser/xsurface/ListContentManagerObserver.java",
     "android/java/src/org/chromium/chrome/browser/xsurface/PersistentKeyValueCache.java",
diff --git a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/FeedActionsHandler.java b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/FeedActionsHandler.java
index 1aa05e9..6dc75fa 100644
--- a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/FeedActionsHandler.java
+++ b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/FeedActionsHandler.java
@@ -4,10 +4,6 @@
 
 package org.chromium.chrome.browser.xsurface;
 
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
 import java.util.Map;
 
 /**
@@ -28,22 +24,6 @@
     default void processThereAndBackAgainData(byte[] data) {}
 
     /**
-     * Sends data back to the server when content is clicked and provides the corresponding view
-     * through |actionSourceView| which can be null.
-     */
-    @Deprecated
-    default void processThereAndBackAgainData(byte[] data, @Nullable View actionSourceView) {}
-
-    /**
-     * Stores a view FeedAction for eventual upload. 'data' is a serialized FeedAction protobuf
-     * message.
-     *
-     * This is in the process of being removed, replaced by FeedLoggingDependencyProvider.
-     */
-    @Deprecated
-    default void processViewAction(byte[] data) {}
-
-    /**
      * Triggers Chrome to send user feedback for this card.
      */
     default void sendFeedback(Map<String, String> productSpecificDataMap) {}
diff --git a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ImagePrefetcher.java b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ImagePrefetcher.java
deleted file mode 100644
index ba891d3a..0000000
--- a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ImagePrefetcher.java
+++ /dev/null
@@ -1,16 +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.
-
-package org.chromium.chrome.browser.xsurface;
-
-// TODO(freedjm): Remove ImagePrefetcher after internal changes
-//                land for ImageCacheHelper.
-
-/**
- * Interface to prefetch an image and cache it on disk. This
- * allows native code to call to the image loader across the
- * xsurface.
- */
-@Deprecated
-public interface ImagePrefetcher extends ImageCacheHelper {}
diff --git a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ProcessScope.java b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ProcessScope.java
index 88602d08..965d5a7 100644
--- a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ProcessScope.java
+++ b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ProcessScope.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.xsurface;
 
-import android.content.Context;
 import androidx.annotation.Nullable;
 
 /**
@@ -26,23 +25,12 @@
      **/
     @Nullable
     default SurfaceScope obtainSurfaceScope(SurfaceScopeDependencyProvider dependencyProvider) {
-        return obtainSurfaceScope(dependencyProvider.getActivityContext());
-    }
-
-    @Nullable
-    @Deprecated
-    default SurfaceScope obtainSurfaceScope(Context activityContext) {
-        return null;
-    }
-
-    @Nullable
-    default ImagePrefetcher provideImagePrefetcher() {
         return null;
     }
 
     @Nullable
     default ImageCacheHelper provideImageCacheHelper() {
-        return provideImagePrefetcher();
+        return null;
     }
 
     @Nullable
diff --git a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceActionsHandler.java b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceActionsHandler.java
index 02fe7d2..c776472 100644
--- a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceActionsHandler.java
+++ b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceActionsHandler.java
@@ -18,10 +18,6 @@
      * @param actionSourceView The View from which the user tap originated. May be null.
      */
     default void navigateTab(String url, View actionSourceView) {}
-    @Deprecated
-    default void navigateTab(String url) {
-        navigateTab(url, null);
-    }
 
     /**
      * Navigates a new tab to a particular URL.
@@ -29,10 +25,6 @@
      * @param actionSourceView The View from which the user tap originated. May be null.
      */
     default void navigateNewTab(String url, View actionSourceView) {}
-    @Deprecated
-    default void navigateNewTab(String url) {
-        navigateNewTab(url, null);
-    }
 
     /**
      * Navigate a new incognito tab to a URL.
@@ -53,10 +45,6 @@
      * @param actionSourceView The View from which the user tap originated. May be null.
      */
     default void showBottomSheet(View view, View actionSourceView) {}
-    @Deprecated
-    default void showBottomSheet(View view) {
-        showBottomSheet(view, null);
-    }
 
     /**
      * Dismiss the open bottom sheet (or do nothing if there isn't one).
diff --git a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceScopeDependencyProvider.java b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceScopeDependencyProvider.java
index c376d35..57116018 100644
--- a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceScopeDependencyProvider.java
+++ b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceScopeDependencyProvider.java
@@ -48,31 +48,6 @@
         return AutoplayPreference.AUTOPLAY_DISABLED;
     }
 
-    /** Events that are triggered during the video auto-play. */
-    @Deprecated
-    public enum AutoplayEvent {
-        /**
-         * Auto-play is triggered, but not started yet. This occurs when the video card becomes
-         * fully visible.
-         */
-        AUTOPLAY_REQUESTED,
-        /** The player starts to auto-play the video. */
-        AUTOPLAY_STARTED,
-        /**
-         * Auto-play stops before reaching the end. This occurs when the video card becomes
-         * partially visible or invisible.
-         */
-        AUTOPLAY_STOPPED,
-        /** Auto-play reaches the end. */
-        AUTOPLAY_ENDED,
-        /** User clicks on the auto-play video. */
-        AUTOPLAY_CLICKED,
-    }
-
-    /** Reports the event related to video auto-play. */
-    @Deprecated
-    default void reportAutoplayEvent(AutoplayEvent event) {}
-
     /** Events that are triggered during the video playing. */
     public @interface VideoPlayEvent {
         // Events applying muted autoplay only.
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 3399956c..24e33fa 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1634039987-c14b3cf6766e27af9b66f7fb02d8bd017252919c.profdata
+chrome-win32-main-1634050758-81ac571898085b700004c0e280809306718f9da2.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index f8b0d084..0bd63ad 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1634039987-cd0af9f9b0d85eae30a1fb4a0ddd84ccd1a74ee5.profdata
+chrome-win64-main-1634050758-7b5caff70f9b52b0f1ba9a90b5545e65e5dac729.profdata
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index 0d34dbed..317cb0e2 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -138,6 +138,7 @@
         "$root_gen_dir/chrome/dev_ui_resources.pak",
         "$root_gen_dir/chrome/download_shelf_resources.pak",
         "$root_gen_dir/chrome/downloads_resources.pak",
+        "$root_gen_dir/chrome/enterprise_casting_resources.pak",
         "$root_gen_dir/chrome/feedback_resources.pak",
         "$root_gen_dir/chrome/gaia_auth_host_resources.pak",
         "$root_gen_dir/chrome/history_resources.pak",
@@ -165,6 +166,7 @@
         "//chrome/browser/resources/commander:resources",
         "//chrome/browser/resources/download_shelf:resources",
         "//chrome/browser/resources/downloads:resources",
+        "//chrome/browser/resources/enterprise_casting:resources",
         "//chrome/browser/resources/feedback_webui:resources",
         "//chrome/browser/resources/gaia_auth_host:resources",
         "//chrome/browser/resources/history:resources",
@@ -211,7 +213,6 @@
         "$root_gen_dir/chrome/browser/supervised_user/supervised_user_unscaled_resources.pak",
         "$root_gen_dir/chrome/common/chromeos/extensions/chromeos_system_extensions_resources.pak",
         "$root_gen_dir/chrome/emoji_picker_resources.pak",
-        "$root_gen_dir/chrome/enterprise_casting_resources.pak",
         "$root_gen_dir/chrome/internet_config_dialog_resources.pak",
         "$root_gen_dir/chrome/internet_detail_dialog_resources.pak",
         "$root_gen_dir/chrome/launcher_internals_resources.pak",
@@ -260,7 +261,6 @@
         "//chrome/browser/resources/chromeos:multidevice_setup_resources",
         "//chrome/browser/resources/chromeos/audio:resources",
         "//chrome/browser/resources/chromeos/emoji_picker:resources",
-        "//chrome/browser/resources/chromeos/enterprise_casting:resources",
         "//chrome/browser/resources/chromeos/launcher_internals:resources",
         "//chrome/browser/resources/chromeos/login:conditional_resources",
         "//chrome/browser/resources/chromeos/login:unconditional_resources",
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 0b9030b..464ec62 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -560,8 +560,8 @@
     kChromeUIWebAppInternalsHost,
 #endif
     content::kChromeUIAppCacheInternalsHost,
+    content::kChromeUIAttributionInternalsHost,
     content::kChromeUIBlobInternalsHost,
-    content::kChromeUIConversionInternalsHost,
     content::kChromeUIDinoHost,
     content::kChromeUIGpuHost,
     content::kChromeUIHistogramHost,
diff --git a/chrome/installer/gcapi_mac/gcapi.mm b/chrome/installer/gcapi_mac/gcapi.mm
index 0d6cc4e..d67b1fa 100644
--- a/chrome/installer/gcapi_mac/gcapi.mm
+++ b/chrome/installer/gcapi_mac/gcapi.mm
@@ -429,9 +429,8 @@
         isbrandchar(brand_code[1]) && isbrandchar(brand_code[2]) &&
         isbrandchar(brand_code[3]);
 
-    NSString* brand_path = nil;
     if (valid_brand_code)
-      brand_path = WriteBrandCode(brand_code, user);
+      WriteBrandCode(brand_code, user);
 
     // Write master prefs.
     if (master_prefs_contents)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index ea47e3e..d10eabea 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -7606,10 +7606,8 @@
       "../browser/ui/views/frame/test_with_browser_view.cc",
       "../browser/ui/views/frame/test_with_browser_view.h",
       "../browser/ui/views/frame/web_contents_close_handler_unittest.cc",
-      "../browser/ui/views/global_media_controls/media_notification_container_impl_view_unittest.cc",
-      "../browser/ui/views/global_media_controls/media_notification_device_selector_view_unittest.cc",
-      "../browser/ui/views/global_media_controls/media_notification_footer_view_unittest.cc",
-      "../browser/ui/views/global_media_controls/media_notification_list_view_unittest.cc",
+      "../browser/ui/views/global_media_controls/media_item_ui_device_selector_view_unittest.cc",
+      "../browser/ui/views/global_media_controls/media_item_ui_footer_view_unittest.cc",
       "../browser/ui/views/hover_button_unittest.cc",
       "../browser/ui/views/infobars/infobar_view_unittest.cc",
       "../browser/ui/views/intent_picker_bubble_view_unittest.cc",
diff --git a/chrome/test/DEPS b/chrome/test/DEPS
index 3c43330..2ad359a 100644
--- a/chrome/test/DEPS
+++ b/chrome/test/DEPS
@@ -23,6 +23,7 @@
   "+components/feature_engagement",
   "+components/find_in_page",
   "+components/gcm_driver/instance_id",
+  "+components/global_media_controls",
   "+components/google/core/common",
   "+components/guest_view/browser",
   "+components/history/content",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/WPRArchiveDirectory.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/WPRArchiveDirectory.java
index d82e6f0..540a7481e 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/WPRArchiveDirectory.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/WPRArchiveDirectory.java
@@ -34,10 +34,35 @@
  * During gClient runhooks, the files in /path_of_file_foo and /path_of_file_bar
  * are downloaded from GCS. Once the files are downloaded, it will be used in
  * tests as isolated.
+ *
+ * Optionally, a test may additionally be annotated with ArchiveName. This allows multiple tests
+ * to share the same archive. This is possible when a single test exercises the same network
+ * requests needed by other tests.
+ *
+ * For example, these two tests use the same WPR file. You can record running testA, and then replay
+ * on both testA and testB.
+ *
+ *     @Feature("WPRRecordReplayTest")
+ *     @WPRArchiveDirectory("/path_of_file_foo")
+ *     @WPRArchiveDirectory.ArchiveName("shared.wprgo")
+ *     public void test_A() {
+ *        ...
+ *     }
+ *
+ *     @Feature("WPRRecordReplayTest")
+ *     @WPRArchiveDirectory("/path_of_file_foo")
+ *     @WPRArchiveDirectory.ArchiveName("shared.wprgo")
+ *     public void test_B() {
+ *     }
  */
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface WPRArchiveDirectory {
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface ArchiveName {
+        String[] value();
+    }
+
     /**
      * @return one WPRArchiveDirectory.
      */
diff --git a/chrome/test/data/banners/128x128-green.png b/chrome/test/data/banners/128x128-green.png
new file mode 100644
index 0000000..5349063
--- /dev/null
+++ b/chrome/test/data/banners/128x128-green.png
Binary files differ
diff --git a/chrome/test/data/banners/128x128-red.png b/chrome/test/data/banners/128x128-red.png
new file mode 100644
index 0000000..485144e9
--- /dev/null
+++ b/chrome/test/data/banners/128x128-red.png
Binary files differ
diff --git a/chrome/test/data/banners/256x256-green.png b/chrome/test/data/banners/256x256-green.png
new file mode 100644
index 0000000..b644451
--- /dev/null
+++ b/chrome/test/data/banners/256x256-green.png
Binary files differ
diff --git a/chrome/test/data/banners/256x256-red.png b/chrome/test/data/banners/256x256-red.png
new file mode 100644
index 0000000..db9c0c2
--- /dev/null
+++ b/chrome/test/data/banners/256x256-red.png
Binary files differ
diff --git a/chrome/test/data/banners/512x512-green.png b/chrome/test/data/banners/512x512-green.png
new file mode 100644
index 0000000..5ebfbe0
--- /dev/null
+++ b/chrome/test/data/banners/512x512-green.png
Binary files differ
diff --git a/chrome/test/data/banners/512x512-red.png b/chrome/test/data/banners/512x512-red.png
new file mode 100644
index 0000000..4f000b3
--- /dev/null
+++ b/chrome/test/data/banners/512x512-red.png
Binary files differ
diff --git a/chrome/test/data/webui/chromeos/personalization_app/test_mojo_interface_provider.js b/chrome/test/data/webui/chromeos/personalization_app/test_mojo_interface_provider.js
index 39372ae..ee75634 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/test_mojo_interface_provider.js
+++ b/chrome/test/data/webui/chromeos/personalization_app/test_mojo_interface_provider.js
@@ -22,6 +22,9 @@
       'setDailyRefreshCollectionId',
       'getDailyRefreshCollectionId',
       'updateDailyRefreshWallpaper',
+      'isInTabletMode',
+      'confirmPreviewWallpaper',
+      'cancelPreviewWallpaper',
     ]);
 
     /**
@@ -91,6 +94,9 @@
     this.updateDailyRefreshWallpaperResponse = true;
 
     /** @public */
+    this.isInTabletModeResponse = true;
+
+    /** @public */
     this.wallpaperObserverUpdateTimeout = 0;
 
     /**
@@ -185,6 +191,21 @@
     return Promise.resolve({success: this.updateDailyRefreshWallpaperResponse});
   }
 
+  isInTabletMode() {
+    this.methodCalled('isInTabletMode');
+    return Promise.resolve({tabletMode: this.isInTabletModeResponse});
+  }
+
+  /** @override */
+  confirmPreviewWallpaper() {
+    this.methodCalled('confirmPreviewWallpaper');
+  }
+
+  /** @override */
+  cancelPreviewWallpaper() {
+    this.methodCalled('cancelPreviewWallpaper');
+  }
+
   /**
    * @param {!Array<!chromeos.personalizationApp.mojom.WallpaperCollection>}
    *     collections
diff --git a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_fullscreen_element_test.js b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_fullscreen_element_test.js
index 6738d63..54e55d7 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_fullscreen_element_test.js
+++ b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_fullscreen_element_test.js
@@ -230,4 +230,28 @@
 
     await wallpaperProvider.whenCalled('updateDailyRefreshWallpaper');
   });
+
+  test('clicking set as wallpaper confirms wallpaper', async () => {
+    wallpaperFullscreenElement = initElement(WallpaperFullscreen.is);
+    const {requestFullscreenPromise} = mockFullscreenApis();
+    await waitAfterNextRender(wallpaperFullscreenElement);
+
+    personalizationStore.data.fullscreen = true;
+    personalizationStore.data.currentSelected = {
+      ...personalizationStore.data.currentSelected,
+      type: chromeos.personalizationApp.mojom.WallpaperType.kDaily,
+    };
+    personalizationStore.data.dailyRefresh.collectionId =
+        wallpaperProvider.collections[0].id;
+    personalizationStore.data.pendingSelected = wallpaperProvider.images[1];
+    personalizationStore.notifyObservers();
+
+    await waitAfterNextRender(wallpaperFullscreenElement);
+
+    const setAsWallpaperButton =
+        wallpaperFullscreenElement.shadowRoot.getElementById('confirm');
+    setAsWallpaperButton.click();
+
+    await wallpaperProvider.whenCalled('confirmPreviewWallpaper');
+  });
 }
diff --git a/chrome/test/data/webui/chromeos/scanning/scan_preview_test.js b/chrome/test/data/webui/chromeos/scanning/scan_preview_test.js
index 11261ad..a2941f9 100644
--- a/chrome/test/data/webui/chromeos/scanning/scan_preview_test.js
+++ b/chrome/test/data/webui/chromeos/scanning/scan_preview_test.js
@@ -319,7 +319,7 @@
         .then(() => {
           imageHeight = scanPreview.$$('#scannedImages')
                             .getElementsByClassName('scanned-image')[0]
-                            .height;
+                            .offsetHeight;
 
           // With two scanned images the viewport should be scrolled so the
           // second image is at the top.
diff --git a/chrome/test/data/webui/print_preview/destination_search_test.js b/chrome/test/data/webui/print_preview/destination_search_test.js
index 5abb246..6a7205a 100644
--- a/chrome/test/data/webui/print_preview/destination_search_test.js
+++ b/chrome/test/data/webui/print_preview/destination_search_test.js
@@ -78,7 +78,8 @@
     // Get print list and fire event.
     const list =
         dialog.shadowRoot.querySelector('print-preview-destination-list');
-    list.fire('destination-selected', item);
+    list.dispatchEvent(new CustomEvent(
+        'destination-selected', {bubbles: true, composed: true, detail: item}));
   }
 
   /**
diff --git a/chrome/test/data/webui/print_preview/destination_search_test_chromeos.js b/chrome/test/data/webui/print_preview/destination_search_test_chromeos.js
index ae73beb..0dc34f9 100644
--- a/chrome/test/data/webui/print_preview/destination_search_test_chromeos.js
+++ b/chrome/test/data/webui/print_preview/destination_search_test_chromeos.js
@@ -87,7 +87,8 @@
     // Get print list and fire event.
     const list =
         dialog.shadowRoot.querySelector('print-preview-destination-list');
-    list.fire('destination-selected', item);
+    list.dispatchEvent(new CustomEvent(
+        'destination-selected', {bubbles: true, composed: true, detail: item}));
   }
 
   /**
diff --git a/chrome/test/data/webui/print_preview/destination_settings_test.js b/chrome/test/data/webui/print_preview/destination_settings_test.js
index b4d5bc1..1189ec7 100644
--- a/chrome/test/data/webui/print_preview/destination_settings_test.js
+++ b/chrome/test/data/webui/print_preview/destination_settings_test.js
@@ -511,7 +511,9 @@
           const whenDestinationSelect = eventToPromise(
               DestinationStoreEventType.DESTINATION_SELECT,
               destinationSettings.getDestinationStoreForTest());
-          dropdown.fire('selected-option-change', 'Save as PDF/local/');
+          dropdown.dispatchEvent(new CustomEvent(
+              'selected-option-change',
+              {bubbles: true, composed: true, detail: 'Save as PDF/local/'}));
 
           // Ensure this fires the destination select event.
           return whenDestinationSelect;
@@ -573,7 +575,11 @@
               const whenDestinationSelect = eventToPromise(
                   DestinationStoreEventType.DESTINATION_SELECT,
                   destinationSettings.getDestinationStoreForTest());
-              dropdown.fire('selected-option-change', driveDestinationKey);
+              dropdown.dispatchEvent(new CustomEvent('selected-option-change', {
+                bubbles: true,
+                composed: true,
+                detail: driveDestinationKey,
+              }));
               return whenDestinationSelect;
             })
             .then(() => {
@@ -623,8 +629,11 @@
               const whenDestinationSelect = eventToPromise(
                   DestinationStoreEventType.DESTINATION_SELECT,
                   destinationSettings.getDestinationStoreForTest());
-              dropdown.fire(
-                  'selected-option-change', makeLocalDestinationKey('ID2'));
+              dropdown.dispatchEvent(new CustomEvent('selected-option-change', {
+                bubbles: true,
+                composed: true,
+                detail: makeLocalDestinationKey('ID2'),
+              }));
               return whenDestinationSelect;
             })
             .then(() => {
@@ -662,7 +671,9 @@
           }
           assertDropdownItems(dropdownItems);
 
-          dropdown.fire('selected-option-change', 'seeMore');
+          dropdown.dispatchEvent(new CustomEvent(
+              'selected-option-change',
+              {bubbles: true, composed: true, detail: 'seeMore'}));
           return waitBeforeNextRender(destinationSettings);
         })
         .then(() => {
@@ -732,7 +743,9 @@
               }
 
               assertDropdownItems(dropdownItems);
-              dropdown.fire('selected-option-change', 'seeMore');
+              dropdown.dispatchEvent(new CustomEvent(
+                  'selected-option-change',
+                  {bubbles: true, composed: true, detail: 'seeMore'}));
               return waitBeforeNextRender(destinationSettings);
             })
             .then(() => {
@@ -746,7 +759,9 @@
                   DestinationStoreEventType.DESTINATIONS_INSERTED,
                   destinationSettings.getDestinationStoreForTest());
               // Simulate setting a new account.
-              dialog.fire('account-change', account2);
+              dialog.dispatchEvent(new CustomEvent(
+                  'account-change',
+                  {bubbles: true, composed: true, detail: account2}));
               flush();
               return whenAdded;
             })
@@ -825,6 +840,8 @@
               nativeLayer.resetResolver('getPrinterCapabilities');
               destinationSettings.shadowRoot.querySelector('#destinationSelect')
                   .dispatchEvent(new CustomEvent('selected-option-change', {
+                    bubbles: true,
+                    composed: true,
                     detail: 'Save as PDF/local/',
                   }));
               flush();
diff --git a/chrome/test/data/webui/print_preview/destination_settings_test_cros.js b/chrome/test/data/webui/print_preview/destination_settings_test_cros.js
index 3b751f9a..ad8a40f 100644
--- a/chrome/test/data/webui/print_preview/destination_settings_test_cros.js
+++ b/chrome/test/data/webui/print_preview/destination_settings_test_cros.js
@@ -174,7 +174,11 @@
               // we don't try to fetch the license again.
               nativeLayer.resetResolver('getPrinterCapabilities');
               destinationSettings.shadowRoot.querySelector('#destinationSelect')
-                  .fire('selected-option-change', 'ID1/chrome_os/');
+                  .dispatchEvent(new CustomEvent('selected-option-change', {
+                    bubbles: true,
+                    composed: true,
+                    detail: 'ID1/chrome_os/',
+                  }));
             })
             .then(() => {
               assertEquals(
diff --git a/chrome/test/enterprise/e2e/policy/password_manager_enabled/password_manager_enabled_webdriver_test.py b/chrome/test/enterprise/e2e/policy/password_manager_enabled/password_manager_enabled_webdriver_test.py
index e595467..0f35055 100644
--- a/chrome/test/enterprise/e2e/policy/password_manager_enabled/password_manager_enabled_webdriver_test.py
+++ b/chrome/test/enterprise/e2e/policy/password_manager_enabled/password_manager_enabled_webdriver_test.py
@@ -6,16 +6,13 @@
 from absl import app
 
 
-def getShadowDom(driver, root, selector):
-  el = root.find_element_by_css_selector(selector)
-  return driver.execute_script("return arguments[0].shadowRoot", el)
-
-
-def getNestedShadowDom(driver, selectors):
-  el = driver
-  for selector in selectors:
-    el = getShadowDom(driver, el, selector)
-  return el
+def getElementFromShadowRoot(driver, element, selector):
+  if element is None:
+    return None
+  else:
+    return driver.execute_script(
+        "return arguments[0].shadowRoot.querySelector(arguments[1])", element,
+        selector)
 
 
 def main(argv):
@@ -23,12 +20,16 @@
   driver.get("chrome://settings/passwords")
 
   # The settings is nested within multiple shadow doms - extract it.
-  el = getNestedShadowDom(driver, [
-      "settings-ui", "settings-main", "settings-basic-page",
-      "settings-autofill-page", "passwords-section", "#passwordToggle"
-  ])
+  selectors = [
+      "settings-main", "settings-basic-page", "settings-autofill-page",
+      "passwords-section", "#passwordToggle", "cr-toggle"
+  ]
 
-  if el.find_element_by_css_selector("cr-toggle").get_attribute("checked"):
+  el = driver.find_element_by_css_selector("settings-ui")
+  for selector in selectors:
+    el = getElementFromShadowRoot(driver, el, selector)
+
+  if el.get_attribute("checked"):
     print("TRUE")
   else:
     print("FALSE")
diff --git a/chrome/test/media_router/BUILD.gn b/chrome/test/media_router/BUILD.gn
index acca219..4f958ed 100644
--- a/chrome/test/media_router/BUILD.gn
+++ b/chrome/test/media_router/BUILD.gn
@@ -36,6 +36,7 @@
     "//chrome/common",
     "//chrome/test:test_support",
     "//chrome/test:test_support_ui",
+    "//components/global_media_controls",
     "//components/media_router/common",
     "//components/policy/core/browser",
     "//components/policy/core/common:test_support",
diff --git a/chrome/test/media_router/media_router_ui_for_test.cc b/chrome/test/media_router/media_router_ui_for_test.cc
index 2dfd051..703412f 100644
--- a/chrome/test/media_router/media_router_ui_for_test.cc
+++ b/chrome/test/media_router/media_router_ui_for_test.cc
@@ -11,11 +11,11 @@
 #include "chrome/browser/ui/media_router/media_router_file_dialog.h"
 #include "chrome/browser/ui/media_router/media_router_ui.h"
 #include "chrome/browser/ui/views/global_media_controls/media_dialog_view.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h"
+#include "chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h"
 #include "chrome/browser/ui/views/media_router/cast_dialog_sink_button.h"
 #include "chrome/browser/ui/views/media_router/cast_dialog_view.h"
 #include "chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h"
+#include "components/global_media_controls/public/views/media_item_ui_view.h"
 #include "components/media_router/browser/media_router_factory.h"
 #include "components/media_router/browser/media_routes_observer.h"
 #include "ui/events/base_event_utils.h"
@@ -398,11 +398,11 @@
 
 CastDialogSinkButton* MediaRouterUiForTest::GetSinkButtonFromGMCDialog(
     const std::string& sink_name) const {
-  auto notifications =
-      MediaDialogView::GetDialogViewForTesting()->GetNotificationsForTesting();
-  MediaNotificationContainerImplView* view = notifications.begin()->second;
-  auto sink_buttons =
-      view->device_selector_view_for_testing()->GetCastSinkButtonsForTesting();
+  auto items = MediaDialogView::GetDialogViewForTesting()->GetItemsForTesting();
+  global_media_controls::MediaItemUIView* view = items.begin()->second;
+  auto* device_selector = static_cast<MediaItemUIDeviceSelectorView*>(
+      view->device_selector_view_for_testing());
+  auto sink_buttons = device_selector->GetCastSinkButtonsForTesting();
   return GetSinkButtonWithName(sink_buttons, sink_name);
 }
 
diff --git a/chrome/updater/win/ui/owner_draw_controls.h b/chrome/updater/win/ui/owner_draw_controls.h
index b0f58b1..cd36992 100644
--- a/chrome/updater/win/ui/owner_draw_controls.h
+++ b/chrome/updater/win/ui/owner_draw_controls.h
@@ -12,9 +12,13 @@
 
 // These headers must be included after base/win/atl.h.
 #include "./atlapp.h"
+#include "./atltypes.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-but-set-variable"
 #include "./atlctrls.h"
 #include "./atlframe.h"
-#include "./atltypes.h"
+#pragma clang diagnostic pop
 
 namespace updater {
 namespace ui {
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 6da82220..5500cf9 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -1023,46 +1023,46 @@
         Internet Connectivity
       </message>
       <message name="IDS_DIAGNOSTICS_LAN_CONNECTIVITY_FAILED_TEXT" desc="The text that displays when the `Lan Connectivity` test fails.">
-        Unable to contact gateway
+        Can't contact gateway
       </message>
       <message name="IDS_DIAGNOSTICS_GATEWAY_CAN_BE_PINGED_FAILED_TEXT" desc="The text that displays when the `Gateway can be pinged` test fails.">
-        Unable to contact gateway
+        Can't contact gateway
       </message>
       <message name="IDS_DIAGNOSTICS_DNS_RESOLVER_PRESENT_FAILED_TEXT" desc="The text that displays when the `DNS resolver present` test fails.">
-        No DNS servers are configured
+        No DNS servers are set up
       </message>
       <message name="IDS_DIAGNOSTICS_DNS_RESOLUTION_FAILED_TEXT" desc="The text that displays when the `DNS resolution` test fails.">
-        Unable to resolve DNS
+        Can't resolve DNS
       </message>
       <message name="IDS_DIAGNOSTICS_DNS_LATENCY_FAILED_TEXT" desc="The text that displays when the `DNS latency` test fails.">
         DNS resolution has high latency
       </message>
       <message name="IDS_DIAGNOSTICS_SIGNAL_STRENGTH_FAILED_TEXT" desc="The text that displays when the `Signal strength` test fails.">
-        Low signal strength, try to move closer to the access point
+        Weak signal strength. Try moving closer to the Wi-Fi signal source.
       </message>
       <message name="IDS_DIAGNOSTICS_CAPTIVE_PORTAL_FAILED_TEXT" desc="The text that displays when the `Captive portal` test fails.">
         This network may have a captive portal
       </message>
       <message name="IDS_DIAGNOSTICS_HAS_SECURE_WIFI_CONNECTION_FAILED_TEXT" desc="The text that displays when the `Has secure Wi-Fi connection` test fails.">
-        You are using an open network
+        You are using an open and unsecure network
       </message>
       <message name="IDS_DIAGNOSTICS_HTTPS_FIREWALL_FAILED_TEXT" desc="The text that displays when the `HTTPS firewall` test fails.">
-        Unable to connect through firewall to HTTPS (port 443) websites
+        Can't connect through firewall to HTTPS websites
       </message>
       <message name="IDS_DIAGNOSTICS_HTTP_FIREWALL_FAILED_TEXT" desc="The text that displays when the `HTTP firewall` test fails.">
-        Unable to connect through firewall to HTTP (port 80) websites
+        Can't connect through firewall to HTTP websites
       </message>
       <message name="IDS_DIAGNOSTICS_HTTPS_LATENCY_FAILED_TEXT" desc="The text that displays when the `HTTPS latency` test fails.">
-        High latency to HTTPS (port 443) websites
+        High latency to HTTPS websites
       </message>
       <message name="IDS_DIAGNOSTICS_ARC_PING_FAILED_TEXT" desc="The text that displays when the `ARC ping` test fails.">
-        Unable to contact the gateway from Android
+        Can't contact the gateway from Android apps
       </message>
       <message name="IDS_DIAGNOSTICS_ARC_HTTP_FAILED_TEXT" desc="The text that displays when the `ARC HTTP` test fails.">
-        Unable to connect through firewall to HTTP (port 80) websites from Android
+        Can't connect through firewall to HTTP websites from Android apps
       </message>
       <message name="IDS_DIAGNOSTICS_ARC_DNS_RESOLUTION_FAILED_TEXT" desc="The text that displays when the `ARC DNS resolution` test fails.">
-        Unable to resolve DNS from Android
+        Can't resolve DNS from Android apps
       </message>
       <message name="IDS_NETWORK_DIAGNOSTICS_NO_IP_ADDRESS_TEXT" desc="The text shown when a network's IP Address is missing.">
         Unable to obtain IP address
@@ -1923,6 +1923,9 @@
       <message name="IDS_PERSONALIZATION_APP_EXIT_FULL_SCREEN" desc="Label for the button to exit full screen wallpaper viewing mode">
         Exit full screen
       </message>
+      <message name="IDS_PERSONALIZATION_APP_SET_AS_WALLPAPER" desc="Label for the button to confirm preview wallpaper">
+        Set as wallpaper
+      </message>
 
       <!-- Traffic Counters UI -->
       <message name="IDS_TRAFFIC_COUNTERS_UNKNOWN" desc="Traffic counters related to an unknown source">
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_DNS_RESOLUTION_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_DNS_RESOLUTION_FAILED_TEXT.png.sha1
index 8b9d67b..ac8b3a3 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_DNS_RESOLUTION_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_DNS_RESOLUTION_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-7bb65eaa8ea38ba6131d9d3980e06fa0bf4e0eb4
\ No newline at end of file
+1ada6db43c4aecf4aba3f65941f0b6fc5901f720
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_HTTP_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_HTTP_FAILED_TEXT.png.sha1
index 40ba73d..bd33642 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_HTTP_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_HTTP_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-2bbefb9d8b033575bb8a84982733383631bbbd57
\ No newline at end of file
+3b303bd73c9e382cea22ab08d1c185ef08e47d75
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_PING_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_PING_FAILED_TEXT.png.sha1
index 61e6d8b..731eda72 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_PING_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_ARC_PING_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-6c5019b0529347c90e9ace3649ad86bc3510b843
\ No newline at end of file
+884d0a78e4c2e20fb206934be7b307877b224551
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_DNS_RESOLUTION_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_DNS_RESOLUTION_FAILED_TEXT.png.sha1
index c4ef45a..83966c2 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_DNS_RESOLUTION_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_DNS_RESOLUTION_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-eb6ffd35cb8be983c9231b7e062b8b7139fad2ee
\ No newline at end of file
+9172ee5aa0bd6a0f5300042e3ab75e6e34d92b18
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_DNS_RESOLVER_PRESENT_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_DNS_RESOLVER_PRESENT_FAILED_TEXT.png.sha1
index cb8dfde..e698a109 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_DNS_RESOLVER_PRESENT_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_DNS_RESOLVER_PRESENT_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-79fdcc6c2315e3394c8d8b147ba6e82e77227c1b
\ No newline at end of file
+a2fa3d77bf1b6e08329b183b769cf9135eb8f7b2
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_GATEWAY_CAN_BE_PINGED_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_GATEWAY_CAN_BE_PINGED_FAILED_TEXT.png.sha1
index c0d4134..24d30a1 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_GATEWAY_CAN_BE_PINGED_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_GATEWAY_CAN_BE_PINGED_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-f4fef44d10afa66d7214d9a286ddcd72c15e13eb
\ No newline at end of file
+d9985f2073af286e4ba1b451c682ada6a70dfd21
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HAS_SECURE_WIFI_CONNECTION_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HAS_SECURE_WIFI_CONNECTION_FAILED_TEXT.png.sha1
index 2600092..110d5a5 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HAS_SECURE_WIFI_CONNECTION_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HAS_SECURE_WIFI_CONNECTION_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-55c3da60ed0cb81633c84ee1f6a0047364a6093d
\ No newline at end of file
+3fdc8a66d978f69072b870bc44df20c8ce68051c
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTPS_FIREWALL_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTPS_FIREWALL_FAILED_TEXT.png.sha1
index 9fe9526..49f5ad7 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTPS_FIREWALL_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTPS_FIREWALL_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-47532716b423ffec66dee80be5205957d0d403a5
\ No newline at end of file
+8b6a850c70374699c92bda21faaad5677b641927
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTPS_LATENCY_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTPS_LATENCY_FAILED_TEXT.png.sha1
index ce378b02..53f13e5a 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTPS_LATENCY_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTPS_LATENCY_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-232487eb4c41b74daab53fde68d7f7b25c2c0b96
\ No newline at end of file
+1124ef4e42d694cb0387c35c35f8c68637e7607c
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTP_FIREWALL_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTP_FIREWALL_FAILED_TEXT.png.sha1
index a858c14..75e1a4d 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTP_FIREWALL_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_HTTP_FIREWALL_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-42611eb90005186fd943a8da6e8be4784c863397
\ No newline at end of file
+002b0142bf4bd29d7ecefd23b7ad94c83331955a
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_LAN_CONNECTIVITY_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_LAN_CONNECTIVITY_FAILED_TEXT.png.sha1
index c0d4134..572615ee 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_LAN_CONNECTIVITY_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_LAN_CONNECTIVITY_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-f4fef44d10afa66d7214d9a286ddcd72c15e13eb
\ No newline at end of file
+837f0728b7ceb3202a96238bb770cbcfae6550ba
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_SIGNAL_STRENGTH_FAILED_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_SIGNAL_STRENGTH_FAILED_TEXT.png.sha1
index 3ef3dac..086a44c4 100644
--- a/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_SIGNAL_STRENGTH_FAILED_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_DIAGNOSTICS_SIGNAL_STRENGTH_FAILED_TEXT.png.sha1
@@ -1 +1 @@
-cd9e258baa0f8860254f0afedbc322604108f412
\ No newline at end of file
+f387549e63e6a1e9801fa51933a42aebf4da878e
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_SET_AS_WALLPAPER.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_SET_AS_WALLPAPER.png.sha1
new file mode 100644
index 0000000..ab1c389
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_SET_AS_WALLPAPER.png.sha1
@@ -0,0 +1 @@
+ffdbe04ef88d7a80b2048700d752077c1af387e8
\ No newline at end of file
diff --git a/chromeos/components/personalization_app/mojom/personalization_app.mojom b/chromeos/components/personalization_app/mojom/personalization_app.mojom
index 6820688..d704975 100644
--- a/chromeos/components/personalization_app/mojom/personalization_app.mojom
+++ b/chromeos/components/personalization_app/mojom/personalization_app.mojom
@@ -121,11 +121,13 @@
     SetWallpaperObserver(pending_remote<WallpaperObserver> observer);
 
     // Sets the given backdrop wallpaper as the user's background.
-    SelectWallpaper(uint64 image_asset_id) => (bool success);
+    SelectWallpaper(uint64 image_asset_id, bool preview_mode) =>
+        (bool success);
 
     // Sets the given local wallpaper as the user's background. Validated
     // against list of file paths returned by |GetLocalImages|.
-    SelectLocalImage(mojo_base.mojom.FilePath path) => (bool success);
+    SelectLocalImage(mojo_base.mojom.FilePath path, bool preview_mode) =>
+        (bool success);
 
     // Sets the custom layout for current wallpaper
     SetCustomWallpaperLayout(WallpaperLayout layout);
@@ -139,4 +141,15 @@
 
     // Refresh the wallpaper. Noop if daily refresh is not enabled.
     UpdateDailyRefreshWallpaper() => (bool success);
+
+    // Indicates whether the device is in tablet mode.
+    IsInTabletMode() => (bool tablet_mode);
+
+    // Confirms the wallpaper being previewed to be set as the actual user
+    // wallpaper. Must be called in preview mode.
+    ConfirmPreviewWallpaper();
+
+    // Cancels the wallpaper preview and reverts to the user wallpaper. Must be
+    // called in preview mode.
+    CancelPreviewWallpaper();
 };
diff --git a/chromeos/components/personalization_app/personalization_app_ui.cc b/chromeos/components/personalization_app/personalization_app_ui.cc
index 0fa8936e..d8e96225 100644
--- a/chromeos/components/personalization_app/personalization_app_ui.cc
+++ b/chromeos/components/personalization_app/personalization_app_ui.cc
@@ -81,7 +81,8 @@
       {"dismiss", IDS_PERSONALIZATION_APP_DISMISS},
       {"ariaLabelViewFullScreen",
        IDS_PERSONALIZATION_APP_ARIA_LABEL_VIEW_FULL_SCREEN},
-      {"exitFullscreen", IDS_PERSONALIZATION_APP_EXIT_FULL_SCREEN}};
+      {"exitFullscreen", IDS_PERSONALIZATION_APP_EXIT_FULL_SCREEN},
+      {"setAsWallpaper", IDS_PERSONALIZATION_APP_SET_AS_WALLPAPER}};
   source->AddLocalizedStrings(kLocalizedStrings);
   source->UseStringsJs();
 }
diff --git a/chromeos/components/personalization_app/resources/trusted/personalization_controller.js b/chromeos/components/personalization_app/resources/trusted/personalization_controller.js
index 62b014a..d0a5cd9 100644
--- a/chromeos/components/personalization_app/resources/trusted/personalization_controller.js
+++ b/chromeos/components/personalization_app/resources/trusted/personalization_controller.js
@@ -4,7 +4,7 @@
 
 import {assert} from 'chrome://resources/js/assert.m.js'
 import {isNonEmptyArray} from '../common/utils.js';
-import {beginLoadImagesForCollectionsAction, beginLoadLocalImageDataAction, beginLoadLocalImagesAction, beginLoadSelectedImageAction, beginSelectImageAction, beginUpdateDailyRefreshImageAction, endSelectImageAction, setCollectionsAction, setDailyRefreshCollectionIdAction, setImagesForCollectionAction, setLocalImageDataAction, setLocalImagesAction, setSelectedImageAction, setUpdatedDailyRefreshImageAction} from './personalization_actions.js';
+import * as action from './personalization_actions.js';
 import {WallpaperLayout, WallpaperType} from './personalization_reducers.js';
 import {PersonalizationStore} from './personalization_store.js';
 
@@ -27,7 +27,7 @@
     console.warn('Failed to fetch wallpaper collections');
     collections = null;
   }
-  store.dispatch(setCollectionsAction(collections));
+  store.dispatch(action.setCollectionsAction(collections));
 }
 
 /**
@@ -43,14 +43,14 @@
         'Cannot fetch data for collections when it is not initialized');
     return;
   }
-  store.dispatch(beginLoadImagesForCollectionsAction(collections));
+  store.dispatch(action.beginLoadImagesForCollectionsAction(collections));
   for (const {id} of /** @type {!Array<{id: string}>} */ (collections)) {
     let {images} = await provider.fetchImagesForCollection(id);
     if (!isNonEmptyArray(images)) {
       console.warn('Failed to fetch images for collection id', id);
       images = null;
     }
-    store.dispatch(setImagesForCollectionAction(id, images));
+    store.dispatch(action.setImagesForCollectionAction(id, images));
   }
 }
 
@@ -61,12 +61,12 @@
  * @param {!PersonalizationStore} store
  */
 async function getLocalImages(provider, store) {
-  store.dispatch(beginLoadLocalImagesAction());
+  store.dispatch(action.beginLoadLocalImagesAction());
   const {images} = await provider.getLocalImages();
   if (images == null) {
     console.warn('Failed to fetch local images');
   }
-  store.dispatch(setLocalImagesAction(images));
+  store.dispatch(action.setLocalImagesAction(images));
 }
 
 /**
@@ -101,7 +101,7 @@
       continue;
     }
     imageThumbnailsToFetch.add(filePath.path);
-    store.dispatch(beginLoadLocalImageDataAction(filePath));
+    store.dispatch(action.beginLoadLocalImageDataAction(filePath));
   }
   store.endBatchUpdate();
 
@@ -113,7 +113,7 @@
     if (!data) {
       console.warn('Failed to fetch local image data', path);
     }
-    store.dispatch(setLocalImageDataAction({path}, data));
+    store.dispatch(action.setLocalImageDataAction({path}, data));
   }
 }
 
@@ -128,30 +128,33 @@
   // Batch these changes together to reduce polymer churn as multiple state
   // fields change quickly.
   store.beginBatchUpdate();
-  store.dispatch(beginSelectImageAction(image));
-  store.dispatch(beginLoadSelectedImageAction());
+  store.dispatch(action.beginSelectImageAction(image));
+  store.dispatch(action.beginLoadSelectedImageAction());
+  const {tabletMode} = await provider.isInTabletMode();
+  if (tabletMode) {
+    store.dispatch(action.setFullscreenEnabledAction(/*enabled=*/ true))
+  }
   store.endBatchUpdate();
   const {success} = await (() => {
     if (image.assetId) {
-      return provider.selectWallpaper(image.assetId);
+      return provider.selectWallpaper(
+          image.assetId, /*preview_mode=*/ tabletMode);
     } else if (image.path) {
       return provider.selectLocalImage(
-          /** @type {!mojoBase.mojom.FilePath} */ (image));
+          /** @type {!mojoBase.mojom.FilePath} */ (image),
+          /*preview_mode=*/ tabletMode);
     } else {
       console.warn('Image must be a local image or a WallpaperImage');
       return {success: false};
     }
   })();
   store.beginBatchUpdate();
-  store.dispatch(endSelectImageAction(image, success));
+  store.dispatch(action.endSelectImageAction(image, success));
   if (!success) {
     console.warn('Error setting wallpaper');
-    store.dispatch(setSelectedImageAction(store.data.currentSelected));
+    store.dispatch(action.setSelectedImageAction(store.data.currentSelected));
   }
   store.endBatchUpdate();
-
-  // Cleared Daily Refresh state should be reflected in UI.
-  getDailyRefreshCollectionId(provider, store);
 }
 
 /**
@@ -170,7 +173,7 @@
   if (image.layout === layout)
     return;
 
-  store.dispatch(beginLoadSelectedImageAction());
+  store.dispatch(action.beginLoadSelectedImageAction());
   await provider.setCustomWallpaperLayout(layout);
 }
 
@@ -196,7 +199,7 @@
  */
 export async function getDailyRefreshCollectionId(provider, store) {
   const {collectionId} = await provider.getDailyRefreshCollectionId();
-  store.dispatch(setDailyRefreshCollectionIdAction(collectionId));
+  store.dispatch(action.setDailyRefreshCollectionIdAction(collectionId));
 }
 
 /**
@@ -206,15 +209,33 @@
  * @param {!PersonalizationStore} store
  */
 export async function updateDailyRefreshWallpaper(provider, store) {
-  store.dispatch(beginUpdateDailyRefreshImageAction());
-  store.dispatch(beginLoadSelectedImageAction());
+  store.dispatch(action.beginUpdateDailyRefreshImageAction());
+  store.dispatch(action.beginLoadSelectedImageAction());
   const {success} = await provider.updateDailyRefreshWallpaper();
   if (success) {
-    store.dispatch(setUpdatedDailyRefreshImageAction());
+    store.dispatch(action.setUpdatedDailyRefreshImageAction());
   }
 }
 
 /**
+ * Confirm and set preview wallpaper as actual wallpaper.
+ * @param {!chromeos.personalizationApp.mojom.WallpaperProviderInterface}
+ *     provider
+ */
+export async function confirmPreviewWallpaper(provider) {
+  await provider.confirmPreviewWallpaper();
+}
+
+/**
+ * Cancel preview wallpaper and show the previous wallpaper.
+ * @param {!chromeos.personalizationApp.mojom.WallpaperProviderInterface}
+ *     provider
+ */
+export async function cancelPreviewWallpaper(provider) {
+  await provider.cancelPreviewWallpaper();
+}
+
+/**
  * Fetches list of collections, then fetches list of images for each collection.
  * @param {!chromeos.personalizationApp.mojom.WallpaperProviderInterface}
  *     provider
diff --git a/chromeos/components/personalization_app/resources/trusted/personalization_reducers.js b/chromeos/components/personalization_app/resources/trusted/personalization_reducers.js
index eef5870..9519352 100644
--- a/chromeos/components/personalization_app/resources/trusted/personalization_reducers.js
+++ b/chromeos/components/personalization_app/resources/trusted/personalization_reducers.js
@@ -342,6 +342,12 @@
         return null;
       }
       return state;
+    case ActionName.SET_FULLSCREEN_ENABLED:
+      if (!(/** @type {{enabled: boolean}} */ (action)).enabled) {
+        // Clear the pending selected state after full screen is dismissed.
+        return null;
+      }
+      return state;
     default:
       return state;
   }
diff --git a/chromeos/components/personalization_app/resources/trusted/wallpaper_fullscreen_element.html b/chromeos/components/personalization_app/resources/trusted/wallpaper_fullscreen_element.html
index 89e30c0..91d5bba 100644
--- a/chromeos/components/personalization_app/resources/trusted/wallpaper_fullscreen_element.html
+++ b/chromeos/components/personalization_app/resources/trusted/wallpaper_fullscreen_element.html
@@ -8,6 +8,10 @@
 </style>
 <div id="container" hidden>
   <button on-click="onClickExit_" id="exit">[[i18n('exitFullscreen')]]</button>
+  <template is="dom-if" if="[[showConfirm_]]">
+    <button on-click="onClickConfirm_" id="confirm">[[i18n('setAsWallpaper')]]
+    </button>
+  </template>
   <template is="dom-if" if="[[showLayoutOptions_]]">
     <div id="layoutButtons">
       <button on-click="onClickLayout_"
diff --git a/chromeos/components/personalization_app/resources/trusted/wallpaper_fullscreen_element.js b/chromeos/components/personalization_app/resources/trusted/wallpaper_fullscreen_element.js
index a12f396..96bce2e2 100644
--- a/chromeos/components/personalization_app/resources/trusted/wallpaper_fullscreen_element.js
+++ b/chromeos/components/personalization_app/resources/trusted/wallpaper_fullscreen_element.js
@@ -12,7 +12,7 @@
 import {getWallpaperLayoutEnum} from '../common/utils.js';
 import {getWallpaperProvider} from './mojo_interface_provider.js';
 import {setFullscreenEnabledAction} from './personalization_actions.js';
-import {setCustomWallpaperLayout} from './personalization_controller.js';
+import {cancelPreviewWallpaper, confirmPreviewWallpaper, setCustomWallpaperLayout} from './personalization_controller.js';
 import {updateDailyRefreshWallpaper} from './personalization_controller.js';
 import {WithPersonalizationStore} from './personalization_store.js';
 
@@ -78,7 +78,8 @@
         'showDailyRefresh_',
         state => state.currentSelected?.type ===
                 chromeos.personalizationApp.mojom.WallpaperType.kDaily &&
-            state.dailyRefresh.collectionId);
+            state.dailyRefresh.collectionId && !state.pendingSelected);
+    this.watch('showConfirm_', state => !!state.pendingSelected);
     this.watch('image_', state => state.currentSelected);
   }
 
@@ -116,12 +117,25 @@
     const hidden = !this.getFullscreenElement();
     this.shadowRoot.getElementById('container').hidden = hidden;
     if (hidden) {
+      // SWA also supports exiting fullscreen when users press ESC. In this
+      // case, the preview mode may be still on so we have to call cancel
+      // preview.
+      // This call is no-op when the user clicks on exit button or set as
+      // wallpaper button.
+      cancelPreviewWallpaper(this.wallpaperProvider_);
       this.dispatch(setFullscreenEnabledAction(/*enabled=*/false));
     }
   }
 
   /** @private */
   onClickExit_() {
+    cancelPreviewWallpaper(this.wallpaperProvider_);
+    this.exitFullscreen();
+  }
+
+  /** @private */
+  onClickConfirm_() {
+    confirmPreviewWallpaper(this.wallpaperProvider_);
     this.exitFullscreen();
   }
 
diff --git a/chromeos/components/personalization_app/resources/trusted/wallpaper_selected_element.js b/chromeos/components/personalization_app/resources/trusted/wallpaper_selected_element.js
index a9332ab..86fe6b0 100644
--- a/chromeos/components/personalization_app/resources/trusted/wallpaper_selected_element.js
+++ b/chromeos/components/personalization_app/resources/trusted/wallpaper_selected_element.js
@@ -263,6 +263,9 @@
       this.initialLoadTimeout_ = null;
     }
     this.dispatch(setSelectedImageAction(currentWallpaper));
+
+    // Daily Refresh state should also get updated when wallpaper changes.
+    getDailyRefreshCollectionId(this.wallpaperProvider_, this.getStore());
   }
 
   /**
diff --git a/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.cc b/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.cc
index dd1dde3..eef3783b 100644
--- a/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.cc
+++ b/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.cc
@@ -75,12 +75,14 @@
 
 void FakePersonalizationAppUiDelegate::SelectWallpaper(
     uint64_t image_asset_id,
+    bool preview_mode,
     SelectWallpaperCallback callback) {
   std::move(callback).Run(/*success=*/true);
 }
 
 void FakePersonalizationAppUiDelegate::SelectLocalImage(
     const base::FilePath& path,
+    bool preview_mode,
     SelectLocalImageCallback callback) {
   std::move(callback).Run(/*success=*/true);
 }
@@ -104,3 +106,16 @@
     UpdateDailyRefreshWallpaperCallback callback) {
   std::move(callback).Run(/*success=*/true);
 }
+
+void FakePersonalizationAppUiDelegate::IsInTabletMode(
+    IsInTabletModeCallback callback) {
+  std::move(callback).Run(/*tablet_mode=*/false);
+}
+
+void FakePersonalizationAppUiDelegate::ConfirmPreviewWallpaper() {
+  return;
+}
+
+void FakePersonalizationAppUiDelegate::CancelPreviewWallpaper() {
+  return;
+}
diff --git a/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.h b/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.h
index fcaadc7..c88eafe 100644
--- a/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.h
+++ b/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.h
@@ -51,9 +51,11 @@
       override;
 
   void SelectWallpaper(uint64_t image_asset_id,
+                       bool preview_mode,
                        SelectWallpaperCallback callback) override;
 
   void SelectLocalImage(const base::FilePath& path,
+                        bool preview_mode,
                         SelectLocalImageCallback callback) override;
 
   void SetCustomWallpaperLayout(ash::WallpaperLayout layout) override;
@@ -66,6 +68,12 @@
   void UpdateDailyRefreshWallpaper(
       UpdateDailyRefreshWallpaperCallback callback) override;
 
+  void IsInTabletMode(IsInTabletModeCallback callback) override;
+
+  void ConfirmPreviewWallpaper() override;
+
+  void CancelPreviewWallpaper() override;
+
  private:
   mojo::Receiver<chromeos::personalization_app::mojom::WallpaperProvider>
       wallpaper_receiver_{this};
diff --git a/chromeos/components/projector_app/BUILD.gn b/chromeos/components/projector_app/BUILD.gn
index ca4202f..f41727d 100644
--- a/chromeos/components/projector_app/BUILD.gn
+++ b/chromeos/components/projector_app/BUILD.gn
@@ -18,6 +18,8 @@
     "projector_message_handler.h",
     "projector_oauth_token_fetcher.cc",
     "projector_oauth_token_fetcher.h",
+    "projector_xhr_sender.cc",
+    "projector_xhr_sender.h",
     "trusted_projector_ui.cc",
     "trusted_projector_ui.h",
     "untrusted_projector_ui_config.cc",
@@ -52,6 +54,7 @@
     "//base/test:test_support",
     "//components/signin/public/identity_manager",
     "//components/signin/public/identity_manager:test_support",
+    "//services/network:test_support",
     "//testing/gmock",
   ]
 }
@@ -62,6 +65,7 @@
     "test/annotator_message_handler_unittest.cc",
     "test/projector_message_handler_unittest.cc",
     "test/projector_oauth_token_fetcher_unittest.cc",
+    "test/projector_xhr_sender_unittest.cc",
   ]
 
   deps = [
diff --git a/chromeos/components/projector_app/DEPS b/chromeos/components/projector_app/DEPS
index 3c09912c..8bc6a58 100644
--- a/chromeos/components/projector_app/DEPS
+++ b/chromeos/components/projector_app/DEPS
@@ -6,6 +6,8 @@
   "+content/public/browser",
   "+content/public/common",
   "+content/public/test",
+  "+services/network/public",
+  "+services/network/test",
   "+third_party/skia",
   "+ui/resources",
   "+ui/webui",
diff --git a/chromeos/components/projector_app/projector_app_client.h b/chromeos/components/projector_app/projector_app_client.h
index 50e6fbfb..d011c0ef 100644
--- a/chromeos/components/projector_app/projector_app_client.h
+++ b/chromeos/components/projector_app/projector_app_client.h
@@ -7,6 +7,12 @@
 
 #include "base/observer_list_types.h"
 
+namespace network {
+namespace mojom {
+class URLLoaderFactory;
+}  // namespace mojom
+}  // namespace network
+
 namespace signin {
 class IdentityManager;
 }  // namespace signin
@@ -35,6 +41,9 @@
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
 
+  // Returns the URLLoaderFactory for the primary user profile.
+  virtual network::mojom::URLLoaderFactory* GetUrlLoaderFactory() = 0;
+
  protected:
   ProjectorAppClient();
   virtual ~ProjectorAppClient();
diff --git a/chromeos/components/projector_app/projector_xhr_sender.cc b/chromeos/components/projector_app/projector_xhr_sender.cc
new file mode 100644
index 0000000..5be4617
--- /dev/null
+++ b/chromeos/components/projector_app/projector_xhr_sender.cc
@@ -0,0 +1,167 @@
+// 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 "chromeos/components/projector_app/projector_xhr_sender.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/strings/string_util.h"
+#include "chromeos/components/projector_app/projector_app_client.h"
+#include "components/signin/public/identity_manager/access_token_info.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "net/base/url_util.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+
+namespace {
+// Projector network traffic annotation tags.
+constexpr net::NetworkTrafficAnnotationTag kNetworkTrafficAnnotationTag =
+    net::DefineNetworkTrafficAnnotation("projector_xhr_loader", R"(
+          semantics: {
+            sender: "ChromeOS Projector"
+            description: "ChromeOS send Projector XHR requests"
+            destination: GOOGLE_OWNED_SERVICE
+          }
+          policy: {
+            cookies_allowed: YES
+          })");
+
+constexpr char kAuthorizationHeaderPrefix[] = "Bearer ";
+
+// List of URL prefix supported by `ProjectorXhrSender`.
+const char* kUrlAllowlist[] = {
+    "https://www.googleapis.com/drive/v3/files/",
+    "https://drive.google.com/get_video_info",
+    "https://translation.googleapis.com/language/translate/v2"};
+
+// Return true if the url matches the allowed URL prefix.
+bool IsUrlAllowlisted(const std::string& url) {
+  for (auto* urlPrefix : kUrlAllowlist) {
+    if (base::StartsWith(url, urlPrefix, base::CompareCase::SENSITIVE))
+      return true;
+  }
+  return false;
+}
+}  // namespace
+
+ProjectorXhrSender::ProjectorXhrSender(
+    network::mojom::URLLoaderFactory* url_loader_factory)
+    : url_loader_factory_(url_loader_factory) {}
+ProjectorXhrSender::~ProjectorXhrSender() = default;
+
+void ProjectorXhrSender::Send(const GURL& url,
+                              const std::string& method,
+                              const std::string& request_body,
+                              bool use_credentials,
+                              SendRequestCallback callback) {
+  if (!IsUrlAllowlisted(url.spec())) {
+    std::move(callback).Run(
+        /*success=*/false,
+        /*response_body=*/std::string(),
+        /*error=*/"UNSUPPORTED_URL");
+    return;
+  }
+
+  if (use_credentials) {
+    // Use end user credentials to authorize the request. Doesn't need to fetch
+    // OAuth token.
+    SendRequest(url, method, request_body, /*token=*/std::string(),
+                std::move(callback));
+    return;
+  }
+
+  // Fetch OAuth token for authorizing the request.
+  // TODO(b/197366265): add support for secondary account.
+  auto primary_account =
+      chromeos::ProjectorAppClient::Get()
+          ->GetIdentityManager()
+          ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
+  oauth_token_fetcher_.GetAccessTokenFor(
+      primary_account.email,
+      base::BindOnce(&ProjectorXhrSender::OnAccessTokenRequestCompleted,
+                     weak_factory_.GetWeakPtr(), url, method, request_body,
+                     std::move(callback)));
+}
+
+void ProjectorXhrSender::OnAccessTokenRequestCompleted(
+    const GURL& url,
+    const std::string& method,
+    const std::string& request_body,
+    SendRequestCallback callback,
+    const std::string& email,
+    GoogleServiceAuthError error,
+    const signin::AccessTokenInfo& info) {
+  if (error.state() != GoogleServiceAuthError::State::NONE) {
+    std::move(callback).Run(
+        /*success=*/false,
+        /*response_body=*/std::string(),
+        /*error=*/"TOKEN_FETCH_FAILURE");
+    return;
+  }
+
+  SendRequest(url, method, request_body, info.token, std::move(callback));
+}
+
+void ProjectorXhrSender::SendRequest(const GURL& url,
+                                     const std::string& method,
+                                     const std::string& request_body,
+                                     const std::string& token,
+                                     SendRequestCallback callback) {
+  // Build resource request.
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = url;
+  resource_request->method = method;
+  // The OAuth token will be empty if the request is using end user credentials
+  // for authorization.
+  if (!token.empty()) {
+    resource_request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
+                                        kAuthorizationHeaderPrefix + token);
+  }
+  resource_request->headers.SetHeader(net::HttpRequestHeaders::kAccept,
+                                      "application/json");
+
+  // Send resource request.
+  auto loader = network::SimpleURLLoader::Create(std::move(resource_request),
+                                                 kNetworkTrafficAnnotationTag);
+
+  if (!request_body.empty())
+    loader->AttachStringForUpload(request_body, "application/json");
+
+  loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+      url_loader_factory_,
+      base::BindOnce(&ProjectorXhrSender::OnSimpleURLLoaderComplete,
+                     weak_factory_.GetWeakPtr(), next_request_id_,
+                     std::move(callback)));
+
+  loader_map_.emplace(next_request_id_++, std::move(loader));
+}
+
+void ProjectorXhrSender::OnSimpleURLLoaderComplete(
+    int request_id,
+    SendRequestCallback callback,
+    std::unique_ptr<std::string> response_body) {
+  auto& loader = loader_map_[request_id];
+  if (!response_body || loader->NetError() != net::OK ||
+      !loader->ResponseInfo() || !loader->ResponseInfo()->headers) {
+    std::move(callback).Run(
+        /*success=*/false,
+        /*response_body=*/std::string(),
+        /*error=*/"XHR_FETCH_FAILURE");
+  } else {
+    std::move(callback).Run(
+        /*success=*/true,
+        /*response_body=*/*response_body,
+        /*error=*/std::string());
+  }
+
+  loader_map_.erase(request_id);
+}
+
+}  // namespace chromeos
diff --git a/chromeos/components/projector_app/projector_xhr_sender.h b/chromeos/components/projector_app/projector_xhr_sender.h
new file mode 100644
index 0000000..88f427cf
--- /dev/null
+++ b/chromeos/components/projector_app/projector_xhr_sender.h
@@ -0,0 +1,90 @@
+// 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 CHROMEOS_COMPONENTS_PROJECTOR_APP_PROJECTOR_XHR_SENDER_H_
+#define CHROMEOS_COMPONENTS_PROJECTOR_APP_PROJECTOR_XHR_SENDER_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "chromeos/components/projector_app/projector_oauth_token_fetcher.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+
+namespace base {
+class GURL;
+}  // namespace base
+
+namespace network {
+class SimpleURLLoader;
+
+namespace mojom {
+class URLLoaderFactory;
+}  // namespace mojom
+}  // namespace network
+
+namespace chromeos {
+
+/**
+ * Projector XHR sender. Used by Projector App to send XHR requests.
+ */
+class ProjectorXhrSender {
+ public:
+  // Callback triggered when a XHR request is completed. `response_body`
+  // contains the response text if success, empty otherwise. `error` contains
+  // error message if not success, empty otherwise.
+  using SendRequestCallback =
+      base::OnceCallback<void(bool success,
+                              const std::string& response_body,
+                              const std::string& error)>;
+
+  explicit ProjectorXhrSender(
+      network::mojom::URLLoaderFactory* url_loader_factory);
+  ProjectorXhrSender(const ProjectorXhrSender&) = delete;
+  ProjectorXhrSender& operator=(const ProjectorXhrSender&) = delete;
+  ~ProjectorXhrSender();
+
+  // Send XHR request and trigger the callback when complete.
+  void Send(const GURL& url,
+            const std::string& method,
+            const std::string& request_body,
+            bool use_credentials,
+            SendRequestCallback callback);
+
+ private:
+  // Triggered when an OAuth token fetch completed.
+  void OnAccessTokenRequestCompleted(const GURL& url,
+                                     const std::string& method,
+                                     const std::string& request_body,
+                                     SendRequestCallback callback,
+                                     const std::string& email,
+                                     GoogleServiceAuthError error,
+                                     const signin::AccessTokenInfo& info);
+
+  void SendRequest(const GURL& url,
+                   const std::string& method,
+                   const std::string& request_body,
+                   const std::string& token,
+                   SendRequestCallback callback);
+
+  // Triggered when an XHR request completed.
+  void OnSimpleURLLoaderComplete(int request_id,
+                                 SendRequestCallback callback,
+                                 std::unique_ptr<std::string> response_body);
+
+  ProjectorOAuthTokenFetcher oauth_token_fetcher_;
+  network::mojom::URLLoaderFactory* url_loader_factory_ = nullptr;
+
+  // Next request ID.
+  int next_request_id_ = 0;
+  // The map to hold the SimpleURLLoader for each request. The key is a unique
+  // request ID associated with each request.
+  std::map<int, std::unique_ptr<network::SimpleURLLoader>> loader_map_;
+
+  base::WeakPtrFactory<ProjectorXhrSender> weak_factory_{this};
+};
+
+}  // namespace chromeos
+#endif  // CHROMEOS_COMPONENTS_PROJECTOR_APP_PROJECTOR_XHR_SENDER_H_
diff --git a/chromeos/components/projector_app/test/mock_app_client.cc b/chromeos/components/projector_app/test/mock_app_client.cc
index b51fed3..45b0097 100644
--- a/chromeos/components/projector_app/test/mock_app_client.cc
+++ b/chromeos/components/projector_app/test/mock_app_client.cc
@@ -26,6 +26,10 @@
   return identity_test_environment_.identity_manager();
 }
 
+network::mojom::URLLoaderFactory* MockAppClient::GetUrlLoaderFactory() {
+  return &test_url_loader_factory_;
+}
+
 void MockAppClient::SetAutomaticIssueOfAccessTokens(bool success) {
   identity_test_environment_.SetAutomaticIssueOfAccessTokens(success);
 }
diff --git a/chromeos/components/projector_app/test/mock_app_client.h b/chromeos/components/projector_app/test/mock_app_client.h
index 78f1209..c514ca0 100644
--- a/chromeos/components/projector_app/test/mock_app_client.h
+++ b/chromeos/components/projector_app/test/mock_app_client.h
@@ -10,8 +10,15 @@
 #include "base/time/time.h"
 #include "chromeos/components/projector_app/projector_app_client.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "services/network/test/test_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
+namespace network {
+namespace mojom {
+class URLLoaderFactory;
+}  // namespace mojom
+}  // namespace network
+
 namespace signin {
 class IdentityManager;
 }  // namespace signin
@@ -25,8 +32,13 @@
   MockAppClient& operator=(const MockAppClient&) = delete;
   ~MockAppClient() override;
 
+  network::TestURLLoaderFactory& test_url_loader_factory() {
+    return test_url_loader_factory_;
+  }
+
   // ProjectorAppClient:
   signin::IdentityManager* GetIdentityManager() override;
+  network::mojom::URLLoaderFactory* GetUrlLoaderFactory() override;
   MOCK_METHOD1(AddObserver, void(Observer*));
   MOCK_METHOD1(RemoveObserver, void(Observer*));
 
@@ -38,6 +50,7 @@
 
  private:
   signin::IdentityTestEnvironment identity_test_environment_;
+  network::TestURLLoaderFactory test_url_loader_factory_;
 };
 
 }  // namespace chromeos
diff --git a/chromeos/components/projector_app/test/projector_xhr_sender_unittest.cc b/chromeos/components/projector_app/test/projector_xhr_sender_unittest.cc
new file mode 100644
index 0000000..7040ec86
--- /dev/null
+++ b/chromeos/components/projector_app/test/projector_xhr_sender_unittest.cc
@@ -0,0 +1,190 @@
+// 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 "chromeos/components/projector_app/projector_xhr_sender.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "chromeos/components/projector_app/test/mock_app_client.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+const char kTestUserEmail[] = "testuser1@gmail.com";
+const base::TimeDelta kExpiryTimeFromNow = base::Minutes(10);
+constexpr char kValidUrl[] = "https://www.googleapis.com/drive/v3/files/fileID";
+constexpr char kValidUrl2[] =
+    "https://translation.googleapis.com/language/translate/v2";
+
+}  // namespace
+
+namespace chromeos {
+
+class ProjectorXhrSenderTest : public testing::Test {
+ public:
+  ProjectorXhrSenderTest() = default;
+  ProjectorXhrSenderTest(const ProjectorXhrSenderTest&) = delete;
+  ProjectorXhrSenderTest& operator=(const ProjectorXhrSenderTest&) = delete;
+  ~ProjectorXhrSenderTest() override = default;
+
+  // testing::Test:
+  void SetUp() override {
+    sender_ = std::make_unique<ProjectorXhrSender>(
+        mock_app_client_.GetUrlLoaderFactory());
+  }
+
+  ProjectorXhrSender* sender() { return sender_.get(); }
+  MockAppClient& mock_app_client() { return mock_app_client_; }
+
+ private:
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<ProjectorXhrSender> sender_;
+  MockAppClient mock_app_client_;
+};
+
+TEST_F(ProjectorXhrSenderTest, Success) {
+  base::RunLoop run_loop;
+
+  const std::string& test_response_body = "{}";
+  sender()->Send(
+      GURL(kValidUrl), "GET", /*request_body=*/"", /*useCredential=*/false,
+      base::BindOnce(
+          [](const std::string& expected_response_body,
+             base::RepeatingClosure quit_closure, bool success,
+             const std::string& response_body, const std::string& error) {
+            EXPECT_TRUE(success);
+            EXPECT_EQ(expected_response_body, response_body);
+            EXPECT_EQ("", error);
+            quit_closure.Run();
+          },
+          test_response_body, run_loop.QuitClosure()));
+
+  mock_app_client().test_url_loader_factory().AddResponse(kValidUrl,
+                                                          test_response_body);
+
+  mock_app_client().GrantOAuthTokenFor(
+      kTestUserEmail,
+      /* expiry_time = */ base::Time::Now() + kExpiryTimeFromNow);
+  run_loop.Run();
+}
+
+TEST_F(ProjectorXhrSenderTest, TwoRequests) {
+  base::RunLoop run_loop;
+  const std::string& test_response_body = "{}";
+  sender()->Send(
+      GURL(kValidUrl), "GET", /*request_body=*/"", /*useCredential=*/false,
+      base::BindOnce(
+          [](const std::string& expected_response_body,
+             base::RepeatingClosure quit_closure, bool success,
+             const std::string& response_body, const std::string& error) {
+            EXPECT_TRUE(success);
+            EXPECT_EQ(expected_response_body, response_body);
+            EXPECT_EQ("", error);
+            quit_closure.Run();
+          },
+          test_response_body, run_loop.QuitClosure()));
+
+  base::RunLoop run_loop2;
+  const std::string& test_response_body2 = "{data: {}}";
+  sender()->Send(
+      GURL(kValidUrl2), "GET", /*request_body=*/"", /*useCredential=*/false,
+      base::BindOnce(
+          [](const std::string& expected_response_body,
+             base::RepeatingClosure quit_closure, bool success,
+             const std::string& response_body, const std::string& error) {
+            EXPECT_TRUE(success);
+            EXPECT_EQ(expected_response_body, response_body);
+            EXPECT_EQ("", error);
+            quit_closure.Run();
+          },
+          test_response_body2, run_loop2.QuitClosure()));
+
+  mock_app_client().test_url_loader_factory().AddResponse(kValidUrl,
+                                                          test_response_body);
+
+  mock_app_client().test_url_loader_factory().AddResponse(kValidUrl2,
+                                                          test_response_body2);
+
+  mock_app_client().GrantOAuthTokenFor(
+      kTestUserEmail,
+      /* expiry_time = */ base::Time::Now() + kExpiryTimeFromNow);
+  run_loop.Run();
+  run_loop2.Run();
+}
+
+TEST_F(ProjectorXhrSenderTest, UseCredentials) {
+  base::RunLoop run_loop;
+
+  const std::string& test_response_body = "{}";
+  sender()->Send(
+      GURL(kValidUrl), "GET", /*request_body=*/"", /*useCredential=*/true,
+      base::BindOnce(
+          [](const std::string& expected_response_body,
+             base::RepeatingClosure quit_closure, bool success,
+             const std::string& response_body, const std::string& error) {
+            EXPECT_TRUE(success);
+            EXPECT_EQ(expected_response_body, response_body);
+            EXPECT_EQ("", error);
+            quit_closure.Run();
+          },
+          test_response_body, run_loop.QuitClosure()));
+
+  // Verify that http request is sent without granting OAuth token.
+  mock_app_client().test_url_loader_factory().AddResponse(kValidUrl,
+                                                          test_response_body);
+
+  run_loop.Run();
+}
+
+TEST_F(ProjectorXhrSenderTest, NetworkError) {
+  base::RunLoop run_loop;
+
+  sender()->Send(
+      GURL(kValidUrl), /*method=*/"GET", /*request_body=*/"",
+      /*use_credentials=*/false,
+      base::BindOnce(
+          [](base::RepeatingClosure quit_closure, bool success,
+             const std::string& response_body, const std::string& error) {
+            EXPECT_FALSE(success);
+            EXPECT_EQ("", response_body);
+            EXPECT_EQ("XHR_FETCH_FAILURE", error);
+            quit_closure.Run();
+          },
+          run_loop.QuitClosure()));
+
+  mock_app_client().test_url_loader_factory().AddResponse(
+      GURL(kValidUrl), network::mojom::URLResponseHead::New(), std::string(),
+      network::URLLoaderCompletionStatus(net::HTTP_NOT_FOUND));
+
+  mock_app_client().GrantOAuthTokenFor(
+      kTestUserEmail,
+      /* expiry_time = */ base::Time::Now() + kExpiryTimeFromNow);
+  run_loop.Run();
+}
+
+TEST_F(ProjectorXhrSenderTest, UnsupportedUrl) {
+  base::RunLoop run_loop;
+
+  sender()->Send(
+      GURL("https://example.com"), /*method=*/"GET", /*request_body=*/"",
+      /*use_credentials=*/false,
+      base::BindOnce(
+          [](base::RepeatingClosure quit_closure, bool success,
+             const std::string& response_body, const std::string& error) {
+            EXPECT_FALSE(success);
+            EXPECT_EQ("", response_body);
+            EXPECT_EQ("UNSUPPORTED_URL", error);
+            quit_closure.Run();
+          },
+          run_loop.QuitClosure()));
+
+  run_loop.Run();
+}
+}  // namespace chromeos
diff --git a/chromeos/crosapi/mojom/BUILD.gn b/chromeos/crosapi/mojom/BUILD.gn
index 426aee0..2c19b86 100644
--- a/chromeos/crosapi/mojom/BUILD.gn
+++ b/chromeos/crosapi/mojom/BUILD.gn
@@ -21,6 +21,7 @@
     "content_protection.mojom",
     "crosapi.mojom",
     "device_attributes.mojom",
+    "device_settings_service.mojom",
     "download_controller.mojom",
     "drive_integration_service.mojom",
     "feedback.mojom",
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index 92efef9..a40b2fdb 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -16,6 +16,7 @@
 import "chromeos/crosapi/mojom/clipboard_history.mojom";
 import "chromeos/crosapi/mojom/content_protection.mojom";
 import "chromeos/crosapi/mojom/device_attributes.mojom";
+import "chromeos/crosapi/mojom/device_settings_service.mojom";
 import "chromeos/crosapi/mojom/download_controller.mojom";
 import "chromeos/crosapi/mojom/drive_integration_service.mojom";
 import "chromeos/crosapi/mojom/feedback.mojom";
@@ -79,8 +80,8 @@
 // please note the milestone when you added it, to help us reason about
 // compatibility between the client applications and older ash-chrome binaries.
 //
-// Next version: 55
-// Next method id: 59
+// Next version: 56
+// Next method id: 60
 [Stable, Uuid="8b79c34f-2bf8-4499-979a-b17cac522c1e",
  RenamedFrom="crosapi.mojom.AshChromeService"]
 interface Crosapi {
@@ -172,6 +173,12 @@
   [MinVersion=12] BindDeviceAttributes@17(
       pending_receiver<DeviceAttributes> receiver);
 
+  // Binds the DeviceSettingsService interface for initializing device settings
+  // in Lacros-Chrome.
+  // Added in 96.
+  [MinVersion=55] BindDeviceSettingsService@59(
+    pending_receiver<DeviceSettingsService> receiver);
+
   // Binds the DownloadController interface, which allows Lacros download
   // information to be passed into Ash Chrome.
   // Added in M92.
@@ -469,44 +476,6 @@
   [MinVersion=1] array<string>? device_affiliation_ids@1;
 };
 
-// Copy of UsbDetachableAllowlistProto from chrome_device_policy.proto.
-[Stable, Extensible]
-struct UsbDetachableAllowlist {
-  array<UsbDeviceId> usb_device_ids@0;
-};
-
-// Copy of UsbDeviceIdInclusiveProto from chrome_device_policy.proto.
-[Stable, Extensible]
-struct UsbDeviceId {
-  // USB Vendor Identifier (aka idVendor).
-  bool has_vendor_id@0;
-  int32 vendor_id@1;
-  // USB Product Identifier (aka idProduct).
-  bool has_product_id@2;
-  int32 product_id@3;
-};
-
-// All the device settings data that are needed in Lacros should be here.
-[Stable, Extensible]
-struct DeviceSettings {
-  // The value of AttestationForContentProtectionEnabled device setting.
-  OptionalBool attestation_for_content_protection_enabled@0;
-
-  // The value of DeviceSystemWideTracingEnabled device policy.
-  OptionalBool device_system_wide_tracing_enabled@1;
-
-  // The value of UsbDetachableAllowlist device policy.
-  UsbDetachableAllowlist? usb_detachable_allow_list@2;
-
-  [Stable]
-  enum OptionalBool {
-    kUnset,
-    kFalse,
-    kTrue,
-  };
-};
-
-
 [Stable, Extensible]
 enum ExoImeSupport {
   kUnsupported = 0,
@@ -757,7 +726,8 @@
   [MinVersion=24]
   array<url.mojom.Url>? startup_urls@24;
 
-  // The set of device settings Lacros needs to know about.
+  // The set of device settings for Lacros. Lacros should *NOT* use this data,
+  // but rely on DeviceSettingsLacros::GetDeviceSettings instead.
   [MinVersion=25]
   DeviceSettings? device_settings@25;
 
diff --git a/chromeos/crosapi/mojom/device_settings_service.mojom b/chromeos/crosapi/mojom/device_settings_service.mojom
new file mode 100644
index 0000000..80894dc
--- /dev/null
+++ b/chromeos/crosapi/mojom/device_settings_service.mojom
@@ -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.
+
+module crosapi.mojom;
+
+// Copy of UsbDetachableAllowlistProto from chrome_device_policy.proto.
+[Stable, Extensible]
+struct UsbDetachableAllowlist {
+  array<UsbDeviceId> usb_device_ids@0;
+};
+
+// Copy of UsbDeviceIdInclusiveProto from chrome_device_policy.proto.
+[Stable, Extensible]
+struct UsbDeviceId {
+  // USB Vendor Identifier (aka idVendor).
+  bool has_vendor_id@0;
+  int32 vendor_id@1;
+  // USB Product Identifier (aka idProduct).
+  bool has_product_id@2;
+  int32 product_id@3;
+};
+
+// All the device settings data that are needed in Lacros should be here.
+[Stable, Extensible]
+struct DeviceSettings {
+  // The value of AttestationForContentProtectionEnabled device setting.
+  OptionalBool attestation_for_content_protection_enabled@0;
+
+  // The value of DeviceSystemWideTracingEnabled device policy.
+  OptionalBool device_system_wide_tracing_enabled@1;
+
+  // The value of UsbDetachableAllowlist device policy.
+  UsbDetachableAllowlist? usb_detachable_allow_list@2;
+
+  [Stable]
+  enum OptionalBool {
+    kUnset,
+    kFalse,
+    kTrue,
+  };
+};
+
+// Interface for device settings observers. Implemented by lacros-chrome. Used
+// by ash-chrome to send device settings updates.
+[Stable, Uuid="c2d2367e-1179-42ca-97ab-426a0c3cd265"]
+interface DeviceSettingsObserver {
+  // Called when device settings have changed.
+  UpdateDeviceSettings@0(DeviceSettings device_settings);
+};
+
+// This interface is implemented by Ash-Chrome.
+// It includes the device policy and other device settings configurable by the
+// user that are needed in Lacros.
+[Stable, Uuid="7ce66db5-5d91-4b45-b7aa-4fcfd8a53985"]
+interface DeviceSettingsService {
+  // Adds an observer for device settings updates.
+  AddDeviceSettingsObserver@0(pending_remote<DeviceSettingsObserver> observer);
+};
diff --git a/chromeos/dbus/BUILD.gn b/chromeos/dbus/BUILD.gn
index 72ea43f..91b8965 100644
--- a/chromeos/dbus/BUILD.gn
+++ b/chromeos/dbus/BUILD.gn
@@ -40,6 +40,7 @@
     "//chromeos/dbus/chunneld:proto",
     "//chromeos/dbus/cros_disks",
     "//chromeos/dbus/easy_unlock",
+    "//chromeos/dbus/fwupd",
     "//chromeos/dbus/gnubby",
     "//chromeos/dbus/image_burner",
     "//chromeos/dbus/image_loader",
@@ -137,6 +138,7 @@
     "//chromeos/dbus/cryptohome:attestation_proto",
     "//chromeos/dbus/dlcservice:test_support",
     "//chromeos/dbus/easy_unlock:unit_tests",
+    "//chromeos/dbus/fwupd:test_support",
     "//chromeos/dbus/gnubby:unit_tests",
     "//chromeos/dbus/hermes:test_support",
     "//chromeos/dbus/ip_peripheral:test_support",
diff --git a/chromeos/dbus/dbus_clients_browser.cc b/chromeos/dbus/dbus_clients_browser.cc
index 429de40..9f87828 100644
--- a/chromeos/dbus/dbus_clients_browser.cc
+++ b/chromeos/dbus/dbus_clients_browser.cc
@@ -29,6 +29,8 @@
 #include "chromeos/dbus/debug_daemon/fake_debug_daemon_client.h"
 #include "chromeos/dbus/easy_unlock/easy_unlock_client.h"
 #include "chromeos/dbus/easy_unlock/fake_easy_unlock_client.h"
+#include "chromeos/dbus/fwupd/fake_fwupd_client.h"
+#include "chromeos/dbus/fwupd/fwupd_client.h"
 #include "chromeos/dbus/gnubby/fake_gnubby_client.h"
 #include "chromeos/dbus/gnubby/gnubby_client.h"
 #include "chromeos/dbus/image_burner/fake_image_burner_client.h"
@@ -88,6 +90,7 @@
   debug_daemon_client_ =
       CREATE_DBUS_CLIENT(DebugDaemonClient, use_real_clients);
   easy_unlock_client_ = CREATE_DBUS_CLIENT(EasyUnlockClient, use_real_clients);
+  fwupd_client_ = CREATE_DBUS_CLIENT(FwupdClient, use_real_clients);
   gnubby_client_ = CREATE_DBUS_CLIENT(GnubbyClient, use_real_clients);
   image_burner_client_ =
       CREATE_DBUS_CLIENT(ImageBurnerClient, use_real_clients);
@@ -129,6 +132,7 @@
   cros_disks_client_->Init(system_bus);
   debug_daemon_client_->Init(system_bus);
   easy_unlock_client_->Init(system_bus);
+  fwupd_client_->Init(system_bus);
   gnubby_client_->Init(system_bus);
   image_burner_client_->Init(system_bus);
   image_loader_client_->Init(system_bus);
diff --git a/chromeos/dbus/dbus_clients_browser.h b/chromeos/dbus/dbus_clients_browser.h
index 5c4e055c..ebcea907 100644
--- a/chromeos/dbus/dbus_clients_browser.h
+++ b/chromeos/dbus/dbus_clients_browser.h
@@ -27,6 +27,7 @@
 class CrosDisksClient;
 class DebugDaemonClient;
 class EasyUnlockClient;
+class FwupdClient;
 class GnubbyClient;
 class ImageBurnerClient;
 class ImageLoaderClient;
@@ -70,6 +71,7 @@
   std::unique_ptr<CrosDisksClient> cros_disks_client_;
   std::unique_ptr<DebugDaemonClient> debug_daemon_client_;
   std::unique_ptr<EasyUnlockClient> easy_unlock_client_;
+  std::unique_ptr<FwupdClient> fwupd_client_;
   std::unique_ptr<GnubbyClient> gnubby_client_;
   std::unique_ptr<ImageBurnerClient> image_burner_client_;
   std::unique_ptr<ImageLoaderClient> image_loader_client_;
diff --git a/chromeos/dbus/fwupd/BUILD.gn b/chromeos/dbus/fwupd/BUILD.gn
new file mode 100644
index 0000000..0f7b7331
--- /dev/null
+++ b/chromeos/dbus/fwupd/BUILD.gn
@@ -0,0 +1,38 @@
+# 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.
+
+assert(is_chromeos, "Non-ChromeOS builds cannot depend on //chromeos")
+
+component("fwupd") {
+  output_name = "chromeos_fwupd"
+  defines = [ "IS_CHROMEOS_DBUS_FUWPD_IMPL" ]
+
+  deps = [
+    "//ash/constants",
+    "//chromeos/dbus:common",
+    "//dbus",
+  ]
+
+  sources = [
+    "fake_fwupd_client.cc",
+    "fake_fwupd_client.h",
+    "fwupd_client.cc",
+    "fwupd_client.h",
+  ]
+}
+
+source_set("test_support") {
+  testonly = true
+
+  deps = [
+    ":fwupd",
+    "//ash/constants",
+    "//base/test:test_support",
+    "//dbus:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+
+  sources = [ "fwupd_client_unittest.cc" ]
+}
diff --git a/chromeos/dbus/fwupd/DEPS b/chromeos/dbus/fwupd/DEPS
new file mode 100644
index 0000000..5aa4ceb6
--- /dev/null
+++ b/chromeos/dbus/fwupd/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+ash/constants",
+]
\ No newline at end of file
diff --git a/chromeos/dbus/fwupd/OWNERS b/chromeos/dbus/fwupd/OWNERS
new file mode 100644
index 0000000..cbaa826
--- /dev/null
+++ b/chromeos/dbus/fwupd/OWNERS
@@ -0,0 +1,2 @@
+jimmyxgong@chromium.org
+zentaro@chromium.org
\ No newline at end of file
diff --git a/chromeos/dbus/fwupd/fake_fwupd_client.cc b/chromeos/dbus/fwupd/fake_fwupd_client.cc
new file mode 100644
index 0000000..e33049e
--- /dev/null
+++ b/chromeos/dbus/fwupd/fake_fwupd_client.cc
@@ -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.
+
+#include "chromeos/dbus/fwupd/fake_fwupd_client.h"
+
+namespace chromeos {
+
+FakeFwupdClient::FakeFwupdClient() = default;
+FakeFwupdClient::~FakeFwupdClient() = default;
+void FakeFwupdClient::Init(dbus::Bus* bus) {}
+
+}  // namespace chromeos
\ No newline at end of file
diff --git a/chromeos/dbus/fwupd/fake_fwupd_client.h b/chromeos/dbus/fwupd/fake_fwupd_client.h
new file mode 100644
index 0000000..9eed98c
--- /dev/null
+++ b/chromeos/dbus/fwupd/fake_fwupd_client.h
@@ -0,0 +1,27 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_DBUS_FWUPD_FAKE_FWUPD_CLIENT_H_
+#define CHROMEOS_DBUS_FWUPD_FAKE_FWUPD_CLIENT_H_
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "chromeos/dbus/fwupd/fwupd_client.h"
+
+namespace chromeos {
+
+class COMPONENT_EXPORT(CHROMEOS_DBUS_FUWPD) FakeFwupdClient
+    : public FwupdClient {
+ public:
+  FakeFwupdClient();
+  FakeFwupdClient(const FakeFwupdClient&) = delete;
+  FakeFwupdClient& operator=(const FakeFwupdClient&) = delete;
+  ~FakeFwupdClient() override;
+
+  void Init(dbus::Bus* bus) override;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_DBUS_FWUPD_FAKE_FWUPD_CLIENT_H_
diff --git a/chromeos/dbus/fwupd/fwupd_client.cc b/chromeos/dbus/fwupd/fwupd_client.cc
new file mode 100644
index 0000000..504b579
--- /dev/null
+++ b/chromeos/dbus/fwupd/fwupd_client.cc
@@ -0,0 +1,94 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/dbus/fwupd/fwupd_client.h"
+
+#include <memory>
+
+#include "ash/constants/ash_features.h"
+#include "base/bind.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/object_proxy.h"
+
+namespace chromeos {
+
+namespace {
+
+FwupdClient* g_instance = nullptr;
+
+const char kFwupdServiceName[] = "org.freedesktop.fwupd";
+const char kFwupdServicePath[] = "/";
+const char kFwupdServiceInterface[] = "org.freedesktop.fwupd";
+const char kFwupdDeviceAddedSignalName[] = "DeviceAdded";
+}  // namespace
+
+class FwupdClientImpl : public FwupdClient {
+ public:
+  FwupdClientImpl();
+  FwupdClientImpl(const FwupdClientImpl&) = delete;
+  FwupdClientImpl& operator=(const FwupdClientImpl&) = delete;
+  ~FwupdClientImpl() override;
+
+ protected:
+  void Init(dbus::Bus* bus) override {
+    if (!features::IsFirmwareUpdaterAppEnabled())
+      return;
+
+    proxy_ = bus->GetObjectProxy(kFwupdServiceName,
+                                 dbus::ObjectPath(kFwupdServicePath));
+
+    proxy_->ConnectToSignal(
+        kFwupdServiceInterface, kFwupdDeviceAddedSignalName,
+        base::BindRepeating(&FwupdClientImpl::OnDeviceAddedReceived,
+                            weak_ptr_factory_.GetWeakPtr()),
+        base::BindOnce(&FwupdClientImpl::OnSignalConnected,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
+ private:
+  void OnSignalConnected(const std::string& interface_name,
+                         const std::string& signal_name,
+                         bool is_connected) {
+    if (!is_connected) {
+      LOG(ERROR) << "Failed to connect to signal " << signal_name;
+    }
+    DCHECK_EQ(kFwupdServiceInterface, interface_name);
+  }
+
+  // TODO(swifton): This is a stub implementation.
+  void OnDeviceAddedReceived(dbus::Signal* signal) {
+    if (client_is_in_testing_mode_) {
+      ++device_signal_call_count_for_testing_;
+    }
+  }
+
+  dbus::ObjectProxy* proxy_ = nullptr;
+
+  // Note: This should remain the last member so it'll be destroyed and
+  // invalidate its weak pointers before any other members are destroyed.
+  base::WeakPtrFactory<FwupdClientImpl> weak_ptr_factory_{this};
+};
+
+FwupdClientImpl::FwupdClientImpl() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
+
+FwupdClientImpl::~FwupdClientImpl() {
+  DCHECK_EQ(g_instance, this);
+  g_instance = nullptr;
+}
+
+// static
+std::unique_ptr<FwupdClient> FwupdClient::Create() {
+  return std::make_unique<FwupdClientImpl>();
+}
+
+// static
+FwupdClient* FwupdClient::Get() {
+  return g_instance;
+}
+
+}  // namespace chromeos
diff --git a/chromeos/dbus/fwupd/fwupd_client.h b/chromeos/dbus/fwupd/fwupd_client.h
new file mode 100644
index 0000000..1a0bff0
--- /dev/null
+++ b/chromeos/dbus/fwupd/fwupd_client.h
@@ -0,0 +1,39 @@
+// 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 CHROMEOS_DBUS_FWUPD_FWUPD_CLIENT_H_
+#define CHROMEOS_DBUS_FWUPD_FWUPD_CLIENT_H_
+
+#include <memory>
+
+#include "base/component_export.h"
+#include "chromeos/dbus/dbus_client.h"
+
+namespace chromeos {
+// FwupdClient is used for handling signals from the fwupd daemon.
+class COMPONENT_EXPORT(CHROMEOS_DBUS_FUWPD) FwupdClient : public DBusClient {
+ public:
+  // Create() should be used instead.
+  FwupdClient() = default;
+  FwupdClient(const FwupdClient&) = delete;
+  FwupdClient& operator=(const FwupdClient&) = delete;
+  ~FwupdClient() override = default;
+
+  // Factory function.
+  static std::unique_ptr<FwupdClient> Create();
+
+  // Returns the global instance if initialized. May return null.
+  static FwupdClient* Get();
+
+ protected:
+  friend class FwupdClientTest;
+
+  // Auxiliary variables for testing.
+  // TODO(swifton): Replace this with an observer.
+  bool client_is_in_testing_mode_ = false;
+  int device_signal_call_count_for_testing_ = 0;
+};
+}  // namespace chromeos
+
+#endif  // CHROMEOS_DBUS_FWUPD_FWUPD_CLIENT_H_
diff --git a/chromeos/dbus/fwupd/fwupd_client_unittest.cc b/chromeos/dbus/fwupd/fwupd_client_unittest.cc
new file mode 100644
index 0000000..e1e7d83b
--- /dev/null
+++ b/chromeos/dbus/fwupd/fwupd_client_unittest.cc
@@ -0,0 +1,107 @@
+// 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 "chromeos/dbus/fwupd/fwupd_client.h"
+
+#include "ash/constants/ash_features.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "dbus/mock_bus.h"
+#include "dbus/mock_object_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+
+namespace {
+const char kFwupdServiceName[] = "org.freedesktop.fwupd";
+const char kFwupdServicePath[] = "/";
+const char kFwupdDeviceAddedSignalName[] = "DeviceAdded";
+}  // namespace
+
+namespace chromeos {
+
+class FwupdClientTest : public testing::Test {
+ public:
+  FwupdClientTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        ::ash::features::kFirmwareUpdaterApp);
+
+    dbus::Bus::Options options;
+    options.bus_type = dbus::Bus::SYSTEM;
+    bus_ = base::MakeRefCounted<dbus::MockBus>(options);
+
+    dbus::ObjectPath fwupd_service_path(kFwupdServicePath);
+    proxy_ = base::MakeRefCounted<dbus::MockObjectProxy>(
+        bus_.get(), kFwupdServiceName, fwupd_service_path);
+
+    EXPECT_CALL(*bus_.get(),
+                GetObjectProxy(kFwupdServiceName, fwupd_service_path))
+        .WillRepeatedly(testing::Return(proxy_.get()));
+
+    EXPECT_CALL(*proxy_, DoConnectToSignal(kFwupdServiceName, _, _, _))
+        .WillRepeatedly(Invoke(this, &FwupdClientTest::ConnectToSignal));
+
+    fwupd_client_ = FwupdClient::Create();
+    fwupd_client_->Init(bus_.get());
+    fwupd_client_->client_is_in_testing_mode_ = true;
+  }
+
+  FwupdClientTest(const FwupdClientTest&) = delete;
+  FwupdClientTest& operator=(const FwupdClientTest&) = delete;
+  ~FwupdClientTest() override = default;
+
+  int GetDeviceSignalCallCount() {
+    return fwupd_client_->device_signal_call_count_for_testing_;
+  }
+
+ protected:
+  // Synchronously passes |signal| to |client_|'s handler, simulating the signal
+  // being emitted by fwupd.
+  void EmitSignal(const std::string& signal_name) {
+    dbus::Signal signal(kFwupdServiceName, signal_name);
+    const auto callback = signal_callbacks_.find(signal_name);
+    ASSERT_TRUE(callback != signal_callbacks_.end())
+        << "Client didn't register for signal " << signal_name;
+    callback->second.Run(&signal);
+  }
+
+ private:
+  // Handles calls to |proxy_|'s ConnectToSignal() method.
+  void ConnectToSignal(
+      const std::string& interface_name,
+      const std::string& signal_name,
+      dbus::ObjectProxy::SignalCallback signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
+    CHECK_EQ(interface_name, kFwupdServiceName);
+    signal_callbacks_[signal_name] = signal_callback;
+
+    task_environment_.GetMainThreadTaskRunner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(*on_connected_callback), interface_name,
+                       signal_name, true /* success */));
+  }
+
+  // Maps from fwupd signal name to the corresponding callback provided by
+  // |client_|.
+  base::flat_map<std::string, dbus::ObjectProxy::SignalCallback>
+      signal_callbacks_;
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  scoped_refptr<dbus::MockBus> bus_;
+  scoped_refptr<dbus::MockObjectProxy> proxy_;
+  std::unique_ptr<FwupdClient> fwupd_client_;
+};
+
+// TODO (swifton): Rewrite this test with an observer when it's available.
+TEST_F(FwupdClientTest, AddOneDevice) {
+  EmitSignal(kFwupdDeviceAddedSignalName);
+  EXPECT_EQ(1, GetDeviceSignalCallCount());
+}
+
+}  // namespace chromeos
diff --git a/chromeos/dbus/rmad/fake_rmad_client.h b/chromeos/dbus/rmad/fake_rmad_client.h
index c8f93d1..b6ff49b 100644
--- a/chromeos/dbus/rmad/fake_rmad_client.h
+++ b/chromeos/dbus/rmad/fake_rmad_client.h
@@ -69,7 +69,8 @@
   std::vector<rmad::GetStateReply> state_replies_;
   size_t state_index_;
   rmad::AbortRmaReply abort_rma_reply_;
-  base::ObserverList<Observer>::Unchecked observers_;
+  base::ObserverList<Observer, /*check_empty=*/true, /*allow_reentrancy=*/false>
+      observers_;
 };
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/rmad/rmad_client.cc b/chromeos/dbus/rmad/rmad_client.cc
index 588908e..f3c93544 100644
--- a/chromeos/dbus/rmad/rmad_client.cc
+++ b/chromeos/dbus/rmad/rmad_client.cc
@@ -63,7 +63,8 @@
                        bool success);
 
   dbus::ObjectProxy* rmad_proxy_ = nullptr;
-  base::ObserverList<Observer, true, false>::Unchecked observers_;
+  base::ObserverList<Observer, /*check_empty=*/true, /*allow_reentrancy=*/false>
+      observers_;
 
   // Note: This should remain the last member so it'll be destroyed and
   // invalidate its weak pointers before any other members are destroyed.
diff --git a/chromeos/dbus/rmad/rmad_client.h b/chromeos/dbus/rmad/rmad_client.h
index dda0ce9..f8f3ba9b 100644
--- a/chromeos/dbus/rmad/rmad_client.h
+++ b/chromeos/dbus/rmad/rmad_client.h
@@ -6,6 +6,7 @@
 #define CHROMEOS_DBUS_RMAD_RMAD_CLIENT_H_
 
 #include "base/component_export.h"
+#include "base/observer_list_types.h"
 #include "chromeos/dbus/dbus_method_call_status.h"
 #include "chromeos/dbus/rmad/rmad.pb.h"
 
@@ -23,10 +24,8 @@
 class COMPONENT_EXPORT(RMAD) RmadClient {
  public:
   // Interface for observing signals from rmad.
-  class Observer {
+  class Observer : public base::CheckedObserver {
    public:
-    virtual ~Observer() {}
-
     // Called when an error occurs outside of state transitions.
     // e.g. while calibrating devices.
     virtual void Error(rmad::RmadErrorCode error) {}
diff --git a/chromeos/lacros/lacros_service.cc b/chromeos/lacros/lacros_service.cc
index 1090a1d..8a7f7e1 100644
--- a/chromeos/lacros/lacros_service.cc
+++ b/chromeos/lacros/lacros_service.cc
@@ -25,6 +25,7 @@
 #include "chromeos/crosapi/mojom/clipboard_history.mojom.h"
 #include "chromeos/crosapi/mojom/content_protection.mojom.h"
 #include "chromeos/crosapi/mojom/crosapi.mojom.h"
+#include "chromeos/crosapi/mojom/device_settings_service.mojom.h"
 #include "chromeos/crosapi/mojom/download_controller.mojom.h"
 #include "chromeos/crosapi/mojom/drive_integration_service.mojom.h"
 #include "chromeos/crosapi/mojom/feedback.mojom.h"
@@ -227,6 +228,10 @@
       crosapi::mojom::DeviceAttributes, &Crosapi::BindDeviceAttributes,
       Crosapi::MethodMinVersions::kBindDeviceAttributesMinVersion>();
   ConstructRemote<
+      crosapi::mojom::DeviceSettingsService,
+      &Crosapi::BindDeviceSettingsService,
+      Crosapi::MethodMinVersions::kBindDeviceSettingsServiceMinVersion>();
+  ConstructRemote<
       crosapi::mojom::DownloadController, &Crosapi::BindDownloadController,
       Crosapi::MethodMinVersions::kBindDownloadControllerMinVersion>();
   ConstructRemote<
diff --git a/chromeos/login/auth/extended_authenticator_impl.cc b/chromeos/login/auth/extended_authenticator_impl.cc
index 32c1d95..cd7c4140 100644
--- a/chromeos/login/auth/extended_authenticator_impl.cc
+++ b/chromeos/login/auth/extended_authenticator_impl.cc
@@ -251,17 +251,6 @@
   LOG(ERROR) << "Extended authenticator cryptohome error, code: "
              << return_code;
 
-  AuthState state = FAILED_MOUNT;
-
-  if (return_code == cryptohome::MOUNT_ERROR_TPM_COMM_ERROR ||
-      return_code == cryptohome::MOUNT_ERROR_TPM_DEFEND_LOCK ||
-      return_code == cryptohome::MOUNT_ERROR_TPM_NEEDS_REBOOT) {
-    state = FAILED_TPM;
-  }
-
-  if (return_code == cryptohome::MOUNT_ERROR_USER_DOES_NOT_EXIST)
-    state = NO_MOUNT;
-
   if (consumer_) {
     AuthFailure failure(AuthFailure::UNLOCK_FAILED);
     consumer_->OnAuthFailure(failure);
diff --git a/chromeos/services/bluetooth_config/cros_bluetooth_config.cc b/chromeos/services/bluetooth_config/cros_bluetooth_config.cc
index 852ecde..28a9633 100644
--- a/chromeos/services/bluetooth_config/cros_bluetooth_config.cc
+++ b/chromeos/services/bluetooth_config/cros_bluetooth_config.cc
@@ -40,8 +40,8 @@
 CrosBluetoothConfig::~CrosBluetoothConfig() = default;
 
 void CrosBluetoothConfig::SetPrefs(PrefService* logged_in_profile_prefs,
-                                   PrefService* device_prefs) {
-  device_name_manager_->SetPrefs(device_prefs);
+                                   PrefService* local_state) {
+  device_name_manager_->SetPrefs(local_state);
 }
 
 void CrosBluetoothConfig::BindPendingReceiver(
diff --git a/chromeos/services/bluetooth_config/cros_bluetooth_config.h b/chromeos/services/bluetooth_config/cros_bluetooth_config.h
index 890bb64..5439fa0 100644
--- a/chromeos/services/bluetooth_config/cros_bluetooth_config.h
+++ b/chromeos/services/bluetooth_config/cros_bluetooth_config.h
@@ -40,8 +40,7 @@
   ~CrosBluetoothConfig() override;
 
   // Sets the PrefServices to be used by classes within CrosBluetoothConfig.
-  void SetPrefs(PrefService* logged_in_profile_prefs,
-                PrefService* device_prefs);
+  void SetPrefs(PrefService* logged_in_profile_prefs, PrefService* local_state);
 
   // Binds a PendingReceiver to this instance. Clients wishing to use the
   // CrosBluetoothConfig API should use this function as an entrypoint.
diff --git a/chromeos/services/bluetooth_config/cros_bluetooth_config_unittest.cc b/chromeos/services/bluetooth_config/cros_bluetooth_config_unittest.cc
index 51e89f2..fd571f4 100644
--- a/chromeos/services/bluetooth_config/cros_bluetooth_config_unittest.cc
+++ b/chromeos/services/bluetooth_config/cros_bluetooth_config_unittest.cc
@@ -35,7 +35,8 @@
 
   // testing::Test:
   void SetUp() override {
-    DeviceNameManagerImpl::RegisterPrefs(test_pref_service_.registry());
+    DeviceNameManagerImpl::RegisterLocalStatePrefs(
+        test_pref_service_.registry());
 
     mock_adapter_ =
         base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
diff --git a/chromeos/services/bluetooth_config/device_name_manager.h b/chromeos/services/bluetooth_config/device_name_manager.h
index 9d783fa..c2081376 100644
--- a/chromeos/services/bluetooth_config/device_name_manager.h
+++ b/chromeos/services/bluetooth_config/device_name_manager.h
@@ -41,7 +41,7 @@
                                  const std::string& nickname) = 0;
 
   // Sets the PrefService used to store nicknames.
-  virtual void SetPrefs(PrefService* pref_service) = 0;
+  virtual void SetPrefs(PrefService* local_state) = 0;
 
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
diff --git a/chromeos/services/bluetooth_config/device_name_manager_impl.cc b/chromeos/services/bluetooth_config/device_name_manager_impl.cc
index f59bfe6..b4d344b3 100644
--- a/chromeos/services/bluetooth_config/device_name_manager_impl.cc
+++ b/chromeos/services/bluetooth_config/device_name_manager_impl.cc
@@ -27,7 +27,8 @@
 }  // namespace
 
 // static
-void DeviceNameManagerImpl::RegisterPrefs(PrefRegistrySimple* registry) {
+void DeviceNameManagerImpl::RegisterLocalStatePrefs(
+    PrefRegistrySimple* registry) {
   registry->RegisterDictionaryPref(kDeviceIdToNicknameMapPrefName,
                                    base::Value(base::Value::Type::DICTIONARY));
 }
@@ -40,11 +41,11 @@
 
 absl::optional<std::string> DeviceNameManagerImpl::GetDeviceNickname(
     const std::string& device_id) {
-  if (!pref_service_)
+  if (!local_state_)
     return absl::nullopt;
 
   const std::string* nickname =
-      pref_service_->GetDictionary(kDeviceIdToNicknameMapPrefName)
+      local_state_->GetDictionary(kDeviceIdToNicknameMapPrefName)
           ->FindStringKey(device_id);
   if (!nickname)
     return absl::nullopt;
@@ -68,14 +69,14 @@
     return;
   }
 
-  if (!pref_service_) {
+  if (!local_state_) {
     BLUETOOTH_LOG(ERROR) << "SetDeviceNickname for device failed because "
-                            "no pref_service_ was set.";
+                            "no local_state_ was set.";
     return;
   }
 
   base::DictionaryValue* device_id_to_nickname_map =
-      DictionaryPrefUpdate(pref_service_, kDeviceIdToNicknameMapPrefName).Get();
+      DictionaryPrefUpdate(local_state_, kDeviceIdToNicknameMapPrefName).Get();
   DCHECK(device_id_to_nickname_map)
       << "Device ID to nickname map pref is unregistered.";
   device_id_to_nickname_map->SetStringKey(device_id, nickname);
@@ -83,8 +84,8 @@
   NotifyDeviceNicknameChanged(device_id);
 }
 
-void DeviceNameManagerImpl::SetPrefs(PrefService* pref_service) {
-  pref_service_ = pref_service;
+void DeviceNameManagerImpl::SetPrefs(PrefService* local_state) {
+  local_state_ = local_state;
 }
 
 bool DeviceNameManagerImpl::DoesDeviceExist(
diff --git a/chromeos/services/bluetooth_config/device_name_manager_impl.h b/chromeos/services/bluetooth_config/device_name_manager_impl.h
index 1dafd92..f94025d 100644
--- a/chromeos/services/bluetooth_config/device_name_manager_impl.h
+++ b/chromeos/services/bluetooth_config/device_name_manager_impl.h
@@ -18,7 +18,7 @@
 // Concrete DeviceNameManager implementation that saves entries into Prefs.
 class DeviceNameManagerImpl : public DeviceNameManager {
  public:
-  static void RegisterPrefs(PrefRegistrySimple* registry);
+  static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
 
   explicit DeviceNameManagerImpl(
       scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
@@ -29,7 +29,7 @@
       const std::string& device_id) override;
   void SetDeviceNickname(const std::string& device_id,
                          const std::string& nickname) override;
-  void SetPrefs(PrefService* pref_service) override;
+  void SetPrefs(PrefService* local_state) override;
 
  private:
   // Returns true if a BluetoothDevice* with identifier |device_id| exists in
@@ -37,7 +37,7 @@
   bool DoesDeviceExist(const std::string& device_id) const;
 
   scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;
-  PrefService* pref_service_ = nullptr;
+  PrefService* local_state_ = nullptr;
 };
 
 }  // namespace bluetooth_config
diff --git a/chromeos/services/bluetooth_config/device_name_manager_impl_unittest.cc b/chromeos/services/bluetooth_config/device_name_manager_impl_unittest.cc
index 7559967..e7d93f9 100644
--- a/chromeos/services/bluetooth_config/device_name_manager_impl_unittest.cc
+++ b/chromeos/services/bluetooth_config/device_name_manager_impl_unittest.cc
@@ -58,7 +58,8 @@
 
   // testing::Test:
   void SetUp() override {
-    DeviceNameManagerImpl::RegisterPrefs(test_pref_service_.registry());
+    DeviceNameManagerImpl::RegisterLocalStatePrefs(
+        test_pref_service_.registry());
 
     mock_adapter_ =
         base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
diff --git a/chromeos/services/bluetooth_config/fake_device_name_manager.h b/chromeos/services/bluetooth_config/fake_device_name_manager.h
index 96bcc0d..3a2a348e 100644
--- a/chromeos/services/bluetooth_config/fake_device_name_manager.h
+++ b/chromeos/services/bluetooth_config/fake_device_name_manager.h
@@ -24,7 +24,7 @@
       const std::string& device_id) override;
   void SetDeviceNickname(const std::string& device_id,
                          const std::string& nickname) override;
-  void SetPrefs(PrefService* pref_service) override {}
+  void SetPrefs(PrefService* local_state) override {}
 
  private:
   base::flat_map<std::string, std::string> device_id_to_nickname_map_;
diff --git a/chromeos/services/libassistant/display_connection.cc b/chromeos/services/libassistant/display_connection.cc
index 41d5428..f11c1198 100644
--- a/chromeos/services/libassistant/display_connection.cc
+++ b/chromeos/services/libassistant/display_connection.cc
@@ -96,11 +96,9 @@
   ::assistant::display::DisplayRequest display_request;
   FillDisplayRequest(display_request);
 
-  std::string s;
-  display_request.SerializeToString(&s);
   ::assistant::api::OnDisplayRequestRequest request;
-  request.set_display_request_bytes(s);
-  assistant_client_->OnDisplayRequest(request);
+  request.set_display_request_bytes(display_request.SerializeAsString());
+  assistant_client_->SendDisplayRequest(request);
 }
 
 void DisplayConnection::FillDisplayRequest(
diff --git a/chromeos/services/libassistant/grpc/assistant_client.h b/chromeos/services/libassistant/grpc/assistant_client.h
index e46ffae..acfc75b 100644
--- a/chromeos/services/libassistant/grpc/assistant_client.h
+++ b/chromeos/services/libassistant/grpc/assistant_client.h
@@ -127,7 +127,7 @@
   virtual void ResetAllDataAndShutdown() = 0;
 
   // Display methods.
-  virtual void OnDisplayRequest(const OnDisplayRequestRequest& request) = 0;
+  virtual void SendDisplayRequest(const OnDisplayRequestRequest& request) = 0;
   virtual void AddDisplayEventObserver(
       GrpcServicesObserver<OnAssistantDisplayEventRequest>* observer) = 0;
 
diff --git a/chromeos/services/libassistant/grpc/assistant_client_impl.cc b/chromeos/services/libassistant/grpc/assistant_client_impl.cc
index 3f3509a..890417a 100644
--- a/chromeos/services/libassistant/grpc/assistant_client_impl.cc
+++ b/chromeos/services/libassistant/grpc/assistant_client_impl.cc
@@ -72,6 +72,11 @@
   }
 }
 
+void AssistantClientImpl::AddDeviceStateEventObserver(
+    GrpcServicesObserver<OnDeviceStateEventRequest>* observer) {
+  grpc_services_.AddObserver(observer);
+}
+
 // static
 std::unique_ptr<AssistantClient> AssistantClient::Create(
     std::unique_ptr<assistant_client::AssistantManager> assistant_manager,
diff --git a/chromeos/services/libassistant/grpc/assistant_client_impl.h b/chromeos/services/libassistant/grpc/assistant_client_impl.h
index b88731d..6511b58 100644
--- a/chromeos/services/libassistant/grpc/assistant_client_impl.h
+++ b/chromeos/services/libassistant/grpc/assistant_client_impl.h
@@ -40,6 +40,9 @@
   // ServicesStatusObserver overrides:
   void OnServicesStatusChanged(ServicesStatus status) override;
 
+  void AddDeviceStateEventObserver(
+      GrpcServicesObserver<OnDeviceStateEventRequest>* observer) override;
+
  private:
   chromeos::libassistant::GrpcServicesInitializer grpc_services_;
 
diff --git a/chromeos/services/libassistant/grpc/assistant_client_v1.cc b/chromeos/services/libassistant/grpc/assistant_client_v1.cc
index 941c489..7f7090ad 100644
--- a/chromeos/services/libassistant/grpc/assistant_client_v1.cc
+++ b/chromeos/services/libassistant/grpc/assistant_client_v1.cc
@@ -197,7 +197,7 @@
     observer_ = observer;
   }
 
-  void OnDisplayRequest(const std::string& display_request_bytes) {
+  void SendDisplayRequest(const std::string& display_request_bytes) {
     if (!delegate_) {
       LOG(ERROR) << "Can't send DisplayRequest before delegate is set.";
       return;
@@ -357,9 +357,9 @@
   assistant_manager()->ResetAllDataAndShutdown();
 }
 
-void AssistantClientV1::OnDisplayRequest(
+void AssistantClientV1::SendDisplayRequest(
     const OnDisplayRequestRequest& request) {
-  display_connection_->OnDisplayRequest(request.display_request_bytes());
+  display_connection_->SendDisplayRequest(request.display_request_bytes());
 }
 
 void AssistantClientV1::AddDisplayEventObserver(
diff --git a/chromeos/services/libassistant/grpc/assistant_client_v1.h b/chromeos/services/libassistant/grpc/assistant_client_v1.h
index 9240147..fcf587c4 100644
--- a/chromeos/services/libassistant/grpc/assistant_client_v1.h
+++ b/chromeos/services/libassistant/grpc/assistant_client_v1.h
@@ -52,7 +52,7 @@
       const GetSpeakerIdEnrollmentInfoRequest& request,
       base::OnceCallback<void(bool user_model_exists)> on_done) override;
   void ResetAllDataAndShutdown() override;
-  void OnDisplayRequest(const OnDisplayRequestRequest& request) override;
+  void SendDisplayRequest(const OnDisplayRequestRequest& request) override;
   void AddDisplayEventObserver(
       GrpcServicesObserver<OnAssistantDisplayEventRequest>* observer) override;
   void ResumeCurrentStream() override;
diff --git a/chromeos/services/libassistant/grpc/external_services/BUILD.gn b/chromeos/services/libassistant/grpc/external_services/BUILD.gn
index 784da3f2..956101e 100644
--- a/chromeos/services/libassistant/grpc/external_services/BUILD.gn
+++ b/chromeos/services/libassistant/grpc/external_services/BUILD.gn
@@ -4,14 +4,18 @@
 
 source_set("grpc_services_initializer") {
   sources = [
+    "event_handler_driver.cc",
+    "event_handler_driver.h",
     "grpc_services_initializer.cc",
     "grpc_services_initializer.h",
   ]
 
   deps = [
     ":customer_registration_client",
+    ":grpc_services_observer",
     ":heartbeat_event_handler_driver",
     "//base",
+    "//chromeos/assistant/internal",
     "//chromeos/services/libassistant/grpc:grpc_client",
     "//chromeos/services/libassistant/grpc:grpc_service",
     "//chromeos/services/libassistant/grpc:grpc_util",
diff --git a/chromeos/services/libassistant/grpc/external_services/event_handler_driver.cc b/chromeos/services/libassistant/grpc/external_services/event_handler_driver.cc
new file mode 100644
index 0000000..5994341
--- /dev/null
+++ b/chromeos/services/libassistant/grpc/external_services/event_handler_driver.cc
@@ -0,0 +1,32 @@
+// 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 "chromeos/services/libassistant/grpc/external_services/event_handler_driver.h"
+
+#include "chromeos/assistant/internal/libassistant_util.h"
+
+namespace chromeos {
+namespace libassistant {
+
+namespace {
+constexpr char kDeviceStateEventName[] = "DeviceStateEvent";
+constexpr char kHandlerMethodName[] = "OnEventFromLibas";
+}  // namespace
+
+template <>
+::assistant::api::RegisterEventHandlerRequest
+CreateRegistrationRequest<::assistant::api::DeviceStateEventHandlerInterface>(
+    const std::string& assistant_service_address) {
+  ::assistant::api::RegisterEventHandlerRequest request;
+  request.mutable_device_state_events_to_handle()->set_select_all(true);
+  auto* external_handler = request.mutable_handler();
+  external_handler->set_server_address(assistant_service_address);
+  external_handler->set_service_name(
+      chromeos::assistant::GetLibassistGrpcServiceName(kDeviceStateEventName));
+  external_handler->set_handler_method(kHandlerMethodName);
+  return request;
+}
+
+}  // namespace libassistant
+}  // namespace chromeos
diff --git a/chromeos/services/libassistant/grpc/external_services/event_handler_driver.h b/chromeos/services/libassistant/grpc/external_services/event_handler_driver.h
new file mode 100644
index 0000000..58fe556
--- /dev/null
+++ b/chromeos/services/libassistant/grpc/external_services/event_handler_driver.h
@@ -0,0 +1,157 @@
+// 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 CHROMEOS_SERVICES_LIBASSISTANT_GRPC_EXTERNAL_SERVICES_EVENT_HANDLER_DRIVER_H_
+#define CHROMEOS_SERVICES_LIBASSISTANT_GRPC_EXTERNAL_SERVICES_EVENT_HANDLER_DRIVER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "chromeos/assistant/internal/proto/shared/proto/v2/delegate/event_handler_service.grpc.pb.h"
+#include "chromeos/assistant/internal/proto/shared/proto/v2/event_notification_interface.pb.h"
+#include "chromeos/services/libassistant/grpc/async_service_driver.h"
+#include "chromeos/services/libassistant/grpc/external_services/grpc_services_observer.h"
+#include "chromeos/services/libassistant/grpc/grpc_libassistant_client.h"
+#include "chromeos/services/libassistant/grpc/rpc_method_driver.h"
+#include "third_party/grpc/src/include/grpcpp/grpcpp.h"
+
+namespace chromeos {
+namespace libassistant {
+
+// Create request to register event handler, setting fields accordingly. It
+// cannot be a virtual method because it will be used in constructor. Derived
+// class should implement specialized function.
+template <typename THandlerInterface>
+::assistant::api::RegisterEventHandlerRequest CreateRegistrationRequest(
+    const std::string& assistant_service_address_);
+
+// EventHandlerDriver is template base class of each libassistant gRPC
+// event handler. There will be one instance for each event type. Whoever wants
+// to observe libassistant gRPC event should add themselves as observers.
+template <typename THandlerInterface>
+class EventHandlerDriver : public AsyncServiceDriver {
+ public:
+  template <typename Func>
+  struct UnwrapTypeFromInterface;
+
+  template <typename TRequest, typename TResponse, typename Scope>
+  struct UnwrapTypeFromInterface<::grpc::Status (  // NOLINT(whitespace/parens)
+      Scope::*)(grpc::ServerContext*, const TRequest*, TResponse*)> {
+   private:
+    typedef TRequest RequestType;
+    typedef TResponse ResponseType;
+
+    friend class EventHandlerDriver;
+  };
+
+  typedef typename UnwrapTypeFromInterface<decltype(
+      &THandlerInterface::AsyncService::OnEventFromLibas)>::ResponseType
+      ResponseType;
+  typedef typename UnwrapTypeFromInterface<decltype(
+      &THandlerInterface::AsyncService::OnEventFromLibas)>::RequestType
+      RequestType;
+  using EventObserverType = GrpcServicesObserver<RequestType>;
+
+  EventHandlerDriver(::grpc::ServerBuilder* server_builder,
+                     GrpcLibassistantClient* libassistant_client,
+                     const std::string& assistant_service_address)
+      : AsyncServiceDriver(server_builder),
+        libassistant_client_(libassistant_client),
+        assistant_service_address_(assistant_service_address) {
+    DCHECK(server_builder);
+    DCHECK(libassistant_client_);
+
+    server_builder_->RegisterService(&service_);
+  }
+
+  ~EventHandlerDriver() override = default;
+
+  void StartRegistration() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    // Make request to libassistant, registering handler.
+    ::assistant::api::RegisterEventHandlerRequest request =
+        CreateRegistrationRequest<THandlerInterface>(
+            assistant_service_address_);
+    StateConfig config;
+    config.max_retries = 5;
+    config.timeout_in_ms = 3000;
+    libassistant_client_->RegisterEventHandler(
+        request,
+        base::BindOnce(&EventHandlerDriver::OnRegisterEventHandlerDone,
+                       weak_factory_.GetWeakPtr()),
+        std::move(config));
+  }
+
+  void AddObserver(EventObserverType* const observer) {
+    observers_.AddObserver(observer);
+  }
+
+  void RemoveObserver(EventObserverType* const observer) {
+    observers_.RemoveObserver(observer);
+  }
+
+ private:
+  void HandleEvent(
+      grpc::ServerContext* context,
+      const RequestType* request,
+      base::OnceCallback<void(const grpc::Status&, const ResponseType&)> done) {
+    for (auto& observer : observers_) {
+      observer.OnGrpcMessage(*request);
+    }
+
+    ResponseType response;
+    std::move(done).Run(grpc::Status::OK, response);
+  }
+
+  void OnRegisterEventHandlerDone(
+      const ::grpc::Status& status,
+      const ::assistant::api::RegisterEventHandlerResponse& response) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    if (!status.ok()) {
+      LOG(ERROR) << "Failed to register event handler. code: "
+                 << status.error_code() << ". Msg: " << status.error_message();
+    }
+  }
+
+  // AsyncServiceDriver implementation:
+  void StartCQ(::grpc::ServerCompletionQueue* cq) override {
+    rpc_event_driver_ =
+        std::make_unique<RpcMethodDriver<RequestType, ResponseType>>(
+            cq,
+            base::BindRepeating(
+                &THandlerInterface::AsyncService::RequestOnEventFromLibas,
+                async_service_weak_factory_.GetWeakPtr()),
+            base::BindRepeating(
+                &EventHandlerDriver<THandlerInterface>::HandleEvent,
+                weak_factory_.GetWeakPtr()));
+  }
+
+  std::unique_ptr<RpcMethodDriver<RequestType, ResponseType>> rpc_event_driver_;
+
+  typename THandlerInterface::AsyncService service_;
+
+  GrpcLibassistantClient* libassistant_client_;
+  const std::string assistant_service_address_;
+
+  base::ObserverList<EventObserverType> observers_;
+
+  // This sequence checker ensures that all callbacks are called on the main
+  // sequence.
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<typename THandlerInterface::AsyncService>
+      async_service_weak_factory_{&service_};
+  base::WeakPtrFactory<EventHandlerDriver<THandlerInterface>> weak_factory_{
+      this};
+};
+
+}  // namespace libassistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_LIBASSISTANT_GRPC_EXTERNAL_SERVICES_EVENT_HANDLER_DRIVER_H_i
diff --git a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc
index 777c0be..dc6452d 100644
--- a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc
+++ b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc
@@ -74,11 +74,27 @@
 
   DVLOG(1) << "Started ChromeOS Assistant gRPC service";
 
+  RegisterEventHandlers();
   StartCQ();
   customer_registration_client_->Start();
   return true;
 }
 
+// AddObserver and RemoveObserver for each handler driver
+void GrpcServicesInitializer::AddObserver(
+    GrpcServicesObserver<::assistant::api::OnDeviceStateEventRequest>*
+        observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  device_state_event_handler_driver_->AddObserver(observer);
+}
+
+void GrpcServicesInitializer::RemoveObserver(
+    GrpcServicesObserver<::assistant::api::OnDeviceStateEventRequest>*
+        observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  device_state_event_handler_driver_->RemoveObserver(observer);
+}
+
 GrpcLibassistantClient& GrpcServicesInitializer::GrpcLibassistantClient() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return *libassistant_client_;
@@ -86,11 +102,16 @@
 
 void GrpcServicesInitializer::InitDrivers(grpc::ServerBuilder* server_builder) {
   // Inits heartbeat driver.
-  auto heartbeat_driver =
+  heartbeat_driver_ =
       std::make_unique<HeartbeatEventHandlerDriver>(&server_builder_);
-  heartbeat_event_observation_.Observe(heartbeat_driver.get());
+  heartbeat_event_observation_.Observe(heartbeat_driver_.get());
+  service_drivers_.emplace_back(heartbeat_driver_.get());
 
-  service_drivers_.emplace_back(std::move(heartbeat_driver));
+  // Inits other event handler drivers.
+  device_state_event_handler_driver_ = std::make_unique<
+      EventHandlerDriver<::assistant::api::DeviceStateEventHandlerInterface>>(
+      &server_builder_, libassistant_client_.get(), assistant_service_address_);
+  service_drivers_.emplace_back(device_state_event_handler_driver_.get());
 }
 
 void GrpcServicesInitializer::InitLibassistGrpcClient() {
@@ -120,5 +141,9 @@
   RegisterServicesAndInitCQ(&server_builder_);
 }
 
+void GrpcServicesInitializer::RegisterEventHandlers() {
+  device_state_event_handler_driver_->StartRegistration();
+}
+
 }  // namespace libassistant
 }  // namespace chromeos
diff --git a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.h b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.h
index b0403a9..f20d579 100644
--- a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.h
+++ b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.h
@@ -12,12 +12,21 @@
 #include "base/scoped_observation.h"
 #include "base/sequence_checker.h"
 #include "chromeos/services/libassistant/grpc/external_services/customer_registration_client.h"
+#include "chromeos/services/libassistant/grpc/external_services/event_handler_driver.h"
+#include "chromeos/services/libassistant/grpc/external_services/grpc_services_observer.h"
 #include "chromeos/services/libassistant/grpc/external_services/heartbeat_event_handler_driver.h"
 #include "chromeos/services/libassistant/grpc/grpc_client_thread.h"
 #include "chromeos/services/libassistant/grpc/services_initializer_base.h"
 #include "chromeos/services/libassistant/grpc/services_status_provider.h"
 #include "third_party/grpc/src/include/grpcpp/server_builder.h"
 
+namespace assistant {
+namespace api {
+class DeviceStateEventHandlerInterface;
+class OnDeviceStateEventRequest;
+}  // namespace api
+}  // namespace assistant
+
 namespace chromeos {
 namespace libassistant {
 
@@ -40,6 +49,14 @@
   // call. Returns false if the attempt to start a gRPC server failed.
   bool Start();
 
+  // Add/Remove observer for each handler driver.
+  void AddObserver(
+      GrpcServicesObserver<::assistant::api::OnDeviceStateEventRequest>*
+          observer);
+  void RemoveObserver(
+      GrpcServicesObserver<::assistant::api::OnDeviceStateEventRequest>*
+          observer);
+
   // Expose a reference to |GrpcLibassistantClient|.
   GrpcLibassistantClient& GrpcLibassistantClient();
 
@@ -65,6 +82,8 @@
   // This should be called before Start().
   void InitAssistantGrpcServer();
 
+  void RegisterEventHandlers();
+
   // Address of assistant gRPC server.
   const std::string assistant_service_address_;
   // Address of Libassistant gRPC server.
@@ -88,6 +107,12 @@
   std::unique_ptr<chromeos::libassistant::CustomerRegistrationClient>
       customer_registration_client_;
 
+  std::unique_ptr<HeartbeatEventHandlerDriver> heartbeat_driver_;
+
+  std::unique_ptr<
+      EventHandlerDriver<::assistant::api::DeviceStateEventHandlerInterface>>
+      device_state_event_handler_driver_;
+
   base::WeakPtrFactory<GrpcServicesInitializer> weak_factory_{this};
 };
 
diff --git a/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc b/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc
index 81a76a8..4ad2a08 100644
--- a/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc
+++ b/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc
@@ -45,6 +45,7 @@
 GrpcLibassistantClient::~GrpcLibassistantClient() = default;
 
 LIBAS_GRPC_CLIENT_METHOD("CustomerRegistrationService", RegisterCustomer)
+LIBAS_GRPC_CLIENT_METHOD("EventNotificationService", RegisterEventHandler)
 
 }  // namespace libassistant
 }  // namespace chromeos
diff --git a/chromeos/services/libassistant/grpc/grpc_libassistant_client.h b/chromeos/services/libassistant/grpc/grpc_libassistant_client.h
index 81828fe9..58828e6b 100644
--- a/chromeos/services/libassistant/grpc/grpc_libassistant_client.h
+++ b/chromeos/services/libassistant/grpc/grpc_libassistant_client.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "chromeos/assistant/internal/proto/shared/proto/v2/customer_registration_interface.pb.h"
+#include "chromeos/assistant/internal/proto/shared/proto/v2/event_notification_interface.pb.h"
 #include "chromeos/services/libassistant/grpc/grpc_client_thread.h"
 #include "chromeos/services/libassistant/grpc/grpc_state.h"
 #include "chromeos/services/libassistant/grpc/grpc_util.h"
@@ -43,6 +44,9 @@
   // register themselves before allowing to use libassistant services.
   LIBAS_GRPC_CLIENT_INTERFACE(RegisterCustomer)
 
+  // EventNotificationService:
+  LIBAS_GRPC_CLIENT_INTERFACE(RegisterEventHandler)
+
  private:
   // This channel will be shared between all stubs used to communicate with
   // multiple services. All channels are reference counted and will be freed
diff --git a/chromeos/services/libassistant/grpc/services_initializer_base.cc b/chromeos/services/libassistant/grpc/services_initializer_base.cc
index e9f6cc4..d41e645 100644
--- a/chromeos/services/libassistant/grpc/services_initializer_base.cc
+++ b/chromeos/services/libassistant/grpc/services_initializer_base.cc
@@ -36,7 +36,7 @@
 
 void ServicesInitializerBase::StartCQ() {
   // Initialize completion queues for each service.
-  for (auto& driver : service_drivers_) {
+  for (auto* const driver : service_drivers_) {
     driver->StartCQ(cq_.get());
   }
   cq_thread_.task_runner()->PostTask(
diff --git a/chromeos/services/libassistant/grpc/services_initializer_base.h b/chromeos/services/libassistant/grpc/services_initializer_base.h
index 2da3964..160ea8d9 100644
--- a/chromeos/services/libassistant/grpc/services_initializer_base.h
+++ b/chromeos/services/libassistant/grpc/services_initializer_base.h
@@ -46,7 +46,10 @@
   void ScanCQInternal();
 
   std::unique_ptr<grpc::ServerCompletionQueue> cq_;
-  std::vector<std::unique_ptr<AsyncServiceDriver>> service_drivers_;
+
+  // Drivers are owned by the subclass that creates them, e.g.
+  // `GrpcServicesInitializer`.
+  std::vector<AsyncServiceDriver*> service_drivers_;
 
   // Use a dedicated thread to poll completion queue. Will also responsible
   // for cleaning up the tags returned by calling cq_->Next() after they are
diff --git a/chromeos/services/libassistant/test_support/fake_assistant_client.cc b/chromeos/services/libassistant/test_support/fake_assistant_client.cc
index b1dec15..b27a4b5f 100644
--- a/chromeos/services/libassistant/test_support/fake_assistant_client.cc
+++ b/chromeos/services/libassistant/test_support/fake_assistant_client.cc
@@ -58,7 +58,7 @@
 
 void FakeAssistantClient::ResetAllDataAndShutdown() {}
 
-void FakeAssistantClient::OnDisplayRequest(
+void FakeAssistantClient::SendDisplayRequest(
     const OnDisplayRequestRequest& request) {}
 
 void FakeAssistantClient::AddDisplayEventObserver(
diff --git a/chromeos/services/libassistant/test_support/fake_assistant_client.h b/chromeos/services/libassistant/test_support/fake_assistant_client.h
index 8866728..b683d32 100644
--- a/chromeos/services/libassistant/test_support/fake_assistant_client.h
+++ b/chromeos/services/libassistant/test_support/fake_assistant_client.h
@@ -53,7 +53,7 @@
       const GetSpeakerIdEnrollmentInfoRequest& request,
       base::OnceCallback<void(bool user_model_exists)> on_done) override;
   void ResetAllDataAndShutdown() override;
-  void OnDisplayRequest(const OnDisplayRequestRequest& request) override;
+  void SendDisplayRequest(const OnDisplayRequestRequest& request) override;
   void AddDisplayEventObserver(
       GrpcServicesObserver<OnAssistantDisplayEventRequest>* observer) override;
   void ResumeCurrentStream() override;
diff --git a/chromeos/services/secure_channel/ble_advertiser_impl_unittest.cc b/chromeos/services/secure_channel/ble_advertiser_impl_unittest.cc
index 80216e79..9a25086 100644
--- a/chromeos/services/secure_channel/ble_advertiser_impl_unittest.cc
+++ b/chromeos/services/secure_channel/ble_advertiser_impl_unittest.cc
@@ -498,7 +498,6 @@
   AddAdvertisementRequest(pair_1, ConnectionPriority::kLow);
   FakeErrorTolerantBleAdvertisement* advertisement_1 =
       GetLastCreatedAdvertisement(pair_1);
-  FakeOneShotTimer* timer_1 = GetLastCreatedTimer();
 
   AddAdvertisementRequest(pair_2, ConnectionPriority::kLow);
   FakeErrorTolerantBleAdvertisement* advertisement_2 =
@@ -525,7 +524,6 @@
   // stopping is asynchronous.
   EXPECT_EQ(2u, GetNumAdvertisementsCreated());
   EXPECT_EQ(3u, GetNumTimersCreated());
-  timer_1 = GetLastCreatedTimer();
   VerifyDelegateNotifiedOnAdvertisingSlotEnded(
       pair_1, true /* expected_replaced_by_higher_priority_advertisement */,
       0u /* expected_index */);
@@ -639,7 +637,6 @@
   EXPECT_EQ(7u, GetNumAdvertisementsCreated());
   EXPECT_EQ(8u, GetNumTimersCreated());
   EXPECT_EQ(4u, GetNumSlotEndedDelegateCallbacks());
-  timer_1 = GetLastCreatedTimer();
   advertisement_1->InvokeStopCallback();
   EXPECT_EQ(8u, GetNumAdvertisementsCreated());
   advertisement_1 = GetLastCreatedAdvertisement(pair_2);
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index ef9a354f..b53e9c3 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -47,6 +47,9 @@
 
   # b/201197372
   "crostini.AppEmacs",
+
+  # crbug.com/1259127
+  "ui.TabletOperations",
 ]
 
 # To disable a specific test in lacros_all_tast_tests, add it the following
diff --git a/components/app_restore/app_restore_arc_info.cc b/components/app_restore/app_restore_arc_info.cc
index 4ce71cc..42e97a8 100644
--- a/components/app_restore/app_restore_arc_info.cc
+++ b/components/app_restore/app_restore_arc_info.cc
@@ -43,6 +43,11 @@
     observer.OnArcConnectionChanged(is_connection_ready);
 }
 
+void AppRestoreArcInfo::NotifyPlayStoreEnabledChanged(bool enabled) {
+  for (auto& observer : observers_)
+    observer.OnArcPlayStoreEnabledChanged(enabled);
+}
+
 void AppRestoreArcInfo::NotifyTaskThemeColorUpdated(int32_t task_id,
                                                     uint32_t primary_color,
                                                     uint32_t status_bar_color) {
diff --git a/components/app_restore/app_restore_arc_info.h b/components/app_restore/app_restore_arc_info.h
index a545cd58..00da9ec 100644
--- a/components/app_restore/app_restore_arc_info.h
+++ b/components/app_restore/app_restore_arc_info.h
@@ -35,6 +35,9 @@
                                          uint32_t primary_color,
                                          uint32_t status_bar_color) {}
 
+    // Invoked when Google Play Store is enabled or disabled.
+    virtual void OnArcPlayStoreEnabledChanged(bool enabled) {}
+
    protected:
     ~Observer() override = default;
   };
@@ -54,6 +57,7 @@
                          int32_t session_id);
   void NotifyTaskDestroyed(int32_t task_id);
   void NotifyArcConnectionChanged(bool is_connection_ready);
+  void NotifyPlayStoreEnabledChanged(bool enabled);
   void NotifyTaskThemeColorUpdated(int32_t task_id,
                                    uint32_t primary_color,
                                    uint32_t status_bar_color);
diff --git a/components/app_restore/arc_save_handler.cc b/components/app_restore/arc_save_handler.cc
index 4d36c37..6dd50f2 100644
--- a/components/app_restore/arc_save_handler.cc
+++ b/components/app_restore/arc_save_handler.cc
@@ -217,6 +217,11 @@
   task_id_to_app_id_.erase(task_id);
 }
 
+void ArcSaveHandler::OnArcPlayStoreEnabledChanged(bool enabled) {
+  if (!enabled)
+    task_id_to_app_id_.clear();
+}
+
 void ArcSaveHandler::OnTaskThemeColorUpdated(int32_t task_id,
                                              uint32_t primary_color,
                                              uint32_t status_bar_color) {
diff --git a/components/app_restore/arc_save_handler.h b/components/app_restore/arc_save_handler.h
index 107a896..576c034 100644
--- a/components/app_restore/arc_save_handler.h
+++ b/components/app_restore/arc_save_handler.h
@@ -20,6 +20,12 @@
 struct WindowInfo;
 }  // namespace app_restore
 
+namespace ash {
+namespace full_restore {
+class FullRestoreAppLaunchHandlerArcAppBrowserTest;
+}
+}  // namespace ash
+
 namespace aura {
 class Window;
 }
@@ -60,6 +66,9 @@
   // Invoked when the task is destroyed for an ARC app.
   void OnTaskDestroyed(int32_t task_id);
 
+  // Invoked when Google Play Store is enabled or disabled.
+  void OnArcPlayStoreEnabledChanged(bool enabled);
+
   // Invoked when the task theme color is updated for an ARC app.
   void OnTaskThemeColorUpdated(int32_t task_id,
                                uint32_t primary_color,
@@ -77,6 +86,7 @@
 
  private:
   friend class FullRestoreSaveHandlerTestApi;
+  friend class ash::full_restore::FullRestoreAppLaunchHandlerArcAppBrowserTest;
 
   // Starts the timer to check whether a task is created for the app launching
   // (if timer isn't already running).
diff --git a/components/app_restore/full_restore_save_handler.cc b/components/app_restore/full_restore_save_handler.cc
index 71a6522..f144934 100644
--- a/components/app_restore/full_restore_save_handler.cc
+++ b/components/app_restore/full_restore_save_handler.cc
@@ -205,6 +205,45 @@
     arc_save_handler_->set_is_connection_ready(is_connection_ready);
 }
 
+void FullRestoreSaveHandler::OnArcPlayStoreEnabledChanged(bool enabled) {
+  if (enabled)
+    return;
+
+  if (arc_save_handler_)
+    arc_save_handler_->OnArcPlayStoreEnabledChanged(enabled);
+
+  auto restore_data_it =
+      profile_path_to_restore_data_.find(active_profile_path_);
+  if (restore_data_it == profile_path_to_restore_data_.end())
+    return;
+
+  auto app_registry_cache_it =
+      profile_path_to_app_registry_cache_.find(active_profile_path_);
+  if (app_registry_cache_it == profile_path_to_app_registry_cache_.end())
+    return;
+
+  // Get the ARC app list saved in the restore data.
+  const auto& launch_list = restore_data_it->second.app_id_to_launch_list();
+  std::vector<std::string> arc_app_ids;
+  for (const auto& it : launch_list) {
+    if (app_registry_cache_it->second->GetAppType(it.first) ==
+        apps::mojom::AppType::kArc) {
+      arc_app_ids.push_back(it.first);
+    }
+  }
+
+  if (arc_app_ids.empty())
+    return;
+
+  // Remove all ARC app data from the restore data.
+  for (const auto& app_id : arc_app_ids)
+    restore_data_it->second.RemoveApp(app_id);
+
+  pending_save_profile_paths_.insert(active_profile_path_);
+
+  MaybeStartSaveTimer(active_profile_path_);
+}
+
 void FullRestoreSaveHandler::OnTaskThemeColorUpdated(
     int32_t task_id,
     uint32_t primary_color,
diff --git a/components/app_restore/full_restore_save_handler.h b/components/app_restore/full_restore_save_handler.h
index 5b3e1db1..689706e 100644
--- a/components/app_restore/full_restore_save_handler.h
+++ b/components/app_restore/full_restore_save_handler.h
@@ -36,6 +36,7 @@
 namespace ash {
 namespace full_restore {
 class FullRestoreServiceTestHavingFullRestoreFile;
+class FullRestoreAppLaunchHandlerArcAppBrowserTest;
 }
 }  // namespace ash
 
@@ -93,6 +94,7 @@
                      int32_t session_id) override;
   void OnTaskDestroyed(int32_t task_id) override;
   void OnArcConnectionChanged(bool is_connection_ready) override;
+  void OnArcPlayStoreEnabledChanged(bool enabled) override;
   void OnTaskThemeColorUpdated(int32_t task_id,
                                uint32_t primary_color,
                                uint32_t status_bar_color) override;
@@ -178,6 +180,7 @@
  private:
   friend class FullRestoreSaveHandlerTestApi;
   friend class ash::full_restore::FullRestoreServiceTestHavingFullRestoreFile;
+  friend class ash::full_restore::FullRestoreAppLaunchHandlerArcAppBrowserTest;
 
   // Map from a profile path to AppLaunchInfos.
   using AppLaunchInfos = std::map<base::FilePath, std::list<AppLaunchInfoPtr>>;
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json
index 2d89c5b1..f491038 100644
--- a/components/certificate_transparency/data/log_list.json
+++ b/components/certificate_transparency/data/log_list.json
@@ -1,6 +1,6 @@
 {
-  "version": "3.58",
-  "log_list_timestamp": "2021-10-11T01:34:10Z",
+  "version": "3.59",
+  "log_list_timestamp": "2021-10-12T01:35:09Z",
   "operators": [
     {
       "name": "Google",
diff --git a/components/components_strings.grd b/components/components_strings.grd
index e414ba39..f2599de 100644
--- a/components/components_strings.grd
+++ b/components/components_strings.grd
@@ -301,6 +301,7 @@
       <part file="find_in_page_strings.grdp" />
       <part file="flags_strings.grdp" />
       <part file="fullscreen_control_strings.grdp" />
+      <part file="global_media_controls_strings.grdp" />
       <part file="heavy_ad_intervention_strings.grdp" />
       <part file="history_strings.grdp" />
       <part file="javascript_dialogs_strings.grdp" />
diff --git a/components/cronet/url_request_context_config.cc b/components/cronet/url_request_context_config.cc
index 4b00c624..8e958be3 100644
--- a/components/cronet/url_request_context_config.cc
+++ b/components/cronet/url_request_context_config.cc
@@ -431,7 +431,6 @@
           quic_args->FindBoolKey(kQuicEnableSocketRecvOptimization)
               .value_or(quic_params->enable_socket_recv_optimization);
 
-      bool quic_migrate_sessions_on_network_change_v2 = false;
       int quic_max_time_on_non_default_network_seconds = 0;
       int quic_max_migrations_to_non_default_network_on_write_error = 0;
       int quic_max_migrations_to_non_default_network_on_path_degrading = 0;
@@ -439,8 +438,6 @@
       absl::optional<bool> quic_migrate_sessions_on_network_change_v2_in =
           quic_args->FindBoolKey(kQuicMigrateSessionsOnNetworkChangeV2);
       if (quic_migrate_sessions_on_network_change_v2_in.has_value()) {
-        quic_migrate_sessions_on_network_change_v2 =
-            quic_migrate_sessions_on_network_change_v2_in.value();
         quic_params->migrate_sessions_on_network_change_v2 =
             quic_migrate_sessions_on_network_change_v2_in.value();
         if (quic_args->GetInteger(
@@ -463,13 +460,11 @@
         }
       }
 
-      bool quic_migrate_idle_sessions = false;
       int quic_idle_session_migration_period_seconds = 0;
 
       absl::optional<bool> quic_migrate_idle_sessions_in =
           quic_args->FindBoolKey(kQuicMigrateIdleSessions);
       if (quic_migrate_idle_sessions_in.has_value()) {
-        quic_migrate_idle_sessions = quic_migrate_idle_sessions_in.value();
         quic_params->migrate_idle_sessions =
             quic_migrate_idle_sessions_in.value();
         if (quic_args->GetInteger(
@@ -480,12 +475,9 @@
         }
       }
 
-      bool quic_migrate_sessions_early_v2 = false;
       absl::optional<bool> quic_migrate_sessions_early_v2_in =
           quic_args->FindBoolKey(kQuicMigrateSessionsEarlyV2);
       if (quic_migrate_sessions_early_v2_in.has_value()) {
-        quic_migrate_sessions_early_v2 =
-            quic_migrate_sessions_early_v2_in.value();
         quic_params->migrate_sessions_early_v2 =
             quic_migrate_sessions_early_v2_in.value();
       }
diff --git a/components/desks_storage/core/desk_template.cc b/components/desks_storage/core/desk_template.cc
deleted file mode 100644
index 03f15ea..0000000
--- a/components/desks_storage/core/desk_template.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/desks_storage/core/desk_template.h"
-
-#include <memory>
-
-#include "base/check.h"
-#include "base/guid.h"
-#include "base/memory/ptr_util.h"
-#include "base/strings/string_util.h"
-#include "components/sync/protocol/workspace_desk_specifics.pb.h"
-
-namespace desks_storage {
-
-namespace {
-
-// Converts a time object to the format used in sync protobufs
-// (Microseconds since the Windows epoch).
-int64_t TimeToProtoTime(const base::Time t) {
-  return t.ToDeltaSinceWindowsEpoch().InMicroseconds();
-}
-
-// Converts a time field from sync protobufs to a time object.
-base::Time ProtoTimeToTime(int64_t proto_t) {
-  return base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(proto_t));
-}
-
-}  // namespace
-
-std::unique_ptr<DeskTemplate> DeskTemplate::FromProto(
-    const sync_pb::WorkspaceDeskSpecifics& pb_entry) {
-  const std::string uuid(pb_entry.uuid());
-  if (uuid.empty())
-    return nullptr;
-
-  const base::Time created_time = ProtoTimeToTime(pb_entry.created_time_usec());
-
-  // Protobuf parsing enforces utf8 encoding for all strings.
-  return std::make_unique<DeskTemplate>(uuid, pb_entry.name(), created_time);
-}
-
-std::unique_ptr<DeskTemplate> DeskTemplate::FromRequiredFields(
-    const std::string& uuid) {
-  return !uuid.empty() ? std::make_unique<DeskTemplate>(uuid, "", base::Time())
-                       : nullptr;
-}
-
-DeskTemplate::DeskTemplate(const std::string& uuid,
-                           const std::string& name,
-                           base::Time created_time)
-    : uuid_(uuid), name_(name), created_time_(created_time) {
-  DCHECK(!uuid_.empty());
-  DCHECK(base::IsStringUTF8(uuid_));
-  DCHECK(base::IsStringUTF8(name_));
-}
-
-DeskTemplate::~DeskTemplate() = default;
-
-sync_pb::WorkspaceDeskSpecifics DeskTemplate::AsSyncProto() const {
-  sync_pb::WorkspaceDeskSpecifics pb_entry;
-
-  pb_entry.set_uuid(uuid());
-  pb_entry.set_name(name());
-  pb_entry.set_created_time_usec(TimeToProtoTime(created_time()));
-
-  // TODO(yzd) copy other data fields
-  return pb_entry;
-}
-
-}  // namespace desks_storage
\ No newline at end of file
diff --git a/components/desks_storage/core/desk_template.h b/components/desks_storage/core/desk_template.h
deleted file mode 100644
index 4bc8b56..0000000
--- a/components/desks_storage/core/desk_template.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_DESKS_STORAGE_CORE_DESK_TEMPLATE_H_
-#define COMPONENTS_DESKS_STORAGE_CORE_DESK_TEMPLATE_H_
-
-#include <string>
-
-#include "base/time/time.h"
-
-namespace sync_pb {
-class WorkspaceDeskSpecifics;
-}
-
-namespace desks_storage {
-
-// A desk template being saved. The UUID is a unique identifier for a
-// template. This class is a temporary placeholder. This could be replaced
-// by future ash::DeskTemplate when it is ready.
-//
-// TODO(crbug/1225727): remove this class.
-class DeskTemplate {
- public:
-  // Creates a DeskTemplate from the protobuf format.
-  static std::unique_ptr<DeskTemplate> FromProto(
-      const sync_pb::WorkspaceDeskSpecifics& pb_entry);
-
-  // Creates a DeskTemplate consisting of only the required fields.
-  static std::unique_ptr<DeskTemplate> FromRequiredFields(
-      const std::string& uuid);
-
-  // Creates a DeskTemplate.
-  DeskTemplate(const std::string& uuid,
-               const std::string& name,
-               base::Time created_time);
-  DeskTemplate(const DeskTemplate&) = delete;
-  DeskTemplate& operator=(const DeskTemplate&) = delete;
-  ~DeskTemplate();
-
-  const std::string& uuid() const { return uuid_; }
-  const std::string& name() const { return name_; }
-  base::Time created_time() const { return created_time_; }
-
-  // Returns a protobuf encoding the content of this DeskTemplate for
-  // Sync.
-  sync_pb::WorkspaceDeskSpecifics AsSyncProto() const;
-
- private:
-  // The unique random id for the entry.
-  std::string uuid_;
-  // The name of the desk template. Might be empty.
-  std::string name_;
-  // The time that the desk template was created.
-  base::Time created_time_;
-};
-
-}  // namespace desks_storage
-
-#endif  // COMPONENTS_DESKS_STORAGE_CORE_DESK_TEMPLATE_H_
\ No newline at end of file
diff --git a/components/desks_storage/core/local_desk_data_manager.cc b/components/desks_storage/core/local_desk_data_manager.cc
index 9471107..0822082 100644
--- a/components/desks_storage/core/local_desk_data_manager.cc
+++ b/components/desks_storage/core/local_desk_data_manager.cc
@@ -17,7 +17,6 @@
 #include "base/values.h"
 #include "components/app_restore/restore_data.h"
 #include "components/desks_storage/core/desk_model.h"
-#include "components/desks_storage/core/desk_template.h"
 #include "components/sync/protocol/workspace_desk_specifics.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/re2/src/re2/re2.h"
diff --git a/components/desks_storage/core/local_desks_data_manager_unittests.cc b/components/desks_storage/core/local_desks_data_manager_unittests.cc
index 6120ad4..46487d75 100644
--- a/components/desks_storage/core/local_desks_data_manager_unittests.cc
+++ b/components/desks_storage/core/local_desks_data_manager_unittests.cc
@@ -20,7 +20,6 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "components/desks_storage/core/desk_template.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace desks_storage {
diff --git a/components/discardable_memory/client/client_discardable_shared_memory_manager.cc b/components/discardable_memory/client/client_discardable_shared_memory_manager.cc
index 8f2aed0..7dcb204 100644
--- a/components/discardable_memory/client/client_discardable_shared_memory_manager.cc
+++ b/components/discardable_memory/client/client_discardable_shared_memory_manager.cc
@@ -357,7 +357,7 @@
             reinterpret_cast<size_t>(leftover->shared_memory()->memory()),
         leftover->length() * base::GetPageSize());
     leftover->set_is_locked(false);
-    heap_->MergeIntoFreeListsClean(std::move(leftover));
+    MergeIntoFreeListsClean(std::move(leftover));
   }
 
   if (pages >= allocation_pages) {
@@ -459,7 +459,7 @@
       auto span = mem->Purge(now - min_age);
       if (span) {
         allocated_memory_.erase(prev);
-        ReleaseSpan(std::move(span));
+        ReleaseSpanLocked(std::move(span));
       }
     }
   }
@@ -540,12 +540,54 @@
   if (!span->shared_memory())
     return;
 
-  heap_->MergeIntoFreeLists(std::move(span));
+  MergeIntoFreeLists(std::move(span));
 
   // Bytes of free memory changed.
   MemoryUsageChanged(heap_->GetSize(), heap_->GetFreelistSize());
 }
 
+void ClientDiscardableSharedMemoryManager::ReleaseSpanLocked(
+    std::unique_ptr<DiscardableSharedMemoryHeap::Span> span) {
+  DCHECK(span);
+
+  // Delete span instead of merging it into free lists if memory is gone.
+  if (!span->shared_memory())
+    return;
+
+  MergeIntoFreeListsLocked(std::move(span));
+
+  // Bytes of free memory changed.
+  MemoryUsageChanged(heap_->GetSize(), heap_->GetFreelistSize());
+}
+
+void ClientDiscardableSharedMemoryManager::MergeIntoFreeLists(
+    std::unique_ptr<DiscardableSharedMemoryHeap::Span> span) {
+  if (ReleaseDiscardableFreeListPages()) {
+    // We release the memory _without_ the lock held here to avoid performance
+    // issues on Windows, as releasing memory is a potentially expensive
+    // operation there.
+    lock_.Release();
+    DiscardableSharedMemoryHeap::ReleaseMemoryIfPossible(span.get());
+    lock_.Acquire();
+  }
+
+  heap_->MergeIntoFreeLists(std::move(span));
+}
+
+void ClientDiscardableSharedMemoryManager::MergeIntoFreeListsLocked(
+    std::unique_ptr<DiscardableSharedMemoryHeap::Span> span) {
+  if (ReleaseDiscardableFreeListPages()) {
+    DiscardableSharedMemoryHeap::ReleaseMemoryIfPossible(span.get());
+  }
+
+  heap_->MergeIntoFreeLists(std::move(span));
+}
+
+void ClientDiscardableSharedMemoryManager::MergeIntoFreeListsClean(
+    std::unique_ptr<DiscardableSharedMemoryHeap::Span> span) {
+  heap_->MergeIntoFreeListsClean(std::move(span));
+}
+
 base::trace_event::MemoryAllocatorDump*
 ClientDiscardableSharedMemoryManager::CreateMemoryAllocatorDump(
     DiscardableSharedMemoryHeap::Span* span,
diff --git a/components/discardable_memory/client/client_discardable_shared_memory_manager.h b/components/discardable_memory/client/client_discardable_shared_memory_manager.h
index 055c8f66..e8a1f83 100644
--- a/components/discardable_memory/client/client_discardable_shared_memory_manager.h
+++ b/components/discardable_memory/client/client_discardable_shared_memory_manager.h
@@ -185,6 +185,19 @@
       EXCLUSIVE_LOCKS_REQUIRED(lock_);
   void ReleaseSpan(std::unique_ptr<DiscardableSharedMemoryHeap::Span> span)
       EXCLUSIVE_LOCKS_REQUIRED(lock_);
+  void ReleaseSpanLocked(
+      std::unique_ptr<DiscardableSharedMemoryHeap::Span> span)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
+
+  void MergeIntoFreeListsClean(
+      std::unique_ptr<DiscardableSharedMemoryHeap::Span> span)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
+  void MergeIntoFreeLists(
+      std::unique_ptr<DiscardableSharedMemoryHeap::Span> span)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
+  void MergeIntoFreeListsLocked(
+      std::unique_ptr<DiscardableSharedMemoryHeap::Span> span)
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
   size_t GetBytesAllocatedLocked() const EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
diff --git a/components/discardable_memory/client/client_discardable_shared_memory_manager_unittest.cc b/components/discardable_memory/client/client_discardable_shared_memory_manager_unittest.cc
index 32bb6a1..bc71ac8 100644
--- a/components/discardable_memory/client/client_discardable_shared_memory_manager_unittest.cc
+++ b/components/discardable_memory/client/client_discardable_shared_memory_manager_unittest.cc
@@ -533,5 +533,41 @@
   ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
 }
 
+TEST_F(ClientDiscardableSharedMemoryManagerTest, PurgeMultipleTimes) {
+  base::test::ScopedFeatureList fl;
+  fl.InitAndEnableFeature(discardable_memory::kReleaseDiscardableFreeListPages);
+  auto client =
+      base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
+
+  auto mem1 =
+      client->AllocateLockedDiscardableMemory(base::GetPageSize() * 1.2);
+
+  task_env_.FastForwardBy(base::TimeDelta::FromSeconds(60));
+
+  auto mem2 =
+      client->AllocateLockedDiscardableMemory(base::GetPageSize() * 2.2);
+
+  EXPECT_TRUE(client->IsPurgeScheduled());
+
+  client->ReleaseFreeMemory();
+  EXPECT_TRUE(client->IsPurgeScheduled());
+
+  task_env_.FastForwardBy(
+      ClientDiscardableSharedMemoryManager::kScheduledPurgeInterval);
+
+  task_env_.FastForwardBy(base::TimeDelta::FromSeconds(60));
+
+  EXPECT_TRUE(client->IsPurgeScheduled());
+
+  client->ReleaseFreeMemory();
+
+  task_env_.FastForwardBy(
+      ClientDiscardableSharedMemoryManager::kScheduledPurgeInterval);
+
+  EXPECT_TRUE(client->IsPurgeScheduled());
+
+  client->ReleaseFreeMemory();
+}
+
 }  // namespace
 }  // namespace discardable_memory
diff --git a/components/discardable_memory/common/discardable_shared_memory_heap.cc b/components/discardable_memory/common/discardable_shared_memory_heap.cc
index 9ab396a..ed90f3f 100644
--- a/components/discardable_memory/common/discardable_shared_memory_heap.cc
+++ b/components/discardable_memory/common/discardable_shared_memory_heap.cc
@@ -26,6 +26,10 @@
 const base::Feature kReleaseDiscardableFreeListPages{
     "ReleaseDiscardableFreeListPages", base::FEATURE_DISABLED_BY_DEFAULT};
 
+bool ReleaseDiscardableFreeListPages() {
+  return base::FeatureList::IsEnabled(kReleaseDiscardableFreeListPages);
+}
+
 namespace {
 
 bool IsInFreeList(DiscardableSharedMemoryHeap::Span* span) {
@@ -188,11 +192,34 @@
   return span;
 }
 
+// static
+void DiscardableSharedMemoryHeap::ReleaseMemoryIfPossible(Span* span) {
+  SCOPED_UMA_HISTOGRAM_TIMER_MICROS("Memory.Discardable.FreeListReleaseTime");
+  // Release as much memory as possible before putting it into the freelists
+  // in order to reduce their size. Getting this memory back is still much
+  // cheaper than an IPC, while also saving us space in the freelists.
+  //
+  // The "+ 1" in the offset is for the SharedState that's at the start of
+  // the DiscardableSharedMemory. See DiscardableSharedMemory for details on
+  // what this is used for. We don't want to remove it, so we offset by an
+  // extra page.
+  size_t offset = (1 + span->start_) * base::GetPageSize() -
+                  reinterpret_cast<size_t>(span->shared_memory()->memory());
+  // Since we always offset by at least one page because of the SharedState,
+  // our offset should never be 0.
+  DCHECK_GT(offset, 0u);
+  span->shared_memory()->ReleaseMemoryIfPossible(
+      offset, span->length_ * base::GetPageSize());
+}
+
 void DiscardableSharedMemoryHeap::MergeIntoFreeLists(
     std::unique_ptr<Span> span) {
+  DCHECK(span->shared_memory_);
+
   if (!base::FeatureList::IsEnabled(kReleaseDiscardableFreeListPages)) {
     dirty_freed_memory_page_count_ += span->MarkAsDirty();
   }
+
   MergeIntoFreeListsClean(std::move(span));
 }
 
@@ -200,25 +227,6 @@
     std::unique_ptr<Span> span) {
   DCHECK(span->shared_memory_);
 
-  if (base::FeatureList::IsEnabled(kReleaseDiscardableFreeListPages)) {
-    SCOPED_UMA_HISTOGRAM_TIMER_MICROS("Memory.Discardable.FreeListReleaseTime");
-    // Release as much memory as possible before putting it into the freelists
-    // in order to reduce their size. Getting this memory back is still much
-    // cheaper than an IPC, while also saving us space in the freelists.
-    //
-    // The "+ 1" in the offset is for the SharedState that's at the start of
-    // the DiscardableSharedMemory. See DiscardableSharedMemory for details on
-    // what this is used for. We don't want to remove it, so we offset by an
-    // extra page.
-    size_t offset = (1 + span->start_) * base::GetPageSize() -
-                    reinterpret_cast<size_t>(span->shared_memory()->memory());
-    // Since we always offset by at least one page because of the SharedState,
-    // our offset should never be 0.
-    DCHECK_GT(offset, 0u);
-    span->shared_memory()->ReleaseMemoryIfPossible(
-        offset, span->length_ * base::GetPageSize());
-  }
-
   // First add length of |span| to |num_free_blocks_|.
   num_free_blocks_ += span->length_;
 
diff --git a/components/discardable_memory/common/discardable_shared_memory_heap.h b/components/discardable_memory/common/discardable_shared_memory_heap.h
index 5fbbbc2..90ffba2 100644
--- a/components/discardable_memory/common/discardable_shared_memory_heap.h
+++ b/components/discardable_memory/common/discardable_shared_memory_heap.h
@@ -29,6 +29,8 @@
 DISCARDABLE_MEMORY_EXPORT extern const base::Feature
     kReleaseDiscardableFreeListPages;
 
+DISCARDABLE_MEMORY_EXPORT extern bool ReleaseDiscardableFreeListPages();
+
 // Implements a heap of discardable shared memory. An array of free lists
 // is used to keep track of free blocks.
 class DISCARDABLE_MEMORY_EXPORT DiscardableSharedMemoryHeap {
@@ -90,6 +92,8 @@
       int32_t id,
       base::OnceClosure deleted_callback);
 
+  static void ReleaseMemoryIfPossible(Span* span);
+
   // Merge |span| into the free lists. This will coalesce |span| with
   // neighboring free spans when possible.
   void MergeIntoFreeLists(std::unique_ptr<Span> span);
diff --git a/components/enterprise/browser/reporting/browser_report_generator.cc b/components/enterprise/browser/reporting/browser_report_generator.cc
index 328d936..91116a7 100644
--- a/components/enterprise/browser/reporting/browser_report_generator.cc
+++ b/components/enterprise/browser/reporting/browser_report_generator.cc
@@ -23,17 +23,9 @@
 
 BrowserReportGenerator::~BrowserReportGenerator() = default;
 
-void BrowserReportGenerator::Generate(ReportType report_type,
-                                      ReportCallback callback) {
+void BrowserReportGenerator::Generate(ReportCallback callback) {
   auto report = std::make_unique<em::BrowserReport>();
-  GenerateProfileInfo(report_type, report.get());
-  delegate_->OnProfileInfoGenerated(report_type);
-
-  if (report_type == ReportType::kExtensionRequest) {
-    report->set_executable_path(delegate_->GetExecutablePath());
-    std::move(callback).Run(std::move(report));
-    return;
-  }
+  GenerateProfileInfo(report.get());
   GenerateBasicInfo(report.get());
 
   // std::move is required here because the function completes the report
@@ -41,9 +33,8 @@
   delegate_->GeneratePluginsIfNeeded(std::move(callback), std::move(report));
 }
 
-void BrowserReportGenerator::GenerateProfileInfo(ReportType report_type,
-                                                 em::BrowserReport* report) {
-  for (auto entry : delegate_->GetReportedProfiles(report_type)) {
+void BrowserReportGenerator::GenerateProfileInfo(em::BrowserReport* report) {
+  for (auto entry : delegate_->GetReportedProfiles()) {
     em::ChromeUserProfileInfo* profile =
         report->add_chrome_user_profile_infos();
     profile->set_id(entry.id);
diff --git a/components/enterprise/browser/reporting/browser_report_generator.h b/components/enterprise/browser/reporting/browser_report_generator.h
index cd94c4d..c0b8e280 100644
--- a/components/enterprise/browser/reporting/browser_report_generator.h
+++ b/components/enterprise/browser/reporting/browser_report_generator.h
@@ -10,7 +10,6 @@
 #include <vector>
 
 #include "base/callback_forward.h"
-#include "components/enterprise/browser/reporting/report_type.h"
 
 namespace enterprise_management {
 class BrowserReport;
@@ -44,15 +43,13 @@
 
     virtual std::string GetExecutablePath() = 0;
     virtual version_info::Channel GetChannel() = 0;
-    virtual std::vector<ReportedProfileData> GetReportedProfiles(
-        ReportType report_type) = 0;
+    virtual std::vector<ReportedProfileData> GetReportedProfiles() = 0;
     virtual bool IsExtendedStableChannel() = 0;
     virtual void GenerateBuildStateInfo(
         enterprise_management::BrowserReport* report) = 0;
     virtual void GeneratePluginsIfNeeded(
         ReportCallback callback,
         std::unique_ptr<enterprise_management::BrowserReport> report) = 0;
-    virtual void OnProfileInfoGenerated(ReportType report_type) = 0;
   };
 
   explicit BrowserReportGenerator(ReportingDelegateFactory* delegate_factory);
@@ -64,11 +61,10 @@
   // - browser_version, channel, executable_path
   // - user profiles: id, name, is_detail_available (always be false).
   // - plugins: name, version, filename, description.
-  void Generate(ReportType report_type, ReportCallback callback);
+  void Generate(ReportCallback callback);
 
   // Generates user profiles info in the given report instance.
-  void GenerateProfileInfo(ReportType report_type,
-                           enterprise_management::BrowserReport* report);
+  void GenerateProfileInfo(enterprise_management::BrowserReport* report);
 
  private:
   std::unique_ptr<Delegate> delegate_;
diff --git a/components/enterprise/browser/reporting/profile_report_generator.cc b/components/enterprise/browser/reporting/profile_report_generator.cc
index 4488f9a..9fe3a4a 100644
--- a/components/enterprise/browser/reporting/profile_report_generator.cc
+++ b/components/enterprise/browser/reporting/profile_report_generator.cc
@@ -32,8 +32,7 @@
 
 std::unique_ptr<em::ChromeUserProfileInfo>
 ProfileReportGenerator::MaybeGenerate(const base::FilePath& path,
-                                      const std::string& name,
-                                      ReportType report_type) {
+                                      const std::string& name) {
   if (!delegate_->Init(path)) {
     return nullptr;
   }
@@ -41,35 +40,25 @@
   report_ = std::make_unique<em::ChromeUserProfileInfo>();
   report_->set_id(path.AsUTF8Unsafe());
 
-  if (report_type == ReportType::kExtensionRequest) {
-    delegate_->GetExtensionRequest(report_.get());
-    report_->set_is_detail_available(true);
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    // Extension request is aggregated at the user level on CrOS.
-    report_->set_name(name);
-    delegate_->GetSigninUserInfo(report_.get());
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-  } else {
-    report_->set_name(name);
-    report_->set_is_detail_available(true);
+  report_->set_name(name);
+  report_->set_is_detail_available(true);
 
-    delegate_->GetSigninUserInfo(report_.get());
-    if (extensions_enabled_) {
-      delegate_->GetExtensionInfo(report_.get());
-    }
-    delegate_->GetExtensionRequest(report_.get());
+  delegate_->GetSigninUserInfo(report_.get());
+  if (extensions_enabled_) {
+    delegate_->GetExtensionInfo(report_.get());
+  }
+  delegate_->GetExtensionRequest(report_.get());
 
-    if (policies_enabled_) {
-      // TODO(crbug.com/983151): Upload policy error as their IDs.
-      auto client = delegate_->MakePolicyConversionsClient();
-      policies_ = policy::DictionaryPolicyConversions(std::move(client))
-                      .EnableConvertTypes(false)
-                      .EnablePrettyPrint(false)
-                      .ToValue();
-      GetChromePolicyInfo();
-      GetExtensionPolicyInfo();
-      GetPolicyFetchTimestampInfo();
-    }
+  if (policies_enabled_) {
+    // TODO(crbug.com/983151): Upload policy error as their IDs.
+    auto client = delegate_->MakePolicyConversionsClient();
+    policies_ = policy::DictionaryPolicyConversions(std::move(client))
+                    .EnableConvertTypes(false)
+                    .EnablePrettyPrint(false)
+                    .ToValue();
+    GetChromePolicyInfo();
+    GetExtensionPolicyInfo();
+    GetPolicyFetchTimestampInfo();
   }
 
   return std::move(report_);
diff --git a/components/enterprise/browser/reporting/profile_report_generator.h b/components/enterprise/browser/reporting/profile_report_generator.h
index fe86556..c24607d 100644
--- a/components/enterprise/browser/reporting/profile_report_generator.h
+++ b/components/enterprise/browser/reporting/profile_report_generator.h
@@ -10,7 +10,6 @@
 
 #include "base/values.h"
 #include "components/enterprise/browser/reporting/report_request_definition.h"
-#include "components/enterprise/browser/reporting/report_type.h"
 #include "components/policy/core/browser/policy_conversions_client.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 
@@ -74,8 +73,7 @@
   // generated.
   std::unique_ptr<enterprise_management::ChromeUserProfileInfo> MaybeGenerate(
       const base::FilePath& path,
-      const std::string& name,
-      ReportType report_type);
+      const std::string& name);
 
  protected:
   void GetChromePolicyInfo();
diff --git a/components/enterprise/browser/reporting/report_generator.cc b/components/enterprise/browser/reporting/report_generator.cc
index be5cf9d..4128360 100644
--- a/components/enterprise/browser/reporting/report_generator.cc
+++ b/components/enterprise/browser/reporting/report_generator.cc
@@ -43,13 +43,6 @@
     std::unique_ptr<ReportRequest> basic_request,
     ReportType report_type,
     ReportCallback callback) {
-  if (report_type == kExtensionRequest) {
-    basic_request->add_partial_report_types(
-        em::PartialReportType::EXTENSION_REQUEST);
-    GenerateReport(report_type, std::move(callback), std::move(basic_request));
-    return;
-  }
-
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   delegate_->SetAndroidAppInfos(basic_request.get());
 #else
@@ -107,7 +100,6 @@
     ReportCallback callback,
     std::unique_ptr<ReportRequest> basic_request) {
   browser_report_generator_.Generate(
-      report_type,
       base::BindOnce(&ReportGenerator::OnBrowserReportReady,
                      weak_ptr_factory_.GetWeakPtr(), std::move(basic_request),
                      report_type, std::move(callback)));
@@ -123,7 +115,7 @@
   if (report_type != kBrowserVersion) {
     // Generate a queue of requests containing detailed profile information.
     std::move(callback).Run(
-        report_request_queue_generator_.Generate(report_type, *basic_request));
+        report_request_queue_generator_.Generate(*basic_request));
     return;
   }
 
diff --git a/components/enterprise/browser/reporting/report_request_queue_generator.cc b/components/enterprise/browser/reporting/report_request_queue_generator.cc
index 3614627..82a1958 100644
--- a/components/enterprise/browser/reporting/report_request_queue_generator.cc
+++ b/components/enterprise/browser/reporting/report_request_queue_generator.cc
@@ -54,8 +54,7 @@
 }
 
 ReportRequestQueueGenerator::ReportRequests
-ReportRequestQueueGenerator::Generate(ReportType report_type,
-                                      const ReportRequest& basic_request) {
+ReportRequestQueueGenerator::Generate(const ReportRequest& basic_request) {
   ReportRequests requests;
   size_t basic_request_size = basic_request.ByteSizeLong();
   base::UmaHistogramMemoryKB(kBasicRequestSizeMetricsName,
@@ -66,8 +65,7 @@
     int profile_infos_size =
         basic_request.browser_report().chrome_user_profile_infos_size();
     for (int index = 0; index < profile_infos_size; index++) {
-      GenerateProfileReportWithIndex(index, report_type, basic_request,
-                                     &requests);
+      GenerateProfileReportWithIndex(index, basic_request, &requests);
     }
 
     base::UmaHistogramMemoryKB(kRequestSizeMetricsName,
@@ -81,7 +79,6 @@
 
 void ReportRequestQueueGenerator::GenerateProfileReportWithIndex(
     int profile_index,
-    ReportType report_type,
     const ReportRequest& basic_request,
     ReportRequests* requests) {
   DCHECK_LT(profile_index,
@@ -91,8 +88,7 @@
   auto basic_profile =
       basic_request.browser_report().chrome_user_profile_infos(profile_index);
   auto profile_report = profile_report_generator_.MaybeGenerate(
-      base::FilePath::FromUTF8Unsafe(basic_profile.id()), basic_profile.name(),
-      report_type);
+      base::FilePath::FromUTF8Unsafe(basic_profile.id()), basic_profile.name());
 
   // Return if Profile is not loaded and there is no full report.
   if (!profile_report)
diff --git a/components/enterprise/browser/reporting/report_request_queue_generator.h b/components/enterprise/browser/reporting/report_request_queue_generator.h
index 4dcb92af..2b458a7 100644
--- a/components/enterprise/browser/reporting/report_request_queue_generator.h
+++ b/components/enterprise/browser/reporting/report_request_queue_generator.h
@@ -12,7 +12,6 @@
 #include "build/build_config.h"
 #include "components/enterprise/browser/reporting/profile_report_generator.h"
 #include "components/enterprise/browser/reporting/report_request_definition.h"
-#include "components/enterprise/browser/reporting/report_type.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 
 namespace enterprise_reporting {
@@ -44,14 +43,12 @@
 
   // Generate a queue of requests including full profile info based on given
   // basic request.
-  ReportRequests Generate(ReportType report_type,
-                          const ReportRequest& basic_request);
+  ReportRequests Generate(const ReportRequest& basic_request);
 
  private:
   // Generate request with full profile info at |profile_index| according to
   // |basic_request|, then store it into |requests|.
   void GenerateProfileReportWithIndex(int profile_index,
-                                      ReportType report_type,
                                       const ReportRequest& basic_request,
                                       ReportRequests* requests);
 
diff --git a/components/enterprise/browser/reporting/report_type.h b/components/enterprise/browser/reporting/report_type.h
index f244dc6..d1498d4 100644
--- a/components/enterprise/browser/reporting/report_type.h
+++ b/components/enterprise/browser/reporting/report_type.h
@@ -10,7 +10,6 @@
 enum ReportType : uint32_t {
   kFull = 0,
   kBrowserVersion = 1u << 0,
-  kExtensionRequest = 2u << 1,
 };
 
 }  // namespace enterprise_reporting
diff --git a/components/feature_engagement/public/feature_configurations.cc b/components/feature_engagement/public/feature_configurations.cc
index e1e6b2e1..452e9582 100644
--- a/components/feature_engagement/public/feature_configurations.cc
+++ b/components/feature_engagement/public/feature_configurations.cc
@@ -478,6 +478,7 @@
     config->event_configs.insert(
         EventConfig("auto_dark_user_education_message_trigger",
                     Comparator(LESS_THAN, 6), 360, 360));
+    return config;
   }
 
   if (kIPHInstanceSwitcherFeature.name == feature->name) {
diff --git a/components/global_media_controls/BUILD.gn b/components/global_media_controls/BUILD.gn
index 5c377e8..d2763db7 100644
--- a/components/global_media_controls/BUILD.gn
+++ b/components/global_media_controls/BUILD.gn
@@ -4,6 +4,7 @@
 
 component("global_media_controls") {
   public = [
+    "public/constants.h",
     "public/media_dialog_delegate.h",
     "public/media_item_manager.h",
     "public/media_item_manager_observer.h",
@@ -11,24 +12,36 @@
     "public/media_item_ui.h",
     "public/media_item_ui_observer.h",
     "public/media_item_ui_observer_set.h",
+    "public/views/media_item_ui_device_selector.h",
+    "public/views/media_item_ui_footer.h",
+    "public/views/media_item_ui_list_view.h",
+    "public/views/media_item_ui_view.h",
   ]
 
   defines = [ "IS_GLOBAL_MEDIA_CONTROLS_IMPL" ]
 
   deps = [
     "//base",
+    "//components/strings:components_strings_grit",
+    "//components/vector_icons",
+    "//media",
     "//skia",
+    "//ui/color",
+    "//ui/message_center/public/cpp",
   ]
 
   public_deps = [
     "//components/media_message_center",
     "//services/media_session/public/cpp",
+    "//ui/views",
   ]
 
   sources = [
     "media_item_manager_impl.cc",
     "media_item_manager_impl.h",
     "public/media_item_ui_observer_set.cc",
+    "public/views/media_item_ui_list_view.cc",
+    "public/views/media_item_ui_view.cc",
   ]
 
   friend = [ ":*" ]
@@ -37,15 +50,23 @@
 source_set("unit_tests") {
   testonly = true
 
-  sources = [ "media_item_manager_impl_unittest.cc" ]
+  sources = [
+    "media_item_manager_impl_unittest.cc",
+    "public/views/media_item_ui_list_view_unittest.cc",
+    "public/views/media_item_ui_view_unittest.cc",
+  ]
 
   deps = [
     ":global_media_controls",
     ":test_support",
     "//base/test:test_support",
+    "//components/media_message_center:test_support",
     "//skia",
     "//testing/gmock",
     "//testing/gtest",
+    "//ui/display:test_support",
+    "//ui/events:test_support",
+    "//ui/views:test_support",
   ]
 }
 
@@ -61,12 +82,17 @@
     "public/test/mock_media_item_manager_observer.h",
     "public/test/mock_media_item_producer.cc",
     "public/test/mock_media_item_producer.h",
+    "public/test/mock_media_item_ui_device_selector.cc",
+    "public/test/mock_media_item_ui_device_selector.h",
+    "public/test/mock_media_item_ui_footer.cc",
+    "public/test/mock_media_item_ui_footer.h",
     "public/test/mock_media_item_ui_observer.cc",
     "public/test/mock_media_item_ui_observer.h",
   ]
 
   deps = [
     ":global_media_controls",
+    "//components/media_message_center:test_support",
     "//skia",
     "//testing/gmock",
   ]
diff --git a/components/global_media_controls/DEPS b/components/global_media_controls/DEPS
index c6626ef..9cd9e75 100644
--- a/components/global_media_controls/DEPS
+++ b/components/global_media_controls/DEPS
@@ -1,4 +1,9 @@
 include_rules = [
   "+components/media_message_center",
+  "+components/strings",
+  "+components/vector_icons",
+  "+media",
   "+services/media_session/public",
+  "+third_party/skia/include/core",
+  "+ui",
 ]
diff --git a/chrome/browser/ui/views/global_media_controls/global_media_controls_types.h b/components/global_media_controls/public/constants.h
similarity index 70%
rename from chrome/browser/ui/views/global_media_controls/global_media_controls_types.h
rename to components/global_media_controls/public/constants.h
index 8e28d29..fe13dce 100644
--- a/chrome/browser/ui/views/global_media_controls/global_media_controls_types.h
+++ b/components/global_media_controls/public/constants.h
@@ -2,8 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_GLOBAL_MEDIA_CONTROLS_TYPES_H_
-#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_GLOBAL_MEDIA_CONTROLS_TYPES_H_
+#ifndef COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_CONSTANTS_H_
+#define COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_CONSTANTS_H_
+
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace global_media_controls {
+
+constexpr SkColor kDefaultForegroundColor = SK_ColorBLACK;
+
+constexpr SkColor kDefaultBackgroundColor = SK_ColorTRANSPARENT;
 
 // The entry point through which the dialog was opened.
 // These values are persisted to logs. Entries should not be renumbered and
@@ -31,4 +39,6 @@
   kMaxValue = kStopViaSystemTray,
 };
 
-#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_GLOBAL_MEDIA_CONTROLS_TYPES_H_
+}  // namespace global_media_controls
+
+#endif  // COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_CONSTANTS_H_
diff --git a/components/global_media_controls/public/media_item_ui_observer.h b/components/global_media_controls/public/media_item_ui_observer.h
index f2559c1..109a09b 100644
--- a/components/global_media_controls/public/media_item_ui_observer.h
+++ b/components/global_media_controls/public/media_item_ui_observer.h
@@ -31,10 +31,6 @@
   // Called when the item UI is about to be deleted.
   virtual void OnMediaItemUIDestroyed(const std::string& id) {}
 
-  // Called when the audio output device for the item UI should change.
-  virtual void OnAudioSinkChosen(const std::string& id,
-                                 const std::string& sink_id) {}
-
  protected:
   ~MediaItemUIObserver() override = default;
 };
diff --git a/components/global_media_controls/public/media_item_ui_observer_set.cc b/components/global_media_controls/public/media_item_ui_observer_set.cc
index 272b1980..c825976 100644
--- a/components/global_media_controls/public/media_item_ui_observer_set.cc
+++ b/components/global_media_controls/public/media_item_ui_observer_set.cc
@@ -53,9 +53,4 @@
   StopObserving(id);
 }
 
-void MediaItemUIObserverSet::OnAudioSinkChosen(const std::string& id,
-                                               const std::string& sink_id) {
-  owner_->OnAudioSinkChosen(id, sink_id);
-}
-
 }  // namespace global_media_controls
diff --git a/components/global_media_controls/public/media_item_ui_observer_set.h b/components/global_media_controls/public/media_item_ui_observer_set.h
index ca9ab4a..d7e047f 100644
--- a/components/global_media_controls/public/media_item_ui_observer_set.h
+++ b/components/global_media_controls/public/media_item_ui_observer_set.h
@@ -32,8 +32,6 @@
   void OnMediaItemUIClicked(const std::string& id) override;
   void OnMediaItemUIDismissed(const std::string& id) override;
   void OnMediaItemUIDestroyed(const std::string& id) override;
-  void OnAudioSinkChosen(const std::string& id,
-                         const std::string& sink_id) override;
 
  private:
   MediaItemUIObserver* const owner_;
diff --git a/components/global_media_controls/public/test/mock_media_item_producer.cc b/components/global_media_controls/public/test/mock_media_item_producer.cc
index 376aa38..276ccd5 100644
--- a/components/global_media_controls/public/test/mock_media_item_producer.cc
+++ b/components/global_media_controls/public/test/mock_media_item_producer.cc
@@ -4,42 +4,11 @@
 
 #include "components/global_media_controls/public/test/mock_media_item_producer.h"
 
-#include "components/media_message_center/media_notification_item.h"
+#include "components/media_message_center/mock_media_notification_item.h"
 
 namespace global_media_controls {
 namespace test {
 
-namespace {
-
-class MockMediaNotificationItem
-    : public media_message_center::MediaNotificationItem {
- public:
-  MockMediaNotificationItem() = default;
-  MockMediaNotificationItem(const MockMediaNotificationItem&) = delete;
-  MockMediaNotificationItem& operator=(const MockMediaNotificationItem&) =
-      delete;
-  ~MockMediaNotificationItem() override = default;
-
-  MOCK_METHOD(void, SetView, (media_message_center::MediaNotificationView*));
-  MOCK_METHOD(void,
-              OnMediaSessionActionButtonPressed,
-              (media_session::mojom::MediaSessionAction));
-  MOCK_METHOD(void, SeekTo, (base::TimeDelta));
-  MOCK_METHOD(void, Dismiss, ());
-  MOCK_METHOD(void, SetVolume, (float));
-  MOCK_METHOD(void, SetMute, (bool));
-  MOCK_METHOD(media_message_center::SourceType, SourceType, ());
-
-  base::WeakPtr<MockMediaNotificationItem> GetWeakPtr() {
-    return weak_ptr_factory_.GetWeakPtr();
-  }
-
- private:
-  base::WeakPtrFactory<MockMediaNotificationItem> weak_ptr_factory_{this};
-};
-
-}  // namespace
-
 struct MockMediaItemProducer::Item {
  public:
   Item(const std::string& item_id,
@@ -58,7 +27,7 @@
   bool active;
   bool frozen;
   bool playing;
-  MockMediaNotificationItem item;
+  media_message_center::test::MockMediaNotificationItem item;
 };
 
 MockMediaItemProducer::MockMediaItemProducer() = default;
diff --git a/components/global_media_controls/public/test/mock_media_item_ui_device_selector.cc b/components/global_media_controls/public/test/mock_media_item_ui_device_selector.cc
new file mode 100644
index 0000000..03fac12
--- /dev/null
+++ b/components/global_media_controls/public/test/mock_media_item_ui_device_selector.cc
@@ -0,0 +1,15 @@
+// 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/global_media_controls/public/test/mock_media_item_ui_device_selector.h"
+
+namespace global_media_controls {
+namespace test {
+
+MockMediaItemUIDeviceSelector::MockMediaItemUIDeviceSelector() = default;
+
+MockMediaItemUIDeviceSelector::~MockMediaItemUIDeviceSelector() = default;
+
+}  // namespace test
+}  // namespace global_media_controls
diff --git a/components/global_media_controls/public/test/mock_media_item_ui_device_selector.h b/components/global_media_controls/public/test/mock_media_item_ui_device_selector.h
new file mode 100644
index 0000000..d471b6b
--- /dev/null
+++ b/components/global_media_controls/public/test/mock_media_item_ui_device_selector.h
@@ -0,0 +1,31 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_TEST_MOCK_MEDIA_ITEM_UI_DEVICE_SELECTOR_H_
+#define COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_TEST_MOCK_MEDIA_ITEM_UI_DEVICE_SELECTOR_H_
+
+#include "components/global_media_controls/public/views/media_item_ui_device_selector.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace global_media_controls {
+namespace test {
+
+class MockMediaItemUIDeviceSelector : public MediaItemUIDeviceSelector {
+ public:
+  MockMediaItemUIDeviceSelector();
+  MockMediaItemUIDeviceSelector(const MockMediaItemUIDeviceSelector&) = delete;
+  MockMediaItemUIDeviceSelector& operator=(
+      const MockMediaItemUIDeviceSelector&) = delete;
+  ~MockMediaItemUIDeviceSelector() override;
+
+  // MediaItemUIDeviceSelector:
+  MOCK_METHOD(void, SetMediaItemUIView, (MediaItemUIView*));
+  MOCK_METHOD(void, OnColorsChanged, (SkColor, SkColor));
+  MOCK_METHOD(void, UpdateCurrentAudioDevice, (const std::string&));
+};
+
+}  // namespace test
+}  // namespace global_media_controls
+
+#endif  // COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_TEST_MOCK_MEDIA_ITEM_UI_DEVICE_SELECTOR_H_
diff --git a/components/global_media_controls/public/test/mock_media_item_ui_footer.cc b/components/global_media_controls/public/test/mock_media_item_ui_footer.cc
new file mode 100644
index 0000000..478b7a3
--- /dev/null
+++ b/components/global_media_controls/public/test/mock_media_item_ui_footer.cc
@@ -0,0 +1,15 @@
+// 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/global_media_controls/public/test/mock_media_item_ui_footer.h"
+
+namespace global_media_controls {
+namespace test {
+
+MockMediaItemUIFooter::MockMediaItemUIFooter() = default;
+
+MockMediaItemUIFooter::~MockMediaItemUIFooter() = default;
+
+}  // namespace test
+}  // namespace global_media_controls
diff --git a/components/global_media_controls/public/test/mock_media_item_ui_footer.h b/components/global_media_controls/public/test/mock_media_item_ui_footer.h
new file mode 100644
index 0000000..b86bdcf
--- /dev/null
+++ b/components/global_media_controls/public/test/mock_media_item_ui_footer.h
@@ -0,0 +1,28 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_TEST_MOCK_MEDIA_ITEM_UI_FOOTER_H_
+#define COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_TEST_MOCK_MEDIA_ITEM_UI_FOOTER_H_
+
+#include "components/global_media_controls/public/views/media_item_ui_footer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace global_media_controls {
+namespace test {
+
+class MockMediaItemUIFooter : public MediaItemUIFooter {
+ public:
+  MockMediaItemUIFooter();
+  MockMediaItemUIFooter(const MockMediaItemUIFooter&) = delete;
+  MockMediaItemUIFooter& operator=(const MockMediaItemUIFooter&) = delete;
+  ~MockMediaItemUIFooter() override;
+
+  // MediaItemUIFooter:
+  MOCK_METHOD(void, OnColorsChanged, (SkColor, SkColor));
+};
+
+}  // namespace test
+}  // namespace global_media_controls
+
+#endif  // COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_TEST_MOCK_MEDIA_ITEM_UI_FOOTER_H_
diff --git a/components/global_media_controls/public/test/mock_media_item_ui_observer.h b/components/global_media_controls/public/test/mock_media_item_ui_observer.h
index 9dabeca..75df9be0 100644
--- a/components/global_media_controls/public/test/mock_media_item_ui_observer.h
+++ b/components/global_media_controls/public/test/mock_media_item_ui_observer.h
@@ -24,9 +24,6 @@
   MOCK_METHOD(void, OnMediaItemUIClicked, (const std::string&));
   MOCK_METHOD(void, OnMediaItemUIDismissed, (const std::string&));
   MOCK_METHOD(void, OnMediaItemUIDestroyed, (const std::string&));
-  MOCK_METHOD(void,
-              OnAudioSinkChosen,
-              (const std::string&, const std::string&));
 };
 
 }  // namespace test
diff --git a/components/global_media_controls/public/views/media_item_ui_device_selector.h b/components/global_media_controls/public/views/media_item_ui_device_selector.h
new file mode 100644
index 0000000..ebc04db
--- /dev/null
+++ b/components/global_media_controls/public/views/media_item_ui_device_selector.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 COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_DEVICE_SELECTOR_H_
+#define COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_DEVICE_SELECTOR_H_
+
+#include "ui/views/view.h"
+
+namespace global_media_controls {
+
+class MediaItemUIView;
+
+// A MediaItemUIDeviceSelector is a views::View that should be inserted into the
+// bottom of the MediaItemUI which contains and expandable list of devices to
+// connect to (audio/Cast/etc).
+class MediaItemUIDeviceSelector : public views::View {
+ public:
+  // Gives the device selector a pointer to the MediaItemUIView so that it can
+  // inform it of size changes.
+  virtual void SetMediaItemUIView(MediaItemUIView* view) = 0;
+
+  // Called when the color theme has changed.
+  virtual void OnColorsChanged(SkColor foreground, SkColor background) = 0;
+
+  // Called when an audio device switch has occurred
+  virtual void UpdateCurrentAudioDevice(
+      const std::string& current_device_id) = 0;
+};
+
+}  // namespace global_media_controls
+
+#endif  // COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_DEVICE_SELECTOR_H_
diff --git a/components/global_media_controls/public/views/media_item_ui_footer.h b/components/global_media_controls/public/views/media_item_ui_footer.h
new file mode 100644
index 0000000..3ca8373
--- /dev/null
+++ b/components/global_media_controls/public/views/media_item_ui_footer.h
@@ -0,0 +1,23 @@
+// 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_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_FOOTER_H_
+#define COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_FOOTER_H_
+
+#include "ui/views/view.h"
+
+namespace global_media_controls {
+
+// A MediaItemUIFooter is a type of views::View that can be inserted at the
+// bottom of a MediaItemUI. Users of global media controls can create views that
+// extend this class that will be inserted into the MediaItemUI and receive
+// color updates.
+class MediaItemUIFooter : public views::View {
+ public:
+  virtual void OnColorsChanged(SkColor foreground, SkColor background) = 0;
+};
+
+}  // namespace global_media_controls
+
+#endif  // COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_FOOTER_H_
diff --git a/components/global_media_controls/public/views/media_item_ui_list_view.cc b/components/global_media_controls/public/views/media_item_ui_list_view.cc
new file mode 100644
index 0000000..0e95602
--- /dev/null
+++ b/components/global_media_controls/public/views/media_item_ui_list_view.cc
@@ -0,0 +1,109 @@
+// Copyright 2019 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/global_media_controls/public/views/media_item_ui_list_view.h"
+
+#include "base/containers/contains.h"
+#include "components/global_media_controls/public/views/media_item_ui_view.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/color/color_id.h"
+#include "ui/color/color_provider.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace global_media_controls {
+
+namespace {
+
+constexpr int kMediaListMaxHeight = 478;
+
+// Thickness of separator border.
+constexpr int kMediaListSeparatorThickness = 2;
+
+std::unique_ptr<views::Border> CreateMediaListSeparatorBorder(SkColor color,
+                                                              int thickness) {
+  return views::CreateSolidSidedBorder(/*top=*/thickness,
+                                       /*left=*/0,
+                                       /*bottom=*/0,
+                                       /*right=*/0, color);
+}
+
+}  // anonymous namespace
+
+MediaItemUIListView::SeparatorStyle::SeparatorStyle(SkColor separator_color,
+                                                    int separator_thickness)
+    : separator_color(separator_color),
+      separator_thickness(separator_thickness) {}
+
+MediaItemUIListView::MediaItemUIListView()
+    : MediaItemUIListView(absl::nullopt) {}
+
+MediaItemUIListView::MediaItemUIListView(
+    const absl::optional<SeparatorStyle>& separator_style)
+    : separator_style_(separator_style) {
+  SetBackgroundColor(absl::nullopt);
+  SetContents(std::make_unique<views::View>());
+  contents()->SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical));
+  ClipHeightTo(0, kMediaListMaxHeight);
+
+  SetVerticalScrollBar(
+      std::make_unique<views::OverlayScrollBar>(/*horizontal=*/false));
+  SetHorizontalScrollBar(
+      std::make_unique<views::OverlayScrollBar>(/*horizontal=*/true));
+}
+
+MediaItemUIListView::~MediaItemUIListView() = default;
+
+void MediaItemUIListView::ShowItem(const std::string& id,
+                                   std::unique_ptr<MediaItemUIView> item) {
+  DCHECK(!base::Contains(items_, id));
+  DCHECK_NE(nullptr, item.get());
+
+  // If this isn't the first item, then create a top-sided separator
+  // border.
+  if (!items_.empty()) {
+    if (separator_style_.has_value()) {
+      item->SetBorder(CreateMediaListSeparatorBorder(
+          separator_style_->separator_color,
+          separator_style_->separator_thickness));
+    } else {
+      item->SetBorder(CreateMediaListSeparatorBorder(
+          GetColorProvider()->GetColor(ui::kColorMenuSeparator),
+          kMediaListSeparatorThickness));
+    }
+  }
+
+  items_[id] = contents()->AddChildView(std::move(item));
+
+  contents()->InvalidateLayout();
+  PreferredSizeChanged();
+}
+
+void MediaItemUIListView::HideItem(const std::string& id) {
+  if (!base::Contains(items_, id))
+    return;
+
+  // If we're removing the topmost item and there are others, then we need to
+  // remove the top-sided separator border from the new topmost item.
+  if (contents()->children().size() > 1 &&
+      contents()->children().at(0) == items_[id]) {
+    contents()->children().at(1)->SetBorder(nullptr);
+  }
+
+  // Remove the item. Note that since |RemoveChildView()| does not delete the
+  // item, we now have ownership.
+  contents()->RemoveChildView(items_[id]);
+  delete items_[id];
+  items_.erase(id);
+
+  contents()->InvalidateLayout();
+  PreferredSizeChanged();
+}
+
+BEGIN_METADATA(MediaItemUIListView, views::ScrollView)
+END_METADATA
+
+}  // namespace global_media_controls
diff --git a/components/global_media_controls/public/views/media_item_ui_list_view.h b/components/global_media_controls/public/views/media_item_ui_list_view.h
new file mode 100644
index 0000000..0dd5dbb3
--- /dev/null
+++ b/components/global_media_controls/public/views/media_item_ui_list_view.h
@@ -0,0 +1,61 @@
+// Copyright 2019 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_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_LIST_VIEW_H_
+#define COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_LIST_VIEW_H_
+
+#include <map>
+#include <memory>
+
+#include "base/component_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/controls/scroll_view.h"
+
+namespace global_media_controls {
+
+class MediaItemUIView;
+
+// MediaItemUIListView is a container that holds a list of MediaItemUIViews and
+// handles adding/removing separators and creating a scrollable view.
+class COMPONENT_EXPORT(GLOBAL_MEDIA_CONTROLS) MediaItemUIListView
+    : public views::ScrollView {
+ public:
+  METADATA_HEADER(MediaItemUIListView);
+  struct SeparatorStyle {
+    SeparatorStyle(SkColor separator_color, int separator_thickness);
+
+    const SkColor separator_color;
+    const int separator_thickness;
+  };
+
+  explicit MediaItemUIListView(
+      const absl::optional<SeparatorStyle>& separator_style);
+  MediaItemUIListView();
+  MediaItemUIListView(const MediaItemUIListView&) = delete;
+  MediaItemUIListView& operator=(const MediaItemUIListView&) = delete;
+  ~MediaItemUIListView() override;
+
+  // Adds the given item into the list.
+  void ShowItem(const std::string& id, std::unique_ptr<MediaItemUIView> item);
+
+  // Removes the given item from the list.
+  void HideItem(const std::string& id);
+
+  bool empty() { return items_.empty(); }
+
+  const std::map<const std::string, MediaItemUIView*>& items_for_testing()
+      const {
+    return items_;
+  }
+
+ private:
+  std::map<const std::string, MediaItemUIView*> items_;
+
+  absl::optional<SeparatorStyle> separator_style_;
+};
+
+}  // namespace global_media_controls
+
+#endif  // COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_LIST_VIEW_H_
diff --git a/components/global_media_controls/public/views/media_item_ui_list_view_unittest.cc b/components/global_media_controls/public/views/media_item_ui_list_view_unittest.cc
new file mode 100644
index 0000000..2a5fa30
--- /dev/null
+++ b/components/global_media_controls/public/views/media_item_ui_list_view_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright 2019 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/global_media_controls/public/views/media_item_ui_list_view.h"
+
+#include <memory>
+#include <string>
+
+#include "components/global_media_controls/public/views/media_item_ui_view.h"
+#include "components/media_message_center/mock_media_notification_item.h"
+#include "ui/views/test/views_test_base.h"
+
+using testing::NiceMock;
+
+namespace global_media_controls {
+
+namespace {
+
+// Test IDs for items.
+const char kTestItemId1[] = "testid1";
+const char kTestItemId2[] = "testid2";
+const char kTestItemId3[] = "testid3";
+
+}  // anonymous namespace
+
+class MediaItemUIListViewTest : public views::ViewsTestBase {
+ public:
+  MediaItemUIListViewTest() = default;
+  MediaItemUIListViewTest(const MediaItemUIListViewTest&) = delete;
+  MediaItemUIListViewTest& operator=(const MediaItemUIListViewTest&) = delete;
+  ~MediaItemUIListViewTest() override = default;
+
+  // views::ViewsTestBase:
+  void SetUp() override {
+    views::ViewsTestBase::SetUp();
+
+    widget_ = CreateTestWidget();
+
+    list_view_ =
+        widget_->SetContentsView(std::make_unique<MediaItemUIListView>());
+
+    item_ = std::make_unique<
+        NiceMock<media_message_center::test::MockMediaNotificationItem>>();
+    widget_->Show();
+  }
+
+  void TearDown() override {
+    widget_.reset();
+    views::ViewsTestBase::TearDown();
+  }
+
+  void ShowItem(const std::string& id) {
+    list_view_->ShowItem(id, std::make_unique<MediaItemUIView>(
+                                 id, item_->GetWeakPtr(), nullptr, nullptr));
+  }
+
+  void HideItem(const std::string& id) { list_view_->HideItem(id); }
+
+  MediaItemUIListView* list_view() { return list_view_; }
+
+ private:
+  std::unique_ptr<views::Widget> widget_;
+  MediaItemUIListView* list_view_ = nullptr;
+  std::unique_ptr<media_message_center::test::MockMediaNotificationItem> item_;
+};
+
+TEST_F(MediaItemUIListViewTest, NoSeparatorForOneItem) {
+  // Show a single item.
+  ShowItem(kTestItemId1);
+
+  // There should be just one item.
+  EXPECT_EQ(1u, list_view()->items_for_testing().size());
+
+  // Since there's only one, there should be no separator line.
+  EXPECT_EQ(nullptr,
+            list_view()->items_for_testing().at(kTestItemId1)->GetBorder());
+}
+
+TEST_F(MediaItemUIListViewTest, SeparatorBetweenItems) {
+  // Show two items.
+  ShowItem(kTestItemId1);
+  ShowItem(kTestItemId2);
+
+  // There should be two items.
+  EXPECT_EQ(2u, list_view()->items_for_testing().size());
+
+  // There should be a separator between them. Since the separators are
+  // top-sided, the bottom item should have one.
+  EXPECT_EQ(nullptr,
+            list_view()->items_for_testing().at(kTestItemId1)->GetBorder());
+  EXPECT_NE(nullptr,
+            list_view()->items_for_testing().at(kTestItemId2)->GetBorder());
+}
+
+TEST_F(MediaItemUIListViewTest, SeparatorRemovedWhenItemRemoved) {
+  // Show three items.
+  ShowItem(kTestItemId1);
+  ShowItem(kTestItemId2);
+  ShowItem(kTestItemId3);
+
+  // There should be three items.
+  EXPECT_EQ(3u, list_view()->items_for_testing().size());
+
+  // There should be separators.
+  EXPECT_EQ(nullptr,
+            list_view()->items_for_testing().at(kTestItemId1)->GetBorder());
+  EXPECT_NE(nullptr,
+            list_view()->items_for_testing().at(kTestItemId2)->GetBorder());
+  EXPECT_NE(nullptr,
+            list_view()->items_for_testing().at(kTestItemId3)->GetBorder());
+
+  // Remove the topmost item.
+  HideItem(kTestItemId1);
+
+  // There should be two items.
+  EXPECT_EQ(2u, list_view()->items_for_testing().size());
+
+  // The new top item should have lost its top separator.
+  EXPECT_EQ(nullptr,
+            list_view()->items_for_testing().at(kTestItemId2)->GetBorder());
+  EXPECT_NE(nullptr,
+            list_view()->items_for_testing().at(kTestItemId3)->GetBorder());
+}
+
+}  // namespace global_media_controls
diff --git a/components/global_media_controls/public/views/media_item_ui_view.cc b/components/global_media_controls/public/views/media_item_ui_view.cc
new file mode 100644
index 0000000..bdf2ff16
--- /dev/null
+++ b/components/global_media_controls/public/views/media_item_ui_view.cc
@@ -0,0 +1,379 @@
+// Copyright 2019 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/global_media_controls/public/views/media_item_ui_view.h"
+
+#include "base/bind.h"
+#include "base/containers/contains.h"
+#include "base/feature_list.h"
+#include "components/global_media_controls/public/constants.h"
+#include "components/global_media_controls/public/media_item_manager.h"
+#include "components/global_media_controls/public/media_item_ui_observer.h"
+#include "components/global_media_controls/public/views/media_item_ui_device_selector.h"
+#include "components/global_media_controls/public/views/media_item_ui_footer.h"
+#include "components/media_message_center/media_notification_item.h"
+#include "components/media_message_center/media_notification_view_modern_impl.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/vector_icons/vector_icons.h"
+#include "media/audio/audio_device_description.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/compositor/layer.h"
+#include "ui/message_center/public/cpp/message_center_constants.h"
+#include "ui/views/animation/ink_drop.h"
+#include "ui/views/animation/slide_out_controller.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/button/image_button_factory.h"
+#include "ui/views/controls/highlight_path_generator.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
+
+namespace global_media_controls {
+
+namespace {
+
+constexpr int kWidth = 400;
+constexpr int kModernUIWidth = 350;
+constexpr gfx::Size kNormalSize = gfx::Size(kWidth, 100);
+constexpr gfx::Size kExpandedSize = gfx::Size(kWidth, 150);
+constexpr gfx::Size kModernUISize = gfx::Size(kModernUIWidth, 168);
+constexpr gfx::Size kDismissButtonSize = gfx::Size(30, 30);
+constexpr int kDismissButtonIconSize = 20;
+constexpr int kDismissButtonBackgroundRadius = 15;
+constexpr gfx::Size kCrOSDismissButtonSize = gfx::Size(20, 20);
+constexpr int kCrOSDismissButtonIconSize = 12;
+constexpr gfx::Size kModernDismissButtonSize = gfx::Size(14, 14);
+constexpr int kModernDismissButtonIconSize = 10;
+
+// The minimum number of enabled and visible user actions such that we should
+// force the MediaNotificationView to be expanded.
+constexpr int kMinVisibleActionsForExpanding = 4;
+
+}  // anonymous namespace
+
+class MediaItemUIView::DismissButton : public views::ImageButton {
+ public:
+  METADATA_HEADER(DismissButton);
+
+  explicit DismissButton(PressedCallback callback)
+      : views::ImageButton(std::move(callback)) {
+    views::ConfigureVectorImageButton(this);
+    views::InstallFixedSizeCircleHighlightPathGenerator(
+        this, kDismissButtonBackgroundRadius);
+    SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
+  }
+  DismissButton(const DismissButton&) = delete;
+  DismissButton& operator=(const DismissButton&) = delete;
+  ~DismissButton() override = default;
+};
+
+BEGIN_METADATA(MediaItemUIView, DismissButton, views::ImageButton)
+END_METADATA
+
+MediaItemUIView::MediaItemUIView(
+    const std::string& id,
+    base::WeakPtr<media_message_center::MediaNotificationItem> item,
+    std::unique_ptr<MediaItemUIFooter> footer_view,
+    std::unique_ptr<MediaItemUIDeviceSelector> device_selector_view,
+    absl::optional<media_message_center::NotificationTheme> theme)
+    : views::Button(base::BindRepeating(&MediaItemUIView::ContainerClicked,
+                                        base::Unretained(this))),
+      id_(id),
+      footer_view_(footer_view.get()),
+      foreground_color_(kDefaultForegroundColor),
+      background_color_(kDefaultBackgroundColor),
+      is_cros_(theme.has_value()) {
+  DCHECK(item);
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical));
+  SetPreferredSize(kNormalSize);
+  SetNotifyEnterExitOnChild(true);
+  SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
+  SetTooltipText(
+      l10n_util::GetStringUTF16(IDS_GLOBAL_MEDIA_CONTROLS_BACK_TO_TAB));
+
+  auto swipeable_container = std::make_unique<views::View>();
+  swipeable_container->SetLayoutManager(std::make_unique<views::FillLayout>());
+  swipeable_container->SetPaintToLayer();
+  swipeable_container->layer()->SetFillsBoundsOpaquely(false);
+  swipeable_container_ = AddChildView(std::move(swipeable_container));
+
+  gfx::Size dismiss_button_size =
+      is_cros_ ? kCrOSDismissButtonSize : kDismissButtonSize;
+  if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI))
+    dismiss_button_size = kModernDismissButtonSize;
+
+  auto dismiss_button_placeholder = std::make_unique<views::View>();
+  dismiss_button_placeholder->SetPreferredSize(dismiss_button_size);
+  dismiss_button_placeholder->SetLayoutManager(
+      std::make_unique<views::FillLayout>());
+  dismiss_button_placeholder_ = dismiss_button_placeholder.get();
+
+  auto dismiss_button_container = std::make_unique<views::View>();
+  dismiss_button_container->SetPreferredSize(dismiss_button_size);
+  dismiss_button_container->SetLayoutManager(
+      std::make_unique<views::FillLayout>());
+  dismiss_button_container->SetVisible(false);
+  dismiss_button_container_ = dismiss_button_placeholder_->AddChildView(
+      std::move(dismiss_button_container));
+
+  auto dismiss_button = std::make_unique<DismissButton>(base::BindRepeating(
+      &MediaItemUIView::DismissNotification, base::Unretained(this)));
+  dismiss_button->SetPreferredSize(dismiss_button_size);
+  dismiss_button->SetTooltipText(l10n_util::GetStringUTF16(
+      IDS_GLOBAL_MEDIA_CONTROLS_DISMISS_ICON_TOOLTIP_TEXT));
+  dismiss_button_ =
+      dismiss_button_container_->AddChildView(std::move(dismiss_button));
+  UpdateDismissButtonIcon();
+
+  std::unique_ptr<media_message_center::MediaNotificationView> view;
+  if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI)) {
+    view =
+        std::make_unique<media_message_center::MediaNotificationViewModernImpl>(
+            this, std::move(item), std::move(dismiss_button_placeholder),
+            std::move(footer_view), kModernUIWidth);
+    SetPreferredSize(kModernUISize);
+  } else {
+    view = std::make_unique<media_message_center::MediaNotificationViewImpl>(
+        this, std::move(item), std::move(dismiss_button_placeholder),
+        std::u16string(), kWidth, /*should_show_icon=*/false, theme);
+
+    if (footer_view)
+      AddChildView(std::move(footer_view));
+
+    SetPreferredSize(kNormalSize);
+  }
+  view_ = swipeable_container_->AddChildView(std::move(view));
+
+  if (device_selector_view) {
+    device_selector_view_ = device_selector_view.get();
+    device_selector_view_->SetMediaItemUIView(this);
+    AddChildView(std::move(device_selector_view));
+    view_->UpdateCornerRadius(message_center::kNotificationCornerRadius, 0);
+  }
+
+  ForceExpandedState();
+
+  slide_out_controller_ =
+      std::make_unique<views::SlideOutController>(this, this);
+}
+
+MediaItemUIView::~MediaItemUIView() {
+  for (auto& observer : observers_)
+    observer.OnMediaItemUIDestroyed(id_);
+}
+
+void MediaItemUIView::AddedToWidget() {
+  if (GetFocusManager())
+    GetFocusManager()->AddFocusChangeListener(this);
+}
+
+void MediaItemUIView::RemovedFromWidget() {
+  if (GetFocusManager())
+    GetFocusManager()->RemoveFocusChangeListener(this);
+}
+
+void MediaItemUIView::OnMouseEntered(const ui::MouseEvent& event) {
+  UpdateDismissButtonVisibility();
+}
+
+void MediaItemUIView::OnMouseExited(const ui::MouseEvent& event) {
+  UpdateDismissButtonVisibility();
+}
+
+void MediaItemUIView::OnDidChangeFocus(views::View* focused_before,
+                                       views::View* focused_now) {
+  UpdateDismissButtonVisibility();
+}
+
+void MediaItemUIView::OnExpanded(bool expanded) {
+  is_expanded_ = expanded;
+  OnSizeChanged();
+}
+
+void MediaItemUIView::OnMediaSessionInfoChanged(
+    const media_session::mojom::MediaSessionInfoPtr& session_info) {
+  is_playing_ =
+      session_info && session_info->playback_state ==
+                          media_session::mojom::MediaPlaybackState::kPlaying;
+  if (session_info) {
+    auto audio_sink_id = session_info->audio_sink_id.value_or(
+        media::AudioDeviceDescription::kDefaultDeviceId);
+    if (device_selector_view_ &&
+        base::FeatureList::IsEnabled(
+            media::kGlobalMediaControlsSeamlessTransfer)) {
+      device_selector_view_->UpdateCurrentAudioDevice(audio_sink_id);
+    }
+  }
+}
+
+void MediaItemUIView::OnMediaSessionMetadataChanged(
+    const media_session::MediaMetadata& metadata) {
+  title_ = metadata.title;
+
+  for (auto& observer : observers_)
+    observer.OnMediaItemUIMetadataChanged();
+}
+
+void MediaItemUIView::OnVisibleActionsChanged(
+    const base::flat_set<media_session::mojom::MediaSessionAction>& actions) {
+  has_many_actions_ =
+      (actions.size() >= kMinVisibleActionsForExpanding ||
+       base::Contains(
+           actions,
+           media_session::mojom::MediaSessionAction::kEnterPictureInPicture) ||
+       base::Contains(
+           actions,
+           media_session::mojom::MediaSessionAction::kExitPictureInPicture));
+  ForceExpandedState();
+
+  for (auto& observer : observers_)
+    observer.OnMediaItemUIActionsChanged();
+}
+
+void MediaItemUIView::OnMediaArtworkChanged(const gfx::ImageSkia& image) {
+  has_artwork_ = !image.isNull();
+
+  UpdateDismissButtonBackground();
+  ForceExpandedState();
+}
+
+void MediaItemUIView::OnColorsChanged(SkColor foreground, SkColor background) {
+  if (foreground_color_ != foreground) {
+    foreground_color_ = foreground;
+    UpdateDismissButtonIcon();
+  }
+
+  if (background_color_ != background) {
+    background_color_ = background;
+    UpdateDismissButtonBackground();
+  }
+  if (footer_view_)
+    footer_view_->OnColorsChanged(foreground, background);
+
+  if (device_selector_view_)
+    device_selector_view_->OnColorsChanged(foreground, background);
+}
+
+void MediaItemUIView::OnHeaderClicked() {
+  // Since we disable the expand button, nothing happens on the
+  // MediaNotificationView when the header is clicked. Treat the click as if we
+  // were clicked directly.
+  ContainerClicked();
+}
+
+void MediaItemUIView::OnDeviceSelectorViewSizeChanged() {
+  OnSizeChanged();
+}
+
+ui::Layer* MediaItemUIView::GetSlideOutLayer() {
+  return swipeable_container_->layer();
+}
+
+void MediaItemUIView::OnSlideOut() {
+  DismissNotification();
+}
+
+void MediaItemUIView::AddObserver(
+    global_media_controls::MediaItemUIObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void MediaItemUIView::RemoveObserver(
+    global_media_controls::MediaItemUIObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+const std::u16string& MediaItemUIView::GetTitle() const {
+  return title_;
+}
+
+views::ImageButton* MediaItemUIView::GetDismissButtonForTesting() {
+  return dismiss_button_;
+}
+
+void MediaItemUIView::UpdateDismissButtonIcon() {
+  int icon_size =
+      is_cros_ ? kCrOSDismissButtonIconSize : kDismissButtonIconSize;
+  if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI))
+    icon_size = kModernDismissButtonIconSize;
+
+  views::SetImageFromVectorIconWithColor(dismiss_button_,
+                                         vector_icons::kCloseRoundedIcon,
+                                         icon_size, foreground_color_);
+}
+
+void MediaItemUIView::UpdateDismissButtonBackground() {
+  if (!has_artwork_) {
+    dismiss_button_container_->SetBackground(nullptr);
+    return;
+  }
+
+  dismiss_button_container_->SetBackground(views::CreateRoundedRectBackground(
+      background_color_, kDismissButtonBackgroundRadius));
+}
+
+void MediaItemUIView::UpdateDismissButtonVisibility() {
+  bool has_focus = false;
+  if (GetFocusManager()) {
+    views::View* focused_view = GetFocusManager()->GetFocusedView();
+    if (focused_view)
+      has_focus = Contains(focused_view);
+  }
+
+  dismiss_button_container_->SetVisible(IsMouseHovered() || has_focus);
+}
+
+void MediaItemUIView::DismissNotification() {
+  for (auto& observer : observers_)
+    observer.OnMediaItemUIDismissed(id_);
+}
+
+void MediaItemUIView::ForceExpandedState() {
+  if (view_) {
+    bool should_expand = has_many_actions_ || has_artwork_;
+    view_->SetForcedExpandedState(&should_expand);
+  }
+}
+
+void MediaItemUIView::ContainerClicked() {
+  for (auto& observer : observers_)
+    observer.OnMediaItemUIClicked(id_);
+}
+
+void MediaItemUIView::OnSizeChanged() {
+  gfx::Size new_size;
+  if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI)) {
+    new_size = kModernUISize;
+  } else {
+    new_size = is_expanded_ ? kExpandedSize : kNormalSize;
+  }
+
+  // |new_size| does not contain the height for the device selector view.
+  // If this view is present, we should query it for its preferred height and
+  // include that in |new_size|.
+  if (device_selector_view_) {
+    auto device_selector_view_size = device_selector_view_->GetPreferredSize();
+    DCHECK(device_selector_view_size.width() == kWidth);
+    new_size.set_height(new_size.height() + device_selector_view_size.height());
+    view_->UpdateDeviceSelectorAvailability(
+        device_selector_view_->GetVisible());
+  }
+
+  SetPreferredSize(new_size);
+  PreferredSizeChanged();
+
+  for (auto& observer : observers_)
+    observer.OnMediaItemUISizeChanged();
+}
+
+BEGIN_METADATA(MediaItemUIView, views::Button)
+ADD_READONLY_PROPERTY_METADATA(std::u16string, Title)
+END_METADATA
+
+}  // namespace global_media_controls
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h b/components/global_media_controls/public/views/media_item_ui_view.h
similarity index 62%
rename from chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h
rename to components/global_media_controls/public/views/media_item_ui_view.h
index 8fe19aff..f41ddc69 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h
+++ b/components/global_media_controls/public/views/media_item_ui_view.h
@@ -2,71 +2,60 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_VIEW_H_
+#ifndef COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_VIEW_H_
+#define COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_VIEW_H_
 
 #include <string>
 
+#include "base/component_export.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
-#include "chrome/browser/ui/views/global_media_controls/global_media_controls_types.h"
-#include "chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_delegate.h"
+#include "components/global_media_controls/public/constants.h"
 #include "components/global_media_controls/public/media_item_ui.h"
+#include "components/global_media_controls/public/views/media_item_ui_device_selector.h"
+#include "components/global_media_controls/public/views/media_item_ui_footer.h"
 #include "components/media_message_center/media_notification_container.h"
 #include "components/media_message_center/media_notification_view_impl.h"
-#include "media/audio/audio_device_description.h"
 #include "media/base/media_switches.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/animation/slide_out_controller_delegate.h"
 #include "ui/views/focus/focus_manager.h"
-#include "ui/views/widget/unique_widget_ptr.h"
-
-namespace global_media_controls {
-class MediaItemUIObserver;
-}  // namespace global_media_controls
 
 namespace media_message_center {
 class MediaNotificationItem;
 }  // namespace media_message_center
 
 namespace views {
-class LabelButton;
 class ImageButton;
 class SlideOutController;
 }  // namespace views
 
-class CastMediaNotificationItem;
-class MediaNotificationDeviceSelectorView;
-class MediaNotificationFooterView;
-class MediaNotificationService;
-class Profile;
+namespace global_media_controls {
 
-// MediaNotificationContainerImplView holds a media notification for display
+class MediaItemUIObserver;
+
+// MediaItemUIView holds a media notification for display
 // within the MediaDialogView. The media notification shows metadata for a media
 // session and can control playback.
-class MediaNotificationContainerImplView
+class COMPONENT_EXPORT(GLOBAL_MEDIA_CONTROLS) MediaItemUIView
     : public views::Button,
       public media_message_center::MediaNotificationContainer,
       public global_media_controls::MediaItemUI,
-      public MediaNotificationDeviceSelectorViewDelegate,
       public views::SlideOutControllerDelegate,
       public views::FocusChangeListener {
  public:
-  METADATA_HEADER(MediaNotificationContainerImplView);
+  METADATA_HEADER(MediaItemUIView);
 
-  MediaNotificationContainerImplView(
+  MediaItemUIView(
       const std::string& id,
       base::WeakPtr<media_message_center::MediaNotificationItem> item,
-      MediaNotificationService* service,
-      GlobalMediaControlsEntryPoint entry_point,
-      Profile* profile,
+      std::unique_ptr<MediaItemUIFooter> footer_view,
+      std::unique_ptr<MediaItemUIDeviceSelector> device_selector_view,
       absl::optional<media_message_center::NotificationTheme> theme =
           absl::nullopt);
-  MediaNotificationContainerImplView(
-      const MediaNotificationContainerImplView&) = delete;
-  MediaNotificationContainerImplView& operator=(
-      const MediaNotificationContainerImplView&) = delete;
-  ~MediaNotificationContainerImplView() override;
+  MediaItemUIView(const MediaItemUIView&) = delete;
+  MediaItemUIView& operator=(const MediaItemUIView&) = delete;
+  ~MediaItemUIView() override;
 
   // views::Button:
   void AddedToWidget() override;
@@ -105,27 +94,17 @@
   void RemoveObserver(
       global_media_controls::MediaItemUIObserver* observer) override;
 
-  // MediaNotificationDeviceSelectorViewDelegate
-  // Called when an audio device has been selected for output.
-  void OnAudioSinkChosen(const std::string& sink_id) override;
-  void OnDeviceSelectorViewSizeChanged() override;
-  base::CallbackListSubscription RegisterAudioOutputDeviceDescriptionsCallback(
-      MediaNotificationDeviceProvider::GetOutputDevicesCallbackList::
-          CallbackType callback) override;
-  base::CallbackListSubscription
-  RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
-      base::RepeatingCallback<void(bool)> callback) override;
+  void OnDeviceSelectorViewSizeChanged();
 
   const std::u16string& GetTitle() const;
 
   views::ImageButton* GetDismissButtonForTesting();
-  views::Button* GetStopCastingButtonForTesting();
 
   media_message_center::MediaNotificationViewImpl* view_for_testing() {
     DCHECK(!base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI));
     return static_cast<media_message_center::MediaNotificationViewImpl*>(view_);
   }
-  MediaNotificationDeviceSelectorView* device_selector_view_for_testing() {
+  MediaItemUIDeviceSelector* device_selector_view_for_testing() {
     return device_selector_view_;
   }
 
@@ -135,12 +114,6 @@
  private:
   class DismissButton;
 
-  void AddStopCastButton(CastMediaNotificationItem* item);
-  void AddDeviceSelectorView(bool is_local_media_session,
-                             bool show_expand_button);
-  void StopCasting(CastMediaNotificationItem* item);
-  void UpdateStopCastButtonIcon();
-  void UpdateStopCastButtonBackground();
   void UpdateDismissButtonIcon();
   void UpdateDismissButtonBackground();
   void UpdateDismissButtonVisibility();
@@ -167,12 +140,10 @@
 
   DismissButton* dismiss_button_ = nullptr;
   media_message_center::MediaNotificationView* view_ = nullptr;
-  MediaNotificationDeviceSelectorView* device_selector_view_ = nullptr;
-  MediaNotificationFooterView* footer_view_ = nullptr;
 
-  // Only shows up for cast notifications.
-  views::View* stop_button_strip_ = nullptr;
-  views::LabelButton* stop_cast_button_ = nullptr;
+  MediaItemUIFooter* const footer_view_;
+
+  MediaItemUIDeviceSelector* device_selector_view_ = nullptr;
 
   SkColor foreground_color_;
   SkColor background_color_;
@@ -184,18 +155,14 @@
 
   bool is_expanded_ = false;
 
-  std::string audio_sink_id_ = media::AudioDeviceDescription::kDefaultDeviceId;
-
   base::ObserverList<global_media_controls::MediaItemUIObserver> observers_;
 
   // Handles gesture events for swiping to dismiss notifications.
   std::unique_ptr<views::SlideOutController> slide_out_controller_;
 
-  MediaNotificationService* const service_;
-
   const bool is_cros_;
-  const GlobalMediaControlsEntryPoint entry_point_;
-  Profile* const profile_;
 };
 
-#endif  // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_VIEW_H_
+}  // namespace global_media_controls
+
+#endif  // COMPONENTS_GLOBAL_MEDIA_CONTROLS_PUBLIC_VIEWS_MEDIA_ITEM_UI_VIEW_H_
diff --git a/components/global_media_controls/public/views/media_item_ui_view_unittest.cc b/components/global_media_controls/public/views/media_item_ui_view_unittest.cc
new file mode 100644
index 0000000..4b656fbd
--- /dev/null
+++ b/components/global_media_controls/public/views/media_item_ui_view_unittest.cc
@@ -0,0 +1,360 @@
+// Copyright 2019 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/global_media_controls/public/views/media_item_ui_view.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/containers/flat_set.h"
+#include "build/build_config.h"
+#include "components/global_media_controls/public/test/mock_media_item_manager.h"
+#include "components/global_media_controls/public/test/mock_media_item_ui_device_selector.h"
+#include "components/global_media_controls/public/test/mock_media_item_ui_footer.h"
+#include "components/global_media_controls/public/test/mock_media_item_ui_observer.h"
+#include "components/media_message_center/mock_media_notification_item.h"
+#include "services/media_session/public/mojom/media_session.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/display/test/scoped_screen_override.h"
+#include "ui/display/test/test_screen.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/events/test/event_generator.h"
+#include "ui/views/test/button_test_api.h"
+#include "ui/views/test/view_metadata_test_utils.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/widget_utils.h"
+
+using media_session::mojom::MediaPlaybackState;
+using media_session::mojom::MediaSessionAction;
+using ::testing::_;
+using ::testing::NiceMock;
+
+namespace global_media_controls {
+
+namespace {
+
+const char kTestNotificationId[] = "testid";
+const char kOtherTestNotificationId[] = "othertestid";
+
+}  // anonymous namespace
+
+class MediaItemUIViewTest : public views::ViewsTestBase {
+ public:
+  MediaItemUIViewTest() : screen_override_(&fake_screen_) {}
+  MediaItemUIViewTest(const MediaItemUIViewTest&) = delete;
+  MediaItemUIViewTest& operator=(const MediaItemUIViewTest&) = delete;
+  ~MediaItemUIViewTest() override = default;
+
+  // views::ViewsTestBase:
+  void SetUp() override {
+    views::ViewsTestBase::SetUp();
+    item_ = std::make_unique<
+        NiceMock<media_message_center::test::MockMediaNotificationItem>>();
+    widget_ = CreateTestWidget();
+
+    auto footer = std::make_unique<NiceMock<test::MockMediaItemUIFooter>>();
+    footer_ = footer.get();
+
+    auto device_selector =
+        std::make_unique<NiceMock<test::MockMediaItemUIDeviceSelector>>();
+    device_selector_ = device_selector.get();
+    device_selector_->SetPreferredSize({400, 50});
+
+    item_ui_ = widget_->SetContentsView(std::make_unique<MediaItemUIView>(
+        kTestNotificationId, item_->GetWeakPtr(), std::move(footer),
+        std::move(device_selector)));
+
+    observer_ = std::make_unique<
+        NiceMock<global_media_controls::test::MockMediaItemUIObserver>>();
+    item_ui_->AddObserver(observer_.get());
+
+    SimulateMediaSessionData();
+
+    widget_->Show();
+  }
+
+  void TearDown() override {
+    item_ui_->RemoveObserver(observer_.get());
+    widget_.reset();
+    ViewsTestBase::TearDown();
+  }
+
+  void SimulateItemUISwipedToDismiss() {
+    // When the item ui is swiped, the SlideOutController sends this to the
+    // MediaItemUIView.
+    item_ui()->OnSlideOut();
+  }
+
+  bool IsDismissButtonVisible() { return GetDismissButton()->IsDrawn(); }
+
+  void SimulateHoverOverItemUI() {
+    fake_screen_.set_cursor_screen_point(
+        item_ui_->GetBoundsInScreen().CenterPoint());
+
+    ui::MouseEvent event(ui::ET_MOUSE_ENTERED, gfx::Point(), gfx::Point(),
+                         ui::EventTimeForNow(), 0, 0);
+    item_ui_->OnMouseEntered(event);
+  }
+
+  void SimulateNotHoveringOverItemUI() {
+    gfx::Rect container_bounds = item_ui_->GetBoundsInScreen();
+    gfx::Point point_outside_container =
+        container_bounds.bottom_right() + gfx::Vector2d(1, 1);
+    fake_screen_.set_cursor_screen_point(point_outside_container);
+
+    ui::MouseEvent event(ui::ET_MOUSE_EXITED, gfx::Point(), gfx::Point(),
+                         ui::EventTimeForNow(), 0, 0);
+    item_ui_->OnMouseExited(event);
+  }
+
+  void SimulateItemUIClicked() {
+    ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+                         ui::EventTimeForNow(), 0, 0);
+    views::test::ButtonTestApi(item_ui_).NotifyClick(event);
+  }
+
+  void SimulateHeaderClicked() {
+    ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+                         ui::EventTimeForNow(), 0, 0);
+    views::test::ButtonTestApi(GetView()->GetHeaderRowForTesting())
+        .NotifyClick(event);
+  }
+
+  void SimulateDismissButtonClicked() {
+    ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+                         ui::EventTimeForNow(), 0, 0);
+    views::test::ButtonTestApi(GetDismissButton()).NotifyClick(event);
+  }
+
+  void SimulatePressingDismissButtonWithKeyboard() {
+    GetFocusManager()->SetFocusedView(item_ui_->GetDismissButtonForTesting());
+
+// On Mac OS, we need to use the space bar to press a button.
+#if defined(OS_MAC)
+    ui::KeyboardCode button_press_keycode = ui::VKEY_SPACE;
+#else
+    ui::KeyboardCode button_press_keycode = ui::VKEY_RETURN;
+#endif  // defined(OS_MAC)
+
+    ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
+    generator.PressKey(button_press_keycode, 0);
+  }
+
+  void SimulateSessionPlaying() { SimulateSessionInfo(true); }
+
+  void SimulateSessionPaused() { SimulateSessionInfo(false); }
+
+  void SimulateMetadataChanged() {
+    media_session::MediaMetadata metadata;
+    metadata.source_title = u"source_title2";
+    metadata.title = u"title2";
+    metadata.artist = u"artist2";
+    GetView()->UpdateWithMediaMetadata(metadata);
+  }
+
+  void SimulateAllActionsEnabled() {
+    actions_.insert(MediaSessionAction::kPlay);
+    actions_.insert(MediaSessionAction::kPause);
+    actions_.insert(MediaSessionAction::kPreviousTrack);
+    actions_.insert(MediaSessionAction::kNextTrack);
+    actions_.insert(MediaSessionAction::kSeekBackward);
+    actions_.insert(MediaSessionAction::kSeekForward);
+    actions_.insert(MediaSessionAction::kStop);
+
+    NotifyUpdatedActions();
+  }
+
+  void SimulateOnlyPlayPauseEnabled() {
+    actions_.clear();
+    actions_.insert(MediaSessionAction::kPlay);
+    actions_.insert(MediaSessionAction::kPause);
+    NotifyUpdatedActions();
+  }
+
+  void SimulateHasArtwork() {
+    SkBitmap image;
+    image.allocN32Pixels(10, 10);
+    image.eraseColor(SK_ColorMAGENTA);
+    GetView()->UpdateWithMediaArtwork(
+        gfx::ImageSkia::CreateFrom1xBitmap(image));
+  }
+
+  void SimulateHasNoArtwork() {
+    GetView()->UpdateWithMediaArtwork(gfx::ImageSkia());
+  }
+
+  views::FocusManager* GetFocusManager() { return item_ui_->GetFocusManager(); }
+
+  test::MockMediaItemUIObserver& observer() { return *observer_; }
+
+  MediaItemUIView* item_ui() { return item_ui_; }
+
+  base::WeakPtr<media_message_center::test::MockMediaNotificationItem>
+  notification_item() {
+    return item_->GetWeakPtr();
+  }
+
+ private:
+  void SimulateSessionInfo(bool playing) {
+    media_session::mojom::MediaSessionInfoPtr session_info(
+        media_session::mojom::MediaSessionInfo::New());
+    session_info->playback_state =
+        playing ? MediaPlaybackState::kPlaying : MediaPlaybackState::kPaused;
+    session_info->is_controllable = true;
+
+    GetView()->UpdateWithMediaSessionInfo(std::move(session_info));
+  }
+
+  void SimulateMediaSessionData() {
+    SimulateSessionInfo(true);
+
+    media_session::MediaMetadata metadata;
+    metadata.source_title = u"source_title";
+    metadata.title = u"title";
+    metadata.artist = u"artist";
+    GetView()->UpdateWithMediaMetadata(metadata);
+
+    SimulateOnlyPlayPauseEnabled();
+  }
+
+  void NotifyUpdatedActions() { GetView()->UpdateWithMediaActions(actions_); }
+
+  media_message_center::MediaNotificationViewImpl* GetView() {
+    return item_ui()->view_for_testing();
+  }
+
+  views::ImageButton* GetDismissButton() {
+    return item_ui()->GetDismissButtonForTesting();
+  }
+
+  std::unique_ptr<views::Widget> widget_;
+  test::MockMediaItemUIFooter* footer_ = nullptr;
+  test::MockMediaItemUIDeviceSelector* device_selector_ = nullptr;
+  MediaItemUIView* item_ui_ = nullptr;
+  std::unique_ptr<global_media_controls::test::MockMediaItemUIObserver>
+      observer_;
+  std::unique_ptr<media_message_center::test::MockMediaNotificationItem> item_;
+
+  // Set of actions currently enabled.
+  base::flat_set<MediaSessionAction> actions_;
+
+  display::test::TestScreen fake_screen_;
+  display::test::ScopedScreenOverride screen_override_;
+};
+
+TEST_F(MediaItemUIViewTest, SwipeToDismiss) {
+  EXPECT_CALL(observer(), OnMediaItemUIDismissed(kTestNotificationId));
+  SimulateItemUISwipedToDismiss();
+}
+
+TEST_F(MediaItemUIViewTest, ClickToDismiss) {
+  // Ensure that the mouse is not over the container and that nothing is
+  // focused. The dismiss button should not be visible.
+  SimulateNotHoveringOverItemUI();
+  ASSERT_EQ(nullptr, GetFocusManager()->GetFocusedView());
+  ASSERT_FALSE(item_ui()->IsMouseHovered());
+  EXPECT_FALSE(IsDismissButtonVisible());
+
+  // Hovering over the notification should show the dismiss button.
+  SimulateHoverOverItemUI();
+  EXPECT_TRUE(IsDismissButtonVisible());
+
+  // Moving the mouse away from the notification should hide the dismiss button.
+  SimulateNotHoveringOverItemUI();
+  EXPECT_FALSE(IsDismissButtonVisible());
+
+  // Moving the mouse back over the notification should re-show it.
+  SimulateHoverOverItemUI();
+  EXPECT_TRUE(IsDismissButtonVisible());
+
+  // Clicking it should inform observers that we've been dismissed.
+  EXPECT_CALL(observer(), OnMediaItemUIDismissed(kTestNotificationId));
+  SimulateDismissButtonClicked();
+  testing::Mock::VerifyAndClearExpectations(&observer());
+}
+
+TEST_F(MediaItemUIViewTest, KeyboardToDismiss) {
+  // Ensure that the mouse is not over the container and that nothing is
+  // focused. The dismiss button should not be visible.
+  SimulateNotHoveringOverItemUI();
+  ASSERT_EQ(nullptr, GetFocusManager()->GetFocusedView());
+  ASSERT_FALSE(item_ui()->IsMouseHovered());
+  EXPECT_FALSE(IsDismissButtonVisible());
+
+  // When the notification receives keyboard focus, the dismiss button should be
+  // visible.
+  GetFocusManager()->SetFocusedView(item_ui());
+  EXPECT_TRUE(IsDismissButtonVisible());
+
+  // When the notification loses keyboard focus, the dismiss button should be
+  // hidden.
+  GetFocusManager()->SetFocusedView(nullptr);
+  EXPECT_FALSE(IsDismissButtonVisible());
+
+  // If it gets focus again, it should re-show the dismiss button.
+  GetFocusManager()->SetFocusedView(item_ui());
+  EXPECT_TRUE(IsDismissButtonVisible());
+
+  // Clicking it should inform observers that we've been dismissed.
+  EXPECT_CALL(observer(), OnMediaItemUIDismissed(kTestNotificationId));
+  SimulatePressingDismissButtonWithKeyboard();
+  testing::Mock::VerifyAndClearExpectations(&observer());
+}
+
+TEST_F(MediaItemUIViewTest, ForceExpandedState) {
+  // When we have many actions enabled, we should be forced into the expanded
+  // state.
+  SimulateAllActionsEnabled();
+  EXPECT_TRUE(item_ui()->is_expanded_for_testing());
+
+  // When we don't have many actions enabled, we should be forced out of the
+  // expanded state.
+  SimulateOnlyPlayPauseEnabled();
+  EXPECT_FALSE(item_ui()->is_expanded_for_testing());
+
+  // We will also be forced into the expanded state when artwork is present.
+  SimulateHasArtwork();
+  EXPECT_TRUE(item_ui()->is_expanded_for_testing());
+
+  // Once the artwork is gone, we should be forced back out of the expanded
+  // state.
+  SimulateHasNoArtwork();
+  EXPECT_FALSE(item_ui()->is_expanded_for_testing());
+}
+
+TEST_F(MediaItemUIViewTest, SendsMetadataUpdates) {
+  EXPECT_CALL(observer(), OnMediaItemUIMetadataChanged());
+  SimulateMetadataChanged();
+}
+
+TEST_F(MediaItemUIViewTest, SendsDestroyedUpdates) {
+  auto container = std::make_unique<MediaItemUIView>(
+      kOtherTestNotificationId, notification_item(), nullptr, nullptr);
+  global_media_controls::test::MockMediaItemUIObserver observer;
+  container->AddObserver(&observer);
+
+  // When the container is destroyed, it should notify the observer.
+  EXPECT_CALL(observer, OnMediaItemUIDestroyed(kOtherTestNotificationId));
+  container.reset();
+  testing::Mock::VerifyAndClearExpectations(&observer);
+}
+
+TEST_F(MediaItemUIViewTest, SendsClicks) {
+  // When the container is clicked directly, it should notify its observers.
+  EXPECT_CALL(observer(), OnMediaItemUIClicked(kTestNotificationId));
+  SimulateItemUIClicked();
+  testing::Mock::VerifyAndClearExpectations(&observer());
+
+  // It should also notify its observers when the header is clicked.
+  EXPECT_CALL(observer(), OnMediaItemUIClicked(kTestNotificationId));
+  SimulateHeaderClicked();
+}
+
+TEST_F(MediaItemUIViewTest, MetadataTest) {
+  auto container_view = std::make_unique<MediaItemUIView>(
+      kOtherTestNotificationId, notification_item(), nullptr, nullptr);
+  views::test::TestViewMetadata(container_view.get());
+}
+
+}  // namespace global_media_controls
diff --git a/components/global_media_controls_strings.grdp b/components/global_media_controls_strings.grdp
new file mode 100644
index 0000000..24e9a21
--- /dev/null
+++ b/components/global_media_controls_strings.grdp
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+  <message name="IDS_GLOBAL_MEDIA_CONTROLS_BACK_TO_TAB" desc="A11y text for the Global Media Controls container describing how clicking it takes you back to the tab.">
+   Back to tab
+  </message>
+  <message name="IDS_GLOBAL_MEDIA_CONTROLS_DISMISS_ICON_TOOLTIP_TEXT" desc="Tooltip for the dismiss button within the Global Media Controls dialog. The tooltip appears on mouseover of the icon.">
+   Dismiss
+  </message>
+</grit-part>
diff --git a/chrome/app/global_media_controls_strings_grdp/IDS_GLOBAL_MEDIA_CONTROLS_BACK_TO_TAB.png.sha1 b/components/global_media_controls_strings_grdp/IDS_GLOBAL_MEDIA_CONTROLS_BACK_TO_TAB.png.sha1
similarity index 100%
rename from chrome/app/global_media_controls_strings_grdp/IDS_GLOBAL_MEDIA_CONTROLS_BACK_TO_TAB.png.sha1
rename to components/global_media_controls_strings_grdp/IDS_GLOBAL_MEDIA_CONTROLS_BACK_TO_TAB.png.sha1
diff --git a/chrome/app/global_media_controls_strings_grdp/IDS_GLOBAL_MEDIA_CONTROLS_DISMISS_ICON_TOOLTIP_TEXT.png.sha1 b/components/global_media_controls_strings_grdp/IDS_GLOBAL_MEDIA_CONTROLS_DISMISS_ICON_TOOLTIP_TEXT.png.sha1
similarity index 100%
rename from chrome/app/global_media_controls_strings_grdp/IDS_GLOBAL_MEDIA_CONTROLS_DISMISS_ICON_TOOLTIP_TEXT.png.sha1
rename to components/global_media_controls_strings_grdp/IDS_GLOBAL_MEDIA_CONTROLS_DISMISS_ICON_TOOLTIP_TEXT.png.sha1
diff --git a/components/media_message_center/BUILD.gn b/components/media_message_center/BUILD.gn
index 106f9729..9389449 100644
--- a/components/media_message_center/BUILD.gn
+++ b/components/media_message_center/BUILD.gn
@@ -60,6 +60,7 @@
 
   deps = [
     ":media_message_center",
+    ":test_support",
     "//base",
     "//base/test:test_support",
     "//services/media_session/public/cpp/test:test_support",
@@ -79,6 +80,8 @@
   testonly = true
 
   sources = [
+    "mock_media_notification_item.cc",
+    "mock_media_notification_item.h",
     "mock_media_notification_view.cc",
     "mock_media_notification_view.h",
   ]
diff --git a/components/media_message_center/media_notification_view_impl_unittest.cc b/components/media_message_center/media_notification_view_impl_unittest.cc
index 44454d3..60bfcb1 100644
--- a/components/media_message_center/media_notification_view_impl_unittest.cc
+++ b/components/media_message_center/media_notification_view_impl_unittest.cc
@@ -18,7 +18,7 @@
 #include "build/build_config.h"
 #include "components/media_message_center/media_notification_background_impl.h"
 #include "components/media_message_center/media_notification_container.h"
-#include "components/media_message_center/media_notification_item.h"
+#include "components/media_message_center/mock_media_notification_item.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/accessibility/ax_enums.mojom.h"
@@ -57,32 +57,6 @@
 constexpr int kViewArtworkWidth = kViewWidth * 0.4;
 const gfx::Size kViewSize(kViewWidth, 400);
 
-class MockMediaNotificationItem : public MediaNotificationItem {
- public:
-  MockMediaNotificationItem() = default;
-  MockMediaNotificationItem(const MockMediaNotificationItem&) = delete;
-  MockMediaNotificationItem& operator=(const MockMediaNotificationItem&) =
-      delete;
-  ~MockMediaNotificationItem() override = default;
-
-  MOCK_METHOD(void, SetView, (MediaNotificationView*));
-  MOCK_METHOD(void,
-              OnMediaSessionActionButtonPressed,
-              (media_session::mojom::MediaSessionAction));
-  MOCK_METHOD(void, SeekTo, (base::TimeDelta));
-  MOCK_METHOD(void, Dismiss, ());
-  MOCK_METHOD(void, SetVolume, (float));
-  MOCK_METHOD(void, SetMute, (bool));
-  MOCK_METHOD(media_message_center::SourceType, SourceType, ());
-
-  base::WeakPtr<MockMediaNotificationItem> GetWeakPtr() {
-    return weak_ptr_factory_.GetWeakPtr();
-  }
-
- private:
-  base::WeakPtrFactory<MockMediaNotificationItem> weak_ptr_factory_{this};
-};
-
 class MockMediaNotificationContainer : public MediaNotificationContainer {
  public:
   MockMediaNotificationContainer() = default;
@@ -212,7 +186,7 @@
     return GetButtonForAction(action)->GetVisible();
   }
 
-  MockMediaNotificationItem& item() { return item_; }
+  test::MockMediaNotificationItem& item() { return item_; }
 
   const gfx::ImageSkia& GetArtworkImage() const {
     return static_cast<MediaNotificationBackgroundImpl*>(
@@ -292,7 +266,7 @@
   base::flat_set<MediaSessionAction> actions_;
 
   MockMediaNotificationContainer container_;
-  MockMediaNotificationItem item_;
+  test::MockMediaNotificationItem item_;
   MediaNotificationViewImpl* view_;
   std::unique_ptr<views::Widget> widget_;
 };
diff --git a/components/media_message_center/media_notification_view_modern_impl_unittest.cc b/components/media_message_center/media_notification_view_modern_impl_unittest.cc
index 42ebd69..e949fd87 100644
--- a/components/media_message_center/media_notification_view_modern_impl_unittest.cc
+++ b/components/media_message_center/media_notification_view_modern_impl_unittest.cc
@@ -19,8 +19,8 @@
 #include "components/media_message_center/media_controls_progress_view.h"
 #include "components/media_message_center/media_notification_background_impl.h"
 #include "components/media_message_center/media_notification_container.h"
-#include "components/media_message_center/media_notification_item.h"
 #include "components/media_message_center/media_notification_util.h"
+#include "components/media_message_center/mock_media_notification_item.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/accessibility/ax_enums.mojom.h"
@@ -52,32 +52,6 @@
 constexpr int kViewWidth = 350;
 const gfx::Size kViewSize(kViewWidth, 400);
 
-class MockMediaNotificationItem : public MediaNotificationItem {
- public:
-  MockMediaNotificationItem() = default;
-  MockMediaNotificationItem(const MockMediaNotificationItem&) = delete;
-  MockMediaNotificationItem& operator=(const MockMediaNotificationItem&) =
-      delete;
-  ~MockMediaNotificationItem() override = default;
-
-  MOCK_METHOD(void, SetView, (MediaNotificationView*));
-  MOCK_METHOD(void,
-              OnMediaSessionActionButtonPressed,
-              (media_session::mojom::MediaSessionAction));
-  MOCK_METHOD(void, SeekTo, (base::TimeDelta));
-  MOCK_METHOD(void, Dismiss, ());
-  MOCK_METHOD(void, SetVolume, (float));
-  MOCK_METHOD(void, SetMute, (bool));
-  MOCK_METHOD(media_message_center::SourceType, SourceType, ());
-
-  base::WeakPtr<MockMediaNotificationItem> GetWeakPtr() {
-    return weak_ptr_factory_.GetWeakPtr();
-  }
-
- private:
-  base::WeakPtrFactory<MockMediaNotificationItem> weak_ptr_factory_{this};
-};
-
 class MockMediaNotificationContainer : public MediaNotificationContainer {
  public:
   MockMediaNotificationContainer() = default;
@@ -172,7 +146,7 @@
     return view()->accessible_name_;
   }
 
-  MockMediaNotificationItem& item() { return item_; }
+  test::MockMediaNotificationItem& item() { return item_; }
 
   views::Label* title_label() const { return view()->title_label_; }
 
@@ -274,7 +248,7 @@
   base::flat_set<MediaSessionAction> actions_;
 
   MockMediaNotificationContainer container_;
-  MockMediaNotificationItem item_;
+  test::MockMediaNotificationItem item_;
   MediaNotificationViewModernImpl* view_;
   std::unique_ptr<views::Widget> widget_;
 };
diff --git a/components/media_message_center/mock_media_notification_item.cc b/components/media_message_center/mock_media_notification_item.cc
new file mode 100644
index 0000000..fce1b78
--- /dev/null
+++ b/components/media_message_center/mock_media_notification_item.cc
@@ -0,0 +1,20 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/media_message_center/mock_media_notification_item.h"
+
+namespace media_message_center {
+namespace test {
+
+MockMediaNotificationItem::MockMediaNotificationItem() = default;
+
+MockMediaNotificationItem::~MockMediaNotificationItem() = default;
+
+base::WeakPtr<MockMediaNotificationItem>
+MockMediaNotificationItem::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+}  // namespace test
+}  // namespace media_message_center
diff --git a/components/media_message_center/mock_media_notification_item.h b/components/media_message_center/mock_media_notification_item.h
new file mode 100644
index 0000000..99c7530
--- /dev/null
+++ b/components/media_message_center/mock_media_notification_item.h
@@ -0,0 +1,41 @@
+// 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_MEDIA_MESSAGE_CENTER_MOCK_MEDIA_NOTIFICATION_ITEM_H_
+#define COMPONENTS_MEDIA_MESSAGE_CENTER_MOCK_MEDIA_NOTIFICATION_ITEM_H_
+
+#include "components/media_message_center/media_notification_item.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media_message_center {
+namespace test {
+
+class MockMediaNotificationItem : public MediaNotificationItem {
+ public:
+  MockMediaNotificationItem();
+  MockMediaNotificationItem(const MockMediaNotificationItem&) = delete;
+  MockMediaNotificationItem& operator=(const MockMediaNotificationItem&) =
+      delete;
+  ~MockMediaNotificationItem() override;
+
+  MOCK_METHOD(void, SetView, (MediaNotificationView*));
+  MOCK_METHOD(void,
+              OnMediaSessionActionButtonPressed,
+              (media_session::mojom::MediaSessionAction));
+  MOCK_METHOD(void, SeekTo, (base::TimeDelta));
+  MOCK_METHOD(void, Dismiss, ());
+  MOCK_METHOD(void, SetVolume, (float));
+  MOCK_METHOD(void, SetMute, (bool));
+  MOCK_METHOD(media_message_center::SourceType, SourceType, ());
+
+  base::WeakPtr<MockMediaNotificationItem> GetWeakPtr();
+
+ private:
+  base::WeakPtrFactory<MockMediaNotificationItem> weak_ptr_factory_{this};
+};
+
+}  // namespace test
+}  // namespace media_message_center
+
+#endif  // COMPONENTS_MEDIA_MESSAGE_CENTER_MOCK_MEDIA_NOTIFICATION_ITEM_H_
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageQueueManager.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageQueueManager.java
index 6d2c46a..215dec81 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageQueueManager.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageQueueManager.java
@@ -25,7 +25,8 @@
      * including situations in which the message is already dismissed and hide animation is running.
      */
     @Nullable
-    private MessageQueueManager.MessageState mCurrentDisplayedMessage;
+    private MessageState mCurrentDisplayedMessage;
+    private MessageState mLastShownMessage;
     private MessageQueueDelegate mMessageQueueDelegate;
     // TokenHolder tracking whether the queue should be suspended.
     private final TokenHolder mSuppressionTokenHolder =
@@ -182,7 +183,13 @@
         if (mCurrentDisplayedMessage != candidate) {
             if (mCurrentDisplayedMessage == null) {
                 mCurrentDisplayedMessage = candidate;
-                mMessageQueueDelegate.onStartShowing(mCurrentDisplayedMessage.handler::show);
+                mMessageQueueDelegate.onStartShowing(() -> {
+                    if (mCurrentDisplayedMessage == null) {
+                        return;
+                    }
+                    mCurrentDisplayedMessage.handler.show();
+                    mLastShownMessage = mCurrentDisplayedMessage;
+                });
             } else {
                 hideMessage(!isQueueSuspended() && animateTransition, !isQueueSuspended());
             }
@@ -205,11 +212,19 @@
     }
 
     private void hideMessage(boolean animate, boolean updateCurrentMessage) {
-        mCurrentDisplayedMessage.handler.hide(animate, () -> {
+        assert mCurrentDisplayedMessage
+                != null
+            : "This should not be called when there is no valid currently displayed message.";
+        Runnable runnable = () -> {
             mMessageQueueDelegate.onFinishHiding();
             mCurrentDisplayedMessage = null;
             if (updateCurrentMessage) updateCurrentDisplayedMessage(true, getNextMessage());
-        });
+        };
+        if (mLastShownMessage != mCurrentDisplayedMessage) {
+            runnable.run();
+            return;
+        }
+        mCurrentDisplayedMessage.handler.hide(animate, runnable);
     }
 
     /**
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageQueueManagerTest.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageQueueManagerTest.java
index 4416605..9ac40db 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageQueueManagerTest.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageQueueManagerTest.java
@@ -24,6 +24,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.robolectric.annotation.Config;
 
@@ -463,6 +464,40 @@
     }
 
     /**
+     * Test that callback can be correctly called if #hide is called without #show called before.
+     */
+    @Test
+    @SmallTest
+    public void testShowHideMultipleTimes() {
+        MessageQueueDelegate delegate = Mockito.spy(MessageQueueDelegate.class);
+        MessageQueueManager queueManager = new MessageQueueManager();
+        queueManager.setDelegate(delegate);
+        MessageStateHandler m1 = Mockito.spy(new EmptyMessageStateHandler());
+        queueManager.enqueueMessage(m1, m1, SCOPE_INSTANCE_ID, false);
+
+        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(delegate).onStartShowing(runnableCaptor.capture());
+        Runnable onShow = runnableCaptor.getValue();
+        verify(m1, never()).show();
+        queueManager.onScopeChange(
+                new MessageScopeChange(SCOPE_TYPE, SCOPE_INSTANCE_ID, ChangeType.INACTIVE));
+        verify(m1, never()).hide(anyBoolean(), any());
+        onShow.run();
+        verify(m1, never()).show();
+
+        queueManager.onScopeChange(
+                new MessageScopeChange(SCOPE_TYPE, SCOPE_INSTANCE_ID, ChangeType.ACTIVE));
+        runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(delegate, times(2)).onStartShowing(runnableCaptor.capture());
+        runnableCaptor.getValue().run();
+        verify(m1).show();
+
+        queueManager.onScopeChange(
+                new MessageScopeChange(SCOPE_TYPE, SCOPE_INSTANCE_ID, ChangeType.DESTROY));
+        verify(m1).hide(anyBoolean(), any());
+    }
+
+    /**
      * Test scope change controller is properly called when message is enqueued and dismissed.
      */
     @Test
diff --git a/components/metrics/clean_exit_beacon_unittest.cc b/components/metrics/clean_exit_beacon_unittest.cc
index 0cdd64a..436fbda7 100644
--- a/components/metrics/clean_exit_beacon_unittest.cc
+++ b/components/metrics/clean_exit_beacon_unittest.cc
@@ -51,23 +51,6 @@
 
 }  // namespace
 
-class FakeTestingPrefStore : public TestingPrefStore {
- public:
-  void CommitPendingWriteSynchronously() override {
-    was_commit_pending_write_synchronously_called_ = true;
-  }
-
-  bool was_commit_pending_write_synchronously_called() {
-    return was_commit_pending_write_synchronously_called_;
-  }
-
- protected:
-  ~FakeTestingPrefStore() override = default;
-
- private:
-  bool was_commit_pending_write_synchronously_called_ = false;
-};
-
 class TestCleanExitBeacon : public CleanExitBeacon {
  public:
   explicit TestCleanExitBeacon(
@@ -363,20 +346,10 @@
   ASSERT_EQ(variations::kControlGroup, base::FieldTrialList::FindFullName(
                                            variations::kExtendedSafeModeTrial));
 
-  PrefServiceFactory factory;
-  scoped_refptr<FakeTestingPrefStore> pref_store(new FakeTestingPrefStore);
-  factory.set_user_prefs(pref_store);
-  scoped_refptr<PrefRegistrySimple> registry(new PrefRegistrySimple);
-  std::unique_ptr<PrefService> prefs(factory.Create(registry.get()));
-  CleanExitBeacon::RegisterPrefs(registry.get());
-  TestCleanExitBeacon clean_exit_beacon(prefs.get(), user_data_dir_.GetPath());
+  TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_.GetPath());
   EXPECT_DCHECK_DEATH(
       clean_exit_beacon.WriteBeaconValue(/*exited_cleanly=*/false,
                                          /*write_synchronously=*/true));
-
-  // Verify that CommitPendingWriteSynchronously() was not called and that
-  // the WritePrefsTime metric was not emitted.
-  EXPECT_FALSE(pref_store->was_commit_pending_write_synchronously_called());
   histogram_tester_.ExpectTotalCount(
       "Variations.ExtendedSafeMode.WritePrefsTime", 0);
 }
diff --git a/components/optimization_guide/proto/common_types.proto b/components/optimization_guide/proto/common_types.proto
index 85c0d62..48683c2 100644
--- a/components/optimization_guide/proto/common_types.proto
+++ b/components/optimization_guide/proto/common_types.proto
@@ -78,6 +78,19 @@
   optional int32 nanos = 2;
 }
 
+message Timestamp {
+  // Represents seconds of UTC time since Unix epoch
+  // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
+  // 9999-12-31T23:59:59Z inclusive.
+  optional int64 seconds = 1;
+
+  // Non-negative fractions of a second at nanosecond resolution. Negative
+  // second values with fractions must still have non-negative nanos values
+  // that count forward in time. Must be from 0 to 999,999,999
+  // inclusive.
+  optional int32 nanos = 2;
+}
+
 message Any {
   // A URL/resource name that uniquely identifies the type of the serialized
   // protocol buffer message.
diff --git a/components/optimization_guide/proto/hints.proto b/components/optimization_guide/proto/hints.proto
index a6fd2fe..889a0bf 100644
--- a/components/optimization_guide/proto/hints.proto
+++ b/components/optimization_guide/proto/hints.proto
@@ -311,19 +311,6 @@
   repeated OptimizationFilter optimization_allowlists = 3;
 }
 
-message Timestamp {
-  // Represents seconds of UTC time since Unix epoch
-  // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
-  // 9999-12-31T23:59:59Z inclusive.
-  optional int64 seconds = 1;
-
-  // Non-negative fractions of a second at nanosecond resolution. Negative
-  // second values with fractions must still have non-negative nanos values
-  // that count forward in time. Must be from 0 to 999,999,999
-  // inclusive.
-  optional int32 nanos = 2;
-}
-
 enum HintSource {
   HINT_SOURCE_UNKNOWN = 0;
   // Served from the Chrome Optimization Hints Component.
diff --git a/components/page_info/BUILD.gn b/components/page_info/BUILD.gn
index d933a85..a3cd3aa 100644
--- a/components/page_info/BUILD.gn
+++ b/components/page_info/BUILD.gn
@@ -1,7 +1,27 @@
+import("//third_party/protobuf/proto_library.gni")
+
 if (is_android) {
   import("//build/config/android/rules.gni")
 }
 
+proto_library("proto") {
+  proto_in_dir = "//"
+  sources = [ "proto/about_this_site_metadata.proto" ]
+
+  deps = [ "//components/optimization_guide/proto:optimization_guide_proto" ]
+}
+
+if (is_android) {
+  proto_java_library("proto_java") {
+    proto_path = "//"
+    sources = [ "proto/about_this_site_metadata.proto" ]
+
+    deps = [
+      "//components/optimization_guide/proto:optimization_guide_proto_java",
+    ]
+  }
+}
+
 static_library("page_info") {
   sources = [
     "about_this_site_service.cc",
@@ -25,6 +45,7 @@
     "//components/keyed_service/core",
     "//components/omnibox/common",
     "//components/optimization_guide/core",
+    "//components/page_info:proto",
     "//components/password_manager/core/browser",
     "//components/permissions",
     "//components/prefs",
diff --git a/components/page_info/DEPS b/components/page_info/DEPS
index dde4bec..bf0ac75 100644
--- a/components/page_info/DEPS
+++ b/components/page_info/DEPS
@@ -4,6 +4,7 @@
   "+components/content_settings",
   "+components/keyed_service/core",
   "+components/optimization_guide/core",
+  "+components/optimization_guide/proto",
   "+components/password_manager",
   "+components/permissions",
   "+components/prefs",
diff --git a/components/page_info/about_this_site_service.cc b/components/page_info/about_this_site_service.cc
index 58b3f174..cc85126 100644
--- a/components/page_info/about_this_site_service.cc
+++ b/components/page_info/about_this_site_service.cc
@@ -12,23 +12,39 @@
 AboutThisSiteService::AboutThisSiteService(std::unique_ptr<Client> client)
     : client_(std::move(client)) {}
 
-std::u16string AboutThisSiteService::GetAboutThisSiteDescription(
-    const GURL& url) const {
-  // TODO(crbug.com/1250653): Return the actual data after server-side is ready
-  // and proto is added.
+absl::optional<page_info::proto::SiteInfo>
+AboutThisSiteService::GetAboutThisSiteInfo(const GURL& url) const {
+  optimization_guide::OptimizationMetadata metadata;
+  client_->CanApplyOptimization(url, &metadata);
+  absl::optional<page_info::proto::AboutThisSiteMetadata>
+      about_this_site_metadata =
+          metadata.ParsedMetadata<page_info::proto::AboutThisSiteMetadata>();
+  // TODO(crbug.com/1250653): Add validation to check if proto contains any
+  // useful data.
+  if (about_this_site_metadata) {
+    return about_this_site_metadata->site_info();
+  }
+
+  // TODO(crbug.com/1250653): Remove returning fake data after server-side is
+  // ready.
+  page_info::proto::SiteInfo site_info_metadata;
   const GURL test_gurl("https://example.com");
   if (url == test_gurl) {
-    return u"A domain used in illustrative examples in documents";
+    site_info_metadata.set_entity_description(
+        "A domain used in illustrative examples in documents");
+    return site_info_metadata;
   }
 
   const GURL permissions_gurl("https://permission.site");
   if (url == permissions_gurl) {
-    return u"A site containing test buttons for various browser APIs, in order"
-           " to trigger permission dialogues and similar UI in modern "
-           "browsers.";
+    site_info_metadata.set_entity_description(
+        "A site containing test buttons for various browser APIs, in order"
+        " to trigger permission dialogues and similar UI in modern "
+        "browsers.");
+    return site_info_metadata;
   }
 
-  return std::u16string();
+  return absl::nullopt;
 }
 
 AboutThisSiteService::~AboutThisSiteService() = default;
diff --git a/components/page_info/about_this_site_service.h b/components/page_info/about_this_site_service.h
index bd82bd5..d12160c 100644
--- a/components/page_info/about_this_site_service.h
+++ b/components/page_info/about_this_site_service.h
@@ -10,6 +10,7 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/optimization_guide/core/optimization_guide_decision.h"
 #include "components/optimization_guide/core/optimization_metadata.h"
+#include "components/page_info/proto/about_this_site_metadata.pb.h"
 
 class GURL;
 
@@ -38,7 +39,8 @@
   AboutThisSiteService& operator=(const AboutThisSiteService&) = delete;
 
   // Returns "About this site" information for the website with |url|.
-  std::u16string GetAboutThisSiteDescription(const GURL& url) const;
+  absl::optional<page_info::proto::SiteInfo> GetAboutThisSiteInfo(
+      const GURL& url) const;
 
  private:
   std::unique_ptr<Client> client_;
diff --git a/components/page_info/proto/about_this_site_metadata.proto b/components/page_info/proto/about_this_site_metadata.proto
new file mode 100644
index 0000000..ce424f8
--- /dev/null
+++ b/components/page_info/proto/about_this_site_metadata.proto
@@ -0,0 +1,60 @@
+// 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 = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_package = "org.chromium.components.page_info.proto";
+
+package page_info.proto;
+
+// Represents a duration like “3 days” or “2 years”. It should always use the
+// lowest possible granularity. E.g. use “1 month” as soon as an event happened
+// at least 30 days ago. When precision is MORE_THAN, the event happened at
+// least the specified time ago, e.g. “more than 2 years ago”.
+message CoarseDuration {
+  optional int32 count = 1;
+  optional DurationUnit unit = 2;
+  optional DurationPrecision precision = 3;
+}
+
+enum DurationUnit {
+  DURATION_UNIT_UNSPECIFIED = 0;
+  DURATION_UNIT_DAYS = 1;
+  DURATION_UNIT_MONTHS = 2;
+  DURATION_UNIT_YEARS = 3;
+}
+
+enum DurationPrecision {
+  DURATION_PRECISION_UNSPECIFIED = 0;
+  DURATION_PRECISION_ABOUT = 1;
+  DURATION_PRECISION_LESS_THAN = 2;
+  DURATION_PRECISION_MORE_THAN = 3;
+}
+
+message SiteInfo {
+  // First-seen date information related to this host/domain.
+  // When Google first indexed this site.
+  optional CoarseDuration first_seen = 1;
+
+  // Name of the entity associated with the site.
+  optional string entity_name = 2;
+
+  // Description of the entity associated with the site.
+  optional string entity_description = 3;
+
+  // Source URL of this information.
+  optional string source_url = 4;
+
+  // Name of the source of this information.
+  optional string source_name = 5;
+}
+
+// Optimization metadata associated with SiteInfo.
+//
+// This is only populated for the ABOUT_THIS_SITE optimization type.
+message AboutThisSiteMetadata {
+  // A SiteInfo hint.
+  optional SiteInfo site_info = 1;
+}
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index a3dca1c..d37db989 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -313,6 +313,8 @@
 
   if (is_android) {
     sources += [
+      "built_in_backend_to_android_backend_migrator.cc",
+      "built_in_backend_to_android_backend_migrator.h",
       "password_scripts_fetcher.h",
       "password_scripts_fetcher_impl.cc",
       "password_scripts_fetcher_impl.h",
diff --git a/components/password_manager/core/browser/built_in_backend_to_android_backend_migrator.cc b/components/password_manager/core/browser/built_in_backend_to_android_backend_migrator.cc
new file mode 100644
index 0000000..b534535
--- /dev/null
+++ b/components/password_manager/core/browser/built_in_backend_to_android_backend_migrator.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/password_manager/core/browser/built_in_backend_to_android_backend_migrator.h"
+
+namespace password_manager {
+
+BuiltInBackendToAndroidBackendMigrator::
+    BuiltInBackendToAndroidBackendMigrator() = default;
+
+BuiltInBackendToAndroidBackendMigrator::
+    ~BuiltInBackendToAndroidBackendMigrator() = default;
+
+void BuiltInBackendToAndroidBackendMigrator::StartMigrationIfNecessary() {
+  // TODO:(crbug.com/1252443) Check current migration version and version
+  // saved in pref. If current version is higher, start migration.
+}
+
+void BuiltInBackendToAndroidBackendMigrator::UpdateMigrationVersionInPref() {
+  // TODO:(crbug.com/1252443) Save current migration version in pref.
+}
+
+}  // namespace password_manager
diff --git a/components/password_manager/core/browser/built_in_backend_to_android_backend_migrator.h b/components/password_manager/core/browser/built_in_backend_to_android_backend_migrator.h
new file mode 100644
index 0000000..c815c22
--- /dev/null
+++ b/components/password_manager/core/browser/built_in_backend_to_android_backend_migrator.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_PASSWORD_MANAGER_CORE_BROWSER_BUILT_IN_BACKEND_TO_ANDROID_BACKEND_MIGRATOR_H_
+#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BUILT_IN_BACKEND_TO_ANDROID_BACKEND_MIGRATOR_H_
+
+#include "base/callback.h"
+#include "base/callback_forward.h"
+
+namespace password_manager {
+// Instantiate this object to migrate all password stored in the built-in
+// backend to the Android backend. Migration is potentially an expensive
+// operation and shouldn't start during the hot phase of Chrome start.
+class BuiltInBackendToAndroidBackendMigrator {
+ public:
+  BuiltInBackendToAndroidBackendMigrator();
+  BuiltInBackendToAndroidBackendMigrator(
+      const BuiltInBackendToAndroidBackendMigrator&) = delete;
+  BuiltInBackendToAndroidBackendMigrator& operator=(
+      const BuiltInBackendToAndroidBackendMigrator&) = delete;
+  BuiltInBackendToAndroidBackendMigrator(
+      BuiltInBackendToAndroidBackendMigrator&&) = delete;
+  BuiltInBackendToAndroidBackendMigrator& operator=(
+      BuiltInBackendToAndroidBackendMigrator&&) = delete;
+  ~BuiltInBackendToAndroidBackendMigrator();
+
+  void StartMigrationIfNecessary();
+
+ private:
+  // Saves current migration version in 'pref_'.
+  void UpdateMigrationVersionInPref();
+};
+
+}  // namespace password_manager
+
+#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BUILT_IN_BACKEND_TO_ANDROID_BACKEND_MIGRATOR_H_
diff --git a/components/password_manager/core/browser/password_store_backend_migration_decorator.cc b/components/password_manager/core/browser/password_store_backend_migration_decorator.cc
index f52b513..4d69935 100644
--- a/components/password_manager/core/browser/password_store_backend_migration_decorator.cc
+++ b/components/password_manager/core/browser/password_store_backend_migration_decorator.cc
@@ -5,6 +5,7 @@
 #include "components/password_manager/core/browser/password_store_backend_migration_decorator.h"
 
 #include "base/bind.h"
+#include "components/password_manager/core/browser/built_in_backend_to_android_backend_migrator.h"
 #include "components/password_manager/core/browser/field_info_table.h"
 #include "components/password_manager/core/browser/password_store_impl.h"
 #include "components/password_manager/core/browser/password_store_proxy_backend.h"
@@ -33,6 +34,8 @@
   active_backend_->InitBackend(std::move(remote_form_changes_received),
                                std::move(sync_enabled_or_disabled_cb),
                                std::move(completion));
+  // TODO:(crbug.com/1252443) If feature is enabled post delayed task to start
+  // migration.
 }
 
 void PasswordStoreBackendMigrationDecorator::Shutdown(
@@ -132,4 +135,9 @@
   return active_backend_->CreateSyncControllerDelegateFactory();
 }
 
+void PasswordStoreBackendMigrationDecorator::StartMigration() {
+  migrator_ = std::make_unique<BuiltInBackendToAndroidBackendMigrator>();
+  migrator_->StartMigrationIfNecessary();
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_store_backend_migration_decorator.h b/components/password_manager/core/browser/password_store_backend_migration_decorator.h
index 39a8c2f..21c536a 100644
--- a/components/password_manager/core/browser/password_store_backend_migration_decorator.h
+++ b/components/password_manager/core/browser/password_store_backend_migration_decorator.h
@@ -12,6 +12,8 @@
 
 namespace password_manager {
 
+class BuiltInBackendToAndroidBackendMigrator;
+
 // This is the backend that should be used on Android platform until the full
 // migration to the Android backend is launched. Internally, this backend
 // owns two backends: the built-in and the Android backend. In addition
@@ -69,11 +71,16 @@
   std::unique_ptr<syncer::ProxyModelTypeControllerDelegate>
   CreateSyncControllerDelegateFactory() override;
 
+  // Creates 'migrator_' and starts migration process.
+  void StartMigration();
+
   std::unique_ptr<PasswordStoreBackend> built_in_backend_;
   std::unique_ptr<PasswordStoreBackend> android_backend_;
 
   // Proxy backend to which all responsibilities are being delegated.
   std::unique_ptr<PasswordStoreBackend> active_backend_;
+
+  std::unique_ptr<BuiltInBackendToAndroidBackendMigrator> migrator_;
 };
 
 }  // namespace password_manager
diff --git a/components/payments/content/android/BUILD.gn b/components/payments/content/android/BUILD.gn
index 03eaa25..2aa2881 100644
--- a/components/payments/content/android/BUILD.gn
+++ b/components/payments/content/android/BUILD.gn
@@ -212,6 +212,7 @@
     "//ui/android:ui_utils_java",
     "//url:gurl_java",
     "//url:origin_java",
+    "//url/mojom:url_mojom_origin_java",
   ]
   srcjar_deps = [
     ":payments_journey_logger_enum_javagen",
@@ -318,6 +319,7 @@
     "//url:gurl_java",
     "//url:gurl_junit_test_support",
     "//url:origin_java",
+    "//url/mojom:url_mojom_origin_java",
   ]
 }
 
@@ -352,6 +354,7 @@
     "//url:gurl_java",
     "//url:gurl_junit_test_support",
     "//url:origin_java",
+    "//url/mojom:url_mojom_origin_java",
   ]
 }
 
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
index 4293837..f468798 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
@@ -533,8 +533,7 @@
         if (PaymentFeatureList.isEnabledOrExperimentalFeaturesEnabled(
                     PaymentFeatureList.SECURE_PAYMENT_CONFIRMATION)
                 && methodData.containsKey(MethodStrings.SECURE_PAYMENT_CONFIRMATION)
-                && (methodData.size() > 1 || options.requestPayerEmail || options.requestPayerPhone
-                        || options.requestShipping || options.requestPayerName)) {
+                && !isValidSecurePaymentConfirmationRequest(methodData, options)) {
             mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
             disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
                     PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
@@ -574,6 +573,22 @@
         return true;
     }
 
+    private boolean isValidSecurePaymentConfirmationRequest(
+            Map<String, PaymentMethodData> methodData, PaymentOptions options) {
+        if (methodData.size() > 1) return false;
+        if (options.requestPayerEmail || options.requestPayerPhone || options.requestShipping
+                || options.requestPayerName) {
+            return false;
+        }
+        PaymentMethodData spcMethodData = methodData.get(MethodStrings.SECURE_PAYMENT_CONFIRMATION);
+        if (spcMethodData.securePaymentConfirmation == null) return false;
+        if (spcMethodData.securePaymentConfirmation.payeeOrigin == null) return false;
+        Origin origin = new Origin(spcMethodData.securePaymentConfirmation.payeeOrigin);
+        if (origin.isOpaque()) return false;
+        if (origin.getScheme() == null) return false;
+        return origin.getScheme().equals("https");
+    }
+
     private void startPaymentAppService() {
         PaymentAppService service = mDelegate.getPaymentAppService();
         mBrowserPaymentRequest.addPaymentAppFactories(service, /*delegate=*/this);
@@ -893,10 +908,13 @@
             assert mSpcAuthnUiController == null;
 
             mSpcAuthnUiController = SecurePaymentConfirmationAuthnController.create(mWebContents);
+            PaymentMethodData spcMethodData =
+                    mSpec.getMethodData().get(MethodStrings.SECURE_PAYMENT_CONFIRMATION);
+            assert spcMethodData != null;
             boolean success = mSpcAuthnUiController.show(
                     mBrowserPaymentRequest.getSelectedPaymentApp().getDrawableIcon(),
-                    mBrowserPaymentRequest.getSelectedPaymentApp().getLabel(), getRawTotal(),
-                    (response) -> {
+                    mBrowserPaymentRequest.getSelectedPaymentApp().getLabel(),
+                    getRawTotal(), (response) -> {
                         if (response) {
                             onSecurePaymentConfirmationUiAccepted(
                                     mBrowserPaymentRequest.getSelectedPaymentApp());
@@ -908,7 +926,7 @@
                         }
 
                         mSpcAuthnUiController = null;
-                    });
+                    }, new Origin(spcMethodData.securePaymentConfirmation.payeeOrigin));
 
             if (success) {
                 mJourneyLogger.setShown();
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnController.java b/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnController.java
index bb2cd81..8ef6320 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnController.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnController.java
@@ -23,6 +23,7 @@
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.url.Origin;
 
 import java.util.Locale;
 
@@ -140,9 +141,10 @@
      * @param paymentInstrumentLabel The label to display for the payment instrument.
      * @param total The total amount of the transaction.
      * @param callback The function to call on sheet dismiss; false if it failed.
+     * @param payeeOrigin The origin of the payee.
      */
     public boolean show(Drawable paymentIcon, String paymentInstrumentLabel, PaymentItem total,
-            Callback<Boolean> callback) {
+            Callback<Boolean> callback, Origin payeeOrigin) {
         if (mHider != null) return false;
 
         WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow();
@@ -155,8 +157,7 @@
 
         PropertyModel model =
                 new PropertyModel.Builder(SecurePaymentConfirmationAuthnProperties.ALL_KEYS)
-                        .with(SecurePaymentConfirmationAuthnProperties.STORE_ORIGIN,
-                                mWebContents.getVisibleUrl().getOrigin())
+                        .with(SecurePaymentConfirmationAuthnProperties.STORE_ORIGIN, payeeOrigin)
                         .with(SecurePaymentConfirmationAuthnProperties.PAYMENT_ICON, paymentIcon)
                         .with(SecurePaymentConfirmationAuthnProperties.PAYMENT_INSTRUMENT_LABEL,
                                 paymentInstrumentLabel)
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnProperties.java b/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnProperties.java
index bff6e9b..65743fb 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnProperties.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnProperties.java
@@ -8,7 +8,7 @@
 
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.ReadableObjectPropertyKey;
-import org.chromium.url.GURL;
+import org.chromium.url.Origin;
 
 /**
  * The properties of the SecurePaymentConfirmation Authn UI, which fully describe the state of the
@@ -16,7 +16,7 @@
  */
 /* package */ class SecurePaymentConfirmationAuthnProperties {
     /** The store value of the UI. */
-    /* package */ static final ReadableObjectPropertyKey<GURL> STORE_ORIGIN =
+    /* package */ static final ReadableObjectPropertyKey<Origin> STORE_ORIGIN =
             new ReadableObjectPropertyKey<>();
 
     /** The payment icon for the UI. */
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnViewBinder.java b/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnViewBinder.java
index 1b0cf11..7360990 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnViewBinder.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnViewBinder.java
@@ -17,7 +17,7 @@
     /* package */ static void bind(
             PropertyModel model, SecurePaymentConfirmationAuthnView view, PropertyKey propertyKey) {
         if (SecurePaymentConfirmationAuthnProperties.STORE_ORIGIN == propertyKey) {
-            String origin = UrlFormatter.formatUrlForSecurityDisplay(
+            String origin = UrlFormatter.formatOriginForSecurityDisplay(
                     model.get(SecurePaymentConfirmationAuthnProperties.STORE_ORIGIN),
                     SchemeDisplay.OMIT_HTTP_AND_HTTPS);
             view.mStoreOrigin.setText(origin);
diff --git a/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceTest.java b/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceTest.java
index 128840a..ba52106e 100644
--- a/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceTest.java
+++ b/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceTest.java
@@ -712,6 +712,37 @@
                 PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
     }
 
+    @Test
+    @Feature({"Payments"})
+    public void testSpcCanOnlyBeRequestedAlone_failedForNullPayeeUrl() {
+        ShadowPaymentFeatureList.setFeatureEnabled(
+                PaymentFeatureList.SECURE_PAYMENT_CONFIRMATION, true);
+        Assert.assertNull(defaultBuilder()
+                                  .setPayeeOrigin(null)
+                                  .setOnlySpcMethodWithoutPaymentOptions()
+                                  .build());
+        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
+                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testSpcCanOnlyBeRequestedAlone_failedForHttpPayeeUrl() {
+        ShadowPaymentFeatureList.setFeatureEnabled(
+                PaymentFeatureList.SECURE_PAYMENT_CONFIRMATION, true);
+        org.chromium.url.internal.mojom.Origin payeeOrigin =
+                new org.chromium.url.internal.mojom.Origin();
+        payeeOrigin.scheme = "http";
+        payeeOrigin.host = "www.example.com";
+        payeeOrigin.port = 443;
+        Assert.assertNull(defaultBuilder()
+                                  .setPayeeOrigin(payeeOrigin)
+                                  .setOnlySpcMethodWithoutPaymentOptions()
+                                  .build());
+        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
+                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
+    }
+
     // The restriction is imposed only when the SPC flag is enabled.
     @Test
     @Feature({"Payments"})
diff --git a/components/payments/content/android/junit/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnTest.java b/components/payments/content/android/junit/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnTest.java
index 4c8b3970..e616eee 100644
--- a/components/payments/content/android/junit/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnTest.java
+++ b/components/payments/content/android/junit/src/org/chromium/components/payments/secure_payment_confirmation/SecurePaymentConfirmationAuthnTest.java
@@ -39,7 +39,7 @@
 import org.chromium.payments.mojom.PaymentCurrencyAmount;
 import org.chromium.payments.mojom.PaymentItem;
 import org.chromium.ui.base.WindowAndroid;
-import org.chromium.url.GURL;
+import org.chromium.url.Origin;
 
 import java.lang.ref.WeakReference;
 
@@ -86,7 +86,6 @@
         Mockito.doReturn(new WeakReference<Context>(RuntimeEnvironment.application))
                 .when(windowAndroid)
                 .getContext();
-        Mockito.when(mWebContents.getVisibleUrl().getOrigin()).thenReturn(GURL.emptyGURL());
 
         // Create formatter mocks
         UrlFormatter.Natives urlFormatterJniMock = Mockito.mock(UrlFormatter.Natives.class);
@@ -141,7 +140,10 @@
 
         mIsPaymentConfirmed = false;
         mIsPaymentCancelled = false;
-        return mAuthnController.show(mDrawable, "paymentInstrumentLabel", mTotal, mCallback);
+        org.chromium.url.internal.mojom.Origin mojoOrigin =
+                new org.chromium.url.internal.mojom.Origin();
+        return mAuthnController.show(
+                mDrawable, "paymentInstrumentLabel", mTotal, mCallback, new Origin(mojoOrigin));
     }
 
     private void setWindowAndroid(WindowAndroid windowAndroid, WebContents webContents) {
diff --git a/components/payments/content/android/junit/src/org/chromium/components/payments/test_support/PaymentRequestServiceBuilder.java b/components/payments/content/android/junit/src/org/chromium/components/payments/test_support/PaymentRequestServiceBuilder.java
index 8e7ef8b..c0b0491 100644
--- a/components/payments/content/android/junit/src/org/chromium/components/payments/test_support/PaymentRequestServiceBuilder.java
+++ b/components/payments/content/android/junit/src/org/chromium/components/payments/test_support/PaymentRequestServiceBuilder.java
@@ -23,6 +23,7 @@
 import org.chromium.payments.mojom.PaymentMethodData;
 import org.chromium.payments.mojom.PaymentOptions;
 import org.chromium.payments.mojom.PaymentRequestClient;
+import org.chromium.payments.mojom.SecurePaymentConfirmationRequest;
 import org.chromium.url.GURL;
 import org.chromium.url.JUnitTestGURLs;
 import org.chromium.url.Origin;
@@ -53,6 +54,7 @@
     private boolean mIsOriginAllowedToUseWebPaymentApis = true;
     private boolean mIsPaymentDetailsValid = true;
     private PaymentRequestSpec mSpec;
+    private SecurePaymentConfirmationRequest mSecurePaymentConfirmationRequest;
 
     public static PaymentRequestServiceBuilder defaultBuilder(Runnable onClosedListener,
             PaymentRequestClient client, PaymentAppService appService,
@@ -92,6 +94,13 @@
         mOnClosedListener = onClosedListener;
         mClient = client;
         mPaymentAppService = appService;
+        mSecurePaymentConfirmationRequest = new SecurePaymentConfirmationRequest();
+        org.chromium.url.internal.mojom.Origin payeeOrigin =
+                new org.chromium.url.internal.mojom.Origin();
+        payeeOrigin.scheme = "https";
+        payeeOrigin.host = "chromium.org";
+        payeeOrigin.port = 443;
+        mSecurePaymentConfirmationRequest.payeeOrigin = payeeOrigin;
     }
 
     @Override
@@ -224,10 +233,18 @@
     }
 
     public PaymentRequestServiceBuilder setOnlySpcMethodWithoutPaymentOptions() {
+        mOptions = new PaymentOptions();
         mMethodData = new PaymentMethodData[1];
         mMethodData[0] = new PaymentMethodData();
         mMethodData[0].supportedMethod = MethodStrings.SECURE_PAYMENT_CONFIRMATION;
-        mOptions = new PaymentOptions();
+
+        mMethodData[0].securePaymentConfirmation = mSecurePaymentConfirmationRequest;
+        return this;
+    }
+
+    public PaymentRequestServiceBuilder setPayeeOrigin(
+            org.chromium.url.internal.mojom.Origin payeeOrigin) {
+        mSecurePaymentConfirmationRequest.payeeOrigin = payeeOrigin;
         return this;
     }
 
diff --git a/components/prefs/in_memory_pref_store.cc b/components/prefs/in_memory_pref_store.cc
index 1f9b76f3..662e7a1 100644
--- a/components/prefs/in_memory_pref_store.cc
+++ b/components/prefs/in_memory_pref_store.cc
@@ -7,7 +7,6 @@
 #include <memory>
 #include <utility>
 
-#include "base/notreached.h"
 #include "base/values.h"
 
 InMemoryPrefStore::InMemoryPrefStore() {}
@@ -81,12 +80,6 @@
   return PersistentPrefStore::PREF_READ_ERROR_NONE;
 }
 
-void InMemoryPrefStore::CommitPendingWriteSynchronously() {
-  // This function was added for one very specific use case and is intentionally
-  // not implemented for other pref stores.
-  NOTREACHED();
-}
-
 void InMemoryPrefStore::ReportValueChanged(const std::string& key,
                                            uint32_t flags) {
   for (Observer& observer : observers_)
diff --git a/components/prefs/in_memory_pref_store.h b/components/prefs/in_memory_pref_store.h
index 682b602..6f8bc490 100644
--- a/components/prefs/in_memory_pref_store.h
+++ b/components/prefs/in_memory_pref_store.h
@@ -49,7 +49,6 @@
   PrefReadError GetReadError() const override;
   PersistentPrefStore::PrefReadError ReadPrefs() override;
   void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override {}
-  void CommitPendingWriteSynchronously() override;
   void SchedulePendingLossyWrites() override {}
   void ClearMutableValues() override {}
   void OnStoreDeletionFromDisk() override {}
diff --git a/components/prefs/json_pref_store.cc b/components/prefs/json_pref_store.cc
index 6ba38dd..e7afbd2 100644
--- a/components/prefs/json_pref_store.cc
+++ b/components/prefs/json_pref_store.cc
@@ -321,28 +321,6 @@
   }
 }
 
-void JsonPrefStore::CommitPendingWriteSynchronously() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  // Schedule a write for any lossy writes that are outstanding to ensure that
-  // they get flushed when this function is called.
-  SchedulePendingLossyWrites();
-  if (!writer_.HasPendingWrite() || read_only_)
-    return;
-
-  const base::FilePath path = writer_.path();
-  std::string data;
-  if (!SerializeData(&data)) {
-    DVLOG(1) << "Failed to serialize data to be saved in " << path.value();
-    return;
-  }
-
-  const std::string suffix = GetHistogramSuffix(path);
-  if (!base::ImportantFileWriter::WriteFileAtomically(path, data, suffix)) {
-    DVLOG(1) << "Could not write " << suffix << " into " << path.value();
-  }
-}
-
 void JsonPrefStore::SchedulePendingLossyWrites() {
   if (pending_lossy_write_)
     writer_.ScheduleWrite(this);
diff --git a/components/prefs/json_pref_store.h b/components/prefs/json_pref_store.h
index 749171e..23cec50a 100644
--- a/components/prefs/json_pref_store.h
+++ b/components/prefs/json_pref_store.h
@@ -104,7 +104,6 @@
       base::OnceClosure reply_callback = base::OnceClosure(),
       base::OnceClosure synchronous_done_callback =
           base::OnceClosure()) override;
-  void CommitPendingWriteSynchronously() override;
   void SchedulePendingLossyWrites() override;
   void ReportValueChanged(const std::string& key, uint32_t flags) override;
 
diff --git a/components/prefs/json_pref_store_unittest.cc b/components/prefs/json_pref_store_unittest.cc
index ccc1e6d..bad903ab 100644
--- a/components/prefs/json_pref_store_unittest.cc
+++ b/components/prefs/json_pref_store_unittest.cc
@@ -24,12 +24,10 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/single_thread_task_runner_forward.h"
-#include "base/test/gtest_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_restrictions.h"
 #include "base/values.h"
 #include "components/prefs/persistent_pref_store_unittest.h"
 #include "components/prefs/pref_filter.h"
@@ -575,65 +573,6 @@
     JsonPrefStoreTest,
     ::testing::Values(CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK));
 
-class JsonPrefStoreWriteSynchronouslyTest : public testing::Test {
- public:
-  JsonPrefStoreWriteSynchronouslyTest() = default;
-  ~JsonPrefStoreWriteSynchronouslyTest() override = default;
-
- protected:
-  void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
-
-  base::test::TaskEnvironment task_environment_;
-  base::ScopedTempDir temp_dir_;
-};
-
-TEST_F(JsonPrefStoreWriteSynchronouslyTest,
-       WriteFailsWhenBlockingIsDisallowed) {
-  const char kEmptyPrefContents[] = "{}";
-
-  base::FilePath pref_file = temp_dir_.GetPath().AppendASCII("write.json");
-  ASSERT_LT(0, base::WriteFile(pref_file, kEmptyPrefContents,
-                               base::size(kEmptyPrefContents) - 1));
-
-  auto pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);
-  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
-
-  const std::string test_pref = "test";
-  pref_store->SetValue(test_pref, std::make_unique<Value>(3),
-                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
-  {
-    base::ScopedDisallowBlocking scoped_disallow_blocking;
-    EXPECT_DCHECK_DEATH(pref_store->CommitPendingWriteSynchronously());
-  }
-}
-
-TEST_F(JsonPrefStoreWriteSynchronouslyTest, CommitPendingWriteSynchronously) {
-  const char kInputPrefContents[] =
-      "{\n"
-      "  \"homepage\": \"http://www.cnn.com\"\n"
-      "}";
-
-  const std::string kExpectedPrefContents =
-      "{\"homepage\":\"http://www.cnn.com\",\"test\":3}";
-
-  base::FilePath pref_file = temp_dir_.GetPath().AppendASCII("write.json");
-  ASSERT_LT(0, base::WriteFile(pref_file, kInputPrefContents,
-                               base::size(kInputPrefContents) - 1));
-
-  auto pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);
-  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
-
-  const std::string test_pref = "test";
-  pref_store->SetValue(test_pref, std::make_unique<Value>(3),
-                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
-  pref_store->CommitPendingWriteSynchronously();
-
-  std::string pref_file_contents;
-  ASSERT_TRUE(base::ReadFileToString(pref_file, &pref_file_contents));
-  EXPECT_EQ(kExpectedPrefContents, pref_file_contents);
-  ASSERT_TRUE(base::DeleteFile(pref_file));
-}
-
 class JsonPrefStoreLossyWriteTest : public JsonPrefStoreTest {
  public:
   JsonPrefStoreLossyWriteTest() = default;
diff --git a/components/prefs/overlay_user_pref_store.cc b/components/prefs/overlay_user_pref_store.cc
index a8b27a3..7861736 100644
--- a/components/prefs/overlay_user_pref_store.cc
+++ b/components/prefs/overlay_user_pref_store.cc
@@ -9,7 +9,6 @@
 #include <utility>
 
 #include "base/memory/ptr_util.h"
-#include "base/notreached.h"
 #include "base/values.h"
 #include "components/prefs/in_memory_pref_store.h"
 
@@ -194,12 +193,6 @@
   // We do not write our content intentionally.
 }
 
-void OverlayUserPrefStore::CommitPendingWriteSynchronously() {
-  // This function was added for one very specific use case and is intentionally
-  // not implemented for other pref stores.
-  NOTREACHED();
-}
-
 void OverlayUserPrefStore::SchedulePendingLossyWrites() {
   persistent_user_pref_store_->SchedulePendingLossyWrites();
 }
diff --git a/components/prefs/overlay_user_pref_store.h b/components/prefs/overlay_user_pref_store.h
index 2da006c2..90cddab 100644
--- a/components/prefs/overlay_user_pref_store.h
+++ b/components/prefs/overlay_user_pref_store.h
@@ -63,7 +63,6 @@
   void ReadPrefsAsync(ReadErrorDelegate* delegate) override;
   void CommitPendingWrite(base::OnceClosure reply_callback,
                           base::OnceClosure synchronous_done_callback) override;
-  void CommitPendingWriteSynchronously() override;
   void SchedulePendingLossyWrites() override;
   void ReportValueChanged(const std::string& key, uint32_t flags) override;
 
diff --git a/components/prefs/persistent_pref_store.h b/components/prefs/persistent_pref_store.h
index 2b1f80b0..df395051 100644
--- a/components/prefs/persistent_pref_store.h
+++ b/components/prefs/persistent_pref_store.h
@@ -71,12 +71,6 @@
       base::OnceClosure reply_callback = base::OnceClosure(),
       base::OnceClosure synchronous_done_callback = base::OnceClosure());
 
-  // Like CommitPendingWrite(), but writes to disk on this thread synchronously
-  // rather than scheduling a write. CommitPendingWriteSynchronously() is
-  // appropriate to call only in the exceptional situation in which you need to
-  // write to disk early on during startup before threads have been started.
-  virtual void CommitPendingWriteSynchronously() = 0;
-
   // Schedules a write if there is any lossy data pending. Unlike
   // CommitPendingWrite() this does not immediately sync to disk, instead it
   // triggers an eventual write if there is lossy data pending and if there
diff --git a/components/prefs/pref_service.cc b/components/prefs/pref_service.cc
index b2cf2462..e6e45d5 100644
--- a/components/prefs/pref_service.cc
+++ b/components/prefs/pref_service.cc
@@ -712,8 +712,3 @@
   DCHECK(value) << "Trying to read an unregistered pref: " << path;
   return value;
 }
-
-void PrefService::CommitPendingWriteSynchronously() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  user_pref_store_->CommitPendingWriteSynchronously();
-}
diff --git a/components/prefs/pref_service.h b/components/prefs/pref_service.h
index e0eac20..2c362d0c 100644
--- a/components/prefs/pref_service.h
+++ b/components/prefs/pref_service.h
@@ -469,12 +469,6 @@
   const base::Value* GetPreferenceValue(const std::string& path) const;
   const base::Value* GetPreferenceValueChecked(const std::string& path) const;
 
-  // Like CommitPendingWrite(), but writes to disk on this thread synchronously
-  // rather than scheduling a write. CommitPendingWriteSynchronously() is
-  // appropriate to call only in the exceptional situation in which you need to
-  // write to disk early on during startup before threads have been started.
-  void CommitPendingWriteSynchronously();
-
   const scoped_refptr<PrefRegistry> pref_registry_;
 
   // Local cache of registered Preference objects. The pref_registry_
diff --git a/components/prefs/segregated_pref_store.cc b/components/prefs/segregated_pref_store.cc
index 6686ef1..1f3ccc52 100644
--- a/components/prefs/segregated_pref_store.cc
+++ b/components/prefs/segregated_pref_store.cc
@@ -195,12 +195,6 @@
                                            synchronous_callback_wrapper);
 }
 
-void SegregatedPrefStore::CommitPendingWriteSynchronously() {
-  // This function was added for one very specific use case and is intentionally
-  // not implemented for other pref stores.
-  NOTREACHED();
-}
-
 void SegregatedPrefStore::SchedulePendingLossyWrites() {
   default_pref_store_->SchedulePendingLossyWrites();
   selected_pref_store_->SchedulePendingLossyWrites();
diff --git a/components/prefs/segregated_pref_store.h b/components/prefs/segregated_pref_store.h
index 8b820d0..cfe90ee7 100644
--- a/components/prefs/segregated_pref_store.h
+++ b/components/prefs/segregated_pref_store.h
@@ -76,7 +76,6 @@
       base::OnceClosure reply_callback = base::OnceClosure(),
       base::OnceClosure synchronous_done_callback =
           base::OnceClosure()) override;
-  void CommitPendingWriteSynchronously() override;
   void SchedulePendingLossyWrites() override;
   void ClearMutableValues() override;
   void OnStoreDeletionFromDisk() override;
diff --git a/components/prefs/testing_pref_store.cc b/components/prefs/testing_pref_store.cc
index ce0bd44..5d3d1ec 100644
--- a/components/prefs/testing_pref_store.cc
+++ b/components/prefs/testing_pref_store.cc
@@ -111,12 +111,6 @@
                                           std::move(synchronous_done_callback));
 }
 
-void TestingPrefStore::CommitPendingWriteSynchronously() {
-  // This function was added for one very specific use case and is intentionally
-  // not implemented for other pref stores.
-  NOTREACHED();
-}
-
 void TestingPrefStore::SchedulePendingLossyWrites() {}
 
 void TestingPrefStore::SetInitializationCompleted() {
diff --git a/components/prefs/testing_pref_store.h b/components/prefs/testing_pref_store.h
index cc23eedb..5a4348e 100644
--- a/components/prefs/testing_pref_store.h
+++ b/components/prefs/testing_pref_store.h
@@ -51,7 +51,6 @@
   void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
   void CommitPendingWrite(base::OnceClosure reply_callback,
                           base::OnceClosure synchronous_done_callback) override;
-  void CommitPendingWriteSynchronously() override;
   void SchedulePendingLossyWrites() override;
 
   // Marks the store as having completed initialization.
diff --git a/components/safe_browsing/content/browser/password_protection/mock_password_protection_service.h b/components/safe_browsing/content/browser/password_protection/mock_password_protection_service.h
index aac6fae..4acad7a 100644
--- a/components/safe_browsing/content/browser/password_protection/mock_password_protection_service.h
+++ b/components/safe_browsing/content/browser/password_protection/mock_password_protection_service.h
@@ -119,6 +119,8 @@
            PasswordType,
            const std::vector<password_manager::MatchingReusedCredential>&,
            bool));
+  MOCK_CONST_METHOD0(GetUserPopulationPref,
+                     ChromeUserPopulation::UserPopulation());
 };
 
 }  // namespace safe_browsing
diff --git a/components/safe_browsing/content/browser/user_population.cc b/components/safe_browsing/content/browser/user_population.cc
index 2d6b3f13..5dafc13 100644
--- a/components/safe_browsing/content/browser/user_population.cc
+++ b/components/safe_browsing/content/browser/user_population.cc
@@ -14,6 +14,20 @@
 
 namespace safe_browsing {
 
+ChromeUserPopulation::UserPopulation GetUserPopulationPref(PrefService* prefs) {
+  if (prefs) {
+    if (IsEnhancedProtectionEnabled(*prefs)) {
+      return ChromeUserPopulation::ENHANCED_PROTECTION;
+    } else if (IsExtendedReportingEnabled(*prefs)) {
+      return ChromeUserPopulation::EXTENDED_REPORTING;
+    } else if (IsSafeBrowsingEnabled(*prefs)) {
+      return ChromeUserPopulation::SAFE_BROWSING;
+    }
+  }
+
+  return ChromeUserPopulation::UNKNOWN_USER_POPULATION;
+}
+
 ChromeUserPopulation GetUserPopulation(
     PrefService* prefs,
     bool is_incognito,
@@ -25,15 +39,9 @@
     absl::optional<size_t> num_open_profiles) {
   ChromeUserPopulation population;
 
-  if (prefs) {
-    if (IsEnhancedProtectionEnabled(*prefs)) {
-      population.set_user_population(ChromeUserPopulation::ENHANCED_PROTECTION);
-    } else if (IsExtendedReportingEnabled(*prefs)) {
-      population.set_user_population(ChromeUserPopulation::EXTENDED_REPORTING);
-    } else if (IsSafeBrowsingEnabled(*prefs)) {
-      population.set_user_population(ChromeUserPopulation::SAFE_BROWSING);
-    }
+  population.set_user_population(GetUserPopulationPref(prefs));
 
+  if (prefs) {
     population.set_is_mbb_enabled(prefs->GetBoolean(
         unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
   }
diff --git a/components/safe_browsing/content/browser/user_population.h b/components/safe_browsing/content/browser/user_population.h
index 62f4a0ea..21c56e42 100644
--- a/components/safe_browsing/content/browser/user_population.h
+++ b/components/safe_browsing/content/browser/user_population.h
@@ -16,6 +16,9 @@
 
 namespace safe_browsing {
 
+// Returns the UserPopulation enum for the given prefs
+ChromeUserPopulation::UserPopulation GetUserPopulationPref(PrefService* prefs);
+
 // Creates a ChromeUserPopulation proto for the given state.
 ChromeUserPopulation GetUserPopulation(
     // The below may be null.
diff --git a/components/safe_browsing/core/browser/password_protection/password_protection_request.cc b/components/safe_browsing/core/browser/password_protection/password_protection_request.cc
index ba7e761..f78790b 100644
--- a/components/safe_browsing/core/browser/password_protection/password_protection_request.cc
+++ b/components/safe_browsing/core/browser/password_protection/password_protection_request.cc
@@ -7,6 +7,7 @@
 #include <cstddef>
 
 #include "base/bind.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/strcat.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/safe_browsing/core/browser/db/allowlist_checker_client.h"
@@ -109,6 +110,17 @@
 
 void PasswordProtectionRequest::Start() {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
+  if (trigger_type_ == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) {
+    base::UmaHistogramExactLinear(
+        "PasswordProtection.OnFocus.UserPopulationStart",
+        password_protection_service_->GetUserPopulationPref(),
+        ChromeUserPopulation::UserPopulation_MAX + 1);
+  } else {
+    base::UmaHistogramExactLinear(
+        "PasswordProtection.PasswordEntry.UserPopulationStart",
+        password_protection_service_->GetUserPopulationPref(),
+        ChromeUserPopulation::UserPopulation_MAX + 1);
+  }
   CheckAllowlist();
 }
 
@@ -321,6 +333,19 @@
 
 void PasswordProtectionRequest::SendRequest() {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
+
+  if (trigger_type_ == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) {
+    base::UmaHistogramExactLinear(
+        "PasswordProtection.OnFocus.UserPopulationOnPing",
+        password_protection_service_->GetUserPopulationPref(),
+        ChromeUserPopulation::UserPopulation_MAX + 1);
+  } else {
+    base::UmaHistogramExactLinear(
+        "PasswordProtection.PasswordEntry.UserPopulationOnPing",
+        password_protection_service_->GetUserPopulationPref(),
+        ChromeUserPopulation::UserPopulation_MAX + 1);
+  }
+
   if (password_protection_service_->CanGetAccessToken() &&
       password_protection_service_->token_fetcher()) {
     password_protection_service_->token_fetcher()->Start(
diff --git a/components/safe_browsing/core/browser/password_protection/password_protection_service_base.h b/components/safe_browsing/core/browser/password_protection/password_protection_service_base.h
index 3026347..d201c3ce 100644
--- a/components/safe_browsing/core/browser/password_protection/password_protection_service_base.h
+++ b/components/safe_browsing/core/browser/password_protection/password_protection_service_base.h
@@ -245,6 +245,10 @@
   // Returns the URL where PasswordProtectionRequest instances send requests.
   static GURL GetPasswordProtectionRequestUrl();
 
+  // Gets the UserPopulation value for this profile.
+  virtual ChromeUserPopulation::UserPopulation GetUserPopulationPref()
+      const = 0;
+
  protected:
   friend class PasswordProtectionRequest;
   friend class PasswordProtectionRequestContent;
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge.cc b/components/send_tab_to_self/send_tab_to_self_bridge.cc
index 5ed63fb..f6e17045 100644
--- a/components/send_tab_to_self/send_tab_to_self_bridge.cc
+++ b/components/send_tab_to_self/send_tab_to_self_bridge.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/check_op.h"
+#include "base/containers/cxx20_erase_vector.h"
 #include "base/guid.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_util.h"
@@ -24,7 +25,6 @@
 #include "components/sync/model/model_type_change_processor.h"
 #include "components/sync/model/mutable_data_batch.h"
 #include "components/sync/protocol/model_type_state.pb.h"
-#include "components/sync_device_info/device_info_tracker.h"
 #include "components/sync_device_info/local_device_info_util.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -99,6 +99,9 @@
   if (history_service) {
     history_service_observation_.Observe(history_service);
   }
+  device_info_tracker_observation_.Observe(device_info_tracker);
+
+  ComputeTargetDeviceInfoSortedList();
 
   std::move(create_store_callback)
       .Run(syncer::SEND_TAB_TO_SELF,
@@ -433,23 +436,33 @@
   DeleteAllEntries();
 }
 
+void SendTabToSelfBridge::OnDeviceInfoChange() {
+  ComputeTargetDeviceInfoSortedList();
+}
+
 bool SendTabToSelfBridge::IsReady() {
   return change_processor()->IsTrackingMetadata();
 }
 
 bool SendTabToSelfBridge::HasValidTargetDevice() {
-  if (ShouldUpdateTargetDeviceInfoList()) {
-    SetTargetDeviceInfoList();
-  }
-  return target_device_name_to_cache_info_.size() > 0;
+  return GetTargetDeviceInfoSortedList().size() > 0;
 }
 
 std::vector<TargetDeviceInfo>
 SendTabToSelfBridge::GetTargetDeviceInfoSortedList() {
-  if (ShouldUpdateTargetDeviceInfoList()) {
-    SetTargetDeviceInfoList();
-  }
-  return target_device_name_to_cache_info_;
+  // Filter expired devices (some timestamps in the cached list may now be too
+  // old). |target_device_info_sorted_list_| is copied here to avoid mutations
+  // inside a getter.
+  // TODO(crbug.com/1257573): Consider having a timer that fires on the next
+  // expiry and removes the corresponding device(s) then.
+  std::vector<TargetDeviceInfo> non_expired_devices =
+      target_device_info_sorted_list_;
+  const base::Time now = clock_->Now();
+  base::EraseIf(non_expired_devices, [now](const TargetDeviceInfo& device) {
+    return now - device.last_updated_timestamp > kDeviceExpiration;
+  });
+
+  return non_expired_devices;
 }
 
 // static
@@ -459,10 +472,6 @@
   return std::move(bridge->store_);
 }
 
-bool SendTabToSelfBridge::ShouldUpdateTargetDeviceInfoListForTest() {
-  return ShouldUpdateTargetDeviceInfoList();
-}
-
 void SendTabToSelfBridge::SetLocalDeviceNameForTest(
     const std::string& local_device_name) {
   local_device_name_ = local_device_name;
@@ -618,28 +627,13 @@
   NotifyRemoteSendTabToSelfEntryDeleted(removed);
 }
 
-bool SendTabToSelfBridge::ShouldUpdateTargetDeviceInfoList() const {
+void SendTabToSelfBridge::ComputeTargetDeviceInfoSortedList() {
   if (!device_info_tracker_->IsSyncing()) {
-    return false;
+    return;
   }
 
-  // The vector should be updated if any of these is true:
-  //   * The vector is empty.
-  //   * The number of total devices changed.
-  //   * The oldest non-expired entry in the vector is now expired.
-  return target_device_name_to_cache_info_.empty() ||
-         device_info_tracker_->GetAllDeviceInfo().size() !=
-             number_of_devices_ ||
-         clock_->Now() - oldest_non_expired_device_timestamp_ >
-             kDeviceExpiration;
-}
-
-void SendTabToSelfBridge::SetTargetDeviceInfoList() {
-  DCHECK(device_info_tracker_->IsSyncing());
-
   std::vector<std::unique_ptr<syncer::DeviceInfo>> all_devices =
       device_info_tracker_->GetAllDeviceInfo();
-  number_of_devices_ = all_devices.size();
 
   // Sort the DeviceInfo vector so the most recently modified devices are first.
   std::stable_sort(all_devices.begin(), all_devices.end(),
@@ -649,7 +643,7 @@
                             device2->last_updated_timestamp();
                    });
 
-  target_device_name_to_cache_info_.clear();
+  target_device_info_sorted_list_.clear();
   std::set<std::string> unique_device_names;
   std::unordered_map<std::string, int> short_names_counter;
   for (const auto& device : all_devices) {
@@ -686,13 +680,12 @@
       TargetDeviceInfo target_device_info(
           device_names.full_name, device_names.short_name, device->guid(),
           device->device_type(), device->last_updated_timestamp());
-      target_device_name_to_cache_info_.push_back(target_device_info);
-      oldest_non_expired_device_timestamp_ = device->last_updated_timestamp();
+      target_device_info_sorted_list_.push_back(target_device_info);
 
       short_names_counter[device_names.short_name]++;
     }
   }
-  for (auto& device_info : target_device_name_to_cache_info_) {
+  for (auto& device_info : target_device_info_sorted_list_) {
     bool unique_short_name = short_names_counter[device_info.short_name] == 1;
     device_info.device_name =
         (unique_short_name ? device_info.short_name : device_info.full_name);
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge.h b/components/send_tab_to_self/send_tab_to_self_bridge.h
index 648c797e..67bb1ab 100644
--- a/components/send_tab_to_self/send_tab_to_self_bridge.h
+++ b/components/send_tab_to_self/send_tab_to_self_bridge.h
@@ -20,9 +20,9 @@
 #include "components/sync/base/model_type.h"
 #include "components/sync/model/model_type_store.h"
 #include "components/sync/model/model_type_sync_bridge.h"
+#include "components/sync_device_info/device_info_tracker.h"
 
 namespace syncer {
-class DeviceInfoTracker;
 class ModelTypeChangeProcessor;
 }  // namespace syncer
 
@@ -38,6 +38,7 @@
 // All interface methods have to be called on main thread.
 class SendTabToSelfBridge : public syncer::ModelTypeSyncBridge,
                             public SendTabToSelfModel,
+                            public syncer::DeviceInfoTracker::Observer,
                             public history::HistoryServiceObserver {
  public:
   // The caller should ensure that all raw pointers are not null and will
@@ -91,10 +92,12 @@
   void OnURLsDeleted(history::HistoryService* history_service,
                      const history::DeletionInfo& deletion_info) override;
 
+  // syncer::DeviceInfoTracker::Observer overrides.
+  void OnDeviceInfoChange() override;
+
   // For testing only.
   static std::unique_ptr<syncer::ModelTypeStore> DestroyAndStealStoreForTest(
       std::unique_ptr<SendTabToSelfBridge> bridge);
-  bool ShouldUpdateTargetDeviceInfoListForTest();
   void SetLocalDeviceNameForTest(const std::string& local_device_name);
 
  private:
@@ -139,11 +142,7 @@
   // Delete expired entries.
   void DoGarbageCollection();
 
-  // Returns whether the target device info list should be updated.
-  bool ShouldUpdateTargetDeviceInfoList() const;
-
-  // Sets the target device info list.
-  void SetTargetDeviceInfoList();
+  void ComputeTargetDeviceInfoSortedList();
 
   // Remove entry with |guid| from entries, but doesn't call Commit on provided
   // |batch|. This allows multiple for deletions without duplicate batch calls.
@@ -174,16 +173,14 @@
   // A pointer to the most recently used entry used for deduplication.
   const SendTabToSelfEntry* mru_entry_;
 
-  // A list of target devices and their associated cache information.
-  std::vector<TargetDeviceInfo> target_device_name_to_cache_info_;
-
-  // The following two variables are used to determine whether we should update
-  // the target device name to cache guid map.
-  base::Time oldest_non_expired_device_timestamp_;
-  size_t number_of_devices_ = 0;
+  // The list of target devices, deduplicated and sorted by most recently used.
+  std::vector<TargetDeviceInfo> target_device_info_sorted_list_;
 
   base::ScopedObservation<history::HistoryService, HistoryServiceObserver>
       history_service_observation_{this};
+  base::ScopedObservation<syncer::DeviceInfoTracker,
+                          syncer::DeviceInfoTracker::Observer>
+      device_info_tracker_observation_{this};
 
   base::WeakPtrFactory<SendTabToSelfBridge> weak_ptr_factory_{this};
 };
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc b/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc
index 5e5252ad..f75ffe44 100644
--- a/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc
+++ b/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc
@@ -812,14 +812,14 @@
       recent_device->guid(), recent_device->device_type(),
       recent_device->last_updated_timestamp());
 
-  // Set the map by calling it. Make sure it has the 2 devices.
+  // Make sure the list has the 2 devices.
   EXPECT_THAT(bridge()->GetTargetDeviceInfoSortedList(),
               ElementsAre(recent_device_info, older_device_info));
 
   // Advance the time so that the older device expires.
   clock()->Advance(base::Days(5));
 
-  // Make sure only the recent device is in the map.
+  // Make sure only the recent device is in the list.
   EXPECT_THAT(bridge()->GetTargetDeviceInfoSortedList(),
               ElementsAre(recent_device_info));
 }
@@ -834,7 +834,7 @@
       CreateDevice("guid", "name", clock()->Now());
   AddTestDevice(device.get());
 
-  // Set the map by calling it. Make sure it has the device.
+  // Make sure the list has the device.
   TargetDeviceInfo device_info(device->client_name(), device->client_name(),
                                device->guid(), device->device_type(),
                                device->last_updated_timestamp());
@@ -847,7 +847,7 @@
       CreateDevice("new_guid", "new_name", clock()->Now());
   AddTestDevice(new_device.get());
 
-  // Make sure both devices are in the map.
+  // Make sure both devices are in the list.
   TargetDeviceInfo new_device_info(
       new_device->client_name(), new_device->client_name(), new_device->guid(),
       new_device->device_type(), new_device->last_updated_timestamp());
@@ -856,6 +856,48 @@
               ElementsAre(device_info, new_device_info));
 }
 
+// Tests the device list is updated if the last_updated_timestamp of one of
+// them changes. Regression test for crbug.com/1257573.
+TEST_F(SendTabToSelfBridgeTest,
+       GetTargetDeviceInfoSortedList_Updated_LastUpdatedTimestampChanged) {
+  InitializeBridge();
+
+  std::unique_ptr<syncer::DeviceInfo> device1 =
+      CreateDevice("guid1", "name1", clock()->Now() - base::Days(1));
+  AddTestDevice(device1.get());
+  std::unique_ptr<syncer::DeviceInfo> device2_old =
+      CreateDevice("guid2", "name2", clock()->Now() - base::Days(2));
+  AddTestDevice(device2_old.get());
+
+  EXPECT_THAT(
+      bridge()->GetTargetDeviceInfoSortedList(),
+      ElementsAre(
+          TargetDeviceInfo(device1->client_name(), device1->client_name(),
+                           device1->guid(), device1->device_type(),
+                           device1->last_updated_timestamp()),
+          TargetDeviceInfo(device2_old->client_name(),
+                           device2_old->client_name(), device2_old->guid(),
+                           device2_old->device_type(),
+                           device2_old->last_updated_timestamp())));
+
+  // Simulate device 2 being used today.
+  std::unique_ptr<syncer::DeviceInfo> device2_new = CreateDevice(
+      device2_old->guid(), device2_old->client_name(), clock()->Now());
+  device_info_tracker()->Replace(device2_old.get(), device2_new.get());
+
+  // Device 2 is now the most recently used and should be the first on the list.
+  EXPECT_THAT(
+      bridge()->GetTargetDeviceInfoSortedList(),
+      ElementsAre(
+          TargetDeviceInfo(device2_new->client_name(),
+                           device2_new->client_name(), device2_new->guid(),
+                           device2_new->device_type(),
+                           device2_new->last_updated_timestamp()),
+          TargetDeviceInfo(device1->client_name(), device1->client_name(),
+                           device1->guid(), device1->device_type(),
+                           device1->last_updated_timestamp())));
+}
+
 TEST_F(SendTabToSelfBridgeTest, NotifyRemoteSendTabToSelfEntryOpened) {
   InitializeBridge();
   SetLocalDeviceCacheGuid("Device1");
@@ -891,14 +933,13 @@
 }
 
 TEST_F(SendTabToSelfBridgeTest,
-       ShouldNotUpdateTargetDeviceInfoListWhileEmptyDeviceInfo) {
+       ShouldHaveEmptyTargetDeviceInfoListWhileEmptyDeviceInfo) {
   InitializeBridgeWithoutDevice();
   SetLocalDeviceCacheGuid("cache_guid");
 
   ASSERT_FALSE(bridge()->change_processor()->TrackedCacheGuid().empty());
   ASSERT_FALSE(device_info_tracker()->IsSyncing());
 
-  EXPECT_FALSE(bridge()->ShouldUpdateTargetDeviceInfoListForTest());
   EXPECT_FALSE(bridge()->HasValidTargetDevice());
 }
 
diff --git a/components/signin/internal/identity_manager/primary_account_mutator_impl.cc b/components/signin/internal/identity_manager/primary_account_mutator_impl.cc
index 11460ad..25bcd85 100644
--- a/components/signin/internal/identity_manager/primary_account_mutator_impl.cc
+++ b/components/signin/internal/identity_manager/primary_account_mutator_impl.cc
@@ -45,13 +45,13 @@
 
 PrimaryAccountMutatorImpl::~PrimaryAccountMutatorImpl() {}
 
-bool PrimaryAccountMutatorImpl::SetPrimaryAccount(
-    const CoreAccountId& account_id,
-    ConsentLevel consent_level) {
+PrimaryAccountMutator::PrimaryAccountError
+PrimaryAccountMutatorImpl::SetPrimaryAccount(const CoreAccountId& account_id,
+                                             ConsentLevel consent_level) {
   DCHECK(!account_id.empty());
   AccountInfo account_info = account_tracker_->GetAccountInfo(account_id);
   if (account_info.IsEmpty())
-    return false;
+    return PrimaryAccountError::kAccountInfoEmpty;
 
   DCHECK_EQ(account_info.account_id, account_id);
   DCHECK(!account_info.email.empty());
@@ -66,17 +66,17 @@
   DCHECK(is_signin_allowed);
 #endif
   if (!is_signin_allowed)
-    return false;
+    return PrimaryAccountError::kSigninNotAllowed;
 #endif
 
   switch (consent_level) {
     case ConsentLevel::kSync:
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
       if (primary_account_manager_->HasPrimaryAccount(ConsentLevel::kSync))
-        return false;
+        return PrimaryAccountError::kSyncConsentAlreadySet;
 #endif
       primary_account_manager_->SetSyncPrimaryAccountInfo(account_info);
-      return true;
+      return PrimaryAccountError::kNoError;
     case ConsentLevel::kSignin:
 #if BUILDFLAG(IS_CHROMEOS_ASH)
       // On Chrome OS the UPA can only be set once and never removed or changed.
@@ -85,9 +85,10 @@
 #endif
       DCHECK(!primary_account_manager_->HasPrimaryAccount(ConsentLevel::kSync));
       primary_account_manager_->SetUnconsentedPrimaryAccountInfo(account_info);
-      return true;
+      return PrimaryAccountError::kNoError;
   }
-  return false;
+  CHECK(false) << "Unknown consent level: " << static_cast<int>(consent_level);
+  return PrimaryAccountError::kNoError;
 }
 
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/components/signin/internal/identity_manager/primary_account_mutator_impl.h b/components/signin/internal/identity_manager/primary_account_mutator_impl.h
index 0fe8259..c29a46d 100644
--- a/components/signin/internal/identity_manager/primary_account_mutator_impl.h
+++ b/components/signin/internal/identity_manager/primary_account_mutator_impl.h
@@ -29,8 +29,8 @@
   ~PrimaryAccountMutatorImpl() override;
 
   // PrimaryAccountMutator implementation.
-  bool SetPrimaryAccount(const CoreAccountId& account_id,
-                         ConsentLevel consent_level) override;
+  PrimaryAccountError SetPrimaryAccount(const CoreAccountId& account_id,
+                                        ConsentLevel consent_level) override;
   void RevokeSyncConsent(signin_metrics::ProfileSignout source_metric,
                          signin_metrics::SignoutDelete delete_metric) override;
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/components/signin/public/identity_manager/identity_manager.cc b/components/signin/public/identity_manager/identity_manager.cc
index 594d563..0339ff1 100644
--- a/components/signin/public/identity_manager/identity_manager.cc
+++ b/components/signin/public/identity_manager/identity_manager.cc
@@ -76,8 +76,11 @@
         signin_metrics::ACCOUNT_REMOVED_FROM_DEVICE,
         signin_metrics::SignoutDelete::kIgnoreMetric);
   }
-  CHECK(identity_manager->GetPrimaryAccountMutator()->SetPrimaryAccount(
-      device_account_id, ConsentLevel::kSync));
+  PrimaryAccountMutator::PrimaryAccountError error =
+      identity_manager->GetPrimaryAccountMutator()->SetPrimaryAccount(
+          device_account_id, ConsentLevel::kSync);
+  CHECK_EQ(PrimaryAccountMutator::PrimaryAccountError::kNoError, error)
+      << "SetPrimaryAccount error: " << static_cast<int>(error);
   CHECK(identity_manager->HasPrimaryAccount(ConsentLevel::kSync));
   CHECK_EQ(identity_manager->GetPrimaryAccountInfo(ConsentLevel::kSync).gaia,
            device_account.key.id());
diff --git a/components/signin/public/identity_manager/identity_mutator.cc b/components/signin/public/identity_manager/identity_mutator.cc
index 094e440..25619a3 100644
--- a/components/signin/public/identity_manager/identity_mutator.cc
+++ b/components/signin/public/identity_manager/identity_mutator.cc
@@ -30,9 +30,11 @@
       identity_mutator_->GetPrimaryAccountMutator();
   DCHECK(primary_account_mutator);
 
-  return primary_account_mutator->SetPrimaryAccount(
-      ConvertFromJavaCoreAccountId(env, primary_account_id),
-      static_cast<ConsentLevel>(j_consent_level));
+  PrimaryAccountMutator::PrimaryAccountError error =
+      primary_account_mutator->SetPrimaryAccount(
+          ConvertFromJavaCoreAccountId(env, primary_account_id),
+          static_cast<ConsentLevel>(j_consent_level));
+  return error == PrimaryAccountMutator::PrimaryAccountError::kNoError;
 }
 
 bool JniIdentityMutator::ClearPrimaryAccount(JNIEnv* env,
diff --git a/components/signin/public/identity_manager/primary_account_mutator.h b/components/signin/public/identity_manager/primary_account_mutator.h
index 604daa2..4fc1464 100644
--- a/components/signin/public/identity_manager/primary_account_mutator.h
+++ b/components/signin/public/identity_manager/primary_account_mutator.h
@@ -27,6 +27,18 @@
 // available at runtime (thus accessors may return null).
 class PrimaryAccountMutator {
  public:
+  // Error returned by SetPrimaryAccount().
+  enum class PrimaryAccountError {
+    // No error, the operation was successful.
+    kNoError = 0,
+    // Account info is empty.
+    kAccountInfoEmpty = 1,
+    // Sync consent was already set.
+    kSyncConsentAlreadySet = 2,
+    // Sign-in is disallowed.
+    kSigninNotAllowed = 4,
+  };
+
   PrimaryAccountMutator() = default;
   virtual ~PrimaryAccountMutator() = default;
 
@@ -55,8 +67,8 @@
   // (i.e. without implying browser sync consent). Requires that the account
   // is known by the IdentityManager. See README.md for details on the meaning
   // of "unconsented". Returns whether the operation succeeded or not.
-  virtual bool SetPrimaryAccount(const CoreAccountId& account_id,
-                                 ConsentLevel consent_level) = 0;
+  virtual PrimaryAccountError SetPrimaryAccount(const CoreAccountId& account_id,
+                                                ConsentLevel consent_level) = 0;
 
   // Revokes sync consent from the primary account. We distinguish the following
   // cases:
diff --git a/components/signin/public/identity_manager/primary_account_mutator_unittest.cc b/components/signin/public/identity_manager/primary_account_mutator_unittest.cc
index e7c8707..ce93c21 100644
--- a/components/signin/public/identity_manager/primary_account_mutator_unittest.cc
+++ b/components/signin/public/identity_manager/primary_account_mutator_unittest.cc
@@ -131,8 +131,11 @@
       identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync));
   AccountInfo account_info =
       environment.MakeAccountAvailable(kPrimaryAccountEmail);
-  EXPECT_TRUE(primary_account_mutator->SetPrimaryAccount(
-      account_info.account_id, signin::ConsentLevel::kSync));
+  signin::PrimaryAccountMutator::PrimaryAccountError setPrimaryAccountResult =
+      primary_account_mutator->SetPrimaryAccount(account_info.account_id,
+                                                 signin::ConsentLevel::kSync);
+  EXPECT_EQ(signin::PrimaryAccountMutator::PrimaryAccountError::kNoError,
+            setPrimaryAccountResult);
   EXPECT_TRUE(identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync));
   EXPECT_TRUE(identity_manager->HasPrimaryAccountWithRefreshToken(
       signin::ConsentLevel::kSync));
@@ -269,8 +272,11 @@
 
   EXPECT_FALSE(environment.identity_manager()->HasPrimaryAccount(
       signin::ConsentLevel::kSync));
-  EXPECT_TRUE(primary_account_mutator->SetPrimaryAccount(
-      account_info.account_id, signin::ConsentLevel::kSync));
+  signin::PrimaryAccountMutator::PrimaryAccountError setPrimaryAccountResult =
+      primary_account_mutator->SetPrimaryAccount(account_info.account_id,
+                                                 signin::ConsentLevel::kSync);
+  EXPECT_EQ(signin::PrimaryAccountMutator::PrimaryAccountError::kNoError,
+            setPrimaryAccountResult);
 
   EXPECT_TRUE(identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync));
   EXPECT_EQ(identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSync),
@@ -300,8 +306,12 @@
 
   EXPECT_FALSE(
       identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync));
-  EXPECT_FALSE(primary_account_mutator->SetPrimaryAccount(
-      CoreAccountId(kUnknownAccountId), signin::ConsentLevel::kSync));
+  signin::PrimaryAccountMutator::PrimaryAccountError setPrimaryAccountResult =
+      primary_account_mutator->SetPrimaryAccount(
+          CoreAccountId(kUnknownAccountId), signin::ConsentLevel::kSync);
+  EXPECT_EQ(
+      signin::PrimaryAccountMutator::PrimaryAccountError::kAccountInfoEmpty,
+      setPrimaryAccountResult);
 }
 
 // Checks that setting the primary account fails if the account is unknown.
@@ -323,8 +333,12 @@
 
   EXPECT_FALSE(
       identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync));
-  EXPECT_FALSE(primary_account_mutator->SetPrimaryAccount(
-      CoreAccountId(kUnknownAccountId), signin::ConsentLevel::kSync));
+  signin::PrimaryAccountMutator::PrimaryAccountError setPrimaryAccountResult =
+      primary_account_mutator->SetPrimaryAccount(
+          CoreAccountId(kUnknownAccountId), signin::ConsentLevel::kSync);
+  EXPECT_EQ(
+      signin::PrimaryAccountMutator::PrimaryAccountError::kAccountInfoEmpty,
+      setPrimaryAccountResult);
 }
 
 // Checks that trying to set the primary account fails when there is already a
@@ -349,12 +363,18 @@
 
   EXPECT_FALSE(
       identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync));
-  EXPECT_TRUE(primary_account_mutator->SetPrimaryAccount(
-      primary_account_info.account_id, signin::ConsentLevel::kSync));
+  signin::PrimaryAccountMutator::PrimaryAccountError setPrimaryAccountResult =
+      primary_account_mutator->SetPrimaryAccount(
+          primary_account_info.account_id, signin::ConsentLevel::kSync);
+  EXPECT_EQ(signin::PrimaryAccountMutator::PrimaryAccountError::kNoError,
+            setPrimaryAccountResult);
 
   EXPECT_TRUE(identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync));
-  EXPECT_FALSE(primary_account_mutator->SetPrimaryAccount(
-      another_account_info.account_id, signin::ConsentLevel::kSync));
+  setPrimaryAccountResult = primary_account_mutator->SetPrimaryAccount(
+      another_account_info.account_id, signin::ConsentLevel::kSync);
+  EXPECT_EQ(signin::PrimaryAccountMutator::PrimaryAccountError::
+                kSyncConsentAlreadySet,
+            setPrimaryAccountResult);
 
   EXPECT_EQ(identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSync),
             primary_account_info.account_id);
@@ -388,8 +408,12 @@
 
   EXPECT_FALSE(
       identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync));
-  EXPECT_FALSE(primary_account_mutator->SetPrimaryAccount(
-      primary_account_info.account_id, signin::ConsentLevel::kSync));
+  signin::PrimaryAccountMutator::PrimaryAccountError setPrimaryAccountResult =
+      primary_account_mutator->SetPrimaryAccount(
+          primary_account_info.account_id, signin::ConsentLevel::kSync);
+  EXPECT_EQ(
+      signin::PrimaryAccountMutator::PrimaryAccountError::kSigninNotAllowed,
+      setPrimaryAccountResult);
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS_LACROS)
 
diff --git a/components/sync_device_info/fake_device_info_tracker.cc b/components/sync_device_info/fake_device_info_tracker.cc
index 2197021..a10fde1 100644
--- a/components/sync_device_info/fake_device_info_tracker.cc
+++ b/components/sync_device_info/fake_device_info_tracker.cc
@@ -4,6 +4,8 @@
 
 #include "components/sync_device_info/fake_device_info_tracker.h"
 
+#include <algorithm>
+
 #include "base/check.h"
 #include "base/notreached.h"
 #include "components/sync_device_info/device_info.h"
@@ -85,6 +87,16 @@
     observer.OnDeviceInfoChange();
 }
 
+void FakeDeviceInfoTracker::Replace(const DeviceInfo* old_device,
+                                    const DeviceInfo* new_device) {
+  std::vector<const DeviceInfo*>::iterator it =
+      std::find(devices_.begin(), devices_.end(), old_device);
+  DCHECK(devices_.end() != it) << "Tracker doesn't contain device";
+  *it = new_device;
+  for (auto& observer : observers_)
+    observer.OnDeviceInfoChange();
+}
+
 void FakeDeviceInfoTracker::OverrideActiveDeviceCount(int count) {
   active_device_count_ = count;
   for (auto& observer : observers_)
diff --git a/components/sync_device_info/fake_device_info_tracker.h b/components/sync_device_info/fake_device_info_tracker.h
index 692f2c7..acd0ca6 100644
--- a/components/sync_device_info/fake_device_info_tracker.h
+++ b/components/sync_device_info/fake_device_info_tracker.h
@@ -42,6 +42,10 @@
   // Adds a new DeviceInfo entry to |devices_|.
   void Add(const DeviceInfo* device);
 
+  // Replaces |old_device| with |new_device|. |old_device| must be present in
+  // the tracker.
+  void Replace(const DeviceInfo* old_device, const DeviceInfo* new_device);
+
   // Overrides the result of CountActiveDevices() to |count| instead of the
   // actual number of devices in |devices_|.
   void OverrideActiveDeviceCount(int count);
diff --git a/components/viz/common/viz_utils.cc b/components/viz/common/viz_utils.cc
index fd78c7e..8d3aa5c4 100644
--- a/components/viz/common/viz_utils.cc
+++ b/components/viz/common/viz_utils.cc
@@ -4,6 +4,9 @@
 
 #include "components/viz/common/viz_utils.h"
 
+#include <algorithm>
+#include <vector>
+
 #include "base/command_line.h"
 #include "base/system/sys_info.h"
 #include "ui/gfx/geometry/rect.h"
@@ -16,6 +19,11 @@
 #include "base/android/build_info.h"
 #endif
 
+#if defined(OS_POSIX)
+#include <poll.h>
+#include <sys/resource.h>
+#endif
+
 namespace viz {
 
 bool PreferRGB565ResourcesForDisplay() {
@@ -103,4 +111,41 @@
   return true;
 }
 
+bool GatherFDStats(base::TimeDelta* delta_time_taken,
+                   int* fd_max,
+                   int* active_fd_count,
+                   int* rlim_cur) {
+#if !defined(OS_POSIX)
+  return false;
+#else   // defined(OS_POSIX)
+  // https://stackoverflow.com/questions/7976769/
+  // getting-count-of-current-used-file-descriptors-from-c-code
+  base::ElapsedTimer timer;
+  rlimit limit_data;
+  getrlimit(RLIMIT_NOFILE, &limit_data);
+  std::vector<pollfd> poll_data;
+  constexpr int kMaxNumFDTested = 1 << 16;
+  // |rlim_cur| is the soft max but is likely the value we can rely on instead
+  // of the real max.
+  *rlim_cur = static_cast<int>(limit_data.rlim_cur);
+  *fd_max = std::max(1, std::min(*rlim_cur, kMaxNumFDTested));
+  poll_data.resize(*fd_max);
+  for (size_t i = 0; i < poll_data.size(); i++) {
+    auto& each = poll_data[i];
+    each.fd = static_cast<int>(i);
+    each.events = 0;
+    each.revents = 0;
+  }
+
+  poll(poll_data.data(), poll_data.size(), 0);
+  *active_fd_count = 0;
+  for (auto&& each : poll_data) {
+    if (each.revents != POLLNVAL)
+      (*active_fd_count)++;
+  }
+  *delta_time_taken = timer.Elapsed();
+  return true;
+#endif  // defined(OS_POSIX)
+}
+
 }  // namespace viz
diff --git a/components/viz/common/viz_utils.h b/components/viz/common/viz_utils.h
index be7ea5e5..b0ef766 100644
--- a/components/viz/common/viz_utils.h
+++ b/components/viz/common/viz_utils.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_VIZ_COMMON_VIZ_UTILS_H_
 #define COMPONENTS_VIZ_COMMON_VIZ_UTILS_H_
 
+#include "base/timer/elapsed_timer.h"
 #include "components/viz/common/viz_common_export.h"
 
 #include "build/build_config.h"
@@ -39,6 +40,14 @@
                                     const gfx::QuadF* clip,
                                     float uvs[8]);
 
+// Returns File Descriptor (FD) stats for current process.
+// Rendering resources can consume FDs. This this function can be used to
+// determine if the process is low on FDs or find an FD leak.
+VIZ_COMMON_EXPORT bool GatherFDStats(base::TimeDelta* delta_time_taken,
+                                     int* fd_max,
+                                     int* active_fd_count,
+                                     int* rlim_cur);
+
 }  // namespace viz
 
 #endif  // COMPONENTS_VIZ_COMMON_VIZ_UTILS_H_
diff --git a/components/viz/service/display/overlay_processor_delegated.cc b/components/viz/service/display/overlay_processor_delegated.cc
index f31d94de..f055f44 100644
--- a/components/viz/service/display/overlay_processor_delegated.cc
+++ b/components/viz/service/display/overlay_processor_delegated.cc
@@ -20,6 +20,8 @@
 #include "build/chromeos_buildflags.h"
 #include "components/viz/common/features.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
+#include "components/viz/common/quads/texture_draw_quad.h"
+#include "components/viz/common/viz_utils.h"
 #include "components/viz/service/debugger/viz_debugger.h"
 #include "components/viz/service/display/display_resource_provider.h"
 #include "components/viz/service/display/output_surface.h"
@@ -36,7 +38,47 @@
 #include "ui/gfx/geometry/transform.h"
 #include "ui/gfx/geometry/vector2d_f.h"
 
-#include "components/viz/common/quads/texture_draw_quad.h"
+namespace {
+DBG_FLAG_FBOOL("delegated.fd.usage", usage_every_frame)
+
+void RecordFDUsageUMA() {
+  static uint64_t sReportUsageFrameCounter = 0;
+  sReportUsageFrameCounter++;
+  constexpr uint32_t kReportEveryNFrames = 60 * 60 * 5;
+  if (((sReportUsageFrameCounter % kReportEveryNFrames) != 0) &&
+      !usage_every_frame()) {
+    return;
+  }
+
+  base::TimeDelta delta_time_taken;
+  int fd_max;
+  int active_fd_count;
+  int rlim_cur;
+
+  if (!viz::GatherFDStats(&delta_time_taken, &fd_max, &active_fd_count,
+                          &rlim_cur))
+    return;
+
+  static constexpr base::TimeDelta kHistogramMinTime =
+      base::TimeDelta::FromMicroseconds(5);
+  static constexpr base::TimeDelta kHistogramMaxTime =
+      base::TimeDelta::FromMilliseconds(10);
+  static constexpr int kHistogramTimeBuckets = 50;
+  int percentage_usage_int = (active_fd_count * 100) / fd_max;
+  UMA_HISTOGRAM_PERCENTAGE("Viz.FileDescriptorTracking.PercentageUsed",
+                           percentage_usage_int);
+  UMA_HISTOGRAM_COUNTS_100000("Viz.FileDescriptorTracking.NumActive",
+                              active_fd_count);
+  UMA_HISTOGRAM_COUNTS_100000("Viz.FileDescriptorTracking.NumSoftMax",
+                              rlim_cur);
+  UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
+      "Viz.FileDescriptorTracking.TimeToCompute", delta_time_taken,
+      kHistogramMinTime, kHistogramMaxTime, kHistogramTimeBuckets);
+
+  DBG_LOG("delegated.fd.usage", "FD usage: %d / %d - time us: %f",
+          active_fd_count, fd_max, delta_time_taken.InMicrosecondsF());
+}
+}  // namespace
 
 namespace viz {
 
@@ -189,6 +231,9 @@
   DCHECK(candidates->empty());
   auto* render_pass = render_passes->back().get();
   bool success = false;
+#if !defined(OS_APPLE)
+  RecordFDUsageUMA();
+#endif
 
   DBG_DRAW_RECT("delegated.incoming.damage", (*damage_rect));
   for (auto&& each : surface_damage_rect_list) {
diff --git a/components/viz/service/display/overlay_processor_using_strategy.cc b/components/viz/service/display/overlay_processor_using_strategy.cc
index 2a38d96..bd0c247 100644
--- a/components/viz/service/display/overlay_processor_using_strategy.cc
+++ b/components/viz/service/display/overlay_processor_using_strategy.cc
@@ -149,8 +149,6 @@
   DBG_DRAW_RECT("overlay.incoming.damage", (*damage_rect));
   for (auto&& each : surface_damage_rect_list) {
     DBG_DRAW_RECT("overlay.surface.damage", each);
-    // TODO(petermcneeley) : Check is temporary to isolate b/191414141.
-    CHECK(each.size().GetCheckedArea().IsValid());
   }
 
   // If we have any copy requests, we can't remove any quads for overlays or
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index ae15e03..1bfdef68 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -399,8 +399,6 @@
     damage_rect_in_root_target_space.Intersect(root_clip_rect);
   }
 
-  // TODO(petermcneeley) : Check is temporary to isolate b/191414141.
-  CHECK(damage_rect_in_root_target_space.size().GetCheckedArea().IsValid());
   surface_damage_rect_list_->push_back(damage_rect_in_root_target_space);
 }
 
@@ -465,8 +463,6 @@
     surface_damage_rect_list_->push_back(gfx::Rect());
   }
 
-  // TODO(petermcneeley) : Check is temporary to isolate b/191414141.
-  CHECK(!surface_damage_rect_list_->empty());
   // The latest surface damage rect.
   *overlay_damage_index = surface_damage_rect_list_->size() - 1;
 
@@ -1190,8 +1186,6 @@
           AddSurfaceDamageToDamageList(damage_rect_in_target_space.value(),
                                        target_transform, {}, dest_pass,
                                        /*resolved_frame=*/nullptr);
-          // TODO(petermcneeley) : Check is temporary to isolate b/191414141.
-          CHECK_GE(surface_damage_rect_list_->size(), 1u);
         } else if (quad == quad_with_overlay_damage_index) {
           dest_shared_quad_state->overlay_damage_index = overlay_damage_index;
         }
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
index d862fb2..f2d24a90 100644
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
@@ -72,7 +72,9 @@
   std::unique_ptr<SyntheticBeginFrameSource> synthetic_begin_frame_source;
   ExternalBeginFrameSourceMojo* external_begin_frame_source_mojo = nullptr;
   bool hw_support_for_multiple_refresh_rates = false;
+#if !defined(OS_APPLE)
   bool wants_vsync_updates = false;
+#endif
 
   if (params->external_begin_frame_controller) {
     auto owned_external_begin_frame_source_mojo =
@@ -103,7 +105,9 @@
 #endif
       // Vsync updates are required to update the FrameRateDecider with
       // supported refresh rates.
+#if !defined(OS_APPLE)
       wants_vsync_updates = params->use_preferred_interval_for_video;
+#endif
       external_begin_frame_source = std::make_unique<GpuVSyncBeginFrameSource>(
           restart_id, output_surface.get());
     } else {
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 656f9ee..592551f 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -438,6 +438,8 @@
     "attribution_reporting/attribution_host_utils.h",
     "attribution_reporting/attribution_internals_handler_impl.cc",
     "attribution_reporting/attribution_internals_handler_impl.h",
+    "attribution_reporting/attribution_internals_ui.cc",
+    "attribution_reporting/attribution_internals_ui.h",
     "attribution_reporting/attribution_manager.h",
     "attribution_reporting/attribution_manager_impl.cc",
     "attribution_reporting/attribution_manager_impl.h",
@@ -463,8 +465,6 @@
     "attribution_reporting/attribution_storage_sql_migrations.h",
     "attribution_reporting/attribution_utils.cc",
     "attribution_reporting/attribution_utils.h",
-    "attribution_reporting/conversion_internals_ui.cc",
-    "attribution_reporting/conversion_internals_ui.h",
     "attribution_reporting/rate_limit_table.cc",
     "attribution_reporting/rate_limit_table.h",
     "attribution_reporting/sent_report_info.cc",
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.cc b/content/browser/accessibility/browser_accessibility_manager_android.cc
index 1388bb5..62f5fd3 100644
--- a/content/browser/accessibility/browser_accessibility_manager_android.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_android.cc
@@ -535,6 +535,15 @@
 
   ClearNodeInfoCacheForGivenId(android_node->unique_id());
 
+  // When a node will be deleted, clear its parent from the cache as well, or
+  // the parent could erroneously report the cleared node as a child later on.
+  BrowserAccessibilityAndroid* parent_node =
+      static_cast<BrowserAccessibilityAndroid*>(
+          android_node->PlatformGetParent());
+  if (parent_node != nullptr) {
+    ClearNodeInfoCacheForGivenId(parent_node->unique_id());
+  }
+
   BrowserAccessibilityManager::OnNodeWillBeDeleted(tree, node);
 }
 
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 2a12bea..2431476 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -2777,14 +2777,8 @@
   RunHtmlTest(FILE_PATH_LITERAL("svg-symbol-with-role.html"));
 }
 
-// On ChromeOS, SVG <g> elements are included.
-#if defined(OS_CHROMEOS)
-#define AccessibilitySvgG_TestFile FILE_PATH_LITERAL("svg-g-for-cros.html")
-#else
-#define AccessibilitySvgG_TestFile FILE_PATH_LITERAL("svg-g.html")
-#endif
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilitySvgG) {
-  RunHtmlTest(AccessibilitySvgG_TestFile);
+  RunHtmlTest(FILE_PATH_LITERAL("svg-g.html"));
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
diff --git a/content/browser/appcache/appcache_request_handler.cc b/content/browser/appcache/appcache_request_handler.cc
index 3d24d68..7248016 100644
--- a/content/browser/appcache/appcache_request_handler.cc
+++ b/content/browser/appcache/appcache_request_handler.cc
@@ -645,18 +645,8 @@
 
 absl::optional<SubresourceLoaderParams>
 AppCacheRequestHandler::MaybeCreateSubresourceLoaderParams() {
-  if (!should_create_subresource_loader_)
-    return absl::nullopt;
-
-  // The factory is destroyed when the renderer drops the connection.
-  mojo::PendingRemote<network::mojom::URLLoaderFactory> factory_remote;
-
-  AppCacheSubresourceURLFactory::CreateURLLoaderFactory(
-      appcache_host_, factory_remote.InitWithNewPipeAndPassReceiver());
-
-  SubresourceLoaderParams params;
-  params.pending_appcache_loader_factory = std::move(factory_remote);
-  return absl::optional<SubresourceLoaderParams>(std::move(params));
+  // TODO(enne): remove the rest of this file.
+  return absl::nullopt;
 }
 
 void AppCacheRequestHandler::MaybeCreateSubresourceLoader(
diff --git a/content/browser/attribution_reporting/BUILD.gn b/content/browser/attribution_reporting/BUILD.gn
index 4dd227e..3c76eb8 100644
--- a/content/browser/attribution_reporting/BUILD.gn
+++ b/content/browser/attribution_reporting/BUILD.gn
@@ -5,7 +5,7 @@
 import("//mojo/public/tools/bindings/mojom.gni")
 
 mojom("mojo_bindings") {
-  sources = [ "conversion_internals.mojom" ]
+  sources = [ "attribution_internals.mojom" ]
   public_deps = [ "//url/mojom:url_mojom_origin" ]
   webui_module_path = "/"
 }
diff --git a/content/browser/attribution_reporting/conversion_internals.mojom b/content/browser/attribution_reporting/attribution_internals.mojom
similarity index 98%
rename from content/browser/attribution_reporting/conversion_internals.mojom
rename to content/browser/attribution_reporting/attribution_internals.mojom
index 84388fe..997077e 100644
--- a/content/browser/attribution_reporting/conversion_internals.mojom
+++ b/content/browser/attribution_reporting/attribution_internals.mojom
@@ -53,7 +53,7 @@
 
 // Mojo interface for the conversion internals WebUI to communicate with the
 // storage layer.
-interface ConversionInternalsHandler {
+interface AttributionInternalsHandler {
   // Returns whether conversion measurement and the debug mode are enabled in
   // the browsing context the WebUI is in.
   IsMeasurementEnabled() => (bool enabled, bool debug_mode);
diff --git a/content/browser/attribution_reporting/attribution_internals_browsertest.cc b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
index 7a2effc..42df253 100644
--- a/content/browser/attribution_reporting/attribution_internals_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_internals_browsertest.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 "content/browser/attribution_reporting/conversion_internals_ui.h"
+#include "content/browser/attribution_reporting/attribution_internals_ui.h"
 
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
@@ -29,7 +29,7 @@
 using CreateReportStatus =
     ::content::AttributionStorage::CreateReportResult::Status;
 
-const char kConversionInternalsUrl[] = "chrome://conversion-internals/";
+const char kAttributionInternalsUrl[] = "chrome://conversion-internals/";
 
 const std::u16string kCompleteTitle = u"Complete";
 const std::u16string kCompleteTitle2 = u"Complete2";
@@ -40,9 +40,9 @@
 
 }  // namespace
 
-class ConversionInternalsWebUiBrowserTest : public ContentBrowserTest {
+class AttributionInternalsWebUiBrowserTest : public ContentBrowserTest {
  public:
-  ConversionInternalsWebUiBrowserTest() = default;
+  AttributionInternalsWebUiBrowserTest() = default;
 
   void ClickRefreshButton() {
     EXPECT_TRUE(ExecJsInWebUI("document.getElementById('refresh').click();"));
@@ -59,12 +59,12 @@
   void OverrideWebUIAttributionManager(AttributionManager* manager) {
     content::WebUI* web_ui = shell()->web_contents()->GetWebUI();
 
-    // Performs a safe downcast to the concrete ConversionInternalsUI subclass.
-    ConversionInternalsUI* conversion_internals_ui =
-        web_ui ? web_ui->GetController()->GetAs<ConversionInternalsUI>()
+    // Performs a safe downcast to the concrete AttributionInternalsUI subclass.
+    AttributionInternalsUI* attribution_internals_ui =
+        web_ui ? web_ui->GetController()->GetAs<AttributionInternalsUI>()
                : nullptr;
-    EXPECT_TRUE(conversion_internals_ui);
-    conversion_internals_ui->SetAttributionManagerProviderForTesting(
+    EXPECT_TRUE(attribution_internals_ui);
+    attribution_internals_ui->SetAttributionManagerProviderForTesting(
         std::make_unique<TestManagerProvider>(manager));
   }
 
@@ -88,9 +88,9 @@
   ConversionDisallowingContentBrowserClient disallowed_browser_client_;
 };
 
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        NavigationUrl_ResolvedToWebUI) {
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   // Execute script to ensure the page has loaded correctly, executing similarly
   // to ExecJsInWebUI().
@@ -100,9 +100,9 @@
                          EXECUTE_SCRIPT_DEFAULT_OPTIONS, /*world_id=*/1));
 }
 
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUIShownWithManager_MeasurementConsideredEnabled) {
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   TestAttributionManager manager;
   OverrideWebUIAttributionManager(&manager);
@@ -125,11 +125,11 @@
   EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
 }
 
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        DisabledByEmbedder_MeasurementConsideredDisabled) {
   ContentBrowserClient* old_browser_client =
       SetBrowserClientForTesting(&disallowed_browser_client_);
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   TestAttributionManager manager;
   OverrideWebUIAttributionManager(&manager);
@@ -154,9 +154,9 @@
 }
 
 IN_PROC_BROWSER_TEST_F(
-    ConversionInternalsWebUiBrowserTest,
+    AttributionInternalsWebUiBrowserTest,
     WebUIShownWithNoActiveImpression_NoImpressionsDisplayed) {
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   TestAttributionManager manager;
   OverrideWebUIAttributionManager(&manager);
@@ -178,9 +178,9 @@
   EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
 }
 
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUIShownWithActiveImpression_ImpressionsDisplayed) {
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   // We use the max values of `uint64_t` and `int64_t` here to ensure that they
   // are properly handled as `bigint` values in JS and don't run into issues
@@ -224,9 +224,9 @@
   EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
 }
 
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUIShownWithNoReports_NoReportsDisplayed) {
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   TestAttributionManager manager;
   OverrideWebUIAttributionManager(&manager);
@@ -237,9 +237,9 @@
   EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
 }
 
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUIShownWithManager_DebugModeDisabled) {
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   TestAttributionManager manager;
   OverrideWebUIAttributionManager(&manager);
@@ -262,12 +262,12 @@
   EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
 }
 
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUIShownWithManager_DebugModeEnabled) {
   base::CommandLine::ForCurrentProcess()->AppendSwitch(
       switches::kConversionsDebugMode);
 
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   TestAttributionManager manager;
   OverrideWebUIAttributionManager(&manager);
@@ -290,9 +290,9 @@
   EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
 }
 
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUIShownWithPendingReports_ReportsDisplayed) {
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   const base::Time now = base::Time::Now();
 
@@ -425,9 +425,9 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUIWithPendingReportsClearStorage_ReportsRemoved) {
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   const base::Time now = base::Time::Now();
 
@@ -473,9 +473,9 @@
 
 // TODO(johnidel): Use a real AttributionManager here and verify that the
 // reports are actually sent.
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUISendReports_ReportsRemoved) {
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   TestAttributionManager manager;
   AttributionReport report(
@@ -512,9 +512,9 @@
   EXPECT_EQ(kSentTitle, sent_title_watcher.WaitAndGetTitle());
 }
 
-IN_PROC_BROWSER_TEST_F(ConversionInternalsWebUiBrowserTest,
+IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        MojoJsBindingsCorrectlyScoped) {
-  EXPECT_TRUE(NavigateToURL(shell(), GURL(kConversionInternalsUrl)));
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
   const std::u16string passed_title = u"passed";
 
diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
index e035b52..27e0f1c 100644
--- a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
+++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
@@ -45,7 +45,7 @@
 }
 
 void ForwardImpressionsToWebUI(
-    mojom::ConversionInternalsHandler::GetActiveImpressionsCallback
+    mojom::AttributionInternalsHandler::GetActiveImpressionsCallback
         web_ui_callback,
     std::vector<StorableSource> stored_impressions) {
   std::vector<mojom::WebUIImpressionPtr> web_ui_impressions;
@@ -81,7 +81,7 @@
 }
 
 void ForwardReportsToWebUI(
-    mojom::ConversionInternalsHandler::GetReportsCallback web_ui_callback,
+    mojom::AttributionInternalsHandler::GetReportsCallback web_ui_callback,
     std::vector<mojom::WebUIConversionReportPtr> web_ui_reports,
     std::vector<AttributionReport> pending_reports) {
   web_ui_reports.reserve(web_ui_reports.capacity() + pending_reports.size());
@@ -100,7 +100,7 @@
 
 AttributionInternalsHandlerImpl::AttributionInternalsHandlerImpl(
     WebUI* web_ui,
-    mojo::PendingReceiver<mojom::ConversionInternalsHandler> receiver)
+    mojo::PendingReceiver<mojom::AttributionInternalsHandler> receiver)
     : web_ui_(web_ui),
       manager_provider_(std::make_unique<AttributionManagerProviderImpl>()),
       receiver_(this, std::move(receiver)) {}
@@ -108,7 +108,7 @@
 AttributionInternalsHandlerImpl::~AttributionInternalsHandlerImpl() = default;
 
 void AttributionInternalsHandlerImpl::IsMeasurementEnabled(
-    mojom::ConversionInternalsHandler::IsMeasurementEnabledCallback callback) {
+    mojom::AttributionInternalsHandler::IsMeasurementEnabledCallback callback) {
   content::WebContents* contents = web_ui_->GetWebContents();
   bool measurement_enabled =
       manager_provider_->GetManager(contents) &&
@@ -123,7 +123,7 @@
 }
 
 void AttributionInternalsHandlerImpl::GetActiveImpressions(
-    mojom::ConversionInternalsHandler::GetActiveImpressionsCallback callback) {
+    mojom::AttributionInternalsHandler::GetActiveImpressionsCallback callback) {
   if (AttributionManager* manager =
           manager_provider_->GetManager(web_ui_->GetWebContents())) {
     manager->GetActiveImpressionsForWebUI(
@@ -134,7 +134,7 @@
 }
 
 void AttributionInternalsHandlerImpl::GetReports(
-    mojom::ConversionInternalsHandler::GetReportsCallback callback) {
+    mojom::AttributionInternalsHandler::GetReportsCallback callback) {
   if (AttributionManager* manager =
           manager_provider_->GetManager(web_ui_->GetWebContents())) {
     const AttributionSessionStorage& session_storage =
@@ -185,7 +185,7 @@
 }
 
 void AttributionInternalsHandlerImpl::SendPendingReports(
-    mojom::ConversionInternalsHandler::SendPendingReportsCallback callback) {
+    mojom::AttributionInternalsHandler::SendPendingReportsCallback callback) {
   if (AttributionManager* manager =
           manager_provider_->GetManager(web_ui_->GetWebContents())) {
     manager->SendReportsForWebUI(std::move(callback));
@@ -195,7 +195,7 @@
 }
 
 void AttributionInternalsHandlerImpl::ClearStorage(
-    mojom::ConversionInternalsHandler::ClearStorageCallback callback) {
+    mojom::AttributionInternalsHandler::ClearStorageCallback callback) {
   if (AttributionManager* manager =
           manager_provider_->GetManager(web_ui_->GetWebContents())) {
     manager->ClearData(base::Time::Min(), base::Time::Max(),
diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.h b/content/browser/attribution_reporting/attribution_internals_handler_impl.h
index 5dc8d981..92501421 100644
--- a/content/browser/attribution_reporting/attribution_internals_handler_impl.h
+++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.h
@@ -5,8 +5,8 @@
 #ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_INTERNALS_HANDLER_IMPL_H_
 #define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_INTERNALS_HANDLER_IMPL_H_
 
+#include "content/browser/attribution_reporting/attribution_internals.mojom.h"
 #include "content/browser/attribution_reporting/attribution_manager.h"
-#include "content/browser/attribution_reporting/conversion_internals.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 
@@ -16,13 +16,13 @@
 
 // Implements the mojo endpoint for the Conversion WebUI which proxies calls to
 // the AttributionManager to get information about stored conversion data. Owned
-// by ConversionInternalsUI.
+// by AttributionInternalsUI.
 class AttributionInternalsHandlerImpl
-    : public mojom::ConversionInternalsHandler {
+    : public mojom::AttributionInternalsHandler {
  public:
   AttributionInternalsHandlerImpl(
       WebUI* web_ui,
-      mojo::PendingReceiver<mojom::ConversionInternalsHandler> receiver);
+      mojo::PendingReceiver<mojom::AttributionInternalsHandler> receiver);
   AttributionInternalsHandlerImpl(
       const AttributionInternalsHandlerImpl& other) = delete;
   AttributionInternalsHandlerImpl& operator=(
@@ -33,19 +33,19 @@
       AttributionInternalsHandlerImpl&& other) = delete;
   ~AttributionInternalsHandlerImpl() override;
 
-  // mojom::ConversionInternalsHandler overrides:
+  // mojom::AttributionInternalsHandler overrides:
   void IsMeasurementEnabled(
-      mojom::ConversionInternalsHandler::IsMeasurementEnabledCallback callback)
+      mojom::AttributionInternalsHandler::IsMeasurementEnabledCallback callback)
       override;
   void GetActiveImpressions(
-      mojom::ConversionInternalsHandler::GetActiveImpressionsCallback callback)
+      mojom::AttributionInternalsHandler::GetActiveImpressionsCallback callback)
       override;
   void GetReports(
-      mojom::ConversionInternalsHandler::GetReportsCallback callback) override;
+      mojom::AttributionInternalsHandler::GetReportsCallback callback) override;
   void SendPendingReports(
-      mojom::ConversionInternalsHandler::SendPendingReportsCallback callback)
+      mojom::AttributionInternalsHandler::SendPendingReportsCallback callback)
       override;
-  void ClearStorage(mojom::ConversionInternalsHandler::ClearStorageCallback
+  void ClearStorage(mojom::AttributionInternalsHandler::ClearStorageCallback
                         callback) override;
 
   void SetAttributionManagerProviderForTesting(
@@ -55,7 +55,7 @@
   WebUI* web_ui_;
   std::unique_ptr<AttributionManager::Provider> manager_provider_;
 
-  mojo::Receiver<mojom::ConversionInternalsHandler> receiver_;
+  mojo::Receiver<mojom::AttributionInternalsHandler> receiver_;
 };
 
 }  // namespace content
diff --git a/content/browser/attribution_reporting/conversion_internals_ui.cc b/content/browser/attribution_reporting/attribution_internals_ui.cc
similarity index 63%
rename from content/browser/attribution_reporting/conversion_internals_ui.cc
rename to content/browser/attribution_reporting/attribution_internals_ui.cc
index 00657c65..ea26d3e 100644
--- a/content/browser/attribution_reporting/conversion_internals_ui.cc
+++ b/content/browser/attribution_reporting/attribution_internals_ui.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 "content/browser/attribution_reporting/conversion_internals_ui.h"
+#include "content/browser/attribution_reporting/attribution_internals_ui.h"
 
 #include "content/browser/attribution_reporting/attribution_internals_handler_impl.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
@@ -17,44 +17,44 @@
 
 namespace content {
 
-ConversionInternalsUI::ConversionInternalsUI(WebUI* web_ui)
+AttributionInternalsUI::AttributionInternalsUI(WebUI* web_ui)
     : WebUIController(web_ui) {
   // Initialize the UI with no bindings. Mojo bindings will be separately
   // granted to frames within this WebContents.
   web_ui->SetBindings(BINDINGS_POLICY_NONE);
   WebUIDataSource* source =
-      WebUIDataSource::Create(kChromeUIConversionInternalsHost);
+      WebUIDataSource::Create(kChromeUIAttributionInternalsHost);
 
-  source->AddResourcePath("conversion_internals.mojom-webui.js",
-                          IDR_CONVERSION_INTERNALS_MOJOM_JS);
-  source->AddResourcePath("conversion_internals.js",
-                          IDR_CONVERSION_INTERNALS_JS);
-  source->AddResourcePath("conversion_internals.css",
-                          IDR_CONVERSION_INTERNALS_CSS);
-  source->SetDefaultResource(IDR_CONVERSION_INTERNALS_HTML);
+  source->AddResourcePath("attribution_internals.mojom-webui.js",
+                          IDR_ATTRIBUTION_INTERNALS_MOJOM_JS);
+  source->AddResourcePath("attribution_internals.js",
+                          IDR_ATTRIBUTION_INTERNALS_JS);
+  source->AddResourcePath("attribution_internals.css",
+                          IDR_ATTRIBUTION_INTERNALS_CSS);
+  source->SetDefaultResource(IDR_ATTRIBUTION_INTERNALS_HTML);
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::TrustedTypes,
       "trusted-types static-types;");
   WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(), source);
 }
 
-WEB_UI_CONTROLLER_TYPE_IMPL(ConversionInternalsUI)
+WEB_UI_CONTROLLER_TYPE_IMPL(AttributionInternalsUI)
 
-ConversionInternalsUI::~ConversionInternalsUI() = default;
+AttributionInternalsUI::~AttributionInternalsUI() = default;
 
-void ConversionInternalsUI::WebUIRenderFrameCreated(RenderFrameHost* rfh) {
+void AttributionInternalsUI::WebUIRenderFrameCreated(RenderFrameHost* rfh) {
   // Enable the JavaScript Mojo bindings in the renderer process, so the JS
   // code can call the Mojo APIs exposed by this WebUI.
   static_cast<RenderFrameHostImpl*>(rfh)->EnableMojoJsBindings();
 }
 
-void ConversionInternalsUI::BindInterface(
-    mojo::PendingReceiver<mojom::ConversionInternalsHandler> receiver) {
+void AttributionInternalsUI::BindInterface(
+    mojo::PendingReceiver<mojom::AttributionInternalsHandler> receiver) {
   ui_handler_ = std::make_unique<AttributionInternalsHandlerImpl>(
       web_ui(), std::move(receiver));
 }
 
-void ConversionInternalsUI::SetAttributionManagerProviderForTesting(
+void AttributionInternalsUI::SetAttributionManagerProviderForTesting(
     std::unique_ptr<AttributionManager::Provider> manager_provider) {
   ui_handler_->SetAttributionManagerProviderForTesting(
       std::move(manager_provider));
diff --git a/content/browser/attribution_reporting/attribution_internals_ui.h b/content/browser/attribution_reporting/attribution_internals_ui.h
new file mode 100644
index 0000000..8f4770f
--- /dev/null
+++ b/content/browser/attribution_reporting/attribution_internals_ui.h
@@ -0,0 +1,49 @@
+// 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 CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_INTERNALS_UI_H_
+#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_INTERNALS_UI_H_
+
+#include <memory>
+
+#include "content/browser/attribution_reporting/attribution_internals.mojom.h"
+#include "content/browser/attribution_reporting/attribution_manager.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace content {
+
+class AttributionInternalsHandlerImpl;
+
+// WebUI which handles serving the chrome://conversion-internals page.
+class CONTENT_EXPORT AttributionInternalsUI : public WebUIController {
+ public:
+  explicit AttributionInternalsUI(WebUI* web_ui);
+  AttributionInternalsUI(const AttributionInternalsUI& other) = delete;
+  AttributionInternalsUI& operator=(const AttributionInternalsUI& other) =
+      delete;
+  AttributionInternalsUI(AttributionInternalsUI&& other) = delete;
+  AttributionInternalsUI& operator=(AttributionInternalsUI&& other) = delete;
+  ~AttributionInternalsUI() override;
+
+  // WebUIController overrides:
+  void WebUIRenderFrameCreated(RenderFrameHost* render_frame_host) override;
+
+  void BindInterface(
+      mojo::PendingReceiver<mojom::AttributionInternalsHandler> receiver);
+
+  void SetAttributionManagerProviderForTesting(
+      std::unique_ptr<AttributionManager::Provider> manager_provider);
+
+ private:
+  std::unique_ptr<AttributionInternalsHandlerImpl> ui_handler_;
+
+  WEB_UI_CONTROLLER_TYPE_DECL();
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_INTERNALS_UI_H_
diff --git a/content/browser/attribution_reporting/conversion_internals_ui.h b/content/browser/attribution_reporting/conversion_internals_ui.h
deleted file mode 100644
index 7f22abb..0000000
--- a/content/browser/attribution_reporting/conversion_internals_ui.h
+++ /dev/null
@@ -1,48 +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 CONTENT_BROWSER_ATTRIBUTION_REPORTING_CONVERSION_INTERNALS_UI_H_
-#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_CONVERSION_INTERNALS_UI_H_
-
-#include <memory>
-
-#include "content/browser/attribution_reporting/attribution_manager.h"
-#include "content/browser/attribution_reporting/conversion_internals.mojom.h"
-#include "content/common/content_export.h"
-#include "content/public/browser/web_contents_observer.h"
-#include "content/public/browser/web_ui_controller.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
-
-namespace content {
-
-class AttributionInternalsHandlerImpl;
-
-// WebUI which handles serving the chrome://conversion-internals page.
-class CONTENT_EXPORT ConversionInternalsUI : public WebUIController {
- public:
-  explicit ConversionInternalsUI(WebUI* web_ui);
-  ConversionInternalsUI(const ConversionInternalsUI& other) = delete;
-  ConversionInternalsUI& operator=(const ConversionInternalsUI& other) = delete;
-  ConversionInternalsUI(ConversionInternalsUI&& other) = delete;
-  ConversionInternalsUI& operator=(ConversionInternalsUI&& other) = delete;
-  ~ConversionInternalsUI() override;
-
-  // WebUIController overrides:
-  void WebUIRenderFrameCreated(RenderFrameHost* render_frame_host) override;
-
-  void BindInterface(
-      mojo::PendingReceiver<mojom::ConversionInternalsHandler> receiver);
-
-  void SetAttributionManagerProviderForTesting(
-      std::unique_ptr<AttributionManager::Provider> manager_provider);
-
- private:
-  std::unique_ptr<AttributionInternalsHandlerImpl> ui_handler_;
-
-  WEB_UI_CONTROLLER_TYPE_DECL();
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_CONVERSION_INTERNALS_UI_H_
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index 5ba624e..b6c3c73 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -13,8 +13,8 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "cc/base/switches.h"
-#include "content/browser/attribution_reporting/conversion_internals.mojom.h"
-#include "content/browser/attribution_reporting/conversion_internals_ui.h"
+#include "content/browser/attribution_reporting/attribution_internals.mojom.h"
+#include "content/browser/attribution_reporting/attribution_internals_ui.h"
 #include "content/browser/background_fetch/background_fetch_service_impl.h"
 #include "content/browser/bad_message.h"
 #include "content/browser/browser_main_loop.h"
@@ -254,19 +254,19 @@
   web_contents->OnColorChooserFactoryReceiver(std::move(receiver));
 }
 
-void BindConversionInternalsHandler(
+void BindAttributionInternalsHandler(
     content::RenderFrameHost* host,
-    mojo::PendingReceiver<mojom::ConversionInternalsHandler> receiver) {
+    mojo::PendingReceiver<mojom::AttributionInternalsHandler> receiver) {
   content::WebUI* web_ui = host->GetWebUI();
 
-  // Performs a safe downcast to the concrete ConversionInternalsUI subclass.
-  ConversionInternalsUI* conversion_internals_ui =
-      web_ui ? web_ui->GetController()->GetAs<ConversionInternalsUI>()
+  // Performs a safe downcast to the concrete AttributionInternalsUI subclass.
+  AttributionInternalsUI* attribution_internals_ui =
+      web_ui ? web_ui->GetController()->GetAs<AttributionInternalsUI>()
              : nullptr;
 
   // This is expected to be called only for main frames and for the right WebUI
   // pages matching the same WebUI associated to the RenderFrameHost.
-  if (host->GetParent() || !conversion_internals_ui) {
+  if (host->GetParent() || !attribution_internals_ui) {
     ReceivedBadMessage(
         host->GetProcess(),
         bad_message::BadMessageReason::RFH_INVALID_WEB_UI_CONTROLLER);
@@ -274,10 +274,10 @@
   }
 
   DCHECK_EQ(host->GetLastCommittedURL().host_piece(),
-            kChromeUIConversionInternalsHost);
+            kChromeUIAttributionInternalsHost);
   DCHECK(host->GetLastCommittedURL().SchemeIs(kChromeUIScheme));
 
-  conversion_internals_ui->BindInterface(std::move(receiver));
+  attribution_internals_ui->BindInterface(std::move(receiver));
 }
 
 void BindPrerenderInternalsHandler(
@@ -1032,8 +1032,8 @@
   map->Add<device::mojom::VRService>(
       base::BindRepeating(&EmptyBinderForFrame<device::mojom::VRService>));
 #endif
-  map->Add<mojom::ConversionInternalsHandler>(
-      base::BindRepeating(&BindConversionInternalsHandler));
+  map->Add<mojom::AttributionInternalsHandler>(
+      base::BindRepeating(&BindAttributionInternalsHandler));
   map->Add<mojom::PrerenderInternalsHandler>(
       base::BindRepeating(&BindPrerenderInternalsHandler));
   map->Add<::mojom::ProcessInternalsHandler>(
diff --git a/content/browser/cookie_store/cookie_store_manager.cc b/content/browser/cookie_store/cookie_store_manager.cc
index d44113c..b9707a3 100644
--- a/content/browser/cookie_store/cookie_store_manager.cc
+++ b/content/browser/cookie_store/cookie_store_manager.cc
@@ -204,6 +204,8 @@
   }
 
   for (const auto& mojo_subscription : mojo_subscriptions) {
+    // TODO(crbug.com/1246549): This validation step should consider the storage
+    // key.
     if (!blink::ServiceWorkerScopeMatches(service_worker_registration->scope(),
                                           mojo_subscription->url)) {
       // Blink should have validated subscription URLs against the service
diff --git a/content/browser/navigation_subresource_loader_params.cc b/content/browser/navigation_subresource_loader_params.cc
index 0f92d78..1fc76c4 100644
--- a/content/browser/navigation_subresource_loader_params.cc
+++ b/content/browser/navigation_subresource_loader_params.cc
@@ -16,8 +16,6 @@
 
 SubresourceLoaderParams& SubresourceLoaderParams::operator=(
     SubresourceLoaderParams&& other) {
-  pending_appcache_loader_factory =
-      std::move(other.pending_appcache_loader_factory);
   controller_service_worker_info =
       std::move(other.controller_service_worker_info);
   controller_service_worker_object_host =
diff --git a/content/browser/navigation_subresource_loader_params.h b/content/browser/navigation_subresource_loader_params.h
index 62c18479..c6180b8 100644
--- a/content/browser/navigation_subresource_loader_params.h
+++ b/content/browser/navigation_subresource_loader_params.h
@@ -27,12 +27,6 @@
   SubresourceLoaderParams(SubresourceLoaderParams&& other);
   SubresourceLoaderParams& operator=(SubresourceLoaderParams&& other);
 
-  // For AppCache.
-  // Subresource loader factory info for appcache, that is to be used to
-  // create a subresource loader in the renderer.
-  mojo::PendingRemote<network::mojom::URLLoaderFactory>
-      pending_appcache_loader_factory;
-
   // For ServiceWorkers.
   // The controller service worker, non-null if the frame is to be
   // controlled by the service worker.
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 754d634..a2c0654f 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -7783,40 +7783,6 @@
             .GetTupleOrPrecursorTupleIfOpaque()
             .scheme();
 
-    // NOTE: On Network Service navigations, we want to ensure that a frame is
-    // given everything it will need to load any accessible subresources. We
-    // however only do this for cross-document navigations, because the
-    // alternative would be redundant effort.
-    if (subresource_loader_params &&
-        subresource_loader_params->pending_appcache_loader_factory.is_valid()) {
-      // If the caller has supplied a factory for AppCache, use it.
-      mojo::Remote<network::mojom::URLLoaderFactory> appcache_remote(std::move(
-          subresource_loader_params->pending_appcache_loader_factory));
-
-      mojo::PendingRemote<network::mojom::URLLoaderFactory>
-          appcache_proxied_remote;
-      auto appcache_proxied_receiver =
-          appcache_proxied_remote.InitWithNewPipeAndPassReceiver();
-      bool use_proxy =
-          GetContentClient()->browser()->WillCreateURLLoaderFactory(
-              browser_context, this, GetProcess()->GetID(),
-              ContentBrowserClient::URLLoaderFactoryType::kDocumentSubResource,
-              subresource_loader_factories_config.origin(),
-              /*navigation_id=*/absl::nullopt,
-              subresource_loader_factories_config.ukm_source_id(),
-              &appcache_proxied_receiver, /*header_client=*/nullptr,
-              /*bypass_redirect_checks=*/nullptr,
-              /*disable_secure_dns=*/nullptr, /*factory_override=*/nullptr);
-      if (use_proxy) {
-        appcache_remote->Clone(std::move(appcache_proxied_receiver));
-        appcache_remote.reset();
-        appcache_remote.Bind(std::move(appcache_proxied_remote));
-      }
-
-      subresource_loader_factories->pending_appcache_factory() =
-          appcache_remote.Unbind();
-    }
-
     ContentBrowserClient::NonNetworkURLLoaderFactoryMap non_network_factories;
 
     // Set up the default factory.
diff --git a/content/browser/resources/attribution_reporting/BUILD.gn b/content/browser/resources/attribution_reporting/BUILD.gn
index 40950f0..fbdeee4 100644
--- a/content/browser/resources/attribution_reporting/BUILD.gn
+++ b/content/browser/resources/attribution_reporting/BUILD.gn
@@ -11,10 +11,10 @@
                             "$root_gen_dir/mojom-webui/content/browser/attribution_reporting",
                             root_build_dir),
                   ]
-  deps = [ ":conversion_internals" ]
+  deps = [ ":attribution_internals" ]
 }
 
-js_library("conversion_internals") {
+js_library("attribution_internals") {
   deps = [
     "//content/browser/attribution_reporting:mojo_bindings_webui_js",
     "//ui/webui/resources/js:assert.m",
diff --git a/content/browser/resources/attribution_reporting/conversion_internals.css b/content/browser/resources/attribution_reporting/attribution_internals.css
similarity index 100%
rename from content/browser/resources/attribution_reporting/conversion_internals.css
rename to content/browser/resources/attribution_reporting/attribution_internals.css
diff --git a/content/browser/resources/attribution_reporting/conversion_internals.html b/content/browser/resources/attribution_reporting/attribution_internals.html
similarity index 90%
rename from content/browser/resources/attribution_reporting/conversion_internals.html
rename to content/browser/resources/attribution_reporting/attribution_internals.html
index c6fcc91..72c1457 100644
--- a/content/browser/resources/attribution_reporting/conversion_internals.html
+++ b/content/browser/resources/attribution_reporting/attribution_internals.html
@@ -8,8 +8,8 @@
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
   <link rel="stylesheet" href="chrome://resources/css/roboto.css">
 
-  <link rel="stylesheet" href="conversion_internals.css">
-  <script type="module" src="conversion_internals.js"></script>
+  <link rel="stylesheet" href="attribution_internals.css">
+  <script type="module" src="attribution_internals.js"></script>
   <title>Attribution Reporting API Internals</title>
 </head>
 <body>
diff --git a/content/browser/resources/attribution_reporting/conversion_internals.js b/content/browser/resources/attribution_reporting/attribution_internals.js
similarity index 97%
rename from content/browser/resources/attribution_reporting/conversion_internals.js
rename to content/browser/resources/attribution_reporting/attribution_internals.js
index fd7de09..a45b765f 100644
--- a/content/browser/resources/attribution_reporting/conversion_internals.js
+++ b/content/browser/resources/attribution_reporting/attribution_internals.js
@@ -7,7 +7,7 @@
 import {$, getRequiredElement} from 'chrome://resources/js/util.m.js';
 import {Origin} from 'chrome://resources/mojo/url/mojom/origin.mojom-webui.js';
 
-import {ConversionInternalsHandler, ConversionInternalsHandlerRemote, SourceType, WebUIConversionReport, WebUIConversionReport_Status, WebUIImpression,} from './conversion_internals.mojom-webui.js';
+import {AttributionInternalsHandler, AttributionInternalsHandlerRemote, SourceType, WebUIConversionReport, WebUIConversionReport_Status, WebUIImpression,} from './attribution_internals.mojom-webui.js';
 
 /**
  * @template T
@@ -396,7 +396,7 @@
 
 /**
  * Reference to the backend providing all the data.
- * @type {?ConversionInternalsHandlerRemote}
+ * @type {?AttributionInternalsHandlerRemote}
  */
 let pageHandler = null;
 
@@ -495,7 +495,7 @@
 
 document.addEventListener('DOMContentLoaded', function() {
   // Setup the mojo interface.
-  pageHandler = ConversionInternalsHandler.getRemote();
+  pageHandler = AttributionInternalsHandler.getRemote();
 
   $('refresh').addEventListener('click', updatePageData);
   $('clear-data').addEventListener('click', clearStorage);
diff --git a/content/browser/service_worker/service_worker_container_host.cc b/content/browser/service_worker/service_worker_container_host.cc
index f2c8652d..5625c08 100644
--- a/content/browser/service_worker/service_worker_container_host.cc
+++ b/content/browser/service_worker/service_worker_container_host.cc
@@ -480,6 +480,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(blink::ServiceWorkerScopeMatches(registration->scope(),
                                           GetUrlForScopeMatch()));
+  DCHECK(registration->key() == key());
   if (!IsEligibleForServiceWorkerController())
     return;
   size_t key = registration->scope().spec().size();
@@ -1199,7 +1200,7 @@
   const auto& registrations = context_->GetLiveRegistrations();
   for (const auto& key_registration : registrations) {
     ServiceWorkerRegistration* registration = key_registration.second;
-    if (!registration->is_uninstalled() &&
+    if (!registration->is_uninstalled() && registration->key() == key() &&
         blink::ServiceWorkerScopeMatches(registration->scope(),
                                          GetUrlForScopeMatch())) {
       AddMatchingRegistration(registration);
diff --git a/content/browser/service_worker/service_worker_container_host.h b/content/browser/service_worker/service_worker_container_host.h
index 2378d100..a68dd80 100644
--- a/content/browser/service_worker/service_worker_container_host.h
+++ b/content/browser/service_worker/service_worker_container_host.h
@@ -680,8 +680,8 @@
   using ServiceWorkerRegistrationMap =
       std::map<size_t, scoped_refptr<ServiceWorkerRegistration>>;
   // Contains all living registrations whose scope this client's URL starts
-  // with, used for .ready and claim(). It is empty if
-  // IsEligibleForServiceWorkerController() is false. See also
+  // with and whose keys match this client's key, used for .ready and claim().
+  // It is empty if IsEligibleForServiceWorkerController() is false. See also
   // AddMatchingRegistration().
   ServiceWorkerRegistrationMap matching_registrations_;
 
diff --git a/content/browser/service_worker/service_worker_register_job.cc b/content/browser/service_worker/service_worker_register_job.cc
index b90a5515..fd709dde 100644
--- a/content/browser/service_worker/service_worker_register_job.cc
+++ b/content/browser/service_worker/service_worker_register_job.cc
@@ -848,7 +848,8 @@
        !it->IsAtEnd(); it->Advance()) {
     ServiceWorkerContainerHost* container_host = it->GetContainerHost();
     DCHECK(container_host->IsContainerForClient());
-    if (!blink::ServiceWorkerScopeMatches(
+    if (container_host->key() != registration->key() ||
+        !blink::ServiceWorkerScopeMatches(
             registration->scope(), container_host->GetUrlForScopeMatch())) {
       continue;
     }
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 0740b988..3fb3b4a 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -31,7 +31,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
-#include "components/services/storage/public/cpp/buckets/bucket_info.h"
+#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
 #include "components/services/storage/public/cpp/constants.h"
 #include "components/services/storage/public/cpp/filesystem/filesystem_impl.h"
 #include "components/services/storage/public/mojom/filesystem/directory.mojom.h"
@@ -257,7 +257,7 @@
   }
 }
 
-void OnQuotaManagedBucketDeleted(const storage::BucketInfo& bucket,
+void OnQuotaManagedBucketDeleted(const storage::BucketLocator& bucket,
                                  size_t* deletion_task_count,
                                  base::OnceClosure callback,
                                  blink::mojom::QuotaStatusCode status) {
@@ -265,9 +265,10 @@
   DCHECK_GT(*deletion_task_count, 0u);
   if (status != blink::mojom::QuotaStatusCode::kOk) {
     DLOG(ERROR) << "Couldn't remove data type " << static_cast<int>(bucket.type)
-                << " for bucket " << bucket.name << " with storage key "
-                << bucket.storage_key.GetDebugString() << " and bucket id "
-                << bucket.id << ". Status: " << static_cast<int>(status);
+                << " for bucket with storage key "
+                << bucket.storage_key.GetDebugString() << " is_default "
+                << bucket.is_default << " and bucket id " << bucket.id
+                << ". Status: " << static_cast<int>(status);
   }
 
   (*deletion_task_count)--;
@@ -893,7 +894,7 @@
       StoragePartition::OriginMatcherFunction origin_matcher,
       bool perform_storage_cleanup,
       base::OnceClosure callback,
-      const std::set<storage::BucketInfo>& buckets,
+      const std::set<storage::BucketLocator>& buckets,
       blink::mojom::StorageType quota_storage_type);
 
  private:
@@ -2208,7 +2209,7 @@
         StoragePartition::OriginMatcherFunction origin_matcher,
         bool perform_storage_cleanup,
         base::OnceClosure callback,
-        const std::set<storage::BucketInfo>& buckets,
+        const std::set<storage::BucketLocator>& buckets,
         blink::mojom::StorageType quota_storage_type) {
   // The QuotaManager manages all storage other than cookies, LocalStorage,
   // and SessionStorage. This loop wipes out most HTML5 storage for the given
diff --git a/content/browser/webrtc/resources/peer_connection_update_table.js b/content/browser/webrtc/resources/peer_connection_update_table.js
index 33ad19b9..688e194 100644
--- a/content/browser/webrtc/resources/peer_connection_update_table.js
+++ b/content/browser/webrtc/resources/peer_connection_update_table.js
@@ -4,6 +4,7 @@
 
 import {$} from 'chrome://resources/js/util.m.js';
 
+const MAX_NUMBER_OF_STATE_CHANGES_DISPLAYED = 10;
 /**
  * The data of a peer connection update.
  * @param {number} pid The id of the renderer.
@@ -101,42 +102,6 @@
       return;
     }
 
-    if (update.type === 'onIceCandidate' || update.type === 'addIceCandidate') {
-      // extract ICE candidate type from the field following typ.
-      const candidateType = update.value.match(/(?: typ )(host|srflx|relay)/);
-      if (candidateType) {
-        type += ' (' + candidateType[1] + ')';
-      }
-    } else if (
-        update.type === 'createOfferOnSuccess' ||
-        update.type === 'createAnswerOnSuccess') {
-      this.setLastOfferAnswer_(tableElement, update);
-    } else if (update.type === 'setLocalDescription') {
-      if (update.value !== this.getLastOfferAnswer_(tableElement)) {
-        type += ' (munged)';
-      }
-    } else if (update.type === 'setConfiguration') {
-      // Update the configuration that is displayed at the top.
-      peerConnectionElement.firstChild.children[2].textContent = update.value;
-    }
-
-    const summaryItem = $('summary-template').content.cloneNode(true);
-    const summary = summaryItem.querySelector('summary');
-    summary.textContent = type;
-    row.appendChild(summaryItem);
-
-    const valueContainer = document.createElement('pre');
-    const details = row.cells[1].childNodes[0];
-    details.appendChild(valueContainer);
-
-    // Highlight ICE failures and failure callbacks.
-    if ((update.type === 'iceConnectionStateChange' &&
-         update.value === 'ICEConnectionStateFailed') ||
-        update.type.indexOf('OnFailure') !== -1 ||
-        update.type === 'addIceCandidateFailed') {
-      valueContainer.parentElement.classList.add('update-log-failure');
-    }
-
     let {value} = update;
     // map internal names and values to names and events from the
     // specification. This is a display change which shall not
@@ -171,6 +136,57 @@
           value;
     }
 
+    if (update.type === 'onIceCandidate' || update.type === 'addIceCandidate') {
+      // extract ICE candidate type from the field following typ.
+      const candidateType = update.value.match(/(?: typ )(host|srflx|relay)/);
+      if (candidateType) {
+        type += ' (' + candidateType[1] + ')';
+      }
+    } else if (
+        update.type === 'createOfferOnSuccess' ||
+        update.type === 'createAnswerOnSuccess') {
+      this.setLastOfferAnswer_(tableElement, update);
+    } else if (update.type === 'setLocalDescription') {
+      if (update.value !== this.getLastOfferAnswer_(tableElement)) {
+        type += ' (munged)';
+      }
+    } else if (update.type === 'setConfiguration') {
+      // Update the configuration that is displayed at the top.
+      peerConnectionElement.firstChild.children[2].textContent = update.value;
+    } else if (['iceConnectionStateChange', 'connectionStateChange',
+        'signalingStateChange'].includes(update.type)) {
+      const fieldName = {
+        'iceConnectionStateChange' : 'iceconnectionstate',
+        'connectionStateChange' : 'connectionstate',
+        'signalingStateChange' : 'signalingstate',
+      }[update.type];
+      const el = peerConnectionElement.getElementsByClassName(fieldName)[0];
+      const numberOfEvents = el.textContent.split(' => ').length;
+      if (numberOfEvents < MAX_NUMBER_OF_STATE_CHANGES_DISPLAYED) {
+        el.textContent += ' => ' + value;
+      } else if (numberOfEvents === MAX_NUMBER_OF_STATE_CHANGES_DISPLAYED) {
+        el.textContent += ' ...';
+      }
+    }
+
+    const summaryItem = $('summary-template').content.cloneNode(true);
+    const summary = summaryItem.querySelector('summary');
+    summary.textContent = type;
+    row.appendChild(summaryItem);
+
+    const valueContainer = document.createElement('pre');
+    const details = row.cells[1].childNodes[0];
+    details.appendChild(valueContainer);
+
+    // Highlight ICE failures and failure callbacks.
+    if ((update.type === 'iceConnectionStateChange' &&
+         update.value === 'ICEConnectionStateFailed') ||
+        update.type.indexOf('OnFailure') !== -1 ||
+        update.type === 'addIceCandidateFailed') {
+      valueContainer.parentElement.classList.add('update-log-failure');
+    }
+
+
     // RTCSessionDescription is serialized as 'type: <type>, sdp:'
     if (update.value.indexOf(', sdp:') !== -1) {
       const [type, sdp] = update.value.substr(6).split(', sdp: ');
diff --git a/content/browser/webrtc/resources/webrtc_internals.js b/content/browser/webrtc/resources/webrtc_internals.js
index 1ea6d3f..341edb51 100644
--- a/content/browser/webrtc/resources/webrtc_internals.js
+++ b/content/browser/webrtc/resources/webrtc_internals.js
@@ -374,6 +374,21 @@
   }
   peerConnectionElement.appendChild(deprecationNotices);
 
+  const iceConnectionStates = document.createElement('div');
+  iceConnectionStates.textContent = 'ICE connection state: new';
+  iceConnectionStates.className = 'iceconnectionstate';
+  peerConnectionElement.appendChild(iceConnectionStates);
+
+  const connectionStates = document.createElement('div');
+  connectionStates.textContent = 'Connection state: new';
+  connectionStates.className = 'connectionstate';
+  peerConnectionElement.appendChild(connectionStates);
+
+  const signalingStates = document.createElement('div');
+  signalingStates.textContent = 'Signaling state: new';
+  signalingStates.className = 'signalingstate';
+  peerConnectionElement.appendChild(signalingStates);
+
   return peerConnectionElement;
 }
 
diff --git a/content/browser/webui/content_web_ui_controller_factory.cc b/content/browser/webui/content_web_ui_controller_factory.cc
index daf6c25..349e128 100644
--- a/content/browser/webui/content_web_ui_controller_factory.cc
+++ b/content/browser/webui/content_web_ui_controller_factory.cc
@@ -6,7 +6,7 @@
 
 #include "build/build_config.h"
 #include "content/browser/appcache/appcache_internals_ui.h"
-#include "content/browser/attribution_reporting/conversion_internals_ui.h"
+#include "content/browser/attribution_reporting/attribution_internals_ui.h"
 #include "content/browser/gpu/gpu_internals_ui.h"
 #include "content/browser/indexed_db/indexed_db_internals_ui.h"
 #include "content/browser/media/media_internals_ui.h"
@@ -46,7 +46,7 @@
       url.host_piece() == kChromeUINetworkErrorsListingHost ||
       url.host_piece() == kChromeUIPrerenderInternalsHost ||
       url.host_piece() == kChromeUIProcessInternalsHost ||
-      url.host_piece() == kChromeUIConversionInternalsHost ||
+      url.host_piece() == kChromeUIAttributionInternalsHost ||
       url.host_piece() == kChromeUIUkmHost) {
     return const_cast<ContentWebUIControllerFactory*>(this);
   }
@@ -87,8 +87,8 @@
     return std::make_unique<PrerenderInternalsUI>(web_ui);
   if (url.host_piece() == kChromeUIProcessInternalsHost)
     return std::make_unique<ProcessInternalsUI>(web_ui);
-  if (url.host_piece() == kChromeUIConversionInternalsHost)
-    return std::make_unique<ConversionInternalsUI>(web_ui);
+  if (url.host_piece() == kChromeUIAttributionInternalsHost)
+    return std::make_unique<AttributionInternalsUI>(web_ui);
   if (url.host_piece() == kChromeUIUkmHost)
     return std::make_unique<UkmInternalsUI>(web_ui);
   if (url.host_piece() == kChromeUIMediaInternalsHost) {
diff --git a/content/browser/worker_host/dedicated_worker_host.cc b/content/browser/worker_host/dedicated_worker_host.cc
index a396957..99e67ee 100644
--- a/content/browser/worker_host/dedicated_worker_host.cc
+++ b/content/browser/worker_host/dedicated_worker_host.cc
@@ -722,7 +722,8 @@
     return;
 
   if (!blink::ServiceWorkerScopeMatches(container_host->controller()->scope(),
-                                        script_url)) {
+                                        script_url) ||
+      container_host->key() != storage_key_) {
     // Count the number of dedicated workers that 1) are controlled by a service
     // worker that is inherited from a controlled document, and 2) will not be
     // controlled by that service worker after PlzDedicatedWorker is enabled.
@@ -775,7 +776,8 @@
     // one of service workers registered for the origin. The scope matched
     // service worker may be different from the one that controls the ancestor
     // frame.
-    if (blink::ServiceWorkerScopeMatches(registration->scope(), script_url))
+    if (blink::ServiceWorkerScopeMatches(registration->scope(), script_url) &&
+        registration->key() == storage_key_)
       return;
   }
 
diff --git a/content/browser/worker_host/shared_worker_host_unittest.cc b/content/browser/worker_host/shared_worker_host_unittest.cc
index bb03db51..3d77662 100644
--- a/content/browser/worker_host/shared_worker_host_unittest.cc
+++ b/content/browser/worker_host/shared_worker_host_unittest.cc
@@ -113,8 +113,6 @@
     mojo::PendingRemote<network::mojom::URLLoaderFactory>
         loader_factory_remote =
             network::NotImplementedURLLoaderFactory::Create();
-    subresource_loader_params->pending_appcache_loader_factory =
-        std::move(loader_factory_remote);
 
     // Set up for service worker.
     auto service_worker_handle =
diff --git a/content/browser/worker_host/worker_script_fetcher.cc b/content/browser/worker_host/worker_script_fetcher.cc
index f635246d..c00b61c 100644
--- a/content/browser/worker_host/worker_script_fetcher.cc
+++ b/content/browser/worker_host/worker_script_fetcher.cc
@@ -114,13 +114,6 @@
     bool success) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  // If a URLLoaderFactory for AppCache is supplied, use that.
-  if (subresource_loader_params &&
-      subresource_loader_params->pending_appcache_loader_factory) {
-    subresource_loader_factories->pending_appcache_factory() =
-        std::move(subresource_loader_params->pending_appcache_loader_factory);
-  }
-
   // Prepare the controller service worker info to pass to the renderer.
   blink::mojom::ControllerServiceWorkerInfoPtr controller;
   base::WeakPtr<ServiceWorkerObjectHost> controller_service_worker_object_host;
diff --git a/content/dev_ui_content_resources.grd b/content/dev_ui_content_resources.grd
index b9c7b262..21a8da02 100644
--- a/content/dev_ui_content_resources.grd
+++ b/content/dev_ui_content_resources.grd
@@ -18,10 +18,10 @@
       <include name="IDR_APPCACHE_INTERNALS_HTML" file="browser/resources/appcache/appcache_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
       <include name="IDR_APPCACHE_INTERNALS_JS" file="browser/resources/appcache/appcache_internals.js" flattenhtml="false" type="BINDATA" />
       <include name="IDR_APPCACHE_INTERNALS_CSS" file="browser/resources/appcache/appcache_internals.css" type="BINDATA" />
-      <include name="IDR_CONVERSION_INTERNALS_HTML" file="browser/resources/attribution_reporting/conversion_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
-      <include name="IDR_CONVERSION_INTERNALS_JS" file="browser/resources/attribution_reporting/conversion_internals.js" type="BINDATA" />
-      <include name="IDR_CONVERSION_INTERNALS_CSS" file="browser/resources/attribution_reporting/conversion_internals.css" type="BINDATA" />
-      <include name="IDR_CONVERSION_INTERNALS_MOJOM_JS" file="${root_gen_dir}/mojom-webui/content/browser/attribution_reporting/conversion_internals.mojom-webui.js" use_base_dir="false" type="BINDATA" />
+      <include name="IDR_ATTRIBUTION_INTERNALS_HTML" file="browser/resources/attribution_reporting/attribution_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+      <include name="IDR_ATTRIBUTION_INTERNALS_JS" file="browser/resources/attribution_reporting/attribution_internals.js" type="BINDATA" />
+      <include name="IDR_ATTRIBUTION_INTERNALS_CSS" file="browser/resources/attribution_reporting/attribution_internals.css" type="BINDATA" />
+      <include name="IDR_ATTRIBUTION_INTERNALS_MOJOM_JS" file="${root_gen_dir}/mojom-webui/content/browser/attribution_reporting/attribution_internals.mojom-webui.js" use_base_dir="false" type="BINDATA" />
       <include name="IDR_GPU_BROWSER_BRIDGE_JS" file="browser/resources/gpu/browser_bridge.js" type="BINDATA" />
       <include name="IDR_GPU_INFO_VIEW_JS" file="browser/resources/gpu/info_view.js" type="BINDATA" />
       <include name="IDR_GPU_INTERNALS_HTML" file="browser/resources/gpu/gpu_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
index 903f72c..366eab47 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
@@ -251,6 +251,14 @@
         Log.d(TAG, "Encountered the first usable RELRO bundle.");
         sZygotePid = connection.getZygotePid();
         sZygoteBundle = zygoteBundle;
+
+        // Use the RELRO FD in the current process. Some nontrivial CPU cycles are consumed because
+        // it needs an mmap+memcmp(5 megs)+mmap+munmap. This happens on the process launcher thread,
+        // will work correctly on any thread.
+        LibraryLoader.getInstance().getMediator().takeSharedRelrosFromBundle(zygoteBundle);
+
+        // Use the RELRO FD for all processes launched up to now. Non-blocking 'oneway' IPCs are
+        // used. The CPU time costs in the child process are the same.
         sendPreviouslySeenZygoteBundleToExistingConnections(connection.getPid());
     }
 
diff --git a/content/public/common/url_constants.cc b/content/public/common/url_constants.cc
index efe3bd7..dfc5dc4 100644
--- a/content/public/common/url_constants.cc
+++ b/content/public/common/url_constants.cc
@@ -26,10 +26,10 @@
 const char kGoogleChromeScheme[] = "googlechrome";
 
 const char kChromeUIAppCacheInternalsHost[] = "appcache-internals";
+const char kChromeUIAttributionInternalsHost[] = "conversion-internals";
 const char kChromeUIIndexedDBInternalsHost[] = "indexeddb-internals";
 const char kChromeUIBlobInternalsHost[] = "blob-internals";
 const char kChromeUIBrowserCrashHost[] = "inducebrowsercrashforrealz";
-const char kChromeUIConversionInternalsHost[] = "conversion-internals";
 const char kChromeUIDinoHost[] = "dino";
 const char kChromeUIGpuHost[] = "gpu";
 const char kChromeUIHistogramHost[] = "histograms";
diff --git a/content/public/common/url_constants.h b/content/public/common/url_constants.h
index f3f67fb8..e59c9a7 100644
--- a/content/public/common/url_constants.h
+++ b/content/public/common/url_constants.h
@@ -36,9 +36,9 @@
 
 CONTENT_EXPORT extern const char kChromeUIAccessibilityHost[];
 CONTENT_EXPORT extern const char kChromeUIAppCacheInternalsHost[];
+CONTENT_EXPORT extern const char kChromeUIAttributionInternalsHost[];
 CONTENT_EXPORT extern const char kChromeUIBlobInternalsHost[];
 CONTENT_EXPORT extern const char kChromeUIBrowserCrashHost[];
-CONTENT_EXPORT extern const char kChromeUIConversionInternalsHost[];
 CONTENT_EXPORT extern const char kChromeUIDinoHost[];
 CONTENT_EXPORT extern const char kChromeUIGpuHost[];
 CONTENT_EXPORT extern const char kChromeUIHistogramHost[];
diff --git a/content/public/test/dump_accessibility_test_helper.cc b/content/public/test/dump_accessibility_test_helper.cc
index c2e0781..e385a99c 100644
--- a/content/public/test/dump_accessibility_test_helper.cc
+++ b/content/public/test/dump_accessibility_test_helper.cc
@@ -358,6 +358,14 @@
            FILE_PATH_LITERAL(".txt");
   }
 #endif
+#if defined(OS_CHROMEOS)
+  if (expectation_type_ == "blink") {
+    FilePath::StringType suffix;
+    if (!expectations_qualifier.empty())
+      suffix = FILE_PATH_LITERAL("-") + expectations_qualifier;
+    return suffix + FILE_PATH_LITERAL("-expected-blink-cros.txt");
+  }
+#endif
   return FILE_PATH_LITERAL("");
 }
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 0117157..c70c1a4 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -2887,7 +2887,7 @@
             this, std::move(container_info),
             std::move(controller_service_worker_info),
             network::SharedURLLoaderFactory::Create(
-                new_loader_factories->CloneWithoutAppCacheFactory()));
+                new_loader_factories->Clone()));
   }
 
   WebString subresource_filter = navigation_params->response.HttpHeaderField(
@@ -3363,7 +3363,7 @@
           blink::WebDedicatedOrSharedWorkerFetchContext::Create(
               provider->context(), GetWebView()->GetRendererPreferences(),
               std::move(watcher_receiver), GetLoaderFactoryBundle()->Clone(),
-              GetLoaderFactoryBundle()->CloneWithoutAppCacheFactory(),
+              GetLoaderFactoryBundle()->Clone(),
               /*pending_subresource_loader_updater=*/mojo::NullReceiver(),
               web_cors_exempt_header_list,
               std::move(pending_resource_load_info_notifier));
diff --git a/content/renderer/worker/dedicated_worker_host_factory_client.cc b/content/renderer/worker/dedicated_worker_host_factory_client.cc
index d2197eed..c322ecf 100644
--- a/content/renderer/worker/dedicated_worker_host_factory_client.cc
+++ b/content/renderer/worker/dedicated_worker_host_factory_client.cc
@@ -83,8 +83,7 @@
             ->CloneForNestedWorker(
                 service_worker_provider_context_.get(),
                 subresource_loader_factory_bundle_->Clone(),
-                subresource_loader_factory_bundle_
-                    ->CloneWithoutAppCacheFactory(),
+                subresource_loader_factory_bundle_->Clone(),
                 std::move(pending_subresource_loader_updater_),
                 std::move(task_runner));
   } else {
@@ -119,7 +118,7 @@
               service_worker_provider_context_.get(), renderer_preference,
               std::move(watcher_receiver),
               subresource_loader_factory_bundle_->Clone(),
-              subresource_loader_factory_bundle_->CloneWithoutAppCacheFactory(),
+              subresource_loader_factory_bundle_->Clone(),
               std::move(pending_subresource_loader_updater_),
               web_cors_exempt_header_list,
               std::move(pending_resource_load_info_notifier));
diff --git a/content/renderer/worker/embedded_shared_worker_stub.cc b/content/renderer/worker/embedded_shared_worker_stub.cc
index ebb5b2c5..5e851e7 100644
--- a/content/renderer/worker/embedded_shared_worker_stub.cc
+++ b/content/renderer/worker/embedded_shared_worker_stub.cc
@@ -150,10 +150,9 @@
     mojo::PendingReceiver<blink::mojom::RendererPreferenceWatcher>
         preference_watcher_receiver,
     const std::vector<std::string>& cors_exempt_header_list) {
-  // Make the factory used for service worker network fallback (that should
-  // skip AppCache if it is provided).
+  // Make the factory used for service worker network fallback.
   std::unique_ptr<network::PendingSharedURLLoaderFactory> fallback_factory =
-      subresource_loader_factory_bundle_->CloneWithoutAppCacheFactory();
+      subresource_loader_factory_bundle_->Clone();
 
   blink::WebVector<blink::WebString> web_cors_exempt_header_list(
       cors_exempt_header_list.size());
diff --git a/content/test/data/accessibility/html/input-date-with-popup-open-expected-auralinux.txt b/content/test/data/accessibility/html/input-date-with-popup-open-expected-auralinux.txt
index 81c55c17..c8d08ce 100644
--- a/content/test/data/accessibility/html/input-date-with-popup-open-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/input-date-with-popup-open-expected-auralinux.txt
@@ -16,11 +16,11 @@
 ++++++++++++++[section]
 ++++++++++++++++[push button] name='Show month selection panel'
 ++++++++++++++++++[static] name='September 2008'
-++++++++++++++++++[document frame]
+++++++++++++++++++[image]
 ++++++++++++++[push button] name='Show previous month'
-++++++++++++++++[document frame]
+++++++++++++++++[image]
 ++++++++++++++[push button] name='Show next month'
-++++++++++++++++[document frame]
+++++++++++++++++[image]
 ++++++++++++++[table] cols=7 headers=(NONE); rows=6 headers=(NONE); caption=false; spans=(all: 1x1)
 ++++++++++++++++[section]
 ++++++++++++++++++[section]
diff --git a/content/test/data/accessibility/html/input-date-with-popup-open-expected-blink.txt b/content/test/data/accessibility/html/input-date-with-popup-open-expected-blink.txt
index f064a28..bdb6069 100644
--- a/content/test/data/accessibility/html/input-date-with-popup-open-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-date-with-popup-open-expected-blink.txt
@@ -27,11 +27,11 @@
 ++++++++++++++++++++genericContainer
 ++++++++++++++++++++++button name='Show month selection panel'
 ++++++++++++++++++++++++staticText
-++++++++++++++++++++++++svgRoot
+++++++++++++++++++++++++image
 ++++++++++++++++++++button name='Show previous month'
-++++++++++++++++++++++svgRoot
+++++++++++++++++++++++image
 ++++++++++++++++++++button name='Show next month'
-++++++++++++++++++++++svgRoot
+++++++++++++++++++++++image
 ++++++++++++++++++grid activedescendantId=cell
 ++++++++++++++++++++genericContainer
 ++++++++++++++++++++++genericContainer
diff --git a/content/test/data/accessibility/html/input-date-with-popup-open-expected-mac.txt b/content/test/data/accessibility/html/input-date-with-popup-open-expected-mac.txt
index 3077f35..caa575b 100644
--- a/content/test/data/accessibility/html/input-date-with-popup-open-expected-mac.txt
+++ b/content/test/data/accessibility/html/input-date-with-popup-open-expected-mac.txt
@@ -16,11 +16,11 @@
 ++++++++++++++AXGroup AXRoleDescription='group'
 ++++++++++++++++AXButton AXDescription='Show month selection panel' AXRoleDescription='button'
 ++++++++++++++++++AXStaticText AXRoleDescription='text' AXValue='September 2008'
-++++++++++++++++++AXGroup AXRoleDescription='group'
+++++++++++++++++++AXImage AXRoleDescription='image'
 ++++++++++++++AXButton AXDescription='Show previous month' AXRoleDescription='button'
-++++++++++++++++AXGroup
+++++++++++++++++AXImage
 ++++++++++++++AXButton AXDescription='Show next month' AXRoleDescription='button'
-++++++++++++++++AXGroup
+++++++++++++++++AXImage
 ++++++++++++++AXTable AXRoleDescription='table'
 ++++++++++++++++AXGroup AXRoleDescription='group'
 ++++++++++++++++++AXGroup AXRoleDescription='group'
diff --git a/content/test/data/accessibility/html/input-date-with-popup-open-expected-win.txt b/content/test/data/accessibility/html/input-date-with-popup-open-expected-win.txt
index 42fbda0..e01204a2d 100644
--- a/content/test/data/accessibility/html/input-date-with-popup-open-expected-win.txt
+++ b/content/test/data/accessibility/html/input-date-with-popup-open-expected-win.txt
@@ -16,11 +16,11 @@
 ++++++++++++++IA2_ROLE_SECTION ia2_hypertext='<obj0>'
 ++++++++++++++++ROLE_SYSTEM_PUSHBUTTON name='Show month selection panel' FOCUSABLE
 ++++++++++++++++++ROLE_SYSTEM_STATICTEXT
-++++++++++++++++++ROLE_SYSTEM_GRAPHIC
+++++++++++++++++++ROLE_SYSTEM_GRAPHIC READONLY
 ++++++++++++++ROLE_SYSTEM_PUSHBUTTON name='Show previous month' FOCUSABLE ia2_hypertext='<obj0>'
-++++++++++++++++ROLE_SYSTEM_GRAPHIC
+++++++++++++++++ROLE_SYSTEM_GRAPHIC READONLY
 ++++++++++++++ROLE_SYSTEM_PUSHBUTTON name='Show next month' FOCUSABLE ia2_hypertext='<obj0>'
-++++++++++++++++ROLE_SYSTEM_GRAPHIC
+++++++++++++++++ROLE_SYSTEM_GRAPHIC READONLY
 ++++++++++++++ROLE_SYSTEM_TABLE FOCUSABLE ia2_hypertext='<obj0><obj1><obj2><obj3>'
 ++++++++++++++++IA2_ROLE_SECTION ia2_hypertext='<obj0><obj1><obj2><obj3><obj4><obj5><obj6>'
 ++++++++++++++++++IA2_ROLE_SECTION ia2_hypertext='Su'
diff --git a/content/test/data/accessibility/html/svg-and-math-elements-expected-auralinux.txt b/content/test/data/accessibility/html/svg-and-math-elements-expected-auralinux.txt
index 5331e41bb..c70e2b6 100644
--- a/content/test/data/accessibility/html/svg-and-math-elements-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/svg-and-math-elements-expected-auralinux.txt
@@ -1,6 +1,6 @@
 [document web] tag:#document
 ++[section] class:svg-container tag:div
-++++[document frame] name='original' tag:svg
+++++[image] name='original' tag:svg
 ++++[section] name='created' class:html-namespace tag:svg
 ++++[section] name='created' class:custom-namespace tag:svg
 ++[section] class:math-container tag:div
diff --git a/content/test/data/accessibility/html/svg-and-math-elements-expected-blink.txt b/content/test/data/accessibility/html/svg-and-math-elements-expected-blink.txt
index 6ffd49f..78affaf 100644
--- a/content/test/data/accessibility/html/svg-and-math-elements-expected-blink.txt
+++ b/content/test/data/accessibility/html/svg-and-math-elements-expected-blink.txt
@@ -2,7 +2,7 @@
 ++genericContainer ignored htmlTag='html'
 ++++genericContainer ignored htmlTag='body'
 ++++++genericContainer className='svg-container' htmlTag='div'
-++++++++svgRoot htmlTag='svg' name='original'
+++++++++image htmlTag='svg' name='original'
 ++++++++genericContainer className='html-namespace' htmlTag='svg' name='created'
 ++++++++genericContainer className='custom-namespace' htmlTag='svg' name='created'
 ++++++genericContainer className='math-container' htmlTag='div'
diff --git a/content/test/data/accessibility/html/svg-child-of-button-expected-auralinux.txt b/content/test/data/accessibility/html/svg-child-of-button-expected-auralinux.txt
index 03788b2..1f8660d0 100644
--- a/content/test/data/accessibility/html/svg-child-of-button-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/svg-child-of-button-expected-auralinux.txt
@@ -1,4 +1,4 @@
 [document web] tag:#document
 ++[section] tag:body
 ++++[push button] name='Search' tag:button
-++++++[document frame] class:button-child-icon tag:svg
+++++++[image] class:button-child-icon tag:svg
diff --git a/content/test/data/accessibility/html/svg-child-of-button-expected-blink.txt b/content/test/data/accessibility/html/svg-child-of-button-expected-blink.txt
index 3898396..d682ab56 100644
--- a/content/test/data/accessibility/html/svg-child-of-button-expected-blink.txt
+++ b/content/test/data/accessibility/html/svg-child-of-button-expected-blink.txt
@@ -2,4 +2,4 @@
 ++genericContainer ignored htmlTag='html'
 ++++genericContainer htmlTag='body'
 ++++++button htmlTag='button' name='Search'
-++++++++svgRoot className='button-child-icon' htmlTag='svg'
+++++++++image className='button-child-icon' htmlTag='svg'
diff --git a/content/test/data/accessibility/html/svg-elements-not-mapped-expected-auralinux.txt b/content/test/data/accessibility/html/svg-elements-not-mapped-expected-auralinux.txt
index 74aa0fc..66f79e82 100644
--- a/content/test/data/accessibility/html/svg-elements-not-mapped-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/svg-elements-not-mapped-expected-auralinux.txt
@@ -1,45 +1,45 @@
 [document web] tag:#document
 ++[section] tag:body
-++++[document frame] name='animate should not be exposed' tag:svg
-++++[document frame] name='animateMotion should not be exposed' tag:svg
-++++[document frame] name='animateTransform should not be exposed' tag:svg
-++++[document frame] name='clipPath should not be exposed' tag:svg
-++++[document frame] name='cursor should not be exposed' tag:svg
-++++[document frame] name='defs should not be exposed' tag:svg
-++++[document frame] name='discard should not be exposed' tag:svg
-++++[document frame] name='filter and filter effect elements should not be exposed' tag:svg
-++++[document frame] name='hatch should not be exposed' tag:svg
-++++[document frame] name='hatchpath should not be exposed' tag:svg
-++++[document frame] name='image which lacks criteria for inclusion should not be exposed' tag:svg
-++++[document frame] name='linearGradient should not be exposed' tag:svg
-++++[document frame] name='marker should not be exposed' tag:svg
-++++[document frame] name='mask should not be exposed' tag:svg
-++++[document frame] name='metadata should not be exposed' tag:svg
-++++[document frame] name='mpath should not be exposed' tag:svg
-++++[document frame] name='pattern should not be exposed' tag:svg
-++++[document frame] name='radialGradient should not be exposed' tag:svg
-++++[document frame] name='solidColor should not be exposed' tag:svg
-++++[document frame] name='stop should not be exposed' tag:svg
+++++[image] name='animate should not be exposed' tag:svg
+++++[image] name='animateMotion should not be exposed' tag:svg
+++++[image] name='animateTransform should not be exposed' tag:svg
+++++[image] name='clipPath should not be exposed' tag:svg
+++++[image] name='cursor should not be exposed' tag:svg
+++++[image] name='defs should not be exposed' tag:svg
+++++[image] name='discard should not be exposed' tag:svg
+++++[image] name='filter and filter effect elements should not be exposed' tag:svg
+++++[image] name='hatch should not be exposed' tag:svg
+++++[image] name='hatchpath should not be exposed' tag:svg
+++++[image] name='image which lacks criteria for inclusion should not be exposed' tag:svg
+++++[image] name='linearGradient should not be exposed' tag:svg
+++++[image] name='marker should not be exposed' tag:svg
+++++[image] name='mask should not be exposed' tag:svg
+++++[image] name='metadata should not be exposed' tag:svg
+++++[image] name='mpath should not be exposed' tag:svg
+++++[image] name='pattern should not be exposed' tag:svg
+++++[image] name='radialGradient should not be exposed' tag:svg
+++++[image] name='solidColor should not be exposed' tag:svg
+++++[image] name='stop should not be exposed' tag:svg
 ++++[document frame] name='style and set should not be exposed' tag:svg
 ++++++[image] id:clickable tag:rect
 ++++[document frame] name='switch should not be exposed' tag:svg
 ++++++[section] tag:text
 ++++++++[static] name='Hey!'
-++++[document frame] name='view should not be exposed' tag:svg
-++++[document frame] name='desc should not be exposed as accessible object' tag:svg
-++++[document frame] name='title should not be exposed as accessible object' tag:svg
-++++[document frame] name='circle which lacks criteria for inclusion should not be exposed' tag:svg
-++++[document frame] name='ellipse which lacks criteria for inclusion should not be exposed' tag:svg
+++++[image] name='view should not be exposed' tag:svg
+++++[image] name='desc should not be exposed as accessible object' tag:svg
+++++[image] name='title should not be exposed as accessible object' tag:svg
+++++[image] name='circle which lacks criteria for inclusion should not be exposed' tag:svg
+++++[image] name='ellipse which lacks criteria for inclusion should not be exposed' tag:svg
 ++++[document frame] name='foreignObject which lacks criteria for inclusion should not be exposed' tag:svg
 ++++++[paragraph] tag:p
 ++++++++[static] name='Hello world'
-++++[document frame] name='group which lacks criteria for inclusion should not be exposed' tag:svg
-++++[document frame] name='line which lacks criteria for inclusion should not be exposed' tag:svg
-++++[document frame] name='path which lacks criteria for inclusion should not be exposed' tag:svg
-++++[document frame] name='polygon which lacks criteria for inclusion should not be exposed' tag:svg
-++++[document frame] name='polyline which lacks criteria for inclusion should not be exposed' tag:svg
-++++[document frame] name='rect which lacks criteria for inclusion should not be exposed' tag:svg
-++++[document frame] name='symbol which lacks criteria for inclusion should not be exposed' tag:svg
+++++[image] name='group which lacks criteria for inclusion should not be exposed' tag:svg
+++++[image] name='line which lacks criteria for inclusion should not be exposed' tag:svg
+++++[image] name='path which lacks criteria for inclusion should not be exposed' tag:svg
+++++[image] name='polygon which lacks criteria for inclusion should not be exposed' tag:svg
+++++[image] name='polyline which lacks criteria for inclusion should not be exposed' tag:svg
+++++[image] name='rect which lacks criteria for inclusion should not be exposed' tag:svg
+++++[image] name='symbol which lacks criteria for inclusion should not be exposed' tag:svg
 ++++[document frame] name='textPath which lacks criteria for inclusion should not be exposed' tag:svg
 ++++++[section] tag:text
 ++++++++[static] name='Hello'
diff --git a/content/test/data/accessibility/html/svg-elements-not-mapped-expected-blink-cros.txt b/content/test/data/accessibility/html/svg-elements-not-mapped-expected-blink-cros.txt
new file mode 100644
index 0000000..bfc13f0
--- /dev/null
+++ b/content/test/data/accessibility/html/svg-elements-not-mapped-expected-blink-cros.txt
@@ -0,0 +1,69 @@
+rootWebArea htmlTag='#document'
+++genericContainer ignored htmlTag='html'
+++++genericContainer htmlTag='body'
+++++++image htmlTag='svg' name='animate should not be exposed'
+++++++image htmlTag='svg' name='animateMotion should not be exposed'
+++++++image htmlTag='svg' name='animateTransform should not be exposed'
+++++++image htmlTag='svg' name='clipPath should not be exposed'
+++++++image htmlTag='svg' name='cursor should not be exposed'
+++++++image htmlTag='svg' name='defs should not be exposed'
+++++++++genericContainer ignored invisible htmlTag='solidcolor'
+++++++image htmlTag='svg' name='discard should not be exposed'
+++++++svgRoot htmlTag='svg' name='filter and filter effect elements should not be exposed'
+++++++++group htmlTag='g'
+++++++++++group htmlTag='g'
+++++++image htmlTag='svg' name='hatch should not be exposed'
+++++++image htmlTag='svg' name='hatchpath should not be exposed'
+++++++image htmlTag='svg' name='image which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='linearGradient should not be exposed'
+++++++image htmlTag='svg' name='marker should not be exposed'
+++++++image htmlTag='svg' name='mask should not be exposed'
+++++++image htmlTag='svg' name='metadata should not be exposed'
+++++++image htmlTag='svg' name='mpath should not be exposed'
+++++++image htmlTag='svg' name='pattern should not be exposed'
+++++++image htmlTag='svg' name='radialGradient should not be exposed'
+++++++image htmlTag='svg' name='solidColor should not be exposed'
+++++++image htmlTag='svg' name='stop should not be exposed'
+++++++svgRoot htmlTag='svg' name='style and set should not be exposed'
+++++++++graphicsSymbol htmlTag='rect'
+++++++svgRoot htmlTag='svg' name='switch should not be exposed'
+++++++++genericContainer htmlTag='text'
+++++++++++staticText name='Hey!'
+++++++++++++inlineTextBox name='Hey!'
+++++++image htmlTag='svg' name='view should not be exposed'
+++++++++genericContainer ignored invisible htmlTag='view'
+++++++++genericContainer ignored invisible htmlTag='view'
+++++++image htmlTag='svg' name='desc should not be exposed as accessible object'
+++++++image htmlTag='svg' name='title should not be exposed as accessible object'
+++++++image htmlTag='svg' name='circle which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='ellipse which lacks criteria for inclusion should not be exposed'
+++++++svgRoot htmlTag='svg' name='foreignObject which lacks criteria for inclusion should not be exposed'
+++++++++group ignored htmlTag='foreignobject'
+++++++++++paragraph htmlTag='p'
+++++++++++++staticText name='Hello world'
+++++++++++++++inlineTextBox name='Hello '
+++++++++++++++inlineTextBox name='world'
+++++++svgRoot htmlTag='svg' name='group which lacks criteria for inclusion should not be exposed'
+++++++++group htmlTag='g'
+++++++image htmlTag='svg' name='line which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='path which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='polygon which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='polyline which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='rect which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='symbol which lacks criteria for inclusion should not be exposed'
+++++++svgRoot htmlTag='svg' name='textPath which lacks criteria for inclusion should not be exposed'
+++++++++genericContainer htmlTag='text'
+++++++++++staticText name='Hello'
+++++++++++++inlineTextBox name='H'
+++++++++++++inlineTextBox name='e'
+++++++++++++inlineTextBox name='l'
+++++++++++++inlineTextBox name='l'
+++++++++++++inlineTextBox name='o'
+++++++svgRoot htmlTag='svg' name='tspan which lacks criteria for inclusion should not be exposed'
+++++++++genericContainer htmlTag='text'
+++++++++++staticText name='Hello '
+++++++++++++inlineTextBox name='Hello '
+++++++++++staticText name='world'
+++++++++++++inlineTextBox name='world'
+++++++++++staticText name='!'
+++++++++++++inlineTextBox name='!'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/svg-elements-not-mapped-expected-blink.txt b/content/test/data/accessibility/html/svg-elements-not-mapped-expected-blink.txt
index bb61771..06654af 100644
--- a/content/test/data/accessibility/html/svg-elements-not-mapped-expected-blink.txt
+++ b/content/test/data/accessibility/html/svg-elements-not-mapped-expected-blink.txt
@@ -1,56 +1,56 @@
 rootWebArea htmlTag='#document'
-++genericContainer htmlTag='html'
+++genericContainer ignored htmlTag='html'
 ++++genericContainer htmlTag='body'
-++++++svgRoot htmlTag='svg' name='animate should not be exposed'
-++++++svgRoot htmlTag='svg' name='animateMotion should not be exposed'
-++++++svgRoot htmlTag='svg' name='animateTransform should not be exposed'
-++++++svgRoot htmlTag='svg' name='clipPath should not be exposed'
-++++++svgRoot htmlTag='svg' name='cursor should not be exposed'
-++++++svgRoot htmlTag='svg' name='defs should not be exposed'
-++++++++genericContainer invisible htmlTag='solidcolor'
-++++++svgRoot htmlTag='svg' name='discard should not be exposed'
-++++++svgRoot htmlTag='svg' name='filter and filter effect elements should not be exposed'
-++++++++group htmlTag='g'
-++++++++++group htmlTag='g'
-++++++svgRoot htmlTag='svg' name='hatch should not be exposed'
-++++++svgRoot htmlTag='svg' name='hatchpath should not be exposed'
-++++++svgRoot htmlTag='svg' name='image which lacks criteria for inclusion should not be exposed'
-++++++svgRoot htmlTag='svg' name='linearGradient should not be exposed'
-++++++svgRoot htmlTag='svg' name='marker should not be exposed'
-++++++svgRoot htmlTag='svg' name='mask should not be exposed'
-++++++svgRoot htmlTag='svg' name='metadata should not be exposed'
-++++++svgRoot htmlTag='svg' name='mpath should not be exposed'
-++++++svgRoot htmlTag='svg' name='pattern should not be exposed'
-++++++svgRoot htmlTag='svg' name='radialGradient should not be exposed'
-++++++svgRoot htmlTag='svg' name='solidColor should not be exposed'
-++++++svgRoot htmlTag='svg' name='stop should not be exposed'
+++++++image htmlTag='svg' name='animate should not be exposed'
+++++++image htmlTag='svg' name='animateMotion should not be exposed'
+++++++image htmlTag='svg' name='animateTransform should not be exposed'
+++++++image htmlTag='svg' name='clipPath should not be exposed'
+++++++image htmlTag='svg' name='cursor should not be exposed'
+++++++image htmlTag='svg' name='defs should not be exposed'
+++++++++genericContainer ignored invisible htmlTag='solidcolor'
+++++++image htmlTag='svg' name='discard should not be exposed'
+++++++image htmlTag='svg' name='filter and filter effect elements should not be exposed'
+++++++++group ignored htmlTag='g'
+++++++++++group ignored htmlTag='g'
+++++++image htmlTag='svg' name='hatch should not be exposed'
+++++++image htmlTag='svg' name='hatchpath should not be exposed'
+++++++image htmlTag='svg' name='image which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='linearGradient should not be exposed'
+++++++image htmlTag='svg' name='marker should not be exposed'
+++++++image htmlTag='svg' name='mask should not be exposed'
+++++++image htmlTag='svg' name='metadata should not be exposed'
+++++++image htmlTag='svg' name='mpath should not be exposed'
+++++++image htmlTag='svg' name='pattern should not be exposed'
+++++++image htmlTag='svg' name='radialGradient should not be exposed'
+++++++image htmlTag='svg' name='solidColor should not be exposed'
+++++++image htmlTag='svg' name='stop should not be exposed'
 ++++++svgRoot htmlTag='svg' name='style and set should not be exposed'
 ++++++++graphicsSymbol htmlTag='rect'
 ++++++svgRoot htmlTag='svg' name='switch should not be exposed'
 ++++++++genericContainer htmlTag='text'
 ++++++++++staticText name='Hey!'
 ++++++++++++inlineTextBox name='Hey!'
-++++++svgRoot htmlTag='svg' name='view should not be exposed'
-++++++++genericContainer invisible htmlTag='view'
-++++++++genericContainer invisible htmlTag='view'
-++++++svgRoot htmlTag='svg' name='desc should not be exposed as accessible object'
-++++++svgRoot htmlTag='svg' name='title should not be exposed as accessible object'
-++++++svgRoot htmlTag='svg' name='circle which lacks criteria for inclusion should not be exposed'
-++++++svgRoot htmlTag='svg' name='ellipse which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='view should not be exposed'
+++++++++genericContainer ignored invisible htmlTag='view'
+++++++++genericContainer ignored invisible htmlTag='view'
+++++++image htmlTag='svg' name='desc should not be exposed as accessible object'
+++++++image htmlTag='svg' name='title should not be exposed as accessible object'
+++++++image htmlTag='svg' name='circle which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='ellipse which lacks criteria for inclusion should not be exposed'
 ++++++svgRoot htmlTag='svg' name='foreignObject which lacks criteria for inclusion should not be exposed'
-++++++++group htmlTag='foreignobject'
+++++++++group ignored htmlTag='foreignobject'
 ++++++++++paragraph htmlTag='p'
 ++++++++++++staticText name='Hello world'
 ++++++++++++++inlineTextBox name='Hello '
 ++++++++++++++inlineTextBox name='world'
-++++++svgRoot htmlTag='svg' name='group which lacks criteria for inclusion should not be exposed'
-++++++++group htmlTag='g'
-++++++svgRoot htmlTag='svg' name='line which lacks criteria for inclusion should not be exposed'
-++++++svgRoot htmlTag='svg' name='path which lacks criteria for inclusion should not be exposed'
-++++++svgRoot htmlTag='svg' name='polygon which lacks criteria for inclusion should not be exposed'
-++++++svgRoot htmlTag='svg' name='polyline which lacks criteria for inclusion should not be exposed'
-++++++svgRoot htmlTag='svg' name='rect which lacks criteria for inclusion should not be exposed'
-++++++svgRoot htmlTag='svg' name='symbol which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='group which lacks criteria for inclusion should not be exposed'
+++++++++group ignored htmlTag='g'
+++++++image htmlTag='svg' name='line which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='path which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='polygon which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='polyline which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='rect which lacks criteria for inclusion should not be exposed'
+++++++image htmlTag='svg' name='symbol which lacks criteria for inclusion should not be exposed'
 ++++++svgRoot htmlTag='svg' name='textPath which lacks criteria for inclusion should not be exposed'
 ++++++++genericContainer htmlTag='text'
 ++++++++++staticText name='Hello'
diff --git a/content/test/data/accessibility/html/svg-elements-not-mapped.html b/content/test/data/accessibility/html/svg-elements-not-mapped.html
index 6432e49..38ccc21 100644
--- a/content/test/data/accessibility/html/svg-elements-not-mapped.html
+++ b/content/test/data/accessibility/html/svg-elements-not-mapped.html
@@ -2,9 +2,6 @@
 @AURALINUX-ALLOW:id:*
 @AURALINUX-ALLOW:tag:*
 @BLINK-ALLOW:htmlTag*
-
-For compatibility with cros which always includes <g>
-@BLINK-DENY:ignored
 -->
 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
 <html>
diff --git a/content/test/data/accessibility/html/svg-g-for-cros-expected-blink.txt b/content/test/data/accessibility/html/svg-g-expected-blink-cros.txt
similarity index 100%
rename from content/test/data/accessibility/html/svg-g-for-cros-expected-blink.txt
rename to content/test/data/accessibility/html/svg-g-expected-blink-cros.txt
diff --git a/content/test/data/accessibility/html/svg-g-for-cros.html b/content/test/data/accessibility/html/svg-g-for-cros.html
deleted file mode 100644
index a4f8f25..0000000
--- a/content/test/data/accessibility/html/svg-g-for-cros.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!--
-@BLINK-ALLOW:htmlTag='g'
--->
-<!DOCTYPE html>
-<html>
-<body>
-
-<svg aria-label="svg">
-  <g>
-    <text x="10" y="10" fill="red">Group 1 Text 1</text>
-    <text x="10" y="100" fill="red">Group 1 Text 2</text>
-  </g>
-  <g>
-    <text x="200" y="10" fill="red">Group 2 Text 1</text>
-  </g>
-</svg>
-
-</body>
-</html>
diff --git a/content/test/data/accessibility/readme.md b/content/test/data/accessibility/readme.md
index 8430215d..a9dd0af 100644
--- a/content/test/data/accessibility/readme.md
+++ b/content/test/data/accessibility/readme.md
@@ -82,6 +82,8 @@
 * `auralinux-trusty` -- expected Linux ATK output (Version Specific Expected File)
 * `auralinux-xenial` -- expected Linux ATK output (Version Specific Expected File)
 * `blink` -- representation of internal accessibility tree
+* `blink-cros` -- representation of internal accessibility tree
+  (Version Specific Expected File for Chrome OS and Lacros)
 * `mac` -- expected Mac NSAccessibility output
 * `win` -- expected Win IAccessible/IAccessible2 output
 * `uia-win` -- expected Win UIA output
@@ -132,6 +134,13 @@
 At the present time there is no version-specific support for Bionic Beaver,
 which is the current version run on "linux-rel".
 
+The need for a version-specific expectations file on Chrome OS / Lacros is
+extremely rare. However, there can be occasional differences in the internal
+accessibility tree. For instance, the SVG `g` element is always included
+in order to support select-to-speak functionality. If `foo.html` has a
+`foo-expected-blink.txt` file which works on all platforms except the Chrome OS
+and Lacros bots, create `foo-expected-blink-cros.txt`.
+
 ## Directives
 
 Directives allow you to control test flow and test output. The directives are
diff --git a/google_apis/gaia/gaia_constants.cc b/google_apis/gaia/gaia_constants.cc
index 8d77b6d1..1713f4b 100644
--- a/google_apis/gaia/gaia_constants.cc
+++ b/google_apis/gaia/gaia_constants.cc
@@ -63,10 +63,18 @@
 const char kGoogleUserInfoProfile[] =
     "https://www.googleapis.com/auth/userinfo.profile";
 
+// OAuth2 scope for access to the parent approval widget.
+const char kParentApprovalOAuth2Scope[] =
+    "https://www.googleapis.com/auth/kids.parentapproval";
+
 // OAuth2 scope for access to the people API (read-only).
 const char kPeopleApiReadOnlyOAuth2Scope[] =
     "https://www.googleapis.com/auth/peopleapi.readonly";
 
+// OAuth2 scope for access to the programmatic challenge API (read-only).
+const char kProgrammaticChallengeOAuth2Scope[] =
+    "https://www.googleapis.com/auth/accounts.programmaticchallenge";
+
 // OAuth2 scope for access to the Reauth flow.
 const char kAccountsReauthOAuth2Scope[] =
     "https://www.googleapis.com/auth/accounts.reauth";
diff --git a/google_apis/gaia/gaia_constants.h b/google_apis/gaia/gaia_constants.h
index 491c23a..e6db05e 100644
--- a/google_apis/gaia/gaia_constants.h
+++ b/google_apis/gaia/gaia_constants.h
@@ -34,7 +34,9 @@
 extern const char kGoogleTalkOAuth2Scope[];
 extern const char kGoogleUserInfoEmail[];
 extern const char kGoogleUserInfoProfile[];
+extern const char kParentApprovalOAuth2Scope[];
 extern const char kPeopleApiReadOnlyOAuth2Scope[];
+extern const char kProgrammaticChallengeOAuth2Scope[];
 extern const char kAccountsReauthOAuth2Scope[];
 extern const char kAuditRecordingOAuth2Scope[];
 extern const char kClearCutOAuth2Scope[];
diff --git a/gpu/command_buffer/service/shared_image_backing_gl_image.cc b/gpu/command_buffer/service/shared_image_backing_gl_image.cc
index 8f36b8a..1113d21 100644
--- a/gpu/command_buffer/service/shared_image_backing_gl_image.cc
+++ b/gpu/command_buffer/service/shared_image_backing_gl_image.cc
@@ -294,6 +294,40 @@
 ///////////////////////////////////////////////////////////////////////////////
 // SharedImageBackingGLImage
 
+// static
+std::unique_ptr<SharedImageBackingGLImage>
+SharedImageBackingGLImage::CreateFromGLTexture(
+    scoped_refptr<gl::GLImage> image,
+    const Mailbox& mailbox,
+    viz::ResourceFormat format,
+    const gfx::Size& size,
+    const gfx::ColorSpace& color_space,
+    GrSurfaceOrigin surface_origin,
+    SkAlphaType alpha_type,
+    uint32_t usage,
+    GLenum texture_target,
+    scoped_refptr<gles2::TexturePassthrough> wrapped_gl_texture) {
+  DCHECK(!!wrapped_gl_texture);
+
+  // We don't expect the backing to allocate a new
+  // texture but it does need to know the texture target so we supply that
+  // one param.
+  InitializeGLTextureParams params;
+  params.target = texture_target;
+  UnpackStateAttribs attribs;
+
+  auto shared_image = std::make_unique<SharedImageBackingGLImage>(
+      std::move(image), mailbox, format, size, color_space, surface_origin,
+      alpha_type, usage, params, attribs, true);
+
+  shared_image->passthrough_texture_ = std::move(wrapped_gl_texture);
+  shared_image->gl_texture_retained_for_legacy_mailbox_ = true;
+  shared_image->gl_texture_retain_count_ = 1;
+  shared_image->image_bind_or_copy_needed_ = false;
+
+  return shared_image;
+}
+
 SharedImageBackingGLImage::SharedImageBackingGLImage(
     scoped_refptr<gl::GLImage> image,
     const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image_backing_gl_image.h b/gpu/command_buffer/service/shared_image_backing_gl_image.h
index e3727be..b08451b 100644
--- a/gpu/command_buffer/service/shared_image_backing_gl_image.h
+++ b/gpu/command_buffer/service/shared_image_backing_gl_image.h
@@ -167,6 +167,21 @@
     : public SharedImageBacking,
       public SharedImageRepresentationGLTextureClient {
  public:
+  // Used when SharedImageBackingGLImage is serving as a temporary SharedImage
+  // wrapper to an already-allocated texture. The returned backing will not
+  // create any new textures.
+  static std::unique_ptr<SharedImageBackingGLImage> CreateFromGLTexture(
+      scoped_refptr<gl::GLImage> image,
+      const Mailbox& mailbox,
+      viz::ResourceFormat format,
+      const gfx::Size& size,
+      const gfx::ColorSpace& color_space,
+      GrSurfaceOrigin surface_origin,
+      SkAlphaType alpha_type,
+      uint32_t usage,
+      GLenum texture_target,
+      scoped_refptr<gles2::TexturePassthrough> wrapped_gl_texture);
+
   SharedImageBackingGLImage(
       scoped_refptr<gl::GLImage> image,
       const Mailbox& mailbox,
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index e84bbee..5420dafb 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -529,15 +529,13 @@
 
   # Normally set to false (see build/args/headless.gn), but we can optionally
   # use external v8 startup data too.
-  if ((is_win && is_component_build) || !is_win) {
-    if (v8_use_external_startup_data) {
-      public_deps += [ "//v8" ]
-      if (use_v8_context_snapshot) {
-        data += [ "$root_out_dir/$v8_context_snapshot_filename" ]
-        data_deps += [ "//tools/v8_context_snapshot" ]
-      } else {
-        data += [ "$root_out_dir/snapshot_blob.bin" ]
-      }
+  if ((!is_win || is_component_build) && v8_use_external_startup_data) {
+    public_deps += [ "//v8" ]
+    if (use_v8_context_snapshot) {
+      data += [ "$root_out_dir/$v8_context_snapshot_filename" ]
+      data_deps += [ "//tools/v8_context_snapshot" ]
+    } else {
+      data += [ "$root_out_dir/snapshot_blob.bin" ]
     }
   }
 
diff --git a/ios/chrome/app/strings/ios_chromium_strings.grd b/ios/chrome/app/strings/ios_chromium_strings.grd
index e91643a..2e95c878 100644
--- a/ios/chrome/app/strings/ios_chromium_strings.grd
+++ b/ios/chrome/app/strings/ios_chromium_strings.grd
@@ -235,12 +235,12 @@
       <message name="IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE" desc="Text displayed in the popover to inform the user that the browser is managed by Enterprise policies. (Force Sign In) [iOS only]">
           Your organization requires you to sign in to use Chromium.
       </message>
+      <message name="IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE_WITH_LEARN_MORE" desc="Text to inform the user about the forced sign-in policy with a Learn More link that redirects to a page that gives more details. (Force Sign In) [iOS only]">
+        Your organization requires you to sign in to use Chromium. <ph name="BEGIN_LINK">BEGIN_LINK</ph>Learn More<ph name="END_LINK">END_LINK</ph>
+      </message>
       <message name="IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SIGNOUT_DIALOG_TITLE" desc="The signout dialog title when forced sign-in is enabled. (Force Sign In) [iOS only]">
         Sign out of Chromium?
       </message>
-     <message name="IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SYNC_SETTINGS_SIGNOUT_FOOTER" desc="The informational footer below the sign-out button item in sync settings when the forced sign-in policy is enabled. (Force Sign In) [iOS only]">
-       Your organization requires you to sign in to use Chromium. <ph name="BEGIN_LINK">BEGIN_LINK</ph>Learn More<ph name="END_LINK">END_LINK</ph>.
-      </message>
       <message name="IDS_IOS_ENTERPRISE_SIGNED_OUT_SUBTEXT" desc="Text displayed in an alert when the user is signed out due to browser sign-in becoming disabled by policy. [iOS only]">
         Your administrator requires you to sign in with an organization account. Accounts that are restricted by your administrator are hidden.
       </message>
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE_WITH_LEARN_MORE.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE_WITH_LEARN_MORE.png.sha1
new file mode 100644
index 0000000..c5cb9b5
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE_WITH_LEARN_MORE.png.sha1
@@ -0,0 +1 @@
+ce4d8ddebb71283ce2f41e96c8bb8a7dc90a2d83
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SYNC_SETTINGS_SIGNOUT_FOOTER.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SYNC_SETTINGS_SIGNOUT_FOOTER.png.sha1
deleted file mode 100644
index b44d688..0000000
--- a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SYNC_SETTINGS_SIGNOUT_FOOTER.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-657f06214030efcbec1bbfe8c460f13a4ed00078
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings.grd b/ios/chrome/app/strings/ios_google_chrome_strings.grd
index c79342d..4fb59e6 100644
--- a/ios/chrome/app/strings/ios_google_chrome_strings.grd
+++ b/ios/chrome/app/strings/ios_google_chrome_strings.grd
@@ -235,12 +235,12 @@
       <message name="IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE" desc="Text displayed in the popover to inform the user that the browser is managed by Enterprise policies. (Force Sign In) [iOS only]">
           Your organization requires you to sign in to use Chrome.
       </message>
+      <message name="IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE_WITH_LEARN_MORE" desc="Text to inform the user about the forced sign-in policy with a Learn More link that redirects to a page that gives more details. (Force Sign In) [iOS only]">
+        Your organization requires you to sign in to use Chrome. <ph name="BEGIN_LINK">BEGIN_LINK</ph>Learn More<ph name="END_LINK">END_LINK</ph>
+      </message>
       <message name="IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SIGNOUT_DIALOG_TITLE" desc="The signout dialog title when forced sign-in is enabled. (Force Sign In) [iOS only]">
         Sign out of Chrome?
       </message>
-     <message name="IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SYNC_SETTINGS_SIGNOUT_FOOTER" desc="The informational footer below the sign-out button item in sync settings when the forced sign-in policy is enabled. (Force Sign In) [iOS only]">
-       Your organization requires you to sign in to use Chrome. <ph name="BEGIN_LINK">BEGIN_LINK</ph>Learn More<ph name="END_LINK">END_LINK</ph>.
-      </message>
       <message name="IDS_IOS_ENTERPRISE_SIGNED_OUT_SUBTEXT" desc="Text displayed in an alert when the user is signed out due to browser sign-in becoming disabled by policy. [iOS only]">
         Your administrator requires you to sign in with an organization account. Accounts that are restricted by your administrator are hidden.
       </message>
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE_WITH_LEARN_MORE.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE_WITH_LEARN_MORE.png.sha1
new file mode 100644
index 0000000..ac3fbc8
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE_WITH_LEARN_MORE.png.sha1
@@ -0,0 +1 @@
+b31a17f97e2f4430f5ea20ba6a4e3f0c76442b48
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SYNC_SETTINGS_SIGNOUT_FOOTER.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SYNC_SETTINGS_SIGNOUT_FOOTER.png.sha1
deleted file mode 100644
index 77ce7bb3..0000000
--- a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SYNC_SETTINGS_SIGNOUT_FOOTER.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-089477b1c8cfde04c8be0ec43b6f5039994e8433
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 37cf7a7..97e9d271 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -1766,9 +1766,6 @@
       <message name="IDS_IOS_READING_LIST_EDIT_BUTTON" desc="Label of the button to edit the reading list entries (delete, mark as read/unread) [Length: 20em]" meaning="Start editing the reading list entries. [Length: 20em]">
         Edit
       </message>
-      <message name="IDS_IOS_READING_LIST_EMPTY_MESSAGE" desc="Message to explain to the user how to add entries to the reading list" meaning="[Length: unlimited]">
-        Your reading list is available offline. To add a page to your reading list, tap  <ph name="SHARE_OPENING_ICON">SHARE_OPENING_ICON<ex>(menu icon)</ex></ph>  then <ph name="ADD_TO_READING_LIST_TEXT">ADD_TO_READING_LIST_TEXT<ex>Add to Reading List</ex></ph>.
-      </message>
       <message name="IDS_IOS_READING_LIST_JUST_NOW" desc="String indicating that an event (adding item, distillation) happened less than one minute ago. [Length: 25em]">
         Just now
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_READING_LIST_EMPTY_MESSAGE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_READING_LIST_EMPTY_MESSAGE.png.sha1
deleted file mode 100644
index 2f34ef5..0000000
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_READING_LIST_EMPTY_MESSAGE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-29f386f254b404325613ff749da1a707fa8d2ca0
\ No newline at end of file
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 243ff4b..1ea04f99 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -787,6 +787,13 @@
      flag_descriptions::kEnableDiscoverFeedShorterCacheName,
      flag_descriptions::kEnableDiscoverFeedShorterCacheDescription,
      flags_ui::kOsIos, FEATURE_VALUE_TYPE(kEnableDiscoverFeedShorterCache)},
+    {"shared-highlighting-v2", flag_descriptions::kIOSSharedHighlightingV2Name,
+     flag_descriptions::kIOSSharedHighlightingV2Description, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(shared_highlighting::kSharedHighlightingV2)},
+    {"shared-highlighting-amp",
+     flag_descriptions::kIOSSharedHighlightingAmpName,
+     flag_descriptions::kIOSSharedHighlightingAmpDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(shared_highlighting::kSharedHighlightingAmp)},
 };
 
 bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 825e980d..ca6536d 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -323,6 +323,15 @@
     "Changes the Shared Highlighting color of the text fragment"
     "away from the default yellow in iOS. Works with #scroll-to-text-ios flag.";
 
+const char kIOSSharedHighlightingAmpName[] = "Shared Highlighting on AMP pages";
+const char kIOSSharedHighlightingAmpDescription[] =
+    "Enables the Create Link option on AMP pages.";
+
+const char kIOSSharedHighlightingV2Name[] = "Text Fragments UI improvements";
+const char kIOSSharedHighlightingV2Description[] =
+    "Enables improvements to text fragments UI, including a menu for removing "
+    "or resharing a highlight.";
+
 const char kSharedHighlightingUseBlocklistIOSName[] =
     "Shared Highlighting blocklist";
 const char kSharedHighlightingUseBlocklistIOSDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 450fbeb5..cbf9867 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -280,6 +280,16 @@
 extern const char kIOSSharedHighlightingColorChangeName[];
 extern const char kIOSSharedHighlightingColorChangeDescription[];
 
+// Title and description for the flag to enable Shared Highlighting on AMP pages
+// in iOS.
+extern const char kIOSSharedHighlightingAmpName[];
+extern const char kIOSSharedHighlightingAmpDescription[];
+
+// Title and description for the flag to enable browser-layer improvements to
+// the text fragments UI.
+extern const char kIOSSharedHighlightingV2Name[];
+extern const char kIOSSharedHighlightingV2Description[];
+
 // Title and description for the flag to lock the bottom toolbar into place.
 extern const char kLockBottomToolbarName[];
 extern const char kLockBottomToolbarDescription[];
diff --git a/ios/chrome/browser/passwords/password_controller_unittest.mm b/ios/chrome/browser/passwords/password_controller_unittest.mm
index 2de0a2b..22b42875 100644
--- a/ios/chrome/browser/passwords/password_controller_unittest.mm
+++ b/ios/chrome/browser/passwords/password_controller_unittest.mm
@@ -2227,12 +2227,10 @@
                           "password", 3, "pw", nullptr, nullptr, false,
                           &form_data);
   __block BOOL block_was_called = NO;
-  __block BOOL return_value = NO;
   [passwordController_.sharedPasswordController
        fillPasswordForm:form_data
       completionHandler:^(BOOL success) {
         block_was_called = YES;
-        return_value = success;
       }];
   EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool() {
     return block_was_called;
diff --git a/ios/chrome/browser/policy/reporting/browser_report_generator_ios.h b/ios/chrome/browser/policy/reporting/browser_report_generator_ios.h
index d740fad..24df871 100644
--- a/ios/chrome/browser/policy/reporting/browser_report_generator_ios.h
+++ b/ios/chrome/browser/policy/reporting/browser_report_generator_ios.h
@@ -34,15 +34,14 @@
   // BrowserReportGenerator::Delegate implementation.
   std::string GetExecutablePath() override;
   version_info::Channel GetChannel() override;
-  std::vector<BrowserReportGenerator::ReportedProfileData> GetReportedProfiles(
-      ReportType report_type) override;
+  std::vector<BrowserReportGenerator::ReportedProfileData> GetReportedProfiles()
+      override;
   bool IsExtendedStableChannel() override;
   void GenerateBuildStateInfo(
       enterprise_management::BrowserReport* report) override;
   void GeneratePluginsIfNeeded(
       ReportCallback callback,
       std::unique_ptr<enterprise_management::BrowserReport> report) override;
-  void OnProfileInfoGenerated(ReportType report_type) override;
 };
 
 }  // namespace enterprise_reporting
diff --git a/ios/chrome/browser/policy/reporting/browser_report_generator_ios.mm b/ios/chrome/browser/policy/reporting/browser_report_generator_ios.mm
index 1c3e82b5..4cc9a2c1 100644
--- a/ios/chrome/browser/policy/reporting/browser_report_generator_ios.mm
+++ b/ios/chrome/browser/policy/reporting/browser_report_generator_ios.mm
@@ -35,7 +35,7 @@
 }
 
 std::vector<BrowserReportGenerator::ReportedProfileData>
-BrowserReportGeneratorIOS::GetReportedProfiles(ReportType report_type) {
+BrowserReportGeneratorIOS::GetReportedProfiles() {
   std::vector<BrowserReportGenerator::ReportedProfileData> reportedProfileData;
   for (const auto* entry : GetApplicationContext()
                                ->GetChromeBrowserStateManager()
@@ -69,8 +69,4 @@
   std::move(callback).Run(std::move(report));
 }
 
-void BrowserReportGeneratorIOS::OnProfileInfoGenerated(ReportType report_type) {
-  // Not used on iOS because there is no throttling profiles.
-}
-
 }  // namespace enterprise_reporting
diff --git a/ios/chrome/browser/policy/reporting/browser_report_generator_ios_unittest.mm b/ios/chrome/browser/policy/reporting/browser_report_generator_ios_unittest.mm
index d9f384c7..5b294be 100644
--- a/ios/chrome/browser/policy/reporting/browser_report_generator_ios_unittest.mm
+++ b/ios/chrome/browser/policy/reporting/browser_report_generator_ios_unittest.mm
@@ -48,7 +48,6 @@
   void GenerateAndVerify() {
     base::RunLoop run_loop;
     generator_.Generate(
-        ReportType::kFull,
         base::BindLambdaForTesting(
             [&run_loop](std::unique_ptr<em::BrowserReport> report) {
               EXPECT_TRUE(report.get());
diff --git a/ios/chrome/browser/policy/reporting/profile_report_generator_ios_unittest.mm b/ios/chrome/browser/policy/reporting/profile_report_generator_ios_unittest.mm
index 9d70bc2..94f8522 100644
--- a/ios/chrome/browser/policy/reporting/profile_report_generator_ios_unittest.mm
+++ b/ios/chrome/browser/policy/reporting/profile_report_generator_ios_unittest.mm
@@ -106,8 +106,7 @@
   std::unique_ptr<em::ChromeUserProfileInfo> GenerateReport() {
     std::unique_ptr<em::ChromeUserProfileInfo> report =
         generator_.MaybeGenerate(kProfilePath,
-                                 kProfilePath.BaseName().AsUTF8Unsafe(),
-                                 ReportType::kFull);
+                                 kProfilePath.BaseName().AsUTF8Unsafe());
 
     if (!report)
       return nullptr;
diff --git a/ios/chrome/browser/safe_browsing/chrome_password_protection_service.h b/ios/chrome/browser/safe_browsing/chrome_password_protection_service.h
index 5671780f..2d93fc6 100644
--- a/ios/chrome/browser/safe_browsing/chrome_password_protection_service.h
+++ b/ios/chrome/browser/safe_browsing/chrome_password_protection_service.h
@@ -134,6 +134,9 @@
 
   AccountInfo GetAccountInfo() const override;
 
+  safe_browsing::ChromeUserPopulation::UserPopulation GetUserPopulationPref()
+      const override;
+
   AccountInfo GetAccountInfoForUsername(
       const std::string& username) const override;
 
diff --git a/ios/chrome/browser/safe_browsing/chrome_password_protection_service.mm b/ios/chrome/browser/safe_browsing/chrome_password_protection_service.mm
index 644fb89..05489002 100644
--- a/ios/chrome/browser/safe_browsing/chrome_password_protection_service.mm
+++ b/ios/chrome/browser/safe_browsing/chrome_password_protection_service.mm
@@ -378,6 +378,11 @@
       identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSync));
 }
 
+safe_browsing::ChromeUserPopulation::UserPopulation
+ChromePasswordProtectionService::GetUserPopulationPref() const {
+  return ::GetUserPopulationPref(browser_state_);
+}
+
 AccountInfo ChromePasswordProtectionService::GetAccountInfoForUsername(
     const std::string& username) const {
   auto* identity_manager =
diff --git a/ios/chrome/browser/safe_browsing/user_population.h b/ios/chrome/browser/safe_browsing/user_population.h
index 5961b861..8c566a93 100644
--- a/ios/chrome/browser/safe_browsing/user_population.h
+++ b/ios/chrome/browser/safe_browsing/user_population.h
@@ -9,6 +9,10 @@
 
 class ChromeBrowserState;
 
+// Returns the UserPopulation enum for the given |browser_state|.
+safe_browsing::ChromeUserPopulation::UserPopulation GetUserPopulationPref(
+    ChromeBrowserState* browser_state);
+
 // Creates a ChromeUserPopulation proto for the given |browser_state|.
 safe_browsing::ChromeUserPopulation GetUserPopulation(
     ChromeBrowserState* browser_state);
diff --git a/ios/chrome/browser/safe_browsing/user_population.mm b/ios/chrome/browser/safe_browsing/user_population.mm
index 547cd9ca..81d64a6 100644
--- a/ios/chrome/browser/safe_browsing/user_population.mm
+++ b/ios/chrome/browser/safe_browsing/user_population.mm
@@ -21,18 +21,27 @@
 
 using safe_browsing::ChromeUserPopulation;
 
-ChromeUserPopulation GetUserPopulation(ChromeBrowserState* browser_state) {
-  ChromeUserPopulation population;
+ChromeUserPopulation::UserPopulation GetUserPopulationPref(
+    ChromeBrowserState* browser_state) {
   if (browser_state->GetPrefs()) {
     const PrefService& prefs = *browser_state->GetPrefs();
     if (safe_browsing::IsEnhancedProtectionEnabled(prefs)) {
-      population.set_user_population(ChromeUserPopulation::ENHANCED_PROTECTION);
+      return ChromeUserPopulation::ENHANCED_PROTECTION;
     } else if (safe_browsing::IsExtendedReportingEnabled(prefs)) {
-      population.set_user_population(ChromeUserPopulation::EXTENDED_REPORTING);
+      return ChromeUserPopulation::EXTENDED_REPORTING;
     } else if (safe_browsing::IsSafeBrowsingEnabled(prefs)) {
-      population.set_user_population(ChromeUserPopulation::SAFE_BROWSING);
+      return ChromeUserPopulation::SAFE_BROWSING;
     }
+  }
 
+  return ChromeUserPopulation::UNKNOWN_USER_POPULATION;
+}
+
+ChromeUserPopulation GetUserPopulation(ChromeBrowserState* browser_state) {
+  ChromeUserPopulation population;
+  population.set_user_population(GetUserPopulationPref(browser_state));
+  if (browser_state->GetPrefs()) {
+    const PrefService& prefs = *browser_state->GetPrefs();
     population.set_is_mbb_enabled(prefs.GetBoolean(
         unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
   }
diff --git a/ios/chrome/browser/signin/authentication_service.mm b/ios/chrome/browser/signin/authentication_service.mm
index 0f64eaf..aabdb49 100644
--- a/ios/chrome/browser/signin/authentication_service.mm
+++ b/ios/chrome/browser/signin/authentication_service.mm
@@ -331,11 +331,12 @@
   CHECK(!account_info.IsEmpty());
   CHECK(!account_info.hosted_domain.empty());
 
-  const bool success =
+  const signin::PrimaryAccountMutator::PrimaryAccountError error =
       identity_manager_->GetPrimaryAccountMutator()->SetPrimaryAccount(
           account_id, signin::ConsentLevel::kSync);
 
-  CHECK(success);
+  CHECK_EQ(signin::PrimaryAccountMutator::PrimaryAccountError::kNoError, error)
+      << "SetPrimaryAccount error: " << static_cast<int>(error);
   CHECK_EQ(account_id,
            identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSync));
 
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index 481e977..9bcf935 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -3430,6 +3430,12 @@
   completionHandler(configuration);
 }
 
+- (void)webState:(web::WebState*)webState
+    contextMenuWillCommitWithAnimator:
+        (id<UIContextMenuInteractionCommitAnimating>)animator {
+  [self.contextMenuProvider commitPreview];
+}
+
 - (id<CRWResponderInputView>)webStateInputViewProvider:
     (web::WebState*)webState {
   return self.inputViewProvider;
diff --git a/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.h b/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.h
index ff9462eb..d8ae2dc 100644
--- a/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.h
+++ b/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.h
@@ -28,6 +28,9 @@
                                  params:(web::ContextMenuParams)params
                      baseViewController:(UIViewController*)baseViewController;
 
+// Called when the user commits the preview (taps on it).
+- (void)commitPreview;
+
 // DEPRECATED.
 // Displays a context menu using an action sheet on |baseViewController|.
 // |params| is copied in order to be used in blocks.
diff --git a/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.mm b/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.mm
index 0fae521..eb63d97 100644
--- a/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.mm
+++ b/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.mm
@@ -134,6 +134,7 @@
     contextMenuConfigurationForWebState:(web::WebState*)webState
                                  params:(web::ContextMenuParams)params
                      baseViewController:(UIViewController*)baseViewController {
+  self.linkPreview = nil;
   // Prevent context menu from displaying for a tab which is no longer the
   // current one.
   if (webState != self.currentWebState) {
@@ -417,6 +418,10 @@
                                                actionProvider:actionProvider];
 }
 
+- (void)commitPreview {
+  [self.linkPreview handlePreviewAction];
+}
+
 - (void)showLegacyContextMenuForWebState:(web::WebState*)webState
                                   params:(web::ContextMenuParams)params
                       baseViewController:(UIViewController*)baseViewController {
diff --git a/ios/chrome/browser/ui/passwords/OWNERS b/ios/chrome/browser/ui/passwords/OWNERS
index 93ecef3..b6c914d 100644
--- a/ios/chrome/browser/ui/passwords/OWNERS
+++ b/ios/chrome/browser/ui/passwords/OWNERS
@@ -1 +1,2 @@
 javierrobles@chromium.org
+vsemeniuk@google.com
diff --git a/ios/chrome/browser/ui/reading_list/BUILD.gn b/ios/chrome/browser/ui/reading_list/BUILD.gn
index e31a150..58097d21 100644
--- a/ios/chrome/browser/ui/reading_list/BUILD.gn
+++ b/ios/chrome/browser/ui/reading_list/BUILD.gn
@@ -150,8 +150,6 @@
 source_set("reading_list_ui") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
-    "empty_reading_list_message_util.h",
-    "empty_reading_list_message_util.mm",
     "number_badge_view.h",
     "number_badge_view.mm",
     "reading_list_data_sink.h",
@@ -176,7 +174,6 @@
     ":reading_list_constants",
     "resources:reading_list_empty",
     "resources:reading_list_empty_state",
-    "resources:reading_list_tools_icon",
     "//base",
     "//base:i18n",
     "//components/prefs",
diff --git a/ios/chrome/browser/ui/reading_list/empty_reading_list_message_util.h b/ios/chrome/browser/ui/reading_list/empty_reading_list_message_util.h
deleted file mode 100644
index 0838b702..0000000
--- a/ios/chrome/browser/ui/reading_list/empty_reading_list_message_util.h
+++ /dev/null
@@ -1,18 +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.
-
-#ifndef IOS_CHROME_BROWSER_UI_READING_LIST_EMPTY_READING_LIST_MESSAGE_UTIL_H_
-#define IOS_CHROME_BROWSER_UI_READING_LIST_EMPTY_READING_LIST_MESSAGE_UTIL_H_
-
-#import <UIKit/UIKit.h>
-
-// Returns the attributed message to use for the empty Reading List background
-// view.
-NSAttributedString* GetReadingListEmptyMessage();
-
-// Returns the accessibility label to use for the label displaying the text
-// returned by GetReadingListEmptyMessage().
-NSString* GetReadingListEmptyMessageA11yLabel();
-
-#endif  // IOS_CHROME_BROWSER_UI_READING_LIST_EMPTY_READING_LIST_MESSAGE_UTIL_H_
diff --git a/ios/chrome/browser/ui/reading_list/empty_reading_list_message_util.mm b/ios/chrome/browser/ui/reading_list/empty_reading_list_message_util.mm
deleted file mode 100644
index 62731e10..0000000
--- a/ios/chrome/browser/ui/reading_list/empty_reading_list_message_util.mm
+++ /dev/null
@@ -1,157 +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.
-
-#import "ios/chrome/browser/ui/reading_list/empty_reading_list_message_util.h"
-
-#include "base/check.h"
-#include "ios/chrome/browser/system_flags.h"
-#include "ios/chrome/browser/ui/util/rtl_geometry.h"
-#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
-#import "ios/chrome/common/ui/colors/semantic_color_names.h"
-#include "ios/chrome/grit/ios_strings.h"
-#include "ui/base/l10n/l10n_util_mac.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-// Images name.
-NSString* const kToolsIcon = @"reading_list_tools_icon";
-
-// Tag in string.
-NSString* const kOpenShareMarker = @"SHARE_OPENING_ICON";
-NSString* const kAddToReadingListTextMarker = @"ADD_TO_READING_LIST_TEXT";
-
-// Background view constants.
-const CGFloat kLineSpacing = 4;
-
-// Returns the font to use for the message text.
-UIFont* GetMessageFont() {
-  return [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
-}
-
-// Returns the attributes to use for the message text.
-NSMutableDictionary* GetMessageAttributes() {
-  NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
-  UIFont* font = GetMessageFont();
-  attributes[NSFontAttributeName] = font;
-  attributes[NSForegroundColorAttributeName] =
-      [UIColor colorNamed:kTextSecondaryColor];
-  NSMutableParagraphStyle* paragraph_style =
-      [[NSMutableParagraphStyle alloc] init];
-  paragraph_style.lineBreakMode = NSLineBreakByWordWrapping;
-  paragraph_style.alignment = NSTextAlignmentCenter;
-  // If the line wrapping occurs that one of the icons is the first character on
-  // a new line, the default line spacing will result in uneven line heights.
-  // Manually setting the line spacing here prevents that from occurring.
-  paragraph_style.lineSpacing = kLineSpacing;
-  attributes[NSParagraphStyleAttributeName] = paragraph_style;
-  return attributes;
-}
-
-// Returns the attributes to use for the instructions on how to reach the "Read
-// Later" option.
-NSMutableDictionary* GetInstructionAttributes() {
-  NSMutableDictionary* attributes = GetMessageAttributes();
-  attributes[NSFontAttributeName] =
-      [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
-  return attributes;
-}
-
-// Returns the "Add to Reading List" text to appear at the end of the string,
-// with correct styling.
-NSAttributedString* GetReadLaterString() {
-  NSString* add_to_reading_list_text =
-      l10n_util::GetNSString(IDS_IOS_SHARE_MENU_READING_LIST_ACTION);
-  return [[NSAttributedString alloc] initWithString:add_to_reading_list_text
-                                         attributes:GetInstructionAttributes()];
-}
-
-// Appends the tools icon to |text|.  Spacer text that is added by this function
-// is formatted with |attributes|.
-void AppendToolsIcon(NSMutableAttributedString* text,
-                     NSDictionary* attributes) {
-
-  // The icon bounds must be offset to be vertically centered with the message
-  // text.
-  UIImage* icon = [UIImage imageNamed:kToolsIcon];
-  CGRect icon_bounds = CGRectZero;
-  icon_bounds.size = icon.size;
-  icon_bounds.origin.y = (GetMessageFont().xHeight - icon.size.height) / 2.0;
-
-  // Attach the icon image.
-  NSTextAttachment* attachment = [[NSTextAttachment alloc] init];
-  attachment.image =
-      [icon imageWithTintColor:attributes[NSForegroundColorAttributeName]];
-  attachment.bounds = icon_bounds;
-  NSAttributedString* attachment_string =
-      [NSAttributedString attributedStringWithAttachment:attachment];
-  [text appendAttributedString:attachment_string];
-}
-
-// Returns the string to use to describe the buttons needed to access the "Read
-// Later" option.
-NSAttributedString* GetInstructionIconString() {
-  NSDictionary* attributes = GetInstructionAttributes();
-  NSMutableAttributedString* icon_string =
-      [[NSMutableAttributedString alloc] init];
-  AppendToolsIcon(icon_string, attributes);
-  return icon_string;
-}
-
-// Returns the icon string in an accessible format.
-NSAttributedString* GetAccessibleInstructionIconString() {
-  NSDictionary* attributes = GetInstructionAttributes();
-  NSMutableAttributedString* icon_string =
-      [[NSMutableAttributedString alloc] initWithString:@":"
-                                             attributes:attributes];
-  NSString* tools_text = [NSString
-      stringWithFormat:@"%@, ",
-                       l10n_util::GetNSString(IDS_IOS_TOOLBAR_SETTINGS)];
-  [icon_string appendAttributedString:[[NSAttributedString alloc]
-                                          initWithString:tools_text
-                                              attributes:attributes]];
-  return icon_string;
-}
-
-// Returns the attributed text to use for the message.  If |use_icons| is true,
-// icon images are added to the text; otherwise accessible text versions of the
-// instructions are used.
-NSAttributedString* GetReadingListEmptyMessage(bool use_icons) {
-  NSString* raw_text =
-      l10n_util::GetNSString(IDS_IOS_READING_LIST_EMPTY_MESSAGE);
-  NSMutableAttributedString* message =
-      [[NSMutableAttributedString alloc] initWithString:raw_text
-                                             attributes:GetMessageAttributes()];
-  NSAttributedString* instruction_icon_string =
-      use_icons ? GetInstructionIconString()
-                : GetAccessibleInstructionIconString();
-  NSAttributedString* read_later_string = GetReadLaterString();
-  // Two replacements must be made in the text:
-  // - kOpenShareMarker should be replaced with |instruction_icon_string|
-  // - kAddToReadingListTextMarker should be replaced with
-  //   |add_to_reading_list_text|
-  NSRange icon_range = [message.string rangeOfString:kOpenShareMarker];
-  DCHECK(icon_range.location != NSNotFound);
-  [message replaceCharactersInRange:icon_range
-               withAttributedString:instruction_icon_string];
-
-  NSRange read_later_range =
-      [message.string rangeOfString:kAddToReadingListTextMarker];
-  DCHECK(read_later_range.location != NSNotFound);
-  [message replaceCharactersInRange:read_later_range
-               withAttributedString:read_later_string];
-  return message;
-}
-}  // namespace
-
-NSAttributedString* GetReadingListEmptyMessage() {
-  return GetReadingListEmptyMessage(true);
-}
-
-NSString* GetReadingListEmptyMessageA11yLabel() {
-  return GetReadingListEmptyMessage(false).string;
-}
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
index 80061d96..f0d2d12 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
@@ -23,7 +23,6 @@
 #import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
 #import "ios/chrome/browser/ui/list_model/list_item+Controller.h"
 #import "ios/chrome/browser/ui/list_model/list_item.h"
-#import "ios/chrome/browser/ui/reading_list/empty_reading_list_message_util.h"
 #import "ios/chrome/browser/ui/reading_list/reading_list_constants.h"
 #import "ios/chrome/browser/ui/reading_list/reading_list_data_sink.h"
 #import "ios/chrome/browser/ui/reading_list/reading_list_data_source.h"
diff --git a/ios/chrome/browser/ui/reading_list/resources/BUILD.gn b/ios/chrome/browser/ui/reading_list/resources/BUILD.gn
index 931edbb..d7f959f 100644
--- a/ios/chrome/browser/ui/reading_list/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/reading_list/resources/BUILD.gn
@@ -30,12 +30,3 @@
     "reading_list_empty_state.imageset/reading_list_empty_state@3x.png",
   ]
 }
-
-imageset("reading_list_tools_icon") {
-  sources = [
-    "reading_list_tools_icon.imageset/Contents.json",
-    "reading_list_tools_icon.imageset/reading_list_tools_icon.png",
-    "reading_list_tools_icon.imageset/reading_list_tools_icon@2x.png",
-    "reading_list_tools_icon.imageset/reading_list_tools_icon@3x.png",
-  ]
-}
diff --git a/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/Contents.json b/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/Contents.json
deleted file mode 100644
index a6af62e..0000000
--- a/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/Contents.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-    "images": [
-        {
-            "idiom": "universal",
-            "scale": "1x",
-            "filename": "reading_list_tools_icon.png"
-        },
-        {
-            "idiom": "universal",
-            "scale": "2x",
-            "filename": "reading_list_tools_icon@2x.png"
-        },
-        {
-            "idiom": "universal",
-            "scale": "3x",
-            "filename": "reading_list_tools_icon@3x.png"
-        }
-    ],
-    "info": {
-        "version": 1,
-        "author": "xcode"
-    }
-}
diff --git a/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/reading_list_tools_icon.png b/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/reading_list_tools_icon.png
deleted file mode 100644
index f4f4b8d..0000000
--- a/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/reading_list_tools_icon.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/reading_list_tools_icon@2x.png b/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/reading_list_tools_icon@2x.png
deleted file mode 100644
index 701b35a..0000000
--- a/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/reading_list_tools_icon@2x.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/reading_list_tools_icon@3x.png b/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/reading_list_tools_icon@3x.png
deleted file mode 100644
index 981187c6..0000000
--- a/ios/chrome/browser/ui/reading_list/resources/reading_list_tools_icon.imageset/reading_list_tools_icon@3x.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.mm b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.mm
index 9e2731f..aefe720 100644
--- a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.mm
@@ -359,7 +359,7 @@
         [[TableViewLinkHeaderFooterItem alloc]
             initWithType:SignOutItemFooterType];
     footerItem.text = l10n_util::GetNSString(
-        IDS_IOS_ENTERPRISE_FORCED_SIGNIN_SYNC_SETTINGS_SIGNOUT_FOOTER);
+        IDS_IOS_ENTERPRISE_FORCED_SIGNIN_MESSAGE_WITH_LEARN_MORE);
     footerItem.urls = std::vector<GURL>{GURL("chrome://management/")};
     [model setFooter:footerItem
         forSectionWithIdentifier:SignOutSectionIdentifier];
diff --git a/ios/chrome/browser/ui/sharing/sharing_coordinator_unittest.mm b/ios/chrome/browser/ui/sharing/sharing_coordinator_unittest.mm
index 74970a9..98613c57 100644
--- a/ios/chrome/browser/ui/sharing/sharing_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/sharing/sharing_coordinator_unittest.mm
@@ -134,15 +134,11 @@
                           params:params
                       originView:fake_origin_view_];
 
-  // Pointer to allow us to grab the VC instance in our validation callback.
-  __block UIActivityViewController* activityViewController;
-
   id vc_partial_mock = OCMPartialMock(base_view_controller_);
   [[vc_partial_mock expect]
       presentViewController:[OCMArg checkWithBlock:^BOOL(
                                         UIViewController* viewController) {
         if ([viewController isKindOfClass:[UIActivityViewController class]]) {
-          activityViewController = (UIActivityViewController*)viewController;
           return YES;
         }
         return NO;
@@ -208,15 +204,11 @@
                           params:params
                       originView:fake_origin_view_];
 
-  // Pointer to allow us to grab the VC instance in our validation callback.
-  __block UIActivityViewController* activityViewController;
-
   id vc_partial_mock = OCMPartialMock(base_view_controller_);
   [[vc_partial_mock expect]
       presentViewController:[OCMArg checkWithBlock:^BOOL(
                                         UIViewController* viewController) {
         if ([viewController isKindOfClass:[UIActivityViewController class]]) {
-          activityViewController = (UIActivityViewController*)viewController;
           return YES;
         }
         return NO;
diff --git a/ios/chrome/browser/widget_kit/OWNERS b/ios/chrome/browser/widget_kit/OWNERS
index 93ecef3..c2536df3 100644
--- a/ios/chrome/browser/widget_kit/OWNERS
+++ b/ios/chrome/browser/widget_kit/OWNERS
@@ -1 +1,2 @@
 javierrobles@chromium.org
+rkgibson@google.com
diff --git a/ios/net/crn_http_protocol_handler.mm b/ios/net/crn_http_protocol_handler.mm
index 78abb76..6a81e59 100644
--- a/ios/net/crn_http_protocol_handler.mm
+++ b/ios/net/crn_http_protocol_handler.mm
@@ -478,9 +478,7 @@
   DCHECK_EQ(net_request_, request);
 
   // Read data from the socket until no bytes left to read.
-  uint64_t total_bytes_read = 0;
   while (bytes_read > 0) {
-    total_bytes_read += bytes_read;
     // The NSData will take the ownership of |read_buffer_|.
     NSData* data =
         [NSData dataWithBytesNoCopy:read_buffer_.release() length:bytes_read];
diff --git a/ios/web/public/web_state_delegate.h b/ios/web/public/web_state_delegate.h
index 10850ff..b2a97a0 100644
--- a/ios/web/public/web_state_delegate.h
+++ b/ios/web/public/web_state_delegate.h
@@ -80,26 +80,17 @@
   // nil.
   virtual UIView* GetWebViewContainer(WebState* source);
 
-  // Called when iOS13+ context menu is triggered and now it is required to
+  // Called when the context menu is triggered and now it is required to
   // provide a UIContextMenuConfiguration to |completion_handler| to generate
   // the context menu.
   virtual void ContextMenuConfiguration(
       WebState* source,
       const ContextMenuParams& params,
-      void (^completion_handler)(UIContextMenuConfiguration*))
-      API_AVAILABLE(ios(13.0));
-  // Called when iOS13+ context menu is ready to be showed.
-  virtual void ContextMenuDidEnd(WebState* source, const GURL& link_url)
-      API_AVAILABLE(ios(13.0));
-  // Called when iOS13+ context menu will commit with animator.
+      void (^completion_handler)(UIContextMenuConfiguration*));
+  // Called when the context menu will commit with animator.
   virtual void ContextMenuWillCommitWithAnimator(
       WebState* source,
-      const GURL& link_url,
-      id<UIContextMenuInteractionCommitAnimating> animator)
-      API_AVAILABLE(ios(13.0));
-  // Called when iOS13+ context menu will present.
-  virtual void ContextMenuWillPresent(WebState* source, const GURL& link_url)
-      API_AVAILABLE(ios(13.0));
+      id<UIContextMenuInteractionCommitAnimating> animator);
 
   // UIResponder Form Input APIs, consult Apple's UIResponder documentation for
   // more info.
diff --git a/ios/web/public/web_state_delegate_bridge.h b/ios/web/public/web_state_delegate_bridge.h
index f80000d..5a6cc6d 100644
--- a/ios/web/public/web_state_delegate_bridge.h
+++ b/ios/web/public/web_state_delegate_bridge.h
@@ -65,31 +65,18 @@
 // Called to know the size of the view containing the WebView.
 - (UIView*)webViewContainerForWebState:(web::WebState*)webState;
 
-// Called when iOS13+ context menu is triggered and now it is required to
-// provide a UIContextMenuConfiguration to |completion_handler| to generate the
-// context menu.
+// Called when the context menu is triggered and now it is required to provide a
+// UIContextMenuConfiguration to |completion_handler| to generate the context
+// menu.
 - (void)webState:(web::WebState*)webState
     contextMenuConfigurationForParams:(const web::ContextMenuParams&)params
-                    completionHandler:
-                        (void (^)(UIContextMenuConfiguration*))completionHandler
-    API_AVAILABLE(ios(13.0));
+                    completionHandler:(void (^)(UIContextMenuConfiguration*))
+                                          completionHandler;
 
-// Called when iOS13+ context menu is ready to be showed.
+// Called when the context menu will commit with animator.
 - (void)webState:(web::WebState*)webState
-    contextMenuWillPresentForLinkWithURL:(const GURL&)linkURL
-    API_AVAILABLE(ios(13.0));
-
-// Called when iOS13+ context menu will commit with animator.
-- (void)webState:(web::WebState*)webState
-    contextMenuForLinkWithURL:(const GURL&)linkURL
-       willCommitWithAnimator:
-           (id<UIContextMenuInteractionCommitAnimating>)animator
-    API_AVAILABLE(ios(13.0));
-
-// Called when iOS13+ context menu will present.
-- (void)webState:(web::WebState*)webState
-    contextMenuDidEndForLinkWithURL:(const GURL&)linkURL
-    API_AVAILABLE(ios(13.0));
+    contextMenuWillCommitWithAnimator:
+        (id<UIContextMenuInteractionCommitAnimating>)animator;
 
 // This API can be used to show custom input views in the web view.
 - (id<CRWResponderInputView>)webStateInputViewProvider:(web::WebState*)webState;
@@ -131,17 +118,10 @@
   void ContextMenuConfiguration(
       WebState* source,
       const ContextMenuParams& params,
-      void (^completion_handler)(UIContextMenuConfiguration*))
-      API_AVAILABLE(ios(13.0)) override;
-  void ContextMenuDidEnd(WebState* source, const GURL& link_url)
-      API_AVAILABLE(ios(13.0)) override;
+      void (^completion_handler)(UIContextMenuConfiguration*)) override;
   void ContextMenuWillCommitWithAnimator(
       WebState* source,
-      const GURL& link_url,
-      id<UIContextMenuInteractionCommitAnimating> animator)
-      API_AVAILABLE(ios(13.0)) override;
-  void ContextMenuWillPresent(WebState* source, const GURL& link_url)
-      API_AVAILABLE(ios(13.0)) override;
+      id<UIContextMenuInteractionCommitAnimating> animator) override;
 
   id<CRWResponderInputView> GetResponderInputView(WebState* source) override;
 
diff --git a/ios/web/web_state/ui/crw_context_menu_controller.mm b/ios/web/web_state/ui/crw_context_menu_controller.mm
index 126353a3..4723f33e 100644
--- a/ios/web/web_state/ui/crw_context_menu_controller.mm
+++ b/ios/web/web_state/ui/crw_context_menu_controller.mm
@@ -196,6 +196,15 @@
              : nil;
 }
 
+- (void)contextMenuInteraction:(UIContextMenuInteraction*)interaction
+    willPerformPreviewActionForMenuWithConfiguration:
+        (UIContextMenuConfiguration*)configuration
+                                            animator:
+        (id<UIContextMenuInteractionCommitAnimating>)animator {
+  self.webState->GetDelegate()->ContextMenuWillCommitWithAnimator(self.webState,
+                                                                  animator);
+}
+
 #pragma mark - Private
 
 // Prevents the web view gesture recognizer to get the touch events.
diff --git a/ios/web/web_state/ui/crw_wk_ui_handler.mm b/ios/web/web_state/ui/crw_wk_ui_handler.mm
index 99ebbd5..07b4760 100644
--- a/ios/web/web_state/ui/crw_wk_ui_handler.mm
+++ b/ios/web/web_state/ui/crw_wk_ui_handler.mm
@@ -182,7 +182,7 @@
     contextMenuConfigurationForElement:(WKContextMenuElementInfo*)elementInfo
                      completionHandler:
                          (void (^)(UIContextMenuConfiguration* _Nullable))
-                             completionHandler API_AVAILABLE(ios(13.0)) {
+                             completionHandler {
   web::WebStateDelegate* delegate = self.webStateImpl->GetDelegate();
   if (!delegate) {
     completionHandler(nil);
@@ -197,41 +197,15 @@
 }
 
 - (void)webView:(WKWebView*)webView
-    contextMenuDidEndForElement:(WKContextMenuElementInfo*)elementInfo
-    API_AVAILABLE(ios(13.0)) {
-  web::WebStateDelegate* delegate = self.webStateImpl->GetDelegate();
-  if (!delegate) {
-    return;
-  }
-
-  delegate->ContextMenuDidEnd(self.webStateImpl,
-                              net::GURLWithNSURL(elementInfo.linkURL));
-}
-
-- (void)webView:(WKWebView*)webView
-     contextMenuForElement:(nonnull WKContextMenuElementInfo*)elementInfo
+     contextMenuForElement:(WKContextMenuElementInfo*)elementInfo
     willCommitWithAnimator:
-        (nonnull id<UIContextMenuInteractionCommitAnimating>)animator
-    API_AVAILABLE(ios(13.0)) {
+        (id<UIContextMenuInteractionCommitAnimating>)animator {
   web::WebStateDelegate* delegate = self.webStateImpl->GetDelegate();
   if (!delegate) {
     return;
   }
 
-  delegate->ContextMenuWillCommitWithAnimator(
-      self.webStateImpl, net::GURLWithNSURL(elementInfo.linkURL), animator);
-}
-
-- (void)webView:(WKWebView*)webView
-    contextMenuWillPresentForElement:(WKContextMenuElementInfo*)elementInfo
-    API_AVAILABLE(ios(13.0)) {
-  web::WebStateDelegate* delegate = self.webStateImpl->GetDelegate();
-  if (!delegate) {
-    return;
-  }
-
-  delegate->ContextMenuWillPresent(self.webStateImpl,
-                                   net::GURLWithNSURL(elementInfo.linkURL));
+  delegate->ContextMenuWillCommitWithAnimator(self.webStateImpl, animator);
 }
 
 #pragma mark - Helper
diff --git a/ios/web/web_state/web_state_context_menu_bridge_unittest.mm b/ios/web/web_state/web_state_context_menu_bridge_unittest.mm
index 49bbd3c..d3ab282 100644
--- a/ios/web/web_state/web_state_context_menu_bridge_unittest.mm
+++ b/ios/web/web_state/web_state_context_menu_bridge_unittest.mm
@@ -22,49 +22,27 @@
 @property(nonatomic) web::WebState* webState;
 @property(nonatomic) NSURL* URL;
 @property(nonatomic) BOOL contextMenuConfigurationNeeded;
-@property(nonatomic) BOOL contextMenuWillPresent;
 @property(nonatomic) BOOL contextMenuWillCommitWithAnimator;
-@property(nonatomic) BOOL contextMenuDidEnd;
 @end
 
 @implementation FakeCRWWebStateDelegate
 
 - (void)webState:(web::WebState*)webState
     contextMenuConfigurationForParams:(const web::ContextMenuParams&)params
-                    completionHandler:
-                        (void (^)(UIContextMenuConfiguration*))completionHandler
-    API_AVAILABLE(ios(13.0)) {
+                    completionHandler:(void (^)(UIContextMenuConfiguration*))
+                                          completionHandler {
   self.webState = webState;
   self.URL = net::NSURLWithGURL(params.link_url);
   self.contextMenuConfigurationNeeded = YES;
 }
 
 - (void)webState:(web::WebState*)webState
-    contextMenuWillPresentForLinkWithURL:(const GURL&)linkURL
-    API_AVAILABLE(ios(13.0)) {
+    contextMenuWillCommitWithAnimator:
+        (id<UIContextMenuInteractionCommitAnimating>)animator {
   self.webState = webState;
-  self.URL = net::NSURLWithGURL(linkURL);
-  self.contextMenuWillPresent = YES;
-}
-
-- (void)webState:(web::WebState*)webState
-    contextMenuForLinkWithURL:(const GURL&)linkURL
-       willCommitWithAnimator:
-           (id<UIContextMenuInteractionCommitAnimating>)animator
-    API_AVAILABLE(ios(13.0)) {
-  self.webState = webState;
-  self.URL = net::NSURLWithGURL(linkURL);
   self.contextMenuWillCommitWithAnimator = YES;
 }
 
-- (void)webState:(web::WebState*)webState
-    contextMenuDidEndForLinkWithURL:(const GURL&)linkURL
-    API_AVAILABLE(ios(13.0)) {
-  self.webState = webState;
-  self.URL = net::NSURLWithGURL(linkURL);
-  self.contextMenuDidEnd = YES;
-}
-
 @end
 
 namespace web {
@@ -88,59 +66,33 @@
   std::unique_ptr<web::WebStateDelegateBridge> web_state_delegate_bridge_;
 };
 
-TEST_F(WebStateContextMenuBridgeTest, IOS13ContextMenuDelegateBridgeTest) {
-  if (@available(iOS 13, *)) {
-    WKWebView* web_view = [web_controller() ensureWebViewCreated];
-    id<WKUIDelegate> ui_delegate = web_view.UIDelegate;
+TEST_F(WebStateContextMenuBridgeTest, ContextMenuDelegateBridgeTest) {
+  WKWebView* web_view = [web_controller() ensureWebViewCreated];
+  id<WKUIDelegate> ui_delegate = web_view.UIDelegate;
 
-    NSURL* url = [NSURL URLWithString:@"https://google.com/"];
-    id element_info = OCMClassMock([WKContextMenuElementInfo class]);
-    [[[element_info stub] andReturn:url] linkURL];
+  NSURL* url = [NSURL URLWithString:@"https://google.com/"];
+  id element_info = OCMClassMock([WKContextMenuElementInfo class]);
+  [[[element_info stub] andReturn:url] linkURL];
 
-    FakeCRWWebStateDelegate* web_state_delegate = MakeFakeCRWWebStateDelegate();
-    [ui_delegate webView:web_view
-        contextMenuConfigurationForElement:element_info
-                         completionHandler:^(id){
-                         }];
-    EXPECT_EQ(web_state(), web_state_delegate.webState);
-    EXPECT_NSEQ(url, web_state_delegate.URL);
-    EXPECT_TRUE(web_state_delegate.contextMenuConfigurationNeeded);
-    EXPECT_FALSE(web_state_delegate.contextMenuDidEnd);
-    EXPECT_FALSE(web_state_delegate.contextMenuWillCommitWithAnimator);
-    EXPECT_FALSE(web_state_delegate.contextMenuWillPresent);
+  FakeCRWWebStateDelegate* web_state_delegate = MakeFakeCRWWebStateDelegate();
+  [ui_delegate webView:web_view
+      contextMenuConfigurationForElement:element_info
+                       completionHandler:^(id){
+                       }];
+  EXPECT_EQ(web_state(), web_state_delegate.webState);
+  EXPECT_NSEQ(url, web_state_delegate.URL);
+  EXPECT_TRUE(web_state_delegate.contextMenuConfigurationNeeded);
+  EXPECT_FALSE(web_state_delegate.contextMenuWillCommitWithAnimator);
 
-    web_state_delegate = MakeFakeCRWWebStateDelegate();
-    [ui_delegate webView:web_view contextMenuDidEndForElement:element_info];
-    EXPECT_EQ(web_state(), web_state_delegate.webState);
-    EXPECT_NSEQ(url, web_state_delegate.URL);
-    EXPECT_FALSE(web_state_delegate.contextMenuConfigurationNeeded);
-    EXPECT_TRUE(web_state_delegate.contextMenuDidEnd);
-    EXPECT_FALSE(web_state_delegate.contextMenuWillCommitWithAnimator);
-    EXPECT_FALSE(web_state_delegate.contextMenuWillPresent);
-
-    web_state_delegate = MakeFakeCRWWebStateDelegate();
-    [ui_delegate webView:web_view
-         contextMenuForElement:element_info
-        willCommitWithAnimator:
-            [OCMockObject
-                mockForProtocol:@protocol(UIContextMenuInteractionDelegate)]];
-    EXPECT_EQ(web_state(), web_state_delegate.webState);
-    EXPECT_NSEQ(url, web_state_delegate.URL);
-    EXPECT_FALSE(web_state_delegate.contextMenuConfigurationNeeded);
-    EXPECT_FALSE(web_state_delegate.contextMenuDidEnd);
-    EXPECT_TRUE(web_state_delegate.contextMenuWillCommitWithAnimator);
-    EXPECT_FALSE(web_state_delegate.contextMenuWillPresent);
-
-    web_state_delegate = MakeFakeCRWWebStateDelegate();
-    [ui_delegate webView:web_view
-        contextMenuWillPresentForElement:element_info];
-    EXPECT_EQ(web_state(), web_state_delegate.webState);
-    EXPECT_NSEQ(url, web_state_delegate.URL);
-    EXPECT_FALSE(web_state_delegate.contextMenuConfigurationNeeded);
-    EXPECT_FALSE(web_state_delegate.contextMenuDidEnd);
-    EXPECT_FALSE(web_state_delegate.contextMenuWillCommitWithAnimator);
-    EXPECT_TRUE(web_state_delegate.contextMenuWillPresent);
-  }
+  web_state_delegate = MakeFakeCRWWebStateDelegate();
+  [ui_delegate webView:web_view
+       contextMenuForElement:element_info
+      willCommitWithAnimator:
+          [OCMockObject
+              mockForProtocol:@protocol(UIContextMenuInteractionDelegate)]];
+  EXPECT_EQ(web_state(), web_state_delegate.webState);
+  EXPECT_FALSE(web_state_delegate.contextMenuConfigurationNeeded);
+  EXPECT_TRUE(web_state_delegate.contextMenuWillCommitWithAnimator);
 }
 
 }  // namespace web
diff --git a/ios/web/web_state/web_state_delegate.mm b/ios/web/web_state/web_state_delegate.mm
index 9158fba..b2e45a7d 100644
--- a/ios/web/web_state/web_state_delegate.mm
+++ b/ios/web/web_state/web_state_delegate.mm
@@ -72,23 +72,13 @@
 void WebStateDelegate::ContextMenuConfiguration(
     WebState* source,
     const ContextMenuParams& params,
-    void (^completion_handler)(UIContextMenuConfiguration*))
-    API_AVAILABLE(ios(13.0)) {
+    void (^completion_handler)(UIContextMenuConfiguration*)) {
   completion_handler(nil);
 }
 
-void WebStateDelegate::ContextMenuDidEnd(WebState* source, const GURL& link_url)
-    API_AVAILABLE(ios(13.0)) {}
-
 void WebStateDelegate::ContextMenuWillCommitWithAnimator(
     WebState* source,
-    const GURL& link_url,
-    id<UIContextMenuInteractionCommitAnimating> animator)
-    API_AVAILABLE(ios(13.0)) {}
-
-void WebStateDelegate::ContextMenuWillPresent(WebState* source,
-                                              const GURL& link_url)
-    API_AVAILABLE(ios(13.0)) {}
+    id<UIContextMenuInteractionCommitAnimating> animator) {}
 
 id<CRWResponderInputView> WebStateDelegate::GetResponderInputView(
     WebState* source) {
diff --git a/ios/web/web_state/web_state_delegate_bridge.mm b/ios/web/web_state/web_state_delegate_bridge.mm
index 5f3282b..dd26b9c 100644
--- a/ios/web/web_state/web_state_delegate_bridge.mm
+++ b/ios/web/web_state/web_state_delegate_bridge.mm
@@ -111,8 +111,7 @@
 void WebStateDelegateBridge::ContextMenuConfiguration(
     WebState* source,
     const ContextMenuParams& params,
-    void (^completion_handler)(UIContextMenuConfiguration*))
-    API_AVAILABLE(ios(13.0)) {
+    void (^completion_handler)(UIContextMenuConfiguration*)) {
   if ([delegate_ respondsToSelector:@selector
                  (webState:
                      contextMenuConfigurationForParams:completionHandler:)]) {
@@ -124,35 +123,12 @@
   }
 }
 
-void WebStateDelegateBridge::ContextMenuDidEnd(WebState* source,
-                                               const GURL& link_url)
-    API_AVAILABLE(ios(13.0)) {
-  if ([delegate_ respondsToSelector:@selector(webState:
-                                        contextMenuDidEndForLinkWithURL:)]) {
-    [delegate_ webState:source contextMenuDidEndForLinkWithURL:link_url];
-  }
-}
-
 void WebStateDelegateBridge::ContextMenuWillCommitWithAnimator(
     WebState* source,
-    const GURL& link_url,
-    id<UIContextMenuInteractionCommitAnimating> animator)
-    API_AVAILABLE(ios(13.0)) {
-  if ([delegate_ respondsToSelector:@selector
-                 (webState:
-                     contextMenuForLinkWithURL:willCommitWithAnimator:)]) {
-    [delegate_ webState:source
-        contextMenuForLinkWithURL:link_url
-           willCommitWithAnimator:animator];
-  }
-}
-
-void WebStateDelegateBridge::ContextMenuWillPresent(WebState* source,
-                                                    const GURL& link_url)
-    API_AVAILABLE(ios(13.0)) {
-  if ([delegate_ respondsToSelector:@selector
-                 (webState:contextMenuWillPresentForLinkWithURL:)]) {
-    [delegate_ webState:source contextMenuWillPresentForLinkWithURL:link_url];
+    id<UIContextMenuInteractionCommitAnimating> animator) {
+  if ([delegate_ respondsToSelector:@selector(webState:
+                                        contextMenuWillCommitWithAnimator:)]) {
+    [delegate_ webState:source contextMenuWillCommitWithAnimator:animator];
   }
 }
 
diff --git a/ios/web_view/internal/cwv_web_view.mm b/ios/web_view/internal/cwv_web_view.mm
index f4f675c..629bfdf9 100644
--- a/ios/web_view/internal/cwv_web_view.mm
+++ b/ios/web_view/internal/cwv_web_view.mm
@@ -509,9 +509,8 @@
 
 - (void)webState:(web::WebState*)webState
     contextMenuConfigurationForParams:(const web::ContextMenuParams&)params
-                    completionHandler:
-                        (void (^)(UIContextMenuConfiguration*))completionHandler
-    API_AVAILABLE(ios(13.0)) {
+                    completionHandler:(void (^)(UIContextMenuConfiguration*))
+                                          completionHandler {
   SEL selector = @selector(webView:
       contextMenuConfigurationForElement:completionHandler:);
   if ([_UIDelegate respondsToSelector:selector]) {
@@ -531,36 +530,11 @@
 }
 
 - (void)webState:(web::WebState*)webState
-    contextMenuWillPresentForLinkWithURL:(const GURL&)linkURL
-    API_AVAILABLE(ios(13.0)) {
-  SEL selector = @selector(webView:contextMenuWillPresentForLinkWithURL:);
+    contextMenuWillCommitWithAnimator:
+        (id<UIContextMenuInteractionCommitAnimating>)animator {
+  SEL selector = @selector(webView:contextMenuWillCommitWithAnimator:);
   if ([_UIDelegate respondsToSelector:selector]) {
-    [_UIDelegate webView:self
-        contextMenuWillPresentForLinkWithURL:net::NSURLWithGURL(linkURL)];
-  }
-}
-
-- (void)webState:(web::WebState*)webState
-    contextMenuForLinkWithURL:(const GURL&)linkURL
-       willCommitWithAnimator:
-           (id<UIContextMenuInteractionCommitAnimating>)animator
-    API_AVAILABLE(ios(13.0)) {
-  SEL selector = @selector(webView:
-         contextMenuForLinkWithURL:willCommitWithAnimator:);
-  if ([_UIDelegate respondsToSelector:selector]) {
-    [_UIDelegate webView:self
-        contextMenuForLinkWithURL:net::NSURLWithGURL(linkURL)
-           willCommitWithAnimator:animator];
-  }
-}
-
-- (void)webState:(web::WebState*)webState
-    contextMenuDidEndForLinkWithURL:(const GURL&)linkURL
-    API_AVAILABLE(ios(13.0)) {
-  SEL selector = @selector(webView:contextMenuDidEndForLinkWithURL:);
-  if ([_UIDelegate respondsToSelector:selector]) {
-    [_UIDelegate webView:self
-        contextMenuDidEndForLinkWithURL:net::NSURLWithGURL(linkURL)];
+    [_UIDelegate webView:self contextMenuWillCommitWithAnimator:animator];
   }
 }
 
diff --git a/ios/web_view/public/cwv_ui_delegate.h b/ios/web_view/public/cwv_ui_delegate.h
index 7af95a467..dc65c02 100644
--- a/ios/web_view/public/cwv_ui_delegate.h
+++ b/ios/web_view/public/cwv_ui_delegate.h
@@ -70,26 +70,13 @@
     contextMenuConfigurationForElement:(CWVHTMLElement*)element
                      completionHandler:
                          (void (^)(UIContextMenuConfiguration* _Nullable))
-                             completionHandler API_AVAILABLE(ios(13.0));
-
-// Equivalent of -[WKUIDelegate
-// webView:contextMenuWillPresentForElement:].
-- (void)webView:(CWVWebView*)webView
-    contextMenuWillPresentForLinkWithURL:(NSURL*)linkURL
-    API_AVAILABLE(ios(13.0));
+                             completionHandler;
 
 // Equivalent of -[WKUIDelegate
 // webView:contextMenuForElement:willCommitWithAnimator:].
 - (void)webView:(CWVWebView*)webView
-    contextMenuForLinkWithURL:(NSURL*)linkURL
-       willCommitWithAnimator:
-           (id<UIContextMenuInteractionCommitAnimating>)animator
-    API_AVAILABLE(ios(13.0));
-
-// Equivalent of -[WKUIDelegate
-// webView:contextMenuDidEndForElement:].
-- (void)webView:(CWVWebView*)webView
-    contextMenuDidEndForLinkWithURL:(NSURL*)linkURL API_AVAILABLE(ios(13.0));
+    contextMenuWillCommitWithAnimator:
+           (id<UIContextMenuInteractionCommitAnimating>)animator;
 
 @end
 
diff --git a/ios/web_view/shell/shell_view_controller.m b/ios/web_view/shell/shell_view_controller.m
index 64d87cba..385632bb 100644
--- a/ios/web_view/shell/shell_view_controller.m
+++ b/ios/web_view/shell/shell_view_controller.m
@@ -833,8 +833,7 @@
 - (void)webView:(CWVWebView*)webView
     contextMenuConfigurationForElement:(CWVHTMLElement*)element
                      completionHandler:(void (^)(UIContextMenuConfiguration*))
-                                           completionHandler
-    API_AVAILABLE(ios(13.0)) {
+                                           completionHandler {
   void (^copyHandler)(UIAction*) = ^(UIAction* action) {
     NSDictionary* item = @{
       (NSString*)(kUTTypeURL) : element.hyperlink.absoluteString,
@@ -876,24 +875,9 @@
 }
 
 - (void)webView:(CWVWebView*)webView
-    contextMenuWillPresentForLinkWithURL:(NSURL*)linkURL
-    API_AVAILABLE(ios(13.0)) {
-  NSLog(@"webView:contextMenuWillPresentForLinkWithURL: %@",
-        linkURL.absoluteString);
-}
-
-- (void)webView:(CWVWebView*)webView
-    contextMenuForLinkWithURL:(NSURL*)linkURL
-       willCommitWithAnimator:
-           (id<UIContextMenuInteractionCommitAnimating>)animator
-    API_AVAILABLE(ios(13.0)) {
-  NSLog(@"webView:contextMenuForLinkWithURL:willCommitWithAnimator: %@",
-        linkURL.absoluteString);
-}
-
-- (void)webView:(CWVWebView*)webView
-    contextMenuDidEndForLinkWithURL:(NSURL*)linkURL API_AVAILABLE(ios(13.0)) {
-  NSLog(@"webView:contextMenuDidEndForLinkWithURL: %@", linkURL.absoluteString);
+    contextMenuWillCommitWithAnimator:
+        (id<UIContextMenuInteractionCommitAnimating>)animator {
+  NSLog(@"webView:contextMenuWillCommitWithAnimator:");
 }
 
 - (void)webView:(CWVWebView*)webView
diff --git a/media/capture/video/mac/sample_buffer_transformer_mac_unittest.mm b/media/capture/video/mac/sample_buffer_transformer_mac_unittest.mm
index 3c83dd24..0d4540480 100644
--- a/media/capture/video/mac/sample_buffer_transformer_mac_unittest.mm
+++ b/media/capture/video/mac/sample_buffer_transformer_mac_unittest.mm
@@ -187,7 +187,6 @@
     int height,
     int padding) {
   size_t num_planes = CVPixelBufferGetPlaneCount(pixel_buffer);
-  size_t padded_size = 0;
   std::vector<size_t> plane_widths;
   std::vector<size_t> plane_heights;
   std::vector<size_t> plane_strides;
@@ -201,7 +200,6 @@
       plane_heights.push_back(h);
       plane_widths.push_back(w);
       plane_strides.push_back(padded_stride);
-      padded_size += h * padded_stride;
     }
   } else {
     // CVPixelBufferGetPlaneCount returns 0 for non-planar buffers.
@@ -209,7 +207,6 @@
     size_t plane_stride = CVPixelBufferGetBytesPerRow(pixel_buffer);
     size_t padded_stride = plane_stride + padding;
     size_t h = CVPixelBufferGetHeight(pixel_buffer);
-    padded_size += h * padded_stride;
     plane_heights.push_back(h);
     plane_strides.push_back(padded_stride);
   }
diff --git a/media/gpu/windows/dxva_video_decode_accelerator_win.cc b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
index c09bde8..d21dc50 100644
--- a/media/gpu/windows/dxva_video_decode_accelerator_win.cc
+++ b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
@@ -42,7 +42,9 @@
 #include "build/build_config.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "gpu/command_buffer/common/shared_image_usage.h"
+#include "gpu/command_buffer/service/shared_image_backing.h"
 #include "gpu/command_buffer/service/shared_image_backing_d3d.h"
+#include "gpu/command_buffer/service/shared_image_backing_gl_image.h"
 #include "gpu/command_buffer/service/shared_image_factory.h"
 #include "gpu/config/gpu_driver_bug_workarounds.h"
 #include "gpu/config/gpu_preferences.h"
@@ -1406,10 +1408,7 @@
 }
 
 bool DXVAVideoDecodeAccelerator::SupportsSharedImagePictureBuffers() const {
-  // Shared image is needed to display overlays which can be used directly
-  // by the video processor.
-  // TODO(crbug.com/1011555): Support for non-bind cases.
-  return GetPictureBufferMechanism() == PictureBufferMechanism::BIND;
+  return true;
 }
 
 // static
@@ -2676,9 +2675,13 @@
   RETURN_AND_NOTIFY_ON_FAILURE(result, "Failed to complete copying surface",
                                PLATFORM_FAILURE, );
 
+  std::vector<scoped_refptr<Picture::ScopedSharedImage>> scoped_shared_images =
+      GetSharedImagesFromPictureBuffer(picture_buffer);
+
   NotifyPictureReady(
       picture_buffer->id(), input_buffer_id, picture_buffer->visible_rect(),
-      picture_buffer->color_space(), picture_buffer->AllowOverlay());
+      picture_buffer->color_space(), picture_buffer->AllowOverlay(),
+      std::move(scoped_shared_images));
 
   {
     base::AutoLock lock(decoder_lock_);
@@ -2733,84 +2736,8 @@
   RETURN_AND_NOTIFY_ON_FAILURE(result, "Failed to bind sample to texture",
                                PLATFORM_FAILURE, );
 
-  // Create the DX11 texture backed shared images (texture per plane).
-  std::vector<scoped_refptr<Picture::ScopedSharedImage>> scoped_shared_images;
-  if (SupportsSharedImagePictureBuffers()) {
-    gl::GLImageDXGI* gl_image_dxgi =
-        gl::GLImageDXGI::FromGLImage(picture_buffer->gl_image().get());
-    DCHECK(gl_image_dxgi);
-
-    const size_t textures_per_picture =
-        picture_buffer->service_texture_ids().size();
-
-    // Get the viz resource format per texture.
-    std::array<viz::ResourceFormat, VideoFrame::kMaxPlanes> viz_formats;
-    {
-      result = VideoPixelFormatToVizFormat(picture_buffer->pixel_format(),
-                                           textures_per_picture, viz_formats);
-      RETURN_AND_NOTIFY_ON_FAILURE(
-          result, "Could not convert pixel format to viz format",
-          PLATFORM_FAILURE, );
-    }
-
-    CommandBufferHelper* helper = client_->GetCommandBufferHelper();
-    DCHECK(helper);
-
-    for (uint32_t texture_idx = 0; texture_idx < textures_per_picture;
-         texture_idx++) {
-      // Usage flags to allow the display compositor to draw from it, video
-      // to decode, and allow webgl/canvas access.
-      constexpr uint32_t shared_image_usage =
-          gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE | gpu::SHARED_IMAGE_USAGE_GLES2 |
-          gpu::SHARED_IMAGE_USAGE_RASTER | gpu::SHARED_IMAGE_USAGE_DISPLAY |
-          gpu::SHARED_IMAGE_USAGE_SCANOUT;
-
-      // Create a shared image
-      // TODO(crbug.com/1011555): Need key shared mutex if shared image is ever
-      // used by another device.
-      scoped_refptr<gpu::gles2::TexturePassthrough> gl_texture =
-          gpu::gles2::TexturePassthrough::CheckedCast(helper->GetTexture(
-              picture_buffer->service_texture_ids()[texture_idx]));
-
-      // Create a new shared image mailbox. The existing mailbox belonging to
-      // this |picture_buffer| will be updated when the video frame is created.
-      const auto& mailbox = gpu::Mailbox::GenerateForSharedImage();
-
-      auto shared_image = gpu::SharedImageBackingD3D::CreateFromGLTexture(
-          mailbox, viz_formats[texture_idx],
-          picture_buffer->texture_size(texture_idx),
-          picture_buffer->color_space(), kTopLeft_GrSurfaceOrigin,
-          kPremul_SkAlphaType, shared_image_usage, gl_image_dxgi->texture(),
-          std::move(gl_texture));
-
-      // Caller is assumed to provide cleared d3d textures.
-      shared_image->SetCleared();
-
-      gpu::SharedImageStub* shared_image_stub = client_->GetSharedImageStub();
-      DCHECK(shared_image_stub);
-      const bool success = shared_image_stub->factory()->RegisterBacking(
-          std::move(shared_image), /* legacy_mailbox */ true);
-      if (!success) {
-        RETURN_AND_NOTIFY_ON_FAILURE(false, "Failed to register shared image",
-                                     PLATFORM_FAILURE, );
-      }
-
-      auto destroy_shared_image_callback = base::BindPostTask(
-          main_thread_task_runner_,
-          base::BindOnce(
-              shared_image_stub->GetSharedImageDestructionCallback(mailbox),
-              gpu::SyncToken()));
-
-      // Wrap the factory ref with a scoped shared image. The factory ref
-      // is used instead of requiring a destruction call-back.
-      auto scoped_shared_image =
-          base::MakeRefCounted<Picture::ScopedSharedImage>(
-              mailbox, GetTextureTarget(),
-              std::move(destroy_shared_image_callback));
-
-      scoped_shared_images.push_back(std::move(scoped_shared_image));
-    }
-  }
+  std::vector<scoped_refptr<Picture::ScopedSharedImage>> scoped_shared_images =
+      GetSharedImagesFromPictureBuffer(picture_buffer);
 
   NotifyPictureReady(
       picture_buffer->id(), input_buffer_id, picture_buffer->visible_rect(),
@@ -3304,6 +3231,95 @@
   num_picture_buffers_requested_ = kNumPictureBuffers;
 }
 
+std::vector<scoped_refptr<Picture::ScopedSharedImage>>
+DXVAVideoDecodeAccelerator::GetSharedImagesFromPictureBuffer(
+    DXVAPictureBuffer* picture_buffer) {
+  const size_t textures_per_picture =
+      picture_buffer->service_texture_ids().size();
+
+  // Get the viz resource format per texture.
+  std::array<viz::ResourceFormat, VideoFrame::kMaxPlanes> viz_formats;
+  {
+    bool result = VideoPixelFormatToVizFormat(
+        picture_buffer->pixel_format(), textures_per_picture, viz_formats);
+    RETURN_AND_NOTIFY_ON_FAILURE(result,
+                                 "Could not convert pixel format to viz format",
+                                 PLATFORM_FAILURE, {});
+  }
+
+  std::vector<scoped_refptr<Picture::ScopedSharedImage>> scoped_shared_images;
+  CommandBufferHelper* helper = client_->GetCommandBufferHelper();
+  DCHECK(helper);
+
+  for (uint32_t texture_idx = 0; texture_idx < textures_per_picture;
+       texture_idx++) {
+    // Usage flags to allow the display compositor to draw from it, video
+    // to decode, and allow webgl/canvas access.
+    constexpr uint32_t shared_image_usage =
+        gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE | gpu::SHARED_IMAGE_USAGE_GLES2 |
+        gpu::SHARED_IMAGE_USAGE_RASTER | gpu::SHARED_IMAGE_USAGE_DISPLAY |
+        gpu::SHARED_IMAGE_USAGE_SCANOUT;
+
+    // Create a shared image
+    // TODO(crbug.com/1011555): Need key shared mutex if shared image is ever
+    // used by another device.
+    scoped_refptr<gpu::gles2::TexturePassthrough> gl_texture =
+        gpu::gles2::TexturePassthrough::CheckedCast(helper->GetTexture(
+            picture_buffer->service_texture_ids()[texture_idx]));
+
+    // Create a new shared image mailbox. The existing mailbox belonging to
+    // this |picture_buffer| will be updated when the video frame is created.
+    const auto& mailbox = gpu::Mailbox::GenerateForSharedImage();
+
+    std::unique_ptr<gpu::SharedImageBacking> shared_image;
+    // Check if this picture buffer has a DX11 texture.
+    gl::GLImageDXGI* gl_image_dxgi =
+        gl::GLImageDXGI::FromGLImage(picture_buffer->gl_image().get());
+    if (gl_image_dxgi) {
+      shared_image = gpu::SharedImageBackingD3D::CreateFromGLTexture(
+          mailbox, viz_formats[texture_idx],
+          picture_buffer->texture_size(texture_idx),
+          picture_buffer->color_space(), kTopLeft_GrSurfaceOrigin,
+          kPremul_SkAlphaType, shared_image_usage, gl_image_dxgi->texture(),
+          std::move(gl_texture));
+    } else {
+      shared_image = gpu::SharedImageBackingGLImage::CreateFromGLTexture(
+          picture_buffer->gl_image(), mailbox, viz_formats[texture_idx],
+          picture_buffer->size(), picture_buffer->color_space(),
+          kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, shared_image_usage,
+          GetTextureTarget(), std::move(gl_texture));
+    }
+
+    DCHECK(shared_image);
+    // Caller is assumed to provide cleared d3d textures.
+    shared_image->SetCleared();
+
+    gpu::SharedImageStub* shared_image_stub = client_->GetSharedImageStub();
+    DCHECK(shared_image_stub);
+    const bool success = shared_image_stub->factory()->RegisterBacking(
+        std::move(shared_image), /* legacy_mailbox */ true);
+    if (!success) {
+      RETURN_AND_NOTIFY_ON_FAILURE(false, "Failed to register shared image",
+                                   PLATFORM_FAILURE, {});
+    }
+
+    auto destroy_shared_image_callback = base::BindPostTask(
+        main_thread_task_runner_,
+        base::BindOnce(
+            shared_image_stub->GetSharedImageDestructionCallback(mailbox),
+            gpu::SyncToken()));
+
+    // Wrap the factory ref with a scoped shared image. The factory ref
+    // is used instead of requiring a destruction call-back.
+    auto scoped_shared_image = base::MakeRefCounted<Picture::ScopedSharedImage>(
+        mailbox, GetTextureTarget(), std::move(destroy_shared_image_callback));
+
+    scoped_shared_images.push_back(std::move(scoped_shared_image));
+  }
+
+  return scoped_shared_images;
+}
+
 DXVAVideoDecodeAccelerator::PictureBufferMechanism
 DXVAVideoDecodeAccelerator::GetPictureBufferMechanism() const {
   if (use_fp16_)
diff --git a/media/gpu/windows/dxva_video_decode_accelerator_win.h b/media/gpu/windows/dxva_video_decode_accelerator_win.h
index b8632891..39a3d118 100644
--- a/media/gpu/windows/dxva_video_decode_accelerator_win.h
+++ b/media/gpu/windows/dxva_video_decode_accelerator_win.h
@@ -38,6 +38,7 @@
 
 namespace gl {
 class GLContext;
+class GLImageDXGI;
 }
 
 namespace gpu {
@@ -415,6 +416,13 @@
   // |num_picture_buffers_requested_|.
   void DisableSharedTextureSupport();
 
+  // Creates ScopedSharedImages for the provided PictureBuffer. If the buffer
+  // has a GLImageDXGI this function will create SharedImageBackingD3D using the
+  // DX11 texture. Otherwise it will create thin SharedImageBackingGLImage
+  // wrappers around the existing textures in |picture_buffer|.
+  std::vector<scoped_refptr<Picture::ScopedSharedImage>>
+  GetSharedImagesFromPictureBuffer(DXVAPictureBuffer* picture_buffer);
+
   uint32_t GetTextureTarget() const;
 
   PictureBufferMechanism GetPictureBufferMechanism() const;
diff --git a/net/base/features.cc b/net/base/features.cc
index 549f376..672f24b 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -89,6 +89,9 @@
 const base::Feature kEnableTLS13EarlyData{"EnableTLS13EarlyData",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kEncryptedClientHello{"EncryptedClientHello",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kNetworkQualityEstimator{"NetworkQualityEstimator",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/net/base/features.h b/net/base/features.h
index e516e675..fcdb5c4 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -34,9 +34,6 @@
 // origin requests are restricted to contain at most the source origin.
 NET_EXPORT extern const base::Feature kCapReferrerToOriginOnCrossOrigin;
 
-// Enables TLS 1.3 early data.
-NET_EXPORT extern const base::Feature kEnableTLS13EarlyData;
-
 // Support for altering the parameters used for DNS transaction timeout. See
 // ResolveContext::SecureTransactionTimeout().
 NET_EXPORT extern const base::Feature kDnsTransactionDynamicTimeouts;
@@ -148,6 +145,16 @@
 NET_EXPORT extern const base::FeatureParam<int>
     kUseDnsHttpsSvcbExtraTimePercent;
 
+// Enables TLS 1.3 early data.
+NET_EXPORT extern const base::Feature kEnableTLS13EarlyData;
+
+// Enables the TLS Encrypted ClientHello feature.
+// https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-13
+//
+// TODO(https://crbug.com/1091403): This flag does not currently do much yet.
+// ECH is still in development.
+NET_EXPORT extern const base::Feature kEncryptedClientHello;
+
 // Enables optimizing the network quality estimation algorithms in network
 // quality estimator (NQE).
 NET_EXPORT extern const base::Feature kNetworkQualityEstimator;
diff --git a/net/cert/cert_verify_proc_mac.cc b/net/cert/cert_verify_proc_mac.cc
index 909c4ac..b4563ba 100644
--- a/net/cert/cert_verify_proc_mac.cc
+++ b/net/cert/cert_verify_proc_mac.cc
@@ -1058,7 +1058,6 @@
   // error was due to an unsupported key size.
   bool policy_failed = false;
   bool policy_fail_already_mapped = false;
-  bool weak_key_or_signature_algorithm = false;
 
   // As of macOS 10.13, if |trust_result| (from SecTrustGetResult) returns
   // kSecTrustResultInvalid, subsequent invocations of SecTrust APIs may
@@ -1130,7 +1129,6 @@
           CertStatus mapped_status = 0;
           if (policy_failed && status_code == CSSMERR_TP_INVALID_CERTIFICATE) {
             mapped_status = CERT_STATUS_WEAK_SIGNATURE_ALGORITHM;
-            weak_key_or_signature_algorithm = true;
             policy_fail_already_mapped = true;
           } else if (base::mac::IsOS10_12() && policy_failed &&
                      (flags & CertVerifyProc::VERIFY_REV_CHECKING_ENABLED) &&
@@ -1146,7 +1144,6 @@
           } else {
             mapped_status = CertStatusFromOSStatus(status_code);
             if (mapped_status == CERT_STATUS_WEAK_KEY) {
-              weak_key_or_signature_algorithm = true;
               policy_fail_already_mapped = true;
             }
           }
diff --git a/net/socket/ssl_client_socket_impl.cc b/net/socket/ssl_client_socket_impl.cc
index f6ba903..daac901 100644
--- a/net/socket/ssl_client_socket_impl.cc
+++ b/net/socket/ssl_client_socket_impl.cc
@@ -912,9 +912,11 @@
         host_and_port_, &client_cert_, &client_private_key_);
   }
 
-  // TODO(https://crbug.com/1091403): Also enable ECH GREASE, gated on SSLConfig
-  // or a base::Feature, for when we don't have an ECHConfig.
+  if (base::FeatureList::IsEnabled(features::kEncryptedClientHello)) {
+    SSL_set_enable_ech_grease(ssl_.get(), 1);
+  }
   if (!ssl_config_.ech_config_list.empty()) {
+    DCHECK(base::FeatureList::IsEnabled(features::kEncryptedClientHello));
     net_log_.AddEvent(NetLogEventType::SSL_ECH_CONFIG_LIST, [&] {
       base::Value dict(base::Value::Type::DICTIONARY);
       dict.SetKey("bytes", NetLogBinaryValue(ssl_config_.ech_config_list));
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index abcc990..675f010 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -5544,6 +5544,9 @@
 }
 
 TEST_F(SSLClientSocketTest, ECH) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kEncryptedClientHello);
+
   SSLServerConfig server_config;
   SSLConfig client_config;
   server_config.ech_keys = MakeTestECHKeys(
@@ -5599,6 +5602,9 @@
 }
 
 TEST_F(SSLClientSocketTest, ECHWrongKeys) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kEncryptedClientHello);
+
   std::vector<uint8_t> ech_config_list1, ech_config_list2;
   bssl::UniquePtr<SSL_ECH_KEYS> keys1 =
       MakeTestECHKeys("public.example", /*max_name_len=*/64, &ech_config_list1);
@@ -5625,6 +5631,9 @@
 }
 
 TEST_F(SSLClientSocketTest, InvalidECHConfigList) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kEncryptedClientHello);
+
   ASSERT_TRUE(
       StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, SSLServerConfig()));
 
@@ -5637,6 +5646,55 @@
   EXPECT_THAT(rv, IsError(ERR_INVALID_ECH_CONFIG_LIST));
 }
 
+// Test that, if no ECHConfigList is available, the client sends ECH GREASE.
+TEST_F(SSLClientSocketTest, ECHGreaseEnabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kEncryptedClientHello);
+
+  // Configure the server to expect an ECH extension.
+  bool ran_callback = false;
+  SSLServerConfig server_config;
+  server_config.client_hello_callback_for_testing =
+      base::BindLambdaForTesting([&](const SSL_CLIENT_HELLO* client_hello) {
+        const uint8_t* data;
+        size_t len;
+        EXPECT_TRUE(SSL_early_callback_ctx_extension_get(
+            client_hello, TLSEXT_TYPE_encrypted_client_hello, &data, &len));
+        ran_callback = true;
+      });
+  ASSERT_TRUE(
+      StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, server_config));
+  int rv;
+  ASSERT_TRUE(CreateAndConnectSSLClientSocket(SSLConfig(), &rv));
+  EXPECT_THAT(rv, IsOk());
+  EXPECT_TRUE(ran_callback);
+}
+
+// Test that, if the feature flag is disabled, the client does not send ECH
+// GREASE.
+TEST_F(SSLClientSocketTest, ECHGreaseDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(features::kEncryptedClientHello);
+
+  // Configure the server not to expect an ECH extension.
+  bool ran_callback = false;
+  SSLServerConfig server_config;
+  server_config.client_hello_callback_for_testing =
+      base::BindLambdaForTesting([&](const SSL_CLIENT_HELLO* client_hello) {
+        const uint8_t* data;
+        size_t len;
+        EXPECT_FALSE(SSL_early_callback_ctx_extension_get(
+            client_hello, TLSEXT_TYPE_encrypted_client_hello, &data, &len));
+        ran_callback = true;
+      });
+  ASSERT_TRUE(
+      StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, server_config));
+  int rv;
+  ASSERT_TRUE(CreateAndConnectSSLClientSocket(SSLConfig(), &rv));
+  EXPECT_THAT(rv, IsOk());
+  EXPECT_TRUE(ran_callback);
+}
+
 class TLS13DowngradeTest
     : public SSLClientSocketTest,
       public ::testing::WithParamInterface<
diff --git a/net/socket/ssl_server_socket_impl.cc b/net/socket/ssl_server_socket_impl.cc
index 4bed18d..fc9b766 100644
--- a/net/socket/ssl_server_socket_impl.cc
+++ b/net/socket/ssl_server_socket_impl.cc
@@ -9,7 +9,6 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
-#include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string_util.h"
@@ -43,27 +42,6 @@
 // overlap with any value of the net::Error range, including net::OK).
 const int kSSLServerSocketNoPendingResult = 1;
 
-class SocketDataIndex {
- public:
-  static SocketDataIndex* GetInstance();
-  SocketDataIndex() {
-    ssl_socket_data_index_ =
-        SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
-  }
-
-  // This is the index used with SSL_get_ex_data to retrieve the owner
-  // SSLServerSocketImpl object from an SSL instance.
-  int ssl_socket_data_index_;
-};
-
-base::LazyInstance<SocketDataIndex>::Leaky g_ssl_socket_data_index_ =
-    LAZY_INSTANCE_INITIALIZER;
-
-// static
-SocketDataIndex* SocketDataIndex::GetInstance() {
-  return g_ssl_socket_data_index_.Pointer();
-}
-
 }  // namespace
 
 class SSLServerContextImpl::SocketImpl : public SSLServerSocket,
@@ -121,6 +99,8 @@
   int64_t GetTotalReceivedBytes() const override;
   void ApplySocketTag(const SocketTag& tag) override;
 
+  static SocketImpl* FromSSL(SSL* ssl);
+
   static ssl_verify_result_t CertVerifyCallback(SSL* ssl, uint8_t* out_alert);
   ssl_verify_result_t CertVerifyCallbackImpl(uint8_t* out_alert);
 
@@ -161,6 +141,9 @@
                                 unsigned in_len,
                                 void* arg);
 
+  static ssl_select_cert_result_t SelectCertificateCallback(
+      const SSL_CLIENT_HELLO* client_hello);
+
   // SocketBIOAdapter::Delegate implementation.
   void OnReadReady() override;
   void OnWriteReady() override;
@@ -237,11 +220,7 @@
       transport_socket_(std::move(transport_socket)),
       next_handshake_state_(STATE_NONE),
       completed_handshake_(false),
-      negotiated_protocol_(kProtoUnknown) {
-  ssl_.reset(SSL_new(context_->ssl_ctx_.get()));
-  SSL_set_app_data(ssl_.get(), this);
-  SSL_set_shed_handshake_config(ssl_.get(), 1);
-}
+      negotiated_protocol_(kProtoUnknown) {}
 
 SSLServerContextImpl::SocketImpl::~SocketImpl() {
   if (ssl_) {
@@ -269,13 +248,8 @@
                                                          uint16_t algorithm,
                                                          const uint8_t* in,
                                                          size_t in_len) {
-  DCHECK(ssl);
-  SSLServerContextImpl::SocketImpl* socket =
-      static_cast<SSLServerContextImpl::SocketImpl*>(SSL_get_ex_data(
-          ssl, SocketDataIndex::GetInstance()->ssl_socket_data_index_));
-  DCHECK(socket);
-  return socket->PrivateKeySignCallback(out, out_len, max_out, algorithm, in,
-                                        in_len);
+  return FromSSL(ssl)->PrivateKeySignCallback(out, out_len, max_out, algorithm,
+                                              in, in_len);
 }
 
 // static
@@ -296,12 +270,7 @@
                                                              uint8_t* out,
                                                              size_t* out_len,
                                                              size_t max_out) {
-  DCHECK(ssl);
-  SSLServerContextImpl::SocketImpl* socket =
-      static_cast<SSLServerContextImpl::SocketImpl*>(SSL_get_ex_data(
-          ssl, SocketDataIndex::GetInstance()->ssl_socket_data_index_));
-  DCHECK(socket);
-  return socket->PrivateKeyCompleteCallback(out, out_len, max_out);
+  return FromSSL(ssl)->PrivateKeyCompleteCallback(out, out_len, max_out);
 }
 
 ssl_private_key_result_t
@@ -360,9 +329,7 @@
                                                          const uint8_t* in,
                                                          unsigned in_len,
                                                          void* arg) {
-  SSLServerContextImpl::SocketImpl* socket =
-      static_cast<SSLServerContextImpl::SocketImpl*>(SSL_get_ex_data(
-          ssl, SocketDataIndex::GetInstance()->ssl_socket_data_index_));
+  SSLServerContextImpl::SocketImpl* socket = FromSSL(ssl);
 
   // Iterate over the server protocols in preference order.
   for (NextProto server_proto :
@@ -399,6 +366,17 @@
   return SSL_TLSEXT_ERR_NOACK;
 }
 
+ssl_select_cert_result_t
+SSLServerContextImpl::SocketImpl::SelectCertificateCallback(
+    const SSL_CLIENT_HELLO* client_hello) {
+  SSLServerContextImpl::SocketImpl* socket = FromSSL(client_hello->ssl);
+  const SSLServerConfig& config = socket->context_->ssl_server_config_;
+  if (!config.client_hello_callback_for_testing.is_null()) {
+    config.client_hello_callback_for_testing.Run(client_hello);
+  }
+  return ssl_select_cert_success;
+}
+
 int SSLServerContextImpl::SocketImpl::Handshake(
     CompletionOnceCallback callback) {
   net_log_.BeginEvent(NetLogEventType::SSL_SERVER_HANDSHAKE);
@@ -828,11 +806,12 @@
 
   crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
 
-  if (!ssl_ ||
-      !SSL_set_ex_data(ssl_.get(),
-                       SocketDataIndex::GetInstance()->ssl_socket_data_index_,
-                       this))
+  ssl_.reset(SSL_new(context_->ssl_ctx_.get()));
+  if (!ssl_ || !SSL_set_app_data(ssl_.get(), this)) {
     return ERR_UNEXPECTED;
+  }
+
+  SSL_set_shed_handshake_config(ssl_.get(), 1);
 
   // Set certificate and private key.
   if (context_->pkey_) {
@@ -878,12 +857,18 @@
   return OK;
 }
 
+SSLServerContextImpl::SocketImpl* SSLServerContextImpl::SocketImpl::FromSSL(
+    SSL* ssl) {
+  SocketImpl* socket = reinterpret_cast<SocketImpl*>(SSL_get_app_data(ssl));
+  DCHECK(socket);
+  return socket;
+}
+
 // static
 ssl_verify_result_t SSLServerContextImpl::SocketImpl::CertVerifyCallback(
     SSL* ssl,
     uint8_t* out_alert) {
-  SocketImpl* socket = reinterpret_cast<SocketImpl*>(SSL_get_app_data(ssl));
-  return socket->CertVerifyCallbackImpl(out_alert);
+  return FromSSL(ssl)->CertVerifyCallbackImpl(out_alert);
 }
 
 ssl_verify_result_t SSLServerContextImpl::SocketImpl::CertVerifyCallbackImpl(
@@ -1068,6 +1053,9 @@
     CHECK(SSL_CTX_set1_ech_keys(ssl_ctx_.get(),
                                 ssl_server_config_.ech_keys.get()));
   }
+
+  SSL_CTX_set_select_certificate_cb(ssl_ctx_.get(),
+                                    &SocketImpl::SelectCertificateCallback);
 }
 
 SSLServerContextImpl::~SSLServerContextImpl() = default;
diff --git a/net/ssl/ssl_server_config.h b/net/ssl/ssl_server_config.h
index ae5da3e..6013422 100644
--- a/net/ssl/ssl_server_config.h
+++ b/net/ssl/ssl_server_config.h
@@ -10,6 +10,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/containers/flat_map.h"
 #include "net/base/net_export.h"
 #include "net/socket/next_proto.h"
@@ -118,6 +119,10 @@
   // handshake.
   std::vector<uint8_t> signed_cert_timestamp_list;
 
+  // If specified, called at the start of each connection with the ClientHello.
+  base::RepeatingCallback<void(const SSL_CLIENT_HELLO*)>
+      client_hello_callback_for_testing;
+
   // This is a workaround for BoringSSL's scopers not being copyable. See
   // https://crbug.com/boringssl/431.
   class NET_EXPORT ECHKeysContainer {
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index dac7ada..e3fa3f3 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -465,7 +465,6 @@
 
   if (is_linux && !is_chromeos_lacros) {
     sources += [
-      "desktop_resizer_linux.cc",
       "linux/audio_pipe_reader.cc",
       "linux/audio_pipe_reader.h",
       "linux/certificate_watcher.cc",
@@ -481,7 +480,7 @@
     }
   }
 
-  if (use_x11 && ((!is_chromeos_ash && !is_chromeos_lacros) || !use_ozone)) {
+  if (use_x11 && !is_chromeos_ash && !is_chromeos_lacros) {
     sources += [
       "desktop_display_info_loader_x11.cc",
       "desktop_resizer_x11.cc",
@@ -498,11 +497,11 @@
     }
   }
 
-  if (use_ozone && !is_chromeos_ash && !is_chromeos_lacros) {
-    sources += [
-      "desktop_resizer_ozone.cc",
-      "desktop_resizer_ozone.h",
-    ]
+  if (is_linux && (!use_x11 || is_chromeos_ash || is_chromeos_lacros)) {
+    # On Linux, exactly one of desktop_resizer_{linux,x11}.cc should be
+    # compiled in. The _linux version is a no-op and should be used on
+    # platforms without X11.
+    sources += [ "desktop_resizer_linux.cc" ]
   }
 
   if (is_linux && !is_chromeos_lacros) {
diff --git a/remoting/host/desktop_resizer_linux.cc b/remoting/host/desktop_resizer_linux.cc
index d00697c..dd029d0 100644
--- a/remoting/host/desktop_resizer_linux.cc
+++ b/remoting/host/desktop_resizer_linux.cc
@@ -8,28 +8,44 @@
 
 #include "base/notreached.h"
 
-#if defined(USE_X11)
-#include "remoting/host/desktop_resizer_x11.h"
-#endif
-
-#if defined(USE_OZONE)
-#include "remoting/host/desktop_resizer_ozone.h"
-#include "ui/base/ui_base_features.h"
-#endif
-
 namespace remoting {
 
+namespace {
+
+// DesktopResizer implementation for Linux platforms where
+// X11 is not enabled.
+class DesktopResizerLinux : public DesktopResizer {
+ public:
+  DesktopResizerLinux() = default;
+  DesktopResizerLinux(const DesktopResizerLinux&) = delete;
+  DesktopResizerLinux& operator=(const DesktopResizerLinux&) = delete;
+  ~DesktopResizerLinux() override = default;
+
+  ScreenResolution GetCurrentResolution() override {
+    NOTIMPLEMENTED();
+    return ScreenResolution();
+  }
+
+  std::list<ScreenResolution> GetSupportedResolutions(
+      const ScreenResolution& preferred) override {
+    NOTIMPLEMENTED();
+    return std::list<ScreenResolution>();
+  }
+
+  void SetResolution(const ScreenResolution& resolution) override {
+    NOTIMPLEMENTED();
+  }
+
+  void RestoreResolution(const ScreenResolution& original) override {
+    NOTIMPLEMENTED();
+  }
+};
+
+}  // namespace
+
+// static
 std::unique_ptr<DesktopResizer> DesktopResizer::Create() {
-#if defined(USE_OZONE)
-  if (features::IsUsingOzonePlatform())
-    return std::make_unique<DesktopResizerOzone>();
-#endif
-#if defined(USE_X11)
-  return std::make_unique<DesktopResizerX11>();
-#else
-  NOTREACHED();
-  return nullptr;
-#endif
+  return std::make_unique<DesktopResizerLinux>();
 }
 
 }  // namespace remoting
diff --git a/remoting/host/desktop_resizer_ozone.cc b/remoting/host/desktop_resizer_ozone.cc
deleted file mode 100644
index 35a5133..0000000
--- a/remoting/host/desktop_resizer_ozone.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "remoting/host/desktop_resizer_ozone.h"
-
-#include "build/build_config.h"
-
-namespace remoting {
-
-DesktopResizerOzone::DesktopResizerOzone() = default;
-
-DesktopResizerOzone::~DesktopResizerOzone() = default;
-
-ScreenResolution DesktopResizerOzone::GetCurrentResolution() {
-  NOTIMPLEMENTED();
-  return ScreenResolution();
-}
-
-std::list<ScreenResolution> DesktopResizerOzone::GetSupportedResolutions(
-    const ScreenResolution& preferred) {
-  NOTIMPLEMENTED();
-  return std::list<ScreenResolution>();
-}
-
-void DesktopResizerOzone::SetResolution(const ScreenResolution& resolution) {
-  NOTIMPLEMENTED();
-}
-
-void DesktopResizerOzone::RestoreResolution(const ScreenResolution& original) {}
-
-// To avoid multiple definitions when use_x11 && use_ozone is true, disable this
-// factory method for OS_LINUX as Linux has a factory method that decides what
-// desktopresizer to use based on IsUsingOzonePlatform feature flag.
-#if !defined(OS_LINUX) && !defined(OS_CHROMEOS)
-std::unique_ptr<DesktopResizer> DesktopResizer::Create() {
-  return base::WrapUnique(new DesktopResizerOzone);
-}
-#endif
-
-}  // namespace remoting
diff --git a/remoting/host/desktop_resizer_ozone.h b/remoting/host/desktop_resizer_ozone.h
deleted file mode 100644
index 47e1e77..0000000
--- a/remoting/host/desktop_resizer_ozone.h
+++ /dev/null
@@ -1,32 +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 REMOTING_HOST_DESKTOP_RESIZER_OZONE_H_
-#define REMOTING_HOST_DESKTOP_RESIZER_OZONE_H_
-
-#include "base/macros.h"
-#include "base/memory/ptr_util.h"
-#include "base/notreached.h"
-#include "remoting/host/desktop_resizer.h"
-
-namespace remoting {
-
-class DesktopResizerOzone : public DesktopResizer {
- public:
-  DesktopResizerOzone();
-  DesktopResizerOzone(const DesktopResizerOzone&) = delete;
-  DesktopResizerOzone& operator=(const DesktopResizerOzone&) = delete;
-  ~DesktopResizerOzone() override;
-
-  // DesktopResizer:
-  ScreenResolution GetCurrentResolution() override;
-  std::list<ScreenResolution> GetSupportedResolutions(
-      const ScreenResolution& preferred) override;
-  void SetResolution(const ScreenResolution& resolution) override;
-  void RestoreResolution(const ScreenResolution& original) override;
-};
-
-}  // namespace remoting
-
-#endif  // REMOTING_HOST_DESKTOP_RESIZER_OZONE_H_
diff --git a/remoting/host/desktop_resizer_x11.cc b/remoting/host/desktop_resizer_x11.cc
index a1c4df5..85e9e1d 100644
--- a/remoting/host/desktop_resizer_x11.cc
+++ b/remoting/host/desktop_resizer_x11.cc
@@ -4,7 +4,8 @@
 
 #include "remoting/host/desktop_resizer_x11.h"
 
-#include <string.h>
+#include <memory>
+#include <string>
 
 #include "base/command_line.h"
 #include "base/cxx17_backports.h"
@@ -300,4 +301,9 @@
   });
 }
 
+// static
+std::unique_ptr<DesktopResizer> DesktopResizer::Create() {
+  return std::make_unique<DesktopResizerX11>();
+}
+
 }  // namespace remoting
diff --git a/storage/browser/quota/quota_callbacks.h b/storage/browser/quota/quota_callbacks.h
index 36013d11..d64ab3d 100644
--- a/storage/browser/quota/quota_callbacks.h
+++ b/storage/browser/quota/quota_callbacks.h
@@ -15,7 +15,7 @@
 
 #include "base/callback.h"
 #include "base/containers/contains.h"
-#include "components/services/storage/public/cpp/buckets/bucket_info.h"
+#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom-forward.h"
 
@@ -43,13 +43,13 @@
     base::OnceCallback<void(blink::mojom::QuotaStatusCode, int64_t)>;
 using StatusCallback = base::OnceCallback<void(blink::mojom::QuotaStatusCode)>;
 using GetBucketsCallback =
-    base::OnceCallback<void(const std::set<BucketInfo>& buckets,
+    base::OnceCallback<void(const std::set<BucketLocator>& buckets,
                             blink::mojom::StorageType type)>;
 using GetStorageKeysCallback =
     base::OnceCallback<void(const std::set<blink::StorageKey>& storage_keys)>;
 using GetUsageInfoCallback = base::OnceCallback<void(UsageInfoEntries)>;
 using GetBucketCallback =
-    base::OnceCallback<void(const absl::optional<BucketInfo>& bucket_info)>;
+    base::OnceCallback<void(const absl::optional<BucketLocator>& bucket_info)>;
 
 // Simple template wrapper for a callback queue.
 template <typename CallbackType, typename... Args>
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc
index 328c009..0c541155 100644
--- a/storage/browser/quota/quota_database.cc
+++ b/storage/browser/quota/quota_database.cc
@@ -574,7 +574,7 @@
   return true;
 }
 
-QuotaErrorOr<BucketInfo> QuotaDatabase::GetLRUBucket(
+QuotaErrorOr<BucketLocator> QuotaDatabase::GetLRUBucket(
     StorageType type,
     const std::set<BucketId>& bucket_exceptions,
     SpecialStoragePolicy* special_storage_policy) {
@@ -585,8 +585,7 @@
 
   // clang-format off
   static constexpr char kSql[] =
-      "SELECT id, storage_key, name, expiration, quota "
-        "FROM buckets "
+      "SELECT id, storage_key, name FROM buckets "
         "WHERE type = ? "
         "ORDER BY last_accessed";
   // clang-format on
@@ -612,9 +611,8 @@
          special_storage_policy->IsStorageUnlimited(read_gurl))) {
       continue;
     }
-    return BucketInfo(read_bucket_id, std::move(read_storage_key).value(), type,
-                      statement.ColumnString(2), statement.ColumnTime(3),
-                      statement.ColumnInt(4));
+    return BucketLocator(read_bucket_id, std::move(read_storage_key).value(),
+                         type, statement.ColumnString(2) == kDefaultBucketName);
   }
   return QuotaError::kNotFound;
 }
@@ -643,7 +641,7 @@
   return storage_keys;
 }
 
-QuotaErrorOr<std::set<BucketInfo>> QuotaDatabase::GetBucketsModifiedBetween(
+QuotaErrorOr<std::set<BucketLocator>> QuotaDatabase::GetBucketsModifiedBetween(
     StorageType type,
     base::Time begin,
     base::Time end) {
@@ -656,7 +654,7 @@
   DCHECK(end != base::Time());
   // clang-format off
   static constexpr char kSql[] =
-      "SELECT id, storage_key, name, expiration, quota FROM buckets "
+      "SELECT id, storage_key, name FROM buckets "
         "WHERE type = ? AND last_modified >= ? AND last_modified < ?";
   // clang-format on
 
@@ -665,15 +663,15 @@
   statement.BindTime(1, begin);
   statement.BindTime(2, end);
 
-  std::set<BucketInfo> buckets;
+  std::set<BucketLocator> buckets;
   while (statement.Step()) {
     absl::optional<StorageKey> read_storage_key =
         StorageKey::Deserialize(statement.ColumnString(1));
     if (!read_storage_key.has_value())
       continue;
     buckets.emplace(BucketId(statement.ColumnInt64(0)),
-                    read_storage_key.value(), type, statement.ColumnString(2),
-                    statement.ColumnTime(3), statement.ColumnInt(4));
+                    read_storage_key.value(), type,
+                    statement.ColumnString(2) == kDefaultBucketName);
   }
   return buckets;
 }
diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h
index 5864a9a..b825975 100644
--- a/storage/browser/quota/quota_database.h
+++ b/storage/browser/quota/quota_database.h
@@ -188,10 +188,10 @@
   // Deletes the specified bucket.
   bool DeleteBucketInfo(BucketId bucket_id);
 
-  // Returns the BucketInfo for the least recently used bucket. Will exclude
+  // Returns the BucketLocator for the least recently used bucket. Will exclude
   // buckets with ids in `bucket_exceptions` and origins that have the special
   // unlimited storage policy. Returns a QuotaError if the operation has failed.
-  QuotaErrorOr<BucketInfo> GetLRUBucket(
+  QuotaErrorOr<BucketLocator> GetLRUBucket(
       blink::mojom::StorageType type,
       const std::set<BucketId>& bucket_exceptions,
       SpecialStoragePolicy* special_storage_policy);
@@ -202,7 +202,7 @@
 
   // Returns a set of buckets that have been modified since the `begin` and
   // until the `end`. Returns a QuotaError if the operations has failed.
-  QuotaErrorOr<std::set<BucketInfo>> GetBucketsModifiedBetween(
+  QuotaErrorOr<std::set<BucketLocator>> GetBucketsModifiedBetween(
       blink::mojom::StorageType type,
       base::Time begin,
       base::Time end);
diff --git a/storage/browser/quota/quota_database_unittest.cc b/storage/browser/quota/quota_database_unittest.cc
index 0a82637c..54d9a64 100644
--- a/storage/browser/quota/quota_database_unittest.cc
+++ b/storage/browser/quota/quota_database_unittest.cc
@@ -503,7 +503,7 @@
   EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound));
 
   std::set<BucketId> bucket_exceptions;
-  QuotaErrorOr<BucketInfo> result =
+  QuotaErrorOr<BucketLocator> result =
       db.GetLRUBucket(kTemp, bucket_exceptions, nullptr);
   EXPECT_FALSE(result.ok());
   EXPECT_EQ(result.error(), QuotaError::kNotFound);
@@ -654,10 +654,10 @@
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
   EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound));
 
-  QuotaErrorOr<std::set<BucketInfo>> result =
+  QuotaErrorOr<std::set<BucketLocator>> result =
       db.GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max());
   EXPECT_TRUE(result.ok());
-  std::set<BucketInfo> buckets = result.value();
+  std::set<BucketLocator> buckets = result.value();
   EXPECT_TRUE(buckets.empty());
 
   QuotaErrorOr<BucketInfo> result1 = db.CreateBucketForTesting(
@@ -699,30 +699,30 @@
   EXPECT_TRUE(result.ok());
   buckets = result.value();
   EXPECT_EQ(3U, buckets.size());
-  EXPECT_EQ(1U, buckets.count(bucket1));
-  EXPECT_EQ(1U, buckets.count(bucket2));
-  EXPECT_EQ(1U, buckets.count(bucket3));
-  EXPECT_EQ(0U, buckets.count(bucket4));
+  EXPECT_TRUE(ContainsBucket(buckets, bucket1));
+  EXPECT_TRUE(ContainsBucket(buckets, bucket2));
+  EXPECT_TRUE(ContainsBucket(buckets, bucket3));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket4));
 
   result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5),
                                         base::Time::Max());
   EXPECT_TRUE(result.ok());
   buckets = result.value();
   EXPECT_EQ(2U, buckets.size());
-  EXPECT_EQ(0U, buckets.count(bucket1));
-  EXPECT_EQ(1U, buckets.count(bucket2));
-  EXPECT_EQ(1U, buckets.count(bucket3));
-  EXPECT_EQ(0U, buckets.count(bucket4));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket1));
+  EXPECT_TRUE(ContainsBucket(buckets, bucket2));
+  EXPECT_TRUE(ContainsBucket(buckets, bucket3));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket4));
 
   result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(15),
                                         base::Time::Max());
   EXPECT_TRUE(result.ok());
   buckets = result.value();
   EXPECT_EQ(1U, buckets.size());
-  EXPECT_EQ(0U, buckets.count(bucket1));
-  EXPECT_EQ(0U, buckets.count(bucket2));
-  EXPECT_EQ(1U, buckets.count(bucket3));
-  EXPECT_EQ(0U, buckets.count(bucket4));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket1));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket2));
+  EXPECT_TRUE(ContainsBucket(buckets, bucket3));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket4));
 
   result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(25),
                                         base::Time::Max());
@@ -735,30 +735,30 @@
   EXPECT_TRUE(result.ok());
   buckets = result.value();
   EXPECT_EQ(1U, buckets.size());
-  EXPECT_EQ(0U, buckets.count(bucket1));
-  EXPECT_EQ(1U, buckets.count(bucket2));
-  EXPECT_EQ(0U, buckets.count(bucket3));
-  EXPECT_EQ(0U, buckets.count(bucket4));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket1));
+  EXPECT_TRUE(ContainsBucket(buckets, bucket2));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket3));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket4));
 
   result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(0),
                                         base::Time::FromJavaTime(20));
   EXPECT_TRUE(result.ok());
   buckets = result.value();
   EXPECT_EQ(2U, buckets.size());
-  EXPECT_EQ(1U, buckets.count(bucket1));
-  EXPECT_EQ(1U, buckets.count(bucket2));
-  EXPECT_EQ(0U, buckets.count(bucket3));
-  EXPECT_EQ(0U, buckets.count(bucket4));
+  EXPECT_TRUE(ContainsBucket(buckets, bucket1));
+  EXPECT_TRUE(ContainsBucket(buckets, bucket2));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket3));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket4));
 
   result = db.GetBucketsModifiedBetween(kPerm, base::Time::FromJavaTime(0),
                                         base::Time::FromJavaTime(35));
   EXPECT_TRUE(result.ok());
   buckets = result.value();
   EXPECT_EQ(1U, buckets.size());
-  EXPECT_EQ(0U, buckets.count(bucket1));
-  EXPECT_EQ(0U, buckets.count(bucket2));
-  EXPECT_EQ(0U, buckets.count(bucket3));
-  EXPECT_EQ(1U, buckets.count(bucket4));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket1));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket2));
+  EXPECT_FALSE(ContainsBucket(buckets, bucket3));
+  EXPECT_TRUE(ContainsBucket(buckets, bucket4));
 }
 
 TEST_P(QuotaDatabaseTest, RegisterInitialStorageKeyInfo) {
diff --git a/storage/browser/quota/quota_manager_impl.cc b/storage/browser/quota/quota_manager_impl.cc
index 878b150..1bd78d05 100644
--- a/storage/browser/quota/quota_manager_impl.cc
+++ b/storage/browser/quota/quota_manager_impl.cc
@@ -151,7 +151,7 @@
   return database->GetBucketsForStorageKey(storage_key, type);
 }
 
-QuotaErrorOr<std::set<BucketInfo>> GetModifiedBetweenOnDBThread(
+QuotaErrorOr<std::set<BucketLocator>> GetModifiedBetweenOnDBThread(
     StorageType type,
     base::Time begin,
     base::Time end,
@@ -178,7 +178,7 @@
   return false;
 }
 
-QuotaErrorOr<BucketInfo> GetLRUBucketOnDBThread(
+QuotaErrorOr<BucketLocator> GetLRUBucketOnDBThread(
     StorageType type,
     const std::set<BucketId>& bucket_exceptions,
     SpecialStoragePolicy* policy,
@@ -740,7 +740,7 @@
 class QuotaManagerImpl::BucketDataDeleter : public QuotaTask {
  public:
   BucketDataDeleter(QuotaManagerImpl* manager,
-                    const BucketInfo& bucket,
+                    const BucketLocator& bucket,
                     QuotaClientTypes quota_client_types,
                     bool is_eviction,
                     StatusCallback callback)
@@ -758,7 +758,7 @@
     // they are not being tracked yet.
     // TODO(crbug.com/1199417): Update to call for all buckets once QuotaClient
     // is migrated to operate on buckets.
-    if (!bucket_.is_default()) {
+    if (!bucket_.is_default) {
       CallCompleted();
       return;
     }
@@ -772,7 +772,8 @@
         static int tracing_id = 0;
         std::ostringstream bucket_params;
         bucket_params << "storage_key: " << bucket_.storage_key.Serialize()
-                      << ", name: " << bucket_.name << ", id: " << bucket_.id;
+                      << ", is_default: " << bucket_.is_default
+                      << ", id: " << bucket_.id;
         TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(
             "browsing_data", "QuotaManagerImpl::BucketDataDeleter",
             ++tracing_id, "client_type", client_type, "bucket",
@@ -827,7 +828,7 @@
     return static_cast<QuotaManagerImpl*>(observer());
   }
 
-  const BucketInfo bucket_;
+  const BucketLocator bucket_;
   const QuotaClientTypes quota_client_types_;
   int error_count_ = 0;
   size_t remaining_clients_ = 0;
@@ -1317,7 +1318,7 @@
   GetUsageTracker(type)->SetUsageCacheEnabled(client_id, storage_key, enabled);
 }
 
-void QuotaManagerImpl::DeleteBucketData(const BucketInfo& bucket,
+void QuotaManagerImpl::DeleteBucketData(const BucketLocator& bucket,
                                         QuotaClientTypes quota_client_types,
                                         StatusCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -1796,7 +1797,7 @@
 }
 
 void QuotaManagerImpl::DeleteBucketDataInternal(
-    const BucketInfo& bucket,
+    const BucketLocator& bucket,
     QuotaClientTypes quota_client_types,
     bool is_eviction,
     StatusCallback callback) {
@@ -2011,7 +2012,7 @@
 
 void QuotaManagerImpl::DidGetEvictionBucket(
     GetBucketCallback callback,
-    const absl::optional<BucketInfo>& bucket) {
+    const absl::optional<BucketLocator>& bucket) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // Make sure the returned bucket has not been accessed since we posted the
   // eviction task.
@@ -2020,7 +2021,7 @@
   // TODO(crbug.com/1208141): Remove this evaluation for storage key once
   // QuotaClient is migrated to operate on buckets and NotifyStorageAccessed
   // no longer used.
-  if (bucket.has_value() && bucket->is_default() &&
+  if (bucket.has_value() && bucket->is_default &&
       base::Contains(access_notified_storage_keys_, bucket->storage_key)) {
     std::move(callback).Run(absl::nullopt);
   } else if (bucket.has_value() &&
@@ -2061,7 +2062,7 @@
   GetLRUBucket(type, std::move(did_get_bucket_callback));
 }
 
-void QuotaManagerImpl::EvictBucketData(const BucketInfo& bucket,
+void QuotaManagerImpl::EvictBucketData(const BucketLocator& bucket,
                                        StatusCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_thread_->BelongsToCurrentThread());
@@ -2137,7 +2138,7 @@
       *new_quota);
 }
 
-void QuotaManagerImpl::DidGetLRUBucket(QuotaErrorOr<BucketInfo> result) {
+void QuotaManagerImpl::DidGetLRUBucket(QuotaErrorOr<BucketLocator> result) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DidDatabaseWork(result.ok() || result.error() != QuotaError::kDatabaseError);
 
@@ -2261,7 +2262,9 @@
     return;
   }
 
-  DeleteBucketDataInternal(result.value(), AllQuotaClientTypes(),
+  BucketLocator bucket(result->id, result->storage_key, result->type,
+                       result->name == kDefaultBucketName);
+  DeleteBucketDataInternal(bucket, AllQuotaClientTypes(),
                            /*is_eviction=*/false, std::move(callback));
   return;
 }
@@ -2289,11 +2292,11 @@
 void QuotaManagerImpl::DidGetModifiedBetween(
     GetBucketsCallback callback,
     StorageType type,
-    QuotaErrorOr<std::set<BucketInfo>> result) {
+    QuotaErrorOr<std::set<BucketLocator>> result) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DidDatabaseWork(result.ok() || result.error() != QuotaError::kDatabaseError);
   if (!result.ok()) {
-    std::move(callback).Run(std::set<BucketInfo>(), type);
+    std::move(callback).Run(std::set<BucketLocator>(), type);
     return;
   }
   std::move(callback).Run(result.value(), type);
diff --git a/storage/browser/quota/quota_manager_impl.h b/storage/browser/quota/quota_manager_impl.h
index eab17be..6a3988d 100644
--- a/storage/browser/quota/quota_manager_impl.h
+++ b/storage/browser/quota/quota_manager_impl.h
@@ -85,7 +85,7 @@
                                  GetBucketCallback callback) = 0;
 
   // Called to evict a bucket.
-  virtual void EvictBucketData(const BucketInfo& bucket,
+  virtual void EvictBucketData(const BucketLocator& bucket,
                                StatusCallback callback) = 0;
 
  protected:
@@ -318,7 +318,7 @@
   // the types of QuotaClients to delete from the storage key.
   // Pass in QuotaClientType::AllClients() to remove all clients from the
   // storage key, regardless of type.
-  virtual void DeleteBucketData(const BucketInfo& bucket,
+  virtual void DeleteBucketData(const BucketLocator& bucket,
                                 QuotaClientTypes quota_client_types,
                                 StatusCallback callback);
   void DeleteHostData(const std::string& host,
@@ -468,7 +468,7 @@
   struct EvictionContext {
     EvictionContext();
     ~EvictionContext();
-    BucketInfo evicted_bucket;
+    BucketLocator evicted_bucket;
     StatusCallback evict_bucket_data_callback;
   };
 
@@ -505,7 +505,7 @@
   // Runs BucketDataDeleter which calls QuotaClients to clear data for the
   // bucket. Once the task is complete, calls the QuotaDatabase to delete the
   // bucket from the bucket table.
-  void DeleteBucketDataInternal(const BucketInfo& bucket,
+  void DeleteBucketDataInternal(const BucketLocator& bucket,
                                 QuotaClientTypes quota_client_types,
                                 bool is_eviction,
                                 StatusCallback callback);
@@ -532,13 +532,13 @@
   // consistent errors after multiple attempts.
   std::set<BucketId> GetEvictionBucketExceptions();
   void DidGetEvictionBucket(GetBucketCallback callback,
-                            const absl::optional<BucketInfo>& bucket);
+                            const absl::optional<BucketLocator>& bucket);
 
   // QuotaEvictionHandler.
   void GetEvictionBucket(blink::mojom::StorageType type,
                          int64_t global_quota,
                          GetBucketCallback callback) override;
-  void EvictBucketData(const BucketInfo& bucket,
+  void EvictBucketData(const BucketLocator& bucket,
                        StatusCallback callback) override;
   void GetEvictionRoundInfo(EvictionRoundInfoCallback callback) override;
 
@@ -553,7 +553,7 @@
                                  QuotaCallback callback,
                                  const int64_t* new_quota,
                                  bool success);
-  void DidGetLRUBucket(QuotaErrorOr<BucketInfo> result);
+  void DidGetLRUBucket(QuotaErrorOr<BucketLocator> result);
   void GetQuotaSettings(QuotaSettingsCallback callback);
   void DidGetSettings(absl::optional<QuotaSettings> settings);
   void GetStorageCapacity(StorageCapacityCallback callback);
@@ -574,7 +574,7 @@
       QuotaErrorOr<std::set<BucketLocator>> result);
   void DidGetModifiedBetween(GetBucketsCallback callback,
                              blink::mojom::StorageType type,
-                             QuotaErrorOr<std::set<BucketInfo>> result);
+                             QuotaErrorOr<std::set<BucketLocator>> result);
 
   void DeleteOnCorrectThread() const;
 
diff --git a/storage/browser/quota/quota_manager_unittest.cc b/storage/browser/quota/quota_manager_unittest.cc
index e9be6133..c135853 100644
--- a/storage/browser/quota/quota_manager_unittest.cc
+++ b/storage/browser/quota/quota_manager_unittest.cc
@@ -86,6 +86,11 @@
   return StorageKey::CreateFromStringForTesting(url);
 }
 
+BucketLocator ToBucketLocator(const BucketInfo& bucket) {
+  return BucketLocator(bucket.id, bucket.storage_key, bucket.type,
+                       bucket.name == kDefaultBucketName);
+}
+
 MATCHER_P3(MatchesBucketTableEntry, storage_key, type, use_count, "") {
   return testing::ExplainMatchResult(storage_key, arg.storage_key,
                                      result_listener) &&
@@ -374,14 +379,14 @@
                        weak_factory_.GetWeakPtr()));
   }
 
-  void EvictBucketData(const BucketInfo& bucket) {
+  void EvictBucketData(const BucketLocator& bucket) {
     quota_status_ = QuotaStatusCode::kUnknown;
     quota_manager_impl_->EvictBucketData(
         bucket, base::BindOnce(&QuotaManagerImplTest::StatusCallback,
                                weak_factory_.GetWeakPtr()));
   }
 
-  void DeleteBucketData(const BucketInfo& bucket,
+  void DeleteBucketData(const BucketLocator& bucket,
                         QuotaClientTypes quota_client_types) {
     quota_status_ = QuotaStatusCode::kUnknown;
     quota_manager_impl_->DeleteBucketData(
@@ -582,14 +587,14 @@
     usage_ = global_usage;
   }
 
-  void DidGetEvictionBucket(const absl::optional<BucketInfo>& bucket) {
+  void DidGetEvictionBucket(const absl::optional<BucketLocator>& bucket) {
     eviction_bucket_ = bucket;
     DCHECK(!bucket.has_value() ||
            !bucket->storage_key.origin().GetURL().is_empty());
   }
 
   void DidGetModifiedBuckets(base::OnceClosure quit_closure,
-                             const std::set<BucketInfo>& buckets,
+                             const std::set<BucketLocator>& buckets,
                              StorageType type) {
     modified_buckets_ = buckets;
     modified_buckets_type_ = type;
@@ -663,10 +668,10 @@
   int64_t quota() const { return quota_; }
   int64_t total_space() const { return total_space_; }
   int64_t available_space() const { return available_space_; }
-  const absl::optional<BucketInfo>& eviction_bucket() const {
+  const absl::optional<BucketLocator>& eviction_bucket() const {
     return eviction_bucket_;
   }
-  const std::set<BucketInfo>& modified_buckets() const {
+  const std::set<BucketLocator>& modified_buckets() const {
     return modified_buckets_;
   }
   StorageType modified_buckets_type() const { return modified_buckets_type_; }
@@ -701,8 +706,8 @@
   int64_t quota_;
   int64_t total_space_;
   int64_t available_space_;
-  absl::optional<BucketInfo> eviction_bucket_;
-  std::set<BucketInfo> modified_buckets_;
+  absl::optional<BucketLocator> eviction_bucket_;
+  std::set<BucketLocator> modified_buckets_;
   StorageType modified_buckets_type_;
   QuotaTableEntries quota_entries_;
   BucketTableEntries bucket_entries_;
@@ -1802,7 +1807,7 @@
   GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kTemp);
   ASSERT_TRUE(bucket_.ok());
 
-  EvictBucketData(bucket_.value());
+  EvictBucketData(ToBucketLocator(bucket_.value()));
   task_environment_.RunUntilIdle();
 
   DumpBucketTable();
@@ -1844,7 +1849,7 @@
   ASSERT_TRUE(bucket_.ok());
   BucketInfo created_bucket = bucket_.value();
 
-  EvictBucketData(created_bucket);
+  EvictBucketData(ToBucketLocator(created_bucket));
   task_environment_.RunUntilIdle();
 
   EXPECT_EQ(QuotaStatusCode::kOk, status());
@@ -1868,7 +1873,7 @@
   ASSERT_TRUE(bucket_.ok());
   BucketInfo default_bucket = bucket_.value();
 
-  EvictBucketData(default_bucket);
+  EvictBucketData(ToBucketLocator(default_bucket));
   task_environment_.RunUntilIdle();
 
   EXPECT_EQ(QuotaStatusCode::kOk, status());
@@ -1897,7 +1902,7 @@
   CreateBucketForTesting(kStorageKey, kDefaultBucketName, kTemp);
   ASSERT_TRUE(bucket_.ok());
 
-  EvictBucketData(bucket_.value());
+  EvictBucketData(ToBucketLocator(bucket_.value()));
   task_environment_.RunUntilIdle();
 
   // Ensure use count and time since access are recorded.
@@ -1917,7 +1922,7 @@
 
   GetGlobalUsage(kTemp);
 
-  EvictBucketData(bucket_.value());
+  EvictBucketData(ToBucketLocator(bucket_.value()));
   task_environment_.RunUntilIdle();
 
   // The new use count should be logged.
@@ -1962,7 +1967,7 @@
 
   for (int i = 0; i < QuotaManagerImpl::kThresholdOfErrorsToBeDenylisted + 1;
        ++i) {
-    EvictBucketData(bucket_.value());
+    EvictBucketData(ToBucketLocator(bucket_.value()));
     task_environment_.RunUntilIdle();
     EXPECT_EQ(QuotaStatusCode::kErrorInvalidModification, status());
   }
@@ -2245,7 +2250,7 @@
                          kTemp);
   ASSERT_TRUE(bucket_.ok());
 
-  DeleteBucketData(bucket_.value(), AllQuotaClientTypes());
+  DeleteBucketData(ToBucketLocator(bucket_.value()), AllQuotaClientTypes());
   task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
 }
@@ -2305,9 +2310,9 @@
   task_environment_.RunUntilIdle();
 
   reset_status_callback_count();
-  DeleteBucketData(foo_temp_bucket, AllQuotaClientTypes());
-  DeleteBucketData(bar_temp_bucket, AllQuotaClientTypes());
-  DeleteBucketData(foo_temp_bucket, AllQuotaClientTypes());
+  DeleteBucketData(ToBucketLocator(foo_temp_bucket), AllQuotaClientTypes());
+  DeleteBucketData(ToBucketLocator(bar_temp_bucket), AllQuotaClientTypes());
+  DeleteBucketData(ToBucketLocator(foo_temp_bucket), AllQuotaClientTypes());
   task_environment_.RunUntilIdle();
 
   EXPECT_EQ(3, status_callback_count());
@@ -2399,8 +2404,8 @@
   task_environment_.RunUntilIdle();
 
   reset_status_callback_count();
-  DeleteBucketData(foo_perm_bucket, AllQuotaClientTypes());
-  DeleteBucketData(bar_perm_bucket, AllQuotaClientTypes());
+  DeleteBucketData(ToBucketLocator(foo_perm_bucket), AllQuotaClientTypes());
+  DeleteBucketData(ToBucketLocator(bar_perm_bucket), AllQuotaClientTypes());
   task_environment_.RunUntilIdle();
 
   EXPECT_EQ(2, status_callback_count());
@@ -2609,13 +2614,13 @@
 
   GetEvictionBucket(kTemp);
   task_environment_.RunUntilIdle();
-  EXPECT_EQ(bucket_a, eviction_bucket());
+  EXPECT_EQ(ToBucketLocator(bucket_a), eviction_bucket());
 
   // Notify that the `bucket_a` is accessed.
   NotifyBucketAccessed(bucket_a.id);
   GetEvictionBucket(kTemp);
   task_environment_.RunUntilIdle();
-  EXPECT_EQ(bucket_b, eviction_bucket());
+  EXPECT_EQ(ToBucketLocator(bucket_b), eviction_bucket());
 
   // Notify that the `bucket_b` is accessed while GetEvictionBucket is running.
   GetEvictionBucket(kTemp);
@@ -2638,7 +2643,6 @@
                                blink::mojom::StorageType::kPersistent});
 
   GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max());
-  task_environment_.RunUntilIdle();
   EXPECT_TRUE(modified_buckets().empty());
   EXPECT_EQ(modified_buckets_type(), kTemp);
 
@@ -2652,35 +2656,31 @@
   base::Time time3 = client->IncrementMockTime();
 
   GetBucketsModifiedBetween(kTemp, time1, base::Time::Max());
-  task_environment_.RunUntilIdle();
   EXPECT_EQ(modified_buckets_type(), kTemp);
   EXPECT_THAT(modified_buckets(),
               testing::UnorderedElementsAre(
-                  testing::Field(&BucketInfo::storage_key,
+                  testing::Field(&BucketLocator::storage_key,
                                  ToStorageKey("http://a.com")),
-                  testing::Field(&BucketInfo::storage_key,
+                  testing::Field(&BucketLocator::storage_key,
                                  ToStorageKey("http://a.com:1")),
-                  testing::Field(&BucketInfo::storage_key,
+                  testing::Field(&BucketLocator::storage_key,
                                  ToStorageKey("https://a.com")),
-                  testing::Field(&BucketInfo::storage_key,
+                  testing::Field(&BucketLocator::storage_key,
                                  ToStorageKey("http://c.com"))));
 
   GetBucketsModifiedBetween(kTemp, time2, base::Time::Max());
-  task_environment_.RunUntilIdle();
   EXPECT_EQ(2U, modified_buckets().size());
 
   GetBucketsModifiedBetween(kTemp, time3, base::Time::Max());
-  task_environment_.RunUntilIdle();
   EXPECT_TRUE(modified_buckets().empty());
   EXPECT_EQ(modified_buckets_type(), kTemp);
 
   client->ModifyStorageKeyAndNotify(ToStorageKey("http://a.com/"), kTemp, 10);
 
   GetBucketsModifiedBetween(kTemp, time3, base::Time::Max());
-  task_environment_.RunUntilIdle();
   EXPECT_THAT(modified_buckets(),
               testing::UnorderedElementsAre(testing::Field(
-                  &BucketInfo::storage_key, ToStorageKey("http://a.com/"))));
+                  &BucketLocator::storage_key, ToStorageKey("http://a.com/"))));
   EXPECT_EQ(modified_buckets_type(), kTemp);
 }
 
@@ -2691,7 +2691,6 @@
   disable_quota_database(true);
 
   GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max());
-  task_environment_.RunUntilIdle();
 
   // Return empty set when error is encountered.
   EXPECT_TRUE(modified_buckets().empty());
@@ -2777,22 +2776,24 @@
   GetHostUsageWithBreakdown("foo.com", kTemp);
   const int64_t predelete_foo_tmp = usage();
 
-  DeleteBucketData(foo_bucket, {QuotaClientType::kFileSystem});
+  DeleteBucketData(ToBucketLocator(foo_bucket), {QuotaClientType::kFileSystem});
   task_environment_.RunUntilIdle();
   GetHostUsageWithBreakdown("foo.com", kTemp);
   EXPECT_EQ(predelete_foo_tmp - 1, usage());
 
-  DeleteBucketData(foo_bucket, {QuotaClientType::kServiceWorkerCache});
+  DeleteBucketData(ToBucketLocator(foo_bucket),
+                   {QuotaClientType::kServiceWorkerCache});
   task_environment_.RunUntilIdle();
   GetHostUsageWithBreakdown("foo.com", kTemp);
   EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage());
 
-  DeleteBucketData(foo_bucket, {QuotaClientType::kDatabase});
+  DeleteBucketData(ToBucketLocator(foo_bucket), {QuotaClientType::kDatabase});
   task_environment_.RunUntilIdle();
   GetHostUsageWithBreakdown("foo.com", kTemp);
   EXPECT_EQ(predelete_foo_tmp - 4 - 2 - 1, usage());
 
-  DeleteBucketData(foo_bucket, {QuotaClientType::kIndexedDatabase});
+  DeleteBucketData(ToBucketLocator(foo_bucket),
+                   {QuotaClientType::kIndexedDatabase});
   task_environment_.RunUntilIdle();
   GetHostUsageWithBreakdown("foo.com", kTemp);
   EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
@@ -2874,14 +2875,15 @@
   GetHostUsageWithBreakdown("foo.com", kTemp);
   const int64_t predelete_foo_tmp = usage();
 
-  DeleteBucketData(foo_bucket,
+  DeleteBucketData(ToBucketLocator(foo_bucket),
                    {QuotaClientType::kFileSystem, QuotaClientType::kDatabase});
   task_environment_.RunUntilIdle();
   GetHostUsageWithBreakdown("foo.com", kTemp);
   EXPECT_EQ(predelete_foo_tmp - 4 - 1, usage());
 
-  DeleteBucketData(foo_bucket, {QuotaClientType::kServiceWorkerCache,
-                                QuotaClientType::kIndexedDatabase});
+  DeleteBucketData(ToBucketLocator(foo_bucket),
+                   {QuotaClientType::kServiceWorkerCache,
+                    QuotaClientType::kIndexedDatabase});
   task_environment_.RunUntilIdle();
   GetHostUsageWithBreakdown("foo.com", kTemp);
   EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.cc b/storage/browser/quota/quota_temporary_storage_evictor.cc
index d6e4b90f..38faa8a 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.cc
+++ b/storage/browser/quota/quota_temporary_storage_evictor.cc
@@ -216,7 +216,7 @@
 }
 
 void QuotaTemporaryStorageEvictor::OnGotEvictionBucket(
-    const absl::optional<BucketInfo>& bucket) {
+    const absl::optional<BucketLocator>& bucket) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!bucket.has_value()) {
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.h b/storage/browser/quota/quota_temporary_storage_evictor.h
index fb536df..fd86774 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.h
+++ b/storage/browser/quota/quota_temporary_storage_evictor.h
@@ -16,7 +16,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/timer/timer.h"
-#include "components/services/storage/public/cpp/buckets/bucket_info.h"
+#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 
@@ -79,7 +79,7 @@
                               int64_t total_space,
                               int64_t current_usage,
                               bool current_usage_is_complete);
-  void OnGotEvictionBucket(const absl::optional<BucketInfo>& bucket);
+  void OnGotEvictionBucket(const absl::optional<BucketLocator>& bucket);
   void OnEvictionComplete(blink::mojom::QuotaStatusCode status);
 
   void OnEvictionRoundStarted();
diff --git a/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc b/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
index 89ffbc6..aa3306e 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
+++ b/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
@@ -17,7 +17,7 @@
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "components/services/storage/public/cpp/buckets/bucket_id.h"
-#include "components/services/storage/public/cpp/buckets/bucket_info.h"
+#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
 #include "storage/browser/quota/quota_manager_impl.h"
 #include "storage/browser/quota/quota_temporary_storage_evictor.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -37,7 +37,7 @@
         error_on_evict_buckets_data_(false),
         error_on_get_usage_and_quota_(false) {}
 
-  void EvictBucketData(const BucketInfo& bucket,
+  void EvictBucketData(const BucketLocator& bucket,
                        StatusCallback callback) override {
     if (error_on_evict_buckets_data_) {
       std::move(callback).Run(
@@ -103,7 +103,7 @@
 
   // Simulates an access to `bucket`. It reorders the internal LRU list.
   // It internally uses AddBucket().
-  void AccessBucket(const BucketInfo& bucket) {
+  void AccessBucket(const BucketLocator& bucket) {
     const auto& it = buckets_.find(bucket.id);
     EXPECT_TRUE(buckets_.end() != it);
     AddBucket(bucket, it->second);
@@ -112,14 +112,14 @@
   // Simulates adding or overwriting the `bucket` to the internal bucket set
   // with the `usage`.  It also adds or moves the `bucket` to the
   // end of the LRU list.
-  void AddBucket(const BucketInfo& bucket, int64_t usage) {
+  void AddBucket(const BucketLocator& bucket, int64_t usage) {
     EnsureBucketRemoved(bucket);
     bucket_order_.push_back(bucket);
     buckets_[bucket.id] = usage;
   }
 
  private:
-  int64_t EnsureBucketRemoved(const BucketInfo& bucket) {
+  int64_t EnsureBucketRemoved(const BucketLocator& bucket) {
     int64_t bucket_usage;
     if (!base::Contains(buckets_, bucket.id))
       return -1;
@@ -133,7 +133,7 @@
 
   QuotaSettings settings_;
   int64_t available_space_;
-  std::list<BucketInfo> bucket_order_;
+  std::list<BucketLocator> bucket_order_;
   std::map<BucketId, int64_t> buckets_;
   bool error_on_evict_buckets_data_;
   bool error_on_get_usage_and_quota_;
@@ -163,8 +163,9 @@
   }
 
   void TaskForRepeatedEvictionTest(
-      const std::pair<absl::optional<BucketInfo>, int64_t>& bucket_to_be_added,
-      const absl::optional<BucketInfo> bucket_to_be_accessed,
+      const std::pair<absl::optional<BucketLocator>, int64_t>&
+          bucket_to_be_added,
+      const absl::optional<BucketLocator> bucket_to_be_accessed,
       int expected_usage_after_first,
       int expected_usage_after_second) {
     EXPECT_GE(4, num_get_usage_and_quota_for_eviction_);
@@ -187,11 +188,10 @@
     ++num_get_usage_and_quota_for_eviction_;
   }
 
-  BucketInfo CreateBucket(const std::string& url, const std::string& name) {
-    return BucketInfo(bucket_id_generator_.GenerateNextId(),
-                      blink::StorageKey::CreateFromStringForTesting(url),
-                      blink::mojom::StorageType::kTemporary, name,
-                      /*expiration=*/base::Time::Max(), /*quota=*/0);
+  BucketLocator CreateBucket(const std::string& url, bool is_default) {
+    return BucketLocator(bucket_id_generator_.GenerateNextId(),
+                         blink::StorageKey::CreateFromStringForTesting(url),
+                         blink::mojom::StorageType::kTemporary, is_default);
   }
 
  protected:
@@ -226,12 +226,12 @@
 };
 
 TEST_F(QuotaTemporaryStorageEvictorTest, SimpleEvictionTest) {
-  quota_eviction_handler()->AddBucket(CreateBucket("http://www.z.com", "test"),
-                                      3000);
-  quota_eviction_handler()->AddBucket(CreateBucket("http://www.y.com", "test"),
-                                      200);
-  quota_eviction_handler()->AddBucket(CreateBucket("http://www.x.com", "test"),
-                                      500);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.z.com", /*is_default=*/false), 3000);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.y.com", /*is_default=*/false), 200);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.x.com", /*is_default=*/false), 500);
   quota_eviction_handler()->SetPoolSize(4000);
   quota_eviction_handler()->set_available_space(1000000000);
   EXPECT_EQ(3000 + 200 + 500, quota_eviction_handler()->GetUsage());
@@ -248,13 +248,13 @@
 
 TEST_F(QuotaTemporaryStorageEvictorTest, MultipleEvictionTest) {
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.z.com", kDefaultBucketName), 20);
+      CreateBucket("http://www.z.com", /*is_default=*/true), 20);
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.y.com", kDefaultBucketName), 2900);
+      CreateBucket("http://www.y.com", /*is_default=*/true), 2900);
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.x.com", kDefaultBucketName), 450);
+      CreateBucket("http://www.x.com", /*is_default=*/true), 450);
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.w.com", kDefaultBucketName), 400);
+      CreateBucket("http://www.w.com", /*is_default=*/true), 400);
   quota_eviction_handler()->SetPoolSize(4000);
   quota_eviction_handler()->set_available_space(1000000000);
   EXPECT_EQ(20 + 2900 + 450 + 400, quota_eviction_handler()->GetUsage());
@@ -277,21 +277,22 @@
   const int64_t initial_total_size = a_size + b_size + c_size + d_size;
   const int64_t e_size = 275;
 
-  quota_eviction_handler()->AddBucket(CreateBucket("http://www.d.com", "test"),
-                                      d_size);
-  quota_eviction_handler()->AddBucket(CreateBucket("http://www.c.com", "test"),
-                                      c_size);
-  quota_eviction_handler()->AddBucket(CreateBucket("http://www.b.com", "test"),
-                                      b_size);
-  quota_eviction_handler()->AddBucket(CreateBucket("http://www.a.com", "test"),
-                                      a_size);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.d.com", /*is_default=*/false), d_size);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.c.com", /*is_default=*/false), c_size);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.b.com", /*is_default=*/false), b_size);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.a.com", /*is_default=*/false), a_size);
   quota_eviction_handler()->SetPoolSize(1000);
   quota_eviction_handler()->set_available_space(1000000000);
   quota_eviction_handler()->set_task_for_get_usage_and_quota(
       base::BindRepeating(
           &QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
           weak_factory_.GetWeakPtr(),
-          std::make_pair(CreateBucket("http://www.e.com", "test"), e_size),
+          std::make_pair(CreateBucket("http://www.e.com", /*is_default=*/false),
+                         e_size),
           absl::nullopt, initial_total_size - d_size,
           initial_total_size - d_size + e_size - c_size));
   EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
@@ -315,13 +316,13 @@
   const int64_t initial_total_size = a_size + b_size + c_size + d_size;
 
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.d.com", kDefaultBucketName), d_size);
+      CreateBucket("http://www.d.com", /*is_default=*/true), d_size);
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.c.com", kDefaultBucketName), c_size);
+      CreateBucket("http://www.c.com", /*is_default=*/true), c_size);
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.b.com", kDefaultBucketName), b_size);
+      CreateBucket("http://www.b.com", /*is_default=*/true), b_size);
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.a.com", kDefaultBucketName), a_size);
+      CreateBucket("http://www.a.com", /*is_default=*/true), a_size);
   quota_eviction_handler()->SetPoolSize(1000);
   quota_eviction_handler()->set_available_space(1000000000);
   quota_eviction_handler()->set_task_for_get_usage_and_quota(
@@ -351,11 +352,16 @@
   const int64_t initial_total_size = a_size + b_size + c_size + d_size;
   const int64_t e_size = 275;
 
-  BucketInfo a_bucket = CreateBucket("http://www.a.com", kDefaultBucketName);
-  BucketInfo b_bucket = CreateBucket("http://www.b.com", kDefaultBucketName);
-  BucketInfo c_bucket = CreateBucket("http://www.c.com", kDefaultBucketName);
-  BucketInfo d_bucket = CreateBucket("http://www.d.com", kDefaultBucketName);
-  BucketInfo e_bucket = CreateBucket("http://www.e.com", kDefaultBucketName);
+  BucketLocator a_bucket =
+      CreateBucket("http://www.a.com", /*is_default=*/true);
+  BucketLocator b_bucket =
+      CreateBucket("http://www.b.com", /*is_default=*/true);
+  BucketLocator c_bucket =
+      CreateBucket("http://www.c.com", /*is_default=*/true);
+  BucketLocator d_bucket =
+      CreateBucket("http://www.d.com", /*is_default=*/true);
+  BucketLocator e_bucket =
+      CreateBucket("http://www.e.com", /*is_default=*/true);
 
   quota_eviction_handler()->AddBucket(d_bucket, d_size);
   quota_eviction_handler()->AddBucket(c_bucket, c_size);
@@ -386,9 +392,9 @@
   // If we're using so little that evicting all of it wouldn't
   // do enough to alleviate a diskspace shortage, we don't evict.
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.z.com", kDefaultBucketName), 10);
+      CreateBucket("http://www.z.com", /*is_default=*/true), 10);
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.x.com", kDefaultBucketName), 20);
+      CreateBucket("http://www.x.com", /*is_default=*/true), 20);
   quota_eviction_handler()->SetPoolSize(10000);
   quota_eviction_handler()->set_available_space(
       quota_eviction_handler()->settings().should_remain_available - 350);
@@ -406,13 +412,13 @@
 
 TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceEvictionTest) {
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.z.com", kDefaultBucketName), 294);
+      CreateBucket("http://www.z.com", /*is_default=*/true), 294);
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.y.com", kDefaultBucketName), 120);
+      CreateBucket("http://www.y.com", /*is_default=*/true), 120);
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.x.com", kDefaultBucketName), 150);
+      CreateBucket("http://www.x.com", /*is_default=*/true), 150);
   quota_eviction_handler()->AddBucket(
-      CreateBucket("http://www.w.com", kDefaultBucketName), 300);
+      CreateBucket("http://www.w.com", /*is_default=*/true), 300);
   quota_eviction_handler()->SetPoolSize(10000);
   quota_eviction_handler()->set_available_space(
       quota_eviction_handler()->settings().should_remain_available - 350);
diff --git a/storage/browser/test/mock_quota_manager.cc b/storage/browser/test/mock_quota_manager.cc
index 96748fa..8b33393b 100644
--- a/storage/browser/test/mock_quota_manager.cc
+++ b/storage/browser/test/mock_quota_manager.cc
@@ -138,11 +138,13 @@
                                                  base::Time begin,
                                                  base::Time end,
                                                  GetBucketsCallback callback) {
-  auto buckets_to_return = std::make_unique<std::set<BucketInfo>>();
+  auto buckets_to_return = std::make_unique<std::set<BucketLocator>>();
   for (const auto& info : buckets_) {
     if (info.bucket.type == type && info.modified >= begin &&
         info.modified < end)
-      buckets_to_return->insert(info.bucket);
+      buckets_to_return->insert(BucketLocator(
+          info.bucket.id, info.bucket.storage_key, info.bucket.type,
+          info.bucket.name == kDefaultBucketName));
   }
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
@@ -151,11 +153,11 @@
                                 std::move(buckets_to_return), type));
 }
 
-void MockQuotaManager::DeleteBucketData(const BucketInfo& bucket,
+void MockQuotaManager::DeleteBucketData(const BucketLocator& bucket,
                                         QuotaClientTypes quota_client_types,
                                         StatusCallback callback) {
   for (auto current = buckets_.begin(); current != buckets_.end(); ++current) {
-    if (current->bucket == bucket) {
+    if (current->bucket.id == bucket.id) {
       // Modify the mask: if it's 0 after "deletion", remove the storage key.
       for (QuotaClientType type : quota_client_types)
         current->quota_client_types.erase(type);
@@ -211,7 +213,7 @@
 
 void MockQuotaManager::DidGetModifiedInTimeRange(
     GetBucketsCallback callback,
-    std::unique_ptr<std::set<BucketInfo>> buckets,
+    std::unique_ptr<std::set<BucketLocator>> buckets,
     StorageType storage_type) {
   std::move(callback).Run(*buckets, storage_type);
 }
diff --git a/storage/browser/test/mock_quota_manager.h b/storage/browser/test/mock_quota_manager.h
index ea46034..dd3c276 100644
--- a/storage/browser/test/mock_quota_manager.h
+++ b/storage/browser/test/mock_quota_manager.h
@@ -77,7 +77,7 @@
   // specifies the types of QuotaClients which should be removed from this
   // bucket. Setting the mask to AllQuotaClientTypes() will remove all
   // clients from the bucket, regardless of type.
-  void DeleteBucketData(const BucketInfo& bucket,
+  void DeleteBucketData(const BucketLocator& bucket,
                         QuotaClientTypes quota_client_types,
                         StatusCallback callback) override;
 
@@ -171,9 +171,10 @@
 
   void DidGetBucket(base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback,
                     QuotaErrorOr<BucketInfo> result);
-  void DidGetModifiedInTimeRange(GetBucketsCallback callback,
-                                 std::unique_ptr<std::set<BucketInfo>> buckets,
-                                 blink::mojom::StorageType storage_type);
+  void DidGetModifiedInTimeRange(
+      GetBucketsCallback callback,
+      std::unique_ptr<std::set<BucketLocator>> buckets,
+      blink::mojom::StorageType storage_type);
   void DidDeleteStorageKeyData(StatusCallback callback,
                                blink::mojom::QuotaStatusCode status);
 
diff --git a/storage/browser/test/mock_quota_manager_unittest.cc b/storage/browser/test/mock_quota_manager_unittest.cc
index e0ea19d56..7d53c753 100644
--- a/storage/browser/test/mock_quota_manager_unittest.cc
+++ b/storage/browser/test/mock_quota_manager_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
 #include "storage/browser/quota/quota_client_type.h"
 #include "storage/browser/test/mock_special_storage_policy.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -34,6 +35,20 @@
 constexpr QuotaClientType kClientFile = QuotaClientType::kFileSystem;
 constexpr QuotaClientType kClientDB = QuotaClientType::kIndexedDatabase;
 
+bool ContainsBucket(const std::set<BucketLocator>& buckets,
+                    const BucketInfo& target_bucket) {
+  BucketLocator target_bucket_locator(
+      target_bucket.id, target_bucket.storage_key, target_bucket.type,
+      target_bucket.name == kDefaultBucketName);
+  auto it = buckets.find(target_bucket_locator);
+  return it != buckets.end();
+}
+
+BucketLocator ToBucketLocator(const BucketInfo& bucket) {
+  return BucketLocator(bucket.id, bucket.storage_key, bucket.type,
+                       bucket.name == kDefaultBucketName);
+}
+
 }  // namespace
 
 class MockQuotaManagerTest : public testing::Test {
@@ -94,14 +109,14 @@
   }
 
   void GotModifiedBuckets(base::OnceClosure quit_closure,
-                          const std::set<BucketInfo>& buckets,
+                          const std::set<BucketLocator>& buckets,
                           StorageType type) {
     buckets_ = buckets;
     type_ = type;
     std::move(quit_closure).Run();
   }
 
-  void DeleteBucketData(const BucketInfo& bucket,
+  void DeleteBucketData(const BucketLocator& bucket,
                         QuotaClientTypes quota_client_types) {
     base::RunLoop run_loop;
     manager_->DeleteBucketData(
@@ -126,7 +141,7 @@
     return manager_.get();
   }
 
-  const std::set<BucketInfo>& buckets() const { return buckets_; }
+  const std::set<BucketLocator>& buckets() const { return buckets_; }
 
   const StorageType& type() const {
     return type_;
@@ -140,7 +155,7 @@
 
   int deletion_callback_count_;
 
-  std::set<BucketInfo> buckets_;
+  std::set<BucketLocator> buckets_;
   StorageType type_;
 
   base::WeakPtrFactory<MockQuotaManagerTest> weak_factory_{this};
@@ -292,7 +307,7 @@
   manager()->AddBucket(bucket2, {kClientFile, kClientDB}, base::Time::Now());
   manager()->AddBucket(bucket3, {kClientFile, kClientDB}, base::Time::Now());
 
-  DeleteBucketData(bucket2, {kClientFile});
+  DeleteBucketData(ToBucketLocator(bucket2), {kClientFile});
 
   EXPECT_EQ(1, deletion_callback_count());
   EXPECT_EQ(manager()->BucketDataCount(kClientFile), 2);
@@ -302,7 +317,7 @@
   EXPECT_TRUE(manager()->BucketHasData(bucket3, kClientFile));
   EXPECT_TRUE(manager()->BucketHasData(bucket3, kClientDB));
 
-  DeleteBucketData(bucket3, {kClientFile, kClientDB});
+  DeleteBucketData(ToBucketLocator(bucket3), {kClientFile, kClientDB});
 
   EXPECT_EQ(2, deletion_callback_count());
   EXPECT_EQ(manager()->BucketDataCount(kClientFile), 1);
@@ -333,8 +348,8 @@
 
   EXPECT_EQ(kTemporary, type());
   EXPECT_EQ(1UL, buckets().size());
-  EXPECT_EQ(1UL, buckets().count(bucket1));
-  EXPECT_EQ(0UL, buckets().count(bucket2));
+  EXPECT_TRUE(ContainsBucket(buckets(), bucket1));
+  EXPECT_FALSE(ContainsBucket(buckets(), bucket2));
 
   manager()->AddBucket(bucket2, {kClientFile}, now);
 
@@ -342,21 +357,21 @@
 
   EXPECT_EQ(kTemporary, type());
   EXPECT_EQ(2UL, buckets().size());
-  EXPECT_EQ(1UL, buckets().count(bucket1));
-  EXPECT_EQ(1UL, buckets().count(bucket2));
+  EXPECT_TRUE(ContainsBucket(buckets(), bucket1));
+  EXPECT_TRUE(ContainsBucket(buckets(), bucket2));
 
   GetModifiedBuckets(kTemporary, then, now);
 
   EXPECT_EQ(kTemporary, type());
   EXPECT_EQ(1UL, buckets().size());
-  EXPECT_EQ(1UL, buckets().count(bucket1));
-  EXPECT_EQ(0UL, buckets().count(bucket2));
+  EXPECT_TRUE(ContainsBucket(buckets(), bucket1));
+  EXPECT_FALSE(ContainsBucket(buckets(), bucket2));
 
   GetModifiedBuckets(kTemporary, now - a_minute, now + a_minute);
 
   EXPECT_EQ(kTemporary, type());
   EXPECT_EQ(1UL, buckets().size());
-  EXPECT_EQ(0UL, buckets().count(bucket1));
-  EXPECT_EQ(1UL, buckets().count(bucket2));
+  EXPECT_FALSE(ContainsBucket(buckets(), bucket1));
+  EXPECT_TRUE(ContainsBucket(buckets(), bucket2));
 }
 }  // namespace storage
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 4f3922c..04bfa4bd 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -2051,46 +2051,6 @@
     "gtest_tests": [
       {
         "args": [
-          "angle_end2end_tests",
-          "--gtest_filter=-*Vulkan_SwiftShader*",
-          "--shard-timeout=180",
-          "-v"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "device_os": "MMB29Q",
-              "device_os_type": "userdebug",
-              "device_type": "bullhead",
-              "os": "Android"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 4
-        },
-        "test": "angle_end2end_tests",
-        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_end2end_tests/",
-        "use_isolated_scripts_api": true
-      },
-      {
-        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 467b2d4..8362879 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -6397,7 +6397,6 @@
     ],
 
     'gpu_fyi_android_gtests': [
-      'gpu_angle_end2end_gtests',
       'gpu_angle_unit_gtests',
       'gpu_common_gtests_validating',
       'gpu_fyi_and_optional_non_linux_gtests',
diff --git a/testing/scripts/PRESUBMIT.py b/testing/scripts/PRESUBMIT.py
index 1298c22..768fef289 100644
--- a/testing/scripts/PRESUBMIT.py
+++ b/testing/scripts/PRESUBMIT.py
@@ -12,7 +12,8 @@
 
 def CommonChecks(input_api, output_api):
   return input_api.canned_checks.RunUnitTestsInDirectory(
-      input_api, output_api, '.', files_to_check=[r'^.+_unittest\.py$'])
+      input_api, output_api, '.', files_to_check=[r'^.+_unittest\.py$'],
+      skip_shebang_check=True)
 
 def CheckChangeOnUpload(input_api, output_api):
   return CommonChecks(input_api, output_api)
diff --git a/testing/scripts/wpt_common_unittest.py b/testing/scripts/wpt_common_unittest.py
index 11d6c76e..85ea0b3 100755
--- a/testing/scripts/wpt_common_unittest.py
+++ b/testing/scripts/wpt_common_unittest.py
@@ -251,7 +251,8 @@
             LAYOUT_TEST_RESULTS_SUBDIR, 'full_results_jsonp.js')]
         match = re.match(r'ADD_FULL_RESULTS\((.*)\);$', full_results_jsonp)
         self.assertIsNotNone(match)
-        self.assertEqual(match.group(1), written_files[OUTPUT_JSON_FILENAME])
+        self.assertEqual(match.group(1),
+            written_files[OUTPUT_JSON_FILENAME].decode(encoding='utf-8'))
         failing_results_jsonp = written_files[os.path.join(
             LAYOUT_TEST_RESULTS_SUBDIR, 'failing_results.json')]
         match = re.match(r'ADD_RESULTS\((.*)\);$', failing_results_jsonp)
@@ -303,7 +304,8 @@
         self.wpt_adapter.do_post_test_run_tasks()
         written_files = self.wpt_adapter.fs.written_files
         actual_path = os.path.join(artifact_subdir, "test-actual.txt")
-        self.assertEqual("test.html actual text", written_files[actual_path])
+        self.assertEqual("test.html actual text",
+            written_files[actual_path].decode(encoding='utf-8'))
         # Ensure the artifact in the json was replaced with the location of
         # the newly-created file.
         updated_json = self._load_json_output()
@@ -341,7 +343,8 @@
                                        "external", "wpt")
         stderr_path = os.path.join(artifact_subdir,
                                    "test-stderr.txt")
-        self.assertEqual("test.html exceptions", written_files[stderr_path])
+        self.assertEqual("test.html exceptions",
+            written_files[stderr_path].decode(encoding='utf-8'))
         # Ensure the artifact in the json was replaced with the location of
         # the newly-created file.
         updated_json = self._load_json_output()
@@ -376,7 +379,8 @@
                                        "external", "wpt")
         crash_log_path = os.path.join(artifact_subdir,
                                       "test-crash-log.txt")
-        self.assertEqual("test.html crashed!", written_files[crash_log_path])
+        self.assertEqual("test.html crashed!",
+            written_files[crash_log_path].decode(encoding='utf-8'))
         # Ensure the artifact in the json was replaced with the location of
         # the newly-created file.
         updated_json = self._load_json_output()
@@ -470,12 +474,13 @@
                                        "external", "wpt")
         actual_path = os.path.join(artifact_subdir,
                                    "test-actual.txt")
-        self.assertEqual("test.html actual text", written_files[actual_path])
+        self.assertEqual("test.html actual text",
+            written_files[actual_path].decode(encoding='utf-8'))
         # The checked-in metadata file gets renamed from .ini to -expected.txt
         expected_path = os.path.join(artifact_subdir,
                                      "test-expected.txt")
         self.assertEqual("test.html checked-in metadata",
-                         written_files[expected_path])
+                         written_files[expected_path].decode(encoding='utf-8'))
         # Ensure the artifacts in the json were replaced with the locations of
         # the newly-created files.
         updated_json = self._load_json_output()
@@ -496,8 +501,9 @@
         # validate the entire diff files to avoid checking line numbers/markup.
         diff_path = os.path.join(artifact_subdir, "test-diff.txt")
         self.assertIn("-test.html checked-in metadata",
-                      written_files[diff_path])
-        self.assertIn("+test.html actual text", written_files[diff_path])
+                      written_files[diff_path].decode(encoding='utf-8'))
+        self.assertIn("+test.html actual text",
+            written_files[diff_path].decode(encoding='utf-8'))
         self.assertEqual(
             [diff_path],
             updated_json["tests"]["external"]["wpt"]["test.html"]["artifacts"]
@@ -505,8 +511,9 @@
         pretty_diff_path = os.path.join(artifact_subdir,
                                         "test-pretty-diff.html")
         self.assertIn("test.html checked-in metadata",
-                      written_files[pretty_diff_path])
-        self.assertIn("test.html actual text", written_files[pretty_diff_path])
+                      written_files[pretty_diff_path].decode(encoding='utf-8'))
+        self.assertIn("test.html actual text",
+            written_files[pretty_diff_path].decode(encoding='utf-8'))
         self.assertEqual(
             [pretty_diff_path],
             updated_json["tests"]["external"]["wpt"]["test.html"]["artifacts"]
@@ -546,12 +553,12 @@
         actual_path = os.path.join(artifact_subdir,
                                    "variant_foo=bar_abc-actual.txt")
         self.assertEqual("variant bar/abc actual text",
-                         written_files[actual_path])
+                         written_files[actual_path].decode(encoding='utf-8'))
         # The checked-in metadata file gets renamed from .ini to -expected.txt
         expected_path = os.path.join(artifact_subdir,
                                      "variant_foo=bar_abc-expected.txt")
         self.assertEqual("variant.html checked-in metadata",
-                         written_files[expected_path])
+                         written_files[expected_path].decode(encoding='utf-8'))
         # Ensure the artifacts in the json were replaced with the locations of
         # the newly-created files.
         updated_json = self._load_json_output()
@@ -605,13 +612,13 @@
         actual_path = os.path.join(artifact_subdir,
                                    "dir/multiglob.https.any.worker-actual.txt")
         self.assertEqual("dir/multiglob worker actual text",
-                         written_files[actual_path])
+                         written_files[actual_path].decode(encoding='utf-8'))
         # The checked-in metadata file gets renamed from .ini to -expected.txt
         expected_path = os.path.join(
             artifact_subdir,
             "dir/multiglob.https.any.worker-expected.txt")
         self.assertEqual("dir/multiglob checked-in metadata",
-                         written_files[expected_path])
+                         written_files[expected_path].decode(encoding='utf-8'))
         # Ensure the artifacts in the json were replaced with the locations of
         # the newly-created files.
         updated_json = self._load_json_output()
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 413e4f2..ecd534e 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4318,6 +4318,30 @@
             ]
         }
     ],
+    "IgnoreSyncEncryptionKeysLongMissing": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "MinGuResponsesToIgnoreKey": "2"
+                    },
+                    "enable_features": [
+                        "IgnoreSyncEncryptionKeysLongMissing"
+                    ]
+                }
+            ]
+        }
+    ],
     "IncludeInitiallyInvisibleImagesInLCP": [
         {
             "platforms": [
@@ -5328,6 +5352,24 @@
             ]
         }
     ],
+    "OmniboxPedalsBatch3": [
+        {
+            "platforms": [
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Batch3_Enabled",
+                    "enable_features": [
+                        "OmniboxPedalsBatch3"
+                    ]
+                }
+            ]
+        }
+    ],
     "OmniboxUpdatedConnectionSecurityIndicatorsIPH": [
         {
             "platforms": [
@@ -7520,6 +7562,40 @@
             ]
         }
     ],
+    "TabSearchEntrypointChevronIcon": [
+        {
+            "platforms": [
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "TabSearchChevronIcon"
+                    ]
+                }
+            ]
+        }
+    ],
+    "TabSearchEntrypointWindowsEntrypoint": [
+        {
+            "platforms": [
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "TabSearchChevronIcon",
+                        "Win10TabSearchCaptionButton"
+                    ]
+                }
+            ]
+        }
+    ],
     "TabSearchIPH": [
         {
             "platforms": [
@@ -8105,7 +8181,6 @@
         {
             "platforms": [
                 "android",
-                "chromeos_lacros",
                 "ios",
                 "linux",
                 "mac",
@@ -8135,7 +8210,8 @@
         },
         {
             "platforms": [
-                "chromeos"
+                "chromeos",
+                "chromeos_lacros"
             ],
             "experiments": [
                 {
@@ -8679,24 +8755,6 @@
             ]
         }
     ],
-    "WebRTC-DataChannel-Dcsctp": [
-        {
-            "platforms": [
-                "android",
-                "chromeos",
-                "chromeos_lacros",
-                "ios",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled"
-                }
-            ]
-        }
-    ],
     "WebRTC-TaskQueuePacer": [
         {
             "platforms": [
diff --git a/third_party/blink/common/loader/url_loader_factory_bundle.cc b/third_party/blink/common/loader/url_loader_factory_bundle.cc
index c8dcae3..39e16d96 100644
--- a/third_party/blink/common/loader/url_loader_factory_bundle.cc
+++ b/third_party/blink/common/loader/url_loader_factory_bundle.cc
@@ -59,7 +59,6 @@
 PendingURLLoaderFactoryBundle::CreateFactory() {
   auto other = std::make_unique<PendingURLLoaderFactoryBundle>();
   other->pending_default_factory_ = std::move(pending_default_factory_);
-  other->pending_appcache_factory_ = std::move(pending_appcache_factory_);
   other->pending_scheme_specific_factories_ =
       std::move(pending_scheme_specific_factories_);
   other->pending_isolated_world_factories_ =
@@ -93,10 +92,6 @@
       return it2->second.get();
   }
 
-  // AppCache factory must be used if it's given.
-  if (appcache_factory_)
-    return appcache_factory_.get();
-
   // Hitting the DCHECK below means that a subresource load has unexpectedly
   // happened in a speculative frame (or in a test frame created via
   // RenderViewTest).  This most likely indicates a bug somewhere else.
@@ -137,11 +132,6 @@
           CloneRemoteMapToPendingRemoteMap(isolated_world_factories_),
           bypass_redirect_checks_);
 
-  if (appcache_factory_) {
-    appcache_factory_->Clone(pending_factories->pending_appcache_factory()
-                                 .InitWithNewPipeAndPassReceiver());
-  }
-
   return pending_factories;
 }
 
@@ -156,11 +146,6 @@
     default_factory_.Bind(
         std::move(pending_factories->pending_default_factory()));
   }
-  if (pending_factories->pending_appcache_factory()) {
-    appcache_factory_.reset();
-    appcache_factory_.Bind(
-        std::move(pending_factories->pending_appcache_factory()));
-  }
   BindPendingRemoteMapToRemoteMap(
       &scheme_specific_factories_,
       std::move(pending_factories->pending_scheme_specific_factories()));
diff --git a/third_party/blink/common/loader/url_loader_factory_bundle_mojom_traits.cc b/third_party/blink/common/loader/url_loader_factory_bundle_mojom_traits.cc
index 843e839..9ed45a86 100644
--- a/third_party/blink/common/loader/url_loader_factory_bundle_mojom_traits.cc
+++ b/third_party/blink/common/loader/url_loader_factory_bundle_mojom_traits.cc
@@ -22,12 +22,6 @@
 }
 
 // static
-mojo::PendingRemote<network::mojom::URLLoaderFactory> Traits::appcache_factory(
-    BundleInfoType& bundle) {
-  return std::move(bundle->pending_appcache_factory());
-}
-
-// static
 blink::PendingURLLoaderFactoryBundle::SchemeMap
 Traits::scheme_specific_factories(BundleInfoType& bundle) {
   return std::move(bundle->pending_scheme_specific_factories());
@@ -51,8 +45,6 @@
 
   (*out_bundle)->pending_default_factory() = data.TakeDefaultFactory<
       mojo::PendingRemote<network::mojom::URLLoaderFactory>>();
-  (*out_bundle)->pending_appcache_factory() = data.TakeAppcacheFactory<
-      mojo::PendingRemote<network::mojom::URLLoaderFactory>>();
   if (!data.ReadSchemeSpecificFactories(
           &(*out_bundle)->pending_scheme_specific_factories())) {
     return false;
diff --git a/third_party/blink/public/common/loader/url_loader_factory_bundle.h b/third_party/blink/public/common/loader/url_loader_factory_bundle.h
index eab9f521..e47e393 100644
--- a/third_party/blink/public/common/loader/url_loader_factory_bundle.h
+++ b/third_party/blink/public/common/loader/url_loader_factory_bundle.h
@@ -60,11 +60,6 @@
     return pending_default_factory_;
   }
 
-  mojo::PendingRemote<network::mojom::URLLoaderFactory>&
-  pending_appcache_factory() {
-    return pending_appcache_factory_;
-  }
-
   SchemeMap& pending_scheme_specific_factories() {
     return pending_scheme_specific_factories_;
   }
@@ -85,8 +80,6 @@
 
   mojo::PendingRemote<network::mojom::URLLoaderFactory>
       pending_default_factory_;
-  mojo::PendingRemote<network::mojom::URLLoaderFactory>
-      pending_appcache_factory_;
   SchemeMap pending_scheme_specific_factories_;
 
   // TODO(https://crbug.com/1098410): Remove the
@@ -156,11 +149,6 @@
   // context should not be given access to the network.
   mojo::Remote<network::mojom::URLLoaderFactory> default_factory_;
 
-  // |appcache_factory_| is a special loader factory that intercepts
-  // requests when the context has AppCache. See also
-  // AppCacheSubresourceURLFactory.
-  mojo::Remote<network::mojom::URLLoaderFactory> appcache_factory_;
-
   // Map from URL scheme to Remote<URLLoaderFactory> for handling URL requests
   // for schemes not handled by the |default_factory_|.  See also
   // PendingURLLoaderFactoryBundle::SchemeMap and
diff --git a/third_party/blink/public/mojom/loader/url_loader_factory_bundle.mojom b/third_party/blink/public/mojom/loader/url_loader_factory_bundle.mojom
index 82abe20d..aa3e86f 100644
--- a/third_party/blink/public/mojom/loader/url_loader_factory_bundle.mojom
+++ b/third_party/blink/public/mojom/loader/url_loader_factory_bundle.mojom
@@ -30,10 +30,6 @@
   map<url.mojom.Origin, pending_remote<network.mojom.URLLoaderFactory>>
       isolated_world_factories;
 
-  // A special factory that is used for AppCache.
-  // TODO(https://crbug.com/582750): Drop this when AppCache is deprecated.
-  pending_remote<network.mojom.URLLoaderFactory>? appcache_factory;
-
   // Whether redirect checks should be bypassed, since they are happening in the
   // browser.
   bool bypass_redirect_checks = false;
diff --git a/third_party/blink/public/platform/child_url_loader_factory_bundle.h b/third_party/blink/public/platform/child_url_loader_factory_bundle.h
index 70656202..f4cdf67 100644
--- a/third_party/blink/public/platform/child_url_loader_factory_bundle.h
+++ b/third_party/blink/public/platform/child_url_loader_factory_bundle.h
@@ -38,8 +38,6 @@
   ChildPendingURLLoaderFactoryBundle(
       mojo::PendingRemote<network::mojom::URLLoaderFactory>
           pending_default_factory,
-      mojo::PendingRemote<network::mojom::URLLoaderFactory>
-          pending_default_network_factory,
       SchemeMap pending_scheme_specific_factories,
       OriginMap pending_isolated_world_factories,
       mojo::PendingRemote<network::mojom::URLLoaderFactory>
@@ -58,7 +56,6 @@
     std::unique_ptr<ChildPendingURLLoaderFactoryBundle> pending_bundle(
         new ChildPendingURLLoaderFactoryBundle(
             std::move(pending_default_factory),
-            {},       // pending_default_network_factory
             {},       // pending_scheme_specific_factories
             {},       // pending_isolated_world_factories
             {},       // pending_prefetch_loader_factory
@@ -100,13 +97,6 @@
       override;
   std::unique_ptr<network::PendingSharedURLLoaderFactory> Clone() override;
 
-  // Does the same as Clone(), but without cloning the appcache_factory_.
-  // This is used for creating a bundle for network fallback loading with
-  // Service Workers (where AppCache must be skipped), and only when
-  // claim() is called.
-  virtual std::unique_ptr<network::PendingSharedURLLoaderFactory>
-  CloneWithoutAppCacheFactory();
-
   std::unique_ptr<ChildPendingURLLoaderFactoryBundle> PassInterface();
 
   void Update(
@@ -124,9 +114,6 @@
   ~ChildURLLoaderFactoryBundle() override;
 
  private:
-  std::unique_ptr<network::PendingSharedURLLoaderFactory> CloneInternal(
-      bool include_appcache);
-
   mojo::Remote<network::mojom::URLLoaderFactory> prefetch_loader_factory_;
 
   std::map<GURL, mojom::TransferrableURLLoaderPtr> subresource_overrides_;
diff --git a/third_party/blink/public/platform/tracked_child_url_loader_factory_bundle.h b/third_party/blink/public/platform/tracked_child_url_loader_factory_bundle.h
index 1810078a..05df1e5 100644
--- a/third_party/blink/public/platform/tracked_child_url_loader_factory_bundle.h
+++ b/third_party/blink/public/platform/tracked_child_url_loader_factory_bundle.h
@@ -31,8 +31,6 @@
   TrackedChildPendingURLLoaderFactoryBundle(
       mojo::PendingRemote<network::mojom::URLLoaderFactory>
           pending_default_factory,
-      mojo::PendingRemote<network::mojom::URLLoaderFactory>
-          pending_appcache_factory,
       SchemeMap pending_scheme_specific_factories,
       OriginMap pending_isolated_world_factories,
       mojo::PendingRemote<network::mojom::URLLoaderFactory>
@@ -135,8 +133,6 @@
   // ChildURLLoaderFactoryBundle overrides.
   // Returns |std::unique_ptr<TrackedChildPendingURLLoaderFactoryBundle>|.
   std::unique_ptr<network::PendingSharedURLLoaderFactory> Clone() override;
-  std::unique_ptr<network::PendingSharedURLLoaderFactory>
-  CloneWithoutAppCacheFactory() override;
   bool IsHostChildURLLoaderFactoryBundle() const override;
 
   // Update this bundle with |info|, and post cloned |info| to tracked bundles.
diff --git a/third_party/blink/renderer/bindings/idl_in_core.gni b/third_party/blink/renderer/bindings/idl_in_core.gni
index f721185..350c7a8 100644
--- a/third_party/blink/renderer/bindings/idl_in_core.gni
+++ b/third_party/blink/renderer/bindings/idl_in_core.gni
@@ -645,6 +645,7 @@
           "//third_party/blink/renderer/core/svg/svg_view_element.idl",
           "//third_party/blink/renderer/core/svg/svg_zoom_and_pan.idl",
           "//third_party/blink/renderer/core/timing/dom_high_res_time_stamp.idl",
+          "//third_party/blink/renderer/core/timing/epoch_time_stamp.idl",
           "//third_party/blink/renderer/core/timing/event_counts.idl",
           "//third_party/blink/renderer/core/timing/internals_profiler.idl",
           "//third_party/blink/renderer/core/timing/largest_contentful_paint.idl",
diff --git a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules_test.cc b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules_test.cc
index 761019b..eb212b5 100644
--- a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules_test.cc
+++ b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules_test.cc
@@ -14,7 +14,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/dictionary.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_rect_read_only.h"
diff --git a/third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules_test.cc b/third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules_test.cc
index b76a6c62..030fe87 100644
--- a/third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules_test.cc
+++ b/third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules_test.cc
@@ -32,7 +32,7 @@
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialization_tag.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
diff --git a/third_party/blink/renderer/core/css/cascade_layer_map.cc b/third_party/blink/renderer/core/css/cascade_layer_map.cc
index 47052b2..62e0cc1 100644
--- a/third_party/blink/renderer/core/css/cascade_layer_map.cc
+++ b/third_party/blink/renderer/core/css/cascade_layer_map.cc
@@ -8,7 +8,8 @@
 
 namespace blink {
 
-const unsigned CascadeLayerMap::kImplicitOuterLayerOrder = 0;
+const unsigned CascadeLayerMap::kImplicitOuterLayerOrder =
+    std::numeric_limits<unsigned>::max();
 
 namespace {
 
@@ -34,9 +35,9 @@
 void ComputeLayerOrder(const CascadeLayer& layer,
                        unsigned& next,
                        LayerOrderMap& layer_order_map) {
-  layer_order_map.insert(&layer, next++);
   for (const auto& sub_layer : layer.GetDirectSubLayers())
     ComputeLayerOrder(*sub_layer, next, layer_order_map);
+  layer_order_map.insert(&layer, next++);
 }
 
 }  // namespace
@@ -53,15 +54,23 @@
     }
   }
 
-  unsigned next = kImplicitOuterLayerOrder;
+  unsigned next = 0;
   LayerOrderMap canonical_layer_order_map;
   ComputeLayerOrder(*canonical_root_layer, next, canonical_layer_order_map);
 
+  canonical_layer_order_map.Set(canonical_root_layer, kImplicitOuterLayerOrder);
+
   for (const auto& iter : canonical_layer_map) {
     const CascadeLayer* layer_from_sheet = iter.key;
     const CascadeLayer* canonical_layer = iter.value;
     unsigned layer_order = canonical_layer_order_map.at(canonical_layer);
     layer_order_map_.insert(layer_from_sheet, layer_order);
+
+#if DCHECK_IS_ON()
+    // The implicit outer layer is placed above all explicit layers.
+    if (canonical_layer != canonical_root_layer)
+      DCHECK_LT(layer_order, kImplicitOuterLayerOrder);
+#endif
   }
 }
 
diff --git a/third_party/blink/renderer/core/css/cssom/css_style_value.cc b/third_party/blink/renderer/core/css/cssom/css_style_value.cc
index c8b1f00b..1b06f74 100644
--- a/third_party/blink/renderer/core/css/cssom/css_style_value.cc
+++ b/third_party/blink/renderer/core/css/cssom/css_style_value.cc
@@ -5,7 +5,7 @@
 #include "third_party/blink/renderer/core/css/cssom/css_style_value.h"
 
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/core/css/cssom/style_value_factory.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
 #include "third_party/blink/renderer/core/css/properties/css_property.h"
diff --git a/third_party/blink/renderer/core/css/resolver/match_result.h b/third_party/blink/renderer/core/css/resolver/match_result.h
index 9748793d..a65ba21 100644
--- a/third_party/blink/renderer/core/css/resolver/match_result.h
+++ b/third_party/blink/renderer/core/css/resolver/match_result.h
@@ -144,7 +144,7 @@
  private:
   unsigned link_match_type_ = CSSSelector::kMatchAll;
   ValidPropertyFilter valid_property_filter_ = ValidPropertyFilter::kNoFilter;
-  unsigned layer_order_ = CascadeLayerMap::kImplicitOuterLayerOrder;
+  unsigned layer_order_ = 0;
   bool is_inline_style_ = false;
 
   friend class Builder;
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc b/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
index 57510c1..23e6d05 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
@@ -1385,19 +1385,22 @@
   const auto& properties = match_result.GetMatchedProperties();
   ASSERT_EQ(properties.size(), 3u);
 
+  const uint16_t kImplicitOuterLayerOrder =
+      CascadeLayerMap::kImplicitOuterLayerOrder;
+
   // div { display: block; }
   EXPECT_TRUE(properties[0].properties->HasProperty(CSSPropertyID::kDisplay));
-  EXPECT_EQ(0u, properties[0].types_.layer_order);
+  EXPECT_EQ(kImplicitOuterLayerOrder, properties[0].types_.layer_order);
   EXPECT_EQ(properties[0].types_.origin, CascadeOrigin::kUserAgent);
 
   // .b { font-size: 16px; }
   EXPECT_TRUE(properties[1].properties->HasProperty(CSSPropertyID::kFontSize));
-  EXPECT_EQ(0u, properties[1].types_.layer_order);
+  EXPECT_EQ(kImplicitOuterLayerOrder, properties[1].types_.layer_order);
   EXPECT_EQ(properties[1].types_.origin, CascadeOrigin::kAuthor);
 
   // #a { color: green; }
   EXPECT_TRUE(properties[2].properties->HasProperty(CSSPropertyID::kColor));
-  EXPECT_EQ(0u, properties[2].types_.layer_order);
+  EXPECT_EQ(kImplicitOuterLayerOrder, properties[2].types_.layer_order);
   EXPECT_EQ(properties[2].types_.origin, CascadeOrigin::kAuthor);
 }
 
@@ -1431,26 +1434,30 @@
   const auto& properties = match_result.GetMatchedProperties();
   ASSERT_EQ(properties.size(), 4u);
 
+  const uint16_t kImplicitOuterLayerOrder =
+      CascadeLayerMap::kImplicitOuterLayerOrder;
+
   // div { display: block; }
   EXPECT_TRUE(properties[0].properties->HasProperty(CSSPropertyID::kDisplay));
-  EXPECT_EQ(0u, properties[0].types_.layer_order);
+  EXPECT_EQ(kImplicitOuterLayerOrder, properties[0].types_.layer_order);
   EXPECT_EQ(properties[0].types_.origin, CascadeOrigin::kUserAgent);
 
   // @layer foo { #a { font-size: 16px } }"
   EXPECT_TRUE(properties[1].properties->HasProperty(CSSPropertyID::kFontSize));
-  EXPECT_EQ(1u, properties[1].types_.layer_order);
+  EXPECT_EQ(0u, properties[1].types_.layer_order);
   EXPECT_EQ(properties[1].types_.origin, CascadeOrigin::kAuthor);
 
   // @layer bar { .b { color: green } }"
   EXPECT_TRUE(properties[2].properties->HasProperty(CSSPropertyID::kColor));
-  EXPECT_EQ(2u, properties[2].types_.layer_order);
+  EXPECT_EQ(1u, properties[2].types_.layer_order);
   EXPECT_EQ(properties[2].types_.origin, CascadeOrigin::kAuthor);
 
   // style="font-family: custom"
   EXPECT_TRUE(
       properties[3].properties->HasProperty(CSSPropertyID::kFontFamily));
-  EXPECT_EQ(0u, properties[3].types_.layer_order);
+  EXPECT_TRUE(properties[3].types_.is_inline_style);
   EXPECT_EQ(properties[3].types_.origin, CascadeOrigin::kAuthor);
+  // There's no layer order for inline style; it's always above all layers.
 }
 
 TEST_F(StyleResolverTest, CascadeLayersInDifferentTreeScopes) {
@@ -1488,21 +1495,24 @@
   const auto& properties = match_result.GetMatchedProperties();
   ASSERT_EQ(properties.size(), 3u);
 
+  const uint16_t kImplicitOuterLayerOrder =
+      CascadeLayerMap::kImplicitOuterLayerOrder;
+
   // div { display: block }
   EXPECT_TRUE(properties[0].properties->HasProperty(CSSPropertyID::kDisplay));
-  EXPECT_EQ(0u, properties[0].types_.layer_order);
+  EXPECT_EQ(kImplicitOuterLayerOrder, properties[0].types_.layer_order);
   EXPECT_EQ(properties[0].types_.origin, CascadeOrigin::kUserAgent);
 
   // @layer bar { :host { font-size: 16px } }
   EXPECT_TRUE(properties[1].properties->HasProperty(CSSPropertyID::kFontSize));
-  EXPECT_EQ(1u, properties[1].types_.layer_order);
+  EXPECT_EQ(0u, properties[1].types_.layer_order);
   EXPECT_EQ(properties[1].types_.origin, CascadeOrigin::kAuthor);
   EXPECT_EQ(match_result.ScopeFromTreeOrder(properties[1].types_.tree_order),
             GetDocument().getElementById("host")->GetShadowRoot());
 
   // @layer foo { #host { color: green } }
   EXPECT_TRUE(properties[2].properties->HasProperty(CSSPropertyID::kColor));
-  EXPECT_EQ(1u, properties[2].types_.layer_order);
+  EXPECT_EQ(0u, properties[2].types_.layer_order);
   EXPECT_EQ(match_result.ScopeFromTreeOrder(properties[2].types_.tree_order),
             &GetDocument());
 }
@@ -1514,10 +1524,10 @@
 
   GetDocument().documentElement()->setInnerHTML(R"HTML(
     <style>
+    @page { margin-top: 100px; }
     @layer {
-      @page { margin-top: 100px; }
+      @page { margin-top: 50px; }
     }
-    @page { margin-top: 50px; }
     </style>
   )HTML");
 
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index a44c8d9..0c8160c 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -4071,24 +4071,25 @@
 
   UpdateAllLifecyclePhases();
 
-  // User layer order: (implicit outer layer), foo, bar
+  // User layer order: foo, bar, (implicit outer layer)
   auto* user_layer_map = GetStyleEngine().GetUserCascadeLayerMap();
   ASSERT_TRUE(user_layer_map);
 
   const CascadeLayer& user_outer_layer =
       user_sheet->GetRuleSet().CascadeLayers();
   EXPECT_EQ("", user_outer_layer.GetName());
-  EXPECT_EQ(0u, user_layer_map->GetLayerOrder(user_outer_layer));
+  EXPECT_EQ(CascadeLayerMap::kImplicitOuterLayerOrder,
+            user_layer_map->GetLayerOrder(user_outer_layer));
 
   const CascadeLayer& user_foo = *user_outer_layer.GetDirectSubLayers()[0];
   EXPECT_EQ("foo", user_foo.GetName());
-  EXPECT_EQ(1u, user_layer_map->GetLayerOrder(user_foo));
+  EXPECT_EQ(0u, user_layer_map->GetLayerOrder(user_foo));
 
   const CascadeLayer& user_bar = *user_outer_layer.GetDirectSubLayers()[1];
   EXPECT_EQ("bar", user_bar.GetName());
-  EXPECT_EQ(2u, user_layer_map->GetLayerOrder(user_bar));
+  EXPECT_EQ(1u, user_layer_map->GetLayerOrder(user_bar));
 
-  // Document scope author layer order: (implicit outer layer), bar, foo
+  // Document scope author layer order: bar, foo, (implicit outer layer)
   auto* document_layer_map =
       GetDocument().GetScopedStyleResolver()->GetCascadeLayerMap();
   ASSERT_TRUE(document_layer_map);
@@ -4100,19 +4101,20 @@
           ->GetRuleSet()
           .CascadeLayers();
   EXPECT_EQ("", document_outer_layer.GetName());
-  EXPECT_EQ(0u, document_layer_map->GetLayerOrder(document_outer_layer));
+  EXPECT_EQ(CascadeLayerMap::kImplicitOuterLayerOrder,
+            document_layer_map->GetLayerOrder(document_outer_layer));
 
   const CascadeLayer& document_bar =
       *document_outer_layer.GetDirectSubLayers()[0];
   EXPECT_EQ("bar", document_bar.GetName());
-  EXPECT_EQ(1u, document_layer_map->GetLayerOrder(document_bar));
+  EXPECT_EQ(0u, document_layer_map->GetLayerOrder(document_bar));
 
   const CascadeLayer& document_foo =
       *document_outer_layer.GetDirectSubLayers()[1];
   EXPECT_EQ("foo", document_foo.GetName());
-  EXPECT_EQ(2u, document_layer_map->GetLayerOrder(document_foo));
+  EXPECT_EQ(1u, document_layer_map->GetLayerOrder(document_foo));
 
-  // Shadow scope author layer order: (implicit outer layer), foo, foo.baz, bar
+  // Shadow scope author layer order: foo.baz, foo, bar, (implicit outer layer)
   ShadowRoot* shadow = GetDocument().getElementById("host")->GetShadowRoot();
   auto* shadow_layer_map =
       shadow->GetScopedStyleResolver()->GetCascadeLayerMap();
@@ -4125,7 +4127,8 @@
           ->GetRuleSet()
           .CascadeLayers();
   EXPECT_EQ("", shadow_outer_layer.GetName());
-  EXPECT_EQ(0u, shadow_layer_map->GetLayerOrder(shadow_outer_layer));
+  EXPECT_EQ(CascadeLayerMap::kImplicitOuterLayerOrder,
+            shadow_layer_map->GetLayerOrder(shadow_outer_layer));
 
   const CascadeLayer& shadow_foo = *shadow_outer_layer.GetDirectSubLayers()[0];
   EXPECT_EQ("foo", shadow_foo.GetName());
@@ -4133,11 +4136,11 @@
 
   const CascadeLayer& shadow_foo_baz = *shadow_foo.GetDirectSubLayers()[0];
   EXPECT_EQ("baz", shadow_foo_baz.GetName());
-  EXPECT_EQ(2u, shadow_layer_map->GetLayerOrder(shadow_foo_baz));
+  EXPECT_EQ(0u, shadow_layer_map->GetLayerOrder(shadow_foo_baz));
 
   const CascadeLayer& shadow_bar = *shadow_outer_layer.GetDirectSubLayers()[1];
   EXPECT_EQ("bar", shadow_bar.GetName());
-  EXPECT_EQ(3u, shadow_layer_map->GetLayerOrder(shadow_bar));
+  EXPECT_EQ(2u, shadow_layer_map->GetLayerOrder(shadow_bar));
 }
 
 TEST_F(StyleEngineTest, CascadeLayersFromMultipleSheets) {
@@ -4156,7 +4159,7 @@
   UpdateAllLifecyclePhases();
 
   // Final layer ordering:
-  // (implicit outer layer), foo, foo.quux, bar, bar.qux, baz
+  // foo.quux, foo, bar.qux, bar, baz, (implicit outer layer)
   auto* layer_map =
       GetDocument().GetScopedStyleResolver()->GetCascadeLayerMap();
   ASSERT_TRUE(layer_map);
@@ -4168,7 +4171,8 @@
           ->GetRuleSet()
           .CascadeLayers();
   EXPECT_EQ("", sheet1_outer_layer.GetName());
-  EXPECT_EQ(0u, layer_map->GetLayerOrder(sheet1_outer_layer));
+  EXPECT_EQ(CascadeLayerMap::kImplicitOuterLayerOrder,
+            layer_map->GetLayerOrder(sheet1_outer_layer));
 
   const CascadeLayer& sheet1_foo = *sheet1_outer_layer.GetDirectSubLayers()[0];
   EXPECT_EQ("foo", sheet1_foo.GetName());
@@ -4185,11 +4189,12 @@
           ->GetRuleSet()
           .CascadeLayers();
   EXPECT_EQ("", sheet2_outer_layer.GetName());
-  EXPECT_EQ(0u, layer_map->GetLayerOrder(sheet2_outer_layer));
+  EXPECT_EQ(CascadeLayerMap::kImplicitOuterLayerOrder,
+            layer_map->GetLayerOrder(sheet2_outer_layer));
 
   const CascadeLayer& sheet2_baz = *sheet2_outer_layer.GetDirectSubLayers()[0];
   EXPECT_EQ("baz", sheet2_baz.GetName());
-  EXPECT_EQ(5u, layer_map->GetLayerOrder(sheet2_baz));
+  EXPECT_EQ(4u, layer_map->GetLayerOrder(sheet2_baz));
 
   const CascadeLayer& sheet2_bar = *sheet2_outer_layer.GetDirectSubLayers()[1];
   EXPECT_EQ("bar", sheet2_bar.GetName());
@@ -4197,7 +4202,7 @@
 
   const CascadeLayer& sheet2_bar_qux = *sheet2_bar.GetDirectSubLayers()[0];
   EXPECT_EQ("qux", sheet2_bar_qux.GetName());
-  EXPECT_EQ(4u, layer_map->GetLayerOrder(sheet2_bar_qux));
+  EXPECT_EQ(2u, layer_map->GetLayerOrder(sheet2_bar_qux));
 
   const CascadeLayer& sheet2_foo = *sheet2_outer_layer.GetDirectSubLayers()[2];
   EXPECT_EQ("foo", sheet2_foo.GetName());
@@ -4205,7 +4210,7 @@
 
   const CascadeLayer& sheet2_foo_quux = *sheet2_foo.GetDirectSubLayers()[0];
   EXPECT_EQ("quux", sheet2_foo_quux.GetName());
-  EXPECT_EQ(2u, layer_map->GetLayerOrder(sheet2_foo_quux));
+  EXPECT_EQ(0u, layer_map->GetLayerOrder(sheet2_foo_quux));
 }
 
 TEST_F(StyleEngineTest, CascadeLayersNotExplicitlyDeclared) {
diff --git a/third_party/blink/renderer/core/exported/web_array_buffer_converter.cc b/third_party/blink/renderer/core/exported/web_array_buffer_converter.cc
index 0008abc..f9f8032e 100644
--- a/third_party/blink/renderer/core/exported/web_array_buffer_converter.cc
+++ b/third_party/blink/renderer/core/exported/web_array_buffer_converter.cc
@@ -31,7 +31,7 @@
 #include "third_party/blink/public/web/web_array_buffer_converter.h"
 
 #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/layout/build.gni b/third_party/blink/renderer/core/layout/build.gni
index 6d20156..6727a66 100644
--- a/third_party/blink/renderer/core/layout/build.gni
+++ b/third_party/blink/renderer/core/layout/build.gni
@@ -322,6 +322,7 @@
   "ng/grid/layout_ng_grid.h",
   "ng/grid/layout_ng_grid_interface.h",
   "ng/grid/ng_grid_data.h",
+  "ng/grid/ng_grid_geometry.h",
   "ng/grid/ng_grid_layout_algorithm.cc",
   "ng/grid/ng_grid_layout_algorithm.h",
   "ng/grid/ng_grid_node.cc",
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
index ed21aee..3f21c78 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
@@ -322,6 +322,72 @@
   return space_builder.ToConstraintSpace();
 }
 
+NGConstraintSpace NGFlexLayoutAlgorithm::BuildSpaceForLayout(
+    const FlexItem& flex_item) const {
+  const ComputedStyle& child_style = flex_item.ng_input_node_.Style();
+  NGConstraintSpaceBuilder space_builder(ConstraintSpace(),
+                                         child_style.GetWritingDirection(),
+                                         /* is_new_fc */ true);
+  SetOrthogonalFallbackInlineSizeIfNeeded(Style(), flex_item.ng_input_node_,
+                                          &space_builder);
+  space_builder.SetIsPaintedAtomically(true);
+
+  LogicalSize available_size;
+  if (is_column_) {
+    available_size.inline_size = ChildAvailableSize().inline_size;
+    available_size.block_size =
+        flex_item.flexed_content_size_ + flex_item.main_axis_border_padding_;
+    space_builder.SetIsFixedBlockSize(true);
+    if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node_))
+      space_builder.SetInlineAutoBehavior(NGAutoBehavior::kStretchExplicit);
+    // https://drafts.csswg.org/css-flexbox/#definite-sizes
+    // If the flex container has a definite main size, a flex item's
+    // post-flexing main size is treated as definite, even though it can
+    // rely on the indefinite sizes of any flex items in the same line.
+    // TODO(crbug.com/1255340): This logic, and the similar below call to
+    // IsColumnContainerMainSizeDefinite need some refinement.
+    if (!IsColumnContainerMainSizeDefinite() &&
+        !IsItemFlexBasisDefinite(flex_item.ng_input_node_) &&
+        !IsItemMainSizeDefinite(flex_item.ng_input_node_)) {
+      space_builder.SetIsInitialBlockSizeIndefinite(true);
+    }
+  } else {
+    available_size.inline_size =
+        flex_item.flexed_content_size_ + flex_item.main_axis_border_padding_;
+    available_size.block_size = ChildAvailableSize().block_size;
+    space_builder.SetIsFixedInlineSize(true);
+    if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node_))
+      space_builder.SetBlockAutoBehavior(NGAutoBehavior::kStretchExplicit);
+  }
+  if (DoesItemStretch(flex_item.ng_input_node_)) {
+    // For stretched items, the goal of this layout is determine the
+    // post-flexed, pre-stretched cross-axis size. Stretched items will
+    // later get a final layout with a potentially different cross size so
+    // use the "measure" slot for this layout. We will use the "layout"
+    // cache slot for the item's final layout.
+    //
+    // Setting the "measure" cache slot on the space writes the result
+    // into both the "measure" and "layout" cache slots. So the stretch
+    // layout will reuse this "measure" result if it can.
+    space_builder.SetCacheSlot(NGCacheSlot::kMeasure);
+  }
+
+  space_builder.SetAvailableSize(available_size);
+  space_builder.SetPercentageResolutionSize(child_percentage_size_);
+  space_builder.SetReplacedPercentageResolutionSize(child_percentage_size_);
+
+  // For a button child, we need the baseline type same as the container's
+  // baseline type for UseCounter. For example, if the container's display
+  // property is 'inline-block', we need the last-line baseline of the
+  // child. See the bottom of GiveLinesAndItemsFinalPositionAndSize().
+  if (Node().IsButton()) {
+    space_builder.SetBaselineAlgorithmType(
+        ConstraintSpace().BaselineAlgorithmType());
+  }
+
+  return space_builder.ToConstraintSpace();
+}
+
 void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() {
   NGFlexChildIterator iterator(Node());
 
@@ -741,69 +807,8 @@
     }
     for (wtf_size_t i = 0; i < line->line_items_.size(); ++i) {
       FlexItem& flex_item = line->line_items_[i];
+      NGConstraintSpace child_space = BuildSpaceForLayout(flex_item);
 
-      const ComputedStyle& child_style = flex_item.ng_input_node_.Style();
-      NGConstraintSpaceBuilder space_builder(ConstraintSpace(),
-                                             child_style.GetWritingDirection(),
-                                             /* is_new_fc */ true);
-      SetOrthogonalFallbackInlineSizeIfNeeded(Style(), flex_item.ng_input_node_,
-                                              &space_builder);
-      space_builder.SetIsPaintedAtomically(true);
-
-      LogicalSize available_size;
-      if (is_column_) {
-        available_size.inline_size = ChildAvailableSize().inline_size;
-        available_size.block_size = flex_item.flexed_content_size_ +
-                                    flex_item.main_axis_border_padding_;
-        space_builder.SetIsFixedBlockSize(true);
-        if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node_))
-          space_builder.SetInlineAutoBehavior(NGAutoBehavior::kStretchExplicit);
-        // https://drafts.csswg.org/css-flexbox/#definite-sizes
-        // If the flex container has a definite main size, a flex item's
-        // post-flexing main size is treated as definite, even though it can
-        // rely on the indefinite sizes of any flex items in the same line.
-        // TODO(crbug.com/1255340): This logic, and the similar below call to
-        // IsColumnContainerMainSizeDefinite need some refinement.
-        if (!IsColumnContainerMainSizeDefinite() &&
-            !IsItemFlexBasisDefinite(flex_item.ng_input_node_) &&
-            !IsItemMainSizeDefinite(flex_item.ng_input_node_)) {
-          space_builder.SetIsInitialBlockSizeIndefinite(true);
-        }
-      } else {
-        available_size.inline_size = flex_item.flexed_content_size_ +
-                                     flex_item.main_axis_border_padding_;
-        available_size.block_size = ChildAvailableSize().block_size;
-        space_builder.SetIsFixedInlineSize(true);
-        if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node_))
-          space_builder.SetBlockAutoBehavior(NGAutoBehavior::kStretchExplicit);
-      }
-      if (DoesItemStretch(flex_item.ng_input_node_)) {
-        // For stretched items, the goal of this layout is determine the
-        // post-flexed, pre-stretched cross-axis size. Stretched items will
-        // later get a final layout with a potentially different cross size so
-        // use the "measure" slot for this layout. We will use the "layout"
-        // cache slot for the item's final layout.
-        //
-        // Setting the "measure" cache slot on the space writes the result
-        // into both the "measure" and "layout" cache slots. So the stretch
-        // layout will reuse this "measure" result if it can.
-        space_builder.SetCacheSlot(NGCacheSlot::kMeasure);
-      }
-
-      space_builder.SetAvailableSize(available_size);
-      space_builder.SetPercentageResolutionSize(child_percentage_size_);
-      space_builder.SetReplacedPercentageResolutionSize(child_percentage_size_);
-
-      // For a button child, we need the baseline type same as the container's
-      // baseline type for UseCounter. For example, if the container's display
-      // property is 'inline-block', we need the last-line baseline of the
-      // child. See the bottom of GiveLinesAndItemsFinalPositionAndSize().
-      if (Node().IsButton()) {
-        space_builder.SetBaselineAlgorithmType(
-            ConstraintSpace().BaselineAlgorithmType());
-      }
-
-      NGConstraintSpace child_space = space_builder.ToConstraintSpace();
       // We need to get the item's cross axis size given its new main size. If
       // the new main size is the item's inline size, then we have to do a
       // layout to get its new block size. But if the new main size is the
@@ -819,7 +824,8 @@
         DCHECK(!MainAxisIsInlineAxis(flex_item.ng_input_node_));
         NGBoxStrut border =
             ComputeBorders(child_space, flex_item.ng_input_node_);
-        NGBoxStrut padding = ComputePadding(child_space, child_style);
+        NGBoxStrut padding =
+            ComputePadding(child_space, flex_item.ng_input_node_.Style());
         if (flex_item.ng_input_node_.IsReplaced()) {
           LogicalSize logical_border_box_size = ComputeReplacedSize(
               flex_item.ng_input_node_, child_space, border + padding);
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
index 2f0704f..d9fc6c19 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
@@ -57,6 +57,7 @@
   NGConstraintSpace BuildSpaceForFlexBasis(const NGBlockNode& flex_item) const;
   NGConstraintSpace BuildSpaceForIntrinsicBlockSize(
       const NGBlockNode& flex_item) const;
+  NGConstraintSpace BuildSpaceForLayout(const FlexItem& flex_item) const;
   void ConstructAndAppendFlexItems();
   void ApplyStretchAlignmentToChild(FlexItem& flex_item);
   bool GiveLinesAndItemsFinalPositionAndSize();
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_geometry.h b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_geometry.h
new file mode 100644
index 0000000..c26d754
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_geometry.h
@@ -0,0 +1,100 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_GEOMETRY_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_GEOMETRY_H_
+
+#include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_data.h"
+#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+using SetOffsetData = NGGridData::SetData;
+
+// Contains the information about the start offset of the tracks, as well as
+// the gutter size between them, for a given direction.
+struct TrackGeometry {
+  LayoutUnit start_offset;
+  LayoutUnit gutter_size;
+};
+
+// Represents the offsets for the sets, and the gutter-size.
+//
+// Initially we only know some of the set sizes - others will be indefinite. To
+// represent this we store both the offset for the set, and a vector of all
+// last indefinite indices (or kNotFound if everything so far has been
+// definite). This allows us to get the appropriate size if a grid item spans
+// only fixed tracks, but will allow us to return an indefinite size if it
+// spans any indefinite set.
+//
+// As an example:
+//   grid-template-rows: auto auto 100px 100px auto 100px;
+//
+// Results in:
+//                  |  auto |  auto |   100   |   100   |   auto  |   100 |
+//   [{0, kNotFound}, {0, 0}, {0, 1}, {100, 1}, {200, 1}, {200, 4}, {300, 4}]
+//
+// Various queries (start/end refer to the grid lines):
+//  start: 0, end: 1 -> indefinite as:
+//    "start <= sets[end].last_indefinite_index"
+//  start: 1, end: 3 -> indefinite as:
+//    "start <= sets[end].last_indefinite_index"
+//  start: 2, end: 4 -> 200px
+//  start: 5, end: 6 -> 100px
+//  start: 3, end: 5 -> indefinite as:
+//    "start <= sets[end].last_indefinite_index"
+struct SetGeometry {
+  SetGeometry() = default;
+
+  SetGeometry(const TrackGeometry track_alignment_geometry,
+              const wtf_size_t set_count)
+      : gutter_size(track_alignment_geometry.gutter_size) {
+    sets.ReserveInitialCapacity(set_count);
+    sets.emplace_back(track_alignment_geometry.start_offset,
+                      /* track_count */ kNotFound);
+  }
+
+  SetGeometry(const Vector<SetOffsetData>& sets, const LayoutUnit gutter_size)
+      : sets(sets), gutter_size(gutter_size) {}
+
+  LayoutUnit FinalGutterSize() const {
+    DCHECK_GT(sets.size(), 0u);
+    return (sets.size() == 1) ? LayoutUnit() : gutter_size;
+  }
+
+  Vector<wtf_size_t> last_indefinite_indices;
+  Vector<SetOffsetData> sets;
+  LayoutUnit gutter_size;
+};
+
+// Contains all the geometry information relevant to the final grid. This
+// should have all the grid information needed to size and position items.
+struct NGGridGeometry {
+  NGGridGeometry(SetGeometry&& column_geometry, SetGeometry&& row_geometry)
+      : column_geometry(column_geometry),
+        row_geometry(row_geometry),
+        major_inline_baselines(column_geometry.sets.size(), LayoutUnit::Min()),
+        minor_inline_baselines(column_geometry.sets.size(), LayoutUnit::Min()),
+        major_block_baselines(row_geometry.sets.size(), LayoutUnit::Min()),
+        minor_block_baselines(row_geometry.sets.size(), LayoutUnit::Min()) {}
+
+  NGGridGeometry() = default;
+
+  const SetGeometry& Geometry(GridTrackSizingDirection track_direction) const {
+    return (track_direction == kForColumns) ? column_geometry : row_geometry;
+  }
+
+  SetGeometry column_geometry;
+  SetGeometry row_geometry;
+
+  Vector<LayoutUnit> major_inline_baselines;
+  Vector<LayoutUnit> minor_inline_baselines;
+  Vector<LayoutUnit> major_block_baselines;
+  Vector<LayoutUnit> minor_block_baselines;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_GEOMETRY_H_
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
index b907ffc..a9fa795 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
@@ -92,8 +92,6 @@
 
 namespace {
 
-using SetGeometry = NGGridLayoutAlgorithm::SetGeometry;
-
 LayoutUnit ComputeSetSpanSize(const SetGeometry& set_geometry,
                               const GridItemIndices& set_indices) {
   DCHECK_LT(set_indices.end, set_geometry.sets.size());
@@ -151,8 +149,7 @@
   ConstructAndAppendGridItems(&grid_items, &grid_properties,
                               &out_of_flow_items);
 
-  const auto& container_style = Style();
-  NGGridPlacement grid_placement(container_style,
+  NGGridPlacement grid_placement(Style(),
                                  ComputeAutomaticRepetitions(kForColumns),
                                  ComputeAutomaticRepetitions(kForRows));
 
@@ -162,186 +159,21 @@
   BuildBlockTrackCollections(&grid_items, &column_block_track_collection,
                              &row_block_track_collection, &grid_placement);
 
-  GridGeometry grid_geometry;
   NGGridLayoutAlgorithmTrackCollection column_track_collection;
   NGGridLayoutAlgorithmTrackCollection row_track_collection;
-
-  auto ComputeGrid = [&]() {
-    // Build algorithm track collections from the block track collections.
-    column_track_collection = NGGridLayoutAlgorithmTrackCollection(
-        column_block_track_collection,
-        grid_available_size_.inline_size == kIndefiniteSize, &grid_properties);
-    row_track_collection = NGGridLayoutAlgorithmTrackCollection(
-        row_block_track_collection,
-        grid_available_size_.block_size == kIndefiniteSize, &grid_properties);
-
-    // Cache set indices for grid items.
-    for (auto& grid_item : grid_items.item_data) {
-      grid_item.ComputeSetIndices(column_track_collection);
-      grid_item.ComputeSetIndices(row_track_collection);
-    }
-
-    // Cache track span properties for grid items.
-    CacheGridItemsTrackSpanProperties(column_track_collection, &grid_items);
-    CacheGridItemsTrackSpanProperties(row_track_collection, &grid_items);
-
-    // We perform the track sizing algorithm using two methods. First
-    // |InitializeTrackSizes|, which we need to get an initial column and row
-    // set geometry. Then |ComputeUsedTrackSizes|, to finalize the sizing
-    // algorithm for both dimensions.
-    grid_geometry = GridGeometry(InitializeTrackSizes(&column_track_collection),
-                                 InitializeTrackSizes(&row_track_collection));
-
-    // Store column baselines, as these contributions can influence column
-    // sizing.
-    bool needs_additional_pass = false;
-    if (grid_properties.HasBaseline(kForColumns)) {
-      CalculateAlignmentBaselines(kForColumns, &grid_geometry, &grid_items,
-                                  &needs_additional_pass);
-    }
-
-    // Resolve inline size.
-    bool has_block_size_dependent_item = false;
-    grid_geometry.column_geometry = ComputeUsedTrackSizes(
-        SizingConstraint::kLayout, grid_geometry, grid_properties,
-        /* is_min_max_pass */ false, &column_track_collection, &grid_items,
-        &needs_additional_pass, &has_block_size_dependent_item);
-
-    if (grid_properties.HasBaseline(kForRows)) {
-      CalculateAlignmentBaselines(kForRows, &grid_geometry, &grid_items,
-                                  &needs_additional_pass);
-    }
-
-    absl::optional<SetGeometry> initial_row_geometry;
-    if (!needs_additional_pass && has_block_size_dependent_item)
-      initial_row_geometry = grid_geometry.row_geometry;
-
-    // Resolve block size.
-    bool unused_needs_additional_pass = false;
-    grid_geometry.row_geometry = ComputeUsedTrackSizes(
-        SizingConstraint::kLayout, grid_geometry, grid_properties,
-        /* is_min_max_pass */ false, &row_track_collection, &grid_items,
-        &unused_needs_additional_pass);
-
-    if (initial_row_geometry) {
-      DCHECK(!needs_additional_pass && has_block_size_dependent_item);
-      needs_additional_pass = MayChangeOrthogonalItemContributions(
-          *initial_row_geometry, grid_geometry.row_geometry, grid_items,
-          container_style.GetWritingMode());
-    }
-
-    // If we had an orthogonal item which may have depended on the resolved row
-    // tracks, re-run the track sizing algorithm for both dimensions.
-    if (needs_additional_pass) {
-      if (grid_properties.HasBaseline(kForColumns)) {
-        CalculateAlignmentBaselines(kForColumns, &grid_geometry, &grid_items,
-                                    &unused_needs_additional_pass);
-      }
-
-      grid_geometry.column_geometry =
-          InitializeTrackSizes(&column_track_collection);
-      grid_geometry.column_geometry = ComputeUsedTrackSizes(
-          SizingConstraint::kLayout, grid_geometry, grid_properties,
-          /* is_min_max_pass */ false, &column_track_collection, &grid_items,
-          &unused_needs_additional_pass);
-
-      if (grid_properties.HasBaseline(kForRows)) {
-        CalculateAlignmentBaselines(kForRows, &grid_geometry, &grid_items,
-                                    &unused_needs_additional_pass);
-      }
-
-      grid_geometry.row_geometry = InitializeTrackSizes(&row_track_collection);
-      grid_geometry.row_geometry = ComputeUsedTrackSizes(
-          SizingConstraint::kLayout, grid_geometry, grid_properties,
-          /* is_min_max_pass */ false, &row_track_collection, &grid_items,
-          &unused_needs_additional_pass);
-    }
-
-    if (grid_properties.HasBaseline(kForColumns)) {
-      CalculateAlignmentBaselines(kForColumns, &grid_geometry, &grid_items,
-                                  &unused_needs_additional_pass);
-    }
-    if (grid_properties.HasBaseline(kForRows)) {
-      CalculateAlignmentBaselines(kForRows, &grid_geometry, &grid_items,
-                                  &unused_needs_additional_pass);
-    }
-    DCHECK(!unused_needs_additional_pass);
-  };
-
-  ComputeGrid();
-
   LayoutUnit intrinsic_block_size;
-  if (contain_intrinsic_block_size_) {
-    intrinsic_block_size = *contain_intrinsic_block_size_;
-  } else {
-    // Intrinsic block size is based on the final row offset. Because gutters
-    // are included in row offsets, subtract out the final gutter (if present).
-    intrinsic_block_size = grid_geometry.row_geometry.sets.back().offset -
-                           grid_geometry.row_geometry.FinalGutterSize() +
-                           BorderScrollbarPadding().block_end;
 
-    // TODO(layout-dev): This isn't great but matches legacy. Ideally this
-    // would only apply when we have only flexible track(s).
-    if (grid_items.IsEmpty() && Node().HasLineIfEmpty()) {
-      intrinsic_block_size =
-          std::max(intrinsic_block_size, BorderScrollbarPadding().BlockSum() +
-                                             Node().EmptyLineBlockSize());
-    }
-
-    intrinsic_block_size =
-        ClampIntrinsicBlockSize(ConstraintSpace(), Node(),
-                                BorderScrollbarPadding(), intrinsic_block_size);
-  }
-
-  const LayoutUnit block_size = ComputeBlockSizeForFragment(
-      ConstraintSpace(), container_style, BorderPadding(), intrinsic_block_size,
-      border_box_size_.inline_size);
-
-  if (grid_available_size_.block_size == kIndefiniteSize) {
-    const LayoutUnit resolved_available_block_size =
-        (block_size - BorderScrollbarPadding().BlockSum())
-            .ClampNegativeToZero();
-
-    grid_available_size_.block_size = grid_min_available_size_.block_size =
-        grid_max_available_size_.block_size = resolved_available_block_size;
-
-    // Re-compute the row geometry now that we have the resolved available
-    // block-size. "align-content: space-evenly" etc, require the resolved size.
-    if (container_style.AlignContent() !=
-        ComputedStyleInitialValues::InitialAlignContent()) {
-      grid_geometry.row_geometry = ComputeSetGeometry(row_track_collection);
-    }
-
-    // If we have any rows, gaps which will resolve differently if we have a
-    // definite |grid_available_size_| re-compute the grid using the
-    // |block_size| calculated above.
-    bool should_recompute_grid =
-        (container_style.RowGap() &&
-         container_style.RowGap()->IsPercentOrCalc()) ||
-        row_track_collection.DependsOnAvailableSize();
-
-    // If we are a flex-item, we may have our initial block-size forced to be
-    // indefinite, however grid layout always re-computes the grid using the
-    // final "used" block-size.
-    // We can detect this case by checking if computing our block-size (with an
-    // indefinite intrinsic size) is definite.
-    //
-    // TODO(layout-dev): A small optimization here would be to do this only if
-    // we have 'auto' tracks which fill the remaining available space.
-    if (ConstraintSpace().IsInitialBlockSizeIndefinite()) {
-      should_recompute_grid |=
-          ComputeBlockSizeForFragment(
-              ConstraintSpace(), container_style, BorderPadding(),
-              /* intrinsic_block_size */ kIndefiniteSize,
-              border_box_size_.inline_size) != kIndefiniteSize;
-    }
-
-    if (should_recompute_grid)
-      ComputeGrid();
-  }
+  const NGGridGeometry grid_geometry = ComputeGridGeometry(
+      column_block_track_collection, row_block_track_collection, &grid_items,
+      &column_track_collection, &row_track_collection, &grid_properties,
+      &intrinsic_block_size);
 
   PlaceGridItems(grid_items, grid_geometry);
 
+  const LayoutUnit block_size = ComputeBlockSizeForFragment(
+      ConstraintSpace(), Style(), BorderPadding(), intrinsic_block_size,
+      border_box_size_.inline_size);
+
   // Cache range placement data for out of flow items.
   for (auto& out_of_flow_item : out_of_flow_items) {
     out_of_flow_item.ComputeOutOfFlowItemPlacement(column_track_collection,
@@ -474,8 +306,8 @@
 
   auto ComputeTotalColumnSize =
       [&](SizingConstraint sizing_constraint) -> LayoutUnit {
-    GridGeometry grid_geometry(InitializeTrackSizes(&column_track_collection),
-                               InitializeTrackSizes(&row_track_collection));
+    NGGridGeometry grid_geometry(InitializeTrackSizes(&column_track_collection),
+                                 InitializeTrackSizes(&row_track_collection));
 
     bool needs_additional_pass = false;
     if (grid_properties.HasBaseline(kForColumns)) {
@@ -785,12 +617,6 @@
   item_data.ReserveCapacity(capacity);
 }
 
-const NGGridLayoutAlgorithm::SetGeometry&
-NGGridLayoutAlgorithm::GridGeometry::Geometry(
-    GridTrackSizingDirection track_direction) const {
-  return (track_direction == kForColumns) ? column_geometry : row_geometry;
-}
-
 namespace {
 
 using SetIterator = NGGridLayoutAlgorithmTrackCollection::SetIterator;
@@ -925,52 +751,219 @@
 
 }  // namespace
 
-void NGGridLayoutAlgorithm::GridGeometry::UpdateBaseline(
-    const GridItemData& grid_item,
-    LayoutUnit candidate_baseline,
-    GridTrackSizingDirection track_direction) {
-  LayoutUnit* track_baseline;
-  if (track_direction == kForColumns) {
-    // "If a box spans multiple shared alignment contexts, then it participates
-    // in first/last baseline alignment within its start-most/end-most shared
-    // alignment context along that axis", so we only need to look at the first
-    // index for baseline/first-baseline support.
-    // https://www.w3.org/TR/css-align-3/#baseline-sharing-group
-    const wtf_size_t set_index = grid_item.column_set_indices.begin;
-    track_baseline = (grid_item.column_baseline_type == BaselineType::kMajor)
-                         ? &major_inline_baselines[set_index]
-                         : &minor_inline_baselines[set_index];
-  } else {
-    const wtf_size_t set_index = grid_item.row_set_indices.begin;
-    track_baseline = (grid_item.row_baseline_type == BaselineType::kMajor)
-                         ? &major_block_baselines[set_index]
-                         : &minor_block_baselines[set_index];
-  }
-
-  *track_baseline = std::max(*track_baseline, candidate_baseline);
-}
-
-LayoutUnit NGGridLayoutAlgorithm::GridGeometry::Baseline(
+LayoutUnit NGGridLayoutAlgorithm::Baseline(
+    const NGGridGeometry& grid_geometry,
     const GridItemData& grid_item,
     GridTrackSizingDirection track_direction) const {
+  // "If a box spans multiple shared alignment contexts, then it participates
+  // in first/last baseline alignment within its start-most/end-most shared
+  // alignment context along that axis", so we only need to look at the first
+  // index for baseline/first-baseline support.
+  // https://www.w3.org/TR/css-align-3/#baseline-sharing-group
   if (track_direction == kForColumns) {
-    // "If a box spans multiple shared alignment contexts, then it participates
-    // in first/last baseline alignment within its start-most/end-most shared
-    // alignment context along that axis", so we only need to look at the first
-    // index for baseline/first-baseline support.
-    // https://www.w3.org/TR/css-align-3/#baseline-sharing-group
     const wtf_size_t set_index = grid_item.column_set_indices.begin;
     return (grid_item.column_baseline_type == BaselineType::kMajor)
-               ? major_inline_baselines[set_index]
-               : minor_inline_baselines[set_index];
+               ? grid_geometry.major_inline_baselines[set_index]
+               : grid_geometry.minor_inline_baselines[set_index];
   } else {
     const wtf_size_t set_index = grid_item.row_set_indices.begin;
     return (grid_item.row_baseline_type == BaselineType::kMajor)
-               ? major_block_baselines[set_index]
-               : minor_block_baselines[set_index];
+               ? grid_geometry.major_block_baselines[set_index]
+               : grid_geometry.minor_block_baselines[set_index];
   }
 }
 
+NGGridGeometry NGGridLayoutAlgorithm::ComputeGridGeometry(
+    const NGGridBlockTrackCollection& column_block_track_collection,
+    const NGGridBlockTrackCollection& row_block_track_collection,
+    GridItems* grid_items,
+    NGGridLayoutAlgorithmTrackCollection* column_track_collection,
+    NGGridLayoutAlgorithmTrackCollection* row_track_collection,
+    NGGridProperties* grid_properties,
+    LayoutUnit* intrinsic_block_size) {
+  DCHECK(grid_items && column_track_collection && row_track_collection &&
+         grid_properties && intrinsic_block_size);
+
+  const auto& container_style = Style();
+  NGGridGeometry grid_geometry;
+
+  auto ComputeGrid = [&]() {
+    // Build algorithm track collections from the block track collections.
+    *column_track_collection = NGGridLayoutAlgorithmTrackCollection(
+        column_block_track_collection,
+        grid_available_size_.inline_size == kIndefiniteSize, grid_properties);
+    *row_track_collection = NGGridLayoutAlgorithmTrackCollection(
+        row_block_track_collection,
+        grid_available_size_.block_size == kIndefiniteSize, grid_properties);
+
+    // Cache set indices for grid items.
+    for (auto& grid_item : grid_items->item_data) {
+      grid_item.ComputeSetIndices(*column_track_collection);
+      grid_item.ComputeSetIndices(*row_track_collection);
+    }
+
+    // Cache track span properties for grid items.
+    CacheGridItemsTrackSpanProperties(*column_track_collection, grid_items);
+    CacheGridItemsTrackSpanProperties(*row_track_collection, grid_items);
+
+    // We perform the track sizing algorithm using two methods. First
+    // |InitializeTrackSizes|, which we need to get an initial column and row
+    // set geometry. Then |ComputeUsedTrackSizes|, to finalize the sizing
+    // algorithm for both dimensions.
+    grid_geometry =
+        NGGridGeometry(InitializeTrackSizes(column_track_collection),
+                       InitializeTrackSizes(row_track_collection));
+
+    // Store column baselines, as these contributions can influence column
+    // sizing.
+    bool needs_additional_pass = false;
+    if (grid_properties->HasBaseline(kForColumns)) {
+      CalculateAlignmentBaselines(kForColumns, &grid_geometry, grid_items,
+                                  &needs_additional_pass);
+    }
+
+    // Resolve inline size.
+    bool has_block_size_dependent_item = false;
+    grid_geometry.column_geometry = ComputeUsedTrackSizes(
+        SizingConstraint::kLayout, grid_geometry, *grid_properties,
+        /* is_min_max_pass */ false, column_track_collection, grid_items,
+        &needs_additional_pass, &has_block_size_dependent_item);
+
+    if (grid_properties->HasBaseline(kForRows)) {
+      CalculateAlignmentBaselines(kForRows, &grid_geometry, grid_items,
+                                  &needs_additional_pass);
+    }
+
+    absl::optional<SetGeometry> initial_row_geometry;
+    if (!needs_additional_pass && has_block_size_dependent_item)
+      initial_row_geometry = grid_geometry.row_geometry;
+
+    // Resolve block size.
+    bool unused_needs_additional_pass = false;
+    grid_geometry.row_geometry = ComputeUsedTrackSizes(
+        SizingConstraint::kLayout, grid_geometry, *grid_properties,
+        /* is_min_max_pass */ false, row_track_collection, grid_items,
+        &unused_needs_additional_pass);
+
+    if (initial_row_geometry) {
+      DCHECK(!needs_additional_pass && has_block_size_dependent_item);
+      needs_additional_pass = MayChangeOrthogonalItemContributions(
+          *initial_row_geometry, grid_geometry.row_geometry, *grid_items,
+          container_style.GetWritingMode());
+    }
+
+    // If we had an orthogonal item which may have depended on the resolved row
+    // tracks, re-run the track sizing algorithm for both dimensions.
+    if (needs_additional_pass) {
+      if (grid_properties->HasBaseline(kForColumns)) {
+        CalculateAlignmentBaselines(kForColumns, &grid_geometry, grid_items,
+                                    &unused_needs_additional_pass);
+      }
+
+      grid_geometry.column_geometry =
+          InitializeTrackSizes(column_track_collection);
+      grid_geometry.column_geometry = ComputeUsedTrackSizes(
+          SizingConstraint::kLayout, grid_geometry, *grid_properties,
+          /* is_min_max_pass */ false, column_track_collection, grid_items,
+          &unused_needs_additional_pass);
+
+      if (grid_properties->HasBaseline(kForRows)) {
+        CalculateAlignmentBaselines(kForRows, &grid_geometry, grid_items,
+                                    &unused_needs_additional_pass);
+      }
+
+      grid_geometry.row_geometry = InitializeTrackSizes(row_track_collection);
+      grid_geometry.row_geometry = ComputeUsedTrackSizes(
+          SizingConstraint::kLayout, grid_geometry, *grid_properties,
+          /* is_min_max_pass */ false, row_track_collection, grid_items,
+          &unused_needs_additional_pass);
+    }
+
+    if (grid_properties->HasBaseline(kForColumns)) {
+      CalculateAlignmentBaselines(kForColumns, &grid_geometry, grid_items,
+                                  &unused_needs_additional_pass);
+    }
+    if (grid_properties->HasBaseline(kForRows)) {
+      CalculateAlignmentBaselines(kForRows, &grid_geometry, grid_items,
+                                  &unused_needs_additional_pass);
+    }
+    DCHECK(!unused_needs_additional_pass);
+  };
+
+  ComputeGrid();
+
+  if (contain_intrinsic_block_size_) {
+    *intrinsic_block_size = *contain_intrinsic_block_size_;
+  } else {
+    // Intrinsic block size is based on the final row offset. Because gutters
+    // are included in row offsets, subtract out the final gutter (if present).
+    *intrinsic_block_size = grid_geometry.row_geometry.sets.back().offset -
+                            grid_geometry.row_geometry.FinalGutterSize() +
+                            BorderScrollbarPadding().block_end;
+
+    // TODO(layout-dev): This isn't great but matches legacy. Ideally this
+    // would only apply when we have only flexible track(s).
+    if (grid_items->IsEmpty() && Node().HasLineIfEmpty()) {
+      *intrinsic_block_size =
+          std::max(*intrinsic_block_size, BorderScrollbarPadding().BlockSum() +
+                                              Node().EmptyLineBlockSize());
+    }
+
+    *intrinsic_block_size = ClampIntrinsicBlockSize(ConstraintSpace(), Node(),
+                                                    BorderScrollbarPadding(),
+                                                    *intrinsic_block_size);
+  }
+
+  const LayoutUnit block_size = ComputeBlockSizeForFragment(
+      ConstraintSpace(), container_style, BorderPadding(),
+      *intrinsic_block_size, border_box_size_.inline_size);
+
+  if (grid_available_size_.block_size == kIndefiniteSize) {
+    const LayoutUnit resolved_available_block_size =
+        (block_size - BorderScrollbarPadding().BlockSum())
+            .ClampNegativeToZero();
+
+    grid_available_size_.block_size = grid_min_available_size_.block_size =
+        grid_max_available_size_.block_size = resolved_available_block_size;
+
+    // Re-compute the row geometry now that we have the resolved available
+    // block-size. "align-content: space-evenly" etc, require the resolved size.
+    if (container_style.AlignContent() !=
+        ComputedStyleInitialValues::InitialAlignContent()) {
+      grid_geometry.row_geometry = ComputeSetGeometry(*row_track_collection);
+    }
+
+    // If we have any rows, gaps which will resolve differently if we have a
+    // definite |grid_available_size_| re-compute the grid using the
+    // |block_size| calculated above.
+    bool should_recompute_grid =
+        (container_style.RowGap() &&
+         container_style.RowGap()->IsPercentOrCalc()) ||
+        row_track_collection->DependsOnAvailableSize();
+
+    // If we are a flex-item, we may have our initial block-size forced to be
+    // indefinite, however grid layout always re-computes the grid using the
+    // final "used" block-size.
+    // We can detect this case by checking if computing our block-size (with an
+    // indefinite intrinsic size) is definite.
+    //
+    // TODO(layout-dev): A small optimization here would be to do this only if
+    // we have 'auto' tracks which fill the remaining available space.
+    if (ConstraintSpace().IsInitialBlockSizeIndefinite()) {
+      should_recompute_grid |=
+          ComputeBlockSizeForFragment(
+              ConstraintSpace(), container_style, BorderPadding(),
+              /* intrinsic_block_size */ kIndefiniteSize,
+              border_box_size_.inline_size) != kIndefiniteSize;
+    }
+
+    if (should_recompute_grid)
+      ComputeGrid();
+  }
+
+  return grid_geometry;
+}
+
 LayoutUnit NGGridLayoutAlgorithm::ComputeIntrinsicBlockSizeIgnoringChildren()
     const {
   DCHECK(Node().ShouldApplyBlockSizeContainment());
@@ -999,8 +992,8 @@
       row_block_track_collection,
       grid_available_size_.block_size == kIndefiniteSize, &grid_properties);
 
-  GridGeometry grid_geometry(SetGeometry(),
-                             InitializeTrackSizes(&row_track_collection));
+  NGGridGeometry grid_geometry(SetGeometry(),
+                               InitializeTrackSizes(&row_track_collection));
 
   // Resolve the rows.
   bool unused_needs_additional_pass = false;
@@ -1015,7 +1008,7 @@
 }
 
 LayoutUnit NGGridLayoutAlgorithm::ContributionSizeForGridItem(
-    const GridGeometry& grid_geometry,
+    const NGGridGeometry& grid_geometry,
     const GridItemData& grid_item,
     GridTrackSizingDirection track_direction,
     GridItemContributionType contribution_type,
@@ -1106,13 +1099,13 @@
 
     if (grid_item.IsBaselineAlignedForDirection(track_direction)) {
       LayoutUnit track_baseline =
-          grid_geometry.Baseline(grid_item, track_direction);
+          Baseline(grid_geometry, grid_item, track_direction);
 
       // The item's baseline alignment impacts the item's contribution as the
       // difference between the track's baseline and the item's baseline.
       if (track_baseline != LayoutUnit::Min()) {
         baseline_shim =
-            grid_geometry.Baseline(grid_item, track_direction) -
+            track_baseline -
             GetLogicalBaseline(
                 fragment, track_direction,
                 ConstraintSpace().GetWritingDirection().GetWritingMode());
@@ -1846,11 +1839,34 @@
 
 void NGGridLayoutAlgorithm::CalculateAlignmentBaselines(
     const GridTrackSizingDirection track_direction,
-    GridGeometry* grid_geometry,
+    NGGridGeometry* grid_geometry,
     GridItems* grid_items,
     bool* needs_additional_pass) const {
   DCHECK(grid_geometry && grid_items && needs_additional_pass);
 
+  auto UpdateBaseline = [&](const GridItemData& grid_item,
+                            LayoutUnit candidate_baseline) {
+    // "If a box spans multiple shared alignment contexts, then it participates
+    // in first/last baseline alignment within its start-most/end-most shared
+    // alignment context along that axis", so we only need to look at the first
+    // index for baseline/first-baseline support.
+    // https://www.w3.org/TR/css-align-3/#baseline-sharing-group
+    LayoutUnit* track_baseline;
+    if (track_direction == kForColumns) {
+      const wtf_size_t set_index = grid_item.column_set_indices.begin;
+      track_baseline = (grid_item.column_baseline_type == BaselineType::kMajor)
+                           ? &grid_geometry->major_inline_baselines[set_index]
+                           : &grid_geometry->minor_inline_baselines[set_index];
+    } else {
+      const wtf_size_t set_index = grid_item.row_set_indices.begin;
+      track_baseline = (grid_item.row_baseline_type == BaselineType::kMajor)
+                           ? &grid_geometry->major_block_baselines[set_index]
+                           : &grid_geometry->minor_block_baselines[set_index];
+    }
+
+    *track_baseline = std::max(*track_baseline, candidate_baseline);
+  };
+
   // Reset existing baselines from geometry so they are clean with each call to
   // this method. Use 'WTF::Vector::Fill()' over 'WTF::Vector::clear()', as
   // 'clear' will reset the capacity to zero and require re-allocations.
@@ -1906,13 +1922,13 @@
     // a better solution.
     if (grid_item.IsBaselineAlignedForDirection(track_direction) ||
         grid_item.node.IsReplaced()) {
-      grid_geometry->UpdateBaseline(grid_item, baseline, track_direction);
+      UpdateBaseline(grid_item, baseline);
     }
   }
 }
 
 // https://drafts.csswg.org/css-grid-2/#algo-init
-NGGridLayoutAlgorithm::SetGeometry NGGridLayoutAlgorithm::InitializeTrackSizes(
+SetGeometry NGGridLayoutAlgorithm::InitializeTrackSizes(
     NGGridLayoutAlgorithmTrackCollection* track_collection) const {
   DCHECK(track_collection);
   const auto track_direction = track_collection->Direction();
@@ -2003,9 +2019,9 @@
 }
 
 // https://drafts.csswg.org/css-grid-2/#algo-track-sizing
-NGGridLayoutAlgorithm::SetGeometry NGGridLayoutAlgorithm::ComputeUsedTrackSizes(
+SetGeometry NGGridLayoutAlgorithm::ComputeUsedTrackSizes(
     SizingConstraint sizing_constraint,
-    const GridGeometry& grid_geometry,
+    const NGGridGeometry& grid_geometry,
     const NGGridProperties& grid_properties,
     const bool is_min_max_pass,
     NGGridLayoutAlgorithmTrackCollection* track_collection,
@@ -2491,7 +2507,7 @@
 }  // namespace
 
 void NGGridLayoutAlgorithm::IncreaseTrackSizesToAccommodateGridItems(
-    const GridGeometry& grid_geometry,
+    const NGGridGeometry& grid_geometry,
     GridItems::Iterator group_begin,
     GridItems::Iterator group_end,
     const bool is_group_spanning_flex_track,
@@ -2606,7 +2622,7 @@
 
 // https://drafts.csswg.org/css-grid-2/#algo-content
 void NGGridLayoutAlgorithm::ResolveIntrinsicTrackSizes(
-    const GridGeometry& grid_geometry,
+    const NGGridGeometry& grid_geometry,
     bool is_min_max_pass,
     NGGridLayoutAlgorithmTrackCollection* track_collection,
     GridItems* grid_items,
@@ -2825,7 +2841,7 @@
 // https://drafts.csswg.org/css-grid-2/#algo-flex-tracks
 void NGGridLayoutAlgorithm::ExpandFlexibleTracks(
     SizingConstraint sizing_constraint,
-    const GridGeometry& grid_geometry,
+    const NGGridGeometry& grid_geometry,
     bool is_min_max_pass,
     NGGridLayoutAlgorithmTrackCollection* track_collection,
     GridItems* grid_items,
@@ -3017,8 +3033,6 @@
 
 namespace {
 
-using TrackGeometry = NGGridLayoutAlgorithm::TrackGeometry;
-
 TrackGeometry ComputeFirstTrackInCollectionGeometry(
     const ComputedStyle& style,
     const StyleContentAlignmentData& content_alignment,
@@ -3132,7 +3146,7 @@
 }  // namespace
 
 // Calculates the offsets for all sets.
-NGGridLayoutAlgorithm::SetGeometry NGGridLayoutAlgorithm::ComputeSetGeometry(
+SetGeometry NGGridLayoutAlgorithm::ComputeSetGeometry(
     const NGGridLayoutAlgorithmTrackCollection& track_collection) const {
   const LayoutUnit available_size = track_collection.IsForColumns()
                                         ? grid_available_size_.inline_size
@@ -3303,7 +3317,7 @@
 }
 
 const NGConstraintSpace NGGridLayoutAlgorithm::CreateConstraintSpaceForLayout(
-    const GridGeometry& grid_geometry,
+    const NGGridGeometry& grid_geometry,
     const GridItemData& grid_item,
     LogicalRect* containing_grid_area) const {
   ComputeGridItemOffsetAndSize(grid_item, grid_geometry.column_geometry,
@@ -3319,7 +3333,7 @@
 }
 
 const NGConstraintSpace NGGridLayoutAlgorithm::CreateConstraintSpaceForMeasure(
-    const GridGeometry& grid_geometry,
+    const NGGridGeometry& grid_geometry,
     const GridItemData& grid_item,
     GridTrackSizingDirection track_direction,
     absl::optional<LayoutUnit> opt_fixed_block_size) const {
@@ -3339,8 +3353,9 @@
                                opt_fixed_block_size, NGCacheSlot::kMeasure);
 }
 
-void NGGridLayoutAlgorithm::PlaceGridItems(const GridItems& grid_items,
-                                           const GridGeometry& grid_geometry) {
+void NGGridLayoutAlgorithm::PlaceGridItems(
+    const GridItems& grid_items,
+    const NGGridGeometry& grid_geometry) {
   const auto& container_space = ConstraintSpace();
   const auto container_writing_direction =
       container_space.GetWritingDirection();
@@ -3382,7 +3397,7 @@
             logical_fragment, track_direction,
             ConstraintSpace().GetWritingDirection().GetWritingMode());
         LayoutUnit track_baseline =
-            grid_geometry.Baseline(grid_item, track_direction);
+            Baseline(grid_geometry, grid_item, track_direction);
         return (track_baseline != LayoutUnit::Min())
                    ? (track_baseline - item_baseline)
                    : item_baseline;
@@ -3466,7 +3481,7 @@
     const NGGridLayoutAlgorithmTrackCollection& column_track_collection,
     const NGGridLayoutAlgorithmTrackCollection& row_track_collection,
     const GridItemStorageVector& out_of_flow_items,
-    const GridGeometry& grid_geometry,
+    const NGGridGeometry& grid_geometry,
     LayoutUnit block_size) {
   const LogicalSize fragment_size(container_builder_.InlineSize(), block_size);
   const LogicalSize default_containing_block_size =
@@ -3547,7 +3562,7 @@
   out_of_flow_item.ComputeOutOfFlowItemPlacement(row_placement_track_collection,
                                                  grid_placement);
 
-  GridGeometry grid_geometry(
+  NGGridGeometry grid_geometry(
       SetGeometry(column_geometry.sets, column_geometry.gutter_size),
       SetGeometry(row_geometry.sets, row_geometry.gutter_size));
 
@@ -3560,7 +3575,7 @@
 LogicalRect NGGridLayoutAlgorithm::ComputeContainingGridAreaRect(
     const NGGridLayoutAlgorithmTrackCollection& column_track_collection,
     const NGGridLayoutAlgorithmTrackCollection& row_track_collection,
-    const GridGeometry& grid_geometry,
+    const NGGridGeometry& grid_geometry,
     const GridItemData& out_of_flow_item,
     const NGBoxStrut& borders,
     const LogicalSize& border_box_size,
@@ -3582,7 +3597,7 @@
 namespace {
 
 Vector<std::div_t> ComputeTrackSizesInRange(
-    const NGGridLayoutAlgorithm::SetGeometry& set_geometry,
+    const SetGeometry& set_geometry,
     const wtf_size_t range_starting_set_index,
     const wtf_size_t range_set_count) {
   Vector<std::div_t> track_sizes;
@@ -3612,11 +3627,10 @@
 
 // For out of flow items that are located in the middle of a range, computes
 // the extra offset relative to the start of its containing range.
-LayoutUnit ComputeTrackOffsetInRange(
-    const NGGridLayoutAlgorithm::SetGeometry& set_geometry,
-    const wtf_size_t range_starting_set_index,
-    const wtf_size_t range_set_count,
-    const wtf_size_t offset_in_range) {
+LayoutUnit ComputeTrackOffsetInRange(const SetGeometry& set_geometry,
+                                     const wtf_size_t range_starting_set_index,
+                                     const wtf_size_t range_set_count,
+                                     const wtf_size_t offset_in_range) {
   if (!range_set_count || !offset_in_range)
     return LayoutUnit();
 
@@ -3650,7 +3664,7 @@
 template <bool snap_to_end_of_track>
 LayoutUnit TrackOffset(
     const NGGridLayoutAlgorithmTrackCollection& track_collection,
-    const NGGridLayoutAlgorithm::SetGeometry& set_geometry,
+    const SetGeometry& set_geometry,
     const wtf_size_t range_index,
     const wtf_size_t offset_in_range) {
   const wtf_size_t range_starting_set_index =
@@ -3687,7 +3701,7 @@
 
 LayoutUnit TrackStartOffset(
     const NGGridLayoutAlgorithmTrackCollection& track_collection,
-    const NGGridLayoutAlgorithm::SetGeometry& set_geometry,
+    const SetGeometry& set_geometry,
     const wtf_size_t range_index,
     const wtf_size_t offset_in_range) {
   if (!track_collection.RangeCount()) {
@@ -3717,7 +3731,7 @@
 
 LayoutUnit TrackEndOffset(
     const NGGridLayoutAlgorithmTrackCollection& track_collection,
-    const NGGridLayoutAlgorithm::SetGeometry& set_geometry,
+    const SetGeometry& set_geometry,
     const wtf_size_t range_index,
     const wtf_size_t offset_in_range) {
   if (!track_collection.RangeCount()) {
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h
index a04eb47..687ac8c 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_LAYOUT_ALGORITHM_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_LAYOUT_ALGORITHM_H_
 
+#include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_geometry.h"
 #include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_node.h"
 #include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
@@ -213,100 +214,6 @@
                                  NGBoxFragmentBuilder,
                                  NGBlockBreakToken> {
    public:
-    // Contains the information about the start offset of the tracks, as well as
-    // the gutter size between them, for a given direction.
-    struct TrackGeometry {
-      LayoutUnit start_offset;
-      LayoutUnit gutter_size;
-    };
-
-    // Represents the offsets for the sets, and the gutter-size.
-    //
-    // Initially we only know some of the set sizes - others will be indefinite.
-    // To represent this we store both the offset for the set, and a vector of
-    // all last indefinite indices (or kNotFound if everything so far has been
-    // definite). This allows us to get the appropriate size if a grid item
-    // spans only fixed tracks, but will allow us to return an indefinite size
-    // if it spans any indefinite set.
-    //
-    // As an example:
-    //   grid-template-rows: auto auto 100px 100px auto 100px;
-    //
-    // Results in:
-    //                  |  auto |  auto |   100   |   100   |   auto  |   100 |
-    //   [{0, kNotFound}, {0, 0}, {0, 1}, {100, 1}, {200, 1}, {200, 4}, {300,
-    //   4}]
-    //
-    // Various queries (start/end refer to the grid lines):
-    //  start: 0, end: 1 -> indefinite as:
-    //    "start <= sets[end].last_indefinite_index"
-    //  start: 1, end: 3 -> indefinite as:
-    //    "start <= sets[end].last_indefinite_index"
-    //  start: 2, end: 4 -> 200px
-    //  start: 5, end: 6 -> 100px
-    //  start: 3, end: 5 -> indefinite as:
-    //    "start <= sets[end].last_indefinite_index"
-    struct SetGeometry {
-      SetGeometry() = default;
-
-      SetGeometry(const TrackGeometry track_alignment_geometry,
-                  const wtf_size_t set_count)
-          : gutter_size(track_alignment_geometry.gutter_size) {
-        sets.ReserveInitialCapacity(set_count);
-        sets.emplace_back(track_alignment_geometry.start_offset,
-                          /* track_count */ kNotFound);
-      }
-
-      SetGeometry(const Vector<SetOffsetData>& sets,
-                  const LayoutUnit gutter_size)
-          : sets(sets), gutter_size(gutter_size) {}
-
-      LayoutUnit FinalGutterSize() const {
-        DCHECK_GT(sets.size(), 0u);
-        return (sets.size() == 1) ? LayoutUnit() : gutter_size;
-      }
-
-      Vector<wtf_size_t> last_indefinite_indices;
-      Vector<SetOffsetData> sets;
-      LayoutUnit gutter_size;
-    };
-
-    // Typically we pass around both the column, and row geometry together.
-    struct GridGeometry {
-      GridGeometry(SetGeometry&& column_geometry, SetGeometry&& row_geometry)
-          : column_geometry(column_geometry),
-            row_geometry(row_geometry),
-            major_inline_baselines(column_geometry.sets.size(),
-                                   LayoutUnit::Min()),
-            minor_inline_baselines(column_geometry.sets.size(),
-                                   LayoutUnit::Min()),
-            major_block_baselines(row_geometry.sets.size(), LayoutUnit::Min()),
-            minor_block_baselines(row_geometry.sets.size(), LayoutUnit::Min()) {
-      }
-
-      GridGeometry() = default;
-
-      const SetGeometry& Geometry(
-          GridTrackSizingDirection track_direction) const;
-
-      // Updates stored major/minor baseline value.
-      void UpdateBaseline(const GridItemData& grid_item,
-                          LayoutUnit candidate_baseline,
-                          GridTrackSizingDirection track_direction);
-
-      // Retrieves major/minor baseline.
-      LayoutUnit Baseline(const GridItemData& grid_item,
-                          GridTrackSizingDirection track_direction) const;
-
-      SetGeometry column_geometry;
-      SetGeometry row_geometry;
-
-      Vector<LayoutUnit> major_inline_baselines;
-      Vector<LayoutUnit> minor_inline_baselines;
-      Vector<LayoutUnit> major_block_baselines;
-      Vector<LayoutUnit> minor_block_baselines;
-    };
-
     explicit NGGridLayoutAlgorithm(const NGLayoutAlgorithmParams& params);
 
     scoped_refptr<const NGLayoutResult> Layout() override;
@@ -335,12 +242,25 @@
 
     enum class SizingConstraint { kLayout, kMinContent, kMaxContent };
 
+    LayoutUnit Baseline(const NGGridGeometry& grid_geometry,
+                        const GridItemData& grid_item,
+                        GridTrackSizingDirection track_direction) const;
+
+    NGGridGeometry ComputeGridGeometry(
+        const NGGridBlockTrackCollection& column_block_track_collection,
+        const NGGridBlockTrackCollection& row_block_track_collection,
+        GridItems* grid_items,
+        NGGridLayoutAlgorithmTrackCollection* column_track_collection,
+        NGGridLayoutAlgorithmTrackCollection* row_track_collection,
+        NGGridProperties* grid_properties,
+        LayoutUnit* intrinsic_block_size);
+
     LayoutUnit ComputeIntrinsicBlockSizeIgnoringChildren() const;
 
     // Returns the size that a grid item will distribute across the tracks with
     // an intrinsic sizing function it spans in the relevant track direction.
     LayoutUnit ContributionSizeForGridItem(
-        const GridGeometry& grid_geometry,
+        const NGGridGeometry& grid_geometry,
         const GridItemData& grid_item,
         GridTrackSizingDirection track_direction,
         GridItemContributionType contribution_type,
@@ -388,7 +308,7 @@
     // on each item in |grid_items|, and stores the results in |grid_geometry|.
     void CalculateAlignmentBaselines(
         const GridTrackSizingDirection track_direction,
-        GridGeometry* grid_geometry,
+        NGGridGeometry* grid_geometry,
         GridItems* grid_items,
         bool* needs_additional_pass) const;
 
@@ -401,7 +321,7 @@
     // size.
     SetGeometry ComputeUsedTrackSizes(
         SizingConstraint sizing_constraint,
-        const GridGeometry& grid_geometry,
+        const NGGridGeometry& grid_geometry,
         const NGGridProperties& grid_properties,
         const bool is_min_max_pass,
         NGGridLayoutAlgorithmTrackCollection* track_collection,
@@ -413,7 +333,7 @@
     // size resolution defined in
     // https://drafts.csswg.org/css-grid-2/#algo-content.
     void ResolveIntrinsicTrackSizes(
-        const GridGeometry& grid_geometry,
+        const NGGridGeometry& grid_geometry,
         bool is_min_max_pass,
         NGGridLayoutAlgorithmTrackCollection* track_collection,
         GridItems* grid_items,
@@ -421,7 +341,7 @@
         bool* has_block_size_dependent_item) const;
 
     void IncreaseTrackSizesToAccommodateGridItems(
-        const GridGeometry& grid_geometry,
+        const NGGridGeometry& grid_geometry,
         GridItems::Iterator group_begin,
         GridItems::Iterator group_end,
         const bool is_group_spanning_flex_track,
@@ -441,7 +361,7 @@
 
     void ExpandFlexibleTracks(
         SizingConstraint sizing_constraint,
-        const GridGeometry& grid_geometry,
+        const NGGridGeometry& grid_geometry,
         bool is_min_max_pass,
         NGGridLayoutAlgorithmTrackCollection* track_collection,
         GridItems* grid_items,
@@ -465,19 +385,19 @@
         NGCacheSlot cache_slot) const;
 
     const NGConstraintSpace CreateConstraintSpaceForLayout(
-        const GridGeometry& grid_geometry,
+        const NGGridGeometry& grid_geometry,
         const GridItemData& grid_item,
         LogicalRect* containing_grid_area) const;
 
     const NGConstraintSpace CreateConstraintSpaceForMeasure(
-        const GridGeometry& grid_geometry,
+        const NGGridGeometry& grid_geometry,
         const GridItemData& grid_item,
         GridTrackSizingDirection track_direction,
         absl::optional<LayoutUnit> opt_fixed_block_size = absl::nullopt) const;
 
     // Layout the |grid_items| based on the offsets provided.
     void PlaceGridItems(const GridItems& grid_items,
-                        const GridGeometry& grid_geometry);
+                        const NGGridGeometry& grid_geometry);
 
     // Computes the static position, grid area and its offset of out of flow
     // elements in the grid.
@@ -485,7 +405,7 @@
         const NGGridLayoutAlgorithmTrackCollection& column_track_collection,
         const NGGridLayoutAlgorithmTrackCollection& row_track_collection,
         const GridItemStorageVector& out_of_flow_items,
-        const GridGeometry& grid_geometry,
+        const NGGridGeometry& grid_geometry,
         LayoutUnit block_size);
 
     // Helper method to compute the containing block rect for out of flow
@@ -493,7 +413,7 @@
     static LogicalRect ComputeContainingGridAreaRect(
         const NGGridLayoutAlgorithmTrackCollection& column_track_collection,
         const NGGridLayoutAlgorithmTrackCollection& row_track_collection,
-        const GridGeometry& grid_geometry,
+        const NGGridGeometry& grid_geometry,
         const GridItemData& item,
         const NGBoxStrut& borders,
         const LogicalSize& border_box_size,
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm_test.cc b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm_test.cc
index 3a308bac..400bd27 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm_test.cc
@@ -203,7 +203,7 @@
   NGGridLayoutAlgorithmTrackCollection column_track_collection_;
   NGGridLayoutAlgorithmTrackCollection row_track_collection_;
 
-  NGGridLayoutAlgorithm::GridGeometry grid_geometry_;
+  NGGridGeometry grid_geometry_;
 };
 
 TEST_F(NGGridLayoutAlgorithmTest, NGGridLayoutAlgorithmBaseSetSizes) {
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index d852d2de..c88a09c 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -289,20 +289,13 @@
     // Inside NG block fragmentation we have to perform an offset adjustment.
     // An OOF fragment that is contained by something inside a fragmentainer
     // will be a direct child of the fragmentainer, rather than a child of its
-    // actual containing block. We therefore need to adjust the offset to make
-    // us relative to the fragmentainer before applying the offset of the OOF.
-    PhysicalOffset delta =
-        oof_context.paint_offset - context_.fragmentainer_paint_offset;
-    // So, we did store |fragmentainer_paint_offset| when entering the
-    // fragmentainer, but the offset may have been reset by
-    // UpdateForPaintOffsetTranslation() since we entered it, which we'll need
-    // to compensate for now.
-    delta += context_.adjustment_for_oof_in_fragmentainer;
-    context_.current.paint_offset -= delta;
+    // actual containing block. Set the paint offset to the correct one.
+    context_.current.paint_offset =
+        context_.current.paint_offset_for_oof_in_fragmentainer;
   }
 
   void ResetPaintOffset(PhysicalOffset new_offset = PhysicalOffset()) {
-    context_.adjustment_for_oof_in_fragmentainer +=
+    context_.current.paint_offset_for_oof_in_fragmentainer -=
         context_.current.paint_offset - new_offset;
     context_.current.paint_offset = new_offset;
   }
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.h b/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
index 0202154..eeca586 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
@@ -120,6 +120,11 @@
     const ScrollPaintPropertyNode* scroll = nullptr;
 
     FloatSize pending_scroll_anchor_adjustment;
+
+    // Paint offset of the innermost fragmentainer minus accumulated offsets
+    // that are baked in PaintOffsetTranslations since we entered the
+    // fragmentainer.
+    PhysicalOffset paint_offset_for_oof_in_fragmentainer;
   };
 
   ContainingBlockContext current;
@@ -162,13 +167,6 @@
 
   PhysicalOffset old_paint_offset;
 
-  // Paint offset at the current innermost fragmentainer.
-  PhysicalOffset fragmentainer_paint_offset;
-
-  // Amount of adjustment done by UpdateForPaintOffsetTranslation() since we
-  // entered the innermost fragmentainer.
-  PhysicalOffset adjustment_for_oof_in_fragmentainer;
-
   // An additional offset that applies to the current fragment, but is detected
   // *before* the ContainingBlockContext is updated for it. Once the
   // ContainingBlockContext is set, this value should be added to
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
index 69f67f9..a125019 100644
--- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
+++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -810,22 +810,17 @@
       continue;
     }
 
-    PaintPropertyTreeBuilderContext& tree_builder_context =
-        *context.tree_builder_context;
-    PaintPropertyTreeBuilderFragmentContext& fragment_context =
-        tree_builder_context.fragments[0];
-    PaintPropertyTreeBuilderFragmentContext::ContainingBlockContext*
-        containing_block_context = &fragment_context.current;
+    auto* containing_block_context =
+        &context.tree_builder_context->fragments[0].current;
     containing_block_context->paint_offset += child.offset;
 
     const PhysicalOffset paint_offset = containing_block_context->paint_offset;
-    // Keep track of the paint offset at the fragmentainer, and also reset the
-    // offset adjustment tracker. This is needed when entering OOF
-    // descendants. OOFs have the nearest fragmentainer as their containing
-    // block, so when entering them during LayoutObject tree traversal, we have
-    // to compensate for this.
-    fragment_context.fragmentainer_paint_offset = paint_offset;
-    fragment_context.adjustment_for_oof_in_fragmentainer = PhysicalOffset();
+    // Keep track of the paint offset at the fragmentainer. This is needed
+    // when entering OOF descendants. OOFs have the nearest fragmentainer as
+    // their containing block, so when entering them during LayoutObject tree
+    // traversal, we have to compensate for this.
+    containing_block_context->paint_offset_for_oof_in_fragmentainer =
+        paint_offset;
 
     // Create corresponding |FragmentData|. Hit-testing needs
     // |FragmentData.PaintOffset|.
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator.cc b/third_party/blink/renderer/core/scroll/scroll_animator.cc
index 78907f6..9c30061 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator.cc
@@ -42,6 +42,12 @@
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 
+// This should be after all other #includes.
+#if defined(_WINDOWS_)  // Detect whether windows.h was included.
+// See base/win/windows_h_disallowed.h for details.
+#error Windows.h was included unexpectedly.
+#endif  // defined(_WINDOWS_)
+
 namespace blink {
 
 ScrollAnimatorBase* ScrollAnimatorBase::Create(
diff --git a/third_party/blink/renderer/core/streams/readable_byte_stream_controller.cc b/third_party/blink/renderer/core/streams/readable_byte_stream_controller.cc
index a2ede38..ed1d216f 100644
--- a/third_party/blink/renderer/core/streams/readable_byte_stream_controller.cc
+++ b/third_party/blink/renderer/core/streams/readable_byte_stream_controller.cc
@@ -6,7 +6,7 @@
 
 #include "base/numerics/checked_math.h"
 #include "base/numerics/clamped_math.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_underlying_source.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_underlying_source_cancel_callback.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_underlying_source_pull_callback.h"
diff --git a/third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h b/third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h
index 9843b82e..0a43b0c 100644
--- a/third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h
+++ b/third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_STREAMS_READABLE_STREAM_DEFAULT_CONTROLLER_WITH_SCRIPT_SCOPE_H_
 
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/heap/visitor.h"
 #include "v8/include/v8.h"
diff --git a/third_party/blink/renderer/core/streams/transferable_streams.cc b/third_party/blink/renderer/core/streams/transferable_streams.cc
index 7455c7b4..98087ce 100644
--- a/third_party/blink/renderer/core/streams/transferable_streams.cc
+++ b/third_party/blink/renderer/core/streams/transferable_streams.cc
@@ -9,7 +9,7 @@
 
 #include "base/cxx17_backports.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_post_message_options.h"
diff --git a/third_party/blink/renderer/core/streams/writable_stream.cc b/third_party/blink/renderer/core/streams/writable_stream.cc
index e635e50..038d3ab 100644
--- a/third_party/blink/renderer/core/streams/writable_stream.cc
+++ b/third_party/blink/renderer/core/streams/writable_stream.cc
@@ -5,7 +5,7 @@
 #include "third_party/blink/renderer/core/streams/writable_stream.h"
 
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_queuing_strategy_init.h"
 #include "third_party/blink/renderer/core/streams/count_queuing_strategy.h"
 #include "third_party/blink/renderer/core/streams/miscellaneous_operations.h"
diff --git a/third_party/blink/renderer/core/timing/build.gni b/third_party/blink/renderer/core/timing/build.gni
index c39d5d3..abc8cd0 100644
--- a/third_party/blink/renderer/core/timing/build.gni
+++ b/third_party/blink/renderer/core/timing/build.gni
@@ -5,6 +5,7 @@
 blink_core_sources_timing = [
   "dom_window_performance.cc",
   "dom_window_performance.h",
+  "epoch_time_stamp.h",
   "event_counts.cc",
   "event_counts.h",
   "event_timing.cc",
diff --git a/third_party/blink/renderer/core/timing/epoch_time_stamp.h b/third_party/blink/renderer/core/timing/epoch_time_stamp.h
new file mode 100644
index 0000000..d828fa1e
--- /dev/null
+++ b/third_party/blink/renderer/core/timing/epoch_time_stamp.h
@@ -0,0 +1,22 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_EPOCH_TIME_STAMP_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_EPOCH_TIME_STAMP_H_
+
+#include <stdint.h>
+
+#include "base/time/time.h"
+
+namespace blink {
+
+typedef uint64_t EpochTimeStamp;
+
+inline EpochTimeStamp ConvertTimeToEpochTimeStamp(base::Time time) {
+  return static_cast<EpochTimeStamp>(time.ToDoubleT() * 1000.0);
+}
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_EPOCH_TIME_STAMP_H_
diff --git a/third_party/blink/renderer/core/timing/epoch_time_stamp.idl b/third_party/blink/renderer/core/timing/epoch_time_stamp.idl
new file mode 100644
index 0000000..b16a06d
--- /dev/null
+++ b/third_party/blink/renderer/core/timing/epoch_time_stamp.idl
@@ -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.
+
+// https://www.w3.org/TR/hr-time-3/#the-epochtimestamp-typedef
+
+typedef unsigned long long EpochTimeStamp;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index bc95126..f0339f41 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -1056,7 +1056,7 @@
 
 void AXObject::Serialize(ui::AXNodeData* node_data,
                          ui::AXMode accessibility_mode) {
-  node_data->role = RoleValue();
+  node_data->role = ComputeFinalRoleForSerialization();
   node_data->id = AXObjectID();
 
   DCHECK(!IsDetached()) << "Do not serialize detached nodes: "
@@ -1727,6 +1727,22 @@
   return false;
 }
 
+ax::mojom::blink::Role AXObject::ComputeFinalRoleForSerialization() const {
+  // An SVG with no accessible children should be exposed as an image rather
+  // than a document. See https://github.com/w3c/svg-aam/issues/12.
+  // We do this check here for performance purposes: When
+  // AXLayoutObject::RoleFromLayoutObjectOrNode is called, that node's
+  // accessible children have not been calculated. Rather than force calculation
+  // there, wait until we have the full tree.
+  if (role_ == ax::mojom::blink::Role::kSvgRoot && !UnignoredChildCount())
+    return ax::mojom::blink::Role::kImage;
+
+  // TODO(accessibility): Consider moving the image vs. image map role logic
+  // here. Currently it is implemented in AXPlatformNode subclasses and thus
+  // not available to the InspectorAccessibilityAgent.
+  return role_;
+}
+
 ax::mojom::blink::Role AXObject::RoleValue() const {
   return role_;
 }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h
index c4c07399..459e8ef 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.h
@@ -353,6 +353,14 @@
   // Check object role or purpose.
   ax::mojom::blink::Role RoleValue() const;
 
+  // This method is useful in cases where the final role exposed to ATs needs
+  // to change based on contextual information. For instance, an svgRoot should
+  // be exposed as an image if it lacks accessible children. Whether or not it
+  // has accessible children is not known at the time the role is assigned and
+  // may depend on whether or not a given platform includes children that other
+  // platforms ignore.
+  ax::mojom::blink::Role ComputeFinalRoleForSerialization() const;
+
   // Returns true if this object is an ARIA text field, i.e. it is neither an
   // <input> nor a <textarea>, but it has an ARIA role of textbox, searchbox or
   // (on certain platforms) combobox.
diff --git a/third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc b/third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc
index 40a016c9..fc45597 100644
--- a/third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc
+++ b/third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc
@@ -211,7 +211,7 @@
 
 void FillWidgetProperties(AXObject& ax_object,
                           protocol::Array<AXProperty>& properties) {
-  ax::mojom::Role role = ax_object.RoleValue();
+  ax::mojom::blink::Role role = ax_object.ComputeFinalRoleForSerialization();
   String autocomplete = ax_object.AutoComplete();
   if (!autocomplete.IsEmpty())
     properties.emplace_back(
@@ -303,7 +303,7 @@
 
 void FillWidgetStates(AXObject& ax_object,
                       protocol::Array<AXProperty>& properties) {
-  ax::mojom::Role role = ax_object.RoleValue();
+  ax::mojom::blink::Role role = ax_object.ComputeFinalRoleForSerialization();
   const char* checked_prop_val = nullptr;
   switch (ax_object.CheckedState()) {
     case ax::mojom::CheckedState::kTrue:
@@ -695,7 +695,7 @@
     bool fetch_relatives,
     std::unique_ptr<protocol::Array<AXNode>>& nodes,
     AXObjectCacheImpl& cache) const {
-  ax::mojom::Role role = ax_object.RoleValue();
+  ax::mojom::blink::Role role = ax_object.ComputeFinalRoleForSerialization();
   std::unique_ptr<AXNode> node_object =
       AXNode::create()
           .setNodeId(String::Number(ax_object.AXObjectID()))
@@ -957,7 +957,7 @@
 namespace {
 
 void setNameAndRole(const AXObject& ax_object, std::unique_ptr<AXNode>& node) {
-  ax::mojom::blink::Role role = ax_object.RoleValue();
+  ax::mojom::blink::Role role = ax_object.ComputeFinalRoleForSerialization();
   node->setRole(CreateRoleNameValue(role));
   AXObject::NameSources name_sources;
   String computed_name = ax_object.GetName(&name_sources);
@@ -1021,7 +1021,8 @@
 
     // if querying by role: skip if role of current object does not match.
     if (role.isJust() &&
-        role.fromJust() != AXObject::RoleName(ax_object->RoleValue())) {
+        role.fromJust() !=
+            AXObject::RoleName(ax_object->ComputeFinalRoleForSerialization())) {
       continue;
     }
 
diff --git a/third_party/blink/renderer/modules/breakout_box/media_stream_audio_track_underlying_sink_test.cc b/third_party/blink/renderer/modules/breakout_box/media_stream_audio_track_underlying_sink_test.cc
index 309fcc2..9a61f282 100644
--- a/third_party/blink/renderer/modules/breakout_box/media_stream_audio_track_underlying_sink_test.cc
+++ b/third_party/blink/renderer/modules/breakout_box/media_stream_audio_track_underlying_sink_test.cc
@@ -11,7 +11,7 @@
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_audio_sink.h"
 #include "third_party/blink/public/web/web_heap.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_data.h"
 #include "third_party/blink/renderer/core/streams/writable_stream.h"
diff --git a/third_party/blink/renderer/modules/geolocation/geolocation.cc b/third_party/blink/renderer/modules/geolocation/geolocation.cc
index 4f504ec..854280a 100644
--- a/third_party/blink/renderer/modules/geolocation/geolocation.cc
+++ b/third_party/blink/renderer/modules/geolocation/geolocation.cc
@@ -40,6 +40,7 @@
 #include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/core/timing/epoch_time_stamp.h"
 #include "third_party/blink/renderer/modules/geolocation/geolocation_coordinates.h"
 #include "third_party/blink/renderer/modules/geolocation/geolocation_error.h"
 
@@ -64,8 +65,7 @@
       position.heading >= 0. && position.heading <= 360., position.heading,
       position.speed >= 0., position.speed);
   return MakeGarbageCollected<Geoposition>(
-      coordinates,
-      ConvertSecondsToDOMTimeStamp(position.timestamp.ToDoubleT()));
+      coordinates, ConvertTimeToEpochTimeStamp(position.timestamp));
 }
 
 GeolocationPositionError* CreatePositionError(
@@ -307,8 +307,8 @@
     return false;
   if (!options->maximumAge())
     return false;
-  DOMTimeStamp current_time_millis =
-      ConvertSecondsToDOMTimeStamp(base::Time::Now().ToDoubleT());
+  EpochTimeStamp current_time_millis =
+      ConvertTimeToEpochTimeStamp(base::Time::Now());
   return last_position_->timestamp() >
          current_time_millis - options->maximumAge();
 }
diff --git a/third_party/blink/renderer/modules/geolocation/geolocation_position.idl b/third_party/blink/renderer/modules/geolocation/geolocation_position.idl
index bc3ba9d4..e6daf9b9 100644
--- a/third_party/blink/renderer/modules/geolocation/geolocation_position.idl
+++ b/third_party/blink/renderer/modules/geolocation/geolocation_position.idl
@@ -30,5 +30,5 @@
     ImplementedAs=Geoposition
 ] interface GeolocationPosition {
     readonly attribute GeolocationCoordinates coords;
-    readonly attribute DOMTimeStamp timestamp;
+    readonly attribute EpochTimeStamp timestamp;
 };
diff --git a/third_party/blink/renderer/modules/geolocation/geoposition.h b/third_party/blink/renderer/modules/geolocation/geoposition.h
index 4a2aad5..286e6d6 100644
--- a/third_party/blink/renderer/modules/geolocation/geoposition.h
+++ b/third_party/blink/renderer/modules/geolocation/geoposition.h
@@ -26,7 +26,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_GEOLOCATION_GEOPOSITION_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_GEOLOCATION_GEOPOSITION_H_
 
-#include "third_party/blink/renderer/core/dom/dom_time_stamp.h"
+#include "third_party/blink/renderer/core/timing/epoch_time_stamp.h"
 #include "third_party/blink/renderer/modules/event_modules.h"
 #include "third_party/blink/renderer/modules/geolocation/geolocation_coordinates.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
@@ -38,7 +38,7 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  Geoposition(GeolocationCoordinates* coordinates, DOMTimeStamp timestamp)
+  Geoposition(GeolocationCoordinates* coordinates, EpochTimeStamp timestamp)
       : coordinates_(coordinates), timestamp_(timestamp) {
     DCHECK(coordinates_);
   }
@@ -48,12 +48,12 @@
     ScriptWrappable::Trace(visitor);
   }
 
-  DOMTimeStamp timestamp() const { return timestamp_; }
+  EpochTimeStamp timestamp() const { return timestamp_; }
   GeolocationCoordinates* coords() const { return coordinates_; }
 
  private:
   Member<GeolocationCoordinates> coordinates_;
-  DOMTimeStamp timestamp_;
+  EpochTimeStamp timestamp_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/handwriting/handwriting_type_converters_unittest.cc b/third_party/blink/renderer/modules/handwriting/handwriting_type_converters_unittest.cc
index b053abf..6c125ad 100644
--- a/third_party/blink/renderer/modules/handwriting/handwriting_type_converters_unittest.cc
+++ b/third_party/blink/renderer/modules/handwriting/handwriting_type_converters_unittest.cc
@@ -7,7 +7,7 @@
 #include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/handwriting/handwriting.mojom-blink.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_handwriting_drawing_segment.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_handwriting_feature_query.h"
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_index.cc b/third_party/blink/renderer/modules/indexeddb/idb_index.cc
index 59ac7c7..182f321 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_index.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_index.cc
@@ -29,7 +29,7 @@
 #include <memory>
 #include <utility>
 
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/modules/v8/to_v8_for_modules.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_key_range.cc b/third_party/blink/renderer/modules/indexeddb/idb_key_range.cc
index f9af248..81dc10b 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_key_range.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_key_range.cc
@@ -25,7 +25,7 @@
 
 #include "third_party/blink/renderer/modules/indexeddb/idb_key_range.h"
 
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/modules/v8/to_v8_for_modules.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc b/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc
index 53b3198..7fc3b6e 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc
@@ -37,7 +37,7 @@
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink-forward.h"
 #include "third_party/blink/public/platform/web_blob_info.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_factory.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/modules/v8/to_v8_for_modules.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_idbcursor_idbindex_idbobjectstore.h"
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_request.cc b/third_party/blink/renderer/modules/indexeddb/idb_request.cc
index e2576cf..df7ea5c 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_request.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_request.cc
@@ -33,7 +33,7 @@
 #include <utility>
 
 #include "third_party/blink/public/platform/web_blob_info.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/modules/v8/to_v8_for_modules.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_idbcursor_idbindex_idbobjectstore.h"
diff --git a/third_party/blink/renderer/modules/mediasession/media_metadata.cc b/third_party/blink/renderer/modules/mediasession/media_metadata.cc
index 2c3e465..b0a9f9c88 100644
--- a/third_party/blink/renderer/modules/mediasession/media_metadata.cc
+++ b/third_party/blink/renderer/modules/mediasession/media_metadata.cc
@@ -5,7 +5,7 @@
 #include "third_party/blink/renderer/modules/mediasession/media_metadata.h"
 
 #include "third_party/blink/public/platform/task_type.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_metadata_init.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
diff --git a/third_party/blink/renderer/modules/mediasource/track_default.cc b/third_party/blink/renderer/modules/mediasource/track_default.cc
index 53a2352..63e3de6 100644
--- a/third_party/blink/renderer/modules/mediasource/track_default.cc
+++ b/third_party/blink/renderer/modules/mediasource/track_default.cc
@@ -4,7 +4,7 @@
 
 #include "third_party/blink/renderer/modules/mediasource/track_default.h"
 
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/core/html/track/audio_track.h"
 #include "third_party/blink/renderer/core/html/track/text_track.h"
 #include "third_party/blink/renderer/core/html/track/video_track.h"
diff --git a/third_party/blink/renderer/modules/payments/payment_event_data_conversion.cc b/third_party/blink/renderer/modules/payments/payment_event_data_conversion.cc
index aeacdba..ec005c08 100644
--- a/third_party/blink/renderer/modules/payments/payment_event_data_conversion.cc
+++ b/third_party/blink/renderer/modules/payments/payment_event_data_conversion.cc
@@ -5,7 +5,7 @@
 #include "third_party/blink/renderer/modules/payments/payment_event_data_conversion.h"
 
 #include "third_party/blink/public/mojom/payments/payment_app.mojom-blink.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_currency_amount.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_details_modifier.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_item.h"
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc
index ff96c1c8..c049739 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc
@@ -9,7 +9,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_audio_frame.h"
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
index 8238bd13..3224dec 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
@@ -9,7 +9,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_video_frame.h"
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_test.cc
index 9eb4c6e..370b43b 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_test.cc
@@ -14,7 +14,7 @@
 #include "third_party/blink/public/web/web_heap.h"
 #include "third_party/blink/public/web/web_script_source.h"
 #include "third_party/blink/renderer/bindings/core/v8/dictionary.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_void_function.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_answer_options.h"
diff --git a/third_party/blink/renderer/modules/service_worker/fetch_event.cc b/third_party/blink/renderer/modules/service_worker/fetch_event.cc
index 3bab1f7..6df8cc6 100644
--- a/third_party/blink/renderer/modules/service_worker/fetch_event.cc
+++ b/third_party/blink/renderer/modules/service_worker/fetch_event.cc
@@ -11,7 +11,7 @@
 #include "third_party/blink/public/mojom/timing/worker_timing_container.mojom-blink.h"
 #include "third_party/blink/public/platform/modules/service_worker/web_service_worker_error.h"
 #include "third_party/blink/public/platform/web_url_response.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/core/dom/abort_signal.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/fetch/request.h"
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_error.cc b/third_party/blink/renderer/modules/service_worker/service_worker_error.cc
index 9f1e4c1..5b96b73 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_error.cc
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_error.cc
@@ -32,7 +32,7 @@
 
 #include "third_party/blink/public/mojom/service_worker/service_worker_error_type.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
index 98c009a..d45ea6fc 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
@@ -16,7 +16,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
diff --git a/third_party/blink/renderer/modules/webtransport/incoming_stream_test.cc b/third_party/blink/renderer/modules/webtransport/incoming_stream_test.cc
index 6b8fadb1..8eaf897 100644
--- a/third_party/blink/renderer/modules/webtransport/incoming_stream_test.cc
+++ b/third_party/blink/renderer/modules/webtransport/incoming_stream_test.cc
@@ -12,7 +12,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
diff --git a/third_party/blink/renderer/modules/webtransport/outgoing_stream_test.cc b/third_party/blink/renderer/modules/webtransport/outgoing_stream_test.cc
index 9c4bd35..14ae13a 100644
--- a/third_party/blink/renderer/modules/webtransport/outgoing_stream_test.cc
+++ b/third_party/blink/renderer/modules/webtransport/outgoing_stream_test.cc
@@ -11,7 +11,7 @@
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
diff --git a/third_party/blink/renderer/modules/webtransport/web_transport_test.cc b/third_party/blink/renderer/modules/webtransport/web_transport_test.cc
index ea770ce..43337a2 100644
--- a/third_party/blink/renderer/modules/webtransport/web_transport_test.cc
+++ b/third_party/blink/renderer/modules/webtransport/web_transport_test.cc
@@ -21,7 +21,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
diff --git a/third_party/blink/renderer/modules/webusb/usb_device.cc b/third_party/blink/renderer/modules/webusb/usb_device.cc
index 7c9fa09..4532b21 100644
--- a/third_party/blink/renderer/modules/webusb/usb_device.cc
+++ b/third_party/blink/renderer/modules/webusb/usb_device.cc
@@ -10,7 +10,7 @@
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_arraybuffer_arraybufferview.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_usb_control_transfer_parameters.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
diff --git a/third_party/blink/renderer/platform/fonts/font_cache.cc b/third_party/blink/renderer/platform/fonts/font_cache.cc
index e625b63..155cb28 100644
--- a/third_party/blink/renderer/platform/fonts/font_cache.cc
+++ b/third_party/blink/renderer/platform/fonts/font_cache.cc
@@ -388,12 +388,6 @@
 
 void FontCache::PurgeFallbackListShaperCache() {
   TRACE_EVENT0("fonts,ui", "FontCache::PurgeFallbackListShaperCache");
-  unsigned items = 0;
-  FallbackListShaperCache::iterator iter;
-  for (iter = fallback_list_shaper_cache_.begin();
-       iter != fallback_list_shaper_cache_.end(); ++iter) {
-    items += iter->value->size();
-  }
   fallback_list_shaper_cache_.clear();
 }
 
diff --git a/third_party/blink/renderer/platform/loader/child_url_loader_factory_bundle.cc b/third_party/blink/renderer/platform/loader/child_url_loader_factory_bundle.cc
index f7e9689..8be819e 100644
--- a/third_party/blink/renderer/platform/loader/child_url_loader_factory_bundle.cc
+++ b/third_party/blink/renderer/platform/loader/child_url_loader_factory_bundle.cc
@@ -137,15 +137,11 @@
           std::move(base_factories->pending_scheme_specific_factories()),
           std::move(base_factories->pending_isolated_world_factories()),
           base_factories->bypass_redirect_checks()) {
-  pending_appcache_factory_ =
-      std::move(base_factories->pending_appcache_factory());
 }
 
 ChildPendingURLLoaderFactoryBundle::ChildPendingURLLoaderFactoryBundle(
     mojo::PendingRemote<network::mojom::URLLoaderFactory>
         pending_default_factory,
-    mojo::PendingRemote<network::mojom::URLLoaderFactory>
-        pending_appcache_factory,
     SchemeMap pending_scheme_specific_factories,
     OriginMap pending_isolated_world_factories,
     mojo::PendingRemote<network::mojom::URLLoaderFactory>
@@ -157,9 +153,7 @@
           std::move(pending_isolated_world_factories),
           bypass_redirect_checks),
       pending_prefetch_loader_factory_(
-          std::move(pending_prefetch_loader_factory)) {
-  pending_appcache_factory_ = std::move(pending_appcache_factory);
-}
+          std::move(pending_prefetch_loader_factory)) {}
 
 ChildPendingURLLoaderFactoryBundle::~ChildPendingURLLoaderFactoryBundle() =
     default;
@@ -168,7 +162,6 @@
 ChildPendingURLLoaderFactoryBundle::CreateFactory() {
   auto other = std::make_unique<ChildPendingURLLoaderFactoryBundle>();
   other->pending_default_factory_ = std::move(pending_default_factory_);
-  other->pending_appcache_factory_ = std::move(pending_appcache_factory_);
   other->pending_scheme_specific_factories_ =
       std::move(pending_scheme_specific_factories_);
   other->pending_isolated_world_factories_ =
@@ -238,12 +231,47 @@
 
 std::unique_ptr<network::PendingSharedURLLoaderFactory>
 ChildURLLoaderFactoryBundle::Clone() {
-  return CloneInternal(true /* include_appcache */);
+  mojo::PendingRemote<network::mojom::URLLoaderFactory>
+      default_factory_pending_remote;
+  if (default_factory_) {
+    default_factory_->Clone(
+        default_factory_pending_remote.InitWithNewPipeAndPassReceiver());
+  }
+
+  mojo::PendingRemote<network::mojom::URLLoaderFactory>
+      pending_prefetch_loader_factory;
+  if (prefetch_loader_factory_) {
+    prefetch_loader_factory_->Clone(
+        pending_prefetch_loader_factory.InitWithNewPipeAndPassReceiver());
+  }
+
+  // Currently there is no need to override subresources from workers,
+  // therefore |subresource_overrides| are not shared with the clones.
+
+  return std::make_unique<ChildPendingURLLoaderFactoryBundle>(
+      std::move(default_factory_pending_remote),
+      CloneRemoteMapToPendingRemoteMap(scheme_specific_factories_),
+      CloneRemoteMapToPendingRemoteMap(isolated_world_factories_),
+      std::move(pending_prefetch_loader_factory), bypass_redirect_checks_);
 }
 
-std::unique_ptr<network::PendingSharedURLLoaderFactory>
-ChildURLLoaderFactoryBundle::CloneWithoutAppCacheFactory() {
-  return CloneInternal(false /* include_appcache */);
+std::unique_ptr<ChildPendingURLLoaderFactoryBundle>
+ChildURLLoaderFactoryBundle::PassInterface() {
+  mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_default_factory;
+  if (default_factory_)
+    pending_default_factory = default_factory_.Unbind();
+
+  mojo::PendingRemote<network::mojom::URLLoaderFactory>
+      pending_prefetch_loader_factory;
+  if (prefetch_loader_factory_) {
+    pending_prefetch_loader_factory = prefetch_loader_factory_.Unbind();
+  }
+
+  return std::make_unique<ChildPendingURLLoaderFactoryBundle>(
+      std::move(pending_default_factory),
+      BoundRemoteMapToPendingRemoteMap(std::move(scheme_specific_factories_)),
+      BoundRemoteMapToPendingRemoteMap(std::move(isolated_world_factories_)),
+      std::move(pending_prefetch_loader_factory), bypass_redirect_checks_);
 }
 
 void ChildURLLoaderFactoryBundle::Update(
@@ -272,62 +300,4 @@
   return false;
 }
 
-std::unique_ptr<network::PendingSharedURLLoaderFactory>
-ChildURLLoaderFactoryBundle::CloneInternal(bool include_appcache) {
-  mojo::PendingRemote<network::mojom::URLLoaderFactory>
-      default_factory_pending_remote;
-  if (default_factory_) {
-    default_factory_->Clone(
-        default_factory_pending_remote.InitWithNewPipeAndPassReceiver());
-  }
-
-  mojo::PendingRemote<network::mojom::URLLoaderFactory>
-      appcache_factory_pending_remote;
-  if (appcache_factory_ && include_appcache) {
-    appcache_factory_->Clone(
-        appcache_factory_pending_remote.InitWithNewPipeAndPassReceiver());
-  }
-
-  mojo::PendingRemote<network::mojom::URLLoaderFactory>
-      pending_prefetch_loader_factory;
-  if (prefetch_loader_factory_) {
-    prefetch_loader_factory_->Clone(
-        pending_prefetch_loader_factory.InitWithNewPipeAndPassReceiver());
-  }
-
-  // Currently there is no need to override subresources from workers,
-  // therefore |subresource_overrides| are not shared with the clones.
-
-  return std::make_unique<ChildPendingURLLoaderFactoryBundle>(
-      std::move(default_factory_pending_remote),
-      std::move(appcache_factory_pending_remote),
-      CloneRemoteMapToPendingRemoteMap(scheme_specific_factories_),
-      CloneRemoteMapToPendingRemoteMap(isolated_world_factories_),
-      std::move(pending_prefetch_loader_factory), bypass_redirect_checks_);
-}
-
-std::unique_ptr<ChildPendingURLLoaderFactoryBundle>
-ChildURLLoaderFactoryBundle::PassInterface() {
-  mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_default_factory;
-  if (default_factory_)
-    pending_default_factory = default_factory_.Unbind();
-
-  mojo::PendingRemote<network::mojom::URLLoaderFactory>
-      pending_appcache_factory;
-  if (appcache_factory_)
-    pending_appcache_factory = appcache_factory_.Unbind();
-
-  mojo::PendingRemote<network::mojom::URLLoaderFactory>
-      pending_prefetch_loader_factory;
-  if (prefetch_loader_factory_) {
-    pending_prefetch_loader_factory = prefetch_loader_factory_.Unbind();
-  }
-
-  return std::make_unique<ChildPendingURLLoaderFactoryBundle>(
-      std::move(pending_default_factory), std::move(pending_appcache_factory),
-      BoundRemoteMapToPendingRemoteMap(std::move(scheme_specific_factories_)),
-      BoundRemoteMapToPendingRemoteMap(std::move(isolated_world_factories_)),
-      std::move(pending_prefetch_loader_factory), bypass_redirect_checks_);
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.cc
index 1ee99ed..aacdcd1 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.cc
@@ -641,7 +641,7 @@
   loader_factory_ = network::SharedURLLoaderFactory::Create(
       subresource_loader_factory_bundle->Clone());
   fallback_factory_ = network::SharedURLLoaderFactory::Create(
-      subresource_loader_factory_bundle->CloneWithoutAppCacheFactory());
+      subresource_loader_factory_bundle->Clone());
   web_loader_factory_ = std::make_unique<Factory>(
       loader_factory_, cors_exempt_header_list_, terminate_sync_load_event_);
   ResetServiceWorkerURLLoaderFactory();
diff --git a/third_party/blink/renderer/platform/loader/tracked_child_url_loader_factory_bundle.cc b/third_party/blink/renderer/platform/loader/tracked_child_url_loader_factory_bundle.cc
index 6f0425d..8c206d4 100644
--- a/third_party/blink/renderer/platform/loader/tracked_child_url_loader_factory_bundle.cc
+++ b/third_party/blink/renderer/platform/loader/tracked_child_url_loader_factory_bundle.cc
@@ -19,8 +19,6 @@
     TrackedChildPendingURLLoaderFactoryBundle(
         mojo::PendingRemote<network::mojom::URLLoaderFactory>
             pending_default_factory,
-        mojo::PendingRemote<network::mojom::URLLoaderFactory>
-            pending_appcache_factory,
         SchemeMap pending_scheme_specific_factories,
         OriginMap pending_isolated_world_factories,
         mojo::PendingRemote<network::mojom::URLLoaderFactory>
@@ -29,7 +27,6 @@
         bool bypass_redirect_checks)
     : ChildPendingURLLoaderFactoryBundle(
           std::move(pending_default_factory),
-          std::move(pending_appcache_factory),
           std::move(pending_scheme_specific_factories),
           std::move(pending_isolated_world_factories),
           std::move(pending_prefetch_loader_factory),
@@ -48,7 +45,6 @@
 TrackedChildPendingURLLoaderFactoryBundle::CreateFactory() {
   auto other = std::make_unique<TrackedChildPendingURLLoaderFactoryBundle>();
   other->pending_default_factory_ = std::move(pending_default_factory_);
-  other->pending_appcache_factory_ = std::move(pending_appcache_factory_);
   other->pending_scheme_specific_factories_ =
       std::move(pending_scheme_specific_factories_);
   other->pending_isolated_world_factories_ =
@@ -91,7 +87,6 @@
 
   return std::make_unique<TrackedChildPendingURLLoaderFactoryBundle>(
       std::move(pending_factories->pending_default_factory()),
-      std::move(pending_factories->pending_appcache_factory()),
       std::move(pending_factories->pending_scheme_specific_factories()),
       std::move(pending_factories->pending_isolated_world_factories()),
       std::move(pending_factories->pending_prefetch_loader_factory()),
@@ -156,29 +151,6 @@
 
   return std::make_unique<TrackedChildPendingURLLoaderFactoryBundle>(
       std::move(pending_factories->pending_default_factory()),
-      std::move(pending_factories->pending_appcache_factory()),
-      std::move(pending_factories->pending_scheme_specific_factories()),
-      std::move(pending_factories->pending_isolated_world_factories()),
-      std::move(pending_factories->pending_prefetch_loader_factory()),
-      std::move(main_thread_host_bundle_clone),
-      pending_factories->bypass_redirect_checks());
-}
-
-std::unique_ptr<network::PendingSharedURLLoaderFactory>
-HostChildURLLoaderFactoryBundle::CloneWithoutAppCacheFactory() {
-  auto pending_factories =
-      base::WrapUnique(static_cast<ChildPendingURLLoaderFactoryBundle*>(
-          ChildURLLoaderFactoryBundle::CloneWithoutAppCacheFactory()
-              .release()));
-
-  DCHECK(base::SequencedTaskRunnerHandle::IsSet());
-  auto main_thread_host_bundle_clone = std::make_unique<
-      TrackedChildURLLoaderFactoryBundle::HostPtrAndTaskRunner>(AsWeakPtr(),
-                                                                task_runner_);
-
-  return std::make_unique<TrackedChildPendingURLLoaderFactoryBundle>(
-      std::move(pending_factories->pending_default_factory()),
-      std::move(pending_factories->pending_appcache_factory()),
       std::move(pending_factories->pending_scheme_specific_factories()),
       std::move(pending_factories->pending_isolated_world_factories()),
       std::move(pending_factories->pending_prefetch_loader_factory()),
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 71fb2b2..227ebe54 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1207,7 +1207,7 @@
     },
     {
       name: "InteractionId",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "InterestCohortAPI",
@@ -2413,7 +2413,7 @@
       name: "WebAuth",
       status: "stable",
     },
-    // When enabled adds the authenticator attachment used for registration and 
+    // When enabled adds the authenticator attachment used for registration and
     // authentication to the public key credential response.
     {
       name: "WebAuthAuthenticatorAttachment",
diff --git a/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py b/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py
index 5ba35e8..bae4308 100644
--- a/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py
+++ b/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py
@@ -533,7 +533,8 @@
 
 class ReadableTextFileObject(ReadableBinaryFileObject):
     def __init__(self, fs, path, data):
-        super(ReadableTextFileObject, self).__init__(fs, path, StringIO(data))
+        super(ReadableTextFileObject,
+              self).__init__(fs, path, StringIO(data.decode(encoding='utf-8')))
 
     def close(self):
         self.data.close()
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_github.py b/third_party/blink/tools/blinkpy/w3c/wpt_github.py
index e11747c..a9adf91 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_github.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_github.py
@@ -7,9 +7,11 @@
 import json
 import logging
 import re
+import six
 
 from collections import namedtuple
 from six.moves.urllib.error import HTTPError
+from six.moves.urllib.error import URLError
 from six.moves.urllib.parse import quote
 
 from blinkpy.common.memoized import memoized
@@ -48,7 +50,8 @@
 
     def auth_token(self):
         assert self.has_credentials()
-        return base64.b64encode('{}:{}'.format(self.user, self.token))
+        data = '{}:{}'.format(self.user, self.token).encode('utf-8')
+        return base64.b64encode(data).decode('utf-8')
 
     def request(self, path, method, body=None, accept_header=None):
         """Sends a request to GitHub API and deserializes the response.
@@ -368,18 +371,27 @@
         """
         path = '/repos/%s/%s/pulls/%d/merge' % (WPT_GH_ORG, WPT_GH_REPO_NAME,
                                                 pr_number)
-        try:
-            response = self.request(path, method='GET')
-            if response.status_code == 204:
-                return True
-            else:
-                raise GitHubError(204, response.status_code,
-                                  'check if PR %d is merged' % pr_number)
-        except HTTPError as e:
-            if e.code == 404:
-                return False
-            else:
-                raise
+        cached_error = None
+        for i in range(5):
+            try:
+                response = self.request(path, method='GET')
+                if response.status_code == 204:
+                    return True
+                else:
+                    raise GitHubError(204, response.status_code,
+                                      'check if PR %d is merged' % pr_number)
+            except HTTPError as e:
+                if e.code == 404:
+                    return False
+                else:
+                    raise
+            except URLError as e:
+                # After migrate to py3 we met random timeout issue here,
+                # Retry this request in this case
+                _log.warning("Meet URLError...")
+                cached_error = e
+        else:
+            raise cached_error
 
     def merge_pr(self, pr_number):
         """Merges a PR.
@@ -470,7 +482,10 @@
         """Gets the value of the header with the given name.
 
         Delegates to HTTPMessage.getheader(), which is case-insensitive."""
-        return self._raw_response.info().getheader(header)
+        if six.PY3:
+            return self._raw_response.getheader(header)
+        else:
+            return self._raw_response.info().getheader(header)
 
 
 class GitHubError(Exception):
diff --git a/third_party/blink/web_tests/FlagExpectations/composite-after-paint b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
index f9df09c..0584a88 100644
--- a/third_party/blink/web_tests/FlagExpectations/composite-after-paint
+++ b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
@@ -36,6 +36,7 @@
 crbug.com/1183814 external/wpt/svg/extensibility/foreignObject/scroll-transform-nested-stacked-children.html [ Pass ]
 crbug.com/1167352 external/wpt/css/css-will-change/will-change-transform-add-content.html [ Pass ]
 crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-out-of-bounds-translate.html [ Pass ]
+virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-075.html [ Pass ]
 
 # Outline paints incorrectly with columns. Needs LayoutNGBlockFragmentation.
 crbug.com/1047358 paint/pagination/composited-paginated-outlined-box.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 5d9dc095..0a07e2c 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -360,6 +360,7 @@
 crbug.com/624233 virtual/gpu-rasterization/images/color-profile-background-clip-text.html [ Failure Pass ]
 crbug.com/757605 virtual/gpu-rasterization/images/jpeg-yuv-progressive-image.html [ Failure Pass ]
 crbug.com/1223284 [ Win7 ] virtual/gpu-rasterization/images/color-profile-background-image-cross-fade.html [ Failure Pass ]
+crbug.com/1223284 [ Mac11-arm64 ] virtual/gpu-rasterization/images/color-profile-background-image-cross-fade.html [ Failure Pass ]
 
 ########## Bugs to fix ##########
 # This is a missing event and increasing the timeout or using run-after-layout-and-paint doesn't
@@ -2914,6 +2915,13 @@
 crbug.com/626703 external/wpt/service-workers/service-worker/worker-interception.https.html [ Failure ]
 crbug.com/626703 virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/worker-interception.https.html [ Pass ]
 
+# ====== Test expectations added to unblock wpt-importer ======
+crbug.com/626703 external/wpt/service-workers/service-worker/navigation-timing-extended.https.html [ Failure ]
+crbug.com/626703 fast/animation/scroll-animations/scroll-animation-lifetime.html [ Failure ]
+crbug.com/626703 fast/animation/scroll-animations/scroll-timeline-snapshotting.html [ Failure ]
+crbug.com/626703 fast/animation/scroll-animations/scrolltimeline-root-scroller-quirks-mode.html [ Failure ]
+crbug.com/626703 virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https.html [ Failure ]
+
 # ====== New tests from wpt-importer added here ======
 crbug.com/626703 [ Linux ] external/wpt/webauthn/createcredential-attachment.https.html [ Crash ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/webauthn/createcredential-attachment.https.html [ Crash ]
@@ -3214,10 +3222,12 @@
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/webrtc/RTCSctpTransport-events.html [ Timeout ]
 crbug.com/626703 [ Mac10.14 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/getstats.html [ Timeout ]
 crbug.com/626703 [ Mac11.0 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/getstats.html [ Timeout ]
+crbug.com/626703 [ Mac11-arm64 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/getstats.html [ Timeout ]
 crbug.com/626703 [ Mac10.14 ] external/wpt/webrtc/RTCDataChannel-send.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/RTCDataChannel-send.html [ Skip Timeout ]
 crbug.com/626703 [ Mac10.14 ] external/wpt/webrtc/RTCDataChannel-iceRestart.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/RTCDataChannel-iceRestart.html [ Skip Timeout ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/webrtc/RTCDataChannel-iceRestart.html [ Skip Timeout ]
 crbug.com/626703 [ Mac10.14 ] external/wpt/webrtc/RTCPeerConnection-createDataChannel.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/RTCPeerConnection-createDataChannel.html [ Skip Timeout ]
 crbug.com/626703 [ Mac10.14 ] external/wpt/webrtc-encoded-transform/RTCPeerConnection-insertable-streams-legacy.https.html [ Timeout ]
@@ -3278,6 +3288,7 @@
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/RTCPeerConnection-connectionState.https.html [ Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/RTCDataChannel-close.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc-encoded-transform/RTCPeerConnection-insertable-streams-worker.https.html [ Timeout ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/webrtc-encoded-transform/RTCPeerConnection-insertable-streams-worker.https.html [ Timeout ]
 crbug.com/626703 [ Mac11.0 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-connectionState.https.html [ Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/simulcast/vp8.https.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/protocol/handover-datachannel.html [ Timeout ]
@@ -3305,6 +3316,7 @@
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/promises-call.html [ Timeout ]
 crbug.com/626703 [ Mac11.0 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-createDataChannel.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/RTCPeerConnection-addIceCandidate-connectionSetup.html [ Skip Timeout ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/webrtc/RTCPeerConnection-addIceCandidate-connectionSetup.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11.0 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-getStats.https.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11-arm64 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-getStats.https.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/video-rvfc/request-video-frame-callback-webrtc.https.html [ Timeout ]
@@ -3919,6 +3931,7 @@
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-066.html [ Failure ]
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-071.html [ Failure ]
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-073.html [ Failure ]
+crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-075.html [ Failure ]
 crbug.com/829028 external/wpt/css/css-break/overflow-clip-000.html [ Failure ]
 crbug.com/829028 external/wpt/css/css-break/overflow-clip-001.html [ Failure ]
 crbug.com/829028 external/wpt/css/css-break/overflow-clip-010.html [ Failure ]
@@ -6545,8 +6558,10 @@
 
 # Sheriff 2021-06-02
 crbug.com/1215575 [ Mac11.0 ] fast/peerconnection/RTCPeerConnection-applyConstraints-remoteVideoTrack.html [ Pass Timeout ]
+crbug.com/1215575 [ Mac11-arm64 ] fast/peerconnection/RTCPeerConnection-applyConstraints-remoteVideoTrack.html [ Pass Timeout ]
 crbug.com/1215575 [ Mac11.0 ] fast/peerconnection/RTCPeerConnection-createDTMFSender.html [ Failure Pass ]
 crbug.com/1215575 [ Mac11.0 ] fast/peerconnection/RTCPeerConnection-datachannel.html [ Pass Timeout ]
+crbug.com/1215575 [ Mac11-arm64 ] fast/peerconnection/RTCPeerConnection-datachannel.html [ Pass Timeout ]
 crbug.com/1215575 [ Mac11.0 ] fast/peerconnection/RTCPeerConnection-lifetime.html [ Pass Timeout ]
 crbug.com/1215575 [ Mac11-arm64 ] fast/peerconnection/RTCPeerConnection-lifetime.html [ Pass Timeout ]
 crbug.com/1215584 [ Mac11.0 ] inspector-protocol/layout-fonts/lang-fallback.js [ Failure Pass ]
@@ -6943,6 +6958,7 @@
 crbug.com/1249176 [ Mac11-arm64 ] fast/forms/color/color-picker-escape-cancellation-revert.html [ Failure ]
 crbug.com/1249176 [ Mac11-arm64 ] fast/css/focus-display-block-inline.html [ Failure ]
 crbug.com/1249176 [ Mac11-arm64 ] fast/dom/geometry-interfaces-dom-matrix-rotate.html [ Failure ]
+crbug.com/1249176 [ Mac11-arm64 ] http/tests/devtools/tracing/timeline-paint/timeline-paint-image.js [ Failure ]
 
 # Following tests timeout on mac11-arm64
 crbug.com/1249176 [ Mac11-arm64 ] external/wpt/focus/focus-already-focused-iframe-deep-same-site.html [ Timeout ]
@@ -7035,6 +7051,9 @@
 crbug.com/1249176 [ Mac11-arm64 ] external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-003.html [ Failure Pass ]
 crbug.com/1249176 [ Mac11-arm64 ] external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-014.html [ Failure Pass ]
 crbug.com/1249176 [ Mac11-arm64 ] virtual/gpu-rasterization/images/color-profile-image-filter-all.html [ Failure Pass ]
+crbug.com/1249176 [ Mac11-arm64 ] virtual/scalefactor200withoutzoom/external/wpt/largest-contentful-paint/multiple-redirects-TAO.html [ Failure Pass ]
+crbug.com/1249176 [ Mac11-arm64 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/protocol/split.https.html [ Timeout Pass ]
+
 # mac-arm CI 10-01-2021
 crbug.com/1249176 [ Mac11-arm64 ] editing/text-iterator/beforematch-async.html [ Failure ]
 crbug.com/1249176 [ Mac11-arm64 ] external/wpt/event-timing/click-interactionid.html [ Timeout ]
@@ -7201,3 +7220,5 @@
 crbug.com/1259133 [ Mac10.14 ] external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.commit.html [ Failure Pass ]
 crbug.com/1259133 [ Mac10.14 ] virtual/no-alloc-direct-call/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.commit.html [ Failure Pass ]
 crbug.com/1259167 [ Mac ] virtual/threaded/external/wpt/scroll-animations/scroll-animation-inactive-timeline.html [ Failure Pass ]
+crbug.com/1259188 [ Mac10.14 ] virtual/gpu-rasterization/images/color-profile-animate.html [ Failure Pass ]
+crbug.com/1259255 [ Win7 ] virtual/scroll-unification/fast/events/mouse-cursor-no-mousemove.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 1ebd08a7..b6dea7d 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -121,8 +121,7 @@
   },
   {
     "prefix": "stable",
-    "bases": ["external/wpt/css/css-cascade/presentational-hints-cascade.html",
-              "fast/css3-text/css3-text-decoration/stable",
+    "bases": ["fast/css3-text/css3-text-decoration/stable",
               "fast/dom/Window",
               "http/tests/navigation",
               "http/tests/sendbeacon",
@@ -1119,5 +1118,10 @@
     "prefix": "fenced-frame-mparch",
     "bases": ["fenced_frame", "wpt_internal/fenced_frame"],
     "args": ["--enable-features=FencedFrames:implementation_type/mparch"]
+  },
+  {
+    "prefix": "disable-custom-element-default-style",
+    "bases": ["external/wpt/css/css-cascade/presentational-hints-cascade.html"],
+    "args": ["--disable-blink-features=CustomElementDefaultStyle"]
   }
 ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 415182c..31e27a5 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -2414,13 +2414,15 @@
     ]
    },
    "scroll-animations": {
-    "null-scroll-source-crash.html": [
-     "53ad0d92850992ca00b157f83b7d45c867c3f475",
-     [
-      null,
-      {}
+    "scroll-timelines": {
+     "null-scroll-source-crash.html": [
+      "53ad0d92850992ca00b157f83b7d45c867c3f475",
+      [
+       null,
+       {}
+      ]
      ]
-    ]
+    }
    },
    "selection": {
     "selection-select-all-move-input-crash.html": [
@@ -113619,8 +113621,21 @@
         {}
        ]
       ],
-      "grid-placement-items-spanning-multiple-rows.html": [
-       "f03c5f1d9e879b8d2ece5a3c18a1e964909024a9",
+      "grid-placement-items-spanning-multiple-rows-001.html": [
+       "de6398e324b93d58f54b449118feb8b0c69250af",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "grid-placement-items-spanning-multiple-rows-002.html": [
+       "f055340a3a3c014626700986aadcf1042b2b3fec",
        [
         null,
         [
@@ -118936,6 +118951,21 @@
       ]
      ]
     },
+    "css-inline": {
+     "empty-text-node-001.html": [
+      "eddf31ec59ccc206975b9947d1b4bf75e88063ed",
+      [
+       null,
+       [
+        [
+         "/css/css-inline/empty-text-node-001-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ]
+    },
     "css-layout-api": {
      "auto-block-size": {
       "absolute.https.html": [
@@ -202873,6 +202903,19 @@
         ]
        ]
       },
+      "input-checkbox-disabled-checked.html": [
+       "f72fdf62fdcc148a0799b06bde02ccaf4186c8fb",
+       [
+        null,
+        [
+         [
+          "/html/rendering/widgets/input-checkbox-disabled-checked-notref.html",
+          "!="
+         ]
+        ],
+        {}
+       ]
+      ],
       "input-date-baseline.html": [
        "0d8f46c06421d22d9d680a83785460bfa865dc81",
        [
@@ -202899,6 +202942,19 @@
         {}
        ]
       ],
+      "input-radio-disabled-checked.html": [
+       "3ac2a37199b653ab4e1dd5d098bce9bb9e83dc44",
+       [
+        null,
+        [
+         [
+          "/html/rendering/widgets/input-radio-disabled-checked-notref.html",
+          "!="
+         ]
+        ],
+        {}
+       ]
+      ],
       "input-time-content-size.html": [
        "4a378f6923a8910b96f8afa84125a8fbac4a5d05",
        [
@@ -208236,136 +208292,138 @@
     ]
    },
    "scroll-animations": {
-    "animation-with-animatable-interface.html": [
-     "7d9b11fbefdc0f65d5037b82afa4e2d819a133b2",
-     [
-      null,
+    "scroll-timelines": {
+     "animation-with-animatable-interface.html": [
+      "7d9b11fbefdc0f65d5037b82afa4e2d819a133b2",
       [
+       null,
        [
-        "/scroll-animations/animation-ref.html",
-        "=="
-       ]
-      ],
-      {}
-     ]
-    ],
-    "animation-with-display-none.html": [
-     "85cebad50be37a6b856e72d424b84b0d6d57cc9d",
-     [
-      null,
+        [
+         "/scroll-animations/scroll-timelines/animation-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "animation-with-display-none.html": [
+      "85cebad50be37a6b856e72d424b84b0d6d57cc9d",
       [
+       null,
        [
-        "/scroll-animations/animation-ref.html",
-        "=="
-       ]
-      ],
-      {}
-     ]
-    ],
-    "animation-with-overflow-hidden.html": [
-     "5a0bbfb53a4e2fcd2e2934af379eb99271c4c7dd",
-     [
-      null,
+        [
+         "/scroll-animations/scroll-timelines/animation-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "animation-with-overflow-hidden.html": [
+      "5a0bbfb53a4e2fcd2e2934af379eb99271c4c7dd",
       [
+       null,
        [
-        "/scroll-animations/animation-with-overflow-hidden-ref.html",
-        "=="
-       ]
-      ],
-      {}
-     ]
-    ],
-    "animation-with-root-scroller.html": [
-     "21225146fb44001aec892c10029c83b05a8bd25d",
-     [
-      null,
+        [
+         "/scroll-animations/scroll-timelines/animation-with-overflow-hidden-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "animation-with-root-scroller.html": [
+      "21225146fb44001aec892c10029c83b05a8bd25d",
       [
+       null,
        [
-        "/scroll-animations/animation-with-root-scroller-ref.html",
-        "=="
-       ]
-      ],
-      {}
-     ]
-    ],
-    "animation-with-transform.html": [
-     "e6472cac1261965d4cbdc87852fb4cb40a6ade29",
-     [
-      null,
+        [
+         "/scroll-animations/scroll-timelines/animation-with-root-scroller-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "animation-with-transform.html": [
+      "e6472cac1261965d4cbdc87852fb4cb40a6ade29",
       [
+       null,
        [
-        "/scroll-animations/animation-ref.html",
-        "=="
-       ]
-      ],
-      {}
-     ]
-    ],
-    "layout-changes-on-percentage-based-timeline.html": [
-     "f97358144b0c199e7c08252cb96162ca3502f7a9",
-     [
-      null,
+        [
+         "/scroll-animations/scroll-timelines/animation-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "layout-changes-on-percentage-based-timeline.html": [
+      "f97358144b0c199e7c08252cb96162ca3502f7a9",
       [
+       null,
        [
-        "/scroll-animations/animation-ref.html",
-        "=="
-       ]
-      ],
-      {}
-     ]
-    ],
-    "progress-based-effect-delay.tentative.html": [
-     "f92a3c6c99a74770016c23961c2c267c3fd4a209",
-     [
-      null,
+        [
+         "/scroll-animations/scroll-timelines/animation-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "progress-based-effect-delay.tentative.html": [
+      "f92a3c6c99a74770016c23961c2c267c3fd4a209",
       [
+       null,
        [
-        "/scroll-animations/progress-based-effect-delay-ref.html",
-        "=="
-       ]
-      ],
-      {}
-     ]
-    ],
-    "set-current-time-before-play.html": [
-     "0d7e08d3320c8c29904a798546138ca8db9f2398",
-     [
-      null,
+        [
+         "/scroll-animations/scroll-timelines/progress-based-effect-delay-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "set-current-time-before-play.html": [
+      "0d7e08d3320c8c29904a798546138ca8db9f2398",
       [
+       null,
        [
-        "/scroll-animations/animation-ref.html",
-        "=="
-       ]
-      ],
-      {}
-     ]
-    ],
-    "two-animations-attach-to-same-scroll-timeline-cancel-one.html": [
-     "9071df4f7315f1d960c28dbcec68bc30d6871bf8",
-     [
-      null,
+        [
+         "/scroll-animations/scroll-timelines/animation-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "two-animations-attach-to-same-scroll-timeline-cancel-one.html": [
+      "9071df4f7315f1d960c28dbcec68bc30d6871bf8",
       [
+       null,
        [
-        "/scroll-animations/animation-ref.html",
-        "=="
-       ]
-      ],
-      {}
-     ]
-    ],
-    "two-animations-attach-to-same-scroll-timeline.html": [
-     "885898d26f39c6c691146dbe46e73d202ec1ed97",
-     [
-      null,
+        [
+         "/scroll-animations/scroll-timelines/animation-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "two-animations-attach-to-same-scroll-timeline.html": [
+      "885898d26f39c6c691146dbe46e73d202ec1ed97",
       [
+       null,
        [
-        "/scroll-animations/animation-ref.html",
-        "=="
-       ]
-      ],
-      {}
+        [
+         "/scroll-animations/scroll-timelines/animation-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
      ]
-    ]
+    }
    },
    "selection": {
     "caret": {
@@ -235490,11 +235548,11 @@
        []
       ],
       "font-palette-computed-expected.txt": [
-       "dee166afbcf41df2364be766bd45add367ee68b8",
+       "cb95d39877d98351ba2554847535f893422bc1c9",
        []
       ],
       "font-palette-valid-expected.txt": [
-       "2d88f5719bf349c9b7db5725b06215f66f9748a8",
+       "a500af93d356c334ca369067255a7fa117740f8b",
        []
       ],
       "font-palette-values-invalid-expected.txt": [
@@ -244346,6 +244404,10 @@
       "42e669d316d4f470cf4d339fce632a6a69c583ca",
       []
      ],
+     "empty-text-node-001-ref.html": [
+      "7ec7f1bdb0b910cb1b525535d3cc1a280a3b6136",
+      []
+     ],
      "inheritance-expected.txt": [
       "04a22e0fee19d37384921ec696fe9e6148e04bdc",
       []
@@ -279919,6 +279981,10 @@
         []
        ]
       },
+      "input-checkbox-disabled-checked-notref.html": [
+       "8caced027a54676335db3b2199e85cbafba2abee",
+       []
+      ],
       "input-date-baseline-ref.html": [
        "dcef656bad00792262e05ab643195ef6804d5336",
        []
@@ -279927,6 +279993,10 @@
        "18019c56b1bd2535b998c05f301fca3abc8ae1bb",
        []
       ],
+      "input-radio-disabled-checked-notref.html": [
+       "987e03cd92d7824e61cc75bbd723bd9583ab8fe1",
+       []
+      ],
       "input-time-content-size-ref.html": [
        "938d2659a8a8c02230c92db5b575818a5d056809",
        []
@@ -288143,7 +288213,7 @@
     ]
    },
    "lint.ignore": [
-    "960c1837285bb57ed7cd27818c610baf2276b02c",
+    "38229b4ce7a4b170d6455e6f7ce0936aa44b8465",
     []
    ],
    "loading": {
@@ -290903,7 +290973,7 @@
       []
      ],
      "unload-bubbles.html": [
-      "44f0c0cef319f279435bb38f670ed96d00ebcd17",
+      "cc9e14f787190efb3dd16f434636a831fd57c18d",
       []
      ],
      "unload.html": [
@@ -295001,18 +295071,6 @@
      "c7f0e4903b5ad46b411f0f0741112af80661cfc7",
      []
     ],
-    "animation-ref.html": [
-     "91587153215d4dd9ea952631464f6c4acd3d7208",
-     []
-    ],
-    "animation-with-overflow-hidden-ref.html": [
-     "c045f1a1c9520312db6fd68afd7f6201ce574fbb",
-     []
-    ],
-    "animation-with-root-scroller-ref.html": [
-     "58435be6312cb06f936117c631679663c4ed2b07",
-     []
-    ],
     "css": {
      "scroll-timeline-cssom.tentative-expected.txt": [
       "116f38ce142edb0efb004461c1a8a9a314c36283",
@@ -295023,14 +295081,32 @@
      "130fdb6c1b681ccf9b172eebc705737ab8bbea13",
      []
     ],
-    "progress-based-effect-delay-ref.html": [
-     "59366a88dde86d0deedce5eeea58b9d6c98e0913",
-     []
-    ],
-    "testcommon.js": [
-     "891e1f68699d2e879b360d44aecdb6425140d7fe",
-     []
-    ]
+    "scroll-timelines": {
+     "animation-ref.html": [
+      "91587153215d4dd9ea952631464f6c4acd3d7208",
+      []
+     ],
+     "animation-with-overflow-hidden-ref.html": [
+      "c045f1a1c9520312db6fd68afd7f6201ce574fbb",
+      []
+     ],
+     "animation-with-root-scroller-ref.html": [
+      "58435be6312cb06f936117c631679663c4ed2b07",
+      []
+     ],
+     "idlharness.window-expected.txt": [
+      "130fdb6c1b681ccf9b172eebc705737ab8bbea13",
+      []
+     ],
+     "progress-based-effect-delay-ref.html": [
+      "59366a88dde86d0deedce5eeea58b9d6c98e0913",
+      []
+     ],
+     "testcommon.js": [
+      "891e1f68699d2e879b360d44aecdb6425140d7fe",
+      []
+     ]
+    }
    },
    "scroll-to-text-fragment": {
     "DIR_METADATA": [
@@ -296390,10 +296466,6 @@
       "3fe3911ad6b7f708251a686c5bf2fd575db2f67d",
       []
      ],
-     "fetch-request-css-base-url.https-expected.txt": [
-      "441ac917dc1cd52a30d72cfb570b2fca1155cac3",
-      []
-     ],
      "fetch-request-xhr-sync-error.https.window-expected.txt": [
       "f9ed9ece338caa4fe85b2af0bae8ea5c15c4789f",
       []
@@ -297020,7 +297092,7 @@
        []
       ],
       "fetch-request-css-base-url-worker.js": [
-       "10d3b1880934e633ecaaf9a0dc590547ce46e462",
+       "f3d6a73bdde8ad26e00125c655450ccdc0f0baf8",
        []
       ],
       "fetch-request-css-cross-origin-mime-check-cross.css": [
@@ -297383,6 +297455,10 @@
        "6f2a8ae1d749bb58e547bdb511b895b0e6bdee43",
        []
       ],
+      "navigation-timing-worker-extended.js": [
+       "79c54088ff6004e2b836ab0d4cba613182379410",
+       []
+      ],
       "navigation-timing-worker.js": [
        "8539b40066dd91bbfaf7ef240b8104dcb2ab3b27",
        []
@@ -303946,10 +304022,6 @@
       "01137479f933ad7649b9aa7029d4643bab92494f",
       []
      ],
-     "sdes-dont-dont-dont-expected.txt": [
-      "efdc199bdf252230d68403272860b9c84f7e1680",
-      []
-     ],
      "vp8-fmtp-expected.txt": [
       "812fedeeb10405b2948d70825e901e1581886e9b",
       []
@@ -344910,21 +344982,21 @@
        ]
       ],
       "font-palette-computed.html": [
-       "325875d3f044c4178f9e3fb324ce39e9abaafcf6",
+       "b73013c14756e302ef8de59344a70f4a07a2c7df",
        [
         null,
         {}
        ]
       ],
       "font-palette-invalid.html": [
-       "faba2b1f6d79fac31c66c4d6f00a494bd718a906",
+       "1944831bf30448cfc2e70521f1ba7ab0a420b9ef",
        [
         null,
         {}
        ]
       ],
       "font-palette-valid.html": [
-       "4a3a34ba8c489297bcdbbfb2f7a31c841176449e",
+       "68636ece472fd745ba8153ce6f1d1b49fa1eb6e7",
        [
         null,
         {}
@@ -455750,7 +455822,7 @@
      ]
     ],
     "unload-bubbles.html": [
-     "41f40c5bcf7abbf03f7f1fe8e1cd6822654fdde6",
+     "19b169c5e653a8d190fb000298f5dd33c7412436",
      [
       null,
       {}
@@ -475288,27 +475360,6 @@
     ]
    },
    "scroll-animations": {
-    "cancel-animation.html": [
-     "9b578e664c7da500b1ccad1038f5783c1d23b611",
-     [
-      null,
-      {}
-     ]
-    ],
-    "constructor-no-document.html": [
-     "2062d91b1d694ab397cdd93b9b2ff06801cb5dc0",
-     [
-      null,
-      {}
-     ]
-    ],
-    "constructor.html": [
-     "02f29a0da9a8bbbd59e9bffabf1bea4ee51ac181",
-     [
-      null,
-      {}
-     ]
-    ],
     "css": {
      "animation-shorthand.html": [
       "7b30ec611f50c8b9bee8b28187cd9d39612b6f8d",
@@ -475381,7 +475432,7 @@
       ]
      ],
      "at-scroll-timeline-ignored.tentative.html": [
-      "bb8b44f6c278d88a0a3a894a4d13483011c39f3e",
+      "dfb6ba3ceeda6c5cdd79fbc8eb688e31d797477f",
       [
        null,
        {}
@@ -475402,7 +475453,7 @@
       ]
      ],
      "at-scroll-timeline-offset-invalidation.tentative.html": [
-      "25d2484159df69e18bf21e1fd82c67ce9bac4fe3",
+      "13d816a062009742ab7db36026e115deb432734f",
       [
        null,
        {}
@@ -475423,7 +475474,7 @@
       ]
      ],
      "at-scroll-timeline-source-invalidation.tentative.html": [
-      "6f9ce7a85a52529f29cdf78c91972cf0a2f1e60b",
+      "fee39eb08b77114f2c0f88726acb0a298f0ba9e1",
       [
        null,
        {}
@@ -475465,217 +475516,240 @@
       ]
      ]
     },
-    "current-time-nan.html": [
-     "48b44071681c1556ebad8d019fe00a1a1b2cac14",
-     [
-      null,
-      {}
-     ]
-    ],
-    "current-time-root-scroller.html": [
-     "6d39afaa7408099ab5f3a807c1cac3cea4da8467",
-     [
-      null,
-      {}
-     ]
-    ],
-    "current-time-writing-modes.html": [
-     "0361272d6fef22692e05a5cc8db4fa1fd02e0377",
-     [
-      null,
-      {}
-     ]
-    ],
-    "current-time.html": [
-     "ed3ed49eef78247c23885dddfed43901e3aa959b",
-     [
-      null,
-      {}
-     ]
-    ],
-    "effect-updateTiming.html": [
-     "cd5e33542b8a7ed7aa6250f82d9ef929af8d669a",
-     [
-      null,
-      {}
-     ]
-    ],
-    "element-based-offset-clamp.html": [
-     "46e65f34b7501df8ccb847bc15c2ff161a95f391",
-     [
-      null,
-      {}
-     ]
-    ],
-    "element-based-offset-unresolved.html": [
-     "29c7fa88c0b87b12946874289e5f83f9168f6438",
-     [
-      null,
-      {}
-     ]
-    ],
-    "element-based-offset.html": [
-     "355b2ab03ca29799499ecb62998e1d37bc100352",
-     [
-      null,
-      {}
-     ]
-    ],
-    "finish-animation.html": [
-     "ca46d15f5a5075d42d35e71a802608424ce6f7c3",
-     [
-      null,
-      {}
-     ]
-    ],
-    "idlharness.window.js": [
-     "90157580ce00716403346f369b1e25bba8db23c2",
-     [
-      "scroll-animations/idlharness.window.html",
-      {
-       "script_metadata": [
-        [
-         "script",
-         "/resources/WebIDLParser.js"
-        ],
-        [
-         "script",
-         "/resources/idlharness.js"
+    "scroll-timelines": {
+     "cancel-animation.html": [
+      "9b578e664c7da500b1ccad1038f5783c1d23b611",
+      [
+       null,
+       {}
+      ]
+     ],
+     "constructor-no-document.html": [
+      "2062d91b1d694ab397cdd93b9b2ff06801cb5dc0",
+      [
+       null,
+       {}
+      ]
+     ],
+     "constructor.html": [
+      "02f29a0da9a8bbbd59e9bffabf1bea4ee51ac181",
+      [
+       null,
+       {}
+      ]
+     ],
+     "current-time-nan.html": [
+      "48b44071681c1556ebad8d019fe00a1a1b2cac14",
+      [
+       null,
+       {}
+      ]
+     ],
+     "current-time-root-scroller.html": [
+      "6d39afaa7408099ab5f3a807c1cac3cea4da8467",
+      [
+       null,
+       {}
+      ]
+     ],
+     "current-time-writing-modes.html": [
+      "0361272d6fef22692e05a5cc8db4fa1fd02e0377",
+      [
+       null,
+       {}
+      ]
+     ],
+     "current-time.html": [
+      "ed3ed49eef78247c23885dddfed43901e3aa959b",
+      [
+       null,
+       {}
+      ]
+     ],
+     "effect-updateTiming.html": [
+      "cd5e33542b8a7ed7aa6250f82d9ef929af8d669a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "element-based-offset-clamp.html": [
+      "46e65f34b7501df8ccb847bc15c2ff161a95f391",
+      [
+       null,
+       {}
+      ]
+     ],
+     "element-based-offset-unresolved.html": [
+      "29c7fa88c0b87b12946874289e5f83f9168f6438",
+      [
+       null,
+       {}
+      ]
+     ],
+     "element-based-offset.html": [
+      "355b2ab03ca29799499ecb62998e1d37bc100352",
+      [
+       null,
+       {}
+      ]
+     ],
+     "finish-animation.html": [
+      "ca46d15f5a5075d42d35e71a802608424ce6f7c3",
+      [
+       null,
+       {}
+      ]
+     ],
+     "idlharness.window.js": [
+      "90157580ce00716403346f369b1e25bba8db23c2",
+      [
+       "scroll-animations/scroll-timelines/idlharness.window.html",
+       {
+        "script_metadata": [
+         [
+          "script",
+          "/resources/WebIDLParser.js"
+         ],
+         [
+          "script",
+          "/resources/idlharness.js"
+         ]
         ]
-       ]
-      }
+       }
+      ]
+     ],
+     "multiple-scroll-offsets.html": [
+      "09a3b14c189ea809c4af5f6f3749a61a8f9d9bcb",
+      [
+       null,
+       {}
+      ]
+     ],
+     "pause-animation.html": [
+      "9486788e3fe7e685ff10b44efb36d48a5a09dcdc",
+      [
+       null,
+       {}
+      ]
+     ],
+     "play-animation.html": [
+      "522bdc48829a25707b32afe0f00385265221fc36",
+      [
+       null,
+       {}
+      ]
+     ],
+     "progress-based-current-time.tentative.html": [
+      "afd7e82b6cd71edf0613274a9d568c0b1ca3284e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "reverse-animation.html": [
+      "0e9fbf28828a2304fb6121870784728ce2b1f742",
+      [
+       null,
+       {}
+      ]
+     ],
+     "scroll-animation-effect-phases.tentative.html": [
+      "ba4c692c087f438c95bff36571a6c6f7d8e85bc6",
+      [
+       null,
+       {
+        "timeout": "long"
+       }
+      ]
+     ],
+     "scroll-animation-inactive-timeline.html": [
+      "257f94d26f611c63b9558a3ad6bd501b76290256",
+      [
+       null,
+       {}
+      ]
+     ],
+     "scroll-animation.html": [
+      "102f846b3fe45839cefec9c9c9568fc166d25f0e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "scroll-timeline-invalidation.html": [
+      "d5c8750dcdf8212f16e57f0f1f9ae313ae8fe169",
+      [
+       null,
+       {}
+      ]
+     ],
+     "scroll-timeline-phases.tentative.html": [
+      "6e5c752d59905ae49b7087cf356dee76b12c0f9e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "scroll-timeline-snapshotting.html": [
+      "4398de6265c07488b0017fda7adfda9dd01c09e2",
+      [
+       null,
+       {
+        "testdriver": true
+       }
+      ]
+     ],
+     "setting-current-time.html": [
+      "8573a7aa49fdfdf3fd418c506df7130b2c0bbe4b",
+      [
+       null,
+       {}
+      ]
+     ],
+     "setting-playback-rate.html": [
+      "5bac2712bfda14b2530eaea70297991b131e6224",
+      [
+       null,
+       {}
+      ]
+     ],
+     "setting-start-time.html": [
+      "2cf1ddb9b38d2fe6383505bed8898bcf79177fde",
+      [
+       null,
+       {}
+      ]
+     ],
+     "setting-timeline.tentative.html": [
+      "ae29dc3fe2d8771844fd457f95ec12e6d83fdfe1",
+      [
+       null,
+       {}
+      ]
+     ],
+     "source-quirks-mode.html": [
+      "0614509e4583897bbc82798160c4f8b34a31f118",
+      [
+       null,
+       {}
+      ]
+     ],
+     "update-playback-rate.html": [
+      "1db31602f312c44906a3a14e8ef542d355f31a1f",
+      [
+       null,
+       {}
+      ]
+     ],
+     "updating-the-finished-state.html": [
+      "184b8699170d02f7ad300b909b6a69f404d5d460",
+      [
+       null,
+       {}
+      ]
      ]
-    ],
-    "multiple-scroll-offsets.html": [
-     "09a3b14c189ea809c4af5f6f3749a61a8f9d9bcb",
-     [
-      null,
-      {}
-     ]
-    ],
-    "pause-animation.html": [
-     "9486788e3fe7e685ff10b44efb36d48a5a09dcdc",
-     [
-      null,
-      {}
-     ]
-    ],
-    "play-animation.html": [
-     "522bdc48829a25707b32afe0f00385265221fc36",
-     [
-      null,
-      {}
-     ]
-    ],
-    "progress-based-current-time.tentative.html": [
-     "afd7e82b6cd71edf0613274a9d568c0b1ca3284e",
-     [
-      null,
-      {}
-     ]
-    ],
-    "reverse-animation.html": [
-     "0e9fbf28828a2304fb6121870784728ce2b1f742",
-     [
-      null,
-      {}
-     ]
-    ],
-    "scroll-animation-effect-phases.tentative.html": [
-     "ba4c692c087f438c95bff36571a6c6f7d8e85bc6",
-     [
-      null,
-      {
-       "timeout": "long"
-      }
-     ]
-    ],
-    "scroll-animation-inactive-timeline.html": [
-     "257f94d26f611c63b9558a3ad6bd501b76290256",
-     [
-      null,
-      {}
-     ]
-    ],
-    "scroll-animation.html": [
-     "102f846b3fe45839cefec9c9c9568fc166d25f0e",
-     [
-      null,
-      {}
-     ]
-    ],
-    "scroll-timeline-invalidation.html": [
-     "d5c8750dcdf8212f16e57f0f1f9ae313ae8fe169",
-     [
-      null,
-      {}
-     ]
-    ],
-    "scroll-timeline-phases.tentative.html": [
-     "6e5c752d59905ae49b7087cf356dee76b12c0f9e",
-     [
-      null,
-      {}
-     ]
-    ],
-    "scroll-timeline-snapshotting.html": [
-     "4398de6265c07488b0017fda7adfda9dd01c09e2",
-     [
-      null,
-      {
-       "testdriver": true
-      }
-     ]
-    ],
-    "setting-current-time.html": [
-     "8573a7aa49fdfdf3fd418c506df7130b2c0bbe4b",
-     [
-      null,
-      {}
-     ]
-    ],
-    "setting-playback-rate.html": [
-     "5bac2712bfda14b2530eaea70297991b131e6224",
-     [
-      null,
-      {}
-     ]
-    ],
-    "setting-start-time.html": [
-     "2cf1ddb9b38d2fe6383505bed8898bcf79177fde",
-     [
-      null,
-      {}
-     ]
-    ],
-    "setting-timeline.tentative.html": [
-     "ae29dc3fe2d8771844fd457f95ec12e6d83fdfe1",
-     [
-      null,
-      {}
-     ]
-    ],
-    "source-quirks-mode.html": [
-     "0614509e4583897bbc82798160c4f8b34a31f118",
-     [
-      null,
-      {}
-     ]
-    ],
-    "update-playback-rate.html": [
-     "1db31602f312c44906a3a14e8ef542d355f31a1f",
-     [
-      null,
-      {}
-     ]
-    ],
-    "updating-the-finished-state.html": [
-     "184b8699170d02f7ad300b909b6a69f404d5d460",
-     [
-      null,
-      {}
-     ]
-    ]
+    }
    },
    "scroll-to-text-fragment": {
     "find-range-from-text-directive.html": [
@@ -478817,6 +478891,13 @@
        }
       ]
      ],
+     "navigation-timing-extended.https.html": [
+      "acb02c6fe1f56ae9cf4d61516fdbf05306050914",
+      [
+       null,
+       {}
+      ]
+     ],
      "navigation-timing.https.html": [
       "6b51a5c2da213eb27cf0d9fd401b7c49b5349950",
       [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-075.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-075.html
new file mode 100644
index 0000000..34cf1ea
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-075.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>
+  Out-of-flow positioned in non-containing-block in multicol.
+</title>
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#abspos-breaking">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .multicol {
+    column-count: 4;
+    column-gap: 0px;
+    width: 100px;
+  }
+  .abs {
+    position: absolute;
+    background-color: green;
+    height: 400px;
+    width: 25px;
+    top: 0;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol">
+  <div style="position: relative;">
+    <div style="height: 400px; background: red"></div>
+    <div style="will-change: opacity">
+      <div class="abs"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-basic-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-basic-expected.txt
deleted file mode 100644
index 0cc2db7f..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-basic-expected.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-This is a testharness.js-based test.
-PASS A1 Anonymous layers
-FAIL A2 Anonymous layers assert_equals: A2 Anonymous layers, target 'first' expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
-FAIL A3 Anonymous layers assert_equals: A3 Anonymous layers, target 'first' expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
-PASS A4 Anonymous layers
-PASS A5 Anonymous layers
-PASS A6 Anonymous layers
-PASS A7 Anonymous layers
-PASS A8 Anonymous layers
-PASS A9 Anonymous layers
-PASS B1 Named layers
-FAIL B2 Named layers assert_equals: B2 Named layers, target 'first' expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
-PASS B3 Named layers
-PASS B4 Named layers
-PASS B5 Named layers
-PASS B6 Named layers
-PASS B7 Named layers
-PASS B8 Named layers
-PASS B9 Named layers
-PASS B10 Named layers
-PASS C1 Named layers shorthand
-PASS C2 Named layers shorthand
-PASS C3 Named layers shorthand
-PASS C4 Named layers shorthand
-PASS C5 Named layers shorthand
-PASS D1 Mixed named and anonymous layers
-PASS D2 Mixed named and anonymous layers
-PASS D3 Mixed named and anonymous layers
-PASS D4 Mixed named and anonymous layers
-PASS D5 Mixed named and anonymous layers
-PASS E1 Statement syntax
-PASS E2 Statement syntax
-PASS E3 Statement syntax
-PASS E4 Statement syntax
-PASS E5 Statement syntax
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-basic.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-basic.html
index f92e142..e214bff 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-basic.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-basic.html
@@ -56,9 +56,9 @@
         title: 'A5 Anonymous layers',
         style: `
             @layer {
-                target { color: red; }
+                target { color: green; }
                 @layer {
-                    target { color: green; }
+                    target { color: red; }
                 }
             }
         `,
@@ -68,9 +68,9 @@
         style: `
             @layer {
                 @layer {
-                    target { color: green; }
+                    target { color: red; }
                 }
-                target { color: red; }
+                target { color: green; }
             }
         `,
     },
@@ -85,9 +85,9 @@
             }
             @layer {
                 @layer {
-                    target { color: green; }
+                    target { color: red; }
                 }
-                target { color: red; }
+                target { color: green; }
             }
         `,
     },
@@ -104,9 +104,9 @@
             }
             @layer {
                 @layer {
-                    target { color: green; }
+                    target { color: red; }
                 }
-                target { color: red; }
+                target { color: green; }
             }
         `,
     },
@@ -122,10 +122,10 @@
             @layer {
                 @layer {
                     @layer {
-                        target { color: green; }
+                        target { color: red; }
                     }
                 }
-                target { color: red; }
+                target { color: green; }
             }
         `,
     },
@@ -175,9 +175,9 @@
         title: 'B5 Named layers',
         style: `
             @layer A {
-                target { color: red; }
+                target { color: green; }
                 @layer A {
-                    target { color: green; }
+                    target { color: red; }
                 }
             }
         `,
@@ -362,11 +362,11 @@
         style: `
             @layer A {
                 @layer {
-                    target { color: green; }
+                    target { color: red; }
                 }
             }
             @layer A {
-                target { color: red; }
+                target { color: green; }
             }
         `,
     },
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-counter-style-override.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-counter-style-override.html
index 7ffd358..1720898 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-counter-style-override.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-counter-style-override.html
@@ -35,20 +35,20 @@
 
 const testCases = [
   {
-    title: '@counter-style layered overrides unlayered',
+    title: '@counter-style unlayered overrides layered',
     style: `
       #target::before {
         content: counter(dont-care, custom-counter-style);
       }
 
-      @layer {
-        @counter-style custom-counter-style {
-          system: extends four;
-        }
+      @counter-style custom-counter-style {
+        system: extends four;
       }
 
-      @counter-style custom-counter-style {
-        system: extends three;
+      @layer {
+        @counter-style custom-counter-style {
+          system: extends three;
+        }
       }
     `
   },
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-font-face-override.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-font-face-override.html
index 8275b08..d35caca 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-font-face-override.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-font-face-override.html
@@ -24,17 +24,17 @@
         font-family: custom-font;
       }
 
+      @font-face {
+        font-family: custom-font;
+        src: url('/fonts/Ahem.ttf');
+      }
+
       @layer {
         @font-face {
           font-family: custom-font;
           src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff');
         }
       }
-
-      @font-face {
-        font-family: custom-font;
-        src: url('/fonts/Ahem.ttf');
-      }
     `
   },
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-import-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-import-expected.txt
deleted file mode 100644
index 1fc7e1b..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-import-expected.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-This is a testharness.js-based test.
-FAIL A1 Layer rules with import assert_equals: A1 Layer rules with import, target 'first' expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
-FAIL A2 Layer rules with import assert_equals: A2 Layer rules with import, target 'first' expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
-FAIL A3 Layer rules with import assert_equals: A3 Layer rules with import, target 'first' expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
-PASS A4 Layer rules with import
-FAIL B1 Anonymous imports assert_equals: B1 Anonymous imports, target 'first' expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
-PASS B2 Anonymous imports
-PASS B3 Anonymous imports
-PASS B4 Anonymous imports
-FAIL C1 Named imports assert_equals: C1 Named imports, target 'first' expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
-PASS C2 Named imports
-PASS C3 Named imports
-PASS C4 Named imports
-PASS C5 Named imports
-PASS C6 Named imports
-PASS C7 Named imports
-PASS C8 Named imports
-PASS C9 Named imports
-PASS D1 Layer statement with imports
-PASS D2 Layer statement with imports
-PASS D3 Layer statement with imports
-PASS D4 Layer statement with imports
-PASS D5 Layer statement with imports
-PASS D6 Layer statement with imports
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-import.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-import.html
index 935659a..0406f02 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-import.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-import.html
@@ -145,9 +145,9 @@
     {
         title: 'C4 Named imports',
         style: `
-            @import url(layer-green.css) layer(A);
+            @import url(layer-red.css) layer(A);
             @layer A {
-                target { color: red; }
+                target { color: green; }
             }
         `
     },
@@ -192,8 +192,8 @@
         title: 'C9 Named imports',
         style: `
             @import url(basic-red.css) layer(A);
-            @import url(basic-green.css) layer(B.A);
-            @import url(basic-red.css) layer(B);
+            @import url(basic-red.css) layer(B.A);
+            @import url(basic-green.css) layer(B);
         `
     },
     {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-keyframes-override-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-keyframes-override-expected.txt
deleted file mode 100644
index 1d5989f..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-keyframes-override-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-FAIL @keyframes unlayered overrides layered assert_equals: expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
-PASS @keyframes override between layers
-PASS @keyframes override update with appended sheet 1
-PASS @keyframes override update with appended sheet 2
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-keyframes-override.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-keyframes-override.html
index f896303..d0f4044 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-keyframes-override.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-keyframes-override.html
@@ -29,15 +29,15 @@
         animation: anim 1s paused;
       }
 
+      @keyframes anim {
+        from { background-color: green; }
+      }
+
       @layer {
         @keyframes anim {
           from { background-color: red; }
         }
       }
-
-      @keyframes anim {
-        from { background-color: green; }
-      }
     `
   },
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-property-override.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-property-override.html
index f0f8d83..9d3f9cb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-property-override.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-property-override.html
@@ -23,26 +23,26 @@
 
 const testCases = [
   {
-    title: '@property layered overrides unlayered',
+    title: '@property unlayered overrides layered',
     style: `
       #target {
         background-color: var(--foo);
       }
 
+      @property --foo {
+        syntax: '<color>';
+        inherits: false;
+        initial-value: green;
+      }
+
       @layer {
         @property --foo {
           syntax: '<color>';
           inherits: false;
-          initial-value: green;
+          initial-value: red;
         }
       }
-
-      @property --foo {
-        syntax: '<color>';
-        inherits: false;
-        initial-value: red;
-      }
-    `
+   `
   },
 
   {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-scroll-timeline-override.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-scroll-timeline-override.html
index 9a50914..59b8590 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-scroll-timeline-override.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-scroll-timeline-override.html
@@ -46,25 +46,25 @@
 
 const testCases = [
   {
-    title: '@scroll-timeline layered overrides unlayered',
+    title: '@scroll-timeline unlayered overrides layered',
     style: `
       #target {
         animation-timeline: timeline;
       }
 
+      @scroll-timeline timeline {
+        source: selector(#scroller);
+        start: 0px;
+        end: 50px;
+      }
+
       @layer {
         @scroll-timeline timeline {
           source: selector(#scroller);
           start: 0px;
-          end: 50px;
+          end: 100px;
         }
       }
-
-      @scroll-timeline timeline {
-        source: selector(#scroller);
-        start: 0px;
-        end: 100px;
-      }
     `
   },
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/presentational-hints-cascade.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/presentational-hints-cascade.html
index 729dc71..c3188fd 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/presentational-hints-cascade.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/presentational-hints-cascade.html
@@ -12,10 +12,17 @@
 #target1 {
   width: 100px;
 }
+
+@layer {
+  #target3 {
+    width: 100px;
+  }
+}
 </style>
 
 <img class=test id=target1 width=200>
 <img class=test id=target2 width=200 style="width: 100px">
+<img class=test id=target3 width=200>
 
 <script>
 test(() => {
@@ -25,4 +32,8 @@
 test(() => {
   assert_equals(getComputedStyle(target2).width, '100px');
 }, 'Presentational hints have lower precedence than the style attribute');
+
+test(() => {
+  assert_equals(getComputedStyle(target3).width, '100px');
+}, 'Presentational hints have lower precedence than layered style');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-computed-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-computed-expected.txt
index dee166af..cb95d39 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-computed-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-computed-expected.txt
@@ -1,5 +1,4 @@
 This is a testharness.js-based test.
-FAIL Property font-palette value 'none' assert_true: font-palette doesn't seem to be supported in the computed style expected true got false
 FAIL Property font-palette value 'normal' assert_true: font-palette doesn't seem to be supported in the computed style expected true got false
 FAIL Property font-palette value 'light' assert_true: font-palette doesn't seem to be supported in the computed style expected true got false
 FAIL Property font-palette value 'dark' assert_true: font-palette doesn't seem to be supported in the computed style expected true got false
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-computed.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-computed.html
index 325875d..b73013c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-computed.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-computed.html
@@ -12,7 +12,6 @@
 <body>
 <div id="target"></div>
 <script>
-test_computed_value('font-palette', 'none');
 test_computed_value('font-palette', 'normal');
 test_computed_value('font-palette', 'light');
 test_computed_value('font-palette', 'dark');
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-invalid.html
index faba2b1..1944831 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-invalid.html
@@ -13,6 +13,7 @@
 <script>
 test_invalid_value('font-palette', 'normal none');
 test_invalid_value('font-palette', 'none, light');
+test_invalid_value('font-palette', 'none');
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-valid-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-valid-expected.txt
index 2d88f57..a500af9 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-valid-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-valid-expected.txt
@@ -1,5 +1,4 @@
 This is a testharness.js-based test.
-FAIL e.style['font-palette'] = "none" should set the property value assert_not_equals: property should be set got disallowed value ""
 FAIL e.style['font-palette'] = "normal" should set the property value assert_not_equals: property should be set got disallowed value ""
 FAIL e.style['font-palette'] = "light" should set the property value assert_not_equals: property should be set got disallowed value ""
 FAIL e.style['font-palette'] = "dark" should set the property value assert_not_equals: property should be set got disallowed value ""
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-valid.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-valid.html
index 4a3a34b..68636ece 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-valid.html
@@ -4,14 +4,13 @@
 <meta charset="utf-8">
 <title>CSS Fonts Module Level 4: parsing font-palette with valid values</title>
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-palette-prop">
-<meta name="assert" content="font-palette supports the full grammar 'none | normal | light | dark | <palette-identifier>'.">
+<meta name="assert" content="font-palette supports the full grammar 'normal | light | dark | <palette-identifier>'.">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/css/support/parsing-testcommon.js"></script>
 </head>
 <body>
 <script>
-test_valid_value('font-palette', 'none');
 test_valid_value('font-palette', 'normal');
 test_valid_value('font-palette', 'light');
 test_valid_value('font-palette', 'dark');
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/empty-text-node-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-inline/empty-text-node-001-ref.html
new file mode 100644
index 0000000..7ec7f1bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/empty-text-node-001-ref.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang=en>
+<meta charset="utf-8">
+<title>CSS Inline reference</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+<style>
+.green { color: green; }
+.red { color: red; }
+.ref {
+  display: inline-block;
+}
+div div {
+  width: 50px;
+  height: 0px;
+  border: 20px solid green;
+  margin: 10px;
+}
+</style>
+<p>Test passes if the <span class=green>green</span> boxes have <span class=red>no red</span> in the middle.</p>
+
+<div class=ref>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+</div>
+
+<div class=ref>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+</div>
+
+<div class=ref>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/empty-text-node-001.html b/third_party/blink/web_tests/external/wpt/css/css-inline/empty-text-node-001.html
new file mode 100644
index 0000000..eddf31e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/empty-text-node-001.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html lang=en>
+<meta charset="utf-8">
+<title>CSS Inline test: empty text node</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+<link rel="help" title="2.1. Layout of Line Boxes" href="https://drafts.csswg.org/css-inline/#line-boxes">
+<link rel="match" href="empty-text-node-001-ref.html">
+<meta name="assert" content="Empty text node in a line box is treated as zero height.">
+<style>
+.green { color: green; }
+.red { color: red; }
+.testContent, .testBefore, .testAfter {
+  display: inline-block;
+}
+div div {
+  width: 50px;
+  line-height: 30px;
+  border: 20px solid green;
+  background-color: red;
+  margin: 10px;
+}
+.testBefore div::before {
+  content: "";
+}
+.testAfter div::after {
+  content: "";
+}
+.normal { white-space: normal; }
+.nowrap { white-space: nowrap; }
+.pre { white-space: pre; }
+.prewrap { white-space: pre-wrap; }
+.preline { white-space: pre-line; }
+.breakspaces { white-space: break-spaces; }
+</style>
+<p>Test passes if the <span class=green>green</span> boxes have <span class=red>no red</span> in the middle.</p>
+
+<div class=testContent>
+  <div class=normal></div>
+  <div class=nowrap></div>
+  <div class=pre></div>
+  <div class=prewrap></div>
+  <div class=preline></div>
+  <div class=breakspaces></div>
+  <script>
+  [...document.querySelectorAll(".testContent div")].forEach((node, i) => node.appendChild(document.createTextNode("")));
+  </script>
+</div>
+
+<div class=testBefore>
+  <div class=normal></div>
+  <div class=nowrap></div>
+  <div class=pre></div>
+  <div class=prewrap></div>
+  <div class=preline></div>
+  <div class=breakspaces></div>
+</div>
+
+<div class=testAfter>
+  <div class=normal></div>
+  <div class=nowrap></div>
+  <div class=pre></div>
+  <div class=prewrap></div>
+  <div class=preline></div>
+  <div class=breakspaces></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-checkbox-disabled-checked-notref.html b/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-checkbox-disabled-checked-notref.html
new file mode 100644
index 0000000..8caced0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-checkbox-disabled-checked-notref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<input type=checkbox disabled>
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-checkbox-disabled-checked.html b/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-checkbox-disabled-checked.html
new file mode 100644
index 0000000..f72fdf62
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-checkbox-disabled-checked.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<title>checkbox with disabled and checked attributes renders differently than unchecked</title>
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1735077">
+<link rel=mismatch href="input-checkbox-disabled-checked-notref.html">
+<input type=checkbox disabled checked>
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-radio-disabled-checked-notref.html b/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-radio-disabled-checked-notref.html
new file mode 100644
index 0000000..987e03c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-radio-disabled-checked-notref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<input type=radio disabled>
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-radio-disabled-checked.html b/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-radio-disabled-checked.html
new file mode 100644
index 0000000..3ac2a37
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/widgets/input-radio-disabled-checked.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<title>radio with disabled and checked attributes renders differently than unchecked</title>
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1735077">
+<link rel=mismatch href="input-radio-disabled-checked-notref.html">
+<input type=radio disabled checked>
diff --git a/third_party/blink/web_tests/external/wpt/lint.ignore b/third_party/blink/web_tests/external/wpt/lint.ignore
index 960c1837..38229b4 100644
--- a/third_party/blink/web_tests/external/wpt/lint.ignore
+++ b/third_party/blink/web_tests/external/wpt/lint.ignore
@@ -798,7 +798,7 @@
 TESTHARNESS-IN-OTHER-TYPE: html/semantics/text-level-semantics/the-ruby-element/rt-without-ruby-crash.html
 TESTHARNESS-IN-OTHER-TYPE: portals/portals-no-frame-crash.html
 TESTHARNESS-IN-OTHER-TYPE: quirks/table-replaced-descendant-percentage-height-crash.html
-TESTHARNESS-IN-OTHER-TYPE: scroll-animations/null-scroll-source-crash.html
+TESTHARNESS-IN-OTHER-TYPE: scroll-animations/scroll-timelines/null-scroll-source-crash.html
 TESTHARNESS-IN-OTHER-TYPE: svg/extensibility/foreignObject/foreign-object-circular-filter-reference-crash.html
 TESTHARNESS-IN-OTHER-TYPE: svg/extensibility/foreignObject/foreign-object-under-clip-path-crash.html
 TESTHARNESS-IN-OTHER-TYPE: svg/extensibility/foreignObject/foreign-object-under-defs-crash.html
diff --git a/third_party/blink/web_tests/external/wpt/page-visibility/resources/unload-bubbles.html b/third_party/blink/web_tests/external/wpt/page-visibility/resources/unload-bubbles.html
index 44f0c0c..cc9e14f7 100644
--- a/third_party/blink/web_tests/external/wpt/page-visibility/resources/unload-bubbles.html
+++ b/third_party/blink/web_tests/external/wpt/page-visibility/resources/unload-bubbles.html
@@ -5,8 +5,12 @@
 <h1>Document</h1>
 <script>
 window.addEventListener("load", function() {
-  window.addEventListener("visibilitychange", function() {
-    opener.postMessage(document.visibilityState, "*");
+  window.addEventListener("visibilitychange", event => {
+    opener.postMessage({
+                        target: event.target.nodeName,
+                        state: document.visibilityState,
+                        bubbles: event.bubbles
+                      }, "*");
   });
   opener.postMessage("close", "*");
 });
diff --git a/third_party/blink/web_tests/external/wpt/page-visibility/unload-bubbles.html b/third_party/blink/web_tests/external/wpt/page-visibility/unload-bubbles.html
index 41f40c5..19b169c 100644
--- a/third_party/blink/web_tests/external/wpt/page-visibility/unload-bubbles.html
+++ b/third_party/blink/web_tests/external/wpt/page-visibility/unload-bubbles.html
@@ -11,7 +11,11 @@
       w.close();
       return;
     }
-    assert_equals(event.data, "hidden");
+
+    const {state, target, bubbles} = event.data
+    assert_equals(state, "hidden");
+    assert_equals(target, "#document");
+    assert_equals(bubbles, true);
     t.done();
   });
 });
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-ignored.tentative.html
index bb8b44f6..dfb6ba3 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-ignored.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-ignored.tentative.html
@@ -42,7 +42,7 @@
     width: 0px;
     height: 20px;
     animation-name: expand;
-    animation-duration: 1e10s;
+    animation-duration: 1000s;
     animation-timing-function: linear;
     animation-timeline: timeline1;
   }
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-offset-invalidation.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-offset-invalidation.tentative.html
index 25d2484..13d816a 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-offset-invalidation.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-offset-invalidation.tentative.html
@@ -25,7 +25,7 @@
   #element {
     width: 0px;
     height: 20px;
-    animation: expand 1e10s linear;
+    animation: expand 1000s linear;
     animation-timeline: timeline;
   }
   /* Ensure stable expectations if feature is not supported */
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-source-invalidation.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-source-invalidation.tentative.html
index 6f9ce7a8..fee39eb 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-source-invalidation.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-source-invalidation.tentative.html
@@ -29,7 +29,7 @@
   #element {
     width: 0px;
     height: 20px;
-    animation: expand 1e10s linear;
+    animation: expand 1000s linear;
     animation-timeline: timeline;
   }
   /* Ensure stable expectations if feature is not supported */
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-ref.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/animation-ref.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-animatable-interface.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-animatable-interface.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-animatable-interface.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-animatable-interface.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-display-none.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-display-none.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-display-none.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-display-none.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-overflow-hidden-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-overflow-hidden-ref.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-overflow-hidden-ref.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-overflow-hidden-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-overflow-hidden.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-overflow-hidden.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-overflow-hidden.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-overflow-hidden.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-root-scroller-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-root-scroller-ref.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-root-scroller-ref.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-root-scroller-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-root-scroller.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-root-scroller.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-root-scroller.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-root-scroller.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-transform.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-transform.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/animation-with-transform.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/animation-with-transform.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/cancel-animation.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/cancel-animation.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/cancel-animation.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/cancel-animation.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/constructor-no-document.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/constructor-no-document.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/constructor-no-document.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/constructor-no-document.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/constructor.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/constructor.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/constructor.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/constructor.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/current-time-nan.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time-nan.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/current-time-nan.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time-nan.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/current-time-root-scroller.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time-root-scroller.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/current-time-root-scroller.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time-root-scroller.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/current-time-writing-modes.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time-writing-modes.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/current-time-writing-modes.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time-writing-modes.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/current-time.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/current-time.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/effect-updateTiming.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/effect-updateTiming.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/effect-updateTiming.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/effect-updateTiming.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/element-based-offset-clamp.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset-clamp.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/element-based-offset-clamp.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset-clamp.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/element-based-offset-unresolved.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset-unresolved.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/element-based-offset-unresolved.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset-unresolved.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/element-based-offset.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/element-based-offset.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/finish-animation.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/finish-animation.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/finish-animation.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/finish-animation.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/idlharness.window-expected.txt b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/idlharness.window-expected.txt
new file mode 100644
index 0000000..130fdb6c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/idlharness.window-expected.txt
@@ -0,0 +1,11 @@
+This is a testharness.js-based test.
+FAIL idl_test setup promise_test: Unhandled rejection with value: object "CSSScrollTimelineRule inherits CSSRule, but CSSRule is undefined."
+PASS idl_test validation
+PASS Partial interface Element: member names are unique
+PASS Element includes Animatable: member names are unique
+PASS Element includes ParentNode: member names are unique
+PASS Element includes NonDocumentTypeChildNode: member names are unique
+PASS Element includes ChildNode: member names are unique
+PASS Element includes Slottable: member names are unique
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/idlharness.window.js b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/idlharness.window.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/idlharness.window.js
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/idlharness.window.js
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/layout-changes-on-percentage-based-timeline.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/layout-changes-on-percentage-based-timeline.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/layout-changes-on-percentage-based-timeline.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/layout-changes-on-percentage-based-timeline.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/multiple-scroll-offsets.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/multiple-scroll-offsets.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/multiple-scroll-offsets.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/multiple-scroll-offsets.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/null-scroll-source-crash.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/null-scroll-source-crash.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/null-scroll-source-crash.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/null-scroll-source-crash.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/pause-animation.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/pause-animation.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/pause-animation.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/pause-animation.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/play-animation.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/play-animation.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/play-animation.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/play-animation.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/progress-based-current-time.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/progress-based-current-time.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/progress-based-current-time.tentative.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/progress-based-current-time.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/progress-based-effect-delay-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/progress-based-effect-delay-ref.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/progress-based-effect-delay-ref.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/progress-based-effect-delay-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/progress-based-effect-delay.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/progress-based-effect-delay.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/progress-based-effect-delay.tentative.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/progress-based-effect-delay.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/reverse-animation.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/reverse-animation.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/reverse-animation.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/reverse-animation.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-animation-effect-phases.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-animation-effect-phases.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/scroll-animation-effect-phases.tentative.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-animation-effect-phases.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-animation-inactive-timeline.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-animation-inactive-timeline.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/scroll-animation-inactive-timeline.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-animation-inactive-timeline.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-animation.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-animation.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/scroll-animation.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-animation.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timeline-invalidation.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-timeline-invalidation.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timeline-invalidation.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-timeline-invalidation.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timeline-phases.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-timeline-phases.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timeline-phases.tentative.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-timeline-phases.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timeline-snapshotting.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-timeline-snapshotting.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timeline-snapshotting.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-timeline-snapshotting.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/set-current-time-before-play.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/set-current-time-before-play.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/set-current-time-before-play.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/set-current-time-before-play.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/setting-current-time.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-current-time.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/setting-current-time.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-current-time.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/setting-playback-rate.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-playback-rate.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/setting-playback-rate.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-playback-rate.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/setting-start-time.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-start-time.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/setting-start-time.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-start-time.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/setting-timeline.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-timeline.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/setting-timeline.tentative.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-timeline.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/source-quirks-mode.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/source-quirks-mode.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/source-quirks-mode.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/source-quirks-mode.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/testcommon.js b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/testcommon.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/testcommon.js
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/testcommon.js
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/two-animations-attach-to-same-scroll-timeline-cancel-one.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/two-animations-attach-to-same-scroll-timeline-cancel-one.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/two-animations-attach-to-same-scroll-timeline-cancel-one.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/two-animations-attach-to-same-scroll-timeline-cancel-one.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/two-animations-attach-to-same-scroll-timeline.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/two-animations-attach-to-same-scroll-timeline.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/two-animations-attach-to-same-scroll-timeline.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/two-animations-attach-to-same-scroll-timeline.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/update-playback-rate.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/update-playback-rate.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/update-playback-rate.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/update-playback-rate.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/updating-the-finished-state.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/updating-the-finished-state.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/scroll-animations/updating-the-finished-state.html
rename to third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/updating-the-finished-state.html
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/navigation-timing-extended.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/navigation-timing-extended.https.html
new file mode 100644
index 0000000..acb02c6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/navigation-timing-extended.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+
+<script>
+const timingEventOrder = [
+    'startTime',
+    'workerStart',
+    'fetchStart',
+    'requestStart',
+    'responseStart',
+    'responseEnd',
+];
+
+function navigate_in_frame(frame, url) {
+    frame.contentWindow.location = url;
+    return new Promise((resolve) => {
+        frame.addEventListener('load', () => {
+            const timing = frame.contentWindow.performance.getEntriesByType('navigation')[0];
+            const {timeOrigin} = frame.contentWindow.performance;
+            resolve({
+                workerStart: timing.workerStart + timeOrigin,
+                fetchStart: timing.fetchStart + timeOrigin
+            })
+        });
+    });
+}
+
+const worker_url = 'resources/navigation-timing-worker-extended.js';
+
+promise_test(async (t) => {
+    const scope = 'resources/timings/dummy.html';
+    const registration = await service_worker_unregister_and_register(t, worker_url, scope);
+    t.add_cleanup(() => registration.unregister());
+    await wait_for_state(t, registration.installing, 'activating');
+    const frame = await with_iframe('resources/empty.html');
+    t.add_cleanup(() => frame.remove());
+
+    const [timingFromEntry, timingFromWorker] = await Promise.all([
+        navigate_in_frame(frame, scope),
+        new Promise(resolve => {
+            window.addEventListener('message', m => {
+                resolve(m.data)
+            })
+        })])
+
+    assert_greater_than(timingFromWorker.activateWorkerEnd, timingFromEntry.workerStart,
+        'workerStart marking should not wait for worker activation to finish');
+    assert_greater_than(timingFromEntry.fetchStart, timingFromWorker.activateWorkerEnd,
+        'fetchStart should be marked once the worker is activated');
+    assert_greater_than(timingFromWorker.handleFetchEvent, timingFromEntry.fetchStart,
+        'fetchStart should be marked before the Fetch event handler is called');
+}, 'Service worker controlled navigation timing');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/navigation-timing-worker-extended.js b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/navigation-timing-worker-extended.js
new file mode 100644
index 0000000..79c54088
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/navigation-timing-worker-extended.js
@@ -0,0 +1,22 @@
+importScripts("/resources/testharness.js");
+const timings = {}
+
+const DELAY_ACTIVATION = 500
+
+self.addEventListener('activate', event => {
+    event.waitUntil(new Promise(resolve => {
+        timings.activateWorkerStart = performance.now() + performance.timeOrigin;
+
+        // This gives us enough time to ensure activation would delay fetch handling
+        step_timeout(resolve, DELAY_ACTIVATION);
+    }).then(() => timings.activateWorkerEnd = performance.now() + performance.timeOrigin));
+})
+
+self.addEventListener('fetch', event => {
+    timings.handleFetchEvent = performance.now() + performance.timeOrigin;
+    event.respondWith(Promise.resolve(new Response(new Blob([`
+            <script>
+                parent.postMessage(${JSON.stringify(timings)}, "*")
+            </script>
+    `]))));
+});
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-ignoredNodes-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-ignoredNodes-expected.txt
index fe46ec6..2f1cc512 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-ignoredNodes-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-ignoredNodes-expected.txt
@@ -16,7 +16,7 @@
 
 RootWebArea
   img
-    *SvgRoot
+    *img
 {
     childIds : <object>
     domNode : svg
@@ -47,8 +47,8 @@
     properties : [
     ]
     role : {
-        type : internalRole
-        value : SvgRoot
+        type : role
+        value : img
     }
 }
 
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-nameSources-img-figure-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-nameSources-img-figure-expected.txt
index 6200b57..24e03bce 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-nameSources-img-figure-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-nameSources-img-figure-expected.txt
@@ -671,8 +671,8 @@
         }
     ]
     role : {
-        type : internalRole
-        value : SvgRoot
+        type : role
+        value : img
     }
 }
 
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/resources/register-service-worker-verify-third-party-use-counter.html b/third_party/blink/web_tests/http/tests/serviceworker/resources/register-service-worker-verify-third-party-use-counter.html
index 07afd02..1b154b4 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/resources/register-service-worker-verify-third-party-use-counter.html
+++ b/third_party/blink/web_tests/http/tests/serviceworker/resources/register-service-worker-verify-third-party-use-counter.html
@@ -20,11 +20,8 @@
         return reg.unregister();
     })
     .then(successful => {
-      if (successful) {
-        msg.source.postMessage('unregister_done', '*');
-      } else {
-        msg.source.postMessage('unregister_failed', '*');
-      }
+      // Report "done" as long as unregister completed.
+      msg.source.postMessage('unregister_done', '*');
     });
   } else {
     msg.source.postMessage('unexpected_message', '*');
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..d8073bf
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634042632175.5 but got 1634042632172.9001
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/linux/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/linux/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..f1572277
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634041957803.2998 but got 1634041957802
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.12/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..9cda153d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634043231901 but got 1634043231899.8
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..dc46c023
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634042877263.6 but got 1634042877262.9
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac10.13/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.13/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..2c799ac
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.13/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634043126416.2002 but got 1634043126415.5
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac10.13/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.13/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..092c930
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.13/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634042822263.3 but got 1634042822262.5999
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..d4bcd6d5
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634043416168.2998 but got 1634043416167.4001
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac10.14/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.14/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..f7bbaf1
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.14/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634042744462.5 but got 1634042744461.8
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..d6b1510
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634043667232.9 but got 1634043667232.2
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.15/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..d5246e9
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.15/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634043281629.2 but got 1634043281628.7002
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..e4ff24ec
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634043925788.1 but got 1634043925788
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..f169810
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634043763019.1 but got 1634043763018.9
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/css/css-cascade/layer-font-face-override-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/css/css-cascade/layer-font-face-override-expected.txt
deleted file mode 100644
index 8d7e1ec..0000000
--- a/third_party/blink/web_tests/platform/mac/external/wpt/css/css-cascade/layer-font-face-override-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-FAIL @font-face unlayered overrides layered assert_equals: expected "80px" but got "38.5156px"
-PASS @font-face override between layers
-PASS @font-face override update with appended sheet 1
-PASS @font-face override update with appended sheet 2
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..fcd4d8b
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634043789619.9001 but got 1634043789618.7002
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/mac/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..56be6f9
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634043753605.8 but got 1634043753605.0999
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/css/css-cascade/layer-font-face-override-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/css/css-cascade/layer-font-face-override-expected.txt
deleted file mode 100644
index 53dda9f..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/css/css-cascade/layer-font-face-override-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-FAIL @font-face unlayered overrides layered assert_equals: expected "80px" but got "38px"
-PASS @font-face override between layers
-PASS @font-face override update with appended sheet 1
-PASS @font-face override update with appended sheet 2
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..669bb6e0
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634044893684.5999 but got 1634044893683.9001
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/win/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/win/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..2cd089c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634044522887.2998 but got 1634044522886.8
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/win7/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/win7/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..f8bae45b7
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634044577107.8 but got 1634044577107.5
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/win7/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt b/third_party/blink/web_tests/platform/win7/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
new file mode 100644
index 0000000..4c65b86
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/navigation-timing-extended.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Service worker controlled navigation timing assert_greater_than: workerStart marking should not wait for worker activation to finish expected a number greater than 1634044394837.3 but got 1634044394837
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/disable-custom-element-default-style/external/wpt/css/css-cascade/README.txt b/third_party/blink/web_tests/virtual/disable-custom-element-default-style/external/wpt/css/css-cascade/README.txt
new file mode 100644
index 0000000..1500620
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-custom-element-default-style/external/wpt/css/css-cascade/README.txt
@@ -0,0 +1,2 @@
+This suite runs the wpt/css/css-cascade tests with flag
+"disable-blink-features=CustomElementDefaultStyle".
diff --git a/third_party/blink/web_tests/virtual/stable/external/wpt/css/css-cascade/README.txt b/third_party/blink/web_tests/virtual/stable/external/wpt/css/css-cascade/README.txt
deleted file mode 100644
index 1a9c2e4..0000000
--- a/third_party/blink/web_tests/virtual/stable/external/wpt/css/css-cascade/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-This suite runs the wpt/css/css-cascade tests without experimental web
-platform features enabled.
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index e6f91c5..986c961 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -5250,6 +5250,7 @@
 interface PerformanceEventTiming : PerformanceEntry
     attribute @@toStringTag
     getter cancelable
+    getter interactionId
     getter processingEnd
     getter processingStart
     getter target
diff --git a/third_party/ijar/BUILD.gn b/third_party/ijar/BUILD.gn
index 9ad722f..99ede3a 100644
--- a/third_party/ijar/BUILD.gn
+++ b/third_party/ijar/BUILD.gn
@@ -5,6 +5,12 @@
 # A tool that removes all non-interface-specific parts from a .jar file.
 
 if (is_linux || is_chromeos) {
+  config("ijar_compiler_flags") {
+    if (is_clang) {
+      cflags = [ "-Wno-unused-but-set-variable" ]
+    }
+  }
+
   executable("ijar") {
     sources = [
       "classfile.cc",
@@ -22,6 +28,8 @@
 
     deps = [ "//third_party/zlib" ]
 
+    configs += [ ":ijar_compiler_flags" ]
+
     # Always build release since this is a build tool.
     if (is_debug) {
       configs -= [ "//build/config:debug" ]
diff --git a/third_party/minizip/BUILD.gn b/third_party/minizip/BUILD.gn
index 619b059..00fbcf4 100644
--- a/third_party/minizip/BUILD.gn
+++ b/third_party/minizip/BUILD.gn
@@ -9,6 +9,7 @@
     cflags = [
       # mz_zip.c does |UINT_MAX == UINT16_MAX && ...|.
       "-Wno-unreachable-code",
+      "-Wno-unused-but-set-variable",
       "-Wno-unused-function",
       "-Wno-implicit-fallthrough",
     ]
diff --git a/third_party/tflite/BUILD.gn b/third_party/tflite/BUILD.gn
index e9d0df2c1..a241181 100644
--- a/third_party/tflite/BUILD.gn
+++ b/third_party/tflite/BUILD.gn
@@ -33,6 +33,7 @@
     "-Wno-sign-compare",
     "-Wno-gnu-inline-cpp-without-extern",
     "-Wno-loop-analysis",
+    "-Wno-unused-but-set-variable",
 
     # TODO(thakis): Remove once
     # https://github.com/tensorflow/tensorflow/pull/50528 is rolled in.
diff --git a/third_party/zlib/BUILD.gn b/third_party/zlib/BUILD.gn
index a3378e4..49f52e1f 100644
--- a/third_party/zlib/BUILD.gn
+++ b/third_party/zlib/BUILD.gn
@@ -496,6 +496,9 @@
       "//testing/gtest",
     ]
 
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+
     include_dirs = [
       "//third_party/googletest/src/googletest/include/gtest",
       ".",
diff --git a/tools/clang/pylib/clang/PRESUBMIT.py b/tools/clang/pylib/clang/PRESUBMIT.py
index f7fea30..6315b5a 100644
--- a/tools/clang/pylib/clang/PRESUBMIT.py
+++ b/tools/clang/pylib/clang/PRESUBMIT.py
@@ -10,7 +10,10 @@
   results = []
 
   # Run the unit tests.
-  results.extend(input_api.canned_checks.RunUnitTestsInDirectory(
-      input_api, output_api, '.', [ r'^.+_test\.py$']))
+  results.extend(
+      input_api.canned_checks.RunUnitTestsInDirectory(input_api,
+                                                      output_api,
+                                                      '.', [r'^.+_test\.py$'],
+                                                      skip_shebang_check=True))
 
   return results
diff --git a/tools/find_runtime_symbols/PRESUBMIT.py b/tools/find_runtime_symbols/PRESUBMIT.py
index 74b631d..01cb877 100644
--- a/tools/find_runtime_symbols/PRESUBMIT.py
+++ b/tools/find_runtime_symbols/PRESUBMIT.py
@@ -32,7 +32,8 @@
           input_api,
           output_api,
           input_api.os_path.join(input_api.PresubmitLocalPath(), 'tests'),
-          files_to_check=[r'.+_test\.py$']))
+          files_to_check=[r'.+_test\.py$'],
+          skip_shebang_check=True))
 
   if input_api.is_committing:
     output.extend(input_api.canned_checks.PanProjectChecks(input_api,
diff --git a/tools/find_runtime_symbols/tests/reduce_debugline_test.py b/tools/find_runtime_symbols/tests/reduce_debugline_test.py
index 1e3a21a..a7f3e306 100755
--- a/tools/find_runtime_symbols/tests/reduce_debugline_test.py
+++ b/tools/find_runtime_symbols/tests/reduce_debugline_test.py
@@ -3,13 +3,14 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import cStringIO
 import logging
 import os
 import sys
 import textwrap
 import unittest
 
+from six import StringIO
+
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 sys.path.insert(0, ROOT_DIR)
 
@@ -55,7 +56,7 @@
 
   def test(self):
     ranges_dict = reduce_debugline.reduce_decoded_debugline(
-        cStringIO.StringIO(self._DECODED_DEBUGLINE))
+        StringIO(self._DECODED_DEBUGLINE))
     self.assertEqual(self._EXPECTED_REDUCED_DEBUGLINE, ranges_dict)
 
 
diff --git a/tools/grit/PRESUBMIT.py b/tools/grit/PRESUBMIT.py
index 54a8b3a..655929c5b 100644
--- a/tools/grit/PRESUBMIT.py
+++ b/tools/grit/PRESUBMIT.py
@@ -20,7 +20,8 @@
           input_api.os_path.join(input_api.PresubmitLocalPath(),
                                  'preprocess_if_expr_test.py')
       ],
-      run_on_python2=False)
+      run_on_python2=False,
+      skip_shebang_check=True)
 
 
 def CheckChangeOnUpload(input_api, output_api):
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 4e80d8d..26a258b 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -125,10 +125,6 @@
     "META": {"sizes": {"includes": [10],}},
     "includes": [1320],
   },
-    "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/enterprise_casting/resources.grd": {
-    "META": {"sizes": {"includes": [50]}},
-    "includes": [1340],
-  },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/emoji_picker/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
     "includes": [1360],
@@ -170,6 +166,10 @@
     "META": {"sizes": {"includes": [50],}},
     "includes": [1580],
   },
+  "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/enterprise_casting/resources.grd": {
+    "META": {"sizes": {"includes": [50]}},
+    "includes": [1590],
+  },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/extensions/extensions_resources.grd": {
     "META": {"sizes": {"includes": [50],}},
     "includes": [1600],
diff --git a/tools/linux/PRESUBMIT.py b/tools/linux/PRESUBMIT.py
index bb47057..fb3e64e 100644
--- a/tools/linux/PRESUBMIT.py
+++ b/tools/linux/PRESUBMIT.py
@@ -31,7 +31,8 @@
           input_api,
           output_api,
           input_api.os_path.join(input_api.PresubmitLocalPath(), 'tests'),
-          files_to_check=[r'.+_tests\.py$']))
+          files_to_check=[r'.+_tests\.py$'],
+          skip_shebang_check=True))
 
   if input_api.is_committing:
     output.extend(input_api.canned_checks.PanProjectChecks(input_api,
diff --git a/tools/linux/tests/procfs_tests.py b/tools/linux/tests/procfs_tests.py
index c829199..dd8ebc8 100755
--- a/tools/linux/tests/procfs_tests.py
+++ b/tools/linux/tests/procfs_tests.py
@@ -3,12 +3,13 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import cStringIO
 import logging
 import os
 import sys
 import unittest
 
+from six import StringIO
+
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 sys.path.insert(0, ROOT_DIR)
 
@@ -77,26 +78,26 @@
         }
 
   def test_load(self):
-    maps = ProcMaps.load_file(cStringIO.StringIO(self._TEST_PROCMAPS))
+    maps = ProcMaps.load_file(StringIO(self._TEST_PROCMAPS))
     for index, entry in enumerate(maps):
       self.assertEqual(entry.as_dict(), self._expected_as_dict(index))
 
   def test_constants(self):
-    maps = ProcMaps.load_file(cStringIO.StringIO(self._TEST_PROCMAPS))
+    maps = ProcMaps.load_file(StringIO(self._TEST_PROCMAPS))
     selected = [0, 2, 4, 7]
     for index, entry in enumerate(maps.iter(ProcMaps.constants)):
       self.assertEqual(entry.as_dict(),
                        self._expected_as_dict(selected[index]))
 
   def test_executable(self):
-    maps = ProcMaps.load_file(cStringIO.StringIO(self._TEST_PROCMAPS))
+    maps = ProcMaps.load_file(StringIO(self._TEST_PROCMAPS))
     selected = [1, 3, 6, 9]
     for index, entry in enumerate(maps.iter(ProcMaps.executable)):
       self.assertEqual(entry.as_dict(),
                        self._expected_as_dict(selected[index]))
 
   def test_executable_and_constants(self):
-    maps = ProcMaps.load_file(cStringIO.StringIO(self._TEST_PROCMAPS))
+    maps = ProcMaps.load_file(StringIO(self._TEST_PROCMAPS))
     selected = [0, 1, 2, 3, 4, 6, 7, 9]
     for index, entry in enumerate(maps.iter(ProcMaps.executable_and_constants)):
       self.assertEqual(entry.as_dict(),
diff --git a/tools/mb/PRESUBMIT.py b/tools/mb/PRESUBMIT.py
index aa560e72..d3a1e45 100644
--- a/tools/mb/PRESUBMIT.py
+++ b/tools/mb/PRESUBMIT.py
@@ -13,8 +13,12 @@
   results.extend(input_api.RunTests(pylint_checks))
 
   # Run the MB unittests.
-  results.extend(input_api.canned_checks.RunUnitTestsInDirectory(
-      input_api, output_api, '.', [ r'^.+_unittest\.py$']))
+  results.extend(
+      input_api.canned_checks.RunUnitTestsInDirectory(input_api,
+                                                      output_api,
+                                                      '.',
+                                                      [r'^.+_unittest\.py$'],
+                                                      skip_shebang_check=True))
 
   # Validate the format of the mb_config.pyl file.
   cmd = [input_api.python_executable, 'mb.py', 'validate']
diff --git a/tools/metrics/common/etree_util.py b/tools/metrics/common/etree_util.py
index e8ff91a..b5be692 100644
--- a/tools/metrics/common/etree_util.py
+++ b/tools/metrics/common/etree_util.py
@@ -69,7 +69,7 @@
   first_tag_line = 0
   first_tag_column = 0
   try:
-    xml.sax.parseString(file_content, handler)
+    xml.sax.parseString(file_content.encode('utf-8'), handler)
   except _FirstTagFoundError:
     # This is the expected case, it means a tag was found in the doc.
     first_tag_line = handler.GetFirstTagLine()
@@ -93,7 +93,7 @@
 def ParseXMLString(raw_xml):
   """Parses raw_xml and returns an ElementTree node that includes comments."""
   if sys.version_info.major == 2:
-    return ET.fromstring(raw_xml, _CommentedXMLParser())
+    return ET.fromstring(raw_xml.encode('utf-8'), _CommentedXMLParser())
   else:
     return ET.fromstring(
         raw_xml, ET.XMLParser(target=ET.TreeBuilder(insert_comments=True)))
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 0ca17d5f..afd4a01 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -49663,6 +49663,7 @@
   <int value="-714710496" label="VideoFullscreenOrientationLock:disabled"/>
   <int value="-714543772" label="enable-gpu-service-logging"/>
   <int value="-714043324" label="OutOfBlinkCORS:enabled"/>
+  <int value="-713705575" label="PhoneHubCallNotification:enabled"/>
   <int value="-713104676"
       label="DisablePeripheralDataAccessProtection:enabled"/>
   <int value="-711991950" label="SiteExplorationUi:enabled"/>
@@ -50241,6 +50242,7 @@
   <int value="-250822813" label="PwaImprovedSplashScreen:enabled"/>
   <int value="-250721831" label="AndroidAutofillAccessibility:disabled"/>
   <int value="-250543540" label="DeferAllScript:enabled"/>
+  <int value="-249443346" label="PhoneHubCallNotification:disabled"/>
   <int value="-249415830" label="FilteringScrollPrediction:disabled"/>
   <int value="-248223420" label="AutofillKeyboardAccessory:disabled"/>
   <int value="-247542772" label="CheckOfflineCapability:enabled"/>
@@ -53165,6 +53167,8 @@
   <int value="1991771852" label="LeftToRightUrls:enabled"/>
   <int value="1991912338" label="ModuleScriptsDynamicImport:enabled"/>
   <int value="1992466116" label="enable-passive-event-listeners-due-to-fling"/>
+  <int value="1993161713"
+      label="enable-experimental-accessibility-switch-access-multistep-automation"/>
   <int value="1993258379" label="enable-icon-ntp"/>
   <int value="1994431722" label="MaterialDesignUserMenu:disabled"/>
   <int value="1995322219" label="EmojiHandwritingVoiceInput:enabled"/>
@@ -74842,6 +74846,9 @@
 </enum>
 
 <enum name="ScheduledTaskInvokedReason">
+  <obsolete>
+    Deprecated in M97.
+  </obsolete>
   <int value="0" label="Stopped"/>
   <int value="1" label="Rescheduled"/>
   <int value="2" label="Ready"/>
diff --git a/tools/metrics/histograms/merge_xml.py b/tools/metrics/histograms/merge_xml.py
index ad8f2865..bd8b9188 100755
--- a/tools/metrics/histograms/merge_xml.py
+++ b/tools/metrics/histograms/merge_xml.py
@@ -160,9 +160,9 @@
   # doesn't build indexes for later lookup. And thus, we need to convert the
   # merged |doc| to a xml string and convert it back to force it to build
   # indexes for the merged |doc|.
-  doc = xml.dom.minidom.parseString(doc.toxml())
+  doc = xml.dom.minidom.parseString(doc.toxml().encode('utf-8'))
   # Only perform fancy operations after |doc| becomes stable. This helps improve
-  # the runtime perforamnce.
+  # the runtime performance.
   if should_expand_owners:
     for histograms in doc.getElementsByTagName('histograms'):
       expand_owners.ExpandHistogramsOWNERS(histograms)
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index e7c6bb3..f4ec421 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -38,7 +38,7 @@
 </histogram>
 
 <histogram name="History.BrowsingDataLifetime.Duration.BrowserShutdownDeletion"
-    units="ms" expires_after="2021-11-01">
+    units="ms" expires_after="2022-10-01">
   <owner>ydago@chromium.org</owner>
   <owner>dullweber@chromium.org</owner>
   <summary>
@@ -49,7 +49,7 @@
 
 <histogram
     name="History.BrowsingDataLifetime.Duration.Scheduled{DeletionType}Deletion"
-    units="ms" expires_after="2021-11-01">
+    units="ms" expires_after="2022-10-01">
   <owner>ydago@chromium.org</owner>
   <owner>dullweber@chromium.org</owner>
   <summary>
@@ -64,7 +64,7 @@
 </histogram>
 
 <histogram name="History.BrowsingDataLifetime.State.BrowserShutdownDeletion"
-    enum="BooleanStartedCompleted" expires_after="2021-11-01">
+    enum="BooleanStartedCompleted" expires_after="2022-10-01">
   <owner>ydago@chromium.org</owner>
   <owner>dullweber@chromium.org</owner>
   <summary>The states in which a deletion on shutdown went through.</summary>
@@ -72,7 +72,7 @@
 
 <histogram
     name="History.BrowsingDataLifetime.State.Scheduled{DeletionType}Deletion"
-    enum="BooleanStartedCompleted" expires_after="2021-11-01">
+    enum="BooleanStartedCompleted" expires_after="2022-10-01">
   <owner>ydago@chromium.org</owner>
   <owner>dullweber@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 4c9afaf..0857a4e 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -514,7 +514,7 @@
 </histogram>
 
 <histogram name="Media.Audio.Capture.Win.{AudioProcessingMode}EffectType"
-    enum="AudioEffectType" expires_after="2021-10-22">
+    enum="AudioEffectType" expires_after="2022-10-22">
   <owner>henrika@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/oobe/histograms.xml b/tools/metrics/histograms/metadata/oobe/histograms.xml
index 03578c5..c9fcc95 100644
--- a/tools/metrics/histograms/metadata/oobe/histograms.xml
+++ b/tools/metrics/histograms/metadata/oobe/histograms.xml
@@ -468,6 +468,9 @@
 
 <histogram name="OOBE.WebUIToViewsSwitch.Duration" units="ms"
     expires_after="2021-11-21">
+  <obsolete>
+    Removed in M97.
+  </obsolete>
   <owner>rsorokin@chromium.org</owner>
   <owner>cros-oac@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index b11b3d6..9a650f6 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -17104,7 +17104,7 @@
 </histogram>
 
 <histogram name="SupervisedUsers.PerAppTimeLimits.AppsWithTimeLimit"
-    units="Apps" expires_after="2022-04-24">
+    units="Apps" expires_after="2022-10-11">
   <owner>agawronska@chromium.org</owner>
   <owner>yilkal@chromium.org</owner>
   <owner>cros-families@google.com</owner>
@@ -17115,7 +17115,7 @@
 </histogram>
 
 <histogram name="SupervisedUsers.PerAppTimeLimits.BlockedAppsCount"
-    units="Apps" expires_after="2022-01-16">
+    units="Apps" expires_after="2022-10-11">
   <owner>agawronska@chromium.org</owner>
   <owner>yilkal@chromium.org</owner>
   <owner>cros-families@google.com</owner>
@@ -17126,7 +17126,7 @@
 </histogram>
 
 <histogram name="SupervisedUsers.PerAppTimeLimits.Engagement" units="Apps"
-    expires_after="2022-04-24">
+    expires_after="2022-10-11">
   <owner>agawronska@chromium.org</owner>
   <owner>yilkal@chromium.org</owner>
   <owner>cros-families@google.com</owner>
@@ -17138,7 +17138,7 @@
 </histogram>
 
 <histogram name="SupervisedUsers.PerAppTimeLimits.PolicyChangeCount"
-    units="Changes" expires_after="2021-11-21">
+    units="Changes" expires_after="2022-10-11">
   <owner>agawronska@chromium.org</owner>
   <owner>yilkal@chromium.org</owner>
   <owner>cros-families@google.com</owner>
@@ -19081,6 +19081,36 @@
   </summary>
 </histogram>
 
+<histogram name="Viz.FileDescriptorTracking.TimeToCompute" units="microseconds"
+    expires_after="2022-01-09">
+  <owner>petermcneeley@chromium.org</owner>
+  <owner>rjkroege@chromium.org</owner>
+  <summary>
+    Time spent computing the number of active File Descriptors. This is logged
+    once every 5 minutes as the cost of this computation is estimated to be at
+    least 1ms. Currently only recorded for LaCros delegated compositing.
+
+    Warning: This metric does not include reports from clients with
+    low-resolution clocks.
+  </summary>
+</histogram>
+
+<histogram name="Viz.FileDescriptorTracking.{FdStat}" units="units"
+    expires_after="2022-01-09">
+  <owner>petermcneeley@chromium.org</owner>
+  <owner>rjkroege@chromium.org</owner>
+  <summary>
+    {FdStat} File Descriptors for the GPU process. This is logged once every 5
+    minutes as the cost of this computation is estimated to be at least 1ms.
+    Currently only recorded for LaCros delegated compositing.
+  </summary>
+  <token key="FdStat">
+    <variant name="NumActive" summary="Current number of active"/>
+    <variant name="NumSoftMax" summary="Maximum number of"/>
+    <variant name="PercentageUsed" summary="Percentage of in use"/>
+  </token>
+</histogram>
+
 <histogram name="Viz.FrameSinkVideoCapturer.I420.CaptureDuration" units="ms"
     expires_after="2021-03-07">
   <owner>samans@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index 579657e..b511398 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -2979,6 +2979,25 @@
   </summary>
 </histogram>
 
+<histogram name="PasswordProtection.{TriggerType}.UserPopulation{Moment}"
+    enum="SafeBrowsingUserPopulation" expires_after="M98">
+  <owner>drubery@chromium.org</owner>
+  <owner>chrome-safebrowsing-core@chromium.org</owner>
+  <summary>
+    Records the user population for the profile triggering a password protection
+    check, due to {TriggerType}, {Moment}.
+  </summary>
+  <token key="TriggerType">
+    <variant name="OnFocus" summary="focusing on an unfamiliar login page"/>
+    <variant name="PasswordEntry"
+        summary="entering a password on the wrong login page"/>
+  </token>
+  <token key="Moment">
+    <variant name="OnPing" summary="when we decide to contact Safe Browsing"/>
+    <variant name="Start" summary="when the check is triggered"/>
+  </token>
+</histogram>
+
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/renderer/histograms.xml b/tools/metrics/histograms/metadata/renderer/histograms.xml
index f9baa9cc..205d4ee 100644
--- a/tools/metrics/histograms/metadata/renderer/histograms.xml
+++ b/tools/metrics/histograms/metadata/renderer/histograms.xml
@@ -51,7 +51,7 @@
 
 <histogram
     name="Renderer.DelegatedInkTrail.LatencyImprovement.{Renderer}.{Prediction}"
-    units="ms" expires_after="M98">
+    units="ms" expires_after="M108">
   <owner>jonross@chromium.org</owner>
   <owner>joalmei@microsoft.com</owner>
   <summary>
@@ -73,7 +73,7 @@
 
 <histogram
     name="Renderer.DelegatedInkTrail.LatencyImprovementWithPrediction.Experiment{Number}"
-    units="ms" expires_after="M98">
+    units="ms" expires_after="M108">
   <owner>jonross@chromium.org</owner>
   <owner>joalmei@microsoft.com</owner>
   <summary>
@@ -94,7 +94,7 @@
 </histogram>
 
 <histogram name="Renderer.DelegatedInkTrail.Prediction.WrongDirection"
-    enum="BooleanDirection" expires_after="M98">
+    enum="BooleanDirection" expires_after="M108">
   <owner>jonross@chromium.org</owner>
   <owner>joalmei@microsoft.com</owner>
   <summary>
@@ -109,7 +109,7 @@
 </histogram>
 
 <histogram name="Renderer.DelegatedInkTrail.Prediction.{Direction}Prediction"
-    units="pixels" expires_after="M98">
+    units="pixels" expires_after="M108">
   <owner>jonross@chromium.org</owner>
   <owner>joalmei@microsoft.com</owner>
   <summary>
@@ -127,7 +127,7 @@
 </histogram>
 
 <histogram name="Renderer.DelegatedInkTrail.Prediction.{Type}Jitter"
-    units="pixels" expires_after="M98">
+    units="pixels" expires_after="M108">
   <owner>jonross@chromium.org</owner>
   <owner>joalmei@microsoft.com</owner>
   <summary>
@@ -148,7 +148,7 @@
 
 <histogram
     name="Renderer.DelegatedInkTrail.PredictionExperiment{Number}.Frame{ScoreType}"
-    units="pixels" expires_after="M98">
+    units="pixels" expires_after="M108">
   <owner>jonross@chromium.org</owner>
   <owner>joalmei@microsoft.com</owner>
   <summary>
@@ -176,7 +176,7 @@
 
 <histogram
     name="Renderer.DelegatedInkTrail.PredictionExperiment{Number}.WrongDirection"
-    enum="BooleanDirection" expires_after="M98">
+    enum="BooleanDirection" expires_after="M108">
   <owner>jonross@chromium.org</owner>
   <owner>joalmei@microsoft.com</owner>
   <summary>
@@ -196,7 +196,7 @@
 
 <histogram
     name="Renderer.DelegatedInkTrail.PredictionExperiment{Number}.{ScoreType}"
-    units="pixels" expires_after="M98">
+    units="pixels" expires_after="M108">
   <owner>jonross@chromium.org</owner>
   <owner>joalmei@microsoft.com</owner>
   <summary>
@@ -221,7 +221,7 @@
 
 <histogram
     name="Renderer.DelegatedInkTrail.PredictionExperiment{Number}.{Type}Jitter"
-    units="pixels" expires_after="M98">
+    units="pixels" expires_after="M108">
   <owner>jonross@chromium.org</owner>
   <owner>joalmei@microsoft.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/scheduler/histograms.xml b/tools/metrics/histograms/metadata/scheduler/histograms.xml
index 55dcb1d..5582878d 100644
--- a/tools/metrics/histograms/metadata/scheduler/histograms.xml
+++ b/tools/metrics/histograms/metadata/scheduler/histograms.xml
@@ -139,6 +139,9 @@
 
 <histogram name="Scheduler.TimerBase.ScheduledTaskInvokedReason"
     enum="ScheduledTaskInvokedReason" expires_after="M95">
+  <obsolete>
+    Deprecated in M97.
+  </obsolete>
   <owner>pmonette@chromium.org</owner>
   <owner>chrome-catan@google.com</owner>
   <summary>
diff --git a/tools/origin_trials/generate_token.py b/tools/origin_trials/generate_token.py
index 6e49fbf..4f932f3 100755
--- a/tools/origin_trials/generate_token.py
+++ b/tools/origin_trials/generate_token.py
@@ -28,7 +28,12 @@
 import struct
 import sys
 import time
-import urlparse
+
+try:
+  from urllib.parse import urlparse
+except ImportError:
+  # ToDo: Remove Exception case upon full migration to Python 3
+  from urlparse import urlparse
 
 script_dir = os.path.dirname(os.path.realpath(__file__))
 sys.path.insert(0, os.path.join(script_dir, 'third_party', 'ed25519'))
@@ -87,7 +92,7 @@
   if hostname:
     return "https://" + hostname + ":443"
   # If not, try to construct an origin URL from the argument
-  origin = urlparse.urlparse(arg)
+  origin = urlparse(arg)
   if not origin or not origin.scheme or not origin.netloc:
     raise argparse.ArgumentTypeError("%s is not a hostname or a URL" % arg)
   # HTTPS or HTTP only
@@ -242,7 +247,7 @@
   # Verify that that the signature is correct before printing it.
   try:
     ed25519.checkvalid(signature, data_to_sign, private_key[32:])
-  except Exception, exc:
+  except Exception as exc:
     print("There was an error generating the signature.")
     print("(The original error was: %s)" % exc)
     sys.exit(1)
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index eb5f0ab..357aa4f 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,12 +5,12 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "52f421824816597171d49ad99769576b6eb8c1e8",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/9709a3d80cc9e2ac3e6d808638334f7510ddc59a/trace_processor_shell.exe"
+            "hash": "0c618e76ac4cfb99a59e915f8e9f714e5f321744",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/251c5dd034fdc7ce2578f2b5fd112e2fc0fa0e8e/trace_processor_shell.exe"
         },
         "mac": {
             "hash": "c23e575ca7971342be4b254789513a3d9e75e19f",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/a380238d9c232c8be236d141d0c81ca358a2597e/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/9709a3d80cc9e2ac3e6d808638334f7510ddc59a/trace_processor_shell"
         },
         "linux_arm64": {
             "hash": "5074025a2898ec41a872e70a5719e417acb0a380",
@@ -18,7 +18,7 @@
         },
         "linux": {
             "hash": "8edbb2d758502c92df982ea5aac42e7db953b4f0",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/9709a3d80cc9e2ac3e6d808638334f7510ddc59a/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/251c5dd034fdc7ce2578f2b5fd112e2fc0fa0e8e/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/polymer/PRESUBMIT.py b/tools/polymer/PRESUBMIT.py
index 530a3adb..251e609 100644
--- a/tools/polymer/PRESUBMIT.py
+++ b/tools/polymer/PRESUBMIT.py
@@ -15,7 +15,10 @@
   presubmit_path = input_api.PresubmitLocalPath()
   sources = ['polymer_test.py']
   tests = [input_api.os_path.join(presubmit_path, s) for s in sources]
-  return input_api.canned_checks.RunUnitTests(input_api, output_api, tests)
+  return input_api.canned_checks.RunUnitTests(input_api,
+                                              output_api,
+                                              tests,
+                                              skip_shebang_check=True)
 
 
 def _CheckChangeOnUploadOrCommit(input_api, output_api):
diff --git a/tools/resources/PRESUBMIT.py b/tools/resources/PRESUBMIT.py
index 1f71cc8..12fccbe 100644
--- a/tools/resources/PRESUBMIT.py
+++ b/tools/resources/PRESUBMIT.py
@@ -20,6 +20,9 @@
 
   if any(f for f in files if f.startswith('svgo_presubmit')):
     tests = [path.join(cwd, 'svgo_presubmit_test.py')]
-    return input_api.canned_checks.RunUnitTests(input_api, output_api, tests)
+    return input_api.canned_checks.RunUnitTests(input_api,
+                                                output_api,
+                                                tests,
+                                                skip_shebang_check=True)
 
   return []
diff --git a/tools/typescript/PRESUBMIT.py b/tools/typescript/PRESUBMIT.py
index 9a080c3..05046d6 100644
--- a/tools/typescript/PRESUBMIT.py
+++ b/tools/typescript/PRESUBMIT.py
@@ -14,7 +14,10 @@
   presubmit_path = input_api.PresubmitLocalPath()
   sources = ['ts_library_test.py']
   tests = [input_api.os_path.join(presubmit_path, s) for s in sources]
-  return input_api.canned_checks.RunUnitTests(input_api, output_api, tests)
+  return input_api.canned_checks.RunUnitTests(input_api,
+                                              output_api,
+                                              tests,
+                                              skip_shebang_check=True)
 
 
 def _CheckChangeOnUploadOrCommit(input_api, output_api):
diff --git a/ui/accessibility/accessibility_switches.cc b/ui/accessibility/accessibility_switches.cc
index fbcbad2..734765a 100644
--- a/ui/accessibility/accessibility_switches.cc
+++ b/ui/accessibility/accessibility_switches.cc
@@ -41,6 +41,11 @@
 // zooming in.
 const char kEnableMagnifierDebugDrawRect[] = "enable-magnifier-debug-draw-rect";
 
+// Enables multistep automation for Switch Access, which is a 2021 accessibility
+// sprint project and hasn't launched yet.
+const char kEnableExperimentalAccessibilitySwitchAccessMultistepAutomation[] =
+    "enable-experimental-accessibility-switch-access-multistep-automation";
+
 bool IsExperimentalAccessibilityDictationExtensionEnabled() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
       ::switches::kEnableExperimentalAccessibilityDictationExtension);
@@ -66,6 +71,12 @@
       ::switches::kEnableMagnifierDebugDrawRect);
 }
 
+bool IsExperimentalAccessibilitySwitchAccessMultistepAutomationEnabled() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      ::switches::
+          kEnableExperimentalAccessibilitySwitchAccessMultistepAutomation);
+}
+
 #if defined(OS_WIN)
 // Enables UI Automation platform API in addition to the IAccessible API.
 const char kEnableExperimentalUIAutomation[] =
diff --git a/ui/accessibility/accessibility_switches.h b/ui/accessibility/accessibility_switches.h
index 5547b03e..1ad17812 100644
--- a/ui/accessibility/accessibility_switches.h
+++ b/ui/accessibility/accessibility_switches.h
@@ -22,6 +22,8 @@
     kEnableExperimentalAccessibilityLanguageDetectionDynamic[];
 AX_BASE_EXPORT extern const char
     kEnableExperimentalAccessibilitySwitchAccessText[];
+AX_BASE_EXPORT extern const char
+    kEnableExperimentalAccessibilitySwitchAccessMultistepAutomation[];
 
 // Returns true if experimental accessibility dictation extension is enabled.
 AX_BASE_EXPORT bool IsExperimentalAccessibilityDictationExtensionEnabled();
@@ -37,6 +39,11 @@
 // Returns true if experimental accessibility Switch Access text is enabled.
 AX_BASE_EXPORT bool IsExperimentalAccessibilitySwitchAccessTextEnabled();
 
+// Returns true if experimental accessibility Switch Access multistep automation
+// is enabled.
+AX_BASE_EXPORT bool
+IsExperimentalAccessibilitySwitchAccessMultistepAutomationEnabled();
+
 #if defined(OS_WIN)
 AX_BASE_EXPORT extern const char kEnableExperimentalUIAutomation[];
 #endif
diff --git a/ui/display/mac/display_link_mac.cc b/ui/display/mac/display_link_mac.cc
index b517816..d106bc4 100644
--- a/ui/display/mac/display_link_mac.cc
+++ b/ui/display/mac/display_link_mac.cc
@@ -82,7 +82,25 @@
   ret = CVDisplayLinkCreateWithCGDisplay(display_id,
                                          display_link.InitializeInto());
   if (ret != kCVReturnSuccess) {
-    LOG(ERROR) << "CVDisplayLinkCreateWithActiveCGDisplays failed: " << ret;
+    LOG(ERROR) << "CVDisplayLinkCreateWithCGDisplay failed: " << ret;
+    return nullptr;
+  }
+
+  // Workaround for bug https://crbug.com/1218720. According to
+  // https://hg.mozilla.org/releases/mozilla-esr68/rev/db0628eadb86,
+  // CVDisplayLinkCreateWithCGDisplays()
+  // (called by CVDisplayLinkCreateWithCGDisplay()) sometimes
+  // creates a CVDisplayLinkRef with an uninitialized (nulled) internal
+  // pointer. If we continue to use this CVDisplayLinkRef, we will
+  // eventually crash in CVCGDisplayLink::getDisplayTimes(), where the
+  // internal pointer is dereferenced. Fortunately, when this happens
+  // another internal variable is also left uninitialized (zeroed),
+  // which is accessible via CVDisplayLinkGetCurrentCGDisplay(). In
+  // normal conditions the current display is never zero.
+  if ((ret == kCVReturnSuccess) &&
+      (CVDisplayLinkGetCurrentCGDisplay(display_link) == 0)) {
+    LOG(ERROR)
+        << "CVDisplayLinkCreateWithCGDisplay failed (no current display)";
     return nullptr;
   }
 
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.cc b/ui/ozone/platform/wayland/host/wayland_screen.cc
index bac7711..c998095 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen.cc
@@ -234,7 +234,8 @@
   // the last known cursor position. Otherwise, return such a point, which is
   // not contained by any of the windows.
   auto* cursor_position = connection_->wayland_cursor_position();
-  if (connection_->wayland_window_manager()->GetCurrentFocusedWindow() &&
+  if (connection_->wayland_window_manager()
+          ->GetCurrentPointerOrTouchFocusedWindow() &&
       cursor_position)
     return cursor_position->GetCursorSurfacePoint();
 
@@ -249,8 +250,8 @@
     const gfx::Point& point) const {
   // It is safe to check only for focused windows and test if they contain the
   // point or not.
-  auto* window =
-      connection_->wayland_window_manager()->GetCurrentFocusedWindow();
+  auto* window = connection_->wayland_window_manager()
+                     ->GetCurrentPointerOrTouchFocusedWindow();
   if (window && window->GetBoundsInDIP().Contains(point))
     return window->GetWidget();
   return gfx::kNullAcceleratedWidget;
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
index ef4f090..13e208e 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
@@ -102,7 +102,7 @@
   if (state_ != State::kIdle)
     return true;
 
-  origin_window_ = window_manager_->GetCurrentFocusedWindow();
+  origin_window_ = window_manager_->GetCurrentPointerOrTouchFocusedWindow();
   if (!origin_window_) {
     LOG(ERROR) << "Failed to get origin window.";
     return false;
@@ -175,7 +175,8 @@
   // snapped into a tab strip. So switch to |kAttached| state, store the focused
   // window as the pointer grabber and ask to quit the nested loop.
   state_ = State::kAttaching;
-  pointer_grab_owner_ = window_manager_->GetCurrentFocusedWindow();
+  pointer_grab_owner_ =
+      window_manager_->GetCurrentPointerOrTouchFocusedWindow();
   DCHECK(pointer_grab_owner_);
   QuitLoop();
 }
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
index 28d84dd..efa9bf9 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
@@ -71,7 +71,8 @@
     EXPECT_CALL(*delegate, DispatchEvent(_)).Times(1);
     WaylandDragDropTest::SendPointerEnter(window, delegate);
     Sync();
-    EXPECT_EQ(window, window_manager()->GetCurrentFocusedWindow());
+    EXPECT_EQ(window,
+              window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   }
 
   void SendPointerLeave(WaylandWindow* window,
@@ -79,7 +80,8 @@
     EXPECT_CALL(*delegate, DispatchEvent(_)).Times(1);
     WaylandDragDropTest::SendPointerLeave(window, delegate);
     Sync();
-    EXPECT_EQ(nullptr, window_manager()->GetCurrentFocusedWindow());
+    EXPECT_EQ(nullptr,
+              window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   }
 
   void SendPointerPress(WaylandWindow* window,
@@ -90,7 +92,8 @@
     EXPECT_CALL(*delegate, DispatchEvent(_)).Times(1);
     Sync();
 
-    EXPECT_EQ(window, window_manager()->GetCurrentFocusedWindow());
+    EXPECT_EQ(window,
+              window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   }
 
   void SendPointerMotion(WaylandWindow* window,
@@ -129,7 +132,8 @@
     EXPECT_CALL(*delegate, DispatchEvent(_)).Times(1);
     Sync();
 
-    EXPECT_EQ(window, window_manager()->GetCurrentFocusedWindow());
+    EXPECT_EQ(window,
+              window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   }
   void SendTouchMotion(WaylandWindow* window,
                        MockPlatformWindowDelegate* delegate,
@@ -154,7 +158,7 @@
 // 3. Run move loop, drag it within the window bounds and drop.
 TEST_P(WaylandWindowDragControllerTest, DragInsideWindowAndDrop) {
   // Ensure there is no window currently focused
-  EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow());
+  EXPECT_FALSE(window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(gfx::kNullAcceleratedWidget,
             screen_->GetLocalProcessWidgetAtPoint({10, 10}, {}));
 
@@ -230,7 +234,8 @@
   Sync();
 
   EXPECT_EQ(State::kIdle, drag_controller()->state());
-  EXPECT_EQ(window_.get(), window_manager()->GetCurrentFocusedWindow());
+  EXPECT_EQ(window_.get(),
+            window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(window_->GetWidget(),
             screen_->GetLocalProcessWidgetAtPoint({20, 20}, {}));
 }
@@ -244,7 +249,7 @@
   ASSERT_TRUE(GetWaylandExtension(*window_));
 
   // Ensure there is no window currently focused
-  EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow());
+  EXPECT_FALSE(window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(gfx::kNullAcceleratedWidget,
             screen_->GetLocalProcessWidgetAtPoint({10, 10}, {}));
 
@@ -310,7 +315,8 @@
   Sync();
 
   EXPECT_EQ(State::kIdle, drag_controller()->state());
-  EXPECT_EQ(window_.get(), window_manager()->GetCurrentFocusedWindow());
+  EXPECT_EQ(window_.get(),
+            window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(window_->GetWidget(),
             screen_->GetLocalProcessWidgetAtPoint({20, 20}, {}));
 }
@@ -324,7 +330,7 @@
 //    happens outside the bounds of any surface.
 TEST_P(WaylandWindowDragControllerTest, DragExitWindowAndDrop) {
   // Ensure there is no window currently focused
-  EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow());
+  EXPECT_FALSE(window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(gfx::kNullAcceleratedWidget,
             screen_->GetLocalProcessWidgetAtPoint({10, 10}, {}));
 
@@ -400,7 +406,8 @@
   Sync();
 
   EXPECT_EQ(State::kIdle, drag_controller()->state());
-  EXPECT_EQ(window_.get(), window_manager()->GetCurrentFocusedWindow());
+  EXPECT_EQ(window_.get(),
+            window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(window_->GetWidget(),
             screen_->GetLocalProcessWidgetAtPoint({20, 20}, {}));
 }
@@ -424,7 +431,7 @@
   Sync();
 
   // Ensure there is no window currently focused
-  EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow());
+  EXPECT_FALSE(window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(gfx::kNullAcceleratedWidget,
             screen_->GetLocalProcessWidgetAtPoint({10, 10}, {}));
 
@@ -538,7 +545,8 @@
       case kSnapped:
         EXPECT_EQ(ET_MOUSE_RELEASED, event->type());
         EXPECT_EQ(State::kDropped, drag_controller()->state());
-        EXPECT_EQ(target_window, window_manager()->GetCurrentFocusedWindow());
+        EXPECT_EQ(target_window,
+                  window_manager()->GetCurrentPointerOrTouchFocusedWindow());
         test_step = kDone;
         break;
       default:
@@ -550,7 +558,8 @@
   Sync();
 
   SendPointerEnter(target_window, &delegate_);
-  EXPECT_EQ(target_window, window_manager()->GetCurrentFocusedWindow());
+  EXPECT_EQ(target_window,
+            window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(target_window->GetWidget(),
             screen_->GetLocalProcessWidgetAtPoint({20, 20}, {}));
 }
@@ -574,7 +583,7 @@
   Sync();
 
   // Ensure there is no window currently focused
-  EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow());
+  EXPECT_FALSE(window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(gfx::kNullAcceleratedWidget,
             screen_->GetLocalProcessWidgetAtPoint({10, 10}, {}));
 
@@ -660,7 +669,8 @@
         case kSnapped:
           EXPECT_EQ(ET_TOUCH_RELEASED, event->type());
           EXPECT_EQ(State::kDropped, drag_controller()->state());
-          EXPECT_EQ(target_window, window_manager()->GetCurrentFocusedWindow());
+          EXPECT_EQ(target_window,
+                    window_manager()->GetCurrentPointerOrTouchFocusedWindow());
           test_step = kDone;
           break;
         default:
@@ -689,14 +699,14 @@
   ScheduleTestTask(task_2);
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow());
+  EXPECT_FALSE(window_manager()->GetCurrentPointerOrTouchFocusedWindow());
 }
 
 // Verifies wl_data_device::leave events are properly handled and propagated
 // while in window dragging "attached" mode.
 TEST_P(WaylandWindowDragControllerTest, DragExitAttached) {
   // Ensure there is no window currently focused
-  EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow());
+  EXPECT_FALSE(window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(gfx::kNullAcceleratedWidget,
             screen_->GetLocalProcessWidgetAtPoint({10, 10}, {}));
 
@@ -735,7 +745,8 @@
   SendPointerEnter(window_.get(), &delegate_);
   Sync();
 
-  EXPECT_EQ(window_.get(), window_manager()->GetCurrentFocusedWindow());
+  EXPECT_EQ(window_.get(),
+            window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(window_->GetWidget(),
             screen_->GetLocalProcessWidgetAtPoint({20, 20}, {}));
 }
@@ -785,7 +796,7 @@
 // https://crbug.com/1148021.
 TEST_P(WaylandWindowDragControllerTest, IgnorePointerEventsUntilDrop) {
   // Ensure there is no window currently focused
-  EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow());
+  EXPECT_FALSE(window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(gfx::kNullAcceleratedWidget,
             screen_->GetLocalProcessWidgetAtPoint({200, 200}, {}));
 
@@ -874,7 +885,8 @@
   Sync();
 
   EXPECT_EQ(State::kIdle, drag_controller()->state());
-  EXPECT_EQ(window_.get(), window_manager()->GetCurrentFocusedWindow());
+  EXPECT_EQ(window_.get(),
+            window_manager()->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_EQ(window_->GetWidget(),
             screen_->GetLocalProcessWidgetAtPoint({100, 100}, {}));
   EXPECT_EQ(gfx::kNullAcceleratedWidget,
diff --git a/ui/ozone/platform/wayland/host/wayland_window_manager.cc b/ui/ozone/platform/wayland/host/wayland_window_manager.cc
index f6dfec8..5d7d32d 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_manager.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_manager.cc
@@ -67,6 +67,17 @@
 WaylandWindow* WaylandWindowManager::GetCurrentFocusedWindow() const {
   for (const auto& entry : window_map_) {
     WaylandWindow* window = entry.second;
+    if (window->has_pointer_focus() || window->has_touch_focus() ||
+        window->has_keyboard_focus())
+      return window;
+  }
+  return nullptr;
+}
+
+WaylandWindow* WaylandWindowManager::GetCurrentPointerOrTouchFocusedWindow()
+    const {
+  for (const auto& entry : window_map_) {
+    WaylandWindow* window = entry.second;
     if (window->has_pointer_focus() || window->has_touch_focus())
       return window;
   }
diff --git a/ui/ozone/platform/wayland/host/wayland_window_manager.h b/ui/ozone/platform/wayland/host/wayland_window_manager.h
index 0c880e3..8908d1d 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_manager.h
+++ b/ui/ozone/platform/wayland/host/wayland_window_manager.h
@@ -51,9 +51,12 @@
   // Returns a window with largests bounds.
   WaylandWindow* GetWindowWithLargestBounds() const;
 
-  // Returns a current focused window by pointer or touch.
+  // Returns a current focused window by pointer, touch, or keyboard.
   WaylandWindow* GetCurrentFocusedWindow() const;
 
+  // Returns a current focused window by pointer or touch.
+  WaylandWindow* GetCurrentPointerOrTouchFocusedWindow() const;
+
   // Returns a current focused window by pointer.
   WaylandWindow* GetCurrentPointerFocusedWindow() const;
 
diff --git a/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc b/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc
index b262d31..0e8af0f 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc
@@ -99,6 +99,7 @@
   Sync();
 
   EXPECT_FALSE(manager_->GetCurrentFocusedWindow());
+  EXPECT_FALSE(manager_->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_FALSE(manager_->GetCurrentPointerFocusedWindow());
 
   auto* pointer = server_.seat()->pointer();
@@ -112,6 +113,8 @@
 
   EXPECT_FALSE(manager_->GetCurrentKeyboardFocusedWindow());
   EXPECT_TRUE(window1.get() == manager_->GetCurrentFocusedWindow());
+  EXPECT_TRUE(window1.get() ==
+              manager_->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_TRUE(window1.get() == manager_->GetCurrentPointerFocusedWindow());
 
   wl_pointer_send_leave(pointer->resource(), 2, surface->resource());
@@ -119,6 +122,7 @@
   Sync();
 
   EXPECT_FALSE(manager_->GetCurrentFocusedWindow());
+  EXPECT_FALSE(manager_->GetCurrentPointerOrTouchFocusedWindow());
   EXPECT_FALSE(manager_->GetCurrentPointerFocusedWindow());
 }
 
@@ -152,7 +156,8 @@
 
   Sync();
 
-  EXPECT_FALSE(manager_->GetCurrentFocusedWindow());
+  EXPECT_FALSE(manager_->GetCurrentPointerOrTouchFocusedWindow());
+  EXPECT_TRUE(window1.get() == manager_->GetCurrentFocusedWindow());
   EXPECT_TRUE(window1.get() == manager_->GetCurrentKeyboardFocusedWindow());
 
   wl_keyboard_send_leave(keyboard->resource(), 2, surface->resource());
diff --git a/ui/views/accessibility/ax_aura_obj_cache.cc b/ui/views/accessibility/ax_aura_obj_cache.cc
index df73f76..c1b8a6b 100644
--- a/ui/views/accessibility/ax_aura_obj_cache.cc
+++ b/ui/views/accessibility/ax_aura_obj_cache.cc
@@ -143,6 +143,9 @@
   RemoveInternal(window, &window_to_id_map_);
   if (parent && delegate_)
     delegate_->OnChildWindowRemoved(parent_window_obj);
+
+  if (focused_window_ == window)
+    focused_window_ = nullptr;
 }
 
 AXAuraObjWrapper* AXAuraObjCache::Get(int32_t id) {
diff --git a/ui/views/accessibility/ax_tree_source_views_unittest.cc b/ui/views/accessibility/ax_tree_source_views_unittest.cc
index a2d54aa3..c682448a 100644
--- a/ui/views/accessibility/ax_tree_source_views_unittest.cc
+++ b/ui/views/accessibility/ax_tree_source_views_unittest.cc
@@ -12,6 +12,8 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/ax_tree_data.h"
 #include "ui/accessibility/platform/ax_unique_id.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/test/test_window_delegate.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/views/accessibility/ax_aura_obj_cache.h"
 #include "ui/views/accessibility/ax_aura_obj_wrapper.h"
@@ -167,5 +169,41 @@
   EXPECT_EQ(nullptr, cache.GetOrCreate(textfield_)->GetParent());
 }
 
+#if BUILDFLAG(ENABLE_DESKTOP_AURA)
+class AXTreeSourceViewsDesktopWidgetTest : public AXTreeSourceViewsTest {
+ public:
+  AXTreeSourceViewsDesktopWidgetTest() {
+    set_native_widget_type(ViewsTestBase::NativeWidgetType::kDesktop);
+  }
+};
+
+// Tests that no use-after-free when a focused child window is destroyed in
+// desktop aura widget.
+TEST_F(AXTreeSourceViewsDesktopWidgetTest, FocusedChildWindowDestroyed) {
+  AXAuraObjCache cache;
+  AXAuraObjWrapper* root_wrapper =
+      cache.GetOrCreate(widget_->GetNativeWindow()->GetRootWindow());
+  EXPECT_NE(nullptr, root_wrapper);
+
+  aura::test::TestWindowDelegate child_delegate;
+  aura::Window* child = new aura::Window(&child_delegate);
+  child->Init(ui::LAYER_NOT_DRAWN);
+  widget_->GetNativeView()->AddChild(child);
+  aura::client::GetFocusClient(widget_->GetNativeView())->FocusWindow(child);
+
+  AXAuraObjWrapper* child_wrapper = cache.GetOrCreate(child);
+  EXPECT_NE(nullptr, child_wrapper);
+
+  // GetFocus() reflects the focused child window.
+  EXPECT_NE(nullptr, cache.GetFocus());
+
+  // Close the widget to destroy the child.
+  widget_.reset();
+
+  // GetFocus() should return null and no use-after-free to call it.
+  EXPECT_EQ(nullptr, cache.GetFocus());
+}
+#endif  // defined(USE_AURA)
+
 }  // namespace
 }  // namespace views
diff --git a/ui/views/animation/bounds_animator.cc b/ui/views/animation/bounds_animator.cc
index 862a04e..a042e49 100644
--- a/ui/views/animation/bounds_animator.cc
+++ b/ui/views/animation/bounds_animator.cc
@@ -16,6 +16,11 @@
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
 
+// This should be after all other #includes.
+#if defined(_WINDOWS_)  // Detect whether windows.h was included.
+#include "base/win/windows_h_disallowed.h"
+#endif  // defined(_WINDOWS_)
+
 namespace views {
 
 BoundsAnimator::BoundsAnimator(View* parent, bool use_transforms)
diff --git a/ui/views/widget/root_view.cc b/ui/views/widget/root_view.cc
index 48ec8693..9d09b21 100644
--- a/ui/views/widget/root_view.cc
+++ b/ui/views/widget/root_view.cc
@@ -107,14 +107,14 @@
   // ui::EventHandler:
   void OnKeyEvent(ui::KeyEvent* event) override {
     CHECK_EQ(ui::EP_PRETARGET, event->phase());
+// macOS doesn't have keyboard-triggered context menus.
+#if !defined(OS_MAC)
     if (event->handled())
       return;
 
     View* v = nullptr;
     if (owner_->GetFocusManager())  // Can be NULL in unittests.
       v = owner_->GetFocusManager()->GetFocusedView();
-// macOS doesn't have keyboard-triggered context menus.
-#if !defined(OS_MAC)
     // Special case to handle keyboard-triggered context menus.
     if (v && v->GetEnabled() &&
         ((event->key_code() == ui::VKEY_APPS) ||