diff --git a/DEPS b/DEPS
index 1c9c9a0..3820d96 100644
--- a/DEPS
+++ b/DEPS
@@ -245,15 +245,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': 'bdc0bad2e216eab790842912ad184191c2426ac1',
+  'skia_revision': '112f9f1273ef9505673c4a5154a2f9d8cc929d23',
   # 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': '74de5fddb9c4b85ec495c5ae587578d845737942',
+  'v8_revision': '0eca9b90ee3689f6cbf93e01b5e0fc480175ce42',
   # 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': 'a7e0d520f4ca8947444467424869f6a467f3acbc',
+  'angle_revision': '993f388967f8725d1ef8ce138ce8be4c0ae852ed',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -276,7 +276,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling googletest
   # and whatever else without interference from each other.
-  'googletest_revision': '9a32aee22d771387c494be2d8519fbdf46a713b2',
+  'googletest_revision': '71d4e2f7423274d178b446e94b88082559f2fa7a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling lighttpd
   # and whatever else without interference from each other.
@@ -320,7 +320,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'dc48499246acb27c90ce034619b335726ea2834c',
+  'devtools_frontend_revision': 'eba1a96f6793a1360b99f86597632af9e565b953',
   # 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.
@@ -360,7 +360,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '3324caee9c7c34ac1bffef87a6ca378eb23b5150',
+  'dawn_revision': '007f0d57a8fa7f2c79ded05036e98ba3bfbfe42d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -805,7 +805,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'vmWZ1WUPgUEe8dpm2UVl-azbGg4DDN1As69u4IhVXpgC',
+          'version': 'ZFybfM9WA_ZDTzPoqqNyWkYnKhc-eKP-qu_wHennQ34C',
       },
     ],
     'condition': 'checkout_android',
@@ -1648,7 +1648,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'c843f8d63c8c17acfbb7d48e09059a581ba779b9',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '2be1808023189ba27973a1f0fc437125fbf5da77',
+    Var('webrtc_git') + '/src.git' + '@' + 'ce702dbbe4e86d5248a6ad4b13475cc166dd02a8',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1730,7 +1730,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@662e18c549447c9b9ea21f88058ed54842cc993a',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0b962ed90d3d4a7a9b8561dd52e0910ed8a7ed2b',
     'condition': 'checkout_src_internal',
   },
 
@@ -1782,7 +1782,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'vhsSk8Bb4-9wo4PaV0WUu6vpB-dYlDklx5wajGObTEMC',
+        'version': 'ouEsQyUuqIb66n6x2iR0-Wx33CxqYHAWVD-EZf8po88C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 430de79..ff2447d 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -3720,8 +3720,6 @@
                    input_api.DEFAULT_FILES_TO_SKIP +
                    (r"^chrome/common/extensions/docs",
                     r"^chrome/docs",
-                    r"^components/dom_distiller/core/css/distilledpage_ios.css",
-                    r"^components/neterror/resources/neterror.css",
                     r"^native_client_sdk"))
   file_filter = lambda f: input_api.FilterSourceFile(
       f, files_to_check=file_inclusion_pattern, files_to_skip=files_to_skip)
diff --git a/ash/app_list/views/productivity_launcher_search_view.cc b/ash/app_list/views/productivity_launcher_search_view.cc
index ed45fa3..c8486cc 100644
--- a/ash/app_list/views/productivity_launcher_search_view.cc
+++ b/ash/app_list/views/productivity_launcher_search_view.cc
@@ -23,6 +23,7 @@
 #include "base/notreached.h"
 #include "base/strings/string_number_conversions.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/accessibility/platform/ax_unique_id.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/geometry/rect.h"
@@ -251,22 +252,20 @@
   if (ignore_result_changes_for_a11y_)
     return;
 
-  // Ignore result selection change if the focus moved away from the search box
-  // textfield, for example to the close button.
-  if (!search_box_view_->search_box()->HasFocus())
+  if (!result_selection_controller_->selected_result()) {
+    search_box_view_->SetA11yActiveDescendant(absl::nullopt);
     return;
-
-  if (!result_selection_controller_->selected_result())
-    return;
+  }
 
   views::View* selected_view =
       result_selection_controller_->selected_result()->GetSelectedView();
-  if (!selected_view)
+  if (!selected_view) {
+    search_box_view_->SetA11yActiveDescendant(absl::nullopt);
     return;
+  }
 
-  selected_view->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
-  NotifyAccessibilityEvent(ax::mojom::Event::kSelectedChildrenChanged, true);
-  search_box_view_->set_a11y_selection_on_search_result(true);
+  search_box_view_->SetA11yActiveDescendant(
+      selected_view->GetViewAccessibility().GetUniqueId().Get());
 }
 
 bool ProductivityLauncherSearchView::CanSelectSearchResults() {
diff --git a/ash/app_list/views/productivity_launcher_search_view_unittest.cc b/ash/app_list/views/productivity_launcher_search_view_unittest.cc
index 892541ee..1a06a09 100644
--- a/ash/app_list/views/productivity_launcher_search_view_unittest.cc
+++ b/ash/app_list/views/productivity_launcher_search_view_unittest.cc
@@ -518,22 +518,22 @@
   // Pressing down should not generate a selection accessibility event because
   // A11Y announcements are delayed since the results list just changed.
   PressAndReleaseKey(ui::VKEY_DOWN);
-  EXPECT_EQ(0, ax_counter.GetCount(ax::mojom::Event::kSelection));
+  EXPECT_EQ(0, ax_counter.GetCount(ax::mojom::Event::kActiveDescendantChanged));
   // Advance time to fire the timer to stop ignoring A11Y announcements.
   task_environment()->FastForwardBy(base::Milliseconds(5000));
 
   // A selection event is generated when the timer fires.
-  EXPECT_EQ(1, ax_counter.GetCount(ax::mojom::Event::kSelection));
+  EXPECT_EQ(1, ax_counter.GetCount(ax::mojom::Event::kActiveDescendantChanged));
 
   // Successive up/down key presses should generate additional selection events.
   PressAndReleaseKey(ui::VKEY_DOWN);
-  EXPECT_EQ(2, ax_counter.GetCount(ax::mojom::Event::kSelection));
+  EXPECT_EQ(2, ax_counter.GetCount(ax::mojom::Event::kActiveDescendantChanged));
   PressAndReleaseKey(ui::VKEY_UP);
-  EXPECT_EQ(3, ax_counter.GetCount(ax::mojom::Event::kSelection));
+  EXPECT_EQ(3, ax_counter.GetCount(ax::mojom::Event::kActiveDescendantChanged));
   PressAndReleaseKey(ui::VKEY_DOWN);
-  EXPECT_EQ(4, ax_counter.GetCount(ax::mojom::Event::kSelection));
+  EXPECT_EQ(4, ax_counter.GetCount(ax::mojom::Event::kActiveDescendantChanged));
   PressAndReleaseKey(ui::VKEY_DOWN);
-  EXPECT_EQ(5, ax_counter.GetCount(ax::mojom::Event::kSelection));
+  EXPECT_EQ(5, ax_counter.GetCount(ax::mojom::Event::kActiveDescendantChanged));
 }
 
 TEST_P(ProductivityLauncherSearchViewTest, SearchPageA11y) {
diff --git a/ash/app_list/views/search_box_view.cc b/ash/app_list/views/search_box_view.cc
index c3ad1a6..d2cb81b 100644
--- a/ash/app_list/views/search_box_view.cc
+++ b/ash/app_list/views/search_box_view.cc
@@ -144,6 +144,14 @@
   }
 }
 
+void SearchBoxView::UpdateSearchTextfieldAccessibleNodeData(
+    ui::AXNodeData* node_data) {
+  if (a11y_active_descendant_) {
+    node_data->AddIntAttribute(ax::mojom::IntAttribute::kActivedescendantId,
+                               *a11y_active_descendant_);
+  }
+}
+
 void SearchBoxView::ClearSearch() {
   SearchBoxViewBase::ClearSearch();
   current_query_.clear();
@@ -597,10 +605,8 @@
 }
 
 void SearchBoxView::OnBeforeUserAction(views::Textfield* sender) {
-  if (a11y_selection_on_search_result_) {
-    a11y_selection_on_search_result_ = false;
-    search_box()->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
-  }
+  if (a11y_active_descendant_)
+    SetA11yActiveDescendant(absl::nullopt);
 }
 
 void SearchBoxView::ContentsChanged(views::Textfield* sender,
@@ -673,11 +679,18 @@
   if (!is_search_box_active())
     return;
 
-  a11y_selection_on_search_result_ = false;
+  SetA11yActiveDescendant(absl::nullopt);
   ClearSearch();
   SetSearchBoxActive(false, ui::ET_UNKNOWN);
 }
 
+void SearchBoxView::SetA11yActiveDescendant(
+    const absl::optional<int32_t>& active_descendant) {
+  a11y_active_descendant_ = active_descendant;
+  search_box()->NotifyAccessibilityEvent(
+      ax::mojom::Event::kActiveDescendantChanged, true);
+}
+
 bool SearchBoxView::HandleKeyEvent(views::Textfield* sender,
                                    const ui::KeyEvent& key_event) {
   DCHECK(result_selection_controller_);
@@ -799,9 +812,7 @@
 
       DCHECK(close_button()->GetVisible());
       close_button()->RequestFocus();
-      close_button()->NotifyAccessibilityEvent(ax::mojom::Event::kSelection,
-                                               true);
-      a11y_selection_on_search_result_ = false;
+      SetA11yActiveDescendant(absl::nullopt);
       break;
     case ResultSelectionController::MoveResult::kResultChanged:
       UpdateSearchBoxTextForSelectedResult(
diff --git a/ash/app_list/views/search_box_view.h b/ash/app_list/views/search_box_view.h
index 7b74bc4c..1bd8602 100644
--- a/ash/app_list/views/search_box_view.h
+++ b/ash/app_list/views/search_box_view.h
@@ -5,6 +5,8 @@
 #ifndef ASH_APP_LIST_VIEWS_SEARCH_BOX_VIEW_H_
 #define ASH_APP_LIST_VIEWS_SEARCH_BOX_VIEW_H_
 
+#include <stdint.h>
+
 #include <vector>
 
 #include "ash/app_list/app_list_model_provider.h"
@@ -15,6 +17,7 @@
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/search_box/search_box_view_base.h"
 #include "base/scoped_observation.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace views {
 class Textfield;
@@ -62,6 +65,8 @@
 
   // Overridden from SearchBoxViewBase:
   void Init(const InitParams& params) override;
+  void UpdateSearchTextfieldAccessibleNodeData(
+      ui::AXNodeData* node_data) override;
   void ClearSearch() override;
   void HandleSearchBoxEvent(ui::LocatedEvent* located_event) override;
   void UpdateKeyboardVisibility() override;
@@ -115,15 +120,18 @@
   // Clears the search query and de-activate the search box.
   void ClearSearchAndDeactivateSearchBox();
 
+  // Sets the view accessibility ID of the search box's active descendant.
+  // The active descendant should be the currently selected result view in the
+  // search results list.
+  // `nullopt` indicates no active descendant, i.e. that no result is selected.
+  void SetA11yActiveDescendant(
+      const absl::optional<int32_t>& active_descendant);
+
   void set_contents_view(ContentsView* contents_view) {
     contents_view_ = contents_view;
   }
   ContentsView* contents_view() { return contents_view_; }
 
-  void set_a11y_selection_on_search_result(bool value) {
-    a11y_selection_on_search_result_ = value;
-  }
-
   ResultSelectionController* result_selection_controller_for_test() {
     return result_selection_controller_;
   }
@@ -210,8 +218,9 @@
   bool is_tablet_mode_;
 
   // Set by SearchResultPageView when the accessibility selection moves to a
-  // search result view.
-  bool a11y_selection_on_search_result_ = false;
+  // search result view - the value is the ID of the currently selected result
+  // view.
+  absl::optional<int32_t> a11y_active_descendant_;
 
   // Owned by SearchResultPageView (for fullscreen launcher) or
   // ProductivityLauncherSearchPage (for bubble launcher).
diff --git a/ash/app_list/views/search_result_page_view.cc b/ash/app_list/views/search_result_page_view.cc
index 0461d43..d7dd14e 100644
--- a/ash/app_list/views/search_result_page_view.cc
+++ b/ash/app_list/views/search_result_page_view.cc
@@ -30,6 +30,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/accessibility/platform/ax_unique_id.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -431,26 +432,25 @@
 void SearchResultPageView::NotifySelectedResultChanged() {
   // Result selection should be handled by |productivity_launcher_search_page_|.
   DCHECK(!features::IsProductivityLauncherEnabled());
-  if (ignore_result_changes_for_a11y_ ||
-      !result_selection_controller_->selected_location_details() ||
+  if (ignore_result_changes_for_a11y_)
+    return;
+
+  SearchBoxView* search_box = AppListPage::contents_view()->GetSearchBoxView();
+  if (!result_selection_controller_->selected_location_details() ||
       !result_selection_controller_->selected_result()) {
+    search_box->SetA11yActiveDescendant(absl::nullopt);
     return;
   }
 
-  SearchBoxView* search_box = AppListPage::contents_view()->GetSearchBoxView();
-  // Ignore result selection change if the focus moved away from the search boc
-  // textfield, for example to the close button.
-  if (!search_box->search_box()->HasFocus())
-    return;
-
   views::View* selected_view =
       result_selection_controller_->selected_result()->GetSelectedView();
-  if (!selected_view)
+  if (!selected_view) {
+    search_box->SetA11yActiveDescendant(absl::nullopt);
     return;
+  }
 
-  selected_view->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
-  NotifyAccessibilityEvent(ax::mojom::Event::kSelectedChildrenChanged, true);
-  search_box->set_a11y_selection_on_search_result(true);
+  search_box->SetA11yActiveDescendant(
+      selected_view->GetViewAccessibility().GetUniqueId().Get());
 }
 
 void SearchResultPageView::OnActiveAppListModelsChanged(
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 c23a6d4..a371045 100644
--- a/ash/app_list/views/search_result_tile_item_view.cc
+++ b/ash/app_list/views/search_result_tile_item_view.cc
@@ -257,7 +257,6 @@
 
   // The tile is a list item in the search result page's result list.
   node_data->role = ax::mojom::Role::kListBoxOption;
-  node_data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected());
   node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick);
 
   // Specify |ax::mojom::StringAttribute::kDescription| with an empty string, so
diff --git a/ash/app_list/views/search_result_view.cc b/ash/app_list/views/search_result_view.cc
index e9f866b..30bd19b 100644
--- a/ash/app_list/views/search_result_view.cc
+++ b/ash/app_list/views/search_result_view.cc
@@ -564,7 +564,6 @@
   // button are child button of SearchResultView), which is not supported by
   // ChromeVox. see details in crbug.com/924776.
   node_data->role = ax::mojom::Role::kListBoxOption;
-  node_data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected());
   node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick);
   node_data->SetName(GetAccessibleName());
 }
diff --git a/ash/components/arc/BUILD.gn b/ash/components/arc/BUILD.gn
index 8822e91..2ee8c6a 100644
--- a/ash/components/arc/BUILD.gn
+++ b/ash/components/arc/BUILD.gn
@@ -133,6 +133,7 @@
     "//components/device_event_log",
     "//components/exo",
     "//components/guest_os",
+    "//components/metrics",
     "//components/prefs",
     "//components/session_manager/core",
 
diff --git a/ash/components/arc/arc_features.cc b/ash/components/arc/arc_features.cc
index 546f7b5..65f9f8e5 100644
--- a/ash/components/arc/arc_features.cc
+++ b/ash/components/arc/arc_features.cc
@@ -163,6 +163,14 @@
 const base::Feature kVideoDecoder{"ArcVideoDecoder",
                                   base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Feature to continuously log PSI memory pressure data to Chrome.
+const base::Feature kVmMemoryPSIReports{"ArcVmMemoryPSIReports",
+                                        base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Controls how frequently memory pressure data is logged
+const base::FeatureParam<int> kVmMemoryPSIReportsPeriod{&kVmMemoryPSIReports,
+                                                        "period", 10};
+
 // Controls whether a custom memory size is used when creating ARCVM. When
 // enabled, ARCVM is sized with the following formula:
 //  min(max_mib, RAM + shift_mib)
diff --git a/ash/components/arc/arc_features.h b/ash/components/arc/arc_features.h
index b1e86e5..f44976c 100644
--- a/ash/components/arc/arc_features.h
+++ b/ash/components/arc/arc_features.h
@@ -41,6 +41,8 @@
 extern const base::Feature kUseDalvikMemoryProfile;
 extern const base::Feature kUseDefaultBlockSize;
 extern const base::Feature kVideoDecoder;
+extern const base::Feature kVmMemoryPSIReports;
+extern const base::FeatureParam<int> kVmMemoryPSIReportsPeriod;
 extern const base::Feature kVmMemorySize;
 extern const base::FeatureParam<int> kVmMemorySizeShiftMiB;
 extern const base::FeatureParam<int> kVmMemorySizeMaxMiB;
diff --git a/ash/components/arc/compat_mode/touch_mode_mouse_rewriter.cc b/ash/components/arc/compat_mode/touch_mode_mouse_rewriter.cc
index 4af69f9..dc585b11 100644
--- a/ash/components/arc/compat_mode/touch_mode_mouse_rewriter.cc
+++ b/ash/components/arc/compat_mode/touch_mode_mouse_rewriter.cc
@@ -96,86 +96,29 @@
   // caption bar.
   if (!target || target->GetType() != aura::client::WINDOW_TYPE_CONTROL)
     return SendEvent(continuation, &event);
-  bool found = false;
-  // TODO(b/202679170): Verify that it does not affect performance before
-  // flipping the flag, and fix it if necessary.
-  while (target != nullptr) {
-    if (enabled_windows_.count(target)) {
-      found = true;
-      break;
-    }
-    target = target->parent();
-  }
-  if (!found)
-    return SendEvent(continuation, &event);
 
-  if (base::FeatureList::IsEnabled(arc::kMouseWheelSmoothScroll) &&
-      event.IsMouseWheelEvent()) {
-    const ui::MouseWheelEvent& wheel_event = *event.AsMouseWheelEvent();
-    const bool started = !scroll_timeout_.is_zero();
-    scroll_y_offset_ += kWheelToSmoothScrollScale * wheel_event.y_offset();
-    scroll_timeout_ = kSmoothScrollTimeout;
-    if (started) {
-      ui::ScrollEvent fling_cancel_event(
-          ui::ET_SCROLL_FLING_CANCEL, wheel_event.location_f(),
-          wheel_event.root_location_f(), wheel_event.time_stamp(), 0, 0, 0, 0,
-          0, 0);
-      base::SequencedTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&TouchModeMouseRewriter::SendScrollEvent,
-                                    weak_ptr_factory_.GetWeakPtr(), wheel_event,
-                                    continuation));
-      return SendEvent(continuation, &fling_cancel_event);
-    }
-    return DiscardEvent(continuation);
-  }
+  const bool in_resize_locked = IsInResizeLockedWindow(target);
+  if (event.IsMouseWheelEvent()) {
+    if (!base::FeatureList::IsEnabled(arc::kMouseWheelSmoothScroll))
+      return SendEvent(continuation, &event);
 
-  if (!base::FeatureList::IsEnabled(arc::kRightClickLongPress))
-    return SendEvent(continuation, &event);
+    if (!in_resize_locked)
+      return SendEvent(continuation, &event);
+
+    return RewriteMouseWheelEvent(*event.AsMouseWheelEvent(), continuation);
+  }
 
   const ui::MouseEvent& mouse_event = *event.AsMouseEvent();
-  if (mouse_event.IsRightMouseButton()) {
-    // 1. If there is already an ongoing simulated long press, discard the
-    //    subsequent right click.
-    // 2. If the left button is currently pressed, discard the right click.
-    // 3. Discard events that is not a right press.
-    if (release_event_scheduled_ || left_pressed_ ||
-        mouse_event.type() != ui::ET_MOUSE_PRESSED) {
-      return DiscardEvent(continuation);
-    }
-    // Schedule the release event after |kLongPressInterval|.
-    release_event_scheduled_ = true;
-    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&TouchModeMouseRewriter::SendReleaseEvent,
-                       weak_ptr_factory_.GetWeakPtr(), mouse_event,
-                       continuation),
-        kLongPressInterval);
-    // Send the press event now.
-    ui::MouseEvent press_event(
-        ui::ET_MOUSE_PRESSED, mouse_event.location(),
-        mouse_event.root_location(), mouse_event.time_stamp(),
-        ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
-    return SendEvent(continuation, &press_event);
-  } else if (mouse_event.IsLeftMouseButton()) {
-    if (mouse_event.type() == ui::ET_MOUSE_PRESSED)
-      left_pressed_ = true;
-    else if (mouse_event.type() == ui::ET_MOUSE_RELEASED)
-      left_pressed_ = false;
-    // Discard a release event that corresponds to a previously discarded press
-    // event.
-    if (discard_next_left_release_ &&
-        mouse_event.type() == ui::ET_MOUSE_RELEASED) {
-      discard_next_left_release_ = false;
-      return DiscardEvent(continuation);
-    }
-    // Discard the left click if there is an ongoing simulated long press.
-    if (release_event_scheduled_) {
-      if (mouse_event.type() == ui::ET_MOUSE_PRESSED)
-        discard_next_left_release_ = true;
-      return DiscardEvent(continuation);
-    }
-    return SendEvent(continuation, &event);
+  if (mouse_event.IsRightMouseButton() || mouse_event.IsLeftMouseButton()) {
+    if (!base::FeatureList::IsEnabled(arc::kRightClickLongPress))
+      return SendEvent(continuation, &event);
+
+    if (!in_resize_locked)
+      return SendEvent(continuation, &event);
+
+    return RewriteMouseClickEvent(mouse_event, continuation);
   }
+
   return SendEvent(continuation, &event);
 }
 
@@ -219,4 +162,82 @@
   }
 }
 
+ui::EventDispatchDetails TouchModeMouseRewriter::RewriteMouseWheelEvent(
+    const ui::MouseWheelEvent& event,
+    const Continuation continuation) {
+  const bool started = !scroll_timeout_.is_zero();
+  scroll_y_offset_ += kWheelToSmoothScrollScale * event.y_offset();
+  scroll_timeout_ = kSmoothScrollTimeout;
+  if (started) {
+    ui::ScrollEvent fling_cancel_event(
+        ui::ET_SCROLL_FLING_CANCEL, event.location_f(), event.root_location_f(),
+        event.time_stamp(), 0, 0, 0, 0, 0, 0);
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&TouchModeMouseRewriter::SendScrollEvent,
+                       weak_ptr_factory_.GetWeakPtr(), event, continuation));
+    return SendEvent(continuation, &fling_cancel_event);
+  }
+  return DiscardEvent(continuation);
+}
+
+ui::EventDispatchDetails TouchModeMouseRewriter::RewriteMouseClickEvent(
+    const ui::MouseEvent& event,
+    const Continuation continuation) {
+  if (event.IsRightMouseButton()) {
+    // 1. If there is already an ongoing simulated long press, discard the
+    //    subsequent right click.
+    // 2. If the left button is currently pressed, discard the right click.
+    // 3. Discard events that is not a right press.
+    if (release_event_scheduled_ || left_pressed_ ||
+        event.type() != ui::ET_MOUSE_PRESSED) {
+      return DiscardEvent(continuation);
+    }
+    // Schedule the release event after |kLongPressInterval|.
+    release_event_scheduled_ = true;
+    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&TouchModeMouseRewriter::SendReleaseEvent,
+                       weak_ptr_factory_.GetWeakPtr(), event, continuation),
+        kLongPressInterval);
+    // Send the press event now.
+    ui::MouseEvent press_event(
+        ui::ET_MOUSE_PRESSED, event.location(), event.root_location(),
+        event.time_stamp(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
+    return SendEvent(continuation, &press_event);
+  } else if (event.IsLeftMouseButton()) {
+    if (event.type() == ui::ET_MOUSE_PRESSED)
+      left_pressed_ = true;
+    else if (event.type() == ui::ET_MOUSE_RELEASED)
+      left_pressed_ = false;
+    // Discard a release event that corresponds to a previously discarded press
+    // event.
+    if (discard_next_left_release_ && event.type() == ui::ET_MOUSE_RELEASED) {
+      discard_next_left_release_ = false;
+      return DiscardEvent(continuation);
+    }
+    // Discard the left click if there is an ongoing simulated long press.
+    if (release_event_scheduled_) {
+      if (event.type() == ui::ET_MOUSE_PRESSED)
+        discard_next_left_release_ = true;
+      return DiscardEvent(continuation);
+    }
+    return SendEvent(continuation, &event);
+  }
+
+  return SendEvent(continuation, &event);
+}
+
+bool TouchModeMouseRewriter::IsInResizeLockedWindow(
+    const aura::Window* window) const {
+  // TODO(b/202679170): Verify that it does not affect performance before
+  // flipping the flag, and fix it if necessary.
+  while (window != nullptr) {
+    if (enabled_windows_.count(window))
+      return true;
+    window = window->parent();
+  }
+  return false;
+}
+
 }  // namespace arc
diff --git a/ash/components/arc/compat_mode/touch_mode_mouse_rewriter.h b/ash/components/arc/compat_mode/touch_mode_mouse_rewriter.h
index 2cf5f1c..5422f78 100644
--- a/ash/components/arc/compat_mode/touch_mode_mouse_rewriter.h
+++ b/ash/components/arc/compat_mode/touch_mode_mouse_rewriter.h
@@ -55,6 +55,14 @@
   void SendScrollEvent(const ui::MouseWheelEvent& original_event,
                        const Continuation continuation);
 
+  ui::EventDispatchDetails RewriteMouseWheelEvent(
+      const ui::MouseWheelEvent& event,
+      const Continuation continuation);
+  ui::EventDispatchDetails RewriteMouseClickEvent(
+      const ui::MouseEvent& event,
+      const Continuation continuation);
+  bool IsInResizeLockedWindow(const aura::Window* window) const;
+
   // Used for right click long press.
   bool release_event_scheduled_ = false;
   bool left_pressed_ = false;
@@ -65,7 +73,7 @@
   base::TimeDelta scroll_timeout_;
 
   std::multiset<aura::WindowTreeHost*> hosts_;
-  std::set<aura::Window*> enabled_windows_;
+  std::set<const aura::Window*> enabled_windows_;
 
   base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
       window_observations_{this};
diff --git a/ash/components/arc/ime/arc_ime_bridge_impl.cc b/ash/components/arc/ime/arc_ime_bridge_impl.cc
index 76d8f1f3..1e9e29d 100644
--- a/ash/components/arc/ime/arc_ime_bridge_impl.cc
+++ b/ash/components/arc/ime/arc_ime_bridge_impl.cc
@@ -173,10 +173,6 @@
       is_screen_coordinates);
 }
 
-void ArcImeBridgeImpl::RequestHideImeDeprecated() {
-  DVLOG(1) << "RequestHideIme is deprecated.";
-}
-
 void ArcImeBridgeImpl::SendKeyEvent(std::unique_ptr<ui::KeyEvent> key_event,
                                     SendKeyEventCallback callback) {
   delegate_->SendKeyEvent(std::move(key_event), std::move(callback));
diff --git a/ash/components/arc/ime/arc_ime_bridge_impl.h b/ash/components/arc/ime/arc_ime_bridge_impl.h
index f108ef2..0e3463f 100644
--- a/ash/components/arc/ime/arc_ime_bridge_impl.h
+++ b/ash/components/arc/ime/arc_ime_bridge_impl.h
@@ -55,7 +55,6 @@
                                               const std::string& text_in_range,
                                               const gfx::Range& selection_range,
                                               bool screen_coordinates) override;
-  void RequestHideImeDeprecated() override;
   void SendKeyEvent(std::unique_ptr<ui::KeyEvent> key_event,
                     SendKeyEventCallback callback) override;
 
diff --git a/ash/components/arc/metrics/DEPS b/ash/components/arc/metrics/DEPS
index 5cfe334..cea4cfa2 100644
--- a/ash/components/arc/metrics/DEPS
+++ b/ash/components/arc/metrics/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
+  "+components/metrics",
   "+ui/aura",
   "+ui/events/ozone/gamepad",
   "+ui/wm/public",
-]
\ No newline at end of file
+]
diff --git a/ash/components/arc/metrics/arc_metrics_service.cc b/ash/components/arc/metrics/arc_metrics_service.cc
index e95d750..6ca24b5 100644
--- a/ash/components/arc/metrics/arc_metrics_service.cc
+++ b/ash/components/arc/metrics/arc_metrics_service.cc
@@ -7,6 +7,7 @@
 #include <string>
 #include <utility>
 
+#include "ash/components/arc/arc_features.h"
 #include "ash/components/arc/arc_prefs.h"
 #include "ash/components/arc/arc_util.h"
 #include "ash/components/arc/metrics/stability_metrics_manager.h"
@@ -22,6 +23,7 @@
 #include "chromeos/dbus/power_manager/idle.pb.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
 #include "components/exo/wm_helper.h"
+#include "components/metrics/psi_memory_parser.h"
 #include "components/prefs/pref_service.h"
 #include "components/user_prefs/user_prefs.h"
 #include "content/public/browser/browser_context.h"
@@ -58,6 +60,10 @@
 constexpr char kAppTypeSystem[] = "SystemApp";
 constexpr char kAppTypeOther[] = "Other";
 
+// Memory pressure histograms.
+const char kPSIMemoryPressureSomeARC[] = "ChromeOS.CWP.PSIMemPressure.ArcSome";
+const char kPSIMemoryPressureFullARC[] = "ChromeOS.CWP.PSIMemPressure.ArcFull";
+
 // Logs UMA enum values to facilitate finding feedback reports in Xamine.
 template <typename T>
 void LogStabilityUmaEnum(const std::string& name, T sample) {
@@ -175,6 +181,11 @@
 
   StabilityMetricsManager::Get()->SetArcNativeBridgeType(
       NativeBridgeType::UNKNOWN);
+
+  if (base::FeatureList::IsEnabled(kVmMemoryPSIReports)) {
+    psi_parser_ = std::make_unique<metrics::PSIMemoryParser>(
+        kVmMemoryPSIReportsPeriod.Get());
+  }
 }
 
 ArcMetricsService::~ArcMetricsService() {
@@ -529,6 +540,32 @@
                                 base::Milliseconds(duration_ms));
 }
 
+void ArcMetricsService::ReportMemoryPressure(
+    const std::vector<uint8_t>& psi_file_contents) {
+  if (!psi_parser_) {
+    LOG(WARNING) << "Unexpected PSI reporting call detected";
+    return;
+  }
+
+  int metric_some;
+  int metric_full;
+
+  auto stat = psi_parser_->ParseMetrics(psi_file_contents.data(),
+                                        psi_file_contents.size(), &metric_some,
+                                        &metric_full);
+  psi_parser_->LogParseStatus(
+      stat);  // Log success and failure, for histograms.
+  if (stat != metrics::ParsePSIMemStatus::kSuccess)
+    return;
+
+  base::UmaHistogramCustomCounts(
+      kPSIMemoryPressureSomeARC, metric_some, metrics::kMemPressureMin,
+      metrics::kMemPressureExclusiveMax, metrics::kMemPressureHistogramBuckets);
+  base::UmaHistogramCustomCounts(
+      kPSIMemoryPressureFullARC, metric_full, metrics::kMemPressureMin,
+      metrics::kMemPressureExclusiveMax, metrics::kMemPressureHistogramBuckets);
+}
+
 void ArcMetricsService::OnWindowActivated(
     wm::ActivationChangeObserver::ActivationReason reason,
     aura::Window* gained_active,
diff --git a/ash/components/arc/metrics/arc_metrics_service.h b/ash/components/arc/metrics/arc_metrics_service.h
index ee78a8b..a3f0d01 100644
--- a/ash/components/arc/metrics/arc_metrics_service.h
+++ b/ash/components/arc/metrics/arc_metrics_service.h
@@ -32,6 +32,10 @@
 
 class BrowserContextKeyedServiceFactory;
 
+namespace metrics {
+class PSIMemoryParser;
+}  // namespace metrics
+
 namespace aura {
 class Window;
 }  // namespace aura
@@ -137,6 +141,7 @@
   void ReportAppPrimaryAbi(mojom::AppPrimaryAbi abi) override;
   void ReportDataRestore(mojom::DataRestoreStatus status,
                          int64_t duration_ms) override;
+  void ReportMemoryPressure(const std::vector<uint8_t>& psiFile) override;
 
   // wm::ActivationChangeObserver overrides.
   // Records to UMA when a user has interacted with an ARC app window.
@@ -282,6 +287,7 @@
   ArcBridgeServiceObserver arc_bridge_service_observer_;
   IntentHelperObserver intent_helper_observer_;
   AppLauncherObserver app_launcher_observer_;
+  std::unique_ptr<metrics::PSIMemoryParser> psi_parser_;
 
   bool was_arc_window_active_ = false;
   std::vector<int32_t> task_ids_;
diff --git a/ash/components/arc/mojom/accessibility_helper.mojom b/ash/components/arc/mojom/accessibility_helper.mojom
index 6f4aacf..6940f18 100644
--- a/ash/components/arc/mojom/accessibility_helper.mojom
+++ b/ash/components/arc/mojom/accessibility_helper.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Next MinVersion: 23
+// Next MinVersion: 24
 
 module arc.mojom;
 
@@ -493,9 +493,18 @@
   [MinVersion=18] OnToggleNativeChromeVoxArcSupport@3(bool enabled);
 };
 
+// Response code for SetNativeChromeVoxArcSupportForFocusedWindow.
+[Extensible]
+enum SetNativeChromeVoxResponse {
+  SUCCESS,
+  WINDOW_NOT_FOUND,
+  TALKBACK_NOT_INSTALLED,
+  FAILURE,
+};
+
 // Interface for communicating to Android.
 // Deprecated method IDs: 0, 1, 3, 5
-// Next method ID: 12
+// Next method ID: 13
 interface AccessibilityHelperInstance {
   // Establishes full-duplex communication with the host.
   [MinVersion=9] Init@7(
@@ -510,7 +519,7 @@
 
   // Sets the focused window's package to use ChromeVox (true) or TalkBack
   // (false).
-  [MinVersion=8] SetNativeChromeVoxArcSupportForFocusedWindow@6(
+  [MinVersion=8] SetNativeChromeVoxArcSupportForFocusedWindowDeprecated@6(
       bool enabled) => (bool processed);
 
   // Requests the service to enable or disable Explore By Touch.
@@ -527,4 +536,9 @@
   // Requests the whole accessibility tree with the specified window.
   [MinVersion=17] RequestSendAccessibilityTree@11(
       AccessibilityWindowKey window);
+
+  // Sets the focused window's package to use ChromeVox (true) or TalkBack
+  // (false).
+  [MinVersion=24] SetNativeChromeVoxArcSupportForFocusedWindow@12(
+      bool enabled) => (SetNativeChromeVoxResponse response);
 };
diff --git a/ash/components/arc/mojom/app.mojom b/ash/components/arc/mojom/app.mojom
index aa1531b..1085dd3 100644
--- a/ash/components/arc/mojom/app.mojom
+++ b/ash/components/arc/mojom/app.mojom
@@ -357,9 +357,6 @@
 // Next method ID: 40
 // Deprecated method IDs: 0, 1, 2, 3, 4, 9, 12, 13, 15, 17, 22
 interface AppInstance {
-  // DEPRECATED: Please use Init@21 instead.
-  InitDeprecated@0(pending_remote<AppHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=26] Init@21(pending_remote<AppHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/audio.mojom b/ash/components/arc/mojom/audio.mojom
index eb9a344c7..c17b9eb 100644
--- a/ash/components/arc/mojom/audio.mojom
+++ b/ash/components/arc/mojom/audio.mojom
@@ -24,11 +24,9 @@
   [MinVersion=3] OnSystemVolumeUpdateRequest@1(int32 percent);
 };
 
+// Deprecated method IDs: 1
 // Next method ID: 4
 interface AudioInstance {
-  // DEPRECATED: Please use Init@3 instead.
-  [MinVersion=1] InitDeprecated@1(pending_remote<AudioHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=4] Init@3(pending_remote<AudioHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/bluetooth.mojom b/ash/components/arc/mojom/bluetooth.mojom
index a2c5ac8..b748225 100644
--- a/ash/components/arc/mojom/bluetooth.mojom
+++ b/ash/components/arc/mojom/bluetooth.mojom
@@ -423,11 +423,6 @@
   [MinVersion=1] ReadRemoteRssi@28(BluetoothAddress remote_addr)
       => (int32 rssi);
 
-  // DEPRECATED: Use BluetoothSocketListen@47 or BluetoothSocketConnect@48
-  // instead.
-  [MinVersion=2] OpenBluetoothSocketDeprecated@29()
-      => (handle sock);
-
   // Bluetooth Gatt Server functions
   // Copied from Android API
   // https://source.android.com/devices/halref/bt__gatt__server_8h.html
@@ -474,24 +469,6 @@
   [MinVersion=8] DisableAdvertisement@43(int32 adv_handle)
       => (BluetoothGattStatus status);
 
-  // RFCOMM functions
-  // Opens a bluetooth socket with socket option |optval|, and then bind() and
-  // listen() with the requested RFCOMM |channel| number. When |channel| = 0,
-  // we will select a channel number automatically.  If this process succeeds,
-  // returns SUCCESS in |status|, the actual listening channel in |channel|,
-  // and a new mojo connection which represents the listening socket.
-  [MinVersion=15] RfcommListenDeprecated@45(int32 channel, int32 optval)
-      => (BluetoothStatus status, int32 channel,
-          RfcommListeningSocketClient&? client);
-  // Opens a bluetooth socket with socket option |optval|, and then connect()
-  // to (|remote_addr|, |channel|). If this process succeeds, returns SUCCESS
-  // in |status| and a new mojo connection which holds the connecting socket.
-  // Unlike in RfcommListen(), |channel| here could not be 0, since this is the
-  // peer channel number.
-  [MinVersion=15] RfcommConnectDeprecated@46(BluetoothAddress remote_addr,
-                                             int32 channel, int32 optval)
-      => (BluetoothStatus status, RfcommConnectingSocketClient&? client);
-
   // Bluetooth socket (RFCOMM and L2CAP LE) functions
   // Opens a |sock_type| socket with security options in |sock_flags|, and
   // listens on |port| (RFCOMM channel or L2CAP PSM). When |port| = 0, we will
@@ -512,11 +489,8 @@
 };
 
 // Next Method ID: 24
-// Deprecated Method ID: 2, 6, 11, 12
+// Deprecated Method ID: 0, 2, 6, 11, 12
 interface BluetoothInstance {
-  // DEPRECATED: Please use Init@18 instead.
-  InitDeprecated@0(pending_remote<BluetoothHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=7] Init@18(pending_remote<BluetoothHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/boot_phase_monitor.mojom b/ash/components/arc/mojom/boot_phase_monitor.mojom
index 703df7f..5a38abc 100644
--- a/ash/components/arc/mojom/boot_phase_monitor.mojom
+++ b/ash/components/arc/mojom/boot_phase_monitor.mojom
@@ -12,11 +12,9 @@
   OnBootCompleted@0();
 };
 
+// Deprecated method ID: 0
 // Next method ID: 2
 interface BootPhaseMonitorInstance {
-  // DEPRECATED: Please use Init@1 instead.
-  InitDeprecated@0(pending_remote<BootPhaseMonitorHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=1] Init@1(pending_remote<BootPhaseMonitorHost> host_remote) => ();
 };
diff --git a/ash/components/arc/mojom/cert_store.mojom b/ash/components/arc/mojom/cert_store.mojom
index 984ffdd..94ddb79 100644
--- a/ash/components/arc/mojom/cert_store.mojom
+++ b/ash/components/arc/mojom/cert_store.mojom
@@ -108,11 +108,9 @@
   Abort@5(uint64 operation_handle) => (KeymasterError error);
 };
 
+// Deprecated method IDs: 0
 // Next method ID: 4
 interface CertStoreInstance {
-  // DEPRECATED: Please use Init@3 instead.
-  InitDeprecated@0(pending_remote<CertStoreHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=1] Init@3(pending_remote<CertStoreHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/clipboard.mojom b/ash/components/arc/mojom/clipboard.mojom
index 452599c..ce6d395 100644
--- a/ash/components/arc/mojom/clipboard.mojom
+++ b/ash/components/arc/mojom/clipboard.mojom
@@ -50,11 +50,8 @@
 };
 
 // Next method ID: 4
-// Deprecated method IDs: 1
+// Deprecated method IDs: 0, 1
 interface ClipboardInstance {
-  // DEPRECATED: Please use Init@3 instead.
-  InitDeprecated@0(pending_remote<ClipboardHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=2] Init@3(pending_remote<ClipboardHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/crash_collector.mojom b/ash/components/arc/mojom/crash_collector.mojom
index 4faafb0..dc7dac59 100644
--- a/ash/components/arc/mojom/crash_collector.mojom
+++ b/ash/components/arc/mojom/crash_collector.mojom
@@ -36,11 +36,9 @@
   [MinVersion=5] DumpKernelCrash@3(handle ramoops_handle);
 };
 
+// Deprecated method IDs: 0
 // Next Method ID: 2
 interface CrashCollectorInstance {
-  // DEPRECATED: Please use Init@1 instead.
-  InitDeprecated@0(pending_remote<CrashCollectorHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=2] Init@1(pending_remote<CrashCollectorHost> host_remote) => ();
 };
diff --git a/ash/components/arc/mojom/enterprise_reporting.mojom b/ash/components/arc/mojom/enterprise_reporting.mojom
index a302a5d..ac93f2d8 100644
--- a/ash/components/arc/mojom/enterprise_reporting.mojom
+++ b/ash/components/arc/mojom/enterprise_reporting.mojom
@@ -25,11 +25,9 @@
   ReportManagementState@0(ManagementState state);
 };
 
+// Deprecated method IDs: 0
 // Next method ID: 3
 interface EnterpriseReportingInstance {
-  // DEPRECATED: Please use Init@2 instead.
-  InitDeprecated@0(pending_remote<EnterpriseReportingHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=2] Init@2(
       pending_remote<EnterpriseReportingHost> host_remote) => ();
diff --git a/ash/components/arc/mojom/file_system.mojom b/ash/components/arc/mojom/file_system.mojom
index d7e099c..e4a67617 100644
--- a/ash/components/arc/mojom/file_system.mojom
+++ b/ash/components/arc/mojom/file_system.mojom
@@ -323,6 +323,7 @@
       (FileSelectorElements elements);
 };
 
+// Deprecated method IDs: 5
 // Next method ID: 24
 interface FileSystemInstance {
   // Notes about Android Documents Provider:
@@ -432,9 +433,6 @@
                                   string target_parent_document_id) =>
                                       (Document? document);
 
-  // DEPRECATED: Please use Init@10 instead.
-  [MinVersion=3] InitDeprecated@5(pending_remote<FileSystemHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=7] Init@10(pending_remote<FileSystemHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/ime.mojom b/ash/components/arc/mojom/ime.mojom
index 17bf41de..4624789 100644
--- a/ash/components/arc/mojom/ime.mojom
+++ b/ash/components/arc/mojom/ime.mojom
@@ -70,7 +70,7 @@
   [MinVersion=16] uint32 scan_code;
 };
 
-// Next method ID: 8
+// Next method ID: 5, 8
 interface ImeHost {
   // Notifies Chrome that the text input focus is changed.
   // Each bit of the bitmask |flags| corresponds to TEXT_INPUT_FLAG_*.
@@ -116,10 +116,6 @@
                                                  // are in screen coordinates.
       );
 
-  // Requests Chrome to hide the virtual keyboard.
-  // However, hiding can be canceled if a text field gets focus.
-  [MinVersion=9] RequestHideImeDeprecated@5();
-
   // Sends a key event to Chrome to pass it to the Chrome OS IME.
   [MinVersion=14] SendKeyEvent@7(KeyEventData key_event_data)
       => (bool is_consumed);
@@ -127,11 +123,8 @@
 
 // Represents a text input field that is hosted inside ARC++, allowing Chrome OS
 // browser process to manipulate text within Android apps.
-// Next method ID: 9
+// Next method ID: 0, 9
 interface ImeInstance {
-  // DEPRECATED: Please use Init@6 instead.
-  InitDeprecated@0(pending_remote<ImeHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=6] Init@6(pending_remote<ImeHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/kiosk.mojom b/ash/components/arc/mojom/kiosk.mojom
index 852991b1..4ace32f 100644
--- a/ash/components/arc/mojom/kiosk.mojom
+++ b/ash/components/arc/mojom/kiosk.mojom
@@ -20,11 +20,9 @@
   OnMaintenanceSessionFinished@1(int32 session_id, bool succeeded);
 };
 
+// Deprecated method IDs: 0
 // Next method ID: 2
 interface KioskInstance {
-  // DEPRECATED: Please use Init@1 instead.
-  InitDeprecated@0(pending_remote<KioskHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=1] Init@1(pending_remote<KioskHost> host_remote) => ();
 };
diff --git a/ash/components/arc/mojom/metrics.mojom b/ash/components/arc/mojom/metrics.mojom
index b8af41b..794bdb1 100644
--- a/ash/components/arc/mojom/metrics.mojom
+++ b/ash/components/arc/mojom/metrics.mojom
@@ -1,7 +1,7 @@
 // Copyright 2016 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.
-// Next MinVersion: 20
+// Next MinVersion: 22
 
 module arc.mojom;
 
@@ -326,7 +326,7 @@
   // tools/metrics/histograms/enums.xml.
 };
 
-// Next method ID: 23
+// Next method ID: 24
 interface MetricsHost {
   // Reports boot progress events from ARC instance.
   ReportBootProgress@0(array<BootProgressEvent> events,
@@ -419,13 +419,15 @@
   // for ARC++ P container.
   [MinVersion=20] ReportDataRestore@22(DataRestoreStatus status,
                                        int64 duration_ms);
+
+  // Reports raw memory pressure data, as the contents of /proc/pressure/memory.
+  // This is invoked solely by ARCVM.
+  [MinVersion=21] ReportMemoryPressure@23(array<uint8> psi_file_contents);
 };
 
+// Deprecated method IDs: 0
 // Next method ID: 3
 interface MetricsInstance {
-  // DEPRECATED: Please use Init@1 instead.
-  InitDeprecated@0(pending_remote<MetricsHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=2] Init@1(pending_remote<MetricsHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/midis.mojom b/ash/components/arc/mojom/midis.mojom
index 18d9e403..52e12ebaf 100644
--- a/ash/components/arc/mojom/midis.mojom
+++ b/ash/components/arc/mojom/midis.mojom
@@ -41,6 +41,7 @@
 
 // This interface is used by the client to send messages / requests to the
 // daemon. This should be implemented by midis.
+// Deprecated method IDs: 0
 // Next Method ID: 4
 interface MidisServer {
   // Used to list out the MIDI devices that are currently connected to the
@@ -50,8 +51,6 @@
   // This function returns a handle(a Unix FD wrapped in a Mojo Handle)
   // for a subdevice specified by |request|.
   // In the event of an error, returns an empty handle.
-  // DEPRECATED: Please use RequestPort@3 instead.
-  RequestPortDeprecated@1(MidisRequest request) => (handle port_handle);
   [MinVersion=2]
   RequestPort@3(MidisRequest request) => (handle? port_handle);
 
@@ -72,11 +71,9 @@
 
 // MidisInstance is implemented in the ARC MIDI JNI code that
 // runs in Android and handles the Android side of the ArcBridge connection.
+// Deprecated method IDs: 0
 // Next Method ID: 2
 interface MidisInstance {
-  // DEPRECATED: Please use Init@1 instead.
-  InitDeprecated@0(pending_remote<MidisHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=1] Init@1(pending_remote<MidisHost> host_remote) => ();
 };
diff --git a/ash/components/arc/mojom/net.mojom b/ash/components/arc/mojom/net.mojom
index f7aaba1..f7d32d1 100644
--- a/ash/components/arc/mojom/net.mojom
+++ b/ash/components/arc/mojom/net.mojom
@@ -603,11 +603,8 @@
 };
 
 // Next Method ID: 11
-// ID 2 is missing as it belonged to deprecated method.
+// IDs 0 and 2 are missing as they belonged to deprecated method.
 interface NetInstance {
-  // DEPRECATED: Please use Init@6 instead.
-  InitDeprecated@0(pending_remote<NetHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=8] Init@6(pending_remote<NetHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/notifications.mojom b/ash/components/arc/mojom/notifications.mojom
index b6cfe279..5a544a6 100644
--- a/ash/components/arc/mojom/notifications.mojom
+++ b/ash/components/arc/mojom/notifications.mojom
@@ -248,12 +248,10 @@
   OnLockScreenSettingUpdated@10(ArcLockScreenNotificationSetting setting);
 };
 
+// Deprecated method IDs: 0
 // Next Method ID: 14
 // TODO(lhchavez): Migrate all request/response messages to Mojo.
 interface NotificationsInstance {
-  // DEPRECATED: Please use Init@5 instead.
-  InitDeprecated@0(pending_remote<NotificationsHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=14] Init@5(pending_remote<NotificationsHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/obb_mounter.mojom b/ash/components/arc/mojom/obb_mounter.mojom
index 403522c..a33c3b8 100644
--- a/ash/components/arc/mojom/obb_mounter.mojom
+++ b/ash/components/arc/mojom/obb_mounter.mojom
@@ -18,11 +18,9 @@
   UnmountObb@1(string target_path) => (bool success);
 };
 
+// Deprecated method IDs: 0
 // Next Method ID: 2
 interface ObbMounterInstance {
-  // DEPRECATED: Please use Init@1 instead.
-  InitDeprecated@0(pending_remote<ObbMounterHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=1] Init@1(pending_remote<ObbMounterHost> host_remote) => ();
 };
diff --git a/ash/components/arc/mojom/oemcrypto.mojom b/ash/components/arc/mojom/oemcrypto.mojom
index 98ed0fb..710182a 100644
--- a/ash/components/arc/mojom/oemcrypto.mojom
+++ b/ash/components/arc/mojom/oemcrypto.mojom
@@ -487,11 +487,9 @@
 
 // OemCryptoInstance is implemented in the liboemcrypto.so library that runs in
 // Android and handles the Android side of the ArcBridge connection.
+// Deprecated method IDs: 0
 // Next Method ID: 2
 interface OemCryptoInstance {
-  // DEPRECATED: Please use Init@1 instead.
-  InitDeprecated@0(pending_remote<OemCryptoHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=1] Init@1(pending_remote<OemCryptoHost> host_remote) => ();
 };
diff --git a/ash/components/arc/mojom/policy.mojom b/ash/components/arc/mojom/policy.mojom
index 820e8a4..d479731 100644
--- a/ash/components/arc/mojom/policy.mojom
+++ b/ash/components/arc/mojom/policy.mojom
@@ -95,11 +95,9 @@
                                           array<string> package_names);
 };
 
+// Deprecated method IDs: 0
 // Next Method ID: 4
 interface PolicyInstance {
-  // DEPRECATED: Please use Init@2 instead.
-  InitDeprecated@0(pending_remote<PolicyHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=2] Init@2(pending_remote<PolicyHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/power.mojom b/ash/components/arc/mojom/power.mojom
index 42dcaf8..c926178 100644
--- a/ash/components/arc/mojom/power.mojom
+++ b/ash/components/arc/mojom/power.mojom
@@ -83,11 +83,9 @@
   [MinVersion=8] OnAnrRecoveryFailed@7(AnrType type);
 };
 
+// Deprecated method IDs: 0
 // Next method ID: 9
 interface PowerInstance {
-  // DEPRECATED: Please use Init@5 instead.
-  InitDeprecated@0(pending_remote<PowerHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=4] Init@5(pending_remote<PowerHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/print_spooler.mojom b/ash/components/arc/mojom/print_spooler.mojom
index 6039dd1..c28e1fe 100644
--- a/ash/components/arc/mojom/print_spooler.mojom
+++ b/ash/components/arc/mojom/print_spooler.mojom
@@ -36,15 +36,6 @@
 // Deprecated method ID: 0
 // Next method ID: 2
 interface PrintSpoolerHost {
-  // DEPRECATED: Please use StartPrintInCustomTab@1 instead.
-  [MinVersion=1] StartPrintInCustomTabDeprecated@0(
-      handle scoped_handle,
-      int32 task_id,
-      int32 surface_id,
-      int32 top_margin,
-      pending_remote<PrintSessionInstance> instance)
-      => (pending_remote<PrintSessionHost>? host);
-
   // Opens the file owned by |scoped_handle| in Chrome print preview in an ARC
   // Custom Tab.
   // The |task_id| specifies the Android task on which the ARC Custom Tab should
diff --git a/ash/components/arc/mojom/process.mojom b/ash/components/arc/mojom/process.mojom
index 14c68ab..0733f6c 100644
--- a/ash/components/arc/mojom/process.mojom
+++ b/ash/components/arc/mojom/process.mojom
@@ -153,6 +153,7 @@
   uint32 private_footprint_kb = 0;
 };
 
+// Deprecated method IDs: 6, 7
 // Next Method ID: 11
 interface ProcessInstance {
   // Requests ARC instance to kill a process.
@@ -163,16 +164,6 @@
   RequestProcessList@5() => (array<RunningAppProcessInfo> processes);
 
   // Requests memory usage dumps for all ARC application processes.
-  [MinVersion=7]
-  RequestApplicationProcessMemoryInfoDeprecated@6()
-    => (memory_instrumentation.mojom.GlobalMemoryDump dump);
-
-  // Requests memory usage dumps for all ARC system processes.
-  [MinVersion=7]
-  RequestSystemProcessMemoryInfoDeprecated@7(array<uint32> nspids)
-    => (memory_instrumentation.mojom.GlobalMemoryDump dump);
-
-  // Requests memory usage dumps for all ARC application processes.
   [MinVersion=8]
   RequestApplicationProcessMemoryInfo@8()
     => (array<ArcMemoryDump> process_dumps);
diff --git a/ash/components/arc/mojom/tts.mojom b/ash/components/arc/mojom/tts.mojom
index 27a8643..01667b7 100644
--- a/ash/components/arc/mojom/tts.mojom
+++ b/ash/components/arc/mojom/tts.mojom
@@ -31,14 +31,9 @@
   bool is_network_connection_required;
 };
 
+// Deprecated method IDs: 0
 // Next Method ID: 3
 interface TtsHost {
-  // Notifies Chrome of Android tts events.
-  OnTtsEventDeprecated@0(uint32 utteranceId,
-               TtsEventType event_type,
-               uint32 char_index,
-               string error_msg);
-
   // Notifies Chrome of Android tts voices.
   [MinVersion=2] OnVoicesChanged@1(array<TtsVoice> voices);
 
@@ -50,11 +45,9 @@
                string error_msg);
 };
 
+// Deprecated method IDs: 0
 // Next Method ID: 4
 interface TtsInstance {
-  // DEPRECATED: Please use Init@3 instead.
-  InitDeprecated@0(pending_remote<TtsHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=1] Init@3(pending_remote<TtsHost> host_remote) => ();
 
diff --git a/ash/components/arc/mojom/usb_host.mojom b/ash/components/arc/mojom/usb_host.mojom
index e31181ba..0edc5ec 100644
--- a/ash/components/arc/mojom/usb_host.mojom
+++ b/ash/components/arc/mojom/usb_host.mojom
@@ -8,6 +8,7 @@
 // re-use device.mojom.UsbDeviceInfo
 import "services/device/public/mojom/usb_device.mojom";
 
+// Deprecated method IDs: 0
 // Next method ID: 4
 interface UsbHostHost {
   // Tries to open the USB device node for the device named 'guid' for caller
@@ -16,16 +17,6 @@
   // call will fail. Note the 'pkg_name' is informational purposes only, there
   // is no effective way that host can restrict access to only a specific
   // package at the security boundary formed by this Mojo interface.
-  // Deprecated.
-  OpenDeviceDeprecated@0(string guid,
-                         [MinVersion=1] string? pkg_name) => (handle usb_fd);
-
-  // Tries to open the USB device node for the device named 'guid' for caller
-  // 'pkg_name' and returns an open file descriptor to this node. 'pkg_name'
-  // needs to have previously called RequestPermission for this 'guid' else this
-  // call will fail. Note the 'pkg_name' is informational purposes only, there
-  // is no effective way that host can restrict access to only a specific
-  // package at the security boundary formed by this Mojo interface.
   // When app tries to open a device without requesting permission, or
   // permission_broker rejects the open request, empty handle will be returned.
   [MinVersion=3] OpenDevice@3(string guid, string? pkg_name) =>
diff --git a/ash/components/arc/mojom/video.mojom b/ash/components/arc/mojom/video.mojom
index a9bca34..d45c414 100644
--- a/ash/components/arc/mojom/video.mojom
+++ b/ash/components/arc/mojom/video.mojom
@@ -24,11 +24,9 @@
                                              string token);
 };
 
+// Deprecated method IDs: 0
 // Next method ID: 2
 interface VideoInstance {
-  // DEPRECATED: Please use Init@1 instead.
-  InitDeprecated@0(pending_remote<VideoHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=5] Init@1(pending_remote<VideoHost> host_remote) => ();
 };
diff --git a/ash/components/arc/mojom/video_encode_accelerator.mojom b/ash/components/arc/mojom/video_encode_accelerator.mojom
index 857e97d..0e09919 100644
--- a/ash/components/arc/mojom/video_encode_accelerator.mojom
+++ b/ash/components/arc/mojom/video_encode_accelerator.mojom
@@ -116,20 +116,6 @@
   Initialize@9(VideoEncodeAcceleratorConfig config,
                pending_remote<VideoEncodeClient> client) => (Result result);
 
-  // Initializes the video encoder with specific configuration.  Called once per
-  // encoder construction.
-  // Parameters:
-  //  |config| is the parameter set for encoder initialization.
-  //  |client| is the client of this video encoder. The client must be valid
-  //  during the lifetime of this accelerator.
-  // Callback:
-  //  Called with true iff initialization is successful. The client should not
-  //  invoke any other methods before the callback.
-  [MinVersion=1]
-  InitializeDeprecated@7(VideoEncodeAcceleratorConfig config,
-                         pending_remote<VideoEncodeClient> client) =>
-                             (bool success);
-
   // Encodes the given frame.
   // Parameters:
   //  |format| is the pixel format of video frame. This could be different from
diff --git a/ash/components/arc/mojom/wallpaper.mojom b/ash/components/arc/mojom/wallpaper.mojom
index cab9703..d3497089d 100644
--- a/ash/components/arc/mojom/wallpaper.mojom
+++ b/ash/components/arc/mojom/wallpaper.mojom
@@ -21,11 +21,9 @@
 };
 
 // Connects with container side to publish wallpaper related intents.
+// Deprecated method IDs: 0
 // Next method ID: 4
 interface WallpaperInstance {
-  // DEPRECATED: Please use Init@3 instead.
-  InitDeprecated@0(pending_remote<WallpaperHost> host_remote);
-
   // Establishes full-duplex communication with the host.
   [MinVersion=3] Init@3(pending_remote<WallpaperHost> host_remote) => ();
 
diff --git a/ash/components/arc/session/arc_vm_client_adapter.cc b/ash/components/arc/session/arc_vm_client_adapter.cc
index 6f04f3b..ed702a61 100644
--- a/ash/components/arc/session/arc_vm_client_adapter.cc
+++ b/ash/components/arc/session/arc_vm_client_adapter.cc
@@ -300,6 +300,13 @@
     }
   }
 
+  if (base::FeatureList::IsEnabled(kVmMemoryPSIReports)) {
+    auto period = kVmMemoryPSIReportsPeriod.Get();
+    // Since Android performs parameter validation, not doing it here.
+    result.push_back(base::StringPrintf(
+        "androidboot.arcvm_metrics_mem_psi_period=%d", period));
+  }
+
   if (base::FeatureList::IsEnabled(arc::kUseDalvikMemoryProfile)) {
     switch (start_params.dalvik_memory_profile) {
       case StartParams::DalvikMemoryProfile::DEFAULT:
diff --git a/ash/components/arc/session/arc_vm_client_adapter_unittest.cc b/ash/components/arc/session/arc_vm_client_adapter_unittest.cc
index bb630d9..ce6d923 100644
--- a/ash/components/arc/session/arc_vm_client_adapter_unittest.cc
+++ b/ash/components/arc/session/arc_vm_client_adapter_unittest.cc
@@ -2289,6 +2289,38 @@
       base::Contains(request.params(), "androidboot.arcvm.logd.size=1M"));
 }
 
+// Test that StartArcVmRequest has no matching command line flag
+// when kVmMemoryPSIReports is enabled.
+TEST_F(ArcVmClientAdapterTest, ArcVmMemoryPSIReportsDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(kVmMemoryPSIReports);
+  StartParams start_params(GetPopulatedStartParams());
+  SetValidUserInfo();
+  StartMiniArcWithParams(true, std::move(start_params));
+  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
+  EXPECT_FALSE(is_system_shutdown().has_value());
+  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
+  EXPECT_FALSE(HasParameterWithPrefix(
+      request, "androidboot.arcvm_metrics_mem_psi_period="));
+}
+
+// Test that StartArcVmRequest has correct  command line flag
+// when kVmMemoryPSIReports is enabled.
+TEST_F(ArcVmClientAdapterTest, ArcVmMemoryPSIReportsEnabled) {
+  base::test::ScopedFeatureList feature_list;
+  base::FieldTrialParams params;
+  params["period"] = "300";
+  feature_list.InitAndEnableFeatureWithParameters(kVmMemoryPSIReports, params);
+  StartParams start_params(GetPopulatedStartParams());
+  SetValidUserInfo();
+  StartMiniArcWithParams(true, std::move(start_params));
+  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
+  EXPECT_FALSE(is_system_shutdown().has_value());
+  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
+  EXPECT_TRUE(base::Contains(request.params(),
+                             "androidboot.arcvm_metrics_mem_psi_period=300"));
+}
+
 struct DalvikMemoryProfileTestParam {
   // Requested profile.
   StartParams::DalvikMemoryProfile profile;
diff --git a/ash/components/arc/test/fake_accessibility_helper_instance.cc b/ash/components/arc/test/fake_accessibility_helper_instance.cc
index 1f58f84..859787e 100644
--- a/ash/components/arc/test/fake_accessibility_helper_instance.cc
+++ b/ash/components/arc/test/fake_accessibility_helper_instance.cc
@@ -33,10 +33,18 @@
 }
 
 void FakeAccessibilityHelperInstance::
+    SetNativeChromeVoxArcSupportForFocusedWindowDeprecated(
+        bool enabled,
+        SetNativeChromeVoxArcSupportForFocusedWindowDeprecatedCallback
+            callback) {
+  std::move(callback).Run(true);
+}
+
+void FakeAccessibilityHelperInstance::
     SetNativeChromeVoxArcSupportForFocusedWindow(
         bool enabled,
         SetNativeChromeVoxArcSupportForFocusedWindowCallback callback) {
-  std::move(callback).Run(true);
+  std::move(callback).Run(arc::mojom::SetNativeChromeVoxResponse::SUCCESS);
 }
 
 void FakeAccessibilityHelperInstance::SetExploreByTouchEnabled(bool enabled) {
diff --git a/ash/components/arc/test/fake_accessibility_helper_instance.h b/ash/components/arc/test/fake_accessibility_helper_instance.h
index 8cea85f..aaf0eac 100644
--- a/ash/components/arc/test/fake_accessibility_helper_instance.h
+++ b/ash/components/arc/test/fake_accessibility_helper_instance.h
@@ -27,6 +27,10 @@
   void SetFilter(mojom::AccessibilityFilterType filter_type) override;
   void PerformAction(mojom::AccessibilityActionDataPtr action_data_ptr,
                      PerformActionCallback callback) override;
+  void SetNativeChromeVoxArcSupportForFocusedWindowDeprecated(
+      bool enabled,
+      SetNativeChromeVoxArcSupportForFocusedWindowDeprecatedCallback callback)
+      override;
   void SetNativeChromeVoxArcSupportForFocusedWindow(
       bool enabled,
       SetNativeChromeVoxArcSupportForFocusedWindowCallback callback) override;
diff --git a/ash/components/arc/test/fake_app_instance.cc b/ash/components/arc/test/fake_app_instance.cc
index 021e68b..d5cad83 100644
--- a/ash/components/arc/test/fake_app_instance.cc
+++ b/ash/components/arc/test/fake_app_instance.cc
@@ -62,11 +62,6 @@
     : app_host_(app_host) {}
 FakeAppInstance::~FakeAppInstance() {}
 
-void FakeAppInstance::InitDeprecated(
-    mojo::PendingRemote<mojom::AppHost> host_remote) {
-  Init(std::move(host_remote), base::DoNothing());
-}
-
 void FakeAppInstance::Init(mojo::PendingRemote<mojom::AppHost> host_remote,
                            InitCallback callback) {
   // For every change in a connection bind latest remote.
diff --git a/ash/components/arc/test/fake_app_instance.h b/ash/components/arc/test/fake_app_instance.h
index f835aa07..231cd10 100644
--- a/ash/components/arc/test/fake_app_instance.h
+++ b/ash/components/arc/test/fake_app_instance.h
@@ -98,7 +98,6 @@
   ~FakeAppInstance() override;
 
   // mojom::AppInstance overrides:
-  void InitDeprecated(mojo::PendingRemote<mojom::AppHost> host_remote) override;
   void Init(mojo::PendingRemote<mojom::AppHost> host_remote,
             InitCallback callback) override;
   void LaunchAppDeprecated(const std::string& package_name,
diff --git a/ash/components/arc/test/fake_bluetooth_instance.cc b/ash/components/arc/test/fake_bluetooth_instance.cc
index 24d425f..17cad4e 100644
--- a/ash/components/arc/test/fake_bluetooth_instance.cc
+++ b/ash/components/arc/test/fake_bluetooth_instance.cc
@@ -47,11 +47,6 @@
 FakeBluetoothInstance::LEConnectionStateChangeData::
     ~LEConnectionStateChangeData() = default;
 
-void FakeBluetoothInstance::InitDeprecated(
-    mojo::PendingRemote<mojom::BluetoothHost> host_remote) {
-  Init(std::move(host_remote), base::DoNothing());
-}
-
 void FakeBluetoothInstance::Init(
     mojo::PendingRemote<mojom::BluetoothHost> host_remote,
     InitCallback callback) {
diff --git a/ash/components/arc/test/fake_bluetooth_instance.h b/ash/components/arc/test/fake_bluetooth_instance.h
index ed5ac59..f8f240965 100644
--- a/ash/components/arc/test/fake_bluetooth_instance.h
+++ b/ash/components/arc/test/fake_bluetooth_instance.h
@@ -115,8 +115,6 @@
   ~FakeBluetoothInstance() override;
 
   // mojom::BluetoothInstance overrides:
-  void InitDeprecated(
-      mojo::PendingRemote<mojom::BluetoothHost> host_remote) override;
   void Init(mojo::PendingRemote<mojom::BluetoothHost> host_remote,
             InitCallback callback) override;
   void OnAdapterProperties(
diff --git a/ash/components/arc/test/fake_clipboard_instance.cc b/ash/components/arc/test/fake_clipboard_instance.cc
index 23d71979..6f470e8 100644
--- a/ash/components/arc/test/fake_clipboard_instance.cc
+++ b/ash/components/arc/test/fake_clipboard_instance.cc
@@ -17,11 +17,6 @@
   std::move(callback).Run();
 }
 
-void FakeClipboardInstance::InitDeprecated(
-    mojo::PendingRemote<mojom::ClipboardHost> host_remote) {
-  Init(std::move(host_remote), base::DoNothing());
-}
-
 void FakeClipboardInstance::OnHostClipboardUpdated() {
   num_host_clipboard_updated_++;
 }
diff --git a/ash/components/arc/test/fake_clipboard_instance.h b/ash/components/arc/test/fake_clipboard_instance.h
index 2f52b8b..dd1bfaea 100644
--- a/ash/components/arc/test/fake_clipboard_instance.h
+++ b/ash/components/arc/test/fake_clipboard_instance.h
@@ -24,8 +24,6 @@
   // mojom::ClipboardInstance overrides:
   void Init(mojo::PendingRemote<mojom::ClipboardHost> host_remote,
             InitCallback callback) override;
-  void InitDeprecated(
-      mojo::PendingRemote<mojom::ClipboardHost> host_remote) override;
   void OnHostClipboardUpdated() override;
 
  private:
diff --git a/ash/components/arc/test/fake_file_system_instance.cc b/ash/components/arc/test/fake_file_system_instance.cc
index bc526c9..5c89d58 100644
--- a/ash/components/arc/test/fake_file_system_instance.cc
+++ b/ash/components/arc/test/fake_file_system_instance.cc
@@ -630,11 +630,6 @@
       base::BindOnce(std::move(callback), MakeDocument(iter->second)));
 }
 
-void FakeFileSystemInstance::InitDeprecated(
-    mojo::PendingRemote<mojom::FileSystemHost> host_remote) {
-  Init(std::move(host_remote), base::DoNothing());
-}
-
 void FakeFileSystemInstance::Init(
     mojo::PendingRemote<mojom::FileSystemHost> host_remote,
     InitCallback callback) {
diff --git a/ash/components/arc/test/fake_file_system_instance.h b/ash/components/arc/test/fake_file_system_instance.h
index e49ba99..e39b1ec 100644
--- a/ash/components/arc/test/fake_file_system_instance.h
+++ b/ash/components/arc/test/fake_file_system_instance.h
@@ -339,7 +339,6 @@
                     const std::string& source_parent_document_id,
                     const std::string& target_parent_document_id,
                     MoveDocumentCallback callback) override;
-  void InitDeprecated(mojo::PendingRemote<mojom::FileSystemHost> host) override;
   void Init(mojo::PendingRemote<mojom::FileSystemHost> host,
             InitCallback callback) override;
   void OpenFileToRead(const std::string& url,
diff --git a/ash/components/arc/test/fake_net_instance.cc b/ash/components/arc/test/fake_net_instance.cc
index c9f8eab..4eb132f 100644
--- a/ash/components/arc/test/fake_net_instance.cc
+++ b/ash/components/arc/test/fake_net_instance.cc
@@ -10,9 +10,6 @@
 
 FakeNetInstance::~FakeNetInstance() = default;
 
-void FakeNetInstance::InitDeprecated(
-    mojo::PendingRemote<mojom::NetHost> host_remote) {}
-
 void FakeNetInstance::Init(::mojo::PendingRemote<mojom::NetHost> host_remote,
                            InitCallback callback) {}
 
diff --git a/ash/components/arc/test/fake_net_instance.h b/ash/components/arc/test/fake_net_instance.h
index 499db3e8..7dfd17a 100644
--- a/ash/components/arc/test/fake_net_instance.h
+++ b/ash/components/arc/test/fake_net_instance.h
@@ -17,8 +17,6 @@
   FakeNetInstance(const FakeNetInstance&) = delete;
   FakeNetInstance& operator=(const FakeNetInstance&) = delete;
 
-  void InitDeprecated(mojo::PendingRemote<mojom::NetHost> host_remote) override;
-
   void Init(::mojo::PendingRemote<mojom::NetHost> host_remote,
             InitCallback callback) override;
 
diff --git a/ash/components/arc/test/fake_notifications_instance.cc b/ash/components/arc/test/fake_notifications_instance.cc
index 16d5a56..832a9a66 100644
--- a/ash/components/arc/test/fake_notifications_instance.cc
+++ b/ash/components/arc/test/fake_notifications_instance.cc
@@ -37,11 +37,6 @@
 
 void FakeNotificationsInstance::CancelPress(const std::string& key) {}
 
-void FakeNotificationsInstance::InitDeprecated(
-    mojo::PendingRemote<mojom::NotificationsHost> host_remote) {
-  Init(std::move(host_remote), base::DoNothing());
-}
-
 void FakeNotificationsInstance::Init(
     mojo::PendingRemote<mojom::NotificationsHost> host_remote,
     InitCallback callback) {
diff --git a/ash/components/arc/test/fake_notifications_instance.h b/ash/components/arc/test/fake_notifications_instance.h
index 2b816ec..48db740 100644
--- a/ash/components/arc/test/fake_notifications_instance.h
+++ b/ash/components/arc/test/fake_notifications_instance.h
@@ -25,8 +25,6 @@
   ~FakeNotificationsInstance() override;
 
   // mojom::NotificationsInstance overrides:
-  void InitDeprecated(
-      mojo::PendingRemote<mojom::NotificationsHost> host_remote) override;
   void Init(mojo::PendingRemote<mojom::NotificationsHost> host_remote,
             InitCallback callback) override;
 
diff --git a/ash/components/arc/test/fake_policy_instance.cc b/ash/components/arc/test/fake_policy_instance.cc
index 6ccfce3..c9b1f21c3 100644
--- a/ash/components/arc/test/fake_policy_instance.cc
+++ b/ash/components/arc/test/fake_policy_instance.cc
@@ -17,11 +17,6 @@
 
 FakePolicyInstance::~FakePolicyInstance() = default;
 
-void FakePolicyInstance::InitDeprecated(
-    mojo::PendingRemote<mojom::PolicyHost> host_remote) {
-  Init(std::move(host_remote), base::DoNothing());
-}
-
 void FakePolicyInstance::Init(
     mojo::PendingRemote<mojom::PolicyHost> host_remote,
     InitCallback callback) {
diff --git a/ash/components/arc/test/fake_policy_instance.h b/ash/components/arc/test/fake_policy_instance.h
index ac683b5..8c1816d 100644
--- a/ash/components/arc/test/fake_policy_instance.h
+++ b/ash/components/arc/test/fake_policy_instance.h
@@ -23,8 +23,6 @@
   ~FakePolicyInstance() override;
 
   // mojom::PolicyInstance
-  void InitDeprecated(
-      mojo::PendingRemote<mojom::PolicyHost> host_remote) override;
   void Init(mojo::PendingRemote<mojom::PolicyHost> host_remote,
             InitCallback callback) override;
   void OnPolicyUpdated() override;
diff --git a/ash/components/arc/test/fake_power_instance.cc b/ash/components/arc/test/fake_power_instance.cc
index 2b409f1..8ece156 100644
--- a/ash/components/arc/test/fake_power_instance.cc
+++ b/ash/components/arc/test/fake_power_instance.cc
@@ -19,11 +19,6 @@
   return std::move(suspend_callback_);
 }
 
-void FakePowerInstance::InitDeprecated(
-    mojo::PendingRemote<mojom::PowerHost> host_remote) {
-  Init(std::move(host_remote), base::DoNothing());
-}
-
 void FakePowerInstance::Init(mojo::PendingRemote<mojom::PowerHost> host_remote,
                              InitCallback callback) {
   host_remote_.reset();
diff --git a/ash/components/arc/test/fake_power_instance.h b/ash/components/arc/test/fake_power_instance.h
index 586ff92..a64b8c1 100644
--- a/ash/components/arc/test/fake_power_instance.h
+++ b/ash/components/arc/test/fake_power_instance.h
@@ -35,8 +35,6 @@
   SuspendCallback GetSuspendCallback();
 
   // mojom::PowerInstance overrides:
-  void InitDeprecated(
-      mojo::PendingRemote<mojom::PowerHost> host_remote) override;
   void Init(mojo::PendingRemote<mojom::PowerHost> host_remote,
             InitCallback callback) override;
   void SetInteractive(bool enabled) override;
diff --git a/ash/components/arc/test/fake_process_instance.cc b/ash/components/arc/test/fake_process_instance.cc
index fd8f7e4..e624f9c 100644
--- a/ash/components/arc/test/fake_process_instance.cc
+++ b/ash/components/arc/test/fake_process_instance.cc
@@ -24,17 +24,6 @@
   DCHECK(false);
 }
 
-void FakeProcessInstance::RequestApplicationProcessMemoryInfoDeprecated(
-    RequestApplicationProcessMemoryInfoDeprecatedCallback callback) {
-  DCHECK(false);
-}
-
-void FakeProcessInstance::RequestSystemProcessMemoryInfoDeprecated(
-    const std::vector<uint32_t>& nspids,
-    RequestSystemProcessMemoryInfoDeprecatedCallback callback) {
-  DCHECK(false);
-}
-
 void FakeProcessInstance::RequestApplicationProcessMemoryInfo(
     RequestApplicationProcessMemoryInfoCallback callback) {
   DCHECK(false);
diff --git a/ash/components/arc/test/fake_process_instance.h b/ash/components/arc/test/fake_process_instance.h
index dfa52f50..b2e6359 100644
--- a/ash/components/arc/test/fake_process_instance.h
+++ b/ash/components/arc/test/fake_process_instance.h
@@ -21,11 +21,6 @@
 
   void KillProcess(uint32_t pid, const std::string& reason) override;
   void RequestProcessList(RequestProcessListCallback callback) override;
-  void RequestApplicationProcessMemoryInfoDeprecated(
-      RequestApplicationProcessMemoryInfoDeprecatedCallback callback) override;
-  void RequestSystemProcessMemoryInfoDeprecated(
-      const std::vector<uint32_t>& nspids,
-      RequestSystemProcessMemoryInfoDeprecatedCallback callback) override;
   void RequestApplicationProcessMemoryInfo(
       RequestApplicationProcessMemoryInfoCallback callback) override;
   void RequestSystemProcessMemoryInfo(
diff --git a/ash/components/arc/test/fake_wallpaper_instance.cc b/ash/components/arc/test/fake_wallpaper_instance.cc
index 82150aef..9ca531d6 100644
--- a/ash/components/arc/test/fake_wallpaper_instance.cc
+++ b/ash/components/arc/test/fake_wallpaper_instance.cc
@@ -15,11 +15,6 @@
 
 FakeWallpaperInstance::~FakeWallpaperInstance() = default;
 
-void FakeWallpaperInstance::InitDeprecated(
-    mojo::PendingRemote<mojom::WallpaperHost> host_remote) {
-  Init(std::move(host_remote), base::DoNothing());
-}
-
 void FakeWallpaperInstance::Init(
     mojo::PendingRemote<mojom::WallpaperHost> host_remote,
     InitCallback callback) {
diff --git a/ash/components/arc/test/fake_wallpaper_instance.h b/ash/components/arc/test/fake_wallpaper_instance.h
index 5b4fe284..31cc480 100644
--- a/ash/components/arc/test/fake_wallpaper_instance.h
+++ b/ash/components/arc/test/fake_wallpaper_instance.h
@@ -26,8 +26,6 @@
   const std::vector<int32_t>& changed_ids() const { return changed_ids_; }
 
   // Overridden from mojom::WallpaperInstance
-  void InitDeprecated(
-      mojo::PendingRemote<mojom::WallpaperHost> host_remote) override;
   void Init(mojo::PendingRemote<mojom::WallpaperHost> host_remote,
             InitCallback callback) override;
   void OnWallpaperChanged(int32_t walpaper_id) override;
diff --git a/ash/components/arc/usb/usb_host_bridge.cc b/ash/components/arc/usb/usb_host_bridge.cc
index 3bb72cd..1399040 100644
--- a/ash/components/arc/usb/usb_host_bridge.cc
+++ b/ash/components/arc/usb/usb_host_bridge.cc
@@ -200,14 +200,6 @@
       base::BindOnce(&OnDeviceOpenError, std::move(split_callback.second)));
 }
 
-void ArcUsbHostBridge::OpenDeviceDeprecated(
-    const std::string& guid,
-    const absl::optional<std::string>& package,
-    OpenDeviceCallback callback) {
-  LOG(ERROR) << "ArcUsbHostBridge::OpenDeviceDeprecated is deprecated";
-  OpenDevice(guid, package, std::move(callback));
-}
-
 void ArcUsbHostBridge::GetDeviceInfo(const std::string& guid,
                                      GetDeviceInfoCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
diff --git a/ash/components/arc/usb/usb_host_bridge.h b/ash/components/arc/usb/usb_host_bridge.h
index c4f5eaa..f2c56ef 100644
--- a/ash/components/arc/usb/usb_host_bridge.h
+++ b/ash/components/arc/usb/usb_host_bridge.h
@@ -72,9 +72,6 @@
                          const std::string& package,
                          bool interactive,
                          RequestPermissionCallback callback) override;
-  void OpenDeviceDeprecated(const std::string& guid,
-                            const absl::optional<std::string>& package,
-                            OpenDeviceCallback callback) override;
   void OpenDevice(const std::string& guid,
                   const absl::optional<std::string>& package,
                   OpenDeviceCallback callback) override;
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc b/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc
index fedac047..2fd5857 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc
@@ -103,15 +103,6 @@
   std::move(callback).Run(result);
 }
 
-void GpuArcVideoEncodeAccelerator::InitializeDeprecated(
-    const media::VideoEncodeAccelerator::Config& config,
-    mojo::PendingRemote<mojom::VideoEncodeClient> client,
-    InitializeDeprecatedCallback callback) {
-  auto result = InitializeTask(config, std::move(client));
-  std::move(callback).Run(result ==
-                          mojom::VideoEncodeAccelerator::Result::kSuccess);
-}
-
 mojom::VideoEncodeAccelerator::Result
 GpuArcVideoEncodeAccelerator::InitializeTask(
     const media::VideoEncodeAccelerator::Config& config,
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.h b/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.h
index 520e122..545868c 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.h
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.h
@@ -57,10 +57,6 @@
   void Initialize(const media::VideoEncodeAccelerator::Config& config,
                   mojo::PendingRemote<mojom::VideoEncodeClient> client,
                   InitializeCallback callback) override;
-  void InitializeDeprecated(
-      const media::VideoEncodeAccelerator::Config& config,
-      mojo::PendingRemote<mojom::VideoEncodeClient> client,
-      InitializeDeprecatedCallback callback) override;
   mojom::VideoEncodeAccelerator::Result InitializeTask(
       const media::VideoEncodeAccelerator::Config& config,
       mojo::PendingRemote<mojom::VideoEncodeClient> client);
diff --git a/ash/components/fwupd/BUILD.gn b/ash/components/fwupd/BUILD.gn
index d6c6f1c..396d1440 100644
--- a/ash/components/fwupd/BUILD.gn
+++ b/ash/components/fwupd/BUILD.gn
@@ -10,7 +10,6 @@
   defines = [ "IS_ASH_FIRMWARE_UPDATE_MANAGER_IMPL" ]
 
   deps = [
-    "//ash/public/cpp",
     "//ash/public/mojom",
     "//ash/webui/firmware_update_ui/mojom",
     "//base:base",
@@ -30,21 +29,14 @@
   deps = [
     ":fwupd",
     "//ash/constants",
-    "//ash/public/cpp",
     "//ash/public/mojom",
     "//ash/webui/firmware_update_ui/mojom",
     "//base/test:test_support",
     "//chromeos/dbus/fwupd",
     "//dbus:test_support",
-    "//services/network:test_support",
-    "//services/network/public/cpp",
     "//testing/gmock",
     "//testing/gtest",
   ]
 
-  sources = [
-    "fake_fwupd_download_client.cc",
-    "fake_fwupd_download_client.h",
-    "firmware_update_manager_unittest.cc",
-  ]
+  sources = [ "firmware_update_manager_unittest.cc" ]
 }
diff --git a/ash/components/fwupd/fake_fwupd_download_client.cc b/ash/components/fwupd/fake_fwupd_download_client.cc
deleted file mode 100644
index e293265..0000000
--- a/ash/components/fwupd/fake_fwupd_download_client.cc
+++ /dev/null
@@ -1,81 +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 "ash/components/fwupd/fake_fwupd_download_client.h"
-
-#include <memory>
-#include <utility>
-
-#include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "net/traffic_annotation/network_traffic_annotation.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "services/network/test/test_url_loader_factory.h"
-
-namespace {
-
-class FakeSharedURLLoaderFactory : public network::SharedURLLoaderFactory {
- public:
-  FakeSharedURLLoaderFactory() = default;
-  FakeSharedURLLoaderFactory(const FakeSharedURLLoaderFactory&) = delete;
-  FakeSharedURLLoaderFactory& operator=(const FakeSharedURLLoaderFactory&) =
-      delete;
-
-  // network::mojom::URLLoaderFactory implementation:
-  void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver)
-      override {
-    test_url_loader_factory_.Clone(std::move(receiver));
-  }
-
-  void CreateLoaderAndStart(
-      mojo::PendingReceiver<network::mojom::URLLoader> loader,
-      int32_t request_id,
-      uint32_t options,
-      const network::ResourceRequest& request,
-      mojo::PendingRemote<network::mojom::URLLoaderClient> client,
-      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
-      override {
-    test_url_loader_factory_.CreateLoaderAndStart(
-        std::move(loader), request_id, options, request, std::move(client),
-        traffic_annotation);
-  }
-
-  // network::SharedURLLoaderFactory implementation:
-  std::unique_ptr<network::PendingSharedURLLoaderFactory> Clone() override {
-    NOTREACHED();
-    return nullptr;
-  }
-
-  network::TestURLLoaderFactory& test_url_loader_factory() {
-    return test_url_loader_factory_;
-  }
-
- private:
-  friend class base::RefCounted<FakeSharedURLLoaderFactory>;
-
-  ~FakeSharedURLLoaderFactory() override = default;
-
-  network::TestURLLoaderFactory test_url_loader_factory_;
-};
-
-}  // namespace
-
-namespace ash {
-
-FakeFwupdDownloadClient::FakeFwupdDownloadClient()
-    : url_loader_factory_(base::MakeRefCounted<FakeSharedURLLoaderFactory>()) {}
-FakeFwupdDownloadClient::~FakeFwupdDownloadClient() = default;
-
-scoped_refptr<network::SharedURLLoaderFactory>
-FakeFwupdDownloadClient::GetURLLoaderFactory() {
-  return url_loader_factory_;
-}
-
-network::TestURLLoaderFactory&
-FakeFwupdDownloadClient::test_url_loader_factory() {
-  return static_cast<FakeSharedURLLoaderFactory*>(url_loader_factory_.get())
-      ->test_url_loader_factory();
-}
-
-}  // namespace ash
diff --git a/ash/components/fwupd/fake_fwupd_download_client.h b/ash/components/fwupd/fake_fwupd_download_client.h
deleted file mode 100644
index 3f47de2..0000000
--- a/ash/components/fwupd/fake_fwupd_download_client.h
+++ /dev/null
@@ -1,38 +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 ASH_COMPONENTS_FWUPD_FAKE_FWUPD_DOWNLOAD_CLIENT_H_
-#define ASH_COMPONENTS_FWUPD_FAKE_FWUPD_DOWNLOAD_CLIENT_H_
-
-#include "ash/public/cpp/fwupd_download_client.h"
-#include "base/component_export.h"
-#include "services/network/test/test_url_loader_factory.h"
-
-namespace network {
-class SharedURLLoaderFactory;
-}  // namespace network
-
-namespace ash {
-
-// A fake implementation of FwupdDownloadClient used for testing.
-class COMPONENT_EXPORT(ASH_FIRMWARE_UPDATE_MANAGER) FakeFwupdDownloadClient
-    : public ash::FwupdDownloadClient {
- public:
-  FakeFwupdDownloadClient();
-  FakeFwupdDownloadClient(const FwupdDownloadClient&) = delete;
-  FakeFwupdDownloadClient& operator=(const FwupdDownloadClient&) = delete;
-  ~FakeFwupdDownloadClient() override;
-
-  // ash::FwupdDownloadClient:
-  scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
-
-  network::TestURLLoaderFactory& test_url_loader_factory();
-
- private:
-  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
-};
-
-}  // namespace ash
-
-#endif  // ASH_COMPONENTS_FWUPD_FAKE_FWUPD_DOWNLOAD_CLIENT_H_
diff --git a/ash/components/fwupd/firmware_update_manager.cc b/ash/components/fwupd/firmware_update_manager.cc
index b56e3b9..290e99f 100644
--- a/ash/components/fwupd/firmware_update_manager.cc
+++ b/ash/components/fwupd/firmware_update_manager.cc
@@ -6,7 +6,6 @@
 
 #include <utility>
 
-#include "ash/public/cpp/fwupd_download_client.h"
 #include "ash/webui/firmware_update_ui/mojom/firmware_update.mojom.h"
 #include "base/base_paths.h"
 #include "base/check_op.h"
@@ -25,13 +24,6 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
-#include "net/traffic_annotation/network_traffic_annotation.h"
-#include "services/network/public/cpp/resource_request.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "services/network/public/cpp/simple_url_loader.h"
-#include "services/network/public/mojom/fetch_api.mojom.h"
-#include "services/network/public/mojom/url_response_head.mojom.h"
-#include "url/gurl.h"
 
 namespace ash {
 
@@ -40,12 +32,6 @@
 const char kBaseRootPath[] = "firmware-updates";
 const char kCachePath[] = "cache";
 const char kCabFileExtension[] = ".cab";
-const char kFirmwareMirrorPrefix[] =
-    "https://storage.googleapis.com/chromeos-localmirror/lvfs/";
-
-const char kSampleFirmwareUpgrade[] =
-    "c15a0df7386812781d1f376fe54729e64f69b2a8a6c4b580914d4f6740e4fcc3-HP-USBC_"
-    "DOCK_G5-V1.0.13.0.cab";
 
 FirmwareUpdateManager* g_instance = nullptr;
 
@@ -84,39 +70,6 @@
   return update;
 }
 
-constexpr net::NetworkTrafficAnnotationTag kFwupdFirmwareUpdateNetworkTag =
-    net::DefineNetworkTrafficAnnotation("fwupd_firmware_update", R"(
-        semantics {
-          sender: "FWUPD firmware update"
-          description:
-            "Get the firmware update patch file from url and store it in the "
-            "the device cache. This is used to update a specific peripheral's "
-            "firmware."
-
-          trigger:
-            "Triggered by the user when they explicitly use the Firmware Update"
-            " UI to update their peripheral."
-          data: "None."
-          destination: GOOGLE_OWNED_SERVICE
-        })");
-
-std::unique_ptr<network::SimpleURLLoader> CreateSimpleURLLoader(GURL url) {
-  auto resource_request = std::make_unique<network::ResourceRequest>();
-  resource_request->url = url;
-  resource_request->method = "GET";
-  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
-
-  return network::SimpleURLLoader::Create(std::move(resource_request),
-                                          kFwupdFirmwareUpdateNetworkTag);
-}
-
-int GetResponseCode(network::SimpleURLLoader* simple_loader) {
-  if (simple_loader->ResponseInfo() && simple_loader->ResponseInfo()->headers)
-    return simple_loader->ResponseInfo()->headers->response_code();
-  else
-    return -1;
-}
-
 }  // namespace
 
 FirmwareUpdateManager::FirmwareUpdateManager()
@@ -188,7 +141,7 @@
           .Append(FILE_PATH_LITERAL(kCachePath));
 
   base::OnceClosure dir_created_callback =
-      base::BindOnce(&FirmwareUpdateManager::CreateLocalPatchFile,
+      base::BindOnce(&FirmwareUpdateManager::OnCacheDirectoryCreated,
                      weak_ptr_factory_.GetWeakPtr(), cache_path, device_id,
                      release, std::move(callback));
 
@@ -198,14 +151,14 @@
           [](const base::FilePath& path) {
             if (!CreateDirIfNotExists(path)) {
               LOG(ERROR) << "Cannot create firmware update directory, "
-                         << "may be created already.";
+                         << " may be created already.";
             }
           },
           cache_path),
       std::move(dir_created_callback));
 }
 
-void FirmwareUpdateManager::CreateLocalPatchFile(
+void FirmwareUpdateManager::OnCacheDirectoryCreated(
     const base::FilePath& cache_path,
     const std::string& device_id,
     int release,
@@ -213,59 +166,6 @@
   const base::FilePath patch_path =
       cache_path.Append(GetFilenameFromDevice(device_id, release));
 
-  // Create the patch file.
-  task_runner_->PostTaskAndReply(
-      FROM_HERE,
-      base::BindOnce(
-          [](const base::FilePath& patch_path) {
-            base::WriteFile(patch_path, /*data=*/"", /*size=*/1);
-          },
-          patch_path),
-      base::BindOnce(&FirmwareUpdateManager::DownloadFileToInternal,
-                     weak_ptr_factory_.GetWeakPtr(), patch_path, device_id,
-                     std::move(callback)));
-}
-
-void FirmwareUpdateManager::DownloadFileToInternal(
-    const base::FilePath& patch_path,
-    const std::string& device_id,
-    base::OnceCallback<void()> callback) {
-  std::string prefix_url(kFirmwareMirrorPrefix);
-  GURL download_url(fake_url_for_testing_.empty()
-                        ? prefix_url + std::string(kSampleFirmwareUpgrade)
-                        : fake_url_for_testing_);
-
-  std::unique_ptr<network::SimpleURLLoader> simple_loader =
-      CreateSimpleURLLoader(download_url);
-  DCHECK(FwupdDownloadClient::Get());
-
-  scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
-      FwupdDownloadClient::Get()->GetURLLoaderFactory();
-  // Save the pointer before moving `simple_loader` in the following call to
-  // `DownloadToFile()`.
-  auto* loader_ptr = simple_loader.get();
-
-  loader_ptr->DownloadToFile(
-      loader_factory.get(),
-      base::BindOnce(&FirmwareUpdateManager::OnUrlDownloadedToFile,
-                     weak_ptr_factory_.GetWeakPtr(), device_id,
-                     std::move(simple_loader), std::move(callback)),
-      patch_path);
-}
-
-void FirmwareUpdateManager::OnUrlDownloadedToFile(
-    const std::string& device_id,
-    std::unique_ptr<network::SimpleURLLoader> simple_loader,
-    base::OnceCallback<void()> callback,
-    base::FilePath download_path) {
-  if (simple_loader->NetError() != net::OK) {
-    LOG(ERROR) << "Downloading to file failed with error code: "
-               << GetResponseCode(simple_loader.get()) << " with network error "
-               << simple_loader->NetError();
-    std::move(callback).Run();
-    return;
-  }
-
   // TODO(jimmyxgong): Determine if this options map can be static or will need
   // to remain dynamic.
   // Fwupd Install Dbus flags, flag documentation can be found in
@@ -276,7 +176,7 @@
                                          {"allow-reinstall", true}};
 
   task_runner_->PostTaskAndReplyWithResult(
-      FROM_HERE, base::BindOnce(&OpenFileAndGetFileDescriptor, download_path),
+      FROM_HERE, base::BindOnce(&OpenFileAndGetFileDescriptor, patch_path),
       base::BindOnce(&FirmwareUpdateManager::InstallUpdate,
                      weak_ptr_factory_.GetWeakPtr(), device_id,
                      std::move(options), std::move(callback)));
diff --git a/ash/components/fwupd/firmware_update_manager.h b/ash/components/fwupd/firmware_update_manager.h
index 5ecd0ab..55165a1 100644
--- a/ash/components/fwupd/firmware_update_manager.h
+++ b/ash/components/fwupd/firmware_update_manager.h
@@ -23,12 +23,6 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
 
-namespace network {
-
-class SimpleURLLoader;
-
-}  // namespace network
-
 namespace ash {
 // FirmwareUpdateManager contains all logic that runs the firmware update SWA.
 class COMPONENT_EXPORT(ASH_FIRMWARE_UPDATE_MANAGER) FirmwareUpdateManager
@@ -98,20 +92,10 @@
                      base::OnceCallback<void()> callback,
                      base::ScopedFD file_descriptor);
 
-  void CreateLocalPatchFile(const base::FilePath& cache_path,
-                            const std::string& device_id,
-                            int release,
-                            base::OnceCallback<void()> callback);
-
-  void DownloadFileToInternal(const base::FilePath& patch_path,
-                              const std::string& device_id,
-                              base::OnceCallback<void()> callback);
-
-  void OnUrlDownloadedToFile(
-      const std::string& device_id,
-      std::unique_ptr<network::SimpleURLLoader> simple_loader,
-      base::OnceCallback<void()> callback,
-      base::FilePath download_path);
+  void OnCacheDirectoryCreated(const base::FilePath& root_path,
+                               const std::string& device_id,
+                               int release,
+                               base::OnceCallback<void()> callback);
 
   // Notifies observers registered with ObservePeripheralUpdates() the current
   // list of devices with pending updates (if any).
@@ -119,10 +103,6 @@
 
   bool HasPendingUpdates();
 
-  void SetFakeUrlForTesting(const std::string& fake_url) {
-    fake_url_for_testing_ = fake_url;
-  }
-
   // Map of a device ID to `FwupdDevice` which is waiting for the list
   // of updates.
   base::flat_map<std::string, chromeos::FwupdDevice> devices_pending_update_;
@@ -131,9 +111,6 @@
   // empty then this list is not yet complete.
   std::vector<firmware_update::mojom::FirmwareUpdatePtr> updates_;
 
-  // Only used for testing if StartInstall() queries to a fake URL.
-  std::string fake_url_for_testing_;
-
   // Remotes for tracking observers that will be notified of changes to the
   // list of firmware updates.
   mojo::RemoteSet<firmware_update::mojom::UpdateObserver>
diff --git a/ash/components/fwupd/firmware_update_manager_unittest.cc b/ash/components/fwupd/firmware_update_manager_unittest.cc
index bad84e5..f4b4a15 100644
--- a/ash/components/fwupd/firmware_update_manager_unittest.cc
+++ b/ash/components/fwupd/firmware_update_manager_unittest.cc
@@ -9,7 +9,6 @@
 #include <memory>
 #include <string>
 
-#include "ash/components/fwupd/fake_fwupd_download_client.h"
 #include "ash/constants/ash_features.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
@@ -24,8 +23,6 @@
 #include "dbus/mock_object_proxy.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "services/network/test/test_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -108,7 +105,6 @@
 
     dbus_client_ = FwupdClient::Create();
     dbus_client_->InitForTesting(bus_.get());
-    fake_fwupd_download_client_ = std::make_unique<FakeFwupdDownloadClient>();
     firmware_update_manager_ = std::make_unique<FirmwareUpdateManager>();
   }
   FirmwareUpdateManagerTest(const FirmwareUpdateManagerTest&) = delete;
@@ -137,10 +133,6 @@
     loop.Run();
   }
 
-  void SetFakeUrlForTesting(const std::string& fake_url) {
-    firmware_update_manager_->SetFakeUrlForTesting(fake_url);
-  }
-
   std::unique_ptr<dbus::Response> CreateEmptyDeviceResponse() {
     auto response = dbus::Response::CreateEmpty();
 
@@ -305,13 +297,8 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  network::TestURLLoaderFactory& GetTestUrlLoaderFactory() {
-    return fake_fwupd_download_client_->test_url_loader_factory();
-  }
-
   // `FwupdClient` must be be before `FirmwareUpdateManager`.
   std::unique_ptr<FwupdClient> dbus_client_;
-  std::unique_ptr<FakeFwupdDownloadClient> fake_fwupd_download_client_;
   std::unique_ptr<FirmwareUpdateManager> firmware_update_manager_;
 
   // Mock bus for simulating calls.
@@ -420,26 +407,24 @@
 
   dbus_responses_.push_back(dbus::Response::CreateEmpty());
 
-  std::string fake_url = "https://faketesturl/";
-  std::unique_ptr<FirmwareUpdateManager> firmware_update_manager_;
-  SetFakeUrlForTesting(fake_url);
-  GetTestUrlLoaderFactory().AddResponse(fake_url, "");
-
-  EXPECT_EQ(0, GetOnInstallResponseCallbackCallCountForTesting());
-  StartInstall(std::string(kFakeDeviceIdForTesting), /*release=*/0);
-
-  base::RunLoop().RunUntilIdle();
-
   base::FilePath root_dir;
   CHECK(base::PathService::Get(base::DIR_TEMP, &root_dir));
   const base::FilePath root_path =
       root_dir.Append(FILE_PATH_LITERAL(kDownloadDir))
           .Append(FILE_PATH_LITERAL(kCacheDir));
+
   const std::string test_filename =
       std::string(kFakeDeviceIdForTesting) + std::string(kCabExtension);
   base::FilePath full_path = root_path.Append(test_filename);
-  // Check that that expected patch file was created.
+  // Create a temporary file to simulate a .cab available for install.
+  base::WriteFile(full_path, "", 0);
   EXPECT_TRUE(base::PathExists(full_path));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(0, GetOnInstallResponseCallbackCallCountForTesting());
+  StartInstall(std::string(kFakeDeviceIdForTesting), /*release=*/0);
+
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(1, GetOnInstallResponseCallbackCallCountForTesting());
 }
diff --git a/ash/components/timezone/timezone_request.cc b/ash/components/timezone/timezone_request.cc
index 036aa63..00da029 100644
--- a/ash/components/timezone/timezone_request.cc
+++ b/ash/components/timezone/timezone_request.cc
@@ -93,6 +93,54 @@
   TIMEZONE_REQUEST_RESULT_COUNT = 4
 };
 
+constexpr net::NetworkTrafficAnnotationTag kTimezoneRequestNetworkTag =
+    net::DefineNetworkTrafficAnnotation("timezone_lookup", R"(
+        semantics {
+          sender: "Timezone Resolver"
+          description:
+            "Determine the user's timezone based on their geolocation."
+          trigger:
+            "Triggered during the device setup wizard, on boot, on user "
+            "session start, and in regular intervals."
+          data:
+            "The user's geolocation. By default, the geolocation is determined "
+            "from the ip address of the user, but depending on policies and "
+            "preferences, the user's Wi-Fi and mobile networks can also be "
+            "taken into account."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: NO
+          setting:
+            "You can enable or disable automatic timezone detection in Chrome "
+            "OS settings under 'Advanced' -> 'Date and time' -> 'Timezone' by "
+            "specifying a fixed timezone under 'Choose from list'. When you do "
+            "not specify a fixed timezone, you can select whether just the ip "
+            "address or also Wi-Fi and mobile networks are used to determine "
+            "your geolocation."
+          policy_exception_justification:
+            "Controlled by SystemTimeZone and SystemTimezonAutomaticDetection. "
+            "chrome_device_policy not supported by auditor yet."
+          # TODO(b/210911671): Add the following policies once they are
+          # supported:
+          # chrome_policy {
+          #   SystemTimezone {
+          #     # cmfcmf: HasSystemTimezonePolicy
+          #     timezone: 'not empty, e.g. Europe/Berlin'
+          #   }
+          # }
+          # chrome_policy {
+          #   SystemTimezone {
+          #     AutomaticTimezoneDetectionType: DISABLED
+          #   }
+          # }
+          # chrome_policy {
+          #   SystemTimezone {
+          #     AutomaticTimezoneDetectionType: IP_ONLY
+          #   }
+          # }
+        })");
+
 // Too many requests (more than 1) mean there is a problem in implementation.
 void RecordUmaEvent(TimeZoneRequestEvent event) {
   UMA_HISTOGRAM_ENUMERATION(
@@ -354,7 +402,7 @@
   request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
   request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   url_loader_ = network::SimpleURLLoader::Create(std::move(request),
-                                                 NO_TRAFFIC_ANNOTATION_YET);
+                                                 kTimezoneRequestNetworkTag);
 
   url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
       shared_url_loader_factory_.get(),
diff --git a/ash/search_box/search_box_view_base.cc b/ash/search_box/search_box_view_base.cc
index a37676fa..135b02a 100644
--- a/ash/search_box/search_box_view_base.cc
+++ b/ash/search_box/search_box_view_base.cc
@@ -251,6 +251,11 @@
     }
   }
 
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
+    views::Textfield::GetAccessibleNodeData(node_data);
+    search_box_view_->UpdateSearchTextfieldAccessibleNodeData(node_data);
+  }
+
  private:
   SearchBoxViewBase* const search_box_view_;
 };
@@ -521,6 +526,9 @@
   return trimmed_query.empty();
 }
 
+void SearchBoxViewBase::UpdateSearchTextfieldAccessibleNodeData(
+    ui::AXNodeData* node_data) {}
+
 void SearchBoxViewBase::ClearSearch() {
   // Avoid setting |search_box_| text to empty if it is already empty.
   if (search_box_->GetText() == std::u16string())
diff --git a/ash/search_box/search_box_view_base.h b/ash/search_box/search_box_view_base.h
index 87becba..f6a1922 100644
--- a/ash/search_box/search_box_view_base.h
+++ b/ash/search_box/search_box_view_base.h
@@ -114,6 +114,9 @@
   // Whether the trimmed query in the search box is empty.
   bool IsSearchBoxTrimmedQueryEmpty() const;
 
+  virtual void UpdateSearchTextfieldAccessibleNodeData(
+      ui::AXNodeData* node_data);
+
   virtual void ClearSearch();
 
   // Called when the search box active state changes.
diff --git a/ash/services/recording/recording_encoder_muxer.cc b/ash/services/recording/recording_encoder_muxer.cc
index b8dbd20..b528f70 100644
--- a/ash/services/recording/recording_encoder_muxer.cc
+++ b/ash/services/recording/recording_encoder_muxer.cc
@@ -153,7 +153,7 @@
         // Holds on to the old encoder until it flushes its buffers, then
         // destroys it.
         [](std::unique_ptr<media::VpxVideoEncoder> old_encoder,
-           media::Status status) {},
+           media::EncoderStatus status) {},
         std::move(video_encoder_)));
   }
 
@@ -215,7 +215,7 @@
         base::BindOnce(&RecordingEncoderMuxer::OnAudioEncoderFlushed,
                        weak_ptr_factory_.GetWeakPtr(), std::move(on_done)));
   } else {
-    OnAudioEncoderFlushed(std::move(on_done), media::OkStatus());
+    OnAudioEncoderFlushed(std::move(on_done), media::EncoderStatus::Codes::kOk);
   }
 }
 
@@ -260,7 +260,8 @@
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void RecordingEncoderMuxer::OnAudioEncoderInitialized(media::Status status) {
+void RecordingEncoderMuxer::OnAudioEncoderInitialized(
+    media::EncoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!status.is_ok()) {
@@ -278,7 +279,7 @@
 
 void RecordingEncoderMuxer::OnVideoEncoderInitialized(
     media::VpxVideoEncoder* encoder,
-    media::Status status) {
+    media::EncoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // Ignore initialization of encoders that were removed as part of
@@ -362,7 +363,7 @@
 }
 
 void RecordingEncoderMuxer::OnAudioEncoderFlushed(base::OnceClosure on_done,
-                                                  media::Status status) {
+                                                  media::EncoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!status.is_ok())
@@ -375,7 +376,7 @@
 }
 
 void RecordingEncoderMuxer::OnVideoEncoderFlushed(base::OnceClosure on_done,
-                                                  media::Status status) {
+                                                  media::EncoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!status.is_ok()) {
@@ -388,7 +389,7 @@
 }
 
 void RecordingEncoderMuxer::OnEncoderStatus(bool for_video,
-                                            media::Status status) {
+                                            media::EncoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (status.is_ok())
diff --git a/ash/services/recording/recording_encoder_muxer.h b/ash/services/recording/recording_encoder_muxer.h
index fcfc5c3..e504cc0 100644
--- a/ash/services/recording/recording_encoder_muxer.h
+++ b/ash/services/recording/recording_encoder_muxer.h
@@ -135,13 +135,13 @@
 
   // Called when the audio encoder is initialized to provide the |status| of
   // the initialization.
-  void OnAudioEncoderInitialized(media::Status status);
+  void OnAudioEncoderInitialized(media::EncoderStatus status);
 
   // Called when the video |encoder| is initialized to provide the |status| of
   // the initialization. If initialization failed, |on_failure_callback_| will
   // be triggered.
   void OnVideoEncoderInitialized(media::VpxVideoEncoder* encoder,
-                                 media::Status status);
+                                 media::EncoderStatus status);
 
   // Performs the actual encoding of the given audio |frame|. It should never be
   // called before the audio encoder is initialized. Audio frames received
@@ -169,16 +169,18 @@
   // Called when the audio encoder flushes all its buffered frames, at which
   // point we can flush the video encoder. |on_done| will be passed to
   // OnVideoEncoderFlushed()
-  void OnAudioEncoderFlushed(base::OnceClosure on_done, media::Status status);
+  void OnAudioEncoderFlushed(base::OnceClosure on_done,
+                             media::EncoderStatus status);
 
   // Called when the video encoder flushes all its buffered frames, at which
   // point we can flush the muxer. |on_done| will be called to signal that
   // flushing is complete.
-  void OnVideoEncoderFlushed(base::OnceClosure on_done, media::Status status);
+  void OnVideoEncoderFlushed(base::OnceClosure on_done,
+                             media::EncoderStatus status);
 
   // Called by both the audio and video encoders to provide the |status| of
   // encoding tasks.
-  void OnEncoderStatus(bool for_video, media::Status status);
+  void OnEncoderStatus(bool for_video, media::EncoderStatus status);
 
   // Notifies the owner of this object (via |on_failure_callback_|) that a
   // failure noted by |status| has occurred during audio or video encoding, or
diff --git a/ash/session/fullscreen_controller.cc b/ash/session/fullscreen_controller.cc
index 45c9017a..a012772 100644
--- a/ash/session/fullscreen_controller.cc
+++ b/ash/session/fullscreen_controller.cc
@@ -146,9 +146,4 @@
     MaybeShowNotification();
 }
 
-void FullscreenController::OnLockStateChanged(bool locked) {
-  if (!locked)
-    MaybeShowNotification();
-}
-
 }  // namespace ash
diff --git a/ash/session/fullscreen_controller.h b/ash/session/fullscreen_controller.h
index 190e0fa..9938b50 100644
--- a/ash/session/fullscreen_controller.h
+++ b/ash/session/fullscreen_controller.h
@@ -29,11 +29,6 @@
 
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
-  void OnLockStateChanged(bool locked);
-
-  // Returns the bubble for testing purposes.
-  FullscreenNotificationBubble* bubble_for_test() { return bubble_.get(); }
-
  private:
   // chromeos::PowerManagerClient::Observer:
   void SuspendImminent(power_manager::SuspendImminent::Reason reason) override;
diff --git a/ash/session/session_controller_impl.cc b/ash/session/session_controller_impl.cc
index a6a6a2d..3b05ebd 100644
--- a/ash/session/session_controller_impl.cc
+++ b/ash/session/session_controller_impl.cc
@@ -490,14 +490,6 @@
       observer.OnLockStateChanged(locked);
 
     session_activation_observer_holder_.NotifyLockStateChanged(locked);
-
-    // We cannot use SessionObserver or SessionManagerObserver because they get
-    // notified before the lock screen widget is destroyed which would mean that
-    // the active window would always be a full screen window and the
-    // FullscreenController would therefore always show the
-    // FullscreenNotificationBubble. We cannot use SessionActivationObserver
-    // because they are always tied to an account.
-    fullscreen_controller_->OnLockStateChanged(locked);
   }
 
   EnsureSigninScreenPrefService();
diff --git a/ash/session/session_controller_impl.h b/ash/session/session_controller_impl.h
index 7b9e5e8..17b6f6f 100644
--- a/ash/session/session_controller_impl.h
+++ b/ash/session/session_controller_impl.h
@@ -219,10 +219,6 @@
   // Test helpers.
   void ClearUserSessionsForTest();
 
-  FullscreenController* fullscreen_controller_for_test() {
-    return fullscreen_controller_.get();
-  }
-
  private:
   friend class TestSessionControllerClient;
 
diff --git a/ash/session/session_controller_impl_unittest.cc b/ash/session/session_controller_impl_unittest.cc
index d562520..85a9b917 100644
--- a/ash/session/session_controller_impl_unittest.cc
+++ b/ash/session/session_controller_impl_unittest.cc
@@ -9,28 +9,21 @@
 #include <utility>
 #include <vector>
 
-#include "ash/constants/ash_features.h"
-#include "ash/constants/ash_pref_names.h"
 #include "ash/login_status.h"
 #include "ash/public/cpp/ash_prefs.h"
 #include "ash/public/cpp/session/session_observer.h"
-#include "ash/session/fullscreen_controller.h"
-#include "ash/session/fullscreen_notification_bubble.h"
 #include "ash/session/test_session_controller_client.h"
 #include "ash/shell.h"
 #include "ash/system/tray/system_tray_notifier.h"
 #include "ash/test/ash_test_base.h"
-#include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/run_loop.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/user_manager/user_type.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/window/dialog_delegate.h"
@@ -805,97 +798,5 @@
   EXPECT_TRUE(widget->IsActive());
 }
 
-class SessionControllerImplLockStateChangedTest : public AshTestBase {
- public:
-  SessionControllerImplLockStateChangedTest() {}
-
-  SessionControllerImplLockStateChangedTest(
-      const SessionControllerImplLockStateChangedTest&) = delete;
-  SessionControllerImplLockStateChangedTest& operator=(
-      const SessionControllerImplLockStateChangedTest&) = delete;
-
-  ~SessionControllerImplLockStateChangedTest() override {}
-
-  // AshTestBase:
-  void SetUp() override {
-    // Enable the FullscreenAlertBubble feature for testing.
-    feature_list_.InitAndEnableFeature(features::kFullscreenAlertBubble);
-
-    AshTestBase::SetUp();
-
-    // Set the FullscreenAlertEnabled pref value to true for testing.
-    Shell::Get()->session_controller()->GetPrimaryUserPrefService()->SetBoolean(
-        prefs::kFullscreenAlertEnabled, true);
-
-    fullscreen_controller_ =
-        Shell::Get()->session_controller()->fullscreen_controller_for_test();
-  }
-
-  void TearDown() override {
-    window_.reset();
-    AshTestBase::TearDown();
-  }
-
-  void CreateFullscreenWindow() {
-    window_ = CreateTestWindow();
-    window_->SetProperty(aura::client::kShowStateKey,
-                         ui::SHOW_STATE_FULLSCREEN);
-    window_state_ = WindowState::Get(window_.get());
-  }
-
-  void ExpectFullscreenNotificationShowing(bool expect_notification) {
-    if (expect_notification) {
-      views::Widget* fullscreen_notification_widget =
-          fullscreen_controller_->bubble_for_test()->widget_for_test();
-      EXPECT_TRUE(fullscreen_notification_widget->IsVisible());
-      return;
-    }
-    EXPECT_TRUE(!fullscreen_controller_->bubble_for_test() ||
-                !fullscreen_controller_->bubble_for_test()->widget_for_test() ||
-                !fullscreen_controller_->bubble_for_test()
-                     ->widget_for_test()
-                     ->IsVisible());
-  }
-
- protected:
-  std::unique_ptr<aura::Window> window_;
-
-  WindowState* window_state_ = nullptr;
-  FullscreenController* fullscreen_controller_ = nullptr;
-
-  base::test::ScopedFeatureList feature_list_;
-};
-
-TEST_F(SessionControllerImplLockStateChangedTest, ExitFullscreenBeforeLock) {
-  CreateFullscreenWindow();
-  EXPECT_TRUE(window_state_->IsFullscreen());
-
-  base::RunLoop run_loop;
-  Shell::Get()->session_controller()->PrepareForLock(run_loop.QuitClosure());
-  EXPECT_FALSE(window_state_->IsFullscreen());
-}
-
-// Test that no full screen notification is shown on session lock or unlock when
-// there is no window in full screen mode.
-TEST_F(SessionControllerImplLockStateChangedTest, WithoutFullscreenWindow) {
-  GetSessionControllerClient()->LockScreen();
-  ExpectFullscreenNotificationShowing(false);
-
-  GetSessionControllerClient()->UnlockScreen();
-  ExpectFullscreenNotificationShowing(false);
-}
-
-// Test that the full screen notification is shown on session unlock when there
-// is a window in full screen mode.
-TEST_F(SessionControllerImplLockStateChangedTest, WithFullscreenWindow) {
-  CreateFullscreenWindow();
-
-  GetSessionControllerClient()->LockScreen();
-  ExpectFullscreenNotificationShowing(false);
-
-  GetSessionControllerClient()->UnlockScreen();
-  ExpectFullscreenNotificationShowing(true);
-}
-
 }  // namespace
 }  // namespace ash
diff --git a/ash/webui/camera_app_ui/resources/js/face.js b/ash/webui/camera_app_ui/resources/js/face.js
deleted file mode 100644
index 6f79601..0000000
--- a/ash/webui/camera_app_ui/resources/js/face.js
+++ /dev/null
@@ -1,133 +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.
-
-import {assertInstanceof, assertNotReached} from './assert.js';
-import * as dom from './dom.js';
-
-// eslint-disable-next-line no-unused-vars
-import {Resolution} from './type.js';
-
-// G-yellow-600 with alpha = 0.8
-const RECT_COLOR = 'rgba(249, 171, 0, 0.8)';
-
-/**
- * Rotates the given coordinates in [0, 1] square space by the given
- * orientation.
- * @param {number} x
- * @param {number} y
- * @param {number} orientation
- * @return {!Array<number>} The rotated [x, y].
- */
-function rotate(x, y, orientation) {
-  switch (orientation) {
-    case 0:
-      return [x, y];
-    case 90:
-      return [y, 1.0 - x];
-    case 180:
-      return [1.0 - x, 1.0 - y];
-    case 270:
-      return [1.0 - y, x];
-  }
-  assertNotReached('Unexpected orientation');
-}
-
-/**
- * An overlay to show face rectangles over preview.
- */
-export class FaceOverlay {
-  /**
-   * @param {!Resolution} activeArraySize
-   * @param {number} orientation Counter-clockwise angles to apply rotation to
-   *     the face rectangles.
-   */
-  constructor(activeArraySize, orientation) {
-    /**
-     * @const {!Resolution}
-     * @private
-     */
-    this.activeArraySize_ = activeArraySize;
-
-    /**
-     * @const {number}
-     * @private
-     */
-    this.orientation_ = orientation;
-
-    /**
-     * @const {!HTMLCanvasElement}
-     * @private
-     */
-    this.canvas_ = dom.get('#preview-face-overlay', HTMLCanvasElement);
-
-    /**
-     * @const {!CanvasRenderingContext2D}
-     * @private
-     */
-    this.ctx_ = assertInstanceof(
-        this.canvas_.getContext('2d'), CanvasRenderingContext2D);
-  }
-
-  /**
-   * Shows the given rectangles on overlay. The old rectangles would be
-   * cleared, if any.
-   * @param {!Array<number>} rects An array of [x1, y1, x2, y2] to represent
-   *     rectangles in the coordinate system of active array in sensor.
-   */
-  show(rects) {
-    this.ctx_.clearRect(0, 0, this.canvas_.width, this.canvas_.height);
-
-    // TODO(b/178344897): Handle zoomed preview.
-    // TODO(b/178344897): Handle screen orientation dynamically.
-
-    this.ctx_.strokeStyle = RECT_COLOR;
-    for (let i = 0; i < rects.length; i += 4) {
-      let [x1, y1, x2, y2] = rects.slice(i, i + 4);
-      x1 /= this.activeArraySize_.width;
-      y1 /= this.activeArraySize_.height;
-      x2 /= this.activeArraySize_.width;
-      y2 /= this.activeArraySize_.height;
-      [x1, y1] = rotate(x1, y1, this.orientation_);
-      [x2, y2] = rotate(x2, y2, this.orientation_);
-      const canvasAspectRatio = this.canvas_.width / this.canvas_.height;
-      const sensorAspectRatio =
-          this.activeArraySize_.width / this.activeArraySize_.height;
-      if (canvasAspectRatio > sensorAspectRatio) {
-        // Canvas has wider aspect than the sensor, e.g. when we're showing a
-        // 16:9 stream captured from a 4:3 sensor. Based on our hardware
-        // requirement, we assume the stream is cropped into letterbox from the
-        // active array.
-        const normalizedCanvasHeight = sensorAspectRatio / canvasAspectRatio;
-        const clipped = (1 - normalizedCanvasHeight) / 2;
-        x1 *= this.canvas_.width;
-        y1 = (Math.max(y1 - clipped, 0) / normalizedCanvasHeight) *
-            this.canvas_.height;
-        x2 *= this.canvas_.width;
-        y2 = (Math.max(y2 - clipped, 0) / normalizedCanvasHeight) *
-            this.canvas_.height;
-      } else if (canvasAspectRatio < sensorAspectRatio) {
-        // Canvas has taller aspect than the sensor, e.g. when we're showing a
-        // 4:3 stream captured from a 16:9 sensor. Based on our hardware
-        // requirement, we assume the stream is cropped into pillarbox from the
-        // active array.
-        const normalizedCanvasWidth = canvasAspectRatio / sensorAspectRatio;
-        const clipped = (1 - normalizedCanvasWidth) / 2;
-        x1 = (Math.max(x1 - clipped, 0) * normalizedCanvasWidth) *
-            this.canvas_.width;
-        y1 *= this.canvas_.height;
-        x2 = (Math.max(x2 - clipped, 0) * normalizedCanvasWidth) *
-            this.canvas_.width;
-        y2 *= this.canvas_.height;
-      }
-      this.ctx_.strokeRect(x1, y1, x2 - x1, y2 - y1);
-    }
-  }
-
-  /**
-   * Clears all rectangles.
-   */
-  clear() {
-    this.ctx_.clearRect(0, 0, this.canvas_.width, this.canvas_.height);
-  }
-}
diff --git a/ash/webui/camera_app_ui/resources/js/face.ts b/ash/webui/camera_app_ui/resources/js/face.ts
new file mode 100644
index 0000000..bda76c4
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/face.ts
@@ -0,0 +1,111 @@
+// 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 {assertInstanceof, assertNotReached} from './assert.js';
+import * as dom from './dom.js';
+import {Resolution} from './type.js';
+
+// G-yellow-600 with alpha = 0.8
+const RECT_COLOR = 'rgba(249, 171, 0, 0.8)';
+
+/**
+ * Rotates the given coordinates in [0, 1] square space by the given
+ * orientation.
+ * @return The rotated [x, y].
+ */
+function rotate(x: number, y: number, orientation: number): number[] {
+  switch (orientation) {
+    case 0:
+      return [x, y];
+    case 90:
+      return [y, 1.0 - x];
+    case 180:
+      return [1.0 - x, 1.0 - y];
+    case 270:
+      return [1.0 - y, x];
+  }
+  assertNotReached('Unexpected orientation');
+}
+
+/**
+ * An overlay to show face rectangles over preview.
+ */
+export class FaceOverlay {
+  private readonly canvas = dom.get('#preview-face-overlay', HTMLCanvasElement);
+  private readonly ctx: CanvasRenderingContext2D;
+
+  /**
+   * @param orientation Counter-clockwise angles to apply rotation to
+   *     the face rectangles.
+   */
+  constructor(
+      private readonly activeArraySize: Resolution,
+      private readonly orientation: number) {
+    this.ctx = assertInstanceof(
+        this.canvas.getContext('2d'), CanvasRenderingContext2D);
+  }
+
+  /**
+   * Shows the given rectangles on overlay. The old rectangles would be
+   * cleared, if any.
+   * @param rects An array of [x1, y1, x2, y2] to represent rectangles in the
+   *     coordinate system of active array in sensor.
+   */
+  show(rects: number[]): void {
+    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+
+    // TODO(b/178344897): Handle zoomed preview.
+    // TODO(b/178344897): Handle screen orientation dynamically.
+
+    this.ctx.strokeStyle = RECT_COLOR;
+    for (let i = 0; i < rects.length; i += 4) {
+      let [x1, y1, x2, y2] = rects.slice(i, i + 4);
+      x1 /= this.activeArraySize.width;
+      y1 /= this.activeArraySize.height;
+      x2 /= this.activeArraySize.width;
+      y2 /= this.activeArraySize.height;
+      [x1, y1] = rotate(x1, y1, this.orientation);
+      [x2, y2] = rotate(x2, y2, this.orientation);
+
+      const canvasAspectRatio = this.canvas.width / this.canvas.height;
+      const sensorAspectRatio =
+          this.activeArraySize.width / this.activeArraySize.height;
+      if (canvasAspectRatio > sensorAspectRatio) {
+        // Canvas has wider aspect than the sensor, e.g. when we're showing a
+        // 16:9 stream captured from a 4:3 sensor. Based on our hardware
+        // requirement, we assume the stream is cropped into letterbox from the
+        // active array.
+        const normalizedCanvasHeight = sensorAspectRatio / canvasAspectRatio;
+        const clipped = (1 - normalizedCanvasHeight) / 2;
+        x1 *= this.canvas.width;
+        y1 = (Math.max(y1 - clipped, 0) / normalizedCanvasHeight) *
+            this.canvas.height;
+        x2 *= this.canvas.width;
+        y2 = (Math.max(y2 - clipped, 0) / normalizedCanvasHeight) *
+            this.canvas.height;
+      } else if (canvasAspectRatio < sensorAspectRatio) {
+        // Canvas has taller aspect than the sensor, e.g. when we're showing a
+        // 4:3 stream captured from a 16:9 sensor. Based on our hardware
+        // requirement, we assume the stream is cropped into pillarbox from the
+        // active array.
+        const normalizedCanvasWidth = canvasAspectRatio / sensorAspectRatio;
+        const clipped = (1 - normalizedCanvasWidth) / 2;
+        x1 = (Math.max(x1 - clipped, 0) * normalizedCanvasWidth) *
+            this.canvas.width;
+        y1 *= this.canvas.height;
+        x2 = (Math.max(x2 - clipped, 0) * normalizedCanvasWidth) *
+            this.canvas.width;
+        y2 *= this.canvas.height;
+      }
+      this.ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
+    }
+  }
+
+  /**
+   * Clears all rectangles.
+   */
+  clear(): void {
+    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/focus_ring.js b/ash/webui/camera_app_ui/resources/js/focus_ring.ts
similarity index 85%
rename from ash/webui/camera_app_ui/resources/js/focus_ring.js
rename to ash/webui/camera_app_ui/resources/js/focus_ring.ts
index cbf37bf..02fd6e847 100644
--- a/ash/webui/camera_app_ui/resources/js/focus_ring.js
+++ b/ash/webui/camera_app_ui/resources/js/focus_ring.ts
@@ -12,46 +12,37 @@
 
 /**
  * Focus ring element.
- * @type {?HTMLElement}
  */
-let ring = null;
+let ring: HTMLElement|null = null;
 
-/**
- * @type {?CSSStyleDeclaration}
- */
-let ringCSSStyle = null;
+let ringCSSStyle: CSSStyleDeclaration|null = null;
 
 /**
  * All valid values of '--focus-ring-style'.
- * @const {!Set<string>}
  */
 const ringStyleValues = new Set(['circle', 'mode-item-input', 'none', 'pill']);
 
 /**
  * The reference bounding rectangle of focused UI.
- * @type {!DOMRectReadOnly}
  */
 let uiRect = new DOMRectReadOnly();
 
 /**
  * Name of event triggered for calculating bounding rectangle of focused UI.
- * @const {string}
  */
 export const FOCUS_RING_UI_RECT_EVENT_NAME = 'focusringuirect';
 
 /**
  * Sets reference bounding rectangle of focused UI.
- * @param {!DOMRectReadOnly} rect
  */
-export function setUIRect(rect) {
+export function setUIRect(rect: DOMRectReadOnly): void {
   uiRect = rect;
 }
 
 /**
  * Shows focus ring on |el|.
- * @param {!HTMLElement} el
  */
-function showFocus(el) {
+function showFocus(el: HTMLElement) {
   const style = el.computedStyleMap();
   const size = getStyleValueInPx(style, '--focus-ring-size');
   const ringStyleValue = `${style.get('--focus-ring-style')}`;
@@ -70,17 +61,11 @@
   ringCSSStyle.setProperty('left', `${(uiRect.left + uiRect.right) / 2}px`);
 }
 
-/**
- * @public
- */
-export function initialize() {
+export function initialize(): void {
   ring = dom.get('#focus-ring', HTMLElement);
   ringCSSStyle = cssStyle('#focus-ring');
 
-  /**
-   * @param {!HTMLElement} el
-   */
-  const setup = (el) => {
+  const setup = (el: HTMLElement) => {
     el.addEventListener('focus', () => showFocus(el));
     if (el === document.activeElement) {
       showFocus(el);
diff --git a/ash/webui/camera_app_ui/resources/js/js.gni b/ash/webui/camera_app_ui/resources/js/js.gni
index 305ffd6..f049b5d 100644
--- a/ash/webui/camera_app_ui/resources/js/js.gni
+++ b/ash/webui/camera_app_ui/resources/js/js.gni
@@ -17,9 +17,9 @@
   "dom.ts",
   "error.ts",
   "expert.ts",
-  "face.js",
+  "face.ts",
   "flag.ts",
-  "focus_ring.js",
+  "focus_ring.ts",
   "gallerybutton.js",
   "geometry.ts",
   "h264.js",
diff --git a/ash/webui/shimless_rma/resources/icons.html b/ash/webui/shimless_rma/resources/icons.html
index e6d1b43..afeb2bd 100644
--- a/ash/webui/shimless_rma/resources/icons.html
+++ b/ash/webui/shimless_rma/resources/icons.html
@@ -7,6 +7,9 @@
       <g id="info">
         <path fill-rule="evenodd" clip-rule="evenodd" d="M9 14H11V10H9V14ZM10 2C5.584 2 2 5.584 2 10C2 14.416 5.584 18 10 18C14.416 18 18 14.416 18 10C18 5.584 14.416 2 10 2ZM10 16C6.6925 16 4 13.3075 4 10C4 6.6925 6.6925 4 10 4C13.3075 4 16 6.6925 16 10C16 13.3075 13.3075 16 10 16ZM9 8H11V6H9V8Z"></path>
       </g>
+      <g id="info-grey" width="20" height="20" viewBox="0 0 20 20">
+        <path fill-rule="evenodd" clip-rule="evenodd" d="M9 14H11V10H9V14ZM10 2C5.584 2 2 5.584 2 10C2 14.416 5.584 18 10 18C14.416 18 18 14.416 18 10C18 5.584 14.416 2 10 2ZM10 16C6.6925 16 4 13.3075 4 10C4 6.6925 6.6925 4 10 4C13.3075 4 16 6.6925 16 10C16 13.3075 13.3075 16 10 16ZM9 8H11V6H9V8Z" fill="#202124"></path>
+      </g>
       <g id="warning">
         <path d="M9 12H11V8H9V12Z" fill="#202124"></path>
         <path d="M11 15H9V13H11V15Z" fill="#202124"></path>
diff --git a/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.html b/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.html
index b1c755a..53c683d 100644
--- a/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.html
+++ b/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.html
@@ -12,6 +12,10 @@
     width: 350px;
   }
 
+  :host([plugged-in_]) #batteryCutButton {
+    opacity: 0.5;
+  }
+
   .button-text {
     flex-basis: 250px;
   }
@@ -87,24 +91,30 @@
         </div>
       </div>
     </cr-button>
-    <!-- TODO(gavindodd): Add UI cues that button is disabled. -->
-    <cr-button id="batteryCutButton" class="button-card"
-      on-click="onBatteryCutButtonClick_" disabled$="[[pluggedIn_]]">
-      <iron-icon icon="shimless-icon:battery-cutoff" class="button-icon"
-          hidden="[[!checked]]">
-      </iron-icon>
-      <div class="button-text">
-        <div class="button-label">
-          <span>[[i18n('repairCompletedShutoffButtonText')]]</span>
+    <div id="batteryCutoffTooltipWrapper">
+      <cr-button id="batteryCutButton" class="button-card"
+        on-click="onBatteryCutButtonClick_" disabled$="[[pluggedIn_]]">
+        <iron-icon id="battery-cutoff-icon" icon="shimless-icon:battery-cutoff" class="button-icon"
+            hidden="[[!checked]]">
+        </iron-icon>
+        <div class="button-text">
+          <div class="button-label">
+            <span>[[i18n('repairCompletedShutoffButtonText')]]</span>
+            <iron-icon id="battery-cutoff-info-icon" icon="shimless-icon:info-grey" hidden="[[!pluggedIn_]]">
+            </iron-icon>  
+          </div>
+          <div class="button-description">
+            [[i18n('repairCompletedShutoffDescriptionText')]]
+          </div>
+          <div class="button-description" hidden="[[!pluggedIn_]]">
+            [[i18n('repairCompletedShutoffInstructionsText')]]
+          </div>
         </div>
-        <div class="button-description">
-          [[i18n('repairCompletedShutoffDescriptionText')]]
-        </div>
-        <div class="button-description" hidden="[[!pluggedIn_]]">
-          [[i18n('repairCompletedShutoffInstructionsText')]]
-        </div>
-      </div>
-    </cr-button>
+      </cr-button>
+    </div>
+    <paper-tooltip for="batteryCutoffTooltipWrapper" hidden="[[!pluggedIn_]]">
+      [[i18n('batteryShutoffTooltipText')]]
+    </paper-tooltip>
   </div>
 </base-page>
 
@@ -124,25 +134,3 @@
     </cr-button>
   </div>
 </cr-dialog>
-
-<!-- TODO(gavindodd): Confirm we need this. The cutoff button is guarded by
-     a check for power cable state so it could immediately shut down. -->
-<cr-dialog id="batteryCutDialog" close-text="close">
-  <div slot="title">
-    [[i18n('batteryShutoffTitleText')]]
-  </div>
-  <div slot="body">
-    <div>
-      [[batteryCutInstructions_(pluggedIn_)]]
-    </div>
-  </div>
-  <div slot="footer">
-    <cr-button id="closeBatteryDialogButton" on-click="onCancelClick_">
-      [[i18n('batteryShutoffCancelButtonText')]]
-    </cr-button>
-    <cr-button id="shutdownButton" on-click="onShutdownClick_"
-      disabled$="[[pluggedIn_]]">
-      [[i18n('batteryShutoffShutdownButtonText')]]
-    </cr-button>
-  </div>
-</cr-dialog>
diff --git a/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.js b/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.js
index 74baa6b..aa1dc9a 100644
--- a/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.js
+++ b/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.js
@@ -4,6 +4,7 @@
 
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import 'chrome://resources/polymer/v3_0/paper-tooltip/paper-tooltip.js';
 import './base_page.js';
 import './shimless_rma_shared_css.js';
 
@@ -50,6 +51,7 @@
        * Assume plugged in is true until first observation.
        */
       pluggedIn_: {
+        reflectToAttribute: true,
         type: Boolean,
         value: true,
       }
@@ -115,19 +117,7 @@
   }
 
   /** @protected */
-  onBatteryCutButtonClick_() {
-    const dialog = /** @type {!CrDialogElement} */ (
-        this.shadowRoot.querySelector('#batteryCutDialog'));
-    if (!dialog.open) {
-      dialog.showModal();
-    }
-  }
-
-  /** @protected */
-  batteryCutInstructions_() {
-    return this.pluggedIn_ ? this.i18n('batteryShutoffUnplugMessageText') :
-                             this.i18n('batteryShutoffShutdownMessageText');
-  }
+  onBatteryCutButtonClick_() {}
 
   /** @protected */
   onCancelClick_() {
diff --git a/ash/webui/shimless_rma/shimless_rma.cc b/ash/webui/shimless_rma/shimless_rma.cc
index 7a19f41..0d47855 100644
--- a/ash/webui/shimless_rma/shimless_rma.cc
+++ b/ash/webui/shimless_rma/shimless_rma.cc
@@ -213,15 +213,8 @@
       {"rmaLogsTitleText", IDS_SHIMLESS_RMA_LOGS_TITLE},
       {"rmaLogsCancelButtonText", IDS_SHIMLESS_RMA_LOGS_CANCEL_BUTTON},
       {"rmaLogsSaveToUsbButtonText", IDS_SHIMLESS_RMA_LOGS_SAVE_BUTTON},
-      {"batteryShutoffTitleText", IDS_SHIMLESS_BATTERY_CUTOFF_TITLE},
-      {"batteryShutoffUnplugMessageText",
-       IDS_SHIMLESS_BATTERY_SHUTOFF_UNPLUG_MESSAGE},
-      {"batteryShutoffShutdownMessageText",
-       IDS_SHIMLESS_BATTERY_SHUTOFF_SHUTDOWN_MESSAGE},
-      {"batteryShutoffCancelButtonText",
-       IDS_SHIMLESS_BATTERY_SHUTOFF_CANCEL_BUTTON},
-      {"batteryShutoffShutdownButtonText",
-       IDS_SHIMLESS_BATTERY_SHUTOFF_SHUTDOWN_BUTTON},
+      {"batteryShutoffTooltipText", IDS_SHIMLESS_BATTERY_SHUTOFF_TOOLTIP_TEXT},
+
       // Manual disable wp page
       {"manuallyDisableWpTitleText",
        IDS_SHIMLESS_RMA_MANUALLY_DISABLE_WP_TITLE},
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
index 5724372..8bc58b1 100644
--- a/base/trace_event/trace_log.cc
+++ b/base/trace_event/trace_log.cc
@@ -1654,7 +1654,7 @@
 
 void TraceLog::UseNextTraceBuffer() {
   logged_events_.reset(CreateTraceBuffer());
-  subtle::NoBarrier_AtomicIncrement(&generation_, 1);
+  generation_.fetch_add(1, std::memory_order_relaxed);
   thread_shared_chunk_.reset();
   thread_shared_chunk_index_ = 0;
 }
diff --git a/base/trace_event/trace_log.h b/base/trace_event/trace_log.h
index 0f53e26..f232e0f 100644
--- a/base/trace_event/trace_log.h
+++ b/base/trace_event/trace_log.h
@@ -552,7 +552,7 @@
   void OnFlushTimeout(int generation, bool discard_events);
 
   int generation() const {
-    return static_cast<int>(subtle::NoBarrier_Load(&generation_));
+    return generation_.load(std::memory_order_relaxed);
   }
   bool CheckGeneration(int generation) const {
     return generation == this->generation();
@@ -646,7 +646,7 @@
   ArgumentFilterPredicate argument_filter_predicate_;
   MetadataFilterPredicate metadata_filter_predicate_;
   bool record_host_app_package_name_{false};
-  subtle::AtomicWord generation_;
+  std::atomic<int> generation_;
   bool use_worker_thread_;
   std::atomic<AddTraceEventOverrideFunction> add_trace_event_override_{nullptr};
   std::atomic<OnFlushFunction> on_flush_override_{nullptr};
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 1220bba0..7fa20e5 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-7.20211221.3.1
+7.20211222.1.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 1220bba0..7fa20e5 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-7.20211221.3.1
+7.20211222.1.1
diff --git a/chrome/VERSION b/chrome/VERSION
index 0daadaa1..88bf9e5 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=99
 MINOR=0
-BUILD=4781
+BUILD=4782
 PATCH=0
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 1c03058..073059a1 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -834,8 +834,6 @@
   "java/src/org/chromium/chrome/browser/ntp/SnapScrollHelperImpl.java",
   "java/src/org/chromium/chrome/browser/ntp/TitleUtil.java",
   "java/src/org/chromium/chrome/browser/ntp/cards/SignInPromo.java",
-  "java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoController.java",
-  "java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoUtils.java",
   "java/src/org/chromium/chrome/browser/ntp/search/SearchBoxContainerView.java",
   "java/src/org/chromium/chrome/browser/ntp/search/SearchBoxCoordinator.java",
   "java/src/org/chromium/chrome/browser/ntp/search/SearchBoxMediator.java",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index e7c446f3..760eb2a 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -311,7 +311,6 @@
   "javatests/src/org/chromium/chrome/browser/ntp/RecentTabsPageTest.java",
   "javatests/src/org/chromium/chrome/browser/ntp/RevampedIncognitoDescriptionViewRenderTest.java",
   "javatests/src/org/chromium/chrome/browser/ntp/TitleUtilTest.java",
-  "javatests/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoTest.java",
   "javatests/src/org/chromium/chrome/browser/offlinepages/MHTMLPageTest.java",
   "javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageArchivePublisherBridgeTest.java",
   "javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageAutoFetchTest.java",
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index 1c95533..f3989f0 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -44,7 +44,6 @@
 import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncher;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.ntp.NewTabPageLaunchOrigin;
-import org.chromium.chrome.browser.ntp.cards.promo.enhanced_protection.EnhancedProtectionPromoController;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
@@ -112,11 +111,6 @@
     private boolean mIsActive;
     private int mHeaderCount;
 
-    // Enhanced Protection promo view will be not-null once we have it created, until it is
-    // destroyed.
-    private @Nullable View mEnhancedProtectionPromoView;
-    private @Nullable EnhancedProtectionPromoController mEnhancedProtectionPromoController;
-
     // Used when Feed is enabled.
     private @Nullable Profile mProfile;
     private @Nullable FeedSurfaceLifecycleManager mFeedSurfaceLifecycleManager;
@@ -311,11 +305,6 @@
 
         mHandler = new Handler(Looper.getMainLooper());
 
-        if (isEnhancedProtectionPromoEnabled()) {
-            mEnhancedProtectionPromoController =
-                    new EnhancedProtectionPromoController(mActivity, mProfile);
-        }
-
         // MVC setup for feed header.
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_FEED)) {
             mSectionHeaderView = (SectionHeaderView) LayoutInflater.from(mActivity).inflate(
@@ -372,9 +361,6 @@
         stopBubbleTriggering();
         if (mFeedSurfaceLifecycleManager != null) mFeedSurfaceLifecycleManager.destroy();
         mFeedSurfaceLifecycleManager = null;
-        if (mEnhancedProtectionPromoController != null) {
-            mEnhancedProtectionPromoController.destroy();
-        }
         stopScrollTracking();
         if (mSectionHeaderModelChangeProcessor != null) {
             mSectionHeaderModelChangeProcessor.destroy();
@@ -655,9 +641,6 @@
         if (mNtpHeader != null) UiUtils.removeViewFromParent(mNtpHeader);
         UiUtils.removeViewFromParent(mSectionHeaderView);
         if (mSigninPromoView != null) UiUtils.removeViewFromParent(mSigninPromoView);
-        if (mEnhancedProtectionPromoView != null) {
-            UiUtils.removeViewFromParent(mEnhancedProtectionPromoView);
-        }
 
         // Directly add header views to content manager.
         List<View> headerList = new ArrayList<>();
@@ -741,12 +724,6 @@
             mFeedSurfaceLifecycleManager = null;
             mStream = null;
             mSigninPromoView = null;
-
-            mEnhancedProtectionPromoView = null;
-            if (mEnhancedProtectionPromoController != null) {
-                mEnhancedProtectionPromoController.destroy();
-                mEnhancedProtectionPromoController = null;
-            }
         }
 
         mScrollViewForPolicy = new PolicyScrollView(mActivity);
@@ -793,8 +770,7 @@
     /**
      * Update header views in the Feed.
      */
-    void updateHeaderViews(
-            boolean isSignInPromoVisible, @Nullable View enhancedProtectionPromoView) {
+    void updateHeaderViews(boolean isSignInPromoVisible) {
         if (mStream == null) return;
 
         List<View> headers = new ArrayList<>();
@@ -803,11 +779,6 @@
             headers.add(mNtpHeader);
         }
 
-        if (enhancedProtectionPromoView != null) {
-            mEnhancedProtectionPromoView = enhancedProtectionPromoView;
-            headers.add(enhancedProtectionPromoView);
-        }
-
         headers.add(mSectionHeaderView);
 
         if (isSignInPromoVisible) {
@@ -816,14 +787,6 @@
         setHeaders(headers);
     }
 
-    EnhancedProtectionPromoController getEnhancedProtectionPromoController() {
-        return mEnhancedProtectionPromoController;
-    }
-
-    private boolean isEnhancedProtectionPromoEnabled() {
-        return ChromeFeatureList.isEnabled(ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD);
-    }
-
     @VisibleForTesting
     public FeedSurfaceMediator getMediatorForTesting() {
         return mMediator;
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 ba9546d..48e3eaac 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
@@ -37,7 +37,6 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.ntp.NewTabPageLaunchOrigin;
 import org.chromium.chrome.browser.ntp.cards.SignInPromo;
-import org.chromium.chrome.browser.ntp.cards.promo.enhanced_protection.EnhancedProtectionPromoController.EnhancedProtectionPromoStateListener;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.preferences.PrefChangeRegistrar;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -77,8 +76,7 @@
 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
 public class FeedSurfaceMediator
         implements FeedSurfaceScrollDelegate, TouchEnabledDelegate, TemplateUrlServiceObserver,
-                   ListMenu.Delegate, EnhancedProtectionPromoStateListener,
-                   IdentityManager.Observer {
+                   ListMenu.Delegate, IdentityManager.Observer {
     private static final String TAG = "FeedSurfaceMediator";
     private static final int INTEREST_FEED_HEADER_POSITION = 0;
 
@@ -144,7 +142,7 @@
             if (isVisible() == visible) return;
 
             super.setVisibilityInternal(visible);
-            mCoordinator.updateHeaderViews(visible, null);
+            mCoordinator.updateHeaderViews(visible);
             maybeUpdateSignInPromo();
         }
 
@@ -169,7 +167,7 @@
         @Override
         public void onDismissPromo() {
             super.onDismissPromo();
-            mCoordinator.updateHeaderViews(false, null);
+            mCoordinator.updateHeaderViews(false);
         }
     }
 
@@ -197,7 +195,6 @@
     private @Nullable SignInPromo mSignInPromo;
     private RecyclerViewAnimationFinishDetector mRecyclerViewAnimationFinishDetector =
             new RecyclerViewAnimationFinishDetector();
-    private @Nullable View mEnhancedProtectionPromo;
 
     private boolean mFeedEnabled;
     private boolean mTouchEnabled = true;
@@ -601,12 +598,7 @@
 
     private void initStreamHeaderViews() {
         boolean signInPromoVisible = createSignInPromoIfNeeded();
-        mEnhancedProtectionPromo = null;
-        if (!signInPromoVisible) {
-            mEnhancedProtectionPromo = createEnhancedProtectionPromoIfNeeded();
-        }
-        // We are not going to show two promos at the same time.
-        mCoordinator.updateHeaderViews(signInPromoVisible, mEnhancedProtectionPromo);
+        mCoordinator.updateHeaderViews(signInPromoVisible);
     }
 
     /**
@@ -627,19 +619,6 @@
         return mSignInPromo.isVisible();
     }
 
-    private View createEnhancedProtectionPromoIfNeeded() {
-        if (mCoordinator.getEnhancedProtectionPromoController() == null) return null;
-
-        View enhancedProtectionPromoView =
-                mCoordinator.getEnhancedProtectionPromoController().getPromoView();
-        if (enhancedProtectionPromoView != null) {
-            mCoordinator.getEnhancedProtectionPromoController()
-                    .setEnhancedProtectionPromoStateListener(this);
-            updatePromoCardPadding(enhancedProtectionPromoView);
-        }
-        return enhancedProtectionPromoView;
-    }
-
     private void updatePromoCardPadding(View promoCard) {
         MarginLayoutParams layoutParams = promoCard.getLayoutParams() == null
                 ? new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
@@ -765,9 +744,6 @@
         if (mSignInPromo != null) {
             mSignInPromo.setCanShowPersonalizedSuggestions(suggestionsVisible);
         }
-        if (mEnhancedProtectionPromo != null) {
-            updatePromoCardPadding(mEnhancedProtectionPromo);
-        }
         if (suggestionsVisible) mCoordinator.getSurfaceLifecycleManager().show();
         mStreamContentChanged = true;
 
@@ -1065,12 +1041,6 @@
         }
     }
 
-    @Override
-    public void onEnhancedProtectionPromoStateChange() {
-        // If the enhanced protection promo has been dismissed, delete it.
-        mCoordinator.updateHeaderViews(false, null);
-    }
-
     // IdentityManager.Observer interface.
 
     @Override
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java
index 8abc3a4..b791bf9 100644
--- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java
+++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java
@@ -111,8 +111,6 @@
 @CommandLineFlags.
 Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "disable-features=IPH_FeedHeaderMenu"})
 @Features.EnableFeatures(ChromeFeatureList.INTEREST_FEED_V2)
-@Features.
-DisableFeatures({ChromeFeatureList.QUERY_TILES, ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD})
 public class FeedV2NewTabPageTest {
     private static final int ARTICLE_SECTION_HEADER_POSITION = 1;
     private static final int SIGNIN_PROMO_POSITION = 2;
diff --git a/chrome/android/java/res/values/ids.xml b/chrome/android/java/res/values/ids.xml
index 48fafb5..65fe425f0e 100644
--- a/chrome/android/java/res/values/ids.xml
+++ b/chrome/android/java/res/values/ids.xml
@@ -108,9 +108,6 @@
     <item type="id" name="ntp_feed_header_menu_item_learn" />
     <item type="id" name="ntp_feed_header_menu_item_toggle_switch" />
 
-    <!-- Enhanced Protection Promo -->
-    <item type="id" name="enhanced_protection_promo" />
-
     <!-- Custom Tabs -->
     <item type="id" name="view_id_tag_key" />
 </resources>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/DIR_METADATA b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/DIR_METADATA
deleted file mode 100644
index d4a02b62..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/DIR_METADATA
+++ /dev/null
@@ -1,4 +0,0 @@
-monorail {
-  component: "Services>Safebrowsing"
-}
-team_email: "safebrowsing@chromium.org"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoController.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoController.java
deleted file mode 100644
index 7667d48..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoController.java
+++ /dev/null
@@ -1,186 +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.ntp.cards.promo.enhanced_protection;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-import androidx.appcompat.content.res.AppCompatResources;
-
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ntp.cards.promo.enhanced_protection.EnhancedProtectionPromoUtils.EnhancedProtectionPromoAction;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.safe_browsing.metrics.SettingsAccessPoint;
-import org.chromium.chrome.browser.safe_browsing.settings.SafeBrowsingSettingsFragment;
-import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
-import org.chromium.components.browser_ui.settings.SettingsLauncher;
-import org.chromium.components.browser_ui.widget.promo.PromoCardCoordinator;
-import org.chromium.components.browser_ui.widget.promo.PromoCardProperties;
-import org.chromium.ui.modelutil.PropertyModel;
-
-/**
- * Controller for the Enhanced Protection promo component.
- * The logic for creating and managing the promo is relatively simple, so this class merges the duty
- * of mediator and coordinator.
- */
-public class EnhancedProtectionPromoController {
-    /**
-     * Interface that represents the holder of a Enhanced Protection Promo. When the promo has state
-     * changes due to enhanced protection changes / being dismissed, {@link
-     * #onEnhancedProtectionPromoDataChange()} will be called.
-     */
-    public interface EnhancedProtectionPromoStateListener {
-        /**
-         * Called when promo has state changes due to enhanced protection pref changes / being
-         * dismissed.
-         */
-        void onEnhancedProtectionPromoStateChange();
-    }
-
-    private final Context mContext;
-    private final @Nullable Profile mProfile;
-
-    // Created only when creation criteria is met.
-    private EnhancedProtectionPromoStateListener mStateListener;
-    private PromoCardCoordinator mPromoCoordinator;
-    private PropertyModel mModel;
-    private boolean mIsPromoShowing;
-
-    /**
-     * Build the EnhancedProtectionPromoController that handles the set up / tear down for the
-     * enhanced protection promo.
-     * @param context Context from the activity.
-     * @param profile Current user profile.
-     */
-    public EnhancedProtectionPromoController(Context context, @Nullable Profile profile) {
-        mContext = context;
-        mProfile = profile;
-    }
-
-    /**
-     * @param listener Listener to be notified when the promo should be removed from the parent
-     *         view.
-     */
-    public void setEnhancedProtectionPromoStateListener(
-            EnhancedProtectionPromoStateListener listener) {
-        mStateListener = listener;
-    }
-
-    /**
-     * Retrieve the view representing EnhancedProtectionPromo.
-     *
-     * Internally, this function will check the creation criteria from SharedPreference. If the
-     * creation criteria is not met, this function will return null; otherwise, promo controller
-     * will create the view lazily if not yet created, and return.
-     *
-     * Note that the same View will be returned until the promo is dismissed by internal or external
-     * signal.
-     *
-     * @return View represent EnhancedProtectionPromo if creation criteria is meet; null If the
-     *         criteria is not meet.
-     */
-    public @Nullable View getPromoView() {
-        if (mIsPromoShowing || EnhancedProtectionPromoUtils.shouldCreatePromo(mProfile)) {
-            mIsPromoShowing = true;
-            return getPromoCoordinator().getView();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Destroy the EnhancedProtectionPromo and release its dependencies.
-     */
-    public void destroy() {
-        mStateListener = null;
-
-        // Early return if promo coordinator is not initialized.
-        if (mPromoCoordinator == null) return;
-
-        mPromoCoordinator.destroy();
-
-        // Update the listener if the promo is shown and not yet dismissed.
-        if (mIsPromoShowing) dismissPromoInternal();
-    }
-
-    /**
-     * @return The PromoCardCoordinator for enhanced protection promo. If the coordinator is not
-     *         created, create it lazily.
-     */
-    private PromoCardCoordinator getPromoCoordinator() {
-        if (mPromoCoordinator != null) return mPromoCoordinator;
-
-        mModel = buildModel();
-
-        mPromoCoordinator = new PromoCardCoordinator(mContext, mModel,
-                EnhancedProtectionPromoUtils.ENHANCED_PROTECTION_PROMO_CARD_FEATURE);
-
-        mPromoCoordinator.getView().setId(R.id.enhanced_protection_promo);
-
-        EnhancedProtectionPromoUtils.recordEnhancedProtectionPromoEvent(
-                EnhancedProtectionPromoAction.CREATED);
-
-        return mPromoCoordinator;
-    }
-
-    private PropertyModel buildModel() {
-        Resources r = mContext.getResources();
-        Drawable securityIcon =
-                AppCompatResources.getDrawable(mContext, R.drawable.ic_security_grey);
-        ColorStateList tint = AppCompatResources.getColorStateList(
-                mContext, R.color.default_icon_color_accent1_tint_list);
-
-        PropertyModel.Builder builder = new PropertyModel.Builder(PromoCardProperties.ALL_KEYS);
-        builder.with(PromoCardProperties.PRIMARY_BUTTON_CALLBACK, (v) -> onPrimaryButtonClicked())
-                .with(PromoCardProperties.IMPRESSION_SEEN_CALLBACK, this::onPromoSeen)
-                .with(PromoCardProperties.IS_IMPRESSION_ON_PRIMARY_BUTTON, true)
-                .with(PromoCardProperties.IMAGE, securityIcon)
-                .with(PromoCardProperties.ICON_TINT, tint)
-                .with(PromoCardProperties.TITLE,
-                        r.getString(R.string.enhanced_protection_promo_title))
-                .with(PromoCardProperties.DESCRIPTION,
-                        r.getString(R.string.enhanced_protection_promo_description))
-                .with(PromoCardProperties.PRIMARY_BUTTON_TEXT,
-                        r.getString(R.string.continue_button))
-                .with(PromoCardProperties.HAS_SECONDARY_BUTTON, true)
-                .with(PromoCardProperties.SECONDARY_BUTTON_TEXT, r.getString(R.string.no_thanks))
-                .with(PromoCardProperties.SECONDARY_BUTTON_CALLBACK, (v) -> dismissPromo())
-                .build();
-        return builder.build();
-    }
-
-    /**
-     * Dismissed the promo and record the user action.
-     */
-    private void dismissPromo() {
-        EnhancedProtectionPromoUtils.setPromoDismissedInSharedPreference(true);
-        EnhancedProtectionPromoUtils.recordEnhancedProtectionPromoEvent(
-                EnhancedProtectionPromoAction.DISMISSED);
-        dismissPromoInternal();
-    }
-
-    private void dismissPromoInternal() {
-        mIsPromoShowing = false;
-        if (mStateListener != null) mStateListener.onEnhancedProtectionPromoStateChange();
-    }
-
-    private void onPrimaryButtonClicked() {
-        EnhancedProtectionPromoUtils.recordEnhancedProtectionPromoEvent(
-                EnhancedProtectionPromoAction.ACCEPTED);
-        SettingsLauncher launcher = new SettingsLauncherImpl();
-        launcher.launchSettingsActivity(mContext, SafeBrowsingSettingsFragment.class,
-                SafeBrowsingSettingsFragment.createArguments(
-                        SettingsAccessPoint.SURFACE_EXPLORER_PROMO_SLINGER));
-    }
-
-    private void onPromoSeen() {
-        EnhancedProtectionPromoUtils.recordEnhancedProtectionPromoEvent(
-                EnhancedProtectionPromoAction.SEEN);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoUtils.java
deleted file mode 100644
index 347b0ab1..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoUtils.java
+++ /dev/null
@@ -1,114 +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.ntp.cards.promo.enhanced_protection;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
-
-import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.metrics.RecordUserAction;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
-import org.chromium.chrome.browser.preferences.Pref;
-import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.safe_browsing.SafeBrowsingBridge;
-import org.chromium.components.user_prefs.UserPrefs;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Enhanced Protection promo related utils class.
- */
-final class EnhancedProtectionPromoUtils {
-    /**
-     * Possible user actions associated with homepage promo. Used for Histogram
-     * "NewTabPage.Promo.EnhancedProtectionPromo" recorded in {@link
-     * #recordEnhancedProtectionPromoEvent(int)}.
-     *
-     * These values are corresponded with enum "AndroidEnhancedProtectionPromoAction" and persisted
-     * to logs, therefore should be treated as append only.
-     */
-    @IntDef({EnhancedProtectionPromoAction.CREATED, EnhancedProtectionPromoAction.SEEN,
-            EnhancedProtectionPromoAction.DISMISSED, EnhancedProtectionPromoAction.ACCEPTED})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface EnhancedProtectionPromoAction {
-        int CREATED = 0;
-        int SEEN = 1;
-        int DISMISSED = 2;
-        int ACCEPTED = 3;
-        int TOTAL = 4;
-    }
-
-    // Suffix for PROMO_IS_DISMISSED and PROMO_TIMES_SEEN Chrome preference keys.
-    public static final String ENHANCED_PROTECTION_PROMO_CARD_FEATURE =
-            "EnhancedProtectionPromoCard";
-    private static final int DEFAULT_MAX_IMPRESSION_SEEN = 22;
-
-    // Do not instantiate.
-    private EnhancedProtectionPromoUtils() {}
-
-    /**
-     * @return True if EnhancedProtectionPromo has not yet been dismissed and if the user has not
-     *         selected Enhanced Protection and if the user has not seen the promos too many times
-     *         and if Safe Browsing is not managed.
-     */
-    static boolean shouldCreatePromo(@Nullable Profile profile) {
-        String timesSeenKey = getTimesSeenKey();
-        int timesSeen = SharedPreferencesManager.getInstance().readInt(timesSeenKey, 0);
-        int maxImpressions = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
-                ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD,
-                "MaxEnhancedProtectionPromoImpressions", DEFAULT_MAX_IMPRESSION_SEEN);
-        // TODO(bdea): If the user has pressed "Continue" and not selected Enhanced Protection,
-        // should we still show the promo.
-        return (profile != null) && !UserPrefs.get(profile).getBoolean(Pref.SAFE_BROWSING_ENHANCED)
-                && !isPromoDismissedInSharedPreference() && (timesSeen <= maxImpressions)
-                && !SafeBrowsingBridge.isSafeBrowsingManaged();
-    }
-
-    static boolean isPromoDismissedInSharedPreference() {
-        return SharedPreferencesManager.getInstance().readBoolean(getDismissedKey(), false);
-    }
-
-    static void setPromoDismissedInSharedPreference(boolean isDismissed) {
-        SharedPreferencesManager.getInstance().writeBoolean(getDismissedKey(), isDismissed);
-    }
-
-    static String getDismissedKey() {
-        return ChromePreferenceKeys.PROMO_IS_DISMISSED.createKey(
-                ENHANCED_PROTECTION_PROMO_CARD_FEATURE);
-    }
-
-    static String getTimesSeenKey() {
-        return ChromePreferenceKeys.PROMO_TIMES_SEEN.createKey(
-                ENHANCED_PROTECTION_PROMO_CARD_FEATURE);
-    }
-
-    static void recordEnhancedProtectionPromoEvent(@EnhancedProtectionPromoAction int action) {
-        RecordHistogram.recordEnumeratedHistogram("NewTabPage.Promo.EnhancedProtectionPromo",
-                action, EnhancedProtectionPromoAction.TOTAL);
-
-        if (action == EnhancedProtectionPromoAction.CREATED) return;
-        int maxImpressions = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
-                ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD,
-                "MaxEnhancedProtectionPromoImpressions", DEFAULT_MAX_IMPRESSION_SEEN);
-        String timesSeenKey = getTimesSeenKey();
-        int timesSeen = SharedPreferencesManager.getInstance().readInt(timesSeenKey, 0);
-        if (action == EnhancedProtectionPromoAction.SEEN) {
-            SharedPreferencesManager.getInstance().writeInt(timesSeenKey, timesSeen + 1);
-        } else if (action == EnhancedProtectionPromoAction.ACCEPTED) {
-            RecordUserAction.record("NewTabPage.Promo.EnhancedProtectionPromo.Accepted");
-            RecordHistogram.recordLinearCountHistogram(
-                    "NewTabPage.Promo.EnhancedProtectionPromo.ImpressionUntilAction", timesSeen, 1,
-                    maxImpressions, maxImpressions + 1);
-        } else if (action == EnhancedProtectionPromoAction.DISMISSED) {
-            RecordUserAction.record("NewTabPage.Promo.EnhancedProtectionPromo.Dismissed");
-            RecordHistogram.recordLinearCountHistogram(
-                    "NewTabPage.Promo.EnhancedProtectionPromo.ImpressionUntilDismissal", timesSeen,
-                    1, maxImpressions, maxImpressions + 1);
-        }
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/OWNERS
deleted file mode 100644
index eac9d323..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-bdea@chromium.org
-xinghuilu@chromium.org
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkTest.java
index 7f4953d..4be9638e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkTest.java
@@ -31,6 +31,7 @@
 import org.chromium.base.test.params.ParameterAnnotations;
 import org.chromium.base.test.params.ParameterSet;
 import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -180,6 +181,7 @@
     @Test
     @MediumTest
     @Feature({"RenderTest"})
+    @DisabledTest(message = "crbug.com/1282173")
     public void testShoppingRebindUI() throws IOException {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mPowerBookmarkShoppingItemRow.initPriceTrackingUI("http://foo.com/img", false,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
index 00acf05b..aa6d6052 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
@@ -184,6 +184,7 @@
     @Test
     @MediumTest
     @Feature({"Fullscreen"})
+    @DisabledTest(message = "crbug.com/1282137")
     public void testDelayedPersistentFullscreen() {
         mActivityTestRule.startMainActivityWithURL(LONG_HTML_TEST_PAGE);
 
@@ -215,6 +216,7 @@
     @Test
     @LargeTest
     @Feature({"Fullscreen"})
+    @DisabledTest(message = "crbug.com/1282137")
     public void testPersistentFullscreenChangingUiFlags() throws InterruptedException {
         // Exiting fullscreen via UI Flags is not supported in versions prior to MR2.
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return;
@@ -540,6 +542,7 @@
     @Test
     @LargeTest
     @Feature({"Fullscreen"})
+    @DisabledTest(message = "crbug.com/1282137")
     public void testEnterPendingPersistentFullscreen() {
         FullscreenManagerTestUtils.disableBrowserOverrides();
         mActivityTestRule.startMainActivityWithURL(LONG_FULLSCREEN_API_HTML_TEST_PAGE);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageColorWithFeedV2Test.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageColorWithFeedV2Test.java
index 6d506fe6..44c4eaf 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageColorWithFeedV2Test.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageColorWithFeedV2Test.java
@@ -86,8 +86,6 @@
     @MediumTest
     @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
     @Feature({"NewTabPage", "FeedNewTabPage"})
-    @Features.DisableFeatures({
-            ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD})
     public void testTextBoxBackgroundColor() throws Exception {
         // clang-format on
         RecyclerView recycleView = (RecyclerView) mNtp.getCoordinatorForTesting().getRecyclerView();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoTest.java
deleted file mode 100644
index 29539586..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/cards/promo/enhanced_protection/EnhancedProtectionPromoTest.java
+++ /dev/null
@@ -1,228 +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.ntp.cards.promo.enhanced_protection;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.intent.Intents.intended;
-import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent;
-import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-
-import static org.hamcrest.Matchers.allOf;
-
-import static org.chromium.ui.test.util.ViewUtils.waitForView;
-
-import android.content.Intent;
-import android.os.Build;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.test.espresso.contrib.RecyclerViewActions;
-import androidx.test.espresso.intent.Intents;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SmallTest;
-
-import org.hamcrest.Matcher;
-import org.hamcrest.Matchers;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
-
-import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.Criteria;
-import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
-import org.chromium.base.test.util.UserActionTester;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.browser.ntp.cards.SignInPromo;
-import org.chromium.chrome.browser.ntp.cards.promo.enhanced_protection.EnhancedProtectionPromoUtils.EnhancedProtectionPromoAction;
-import org.chromium.chrome.browser.preferences.Pref;
-import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.safe_browsing.settings.SafeBrowsingSettingsFragment;
-import org.chromium.chrome.browser.settings.SettingsActivity;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.chrome.test.util.NewTabPageTestUtils;
-import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
-import org.chromium.components.embedder_support.util.UrlConstants;
-import org.chromium.components.user_prefs.UserPrefs;
-import org.chromium.content_public.browser.test.util.TestThreadUtils;
-
-/**
- * Unit test for {@link EnhancedProtectionPromoController}
- */
-@RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@EnableFeatures({ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD})
-@Features.DisableFeatures({ChromeFeatureList.QUERY_TILES})
-public class EnhancedProtectionPromoTest {
-    private static final String METRICS_ENHANCED_PROTECTION_PROMO =
-            "NewTabPage.Promo.EnhancedProtectionPromo";
-    private static final String METRICS_ENHANCED_PROTECTION_PROMO_IMPRESSION_ACTION =
-            "NewTabPage.Promo.EnhancedProtectionPromo.ImpressionUntilAction";
-    private static final String METRICS_ENHANCED_PROTECTION_PROMO_IMPRESSION_DISMISSAL =
-            "NewTabPage.Promo.EnhancedProtectionPromo.ImpressionUntilDismissal";
-    private static final int NTP_HEADER_POSITION = 0;
-
-    private boolean mHasEnhancedProtectionPromoDismissed; // Test value before the test.
-    private UserActionTester mActionTester;
-
-    @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mActivityTestRule.startMainActivityOnBlankPage();
-
-        // Set promo not dismissed.
-        mHasEnhancedProtectionPromoDismissed =
-                EnhancedProtectionPromoUtils.isPromoDismissedInSharedPreference();
-        EnhancedProtectionPromoUtils.setPromoDismissedInSharedPreference(false);
-
-        SignInPromo.setDisablePromoForTests(true);
-    }
-
-    @After
-    public void tearDown() {
-        EnhancedProtectionPromoUtils.setPromoDismissedInSharedPreference(
-                mHasEnhancedProtectionPromoDismissed);
-        if (mActionTester != null) mActionTester.tearDown();
-        SignInPromo.setDisablePromoForTests(false);
-    }
-
-    /**
-     * Test that the enhanced protection promo should show for users not signed up for enhanced
-     * protection
-     */
-    @Test
-    @SmallTest
-    @DisableIf.Build(message = "Flaky on Android Lollipop, see crbug.com/1199125",
-            sdk_is_less_than = Build.VERSION_CODES.M)
-    public void
-    testSetUp_Basic() {
-        mActivityTestRule.loadUrl(UrlConstants.NTP_URL);
-
-        View enhancedProtectionPromo =
-                mActivityTestRule.getActivity().findViewById(R.id.enhanced_protection_promo);
-        Assert.assertNotNull(
-                "Enhanced Protection promo should be added to NTP.", enhancedProtectionPromo);
-        Assert.assertEquals("Enhanced Protection promo should be visible.", View.VISIBLE,
-                enhancedProtectionPromo.getVisibility());
-
-        Assert.assertEquals("Promo created should be recorded once. ", 1,
-                RecordHistogram.getHistogramValueCountForTesting(
-                        METRICS_ENHANCED_PROTECTION_PROMO, EnhancedProtectionPromoAction.CREATED));
-    }
-
-    @Test
-    @MediumTest
-    public void testSetUp_NoShow() {
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            UserPrefs.get(Profile.getLastUsedRegularProfile())
-                    .setBoolean(Pref.SAFE_BROWSING_ENHANCED, true);
-        });
-        mActivityTestRule.loadUrl(UrlConstants.NTP_URL);
-
-        View enhancedProtectionPromo =
-                mActivityTestRule.getActivity().findViewById(R.id.enhanced_protection_promo);
-        Assert.assertNull(
-                "Enhanced Protection promo should not be added to NTP.", enhancedProtectionPromo);
-    }
-
-    @Test
-    @MediumTest
-    public void testAcceptImpl() {
-        mActivityTestRule.loadUrl(UrlConstants.NTP_URL);
-        Intents.init();
-
-        scrollToEnhancedProtectionPromo();
-
-        onView(withId(R.id.promo_primary_button)).perform(click());
-
-        Matcher<Intent> isCorrectComponent = hasComponent(SettingsActivity.class.getName());
-        Matcher<Intent> isCorrectFragment =
-                hasExtra("show_fragment", SafeBrowsingSettingsFragment.class.getName());
-        intended(allOf(isCorrectComponent, isCorrectFragment));
-
-        Assert.assertEquals("Promo accepted should be recorded once. ", 1,
-                RecordHistogram.getHistogramValueCountForTesting(
-                        METRICS_ENHANCED_PROTECTION_PROMO, EnhancedProtectionPromoAction.ACCEPTED));
-        Assert.assertEquals("Promo impression until action should be recorded once.", 1,
-                RecordHistogram.getHistogramTotalCountForTesting(
-                        METRICS_ENHANCED_PROTECTION_PROMO_IMPRESSION_ACTION));
-        Intents.release();
-    }
-
-    @Test
-    @MediumTest
-    public void testDismissImpl() {
-        mActionTester = new UserActionTester();
-        mActivityTestRule.loadUrl(UrlConstants.NTP_URL);
-
-        scrollToEnhancedProtectionPromo();
-
-        onView(withId(R.id.promo_secondary_button)).perform(click());
-        Assert.assertNull("Enhanced Protection promo should be removed after dismissed.",
-                mActivityTestRule.getActivity().findViewById(R.id.enhanced_protection_promo));
-        Assert.assertEquals("Promo dismissed should be recorded once. ", 1,
-                RecordHistogram.getHistogramValueCountForTesting(METRICS_ENHANCED_PROTECTION_PROMO,
-                        EnhancedProtectionPromoAction.DISMISSED));
-        Assert.assertEquals("Promo impression until dismissed should be recorded once.", 1,
-                RecordHistogram.getHistogramTotalCountForTesting(
-                        METRICS_ENHANCED_PROTECTION_PROMO_IMPRESSION_DISMISSAL));
-        boolean action_tracked = false;
-        for (String action : mActionTester.getActions()) {
-            if (action.startsWith("NewTabPage.Promo.EnhancedProtectionPromo.Dismissed")) {
-                action_tracked = true;
-            }
-        }
-        Assert.assertTrue(action_tracked);
-
-        // Load to NTP one more time. The promo should not show.
-        mActivityTestRule.loadUrlInNewTab("about:blank");
-        launchNewTabPage();
-        Assert.assertNull("Enhanced Protection promo should not be added to NTP.",
-                mActivityTestRule.getActivity().findViewById(R.id.enhanced_protection_promo));
-    }
-
-    private void scrollToEnhancedProtectionPromo() {
-        onView(withId(R.id.feed_stream_recycler_view))
-                .perform(RecyclerViewActions.scrollToPosition(NTP_HEADER_POSITION + 1));
-        waitForView((ViewGroup) mActivityTestRule.getActivity().findViewById(
-                            R.id.enhanced_protection_promo),
-                allOf(withId(R.id.promo_primary_button), isDisplayed()));
-
-        CriteriaHelper.pollUiThread(() -> {
-            // Verify impression tracking metrics is working.
-            Criteria.checkThat("Promo created should be seen.",
-                    RecordHistogram.getHistogramValueCountForTesting(
-                            METRICS_ENHANCED_PROTECTION_PROMO, EnhancedProtectionPromoAction.SEEN),
-                    Matchers.is(1));
-            Criteria.checkThat("Impression should be tracked in shared preference.",
-                    SharedPreferencesManager.getInstance().readInt(
-                            EnhancedProtectionPromoUtils.getTimesSeenKey()),
-                    Matchers.is(1));
-        });
-    }
-
-    private void launchNewTabPage() {
-        mActivityTestRule.loadUrl(UrlConstants.NTP_URL);
-        Tab tab = mActivityTestRule.getActivity().getActivityTab();
-        NewTabPageTestUtils.waitForNtpLoaded(tab);
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
index b2a1b1f..dbc6d33 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
@@ -75,12 +75,10 @@
 /**
  * Tests for {@link FeedSurfaceCoordinator}.
  *
- * EnhancedProtectionPromoCard does not need to be disabled. Its value just need to be set.
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
-@Features.DisableFeatures({ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD,
-        ChromeFeatureList.WEB_FEED, ChromeFeatureList.INTEREST_FEED_V2_AUTOPLAY,
+@Features.DisableFeatures({ChromeFeatureList.WEB_FEED, ChromeFeatureList.INTEREST_FEED_V2_AUTOPLAY,
         ChromeFeatureList.FEED_INTERACTIVE_REFRESH, ChromeFeatureList.FEED_BACK_TO_TOP})
 @Features.EnableFeatures({ChromeFeatureList.FEED_RELIABILITY_LOGGING})
 public class FeedSurfaceCoordinatorTest {
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 5c796b05..fa944b9 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -2064,6 +2064,9 @@
   <message name="IDS_SETTINGS_SITE_SETTINGS_ALL_SITES_SORT_METHOD_NAME" desc="A selection menu option to sort the All Sites list by the name of the site, which is derived from its URL." meaning="Name of an inanimate object">
     Name
   </message>
+  <message name="IDS_SETTINGS_SITE_SETTINGS_SITE_ENTRY_PARTITIONED_LABEL" desc="The label which is applied to site entries which are partitioned. These entries represent data whose access scope is limited to the site the labeled entry appears under">
+    Partitioned
+  </message>
   <message name="IDS_SETTINGS_SITE_SETTINGS_SITE_REPRESENTATION_SEPARATOR" desc="The separator used to split up the display of a URL into host and scheme/protocol, when the scheme is not HTTPS. For example, it will be used in a string that looks like 'google.co.uk — http'.">

   </message>
@@ -3060,6 +3063,9 @@
   <message name="IDS_SETTINGS_SITE_SETTINGS_REMOVE_SITE_ORIGIN_APP_DIALOG_TITLE" desc="Title of the confirmation dialog when a user selects to remove a specific origin, and that origin also has an installed web app">
     Clear site data and permissions for <ph name="SITE_NAME">$1<ex>www.example.com</ex></ph> and its installed app?
   </message>
+  <message name="IDS_SETTINGS_SITE_SETTINGS_REMOVE_SITE_ORIGIN_PARTITIONED_DIALOG_TITLE" desc="Title of the confirmation dialog when a user selects to remove a specific origins partitioned data. That data will be partitioned on a specific eTLD +1.">
+    Clear site data for <ph name="SITE_NAME">$1<ex>www.ad-tech.com</ex></ph> partitioned on <ph name="PARTITION_SITE_NAME">$2<ex>www.publisher.com</ex></ph>?
+  </message>
   <message name="IDS_SETTINGS_SITE_SETTINGS_REMOVE_SITE_GROUP_DIALOG_TITLE" desc="Title of the confirmation dialog when a user selects to remove a group of sites">
     Clear site data and permissions for <ph name="SITE_NAME">$1<ex>www.example.com</ex></ph> and all sites under it?
   </message>
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_REMOVE_SITE_ORIGIN_PARTITIONED_DIALOG_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_REMOVE_SITE_ORIGIN_PARTITIONED_DIALOG_TITLE.png.sha1
new file mode 100644
index 0000000..2bd5abbb
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_REMOVE_SITE_ORIGIN_PARTITIONED_DIALOG_TITLE.png.sha1
@@ -0,0 +1 @@
+adb7988a3fbbe3c83bae573f61b96430bbc2189a
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_SITE_ENTRY_PARTITIONED_LABEL.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_SITE_ENTRY_PARTITIONED_LABEL.png.sha1
new file mode 100644
index 0000000..c703650
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_SITE_SETTINGS_SITE_ENTRY_PARTITIONED_LABEL.png.sha1
@@ -0,0 +1 @@
+2bcabfbd00b7c37b0ad4b256ef4ffaa1c386fdc6
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index e0b9da01..37fd6b5 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4595,6 +4595,8 @@
       "apps/app_service/metrics/app_platform_metrics_service.h",
       "apps/app_service/metrics/app_platform_metrics_utils.cc",
       "apps/app_service/metrics/app_platform_metrics_utils.h",
+      "apps/app_service/metrics/browser_to_tab_list.cc",
+      "apps/app_service/metrics/browser_to_tab_list.h",
       "apps/app_service/publishers/arc_apps.cc",
       "apps/app_service/publishers/arc_apps.h",
       "apps/app_service/publishers/arc_apps_factory.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index ed4ab2c..ccdf7bee 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1931,24 +1931,6 @@
     {"60 minutes", kConditionalTabStripAndroid_60Minutes,
      base::size(kConditionalTabStripAndroid_60Minutes), nullptr},
 };
-
-const FeatureEntry::FeatureParam
-    kEnhancedProtectionPromoCard_SigninPromoImpressions_Count1[] = {
-        {"MaxSigninPromoImpressions", "1"}};
-const FeatureEntry::FeatureParam
-    kEnhancedProtectionPromoCard_SigninPromoImpressions_Count5[] = {
-        {"MaxSigninPromoImpressions", "5"}};
-const FeatureEntry::FeatureVariation kEnhancedProtectionPromoCardVariations[] =
-    {
-        {"Max Impressions 1",
-         kEnhancedProtectionPromoCard_SigninPromoImpressions_Count1,
-         base::size(kEnhancedProtectionPromoCard_SigninPromoImpressions_Count1),
-         nullptr},
-        {"Max Impressions 5",
-         kEnhancedProtectionPromoCard_SigninPromoImpressions_Count5,
-         base::size(kEnhancedProtectionPromoCard_SigninPromoImpressions_Count5),
-         nullptr},
-};
 #endif  // OS_ANDROID
 
 #if defined(OS_ANDROID)
@@ -5594,6 +5576,10 @@
      flag_descriptions::kArcGhostWindowDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(full_restore::features::kArcGhostWindow)},
 
+    {"arc-window-predictor", flag_descriptions::kArcWindowPredictorName,
+     flag_descriptions::kArcWindowPredictorDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(full_restore::features::kArcWindowPredictor)},
+
     {"arc-input-overlay", flag_descriptions::kArcInputOverlayName,
      flag_descriptions::kArcInputOverlayDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kArcInputOverlay)},
@@ -5907,14 +5893,6 @@
      FEATURE_VALUE_TYPE(features::kSearchHistoryLink)},
 
 #if defined(OS_ANDROID)
-    {"safe-browsing-enhanced-protection-promo-android",
-     flag_descriptions::kEnhancedProtectionPromoAndroidName,
-     flag_descriptions::kEnhancedProtectionPromoAndroidDescription, kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         chrome::android::kEnhancedProtectionPromoCard,
-         kEnhancedProtectionPromoCardVariations,
-         "EnhancedProtectionPromoCard")},
-
     {"safe-browsing-password-protection-for-signed-in-users",
      flag_descriptions::kPasswordProtectionForSignedInUsersName,
      flag_descriptions::kPasswordProtectionForSignedInUsersDescription,
diff --git a/chrome/browser/accessibility/accessibility_extension_api_chromeos.cc b/chrome/browser/accessibility/accessibility_extension_api_chromeos.cc
index 86560da..b4d7a75 100644
--- a/chrome/browser/accessibility/accessibility_extension_api_chromeos.cc
+++ b/chrome/browser/accessibility/accessibility_extension_api_chromeos.cc
@@ -253,9 +253,30 @@
     EXTENSION_FUNCTION_VALIDATE(args().size() >= 1);
     EXTENSION_FUNCTION_VALIDATE(args()[0].is_bool());
     bool enabled = args()[0].GetBool();
-    bridge->SetNativeChromeVoxArcSupport(enabled);
+    bridge->SetNativeChromeVoxArcSupport(
+        enabled,
+        base::BindOnce(
+            &AccessibilityPrivateSetNativeChromeVoxArcSupportForCurrentAppFunction::
+                OnResponse,
+            this));
+    return did_respond() ? AlreadyResponded() : RespondLater();
   }
-  return RespondNow(NoArguments());
+  return RespondNow(ArgumentList(
+      extensions::api::accessibility_private::
+          SetNativeChromeVoxArcSupportForCurrentApp::Results::Create(
+              extensions::api::accessibility_private::
+                  SetNativeChromeVoxResponse::
+                      SET_NATIVE_CHROME_VOX_RESPONSE_FAILURE)));
+}
+
+void AccessibilityPrivateSetNativeChromeVoxArcSupportForCurrentAppFunction::
+    OnResponse(
+        extensions::api::accessibility_private::SetNativeChromeVoxResponse
+            response) {
+  Respond(ArgumentList(
+      extensions::api::accessibility_private::
+          SetNativeChromeVoxArcSupportForCurrentApp::Results::Create(
+              response)));
 }
 
 ExtensionFunction::ResponseAction
diff --git a/chrome/browser/accessibility/accessibility_extension_api_chromeos.h b/chrome/browser/accessibility/accessibility_extension_api_chromeos.h
index 2182f3d..9cc9adc 100644
--- a/chrome/browser/accessibility/accessibility_extension_api_chromeos.h
+++ b/chrome/browser/accessibility/accessibility_extension_api_chromeos.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_ACCESSIBILITY_ACCESSIBILITY_EXTENSION_API_CHROMEOS_H_
 
 #include "build/chromeos_buildflags.h"
+#include "chrome/common/extensions/api/accessibility_private.h"
 #include "extensions/browser/extension_function.h"
 
 // API function that enables or disables web content accessibility support.
@@ -66,6 +67,9 @@
   ~AccessibilityPrivateSetNativeChromeVoxArcSupportForCurrentAppFunction()
       override {}
   ResponseAction Run() override;
+  void OnResponse(
+      extensions::api::accessibility_private::SetNativeChromeVoxResponse
+          response);
   DECLARE_EXTENSION_FUNCTION(
       "accessibilityPrivate.setNativeChromeVoxArcSupportForCurrentApp",
       ACCESSIBILITY_PRIVATE_SETNATIVECHROMEVOXARCSUPPORTFORCURRENTAPP)
diff --git a/chrome/browser/android/webapps/launchpad/java/src/org/chromium/chrome/browser/webapps/launchpad/LaunchpadCoordinatorTest.java b/chrome/browser/android/webapps/launchpad/java/src/org/chromium/chrome/browser/webapps/launchpad/LaunchpadCoordinatorTest.java
index d56d704..8c2b22f 100644
--- a/chrome/browser/android/webapps/launchpad/java/src/org/chromium/chrome/browser/webapps/launchpad/LaunchpadCoordinatorTest.java
+++ b/chrome/browser/android/webapps/launchpad/java/src/org/chromium/chrome/browser/webapps/launchpad/LaunchpadCoordinatorTest.java
@@ -34,7 +34,6 @@
 /**
  * Tests for {@link LaunchpadCoordinator}.
  *
- * EnhancedProtectionPromoCard does not need to be disabled. Its value just need to be set.
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
diff --git a/chrome/browser/apps/app_service/metrics/DEPS b/chrome/browser/apps/app_service/metrics/DEPS
index 64e2821..df77277 100644
--- a/chrome/browser/apps/app_service/metrics/DEPS
+++ b/chrome/browser/apps/app_service/metrics/DEPS
@@ -2,4 +2,8 @@
   "app_platform_input_metrics\.cc": [
     "+ash/shell.h",
   ],
+  "app_platform_metrics_service_unittest\.cc": [
+    "+ash/test/ash_test_base.h",
+    "+ash/test/ash_test_helper.h",
+  ],
 }
diff --git a/chrome/browser/apps/app_service/metrics/app_platform_metrics.cc b/chrome/browser/apps/app_service/metrics/app_platform_metrics.cc
index 6978f57..95933d3 100644
--- a/chrome/browser/apps/app_service/metrics/app_platform_metrics.cc
+++ b/chrome/browser/apps/app_service/metrics/app_platform_metrics.cc
@@ -355,11 +355,6 @@
   }
 }
 
-AppPlatformMetrics::BrowserToTab::BrowserToTab(
-    const aura::Window* browser_window,
-    const base::UnguessableToken& tab_id)
-    : browser_window(browser_window), tab_id(tab_id) {}
-
 AppPlatformMetrics::AppPlatformMetrics(
     Profile* profile,
     apps::AppRegistryCache& app_registry_cache,
@@ -700,7 +695,7 @@
     // For the browser window, if a tab of the browser is activated, we don't
     // need to calculate the browser window running time.
     if (app_id == extension_misc::kChromeAppId &&
-        HasActivatedTab(update.Window())) {
+        browser_to_tab_list_.HasActivatedTab(update.Window())) {
       SetWindowInActivated(app_id, update.InstanceId(), kInActivated);
       return;
     }
@@ -708,9 +703,9 @@
     // For web apps open in tabs, set the top browser window as inactive to stop
     // calculating the browser window running time.
     if (IsAppOpenedInTab(app_type_name, app_id)) {
-      RemoveActivatedTab(update.InstanceId());
-      AddActivatedTab(update.Window()->GetToplevelWindow(),
-                      update.InstanceId());
+      browser_to_tab_list_.RemoveActivatedTab(update.InstanceId());
+      browser_to_tab_list_.AddActivatedTab(update.Window()->GetToplevelWindow(),
+                                           update.InstanceId());
       InstanceState state;
       base::UnguessableToken browser_id;
       GetBrowserIdAndState(update, browser_id, state);
@@ -768,17 +763,17 @@
 void AppPlatformMetrics::UpdateBrowserWindowStatus(
     const InstanceUpdate& update) {
   const base::UnguessableToken& tab_id = update.InstanceId();
-  const auto* browser_window = GetBrowserWindow(tab_id);
+  const auto* browser_window = browser_to_tab_list_.GetBrowserWindow(tab_id);
   if (!browser_window) {
     return;
   }
 
   // Remove the tab id from `active_browser_to_tabs_`.
-  RemoveActivatedTab(tab_id);
+  browser_to_tab_list_.RemoveActivatedTab(tab_id);
 
   // If there are other activated web app tab, we don't need to set the browser
   // window as activated.
-  if (HasActivatedTab(browser_window)) {
+  if (browser_to_tab_list_.HasActivatedTab(browser_window)) {
     return;
   }
 
@@ -798,46 +793,6 @@
   }
 }
 
-bool AppPlatformMetrics::HasActivatedTab(const aura::Window* browser_window) {
-  for (const auto& it : active_browsers_to_tabs_) {
-    if (it.browser_window == browser_window) {
-      return true;
-    }
-  }
-  return false;
-}
-
-const aura::Window* AppPlatformMetrics::GetBrowserWindow(
-    const base::UnguessableToken& tab_id) const {
-  for (const auto& it : active_browsers_to_tabs_) {
-    if (it.tab_id == tab_id) {
-      return it.browser_window;
-    }
-  }
-  return nullptr;
-}
-
-void AppPlatformMetrics::AddActivatedTab(const aura::Window* browser_window,
-                                         const base::UnguessableToken& tab_id) {
-  bool found = false;
-  for (const auto& it : active_browsers_to_tabs_) {
-    if (it.browser_window == browser_window && it.tab_id == tab_id) {
-      found = true;
-      break;
-    }
-  }
-
-  if (!found) {
-    active_browsers_to_tabs_.push_back(BrowserToTab(browser_window, tab_id));
-  }
-}
-
-void AppPlatformMetrics::RemoveActivatedTab(
-    const base::UnguessableToken& tab_id) {
-  active_browsers_to_tabs_.remove_if(
-      [&](const BrowserToTab& item) { return item.tab_id == tab_id; });
-}
-
 void AppPlatformMetrics::SetWindowActivated(
     apps::mojom::AppType app_type,
     AppTypeName app_type_name,
diff --git a/chrome/browser/apps/app_service/metrics/app_platform_metrics.h b/chrome/browser/apps/app_service/metrics/app_platform_metrics.h
index c757bf0..4b90f0e 100644
--- a/chrome/browser/apps/app_service/metrics/app_platform_metrics.h
+++ b/chrome/browser/apps/app_service/metrics/app_platform_metrics.h
@@ -5,13 +5,13 @@
 #ifndef CHROME_BROWSER_APPS_APP_SERVICE_METRICS_APP_PLATFORM_METRICS_H_
 #define CHROME_BROWSER_APPS_APP_SERVICE_METRICS_APP_PLATFORM_METRICS_H_
 
-#include <list>
 #include <map>
 #include <set>
 #include <string>
 
 #include "base/time/time.h"
 #include "chrome/browser/apps/app_service/metrics/app_platform_metrics_utils.h"
+#include "chrome/browser/apps/app_service/metrics/browser_to_tab_list.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/instance_registry.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
@@ -19,10 +19,6 @@
 
 class Profile;
 
-namespace aura {
-class Window;
-}
-
 namespace apps {
 
 class AppUpdate;
@@ -157,15 +153,6 @@
     bool window_is_closed = false;
   };
 
-  struct BrowserToTab {
-    BrowserToTab(const aura::Window* browser_window,
-                 const base::UnguessableToken& tab_id);
-    const aura::Window* browser_window;
-    base::UnguessableToken tab_id;
-  };
-
-  using BrowserToTabs = std::list<BrowserToTab>;
-
   // AppRegistryCache::Observer:
   void OnAppTypeInitialized(apps::mojom::AppType app_type) override;
   void OnAppRegistryCacheWillBeDestroyed(
@@ -189,22 +176,6 @@
   // inactivated.
   void UpdateBrowserWindowStatus(const InstanceUpdate& update);
 
-  // Returns true if the browser with `browser_window` has activated tabs.
-  // Otherwise, returns false.
-  bool HasActivatedTab(const aura::Window* browser_window);
-
-  // Returns the browser window for `tab_id`.
-  const aura::Window* GetBrowserWindow(
-      const base::UnguessableToken& tab_id) const;
-
-  // Adds an activated `browser_window` and `tab_id` to
-  // `active_browser_to_tabs_`.
-  void AddActivatedTab(const aura::Window* browser_window,
-                       const base::UnguessableToken& tab_id);
-
-  // Removes `tab_id` from `active_browser_to_tabs_`.
-  void RemoveActivatedTab(const base::UnguessableToken& tab_id);
-
   void SetWindowActivated(apps::mojom::AppType app_type,
                           AppTypeName app_type_name,
                           AppTypeNameV2 app_type_name_v2,
@@ -246,8 +217,7 @@
 
   int user_type_by_device_type_;
 
-  // Records the map from browsers to activated web apps tabs.
-  BrowserToTabs active_browsers_to_tabs_;
+  BrowserToTabList browser_to_tab_list_;
 
   // |running_start_time_| and |running_duration_| are used for accumulating app
   // running duration per each day interval.
diff --git a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.h b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.h
index 0a8de9c8..72ca4cb7 100644
--- a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.h
+++ b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.h
@@ -45,6 +45,8 @@
   }
 
  private:
+  friend class AppPlatformInputMetricsTest;
+
   // Helper function to check if a new day has arrived.
   void CheckForNewDay();
 
diff --git a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc
index 05a8d3d..39353e2 100644
--- a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc
+++ b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc
@@ -7,6 +7,8 @@
 #include <memory>
 #include <utility>
 
+#include "ash/test/ash_test_base.h"
+#include "ash/test/ash_test_helper.h"
 #include "base/json/values_util.h"
 #include "base/metrics/histogram_base.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -704,6 +706,10 @@
     return std::move(app_platform_metrics_service_);
   }
 
+  AppPlatformMetricsService* app_platform_metrics_service() {
+    return app_platform_metrics_service_.get();
+  }
+
   TestingProfile* profile() { return testing_profile_.get(); }
 
   syncer::TestSyncService* sync_service() { return sync_service_; }
@@ -1572,4 +1578,200 @@
                          apps::mojom::UninstallSource::kAppList);
 }
 
+// Tests for app platform input metrics.
+class AppPlatformInputMetricsTest : public AppPlatformMetricsServiceTest {
+ public:
+  void SetUp() override {
+    AppPlatformMetricsServiceTest::SetUp();
+
+    ash_test_helper_ = std::make_unique<ash::AshTestHelper>();
+    ash_test_helper_->SetUp();
+
+    widget_ = ash::AshTestBase::CreateTestWidget();
+  }
+
+  void TearDown() override {
+    widget_.reset();
+    ash_test_helper_->TearDown();
+    AppPlatformMetricsServiceTest::TearDown();
+  }
+
+  AppPlatformInputMetrics* app_platform_input_metrics() {
+    return app_platform_metrics_service()->app_platform_input_metrics_.get();
+  }
+
+  aura::Window* window() { return widget_->GetNativeWindow(); }
+
+  void CreateInputEvent(InputEventSource event_source) {
+    switch (event_source) {
+      case InputEventSource::kUnknown:
+        break;
+      case InputEventSource::kMouse: {
+        ui::MouseEvent mouse_event(ui::ET_MOUSE_RELEASED, gfx::Point(),
+                                   gfx::Point(), base::TimeTicks(), 0, 0);
+        ui::Event::DispatcherApi(&mouse_event).set_target(window());
+        app_platform_input_metrics()->OnMouseEvent(&mouse_event);
+        break;
+      }
+      case InputEventSource::kStylus: {
+        ui::TouchEvent touch_event(
+            ui::ET_TOUCH_RELEASED, gfx::Point(), base::TimeTicks(),
+            ui::PointerDetails(ui::EventPointerType::kPen, 0));
+        ui::Event::DispatcherApi(&touch_event).set_target(window());
+        app_platform_input_metrics()->OnTouchEvent(&touch_event);
+        break;
+      }
+      case InputEventSource::kTouch: {
+        ui::TouchEvent touch_event(
+            ui::ET_TOUCH_RELEASED, gfx::Point(), base::TimeTicks(),
+            ui::PointerDetails(ui::EventPointerType::kTouch, 0));
+        ui::Event::DispatcherApi(&touch_event).set_target(window());
+        app_platform_input_metrics()->OnTouchEvent(&touch_event);
+        break;
+      }
+      case InputEventSource::kKeyboard: {
+        ui::KeyEvent key_event(ui::ET_KEY_RELEASED, ui::VKEY_MENU,
+                               ui::EF_ALT_DOWN);
+        ui::Event::DispatcherApi(&key_event).set_target(window());
+        app_platform_input_metrics()->OnKeyEvent(&key_event);
+        break;
+      }
+    }
+  }
+
+  void VerifyUkm(const std::string& app_info,
+                 AppTypeName app_type_name,
+                 int event_count,
+                 InputEventSource event_source) {
+    const auto entries =
+        test_ukm_recorder()->GetEntriesByName("ChromeOSApp.InputEvent");
+    ASSERT_EQ(1U, entries.size());
+    const auto* entry = entries[0];
+    test_ukm_recorder()->ExpectEntrySourceHasUrl(entry, GURL(app_info));
+    test_ukm_recorder()->ExpectEntryMetric(entry, "AppType",
+                                           (int)app_type_name);
+    test_ukm_recorder()->ExpectEntryMetric(entry, "AppInputEventCount",
+                                           event_count);
+    test_ukm_recorder()->ExpectEntryMetric(entry, "AppInputEventSource",
+                                           (int)event_source);
+  }
+
+ private:
+  std::unique_ptr<ash::AshTestHelper> ash_test_helper_;
+
+  // Where down events are dispatched to.
+  std::unique_ptr<views::Widget> widget_;
+};
+
+// Verify the input event is recorded when the window is inactivated, and no
+// more input event is recorded when the window is destroyed.
+TEST_F(AppPlatformInputMetricsTest, WindowStateChanged) {
+  ModifyInstance(/*app_id=*/"a", window(), kInactiveInstanceState);
+  CreateInputEvent(InputEventSource::kMouse);
+  app_platform_input_metrics()->OnFiveMinutes();
+  VerifyUkm("app://com.google.A", AppTypeName::kArc, /*event_count=*/1,
+            InputEventSource::kMouse);
+
+  ModifyInstance(/*app_id=*/"a", window(), apps::InstanceState::kDestroyed);
+  CreateInputEvent(InputEventSource::kMouse);
+  app_platform_input_metrics()->OnFiveMinutes();
+  // Verify no more input event is recorded.
+  VerifyUkm("app://com.google.A", AppTypeName::kArc, /*event_count=*/1,
+            InputEventSource::kMouse);
+}
+
+TEST_F(AppPlatformInputMetricsTest, MouseEvent) {
+  ModifyInstance(/*app_id=*/"a", window(), apps::InstanceState::kActive);
+  CreateInputEvent(InputEventSource::kMouse);
+  app_platform_input_metrics()->OnFiveMinutes();
+  VerifyUkm("app://com.google.A", AppTypeName::kArc, /*event_count=*/1,
+            InputEventSource::kMouse);
+}
+
+TEST_F(AppPlatformInputMetricsTest, StylusEvent) {
+  ModifyInstance(/*app_id=*/"w", window(), apps::InstanceState::kActive);
+  CreateInputEvent(InputEventSource::kStylus);
+  app_platform_input_metrics()->OnFiveMinutes();
+  VerifyUkm("https://foo.com", AppTypeName::kWeb, /*event_count=*/1,
+            InputEventSource::kStylus);
+}
+
+TEST_F(AppPlatformInputMetricsTest, TouchEvents) {
+  ModifyInstance(/*app_id=*/"a", window(), apps::InstanceState::kActive);
+  CreateInputEvent(InputEventSource::kTouch);
+  CreateInputEvent(InputEventSource::kTouch);
+  app_platform_input_metrics()->OnFiveMinutes();
+  VerifyUkm("app://com.google.A", AppTypeName::kArc, /*event_count=*/2,
+            InputEventSource::kTouch);
+}
+
+TEST_F(AppPlatformInputMetricsTest, KeyEvents) {
+  ModifyInstance(/*app_id=*/"a", window(), apps::InstanceState::kActive);
+  CreateInputEvent(InputEventSource::kKeyboard);
+  app_platform_input_metrics()->OnFiveMinutes();
+  VerifyUkm("app://com.google.A", AppTypeName::kArc, /*event_count=*/1,
+            InputEventSource::kKeyboard);
+
+  CreateInputEvent(InputEventSource::kKeyboard);
+  CreateInputEvent(InputEventSource::kKeyboard);
+  app_platform_input_metrics()->OnFiveMinutes();
+
+  // Verify 2 input metrics events are recorded.
+  const auto entries =
+      test_ukm_recorder()->GetEntriesByName("ChromeOSApp.InputEvent");
+  ASSERT_EQ(2U, entries.size());
+  std::set<int> counts;
+  for (const auto* entry : entries) {
+    test_ukm_recorder()->ExpectEntrySourceHasUrl(entry,
+                                                 GURL("app://com.google.A"));
+    test_ukm_recorder()->ExpectEntryMetric(entry, "AppType",
+                                           (int)AppTypeName::kArc);
+    test_ukm_recorder()->ExpectEntryMetric(entry, "AppInputEventSource",
+                                           (int)InputEventSource::kKeyboard);
+    counts.insert(
+        *(test_ukm_recorder()->GetEntryMetric(entry, "AppInputEventCount")));
+  }
+  EXPECT_TRUE(base::Contains(counts, 1));
+  EXPECT_TRUE(base::Contains(counts, 2));
+}
+
+TEST_F(AppPlatformInputMetricsTest, MultipleEvents) {
+  ModifyInstance(/*app_id=*/"a", window(), apps::InstanceState::kActive);
+  CreateInputEvent(InputEventSource::kMouse);
+  CreateInputEvent(InputEventSource::kMouse);
+  CreateInputEvent(InputEventSource::kKeyboard);
+  CreateInputEvent(InputEventSource::kStylus);
+  app_platform_input_metrics()->OnFiveMinutes();
+
+  // Verify 3 input metrics events are recorded.
+  const auto entries =
+      test_ukm_recorder()->GetEntriesByName("ChromeOSApp.InputEvent");
+  ASSERT_EQ(3U, entries.size());
+  int event_source;
+  int mouse_event_count = 0;
+  int keyboard_event_count = 0;
+  int stylus_event_count = 0;
+  for (const auto* entry : entries) {
+    test_ukm_recorder()->ExpectEntrySourceHasUrl(entry,
+                                                 GURL("app://com.google.A"));
+    test_ukm_recorder()->ExpectEntryMetric(entry, "AppType",
+                                           (int)AppTypeName::kArc);
+    event_source =
+        *(test_ukm_recorder()->GetEntryMetric(entry, "AppInputEventSource"));
+    if (event_source == (int)InputEventSource::kMouse) {
+      mouse_event_count =
+          *(test_ukm_recorder()->GetEntryMetric(entry, "AppInputEventCount"));
+    } else if (event_source == (int)InputEventSource::kKeyboard) {
+      keyboard_event_count =
+          *(test_ukm_recorder()->GetEntryMetric(entry, "AppInputEventCount"));
+    } else if (event_source == (int)InputEventSource::kStylus) {
+      stylus_event_count =
+          *(test_ukm_recorder()->GetEntryMetric(entry, "AppInputEventCount"));
+    }
+  }
+  EXPECT_EQ(2, mouse_event_count);
+  EXPECT_EQ(1, keyboard_event_count);
+  EXPECT_EQ(1, stylus_event_count);
+}
+
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/metrics/browser_to_tab_list.cc b/chrome/browser/apps/app_service/metrics/browser_to_tab_list.cc
new file mode 100644
index 0000000..3522bdf
--- /dev/null
+++ b/chrome/browser/apps/app_service/metrics/browser_to_tab_list.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/apps/app_service/metrics/browser_to_tab_list.h"
+
+#include "ui/aura/window.h"
+
+namespace apps {
+
+BrowserToTabList::BrowserToTabList() = default;
+
+BrowserToTabList::~BrowserToTabList() = default;
+
+BrowserToTabList::BrowserToTab::BrowserToTab(
+    const aura::Window* browser_window,
+    const base::UnguessableToken& tab_id)
+    : browser_window(browser_window), tab_id(tab_id) {}
+
+bool BrowserToTabList::HasActivatedTab(const aura::Window* browser_window) {
+  for (const auto& it : active_browsers_to_tabs_) {
+    if (it.browser_window == browser_window) {
+      return true;
+    }
+  }
+  return false;
+}
+
+const aura::Window* BrowserToTabList::GetBrowserWindow(
+    const base::UnguessableToken& tab_id) const {
+  for (const auto& it : active_browsers_to_tabs_) {
+    if (it.tab_id == tab_id) {
+      return it.browser_window;
+    }
+  }
+  return nullptr;
+}
+
+void BrowserToTabList::AddActivatedTab(const aura::Window* browser_window,
+                                       const base::UnguessableToken& tab_id) {
+  bool found = false;
+  for (const auto& it : active_browsers_to_tabs_) {
+    if (it.browser_window == browser_window && it.tab_id == tab_id) {
+      found = true;
+      break;
+    }
+  }
+
+  if (!found) {
+    active_browsers_to_tabs_.push_back(BrowserToTab(browser_window, tab_id));
+  }
+}
+
+void BrowserToTabList::RemoveActivatedTab(
+    const base::UnguessableToken& tab_id) {
+  active_browsers_to_tabs_.remove_if(
+      [&](const BrowserToTab& item) { return item.tab_id == tab_id; });
+}
+
+}  // namespace apps
diff --git a/chrome/browser/apps/app_service/metrics/browser_to_tab_list.h b/chrome/browser/apps/app_service/metrics/browser_to_tab_list.h
new file mode 100644
index 0000000..3b16f3c
--- /dev/null
+++ b/chrome/browser/apps/app_service/metrics/browser_to_tab_list.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_APPS_APP_SERVICE_METRICS_BROWSER_TO_TAB_LIST_H_
+#define CHROME_BROWSER_APPS_APP_SERVICE_METRICS_BROWSER_TO_TAB_LIST_H_
+
+#include <list>
+
+#include "base/unguessable_token.h"
+#include "ui/aura/window.h"
+
+namespace aura {
+class Window;
+}
+
+namespace apps {
+
+// BrowserToTabList saves the map from browser windows to tab instance ids.
+class BrowserToTabList {
+ public:
+  BrowserToTabList();
+  ~BrowserToTabList();
+
+  BrowserToTabList(const BrowserToTabList&) = delete;
+  BrowserToTabList& operator=(const BrowserToTabList&) = delete;
+
+  // Returns true if the browser with `browser_window` has activated tabs.
+  // Otherwise, returns false.
+  bool HasActivatedTab(const aura::Window* browser_window);
+
+  // Returns the browser window for `tab_id`.
+  const aura::Window* GetBrowserWindow(
+      const base::UnguessableToken& tab_id) const;
+
+  // Adds `browser_window` and `tab_id` to`active_browser_to_tabs_`.
+  void AddActivatedTab(const aura::Window* browser_window,
+                       const base::UnguessableToken& tab_id);
+
+  // Removes `tab_id` from `active_browser_to_tabs_`.
+  void RemoveActivatedTab(const base::UnguessableToken& tab_id);
+
+ private:
+  struct BrowserToTab {
+    BrowserToTab(const aura::Window* browser_window,
+                 const base::UnguessableToken& tab_id);
+    const aura::Window* browser_window;
+    base::UnguessableToken tab_id;
+  };
+
+  // Stores the list of browser-tab instance id pairs.
+  std::list<BrowserToTab> active_browsers_to_tabs_;
+};
+
+}  // namespace apps
+
+#endif  // CHROME_BROWSER_APPS_APP_SERVICE_METRICS_BROWSER_TO_TAB_LIST_H_
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
index 5a57dc6..cfff5ff3 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
+++ b/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "ash/app_list/app_list_controller_impl.h"
 #include "ash/app_list/app_list_model_provider.h"
 #include "ash/app_list/model/app_list_item.h"
 #include "ash/constants/ash_features.h"
@@ -16,6 +17,9 @@
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
 #include "chrome/browser/ui/app_list/chrome_app_list_model_updater.h"
 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ui/app_list/search/search_controller_impl.h"
+#include "chrome/browser/ui/app_list/search/search_controller_impl_new.h"
+#include "chrome/browser/ui/app_list/search/search_provider.h"
 #include "chrome/browser/ui/app_list/test/chrome_app_list_test_support.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -40,6 +44,105 @@
       nullptr, key, true, true, false, false)));
 }
 
+class TestSearchResult : public ChromeSearchResult {
+ public:
+  TestSearchResult(const std::string& id, double relevance) {
+    set_id(id);
+    SetTitle(base::UTF8ToUTF16(id));
+    set_relevance(relevance);
+  }
+
+  TestSearchResult(const TestSearchResult&) = delete;
+  TestSearchResult& operator=(const TestSearchResult&) = delete;
+
+  ~TestSearchResult() override {}
+
+  // ChromeSearchResult overrides:
+  void Open(int event_flags) override {}
+};
+
+class TestSearchProvider : public app_list::SearchProvider {
+ public:
+  TestSearchProvider(const std::string& prefix,
+                     ChromeSearchResult::DisplayType display_type,
+                     ChromeSearchResult::Category category,
+                     ChromeSearchResult::ResultType result_type)
+      : prefix_(prefix),
+        display_type_(display_type),
+        category_(category),
+        result_type_(result_type) {}
+
+  TestSearchProvider(const TestSearchProvider&) = delete;
+  TestSearchProvider& operator=(const TestSearchProvider&) = delete;
+
+  ~TestSearchProvider() override {}
+
+  // SearchProvider overrides:
+  void Start(const std::u16string& query) override {
+    auto create_result =
+        [this](int index) -> std::unique_ptr<ChromeSearchResult> {
+      const std::string id =
+          base::StringPrintf("%s %d", prefix_.c_str(), index);
+      double relevance = 1.0f - index / 100.0;
+      auto result = std::make_unique<TestSearchResult>(id, relevance);
+
+      result->SetDisplayType(display_type_);
+      result->SetCategory(category_);
+      result->SetResultType(result_type_);
+
+      return result;
+    };
+
+    std::vector<std::unique_ptr<ChromeSearchResult>> results;
+    for (size_t i = 0; i < count_ + best_match_count_; ++i) {
+      std::unique_ptr<ChromeSearchResult> result = create_result(i);
+      result->SetBestMatch(i < best_match_count_);
+      results.push_back(std::move(result));
+    }
+
+    SwapResults(&results);
+  }
+
+  ChromeSearchResult::ResultType ResultType() override { return result_type_; }
+
+  void set_count(size_t count) { count_ = count; }
+  void set_best_match_count(size_t count) { best_match_count_ = count; }
+
+ private:
+  std::string prefix_;
+  size_t count_ = 0;
+  size_t best_match_count_ = 0;
+  ChromeSearchResult::DisplayType display_type_;
+  ChromeSearchResult::Category category_;
+  ChromeSearchResult::ResultType result_type_;
+};
+
+// Adds two test providers to `search_controller` - one for app results, and
+// another one for omnibox results. Returns pointers to created providers
+// through `apps_provder_ptr` and `web_provider_ptr`.
+void InitializeTestSearchProviders(
+    app_list::SearchController* search_controller,
+    TestSearchProvider** apps_provider_ptr,
+    TestSearchProvider** web_provider_ptr) {
+  std::unique_ptr<TestSearchProvider> apps_provider =
+      std::make_unique<TestSearchProvider>(
+          "app", ChromeSearchResult::DisplayType::kTile,
+          ChromeSearchResult::Category::kApps,
+          ChromeSearchResult::ResultType::kInstalledApp);
+  *apps_provider_ptr = apps_provider.get();
+  size_t apps_group_id = search_controller->AddGroup(10);
+  search_controller->AddProvider(apps_group_id, std::move(apps_provider));
+
+  std::unique_ptr<TestSearchProvider> web_provider =
+      std::make_unique<TestSearchProvider>(
+          "item", ChromeSearchResult::DisplayType::kList,
+          ChromeSearchResult::Category::kWeb,
+          ChromeSearchResult::ResultType::kOmnibox);
+  *web_provider_ptr = web_provider.get();
+  size_t omnibox_group_id = search_controller->AddGroup(10);
+  search_controller->AddProvider(omnibox_group_id, std::move(web_provider));
+}
+
 }  // namespace
 
 enum SpokenFeedbackAppListTestVariant { kTestAsNormalUser, kTestAsGuestUser };
@@ -129,6 +232,43 @@
                          ::testing::Values(kTestAsNormalUser,
                                            kTestAsGuestUser));
 
+class SpokenFeedbackAppListSearchTest : public SpokenFeedbackAppListTest {
+ public:
+  // SpokenFeedbackAppListTest:
+  void SetUpOnMainThread() override {
+    SpokenFeedbackAppListTest::SetUpOnMainThread();
+
+    Shell::Get()->app_list_controller()->MarkSuggestedContentInfoDismissed();
+
+    // Reset default search controller, so the test has better control over the
+    // set of results shown in the search result UI.
+    AppListClientImpl* app_list_client = AppListClientImpl::GetInstance();
+    std::unique_ptr<app_list::SearchController> search_controller =
+        std::make_unique<app_list::SearchControllerImpl>(
+            app_list_client->GetModelUpdaterForTest(), app_list_client, nullptr,
+            browser()->profile());
+    InitializeTestSearchProviders(search_controller.get(), &apps_provider_,
+                                  &web_provider_);
+    ASSERT_TRUE(apps_provider_);
+    ASSERT_TRUE(web_provider_);
+    app_list_client->SetSearchControllerForTest(std::move(search_controller));
+  }
+
+  void TearDownOnMainThread() override {
+    AppListClientImpl::GetInstance()->SetSearchControllerForTest(nullptr);
+    SpokenFeedbackAppListTest::TearDownOnMainThread();
+  }
+
+ protected:
+  TestSearchProvider* apps_provider_ = nullptr;
+  TestSearchProvider* web_provider_ = nullptr;
+};
+
+INSTANTIATE_TEST_SUITE_P(TestAsNormalAndGuestUser,
+                         SpokenFeedbackAppListSearchTest,
+                         ::testing::Values(kTestAsNormalUser,
+                                           kTestAsGuestUser));
+
 class TabletModeSpokenFeedbackAppListTest : public SpokenFeedbackAppListTest {
  protected:
   TabletModeSpokenFeedbackAppListTest() = default;
@@ -180,6 +320,43 @@
                          ::testing::Values(kTestAsNormalUser,
                                            kTestAsGuestUser));
 
+class SpokenFeedbackAppListSearchProductivityLauncherTest
+    : public SpokenFeedbackAppListProductivityLauncherTest {
+ public:
+  // SpokenFeedbackAppListProductivityLauncherTest:
+  void SetUpOnMainThread() override {
+    SpokenFeedbackAppListProductivityLauncherTest::SetUpOnMainThread();
+
+    AppListClientImpl* app_list_client = AppListClientImpl::GetInstance();
+
+    // Reset default search controller, so the test has better control over the
+    // set of results shown in the search result UI.
+    std::unique_ptr<app_list::SearchController> search_controller =
+        std::make_unique<app_list::SearchControllerImplNew>(
+            app_list_client->GetModelUpdaterForTest(), app_list_client, nullptr,
+            browser()->profile());
+    InitializeTestSearchProviders(search_controller.get(), &apps_provider_,
+                                  &web_provider_);
+    ASSERT_TRUE(apps_provider_);
+    ASSERT_TRUE(web_provider_);
+    app_list_client->SetSearchControllerForTest(std::move(search_controller));
+  }
+
+  void TearDownOnMainThread() override {
+    AppListClientImpl::GetInstance()->SetSearchControllerForTest(nullptr);
+    SpokenFeedbackAppListProductivityLauncherTest::TearDownOnMainThread();
+  }
+
+ protected:
+  TestSearchProvider* apps_provider_ = nullptr;
+  TestSearchProvider* web_provider_ = nullptr;
+};
+
+INSTANTIATE_TEST_SUITE_P(TestAsNormalAndGuestUser,
+                         SpokenFeedbackAppListSearchProductivityLauncherTest,
+                         ::testing::Values(kTestAsNormalUser,
+                                           kTestAsGuestUser));
+
 // Checks that when an app list item with a notification badge is focused, an
 // announcement is made that the item requests your attention.
 IN_PROC_BROWSER_TEST_P(NotificationSpokenFeedbackAppListTest,
@@ -769,6 +946,117 @@
   sm_.Replay();
 }
 
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListSearchTest,
+                       LauncherSearchInClamshell) {
+  EnableChromeVox();
+
+  // Focus the shelf. This selects the launcher button.
+  sm_.Call([this]() {
+    EXPECT_TRUE(PerformAcceleratorAction(AcceleratorAction::FOCUS_SHELF));
+  });
+  sm_.ExpectSpeechPattern("Launcher");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
+
+  // Activate the launcher button. This opens bubble launcher.
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_SPACE); });
+  sm_.ExpectSpeechPattern("Search your device,*");
+  sm_.ExpectSpeech("Edit text");
+
+  sm_.Call([this]() {
+    apps_provider_->set_count(3);
+    web_provider_->set_count(4);
+    SendKeyPress(ui::VKEY_G);
+  });
+
+  sm_.ExpectSpeech("G");
+  sm_.ExpectSpeech("app 0");
+  sm_.ExpectSpeech("List item 1 of 7");
+
+  // Traverse app results, which are horizontally traversable
+  for (int i = 1; i < 5; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_RIGHT); });
+    sm_.ExpectSpeech(base::StringPrintf("app %d", i % 3));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 7", (i % 3) + 1));
+  }
+
+  // Traverse omnibox results.
+  for (int i = 0; i < 4; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("item %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 7", i + 4));
+  }
+
+  // Cycle focus to the close button.
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+  sm_.ExpectSpeech("Clear searchbox text");
+  sm_.ExpectSpeech("Press Search plus Space to activate");
+
+  // Go back to the last result.
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_UP); });
+  sm_.ExpectSpeech("item 3");
+
+  // Update the query, to initiate new search.
+  sm_.Call([this]() {
+    apps_provider_->set_count(3);
+    web_provider_->set_count(2);
+    SendKeyPress(ui::VKEY_A);
+  });
+
+  sm_.ExpectSpeech("A");
+  sm_.ExpectSpeech("app 0");
+
+  // Make sure key traversal still works.
+  for (int i = 0; i < 2; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("item %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 5", i + 4));
+  }
+
+  sm_.Replay();
+}
+
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListSearchTest,
+                       VocalizeResultCountInClamshell) {
+  EnableChromeVox();
+
+  // Focus the shelf. This selects the launcher button.
+  sm_.Call([this]() {
+    EXPECT_TRUE(PerformAcceleratorAction(AcceleratorAction::FOCUS_SHELF));
+  });
+  sm_.ExpectSpeechPattern("Launcher");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
+
+  // Activate the launcher button. This opens bubble launcher.
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_SPACE); });
+  sm_.ExpectSpeechPattern("Search your device,*");
+  sm_.ExpectSpeech("Edit text");
+
+  sm_.Call([this]() {
+    apps_provider_->set_count(3);
+    web_provider_->set_count(4);
+    SendKeyPress(ui::VKEY_G);
+  });
+
+  sm_.ExpectSpeech("G");
+  sm_.ExpectSpeech("Displaying 7 results for g");
+
+  // Update the query, to initiate new search.
+  sm_.Call([this]() {
+    apps_provider_->set_count(3);
+    web_provider_->set_count(2);
+    SendKeyPress(ui::VKEY_A);
+  });
+
+  sm_.ExpectSpeech("A");
+  sm_.ExpectSpeech("Displaying 5 results for ga");
+
+  sm_.Replay();
+}
+
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListProductivityLauncherTest,
                        ClamshellLauncher) {
   std::vector<std::string> suggestion_chips = GetPublishedSuggestionChips();
@@ -825,4 +1113,243 @@
   sm_.Replay();
 }
 
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListSearchProductivityLauncherTest,
+                       LauncherSearchInClamshell) {
+  EnableChromeVox();
+
+  // Focus the shelf. This selects the launcher button.
+  sm_.Call([this]() {
+    EXPECT_TRUE(PerformAcceleratorAction(AcceleratorAction::FOCUS_SHELF));
+  });
+  sm_.ExpectSpeechPattern("Launcher");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
+
+  // Activate the launcher button. This opens bubble launcher.
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_SPACE); });
+  sm_.ExpectSpeechPattern("Search your device,*");
+  sm_.ExpectSpeech("Edit text");
+
+  sm_.Call([this]() {
+    apps_provider_->set_best_match_count(2);
+    apps_provider_->set_count(3);
+    web_provider_->set_count(4);
+    SendKeyPress(ui::VKEY_G);
+  });
+
+  sm_.ExpectSpeech("G");
+  sm_.ExpectSpeech("app 0");
+  sm_.ExpectSpeech("List item 1 of 8");
+
+  // Traverse best match results;
+  for (int i = 1; i < 2; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("app %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 8", i + 1));
+  }
+
+  // Traverse non-best-match app results.
+  for (int i = 2; i < 5; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("app %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 8", i + 1));
+  }
+
+  // Traverse omnibox results.
+  for (int i = 0; i < 3; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("item %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 8", i + 6));
+  }
+
+  // Cycle focus to the close button.
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+  sm_.ExpectSpeech("Clear searchbox text");
+
+  // Go back to the last result.
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_UP); });
+  sm_.ExpectSpeech("item 2");
+
+  // Update the query, to initiate new search.
+  sm_.Call([this]() {
+    apps_provider_->set_best_match_count(0);
+    apps_provider_->set_count(3);
+    web_provider_->set_count(2);
+    SendKeyPress(ui::VKEY_A);
+  });
+
+  sm_.ExpectSpeech("A");
+  sm_.ExpectSpeech("app 0");
+
+  // Verify traversal works after result change.
+  for (int i = 1; i < 3; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("app %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 5", i + 1));
+  }
+
+  for (int i = 0; i < 2; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("item %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 5", i + 4));
+  }
+
+  sm_.Replay();
+}
+
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListSearchProductivityLauncherTest,
+                       VocalizeResultCountInClamshell) {
+  EnableChromeVox();
+
+  // Focus the shelf. This selects the launcher button.
+  sm_.Call([this]() {
+    EXPECT_TRUE(PerformAcceleratorAction(AcceleratorAction::FOCUS_SHELF));
+  });
+  sm_.ExpectSpeechPattern("Launcher");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
+
+  // Activate the launcher button. This opens bubble launcher.
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_SPACE); });
+  sm_.ExpectSpeechPattern("Search your device,*");
+  sm_.ExpectSpeech("Edit text");
+
+  sm_.Call([this]() {
+    apps_provider_->set_best_match_count(2);
+    apps_provider_->set_count(3);
+    web_provider_->set_count(4);
+    SendKeyPress(ui::VKEY_G);
+  });
+
+  sm_.ExpectSpeech("G");
+  sm_.ExpectSpeech("Displaying 8 results for g");
+
+  // Update the query, to initiate new search.
+  sm_.Call([this]() {
+    apps_provider_->set_best_match_count(0);
+    apps_provider_->set_count(3);
+    web_provider_->set_count(2);
+    SendKeyPress(ui::VKEY_A);
+  });
+
+  sm_.ExpectSpeech("A");
+  sm_.ExpectSpeech("Displaying 5 results for ga");
+
+  sm_.Replay();
+}
+
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListSearchProductivityLauncherTest,
+                       LauncherSearchInTablet) {
+  ShellTestApi().SetTabletModeEnabledForTest(true);
+  EnableChromeVox();
+
+  // Minimize the test window to transition to tablet mode home screen.
+  sm_.Call([this]() { browser()->window()->Minimize(); });
+
+  sm_.ExpectSpeechPattern("Search your device,*");
+  sm_.ExpectSpeech("Edit text");
+
+  sm_.Call([this]() {
+    apps_provider_->set_best_match_count(2);
+    apps_provider_->set_count(3);
+    web_provider_->set_count(4);
+    SendKeyPress(ui::VKEY_G);
+  });
+
+  sm_.ExpectSpeech("G");
+  sm_.ExpectSpeech("app 0");
+
+  // Traverse best match results;
+  for (int i = 1; i < 2; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("app %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 8", i + 1));
+  }
+
+  // Traverse non-best-match app results.
+  for (int i = 2; i < 5; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("app %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 8", i + 1));
+  }
+
+  // Traverse omnibox results.
+  for (int i = 0; i < 3; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("item %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 8", i + 6));
+  }
+
+  // Cycle focus to the close button.
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+  sm_.ExpectSpeech("Clear searchbox text");
+
+  // Go back to the last result.
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_UP); });
+  sm_.ExpectSpeech("item 2");
+
+  // Update the query, to initiate new search.
+  sm_.Call([this]() {
+    apps_provider_->set_best_match_count(0);
+    apps_provider_->set_count(3);
+    web_provider_->set_count(2);
+    SendKeyPress(ui::VKEY_A);
+  });
+
+  sm_.ExpectSpeech("A");
+  sm_.ExpectSpeech("app 0");
+
+  // Verify traversal works after result change.
+  for (int i = 1; i < 3; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("app %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 5", i + 1));
+  }
+
+  for (int i = 0; i < 2; ++i) {
+    sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+    sm_.ExpectSpeech(base::StringPrintf("item %d", i));
+    sm_.ExpectSpeech(base::StringPrintf("List item %d of 5", i + 4));
+  }
+
+  sm_.Replay();
+}
+
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListSearchProductivityLauncherTest,
+                       VocalizeResultCountInTablet) {
+  ShellTestApi().SetTabletModeEnabledForTest(true);
+  EnableChromeVox();
+
+  // Minimize the test window to transition to tablet mode home screen.
+  sm_.Call([this]() { browser()->window()->Minimize(); });
+
+  sm_.ExpectSpeechPattern("Search your device,*");
+  sm_.ExpectSpeech("Edit text");
+
+  sm_.Call([this]() {
+    apps_provider_->set_best_match_count(2);
+    apps_provider_->set_count(3);
+    web_provider_->set_count(4);
+    SendKeyPress(ui::VKEY_G);
+  });
+
+  sm_.ExpectSpeech("G");
+  sm_.ExpectSpeech("Displaying 8 results for g");
+
+  // Update the query, to initiate new search.
+  sm_.Call([this]() {
+    apps_provider_->set_best_match_count(0);
+    apps_provider_->set_count(3);
+    web_provider_->set_count(2);
+    SendKeyPress(ui::VKEY_A);
+  });
+
+  sm_.ExpectSpeech("A");
+  sm_.ExpectSpeech("Displaying 5 results for ga");
+
+  sm_.Replay();
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.cc b/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.cc
index a955e56..31c3d2b 100644
--- a/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.cc
+++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.cc
@@ -244,8 +244,10 @@
 
 ArcAccessibilityHelperBridge::~ArcAccessibilityHelperBridge() = default;
 
-void ArcAccessibilityHelperBridge::SetNativeChromeVoxArcSupport(bool enabled) {
-  tree_tracker_.SetNativeChromeVoxArcSupport(enabled);
+void ArcAccessibilityHelperBridge::SetNativeChromeVoxArcSupport(
+    bool enabled,
+    SetNativeChromeVoxCallback callback) {
+  tree_tracker_.SetNativeChromeVoxArcSupport(enabled, std::move(callback));
 }
 
 bool ArcAccessibilityHelperBridge::EnableTree(const ui::AXTreeID& tree_id) {
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.h b/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.h
index 669863f..ac36233 100644
--- a/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.h
+++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.h
@@ -72,7 +72,8 @@
   ~ArcAccessibilityHelperBridge() override;
 
   // Sets ChromeVox or TalkBack active for the current task.
-  void SetNativeChromeVoxArcSupport(bool enabled);
+  void SetNativeChromeVoxArcSupport(bool enabled,
+                                    SetNativeChromeVoxCallback callback);
 
   // Request Android to send the entire tree with the tree id. Returns true if
   // the specified tree is an ARC tree and a request was sent.
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge_browsertest.cc b/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge_browsertest.cc
index 502fb12..2cbb3d0 100644
--- a/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge_browsertest.cc
+++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge_browsertest.cc
@@ -193,7 +193,11 @@
 
   // Enable TalkBack. Touch exploration pass through of test_window_1
   // (current active window) would become true.
-  bridge->SetNativeChromeVoxArcSupport(false);
+  bridge->SetNativeChromeVoxArcSupport(
+      false,
+      base::BindOnce(
+          [](extensions::api::accessibility_private::SetNativeChromeVoxResponse
+                 response) {}));
 
   EXPECT_TRUE(
       test_window_1.shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.cc b/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.cc
index ab720077..1614685 100644
--- a/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.cc
+++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.cc
@@ -95,6 +95,27 @@
   }
 }
 
+extensions::api::accessibility_private::SetNativeChromeVoxResponse
+FromMojomResponseToAutomationResponse(
+    arc::mojom::SetNativeChromeVoxResponse response) {
+  switch (response) {
+    case arc::mojom::SetNativeChromeVoxResponse::SUCCESS:
+      return extensions::api::accessibility_private::
+          SetNativeChromeVoxResponse::SET_NATIVE_CHROME_VOX_RESPONSE_SUCCESS;
+    case arc::mojom::SetNativeChromeVoxResponse::TALKBACK_NOT_INSTALLED:
+      return extensions::api::accessibility_private::
+          SetNativeChromeVoxResponse::
+              SET_NATIVE_CHROME_VOX_RESPONSE_TALKBACKNOTINSTALLED;
+    case arc::mojom::SetNativeChromeVoxResponse::WINDOW_NOT_FOUND:
+      return extensions::api::accessibility_private::
+          SetNativeChromeVoxResponse::
+              SET_NATIVE_CHROME_VOX_RESPONSE_WINDOWNOTFOUND;
+    case arc::mojom::SetNativeChromeVoxResponse::FAILURE:
+      return extensions::api::accessibility_private::
+          SetNativeChromeVoxResponse::SET_NATIVE_CHROME_VOX_RESPONSE_FAILURE;
+  }
+}
+
 }  // namespace
 
 class ArcAccessibilityTreeTracker::FocusChangeObserver
@@ -473,13 +494,23 @@
   // OnSetNativeChromeVoxArcSupportProcessed()?
 }
 
-void ArcAccessibilityTreeTracker::SetNativeChromeVoxArcSupport(bool enabled) {
+void ArcAccessibilityTreeTracker::SetNativeChromeVoxArcSupport(
+    bool enabled,
+    SetNativeChromeVoxCallback callback) {
   aura::Window* window = GetFocusedArcWindow();
-  if (!window)
+  if (!window) {
+    std::move(callback).Run(
+        extensions::api::accessibility_private::SetNativeChromeVoxResponse::
+            SET_NATIVE_CHROME_VOX_RESPONSE_FAILURE);
     return;
+  }
 
-  if (!arc::GetWindowTaskId(window).has_value())
+  if (!arc::GetWindowTaskId(window).has_value()) {
+    std::move(callback).Run(
+        extensions::api::accessibility_private::SetNativeChromeVoxResponse::
+            SET_NATIVE_CHROME_VOX_RESPONSE_FAILURE);
     return;
+  }
 
   std::unique_ptr<aura::WindowTracker> window_tracker =
       std::make_unique<aura::WindowTracker>();
@@ -489,15 +520,21 @@
       enabled,
       base::BindOnce(
           &ArcAccessibilityTreeTracker::OnSetNativeChromeVoxArcSupportProcessed,
-          base::Unretained(this), std::move(window_tracker), enabled));
+          base::Unretained(this), std::move(window_tracker), enabled,
+          std::move(callback)));
 }
 
 void ArcAccessibilityTreeTracker::OnSetNativeChromeVoxArcSupportProcessed(
     std::unique_ptr<aura::WindowTracker> window_tracker,
     bool enabled,
-    bool processed) {
-  if (!processed || window_tracker->windows().size() != 1)
+    SetNativeChromeVoxCallback callback,
+    arc::mojom::SetNativeChromeVoxResponse response) {
+  std::move(callback).Run(FromMojomResponseToAutomationResponse(response));
+
+  if (response != arc::mojom::SetNativeChromeVoxResponse::SUCCESS ||
+      window_tracker->windows().size() != 1) {
     return;
+  }
 
   aura::Window* window = window_tracker->Pop();
   auto task_id = arc::GetWindowTaskId(window);
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.h b/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.h
index 54311e5..60110eae 100644
--- a/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.h
+++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.h
@@ -15,6 +15,7 @@
 #include "chrome/browser/ash/arc/accessibility/accessibility_helper_instance_remote_proxy.h"
 #include "chrome/browser/ash/arc/accessibility/ax_tree_source_arc.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
+#include "chrome/common/extensions/api/accessibility_private.h"
 #include "ui/aura/client/focus_change_observer.h"
 #include "ui/aura/env.h"
 #include "ui/aura/env_observer.h"
@@ -31,6 +32,9 @@
 
 namespace arc {
 
+using SetNativeChromeVoxCallback = base::OnceCallback<void(
+    extensions::api::accessibility_private::SetNativeChromeVoxResponse)>;
+
 // ArcAccessibilityTreeTracker is responsible for mapping accessibility tree
 // from android to exo window / surfaces.
 class ArcAccessibilityTreeTracker : public aura::EnvObserver {
@@ -90,13 +94,15 @@
   void OnToggleNativeChromeVoxArcSupport(bool enabled);
 
   // To be called from chrome automation private API.
-  void SetNativeChromeVoxArcSupport(bool enabled);
+  void SetNativeChromeVoxArcSupport(bool enabled,
+                                    SetNativeChromeVoxCallback callback);
 
   // Receives the result of setting native ChromeVox ARC support.
   void OnSetNativeChromeVoxArcSupportProcessed(
       std::unique_ptr<aura::WindowTracker> window_tracker,
       bool enabled,
-      bool processed);
+      SetNativeChromeVoxCallback callback,
+      arc::mojom::SetNativeChromeVoxResponse response);
 
   // Returns a tree source for the specified AXTreeID.
   AXTreeSourceArc* GetFromTreeId(const ui::AXTreeID& tree_id) const;
diff --git a/chrome/browser/ash/arc/bluetooth/arc_bluetooth_bridge.cc b/chrome/browser/ash/arc/bluetooth/arc_bluetooth_bridge.cc
index b245265..ef54437 100644
--- a/chrome/browser/ash/arc/bluetooth/arc_bluetooth_bridge.cc
+++ b/chrome/browser/ash/arc/bluetooth/arc_bluetooth_bridge.cc
@@ -1944,26 +1944,6 @@
   }
 }
 
-void ArcBluetoothBridge::OpenBluetoothSocketDeprecated(
-    OpenBluetoothSocketDeprecatedCallback callback) {
-  base::ScopedFD sock(socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM));
-  if (!sock.is_valid()) {
-    LOG(ERROR) << "Failed to open socket.";
-    std::move(callback).Run(mojo::ScopedHandle());
-    return;
-  }
-
-  mojo::ScopedHandle handle =
-      mojo::WrapPlatformHandle(mojo::PlatformHandle(std::move(sock)));
-  if (!handle.is_valid()) {
-    LOG(ERROR) << "Failed to wrap socket handle. Closing";
-    std::move(callback).Run(mojo::ScopedHandle());
-    return;
-  }
-
-  std::move(callback).Run(std::move(handle));
-}
-
 bool ArcBluetoothBridge::IsGattServerAttributeHandleAvailable(int need) {
   return gatt_server_attribute_next_handle_ + need <= kMaxGattAttributeHandle;
 }
@@ -3062,40 +3042,6 @@
 
 }  // namespace
 
-void ArcBluetoothBridge::RfcommListenDeprecated(
-    int32_t channel,
-    int32_t optval,
-    RfcommListenDeprecatedCallback callback) {
-  if (channel != kAutoSockPort &&
-      !IsValidPort(mojom::BluetoothSocketType::TYPE_RFCOMM, channel)) {
-    LOG(ERROR) << "Invalid channel number";
-    std::move(callback).Run(
-        mojom::BluetoothStatus::FAIL, /*channel=*/0,
-        mojo::PendingReceiver<mojom::RfcommListeningSocketClient>());
-    return;
-  }
-
-  uint16_t listen_channel = static_cast<uint16_t>(channel);
-  auto sock_wrapper = CreateBluetoothListenSocket(
-      mojom::BluetoothSocketType::TYPE_RFCOMM, optval, &listen_channel);
-  if (!sock_wrapper) {
-    std::move(callback).Run(
-        mojom::BluetoothStatus::FAIL, /*channel=*/0,
-        mojo::PendingReceiver<mojom::RfcommListeningSocketClient>());
-    return;
-  }
-
-  sock_wrapper->created_by_deprecated_method = true;
-  std::move(callback).Run(
-      mojom::BluetoothStatus::SUCCESS, listen_channel,
-      sock_wrapper->deprecated_remote.BindNewPipeAndPassReceiver());
-
-  sock_wrapper->deprecated_remote.set_disconnect_handler(
-      base::BindOnce(&ArcBluetoothBridge::CloseBluetoothListeningSocket,
-                     weak_factory_.GetWeakPtr(), sock_wrapper.get()));
-  listening_sockets_.insert(std::move(sock_wrapper));
-}
-
 void ArcBluetoothBridge::BluetoothSocketListen(
     mojom::BluetoothSocketType sock_type,
     mojom::BluetoothSocketFlagsPtr sock_flags,
@@ -3142,37 +3088,6 @@
   listening_sockets_.erase(itr);
 }
 
-void ArcBluetoothBridge::RfcommConnectDeprecated(
-    mojom::BluetoothAddressPtr remote_addr,
-    int32_t channel,
-    int32_t optval,
-    RfcommConnectDeprecatedCallback callback) {
-  if (!IsValidPort(mojom::BluetoothSocketType::TYPE_RFCOMM, channel)) {
-    LOG(ERROR) << "Invalid channel number " << channel;
-    std::move(callback).Run(mojom::BluetoothStatus::FAIL,
-                            mojom::RfcommConnectingSocketClientRequest());
-    return;
-  }
-
-  auto sock_wrapper = CreateBluetoothConnectSocket(
-      mojom::BluetoothSocketType::TYPE_RFCOMM, optval, std::move(remote_addr),
-      static_cast<uint16_t>(channel));
-  if (!sock_wrapper) {
-    std::move(callback).Run(mojom::BluetoothStatus::FAIL,
-                            mojom::RfcommConnectingSocketClientRequest());
-    return;
-  }
-
-  sock_wrapper->created_by_deprecated_method = true;
-  std::move(callback).Run(
-      mojom::BluetoothStatus::SUCCESS,
-      sock_wrapper->deprecated_remote.BindNewPipeAndPassReceiver());
-  sock_wrapper->deprecated_remote.set_disconnect_handler(
-      base::BindOnce(&ArcBluetoothBridge::CloseBluetoothConnectingSocket,
-                     weak_factory_.GetWeakPtr(), sock_wrapper.get()));
-  connecting_sockets_.insert(std::move(sock_wrapper));
-}
-
 void ArcBluetoothBridge::BluetoothSocketConnect(
     mojom::BluetoothSocketType sock_type,
     mojom::BluetoothSocketFlagsPtr sock_flags,
diff --git a/chrome/browser/ash/arc/bluetooth/arc_bluetooth_bridge.h b/chrome/browser/ash/arc/bluetooth/arc_bluetooth_bridge.h
index ece137df..5e629a7 100644
--- a/chrome/browser/ash/arc/bluetooth/arc_bluetooth_bridge.h
+++ b/chrome/browser/ash/arc/bluetooth/arc_bluetooth_bridge.h
@@ -270,9 +270,6 @@
   void ReadRemoteRssi(mojom::BluetoothAddressPtr remote_addr,
                       ReadRemoteRssiCallback callback) override;
 
-  void OpenBluetoothSocketDeprecated(
-      OpenBluetoothSocketDeprecatedCallback callback) override;
-
   // Bluetooth Mojo host interface - Bluetooth Gatt Server functions
   // Android counterpart link:
   // https://source.android.com/devices/halref/bt__gatt__server_8h.html
@@ -319,16 +316,6 @@
   void RemoveSdpRecord(uint32_t service_handle,
                        RemoveSdpRecordCallback callback) override;
 
-  // Bluetooth Mojo host interface - Bluetooth RFCOMM functions
-  void RfcommListenDeprecated(int32_t channel,
-                              int32_t optval,
-                              RfcommListenDeprecatedCallback callback) override;
-  void RfcommConnectDeprecated(
-      mojom::BluetoothAddressPtr remote_addr,
-      int32_t channel,
-      int32_t optval,
-      RfcommConnectDeprecatedCallback callback) override;
-
   // Bluetooth Mojo host interface - Bluetooth socket functions
   void BluetoothSocketListen(mojom::BluetoothSocketType sock_type,
                              mojom::BluetoothSocketFlagsPtr sock_flags,
diff --git a/chrome/browser/ash/arc/input_method_manager/arc_input_method_manager_service_unittest.cc b/chrome/browser/ash/arc/input_method_manager/arc_input_method_manager_service_unittest.cc
index 5cc008f..cd8dbc2 100644
--- a/chrome/browser/ash/arc/input_method_manager/arc_input_method_manager_service_unittest.cc
+++ b/chrome/browser/ash/arc/input_method_manager/arc_input_method_manager_service_unittest.cc
@@ -304,7 +304,6 @@
     tablet_mode_controller_.reset();
     profile_.reset();
     im::InputMethodManager::Shutdown();
-    ui::IMEBridge::Shutdown();
   }
 
  private:
diff --git a/chrome/browser/ash/arc/input_method_manager/input_connection_impl_unittest.cc b/chrome/browser/ash/arc/input_method_manager/input_connection_impl_unittest.cc
index 0667f20..9010231 100644
--- a/chrome/browser/ash/arc/input_method_manager/input_connection_impl_unittest.cc
+++ b/chrome/browser/ash/arc/input_method_manager/input_connection_impl_unittest.cc
@@ -216,7 +216,6 @@
     engine_.reset();
     bridge_.reset();
     ash::input_method::InputMethodManager::Shutdown();
-    ui::IMEBridge::Shutdown();
   }
 
  private:
diff --git a/chrome/browser/ash/arc/print_spooler/arc_print_spooler_bridge.cc b/chrome/browser/ash/arc/print_spooler/arc_print_spooler_bridge.cc
index 9b93819..13be0d7 100644
--- a/chrome/browser/ash/arc/print_spooler/arc_print_spooler_bridge.cc
+++ b/chrome/browser/ash/arc/print_spooler/arc_print_spooler_bridge.cc
@@ -68,17 +68,6 @@
   arc_bridge_service_->print_spooler()->SetHost(nullptr);
 }
 
-void ArcPrintSpoolerBridge::StartPrintInCustomTabDeprecated(
-    mojo::ScopedHandle scoped_handle,
-    int32_t task_id,
-    int32_t surface_id,
-    int32_t top_margin,
-    mojo::PendingRemote<mojom::PrintSessionInstance> instance,
-    StartPrintInCustomTabCallback callback) {
-  StartPrintInCustomTab(std::move(scoped_handle), task_id, std::move(instance),
-                        std::move(callback));
-}
-
 void ArcPrintSpoolerBridge::StartPrintInCustomTab(
     mojo::ScopedHandle scoped_handle,
     int32_t task_id,
diff --git a/chrome/browser/ash/arc/print_spooler/arc_print_spooler_bridge.h b/chrome/browser/ash/arc/print_spooler/arc_print_spooler_bridge.h
index c41529a9a..f83f5a3 100644
--- a/chrome/browser/ash/arc/print_spooler/arc_print_spooler_bridge.h
+++ b/chrome/browser/ash/arc/print_spooler/arc_print_spooler_bridge.h
@@ -41,13 +41,6 @@
   ~ArcPrintSpoolerBridge() override;
 
   // mojom::PrintSpoolerHost:
-  void StartPrintInCustomTabDeprecated(
-      mojo::ScopedHandle scoped_handle,
-      int32_t task_id,
-      int32_t surface_id,
-      int32_t top_margin,
-      mojo::PendingRemote<mojom::PrintSessionInstance> instance,
-      StartPrintInCustomTabCallback callback) override;
   void StartPrintInCustomTab(
       mojo::ScopedHandle scoped_handle,
       int32_t task_id,
diff --git a/chrome/browser/ash/arc/tts/arc_tts_service.cc b/chrome/browser/ash/arc/tts/arc_tts_service.cc
index 3ac94e8..070ad142 100644
--- a/chrome/browser/ash/arc/tts/arc_tts_service.cc
+++ b/chrome/browser/ash/arc/tts/arc_tts_service.cc
@@ -80,13 +80,6 @@
   arc_bridge_service_->tts()->SetHost(nullptr);
 }
 
-void ArcTtsService::OnTtsEventDeprecated(uint32_t id,
-                                         mojom::TtsEventType event_type,
-                                         uint32_t char_index,
-                                         const std::string& error_msg) {
-  OnTtsEvent(id, event_type, char_index, -1 /* length */, error_msg);
-}
-
 void ArcTtsService::OnTtsEvent(uint32_t id,
                                mojom::TtsEventType event_type,
                                uint32_t char_index,
diff --git a/chrome/browser/ash/arc/tts/arc_tts_service.h b/chrome/browser/ash/arc/tts/arc_tts_service.h
index bbec1e1..72bdec5 100644
--- a/chrome/browser/ash/arc/tts/arc_tts_service.h
+++ b/chrome/browser/ash/arc/tts/arc_tts_service.h
@@ -39,10 +39,6 @@
   ~ArcTtsService() override;
 
   // mojom::TtsHost overrides:
-  void OnTtsEventDeprecated(uint32_t id,
-                            mojom::TtsEventType event_type,
-                            uint32_t char_index,
-                            const std::string& error_msg) override;
   void OnTtsEvent(uint32_t id,
                   mojom::TtsEventType event_type,
                   uint32_t char_index,
diff --git a/chrome/browser/ash/chrome_browser_main_parts_ash.cc b/chrome/browser/ash/chrome_browser_main_parts_ash.cc
index bad96f0..4ea240e 100644
--- a/chrome/browser/ash/chrome_browser_main_parts_ash.cc
+++ b/chrome/browser/ash/chrome_browser_main_parts_ash.cc
@@ -172,7 +172,6 @@
 #include "chrome/browser/task_manager/task_manager_interface.h"
 #include "chrome/browser/ui/ash/assistant/assistant_browser_delegate_impl.h"
 #include "chrome/browser/ui/ash/assistant/assistant_state_client.h"
-#include "chrome/browser/ui/ash/fwupd_download_client_impl.h"
 #include "chrome/browser/ui/ash/image_downloader_impl.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
 #include "chrome/browser/ui/ash/quick_answers/quick_answers_controller_impl.h"
@@ -1247,7 +1246,6 @@
 
   if (features::IsFirmwareUpdaterAppEnabled()) {
     firmware_update_manager_ = std::make_unique<FirmwareUpdateManager>();
-    fwupd_download_client_ = std::make_unique<FwupdDownloadClientImpl>();
   }
 
   if (features::IsPciguardUiEnabled()) {
diff --git a/chrome/browser/ash/chrome_browser_main_parts_ash.h b/chrome/browser/ash/chrome_browser_main_parts_ash.h
index 643b240..7c0d28f 100644
--- a/chrome/browser/ash/chrome_browser_main_parts_ash.h
+++ b/chrome/browser/ash/chrome_browser_main_parts_ash.h
@@ -73,7 +73,6 @@
 class DemoModeResourcesRemover;
 class EventRewriterDelegateImpl;
 class FirmwareUpdateManager;
-class FwupdDownloadClientImpl;
 class GnubbyNotification;
 class IdleActionWarningObserver;
 class LoginScreenExtensionsLifetimeManager;
@@ -230,8 +229,6 @@
   std::unique_ptr<BulkPrintersCalculatorFactory>
       bulk_printers_calculator_factory_;
 
-  std::unique_ptr<FwupdDownloadClientImpl> fwupd_download_client_;
-
   std::unique_ptr<SessionTerminationManager> session_termination_manager_;
 
   // Set when PreProfileInit() is called. If PreMainMessageLoopRun() exits
diff --git a/chrome/browser/ash/input_method/assistive_window_controller_unittest.cc b/chrome/browser/ash/input_method/assistive_window_controller_unittest.cc
index 58586a6..02c92cb 100644
--- a/chrome/browser/ash/input_method/assistive_window_controller_unittest.cc
+++ b/chrome/browser/ash/input_method/assistive_window_controller_unittest.cc
@@ -53,7 +53,7 @@
 class AssistiveWindowControllerTest : public ChromeAshTestBase {
  protected:
   AssistiveWindowControllerTest() = default;
-  ~AssistiveWindowControllerTest() override { ui::IMEBridge::Shutdown(); }
+  ~AssistiveWindowControllerTest() override = default;
 
   void SetUp() override {
     ChromeAshTestBase::SetUp();
diff --git a/chrome/browser/ash/input_method/input_method_configuration.cc b/chrome/browser/ash/input_method/input_method_configuration.cc
index 3c7e111..660b12d 100644
--- a/chrome/browser/ash/input_method/input_method_configuration.cc
+++ b/chrome/browser/ash/input_method/input_method_configuration.cc
@@ -6,14 +6,11 @@
 
 #include <memory>
 
-#include "base/bind.h"
-#include "base/logging.h"
 #include "chrome/browser/ash/input_method/accessibility.h"
 #include "chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.h"
 #include "chrome/browser/ash/input_method/input_method_delegate_impl.h"
 #include "chrome/browser/ash/input_method/input_method_manager_impl.h"
 #include "chrome/browser/ash/input_method/input_method_persistence.h"
-#include "ui/base/ime/ash/ime_bridge.h"
 
 namespace ash {
 namespace input_method {
@@ -21,63 +18,28 @@
 namespace {
 
 bool g_disable_extension_loading = false;
-
-class InputMethodConfiguration {
- public:
-  InputMethodConfiguration() = default;
-  virtual ~InputMethodConfiguration() = default;
-
-  void Initialize() {
-    auto* impl = new InputMethodManagerImpl(
-        std::make_unique<InputMethodDelegateImpl>(),
-        std::make_unique<ComponentExtensionIMEManagerDelegateImpl>(),
-        !g_disable_extension_loading);
-    InputMethodManager::Initialize(impl);
-
-    DCHECK(InputMethodManager::Get());
-
-    accessibility_ = std::make_unique<Accessibility>(impl);
-    input_method_persistence_ = std::make_unique<InputMethodPersistence>(impl);
-
-    DVLOG(1) << "InputMethodManager initialized";
-  }
-
-  void InitializeForTesting(InputMethodManager* mock_manager) {
-    InputMethodManager::Initialize(mock_manager);
-    DVLOG(1) << "InputMethodManager for testing initialized";
-  }
-
-  void Shutdown() {
-    accessibility_.reset();
-
-    input_method_persistence_.reset();
-
-    InputMethodManager::Shutdown();
-
-    ui::IMEBridge::Shutdown();
-
-    DVLOG(1) << "InputMethodManager shutdown";
-  }
-
- private:
-  std::unique_ptr<Accessibility> accessibility_;
-  std::unique_ptr<InputMethodPersistence> input_method_persistence_;
-};
-
-InputMethodConfiguration* g_input_method_configuration = NULL;
+Accessibility* g_accessibility = nullptr;
+InputMethodPersistence* g_input_method_persistence = nullptr;
 
 }  // namespace
 
 void Initialize() {
-  if (!g_input_method_configuration)
-    g_input_method_configuration = new InputMethodConfiguration();
-  g_input_method_configuration->Initialize();
+  auto* impl = new InputMethodManagerImpl(
+      std::make_unique<InputMethodDelegateImpl>(),
+      std::make_unique<ComponentExtensionIMEManagerDelegateImpl>(),
+      !g_disable_extension_loading);
+  InputMethodManager::Initialize(impl);
+  DCHECK(InputMethodManager::Get());
+
+  delete g_accessibility;
+  g_accessibility = new Accessibility(impl);
+
+  delete g_input_method_persistence;
+  g_input_method_persistence = new InputMethodPersistence(impl);
 }
 
 void InitializeForTesting(InputMethodManager* mock_manager) {
-  if (!g_input_method_configuration)
-    g_input_method_configuration = new InputMethodConfiguration();
-  g_input_method_configuration->InitializeForTesting(mock_manager);
+  InputMethodManager::Initialize(mock_manager);
 }
 
 void DisableExtensionLoading() {
@@ -85,12 +47,13 @@
 }
 
 void Shutdown() {
-  if (!g_input_method_configuration)
-    return;
+  delete g_accessibility;
+  g_accessibility = nullptr;
 
-  g_input_method_configuration->Shutdown();
-  delete g_input_method_configuration;
-  g_input_method_configuration = NULL;
+  delete g_input_method_persistence;
+  g_input_method_persistence = nullptr;
+
+  InputMethodManager::Shutdown();
 }
 
 }  // namespace input_method
diff --git a/chrome/browser/ash/input_method/input_method_configuration.h b/chrome/browser/ash/input_method/input_method_configuration.h
index a078ae0..d8dcae1 100644
--- a/chrome/browser/ash/input_method/input_method_configuration.h
+++ b/chrome/browser/ash/input_method/input_method_configuration.h
@@ -12,22 +12,17 @@
 namespace ash {
 namespace input_method {
 
-// Initializes the InputMethodManager. Must be called before any calls to
-// GetInstance(). We explicitly initialize and shut down the global instance,
-// rather than making it a Singleton, to ensure clean startup and shutdown.
 void Initialize();
 
-// Similar to Initialize(), but can inject an alternative
-// InputMethodManager such as MockInputMethodManager for testing.
-// The injected object will be owned by the internal pointer and deleted
-// by Shutdown().
+// Similar to Initialize(), but can inject an alternative InputMethodManager
+// such as MockInputMethodManager for testing. The injected object will be
+// owned by the internal pointer and deleted by Shutdown().
 // TODO(nona): Remove this and use InputMethodManager::Initialize instead.
 void InitializeForTesting(InputMethodManager* mock_manager);
 
 // Disables the IME extension loading (e.g. for browser tests).
 void DisableExtensionLoading();
 
-// Destroys the global InputMethodManager instance.
 void Shutdown();
 
 }  // namespace input_method
diff --git a/chrome/browser/ash/input_method/input_method_manager_impl_unittest.cc b/chrome/browser/ash/input_method/input_method_manager_impl_unittest.cc
index 447ddbd8..86e1025 100644
--- a/chrome/browser/ash/input_method/input_method_manager_impl_unittest.cc
+++ b/chrome/browser/ash/input_method/input_method_manager_impl_unittest.cc
@@ -212,7 +212,6 @@
     chrome_keyboard_controller_client_test_helper_.reset();
 
     BrowserWithTestWindowTest::TearDown();
-    ui::ShutdownInputMethodForTesting();
 
     candidate_window_controller_ = nullptr;
     keyboard_ = nullptr;
diff --git a/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc b/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc
index d43b0aa2..fe30e2b7 100644
--- a/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc
+++ b/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc
@@ -276,7 +276,7 @@
   app_install_event_log_uploader_ =
       std::make_unique<ArcAppInstallEventLogUploader>(client(), profile_);
   extension_install_event_log_uploader_ =
-      std::make_unique<ExtensionInstallEventLogUploader>(profile_);
+      ExtensionInstallEventLogUploader::Create(profile_);
 }
 
 void UserCloudPolicyManagerAsh::OnAccessTokenAvailable(
diff --git a/chrome/browser/ash/policy/reporting/extension_install_event_log_manager_unittest.cc b/chrome/browser/ash/policy/reporting/extension_install_event_log_manager_unittest.cc
index 48705db6..7f5f80a 100644
--- a/chrome/browser/ash/policy/reporting/extension_install_event_log_manager_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/extension_install_event_log_manager_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <iterator>
 #include <map>
+#include <memory>
 #include <vector>
 
 #include "ash/components/arc/arc_prefs.h"
@@ -18,10 +19,12 @@
 #include "base/test/scoped_mock_time_message_loop_task_runner.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "base/test/test_simple_task_runner.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/tick_clock.h"
 #include "base/time/time.h"
 #include "base/values.h"
 #include "chrome/browser/ash/policy/reporting/extension_install_event_log.h"
+#include "chrome/browser/ash/policy/reporting/extension_install_event_log_uploader.h"
 #include "chrome/browser/ash/policy/reporting/install_event_log_util.h"
 #include "chrome/browser/profiles/reporting_util.h"
 #include "chrome/test/base/testing_profile.h"
@@ -154,8 +157,7 @@
 class ExtensionInstallEventLogManagerTest : public testing::Test {
  protected:
   ExtensionInstallEventLogManagerTest()
-      : uploader_(/*profile=*/nullptr),
-        log_task_runner_(log_task_runner_wrapper_.test_task_runner()),
+      : log_task_runner_(log_task_runner_wrapper_.test_task_runner()),
         log_file_path_(profile_.GetPath().Append(kLogFileName)),
         extension_ids_{std::begin(kExtensionIds), std::end(kExtensionIds)},
         events_value_(base::Value::Type::DICTIONARY),
@@ -165,9 +167,13 @@
 
   // testing::Test:
   void SetUp() override {
-    auto mock_report_queue = std::make_unique<reporting::MockReportQueue>();
+    auto mock_report_queue = std::unique_ptr<::reporting::MockReportQueue,
+                                             base::OnTaskRunnerDeleter>(
+        new ::reporting::MockReportQueue(),
+        base::OnTaskRunnerDeleter(base::SequencedTaskRunnerHandle::Get()));
     mock_report_queue_ = mock_report_queue.get();
-    uploader_.SetReportQueue(std::move(mock_report_queue));
+    uploader_ = ExtensionInstallEventLogUploader::CreateForTest(
+        /*profile=*/nullptr, std::move(mock_report_queue));
     event_.set_timestamp(0);
     event_.set_event_type(em::ExtensionInstallReportLogEvent::SUCCESS);
 
@@ -188,7 +194,7 @@
 
   void CreateManager() {
     manager_ = std::make_unique<ExtensionInstallEventLogManager>(
-        &log_task_runner_wrapper_, &uploader_, &profile_);
+        &log_task_runner_wrapper_, uploader_.get(), &profile_);
     FlushNonDelayedTasks();
   }
 
@@ -308,7 +314,7 @@
       disable_purge_for_testing_;
   TestingProfile profile_;
   reporting::MockReportQueue* mock_report_queue_;
-  ExtensionInstallEventLogUploader uploader_;
+  std::unique_ptr<ExtensionInstallEventLogUploader> uploader_;
   std::unique_ptr<base::ScopedMockTimeMessageLoopTaskRunner>
       scoped_main_task_runner_;
 
diff --git a/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader.cc b/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader.cc
index d315591..6b96eeea 100644
--- a/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader.cc
+++ b/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader.cc
@@ -4,107 +4,47 @@
 
 #include "chrome/browser/ash/policy/reporting/extension_install_event_log_uploader.h"
 
-#include <atomic>
+#include <memory>
+#include <utility>
 
 #include "ash/constants/ash_switches.h"
 #include "base/bind.h"
 #include "base/command_line.h"
-#include "base/task/thread_pool.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "base/time/time.h"
+#include "base/memory/ptr_util.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/values.h"
 #include "chrome/browser/ash/policy/reporting/install_event_log_util.h"
-#include "chrome/browser/policy/dm_token_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/reporting_util.h"
 #include "components/policy/core/common/cloud/realtime_reporting_job_configuration.h"
 #include "components/policy/proto/device_management_backend.pb.h"
-#include "components/reporting/client/report_queue.h"
-#include "components/reporting/client/report_queue_provider.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
+#include "components/reporting/client/report_queue_configuration.h"
+#include "components/reporting/client/report_queue_factory.h"
 
 namespace em = enterprise_management;
 
 namespace policy {
-namespace {
-
-ExtensionInstallEventLogUploader::GetReportQueueConfigCallback
-CreateReportQueueConfigGetter(Profile* profile) {
-  return base::BindRepeating(
-      [](Profile* profile,
-         ExtensionInstallEventLogUploader::ReportQueueConfigResultCallback
-             complete_cb) {
-        auto task = base::BindOnce(
-            [](Profile* profile,
-               base::OnceCallback<void(
-                   ::reporting::StatusOr<std::unique_ptr<
-                       ::reporting::ReportQueueConfiguration>>)> complete_cb) {
-              auto dm_token = GetDMToken(profile);
-              if (!dm_token.is_valid()) {
-                std::move(complete_cb)
-                    .Run(::reporting::Status(
-                        ::reporting::error::INVALID_ARGUMENT,
-                        "DMToken must be valid"));
-                return;
-              }
-              std::move(complete_cb)
-                  .Run(::reporting::ReportQueueConfiguration::Create(
-                      dm_token.value(), ::reporting::Destination::UPLOAD_EVENTS,
-                      base::BindRepeating(
-                          []() { return ::reporting::Status::StatusOK(); })));
-            },
-            profile, std::move(complete_cb));
-
-        content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
-                                                     std::move(task));
-      },
-      profile);
-}
-
-}  // namespace
-
-class ExtensionInstallEventLogUploader::ReportQueueBuilderLeaderTracker
-    : public base::RefCountedThreadSafe<ReportQueueBuilderLeaderTracker> {
- public:
-  static scoped_refptr<ReportQueueBuilderLeaderTracker> Create() {
-    return base::WrapRefCounted(new ReportQueueBuilderLeaderTracker());
-  }
-
-  ::reporting::StatusOr<base::OnceCallback<void()>> RequestLeaderPromotion() {
-    bool expected = false;
-    if (!has_promoted_leader_.compare_exchange_strong(expected, true)) {
-      return ::reporting::Status(::reporting::error::RESOURCE_EXHAUSTED,
-                                 "Only one leader is allowed at a time.");
-    }
-    return base::BindOnce(&ReportQueueBuilderLeaderTracker::ReleaseLeader,
-                          this);
-  }
-
- private:
-  friend class base::RefCountedThreadSafe<ReportQueueBuilderLeaderTracker>;
-
-  ReportQueueBuilderLeaderTracker();
-  virtual ~ReportQueueBuilderLeaderTracker();
-
-  void ReleaseLeader() {
-    bool expected = true;
-    DCHECK(has_promoted_leader_.compare_exchange_strong(expected, false))
-        << "Leader wasn't set";
-  }
-
-  std::atomic<bool> has_promoted_leader_{false};
-};
 
 ExtensionInstallEventLogUploader::Delegate::~Delegate() {}
 
-ExtensionInstallEventLogUploader::ExtensionInstallEventLogUploader(
-    Profile* profile)
-    : InstallEventLogUploaderBase(profile),
-      leader_tracker_(ReportQueueBuilderLeaderTracker::Create()),
-      report_queue_builder_task_runner_(
-          base::ThreadPool::CreateSequencedTaskRunner({})),
-      get_report_queue_config_cb_(CreateReportQueueConfigGetter(profile)) {}
+// static
+std::unique_ptr<ExtensionInstallEventLogUploader>
+ExtensionInstallEventLogUploader::Create(Profile* profile) {
+  return base::WrapUnique(new ExtensionInstallEventLogUploader(
+      profile, ::reporting::ReportQueueFactory::CreateSpeculativeReportQueue(
+                   ::reporting::EventType::kUser,
+                   ::reporting::Destination::UPLOAD_EVENTS)));
+}
+
+// static
+std::unique_ptr<ExtensionInstallEventLogUploader>
+ExtensionInstallEventLogUploader::CreateForTest(
+    Profile* profile,
+    std::unique_ptr<::reporting::ReportQueue, base::OnTaskRunnerDeleter>
+        report_queue) {
+  return base::WrapUnique(
+      new ExtensionInstallEventLogUploader(profile, std::move(report_queue)));
+}
 
 ExtensionInstallEventLogUploader::~ExtensionInstallEventLogUploader() = default;
 
@@ -114,118 +54,12 @@
   delegate_ = delegate;
 }
 
-void ExtensionInstallEventLogUploader::SetReportQueue(
-    std::unique_ptr<::reporting::ReportQueue> report_queue) {
-  if (report_queue_ == nullptr) {
-    report_queue_ = std::move(report_queue);
-  }
-}
-
-void ExtensionInstallEventLogUploader::SetBuildReportQueueConfigurationForTests(
-    const std::string& dm_token) {
-  get_report_queue_config_cb_ = base::BindRepeating(
-      [](const std::string& dm_token,
-         ReportQueueConfigResultCallback complete_cb) {
-        std::move(complete_cb)
-            .Run(::reporting::ReportQueueConfiguration::Create(
-                dm_token, ::reporting::Destination::UPLOAD_EVENTS,
-                base::BindRepeating(
-                    []() { return ::reporting::Status::StatusOK(); })));
-      },
-      dm_token);
-}
-
-ExtensionInstallEventLogUploader::ReportQueueBuilderLeaderTracker::
-    ReportQueueBuilderLeaderTracker() = default;
-ExtensionInstallEventLogUploader::ReportQueueBuilderLeaderTracker::
-    ~ReportQueueBuilderLeaderTracker() = default;
-
-ExtensionInstallEventLogUploader::ReportQueueBuilder::ReportQueueBuilder(
-    base::OnceCallback<void(std::unique_ptr<::reporting::ReportQueue>,
-                            base::OnceCallback<void()>)> set_report_queue_cb,
-    GetReportQueueConfigCallback get_report_queue_config_cb,
-    scoped_refptr<ReportQueueBuilderLeaderTracker> leader_tracker,
-    base::OnceCallback<void(bool)> completion_cb,
-    scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner)
-    : ::reporting::TaskRunnerContext<bool>(std::move(completion_cb),
-                                           sequenced_task_runner),
-      set_report_queue_cb_(std::move(set_report_queue_cb)),
-      get_report_queue_config_cb_(std::move(get_report_queue_config_cb)),
-      leader_tracker_(leader_tracker) {}
-
-ExtensionInstallEventLogUploader::ReportQueueBuilder::~ReportQueueBuilder() =
-    default;
-
-void ExtensionInstallEventLogUploader::ReportQueueBuilder::OnStart() {
-  auto promo_result = leader_tracker_->RequestLeaderPromotion();
-  if (!promo_result.ok()) {
-    // There is already a ReportQueueBuilder building a ReportQueue - go
-    // ahead and exit.
-    Complete();
-    return;
-  }
-
-  release_leader_cb_ = std::move(promo_result.ValueOrDie());
-  BuildReportQueueConfig();
-}
-
-void ExtensionInstallEventLogUploader::ReportQueueBuilder::
-    BuildReportQueueConfig() {
-  std::move(get_report_queue_config_cb_)
-      .Run(base::BindOnce(&ReportQueueBuilder::OnReportQueueConfigResult,
-                          base::Unretained(this)));
-}
-
-void ExtensionInstallEventLogUploader::ReportQueueBuilder::
-    OnReportQueueConfigResult(
-        ReportQueueConfigResult report_queue_config_result) {
-  if (!report_queue_config_result.ok()) {
-    Complete();
-    return;
-  }
-  Schedule(&ReportQueueBuilder::BuildReportQueue, base::Unretained(this),
-           std::move(report_queue_config_result.ValueOrDie()));
-}
-
-void ExtensionInstallEventLogUploader::ReportQueueBuilder::BuildReportQueue(
-    std::unique_ptr<::reporting::ReportQueueConfiguration>
-        report_queue_config) {
-  ::reporting::ReportQueueProvider::CreateQueue(
-      std::move(report_queue_config),
-      base::BindOnce(&ReportQueueBuilder::OnReportQueueResult,
-                     base::Unretained(this)));
-}
-
-void ExtensionInstallEventLogUploader::ReportQueueBuilder::OnReportQueueResult(
-    ::reporting::StatusOr<std::unique_ptr<::reporting::ReportQueue>>
-        report_queue_result) {
-  if (!report_queue_result.ok()) {
-    Complete();
-    return;
-  }
-  Schedule(&ReportQueueBuilder::SetReportQueue, base::Unretained(this),
-           std::move(report_queue_result.ValueOrDie()));
-}
-
-void ExtensionInstallEventLogUploader::ReportQueueBuilder::SetReportQueue(
-    std::unique_ptr<::reporting::ReportQueue> report_queue) {
-  DCHECK(report_queue);
-  std::move(set_report_queue_cb_)
-      .Run(std::move(report_queue),
-           base::BindOnce(&ReportQueueBuilder::Complete,
-                          base::Unretained(this)));
-}
-
-void ExtensionInstallEventLogUploader::ReportQueueBuilder::Complete() {
-  Schedule(&ReportQueueBuilder::ReleaseLeader, base::Unretained(this));
-}
-
-void ExtensionInstallEventLogUploader::ReportQueueBuilder::ReleaseLeader() {
-  if (release_leader_cb_) {
-    std::move(release_leader_cb_).Run();
-  }
-  Response(true);
-}
+ExtensionInstallEventLogUploader::ExtensionInstallEventLogUploader(
+    Profile* profile,
+    std::unique_ptr<::reporting::ReportQueue, base::OnTaskRunnerDeleter>
+        report_queue)
+    : InstallEventLogUploaderBase(profile),
+      report_queue_(std::move(report_queue)) {}
 
 void ExtensionInstallEventLogUploader::CancelClientUpload() {
   weak_factory_.InvalidateWeakPtrs();
@@ -233,7 +67,7 @@
 
 void ExtensionInstallEventLogUploader::StartSerialization() {
   delegate_->SerializeExtensionLogForUpload(
-      base::BindOnce(&ExtensionInstallEventLogUploader::OnSerialized,
+      base::BindOnce(&ExtensionInstallEventLogUploader::EnqueueReport,
                      weak_factory_.GetWeakPtr()));
 }
 
@@ -253,56 +87,10 @@
       base::Milliseconds(retry_backoff_ms_));
 }
 
-void ExtensionInstallEventLogUploader::OnSerialized(
-    const em::ExtensionInstallReportRequest* report) {
-  // If report_queue_ hasn't been set, start a ReportQueueBuilder and call
-  // OnUploadDone(false). The upload will be attempted again in the future.
-  // If another ReportQueueBuilder starts while the previous one is running, it
-  // will exit early because the first ReportQueueBuilder will acquire leader
-  // role from ReportQueueBuilderLeaderTracker.
-  // If the ReportQueueBuilder fails to build, it will release the leader and
-  // the next ReportQueueBuilder will acquire the leader role. If a
-  // ReportQueueBuilder sets the report_queue_ after it has been checked here,
-  // a new ReportQueueBuilder will spawn, but will be unable to set the
-  // report_queue_.
-  if (report_queue_ == nullptr) {
-    auto set_report_queue_cb = base::BindOnce(
-        [](base::WeakPtr<ExtensionInstallEventLogUploader> uploader,
-           scoped_refptr<base::SingleThreadTaskRunner> task_runner,
-           std::unique_ptr<::reporting::ReportQueue> report_queue,
-           base::OnceCallback<void()> on_set_cb) {
-          auto call_uploader_with_report_queue_cb = base::BindOnce(
-              [](base::WeakPtr<ExtensionInstallEventLogUploader> uploader,
-                 std::unique_ptr<::reporting::ReportQueue> report_queue,
-                 base::OnceCallback<void()> on_set_cb) {
-                uploader->SetReportQueue(std::move(report_queue));
-              },
-              std::move(uploader), std::move(report_queue),
-              std::move(on_set_cb));
-          task_runner->PostTask(FROM_HERE,
-                                std::move(call_uploader_with_report_queue_cb));
-        },
-        weak_factory_.GetWeakPtr(), base::ThreadTaskRunnerHandle::Get());
-
-    auto completion_cb = base::BindOnce([](bool success) {
-      LOG_IF(WARNING, !success) << "ReportQueueBuilder was unsuccessful";
-    });
-
-    ::reporting::Start<ReportQueueBuilder>(
-        std::move(set_report_queue_cb), get_report_queue_config_cb_,
-        leader_tracker_, std::move(completion_cb),
-        report_queue_builder_task_runner_);
-
-    OnUploadDone(false);
-    return;
-  }
-  EnqueueReport(*report);
-}
-
 void ExtensionInstallEventLogUploader::EnqueueReport(
-    const em::ExtensionInstallReportRequest& report) {
+    const em::ExtensionInstallReportRequest* report) {
   base::Value context = ::reporting::GetContext(profile_);
-  base::Value event_list = ConvertExtensionProtoToValue(&report, context);
+  base::Value event_list = ConvertExtensionProtoToValue(report, context);
 
   base::Value value_report = RealtimeReportingJobConfiguration::BuildReport(
       std::move(event_list), std::move(context));
@@ -312,7 +100,7 @@
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           ash::switches::kExtensionInstallEventChromeLogForTests)) {
     for (const em::ExtensionInstallReport& extension_install_report :
-         report.extension_install_reports()) {
+         report->extension_install_reports()) {
       for (const em::ExtensionInstallReportLogEvent&
                extension_install_report_log_event :
            extension_install_report.logs()) {
diff --git a/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader.h b/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader.h
index 140a523..e654ac5 100644
--- a/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader.h
+++ b/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader.h
@@ -9,6 +9,7 @@
 
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "base/task/sequenced_task_runner.h"
 #include "chrome/browser/ash/policy/reporting/install_event_log_uploader_base.h"
 #include "components/reporting/client/report_queue.h"
 #include "components/reporting/client/report_queue_provider.h"
@@ -28,18 +29,6 @@
 // logs and the policy system which uploads them to the management server.
 class ExtensionInstallEventLogUploader : public InstallEventLogUploaderBase {
  public:
-  // Result of trying to build a |ReportQueueConfiguration|.
-  using ReportQueueConfigResult = ::reporting::StatusOr<
-      std::unique_ptr<::reporting::ReportQueueConfiguration>>;
-
-  // Callback for handling a |ReportQueueConfigResult|.
-  using ReportQueueConfigResultCallback =
-      base::OnceCallback<void(ReportQueueConfigResult)>;
-
-  // Callback for getting a |ReportQueueConfiguration|.
-  using GetReportQueueConfigCallback =
-      base::RepeatingCallback<void(ReportQueueConfigResultCallback)>;
-
   // The delegate that event logs will be retrieved from.
   class Delegate {
    public:
@@ -62,7 +51,18 @@
     virtual ~Delegate();
   };
 
-  explicit ExtensionInstallEventLogUploader(Profile* profile);
+  // Helper for creating a new |ExtensionInstallEventLogUploader| using the
+  // specified profile.
+  static std::unique_ptr<ExtensionInstallEventLogUploader> Create(
+      Profile* profile);
+
+  // Test helper for creating a new |ExtensionInstallEventLogUploader| using the
+  // specified profile and mock report queue.
+  static std::unique_ptr<ExtensionInstallEventLogUploader> CreateForTest(
+      Profile* profile,
+      std::unique_ptr<::reporting::ReportQueue, base::OnTaskRunnerDeleter>
+          report_queue);
+
   ~ExtensionInstallEventLogUploader() override;
 
   // Sets the delegate. The delegate must either outlive |this| or be explicitly
@@ -70,86 +70,11 @@
   // delegate cancels the pending log upload, if any.
   void SetDelegate(Delegate* delegate);
 
-  // Sets the report queue if it is not already set.
-  void SetReportQueue(std::unique_ptr<reporting::ReportQueue> report_queue);
-
-  // Meant to be used in tests for creating the ReportQueueConfiguration.
-  void SetBuildReportQueueConfigurationForTests(const std::string& dm_token);
-
  private:
-  // Ensures that only one ReportQueueBuilder is working at one time.
-  class ReportQueueBuilderLeaderTracker;
-
-  // ReportQueueBuilder instantiates a ReportQueue and uses
-  // |set_report_queue_cb| to set it in the ExtensionInstallEventLogUploader.
-  // ReportQueueBuilder ensures that only one ReportQueue instance is
-  // built for ExtensionInstallLogUploader.
-  // TODO: For testing there ideally there would be a way to capture the
-  // ReportQueueConfiguration prior to passing it to the ReportQueue.
-  class ReportQueueBuilder : public reporting::TaskRunnerContext<bool> {
-   public:
-    using SetReportQueueCallback =
-        base::OnceCallback<void(std::unique_ptr<reporting::ReportQueue>,
-                                base::OnceCallback<void()>)>;
-
-    ReportQueueBuilder(
-        SetReportQueueCallback set_report_queue_cb,
-        GetReportQueueConfigCallback get_report_queue_config_cb,
-        scoped_refptr<ReportQueueBuilderLeaderTracker> leader_tracker,
-        base::OnceCallback<void(bool)> completion_cb,
-        scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner);
-
-   private:
-    ~ReportQueueBuilder() override;
-
-    // |OnStart| requests leadership promotion from the provided
-    // |leader_tracker|. If there is already a leader, |OnStart| will exit.
-    // Otherwise it will call |BuildReportQueue|.
-    void OnStart() override;
-
-    // Posts the task for building the ReportQueueConfiguration used to
-    // instantiate the ReportQueue.
-    void BuildReportQueueConfig();
-
-    // Handles the result of the task posted in |BuildReportQueueConfig|.
-    void OnReportQueueConfigResult(ReportQueueConfigResult report_queue_result);
-
-    // |BuildReportQueue| uses the |report_queue_config| to build a ReportQueue
-    // with ReportQueueProvider::CreateQueue. Sets |OnReportQueueResult| as
-    // the completion callback for |ReportQueueProvider::CreateQueue|.
-    void BuildReportQueue(std::unique_ptr<::reporting::ReportQueueConfiguration>
-                              report_queue_config);
-
-    // |OnReportQueueResult| will evaluate |report_queue_result|. If it is not
-    // an OK status, it exits the builder with a |Complete| call. On an OK
-    // status it |Schedule|s SetReportQueue.
-    void OnReportQueueResult(
-        reporting::StatusOr<std::unique_ptr<reporting::ReportQueue>>
-            report_queue_result);
-
-    // SetReportQueue will call |set_report_queue_cb_| with the provided
-    // |report_queue|.
-    void SetReportQueue(std::unique_ptr<reporting::ReportQueue> report_queue);
-
-    // |Schedules| |ReleaseLeader|.
-    void Complete();
-
-    // Releases the leader lock if it is held, and then calls |Response|.
-    void ReleaseLeader();
-
-    // Callback for setting the ReportQueue in the calling
-    // |ExtensionInstallEventLogUploader|.
-    SetReportQueueCallback set_report_queue_cb_;
-
-    // Callback for creating the |ReportQueueConfiguration|.
-    GetReportQueueConfigCallback get_report_queue_config_cb_;
-
-    // |leader_tracker_| is used to ensure that only one ReportQueueBuilder is
-    // active at a time.
-    scoped_refptr<ReportQueueBuilderLeaderTracker> leader_tracker_;
-
-    base::OnceCallback<void()> release_leader_cb_;
-  };
+  explicit ExtensionInstallEventLogUploader(
+      Profile* profile,
+      std::unique_ptr<::reporting::ReportQueue, base::OnTaskRunnerDeleter>
+          report_queue);
 
   // InstallEventLogUploaderBase:
   void CheckDelegateSet() override;
@@ -158,14 +83,10 @@
   void OnUploadSuccess() override;
   void StartSerialization() override;
 
-  // Callback invoked by the delegate with the extension logs to be uploaded in
-  // |report|. Forwards the logs to the client for upload.
-  void OnSerialized(
-      const enterprise_management::ExtensionInstallReportRequest* report);
-
-  // Enqueues the report for upload.
+  // Enqueues the report for upload, and is invoked by the delegate with the
+  // extension logs to be uploaded in |report|.
   void EnqueueReport(
-      const enterprise_management::ExtensionInstallReportRequest& report);
+      const enterprise_management::ExtensionInstallReportRequest* report);
 
   // Handles the status of the report enqueue.
   void OnEnqueueDone(reporting::Status status);
@@ -173,18 +94,9 @@
   // The delegate that provides serialized logs to be uploaded.
   Delegate* delegate_ = nullptr;
 
-  // ReportQueueBuilderLeaderTracker for building the ReportQueue,
-  // passed to each ReportQueueBuilder in order to track which is the leader.
-  scoped_refptr<ReportQueueBuilderLeaderTracker> leader_tracker_;
-
-  // SequencedTaskRunenr for building the ReportQueue.
-  scoped_refptr<base::SequencedTaskRunner> report_queue_builder_task_runner_;
-
-  // Callback to generate a ReportQueueConfiguration.
-  GetReportQueueConfigCallback get_report_queue_config_cb_;
-
-  // ReportQueue for uploading events.
-  std::unique_ptr<reporting::ReportQueue> report_queue_;
+  // Speculative report queue for uploading events.
+  const std::unique_ptr<::reporting::ReportQueue, base::OnTaskRunnerDeleter>
+      report_queue_;
 
   // Weak pointer factory for invalidating callbacks passed to the delegate and
   // scheduled retries when the upload request is canceled or |this| is
diff --git a/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader_unittest.cc b/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader_unittest.cc
index d1d7805..5ce2bf5 100644
--- a/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/extension_install_event_log_uploader_unittest.cc
@@ -11,9 +11,11 @@
 
 #include "base/json/json_string_value_serializer.h"
 #include "base/memory/ref_counted.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/test/gmock_move_support.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_mock_time_task_runner.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/values.h"
@@ -96,13 +98,16 @@
   }
 
   void CreateUploader() {
-    uploader_ = std::make_unique<ExtensionInstallEventLogUploader>(
-        /*profile=*/nullptr);
-    uploader_->SetDelegate(&delegate_);
-
-    auto mock_report_queue = std::make_unique<reporting::MockReportQueue>();
+    ASSERT_TRUE(base::SequencedTaskRunnerHandle::IsSet());
+    auto mock_report_queue = std::unique_ptr<::reporting::MockReportQueue,
+                                             base::OnTaskRunnerDeleter>(
+        new ::reporting::MockReportQueue(),
+        base::OnTaskRunnerDeleter(base::SequencedTaskRunnerHandle::Get()));
     mock_report_queue_ = mock_report_queue.get();
-    uploader_->SetReportQueue(std::move(mock_report_queue));
+
+    uploader_ = ExtensionInstallEventLogUploader::CreateForTest(
+        /*profile=*/nullptr, std::move(mock_report_queue));
+    uploader_->SetDelegate(&delegate_);
   }
 
   void CompleteSerialize() {
diff --git a/chrome/browser/ash/psi_memory_metrics.cc b/chrome/browser/ash/psi_memory_metrics.cc
index 4c9276f..95b4fac 100644
--- a/chrome/browser/ash/psi_memory_metrics.cc
+++ b/chrome/browser/ash/psi_memory_metrics.cc
@@ -34,49 +34,19 @@
 
 namespace {
 
-// Default interval between externally-reported metrics being collected.
-constexpr base::TimeDelta kMinCollectionInterval = base::Seconds(10);
-constexpr base::TimeDelta kMidCollectionInterval = base::Seconds(60);
-constexpr base::TimeDelta kMaxCollectionInterval = base::Seconds(300);
-
-constexpr base::TimeDelta kDefaultCollectionInterval = kMinCollectionInterval;
-
 // Name of the histogram that represents the success and various failure modes
 // for parsing PSI memory data.
-const char kParsePSIMemoryHistogramName[] = "ChromeOS.CWP.ParsePSIMemory";
 const char kPSIMemoryPressureSomeName[] = "ChromeOS.CWP.PSIMemPressure.Some";
 const char kPSIMemoryPressureFullName[] = "ChromeOS.CWP.PSIMemPressure.Full";
 
 // File path that stores PSI Memory data.
 const char kPSIMemoryPath[] = "/proc/pressure/memory";
 
-constexpr base::StringPiece kContentPrefixSome = "some";
-constexpr base::StringPiece kContentPrefixFull = "full";
-constexpr base::StringPiece kContentTerminator = " total=";
-constexpr base::StringPiece kMetricTerminator = " ";
-
-const char kMetricPrefixFormat[] = "avg%" PRId64 "=";
-
-// Values as logged in the histogram for memory pressure.
-constexpr int kMemPressureMin = 1;  // As 0 is for underflow.
-constexpr int kMemPressureExclusiveMax = 10000;
-constexpr int kMemPressureHistogramBuckets = 100;
-
 }  // namespace
 
 PSIMemoryMetrics::PSIMemoryMetrics(uint32_t period)
-    : memory_psi_file_(kPSIMemoryPath),
-      collection_interval_(kDefaultCollectionInterval) {
-  if (period == kMinCollectionInterval.InSeconds() ||
-      period == kMidCollectionInterval.InSeconds() ||
-      period == kMaxCollectionInterval.InSeconds()) {
-    collection_interval_ = base::Seconds(period);
-  } else {
-    LOG(WARNING) << "Ignoring invalid interval [" << period << "]";
-  }
-
-  metric_prefix_ =
-      base::StringPrintf(kMetricPrefixFormat, collection_interval_.InSeconds());
+    : memory_psi_file_(kPSIMemoryPath), parser_(period) {
+  collection_interval_ = base::Seconds(parser_.GetPeriod());
 
   runner_ = base::ThreadPool::CreateSequencedTaskRunner(
       {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
@@ -104,103 +74,28 @@
                     base::BindOnce(&PSIMemoryMetrics::CancelTimer, this));
 }
 
-int PSIMemoryMetrics::GetMetricValue(const std::string& content,
-                                     size_t start,
-                                     size_t end) {
-  size_t value_start;
-  size_t value_end;
-  if (!internal::FindMiddleString(content, start, metric_prefix_,
-                                  kMetricTerminator, &value_start,
-                                  &value_end)) {
-    return -1;
-  }
-  if (value_end > end) {
-    return -1;  // Out of bounds of the search area.
-  }
-
-  double n;
-  const base::StringPiece metric_value_text(content.c_str() + value_start,
-                                            value_end - value_start);
-  if (!base::StringToDouble(metric_value_text, &n)) {
-    return -1;  // Unable to convert string to number
-  }
-
-  // Want to multiply by 100, but to avoid integer truncation,
-  // do best-effort rounding.
-  const int preround = static_cast<int>(n * 1000);
-  return (preround + 5) / 10;
-}
-
-PSIMemoryMetrics::ParsePSIMemStatus PSIMemoryMetrics::ParseMetrics(
-    const std::string& content,
-    int* metric_some,
-    int* metric_full) {
-  size_t str_some_start;
-  size_t str_some_end;
-  size_t str_full_start;
-  size_t str_full_end;
-
-  DCHECK_NE(metric_some, nullptr);
-  DCHECK_NE(metric_full, nullptr);
-
-  if (!internal::FindMiddleString(content, 0, kContentPrefixSome,
-                                  kContentTerminator, &str_some_start,
-                                  &str_some_end)) {
-    return ParsePSIMemStatus::kUnexpectedDataFormat;
-  }
-
-  if (!internal::FindMiddleString(content,
-                                  str_some_end + kContentTerminator.length(),
-                                  kContentPrefixFull, kContentTerminator,
-                                  &str_full_start, &str_full_end)) {
-    return ParsePSIMemStatus::kUnexpectedDataFormat;
-  }
-
-  int compute_some = GetMetricValue(content, str_some_start, str_some_end);
-  if (compute_some < 0) {
-    return ParsePSIMemStatus::kInvalidMetricFormat;
-  }
-
-  int compute_full = GetMetricValue(content, str_full_start, str_full_end);
-  if (compute_full < 0) {
-    return ParsePSIMemStatus::kInvalidMetricFormat;
-  }
-
-  *metric_some = compute_some;
-  *metric_full = compute_full;
-
-  return ParsePSIMemStatus::kSuccess;
-}
-
-PSIMemoryMetrics::ParsePSIMemStatus PSIMemoryMetrics::CollectEvents() {
-  // Example file content:
-  // some avg10=0.00 avg60=0.00 avg300=0.00 total=417963
-  //  full avg10=0.00 avg60=0.00 avg300=0.00 total=205933
-  // we will pick one of the columns depending on the colleciton period set
+void PSIMemoryMetrics::CollectEvents() {
   std::string content;
   int metric_some;
   int metric_full;
-  PSIMemoryMetrics::ParsePSIMemStatus stat;
 
   if (!base::ReadFileToString(base::FilePath(memory_psi_file_), &content)) {
-    return ParsePSIMemStatus::kReadFileFailed;
+    parser_.LogParseStatus(metrics::ParsePSIMemStatus::kReadFileFailed);
+    return;
   }
 
-  stat = ParseMetrics(content, &metric_some, &metric_full);
-
-  if (stat != ParsePSIMemStatus::kSuccess) {
-    return stat;
+  auto stat = parser_.ParseMetrics(content, &metric_some, &metric_full);
+  parser_.LogParseStatus(stat);  // Log success and failure, for histograms.
+  if (stat != metrics::ParsePSIMemStatus::kSuccess) {
+    return;
   }
 
-  base::UmaHistogramCustomCounts(kPSIMemoryPressureSomeName, metric_some,
-                                 kMemPressureMin, kMemPressureExclusiveMax,
-                                 kMemPressureHistogramBuckets);
-
-  base::UmaHistogramCustomCounts(kPSIMemoryPressureFullName, metric_full,
-                                 kMemPressureMin, kMemPressureExclusiveMax,
-                                 kMemPressureHistogramBuckets);
-
-  return ParsePSIMemStatus::kSuccess;
+  base::UmaHistogramCustomCounts(
+      kPSIMemoryPressureSomeName, metric_some, metrics::kMemPressureMin,
+      metrics::kMemPressureExclusiveMax, metrics::kMemPressureHistogramBuckets);
+  base::UmaHistogramCustomCounts(
+      kPSIMemoryPressureFullName, metric_full, metrics::kMemPressureMin,
+      metrics::kMemPressureExclusiveMax, metrics::kMemPressureHistogramBuckets);
 }
 
 void PSIMemoryMetrics::CollectEventsAndReschedule() {
@@ -208,12 +103,7 @@
     return;
   }
 
-  ParsePSIMemStatus stat = CollectEvents();
-  constexpr int statCeiling =
-      static_cast<int>(ParsePSIMemStatus::kMaxValue) + 1;
-  base::UmaHistogramExactLinear(kParsePSIMemoryHistogramName,
-                                static_cast<int>(stat), statCeiling);
-
+  CollectEvents();
   ScheduleCollector();
 }
 
@@ -228,34 +118,4 @@
       collection_interval_);
 }
 
-namespace internal {
-
-bool FindMiddleString(const base::StringPiece& content,
-                      size_t search_start,
-                      const base::StringPiece& prefix,
-                      const base::StringPiece& suffix,
-                      size_t* start,
-                      size_t* end) {
-  DCHECK_NE(start, nullptr);
-  DCHECK_NE(end, nullptr);
-
-  size_t compute_start = content.find(prefix, search_start);
-  if (compute_start == std::string::npos) {
-    return false;
-  }
-  compute_start += prefix.length();
-
-  size_t compute_end = content.find(suffix, compute_start);
-  if (compute_end == std::string::npos) {
-    return false;
-  }
-
-  *start = compute_start;
-  *end = compute_end;
-
-  return true;
-}
-
-}  // namespace internal
-
 }  // namespace ash
diff --git a/chrome/browser/ash/psi_memory_metrics.h b/chrome/browser/ash/psi_memory_metrics.h
index 3e48f21..2c4509f1 100644
--- a/chrome/browser/ash/psi_memory_metrics.h
+++ b/chrome/browser/ash/psi_memory_metrics.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_ASH_PSI_MEMORY_METRICS_H_
 
 #include <string>
+#include <vector>
 
 #include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
@@ -13,6 +14,7 @@
 #include "base/task/delayed_task_handle.h"
 #include "base/task/task_runner.h"
 #include "base/time/time.h"
+#include "components/metrics/psi_memory_parser.h"
 
 namespace ash {
 
@@ -44,29 +46,9 @@
 
   // Friend it so it can see private members for testing
   friend class PSIMemoryMetricsTest;
-  FRIEND_TEST_ALL_PREFIXES(PSIMemoryMetricsTest, CustomInterval);
-  FRIEND_TEST_ALL_PREFIXES(PSIMemoryMetricsTest, InvalidInterval);
   FRIEND_TEST_ALL_PREFIXES(PSIMemoryMetricsTest, SunnyDay1);
   FRIEND_TEST_ALL_PREFIXES(PSIMemoryMetricsTest, SunnyDay2);
   FRIEND_TEST_ALL_PREFIXES(PSIMemoryMetricsTest, SunnyDay3);
-  FRIEND_TEST_ALL_PREFIXES(PSIMemoryMetricsTest, InternalsA);
-  FRIEND_TEST_ALL_PREFIXES(PSIMemoryMetricsTest, InternalsB);
-  FRIEND_TEST_ALL_PREFIXES(PSIMemoryMetricsTest, InternalsC);
-  FRIEND_TEST_ALL_PREFIXES(PSIMemoryMetricsTest, InternalsD);
-  FRIEND_TEST_ALL_PREFIXES(PSIMemoryMetricsTest, InternalsE);
-
-  // Enumeration representing success and various failure modes for parsing PSI
-  // memory data. These values are persisted to logs. Entries should not be
-  // renumbered and numeric values should never be reused.
-  enum class ParsePSIMemStatus {
-    kSuccess,
-    kReadFileFailed,
-    kUnexpectedDataFormat,
-    kInvalidMetricFormat,
-    kParsePSIValueFailed,
-    // Magic constant used by the histogram macros.
-    kMaxValue = kParsePSIValueFailed,
-  };
 
   static scoped_refptr<PSIMemoryMetrics> CreateForTesting(
       uint32_t period,
@@ -80,26 +62,7 @@
   // Friend it so it can call our private destructor.
   friend class base::RefCountedThreadSafe<PSIMemoryMetrics>;
 
-  // Retrieves one metric value from |content|, for the currently configured
-  // metrics category (10, 60 or 300 seconds).
-  // Only considers the substring between |start| (inclusive) and |end|
-  // (exclusive).
-  // Returns the floating-point string representation converted into an integer
-  // which has the value multiplied by 100 - (10.20 = 1020), for
-  // histogram usage.
-  int GetMetricValue(const std::string& content, size_t start, size_t end);
-
-  // Parses PSI memory pressure from  |content|, for the currently configured
-  // metrics category (10, 60 or 300 seconds).
-  // The some and full values are output to |metricSome| and |metricFull|,
-  // respectively.
-  // Returns status of the parse operation - ParsePSIMemStatus::kSuccess
-  // or error code otherwise.
-  ParsePSIMemStatus ParseMetrics(const std::string& content,
-                                 int* metric_some,
-                                 int* metric_full);
-
-  ParsePSIMemStatus CollectEvents();
+  void CollectEvents();
 
   // Calls CollectEvents and reschedules a future collection.
   void CollectEventsAndReschedule();
@@ -112,8 +75,8 @@
 
   // Interval between metrics collection.
   std::string memory_psi_file_;
+  metrics::PSIMemoryParser parser_;
   base::TimeDelta collection_interval_;
-  std::string metric_prefix_;
 
   // Task controllers/monitors.
   scoped_refptr<base::SequencedTaskRunner> runner_;
@@ -121,25 +84,6 @@
   base::DelayedTaskHandle last_timer_;
 };
 
-// Items in internal are - as the name implies - NOT for outside consumption.
-// Defined here to allow access to unit test.
-namespace internal {
-
-// Finds the bounds for a substring of |content| which is sandwiched between
-// the given |prefix| and |suffix| indices. Search only considers
-// the portion of the string starting from |search_start|.
-// Returns false if the prefix and/or suffix are not found, true otherwise.
-// |start| and |end| are output parameters populated with the indices
-// for the middle string.
-bool FindMiddleString(const base::StringPiece& content,
-                      size_t search_start,
-                      const base::StringPiece& prefix,
-                      const base::StringPiece& suffix,
-                      size_t* start,
-                      size_t* end);
-
-}  // namespace internal
-
 }  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_PSI_MEMORY_METRICS_H_
diff --git a/chrome/browser/ash/psi_memory_metrics_unittest.cc b/chrome/browser/ash/psi_memory_metrics_unittest.cc
index 116458e..fa3ae5a 100644
--- a/chrome/browser/ash/psi_memory_metrics_unittest.cc
+++ b/chrome/browser/ash/psi_memory_metrics_unittest.cc
@@ -26,11 +26,6 @@
     "some avg10=23.10 avg60=5.06 avg300=15.10 total=417963\n"
     "full avg10=9.00 avg60=19.20 avg300=3.23 total=205933\n";
 
-// Number of decimals not consistent, slightly malformed - but acceptable.
-const char kFileContents2[] =
-    "some avg10=24 avg60=5.06 avg300=15.10 total=417963\n"
-    "full avg10=9.2 avg60=19.20 avg300=3.23 total=205933\n";
-
 }  // namespace
 
 class PSIMemoryMetricsTest : public testing::Test {
@@ -48,7 +43,6 @@
   const base::FilePath& GetTestFileName() { return testfilename_; }
   base::HistogramTester& Histograms() { return histogram_tester_; }
   scoped_refptr<PSIMemoryMetrics> Cit() { return cit_; }
-  const std::string& GetMetricPrefix() { return cit_->metric_prefix_; }
   content::BrowserTaskEnvironment& task_environment() {
     return task_environment_;
   }
@@ -64,18 +58,6 @@
   base::HistogramTester histogram_tester_;
 };
 
-TEST_F(PSIMemoryMetricsTest, CustomInterval) {
-  Init(60);
-
-  EXPECT_EQ(base::Seconds(60), GetCollection());
-}
-
-TEST_F(PSIMemoryMetricsTest, InvalidInterval) {
-  Init(15);
-
-  EXPECT_EQ(base::Seconds(10), GetCollection());
-}
-
 TEST_F(PSIMemoryMetricsTest, SunnyDay1) {
   Init(10);
   int bytes_written = base::WriteFile(GetTestFileName(), kFileContents1,
@@ -192,91 +174,4 @@
                                  323 /*bucket*/, 1 /*count*/);
 }
 
-TEST_F(PSIMemoryMetricsTest, InternalsA) {
-  Init(10);
-
-  std::string testContent1 = "prefix" + GetMetricPrefix() + "9.37 suffix";
-  EXPECT_EQ(base::Seconds(10), GetCollection());
-
-  size_t s = 0;
-  size_t e = 0;
-
-  EXPECT_EQ(false, internal::FindMiddleString(testContent1, 0, "nothere",
-                                              "suffix", &s, &e));
-
-  EXPECT_EQ(false, internal::FindMiddleString(testContent1, 0, "prefix",
-                                              "notthere", &s, &e));
-
-  EXPECT_EQ(true, internal::FindMiddleString(testContent1, 0, "prefix",
-                                             "suffix", &s, &e));
-  EXPECT_EQ(6, s);
-  EXPECT_EQ(17, e);
-
-  EXPECT_EQ(937, Cit()->GetMetricValue(testContent1, s, e));
-
-  std::string testContent2 = "extra " + testContent1;
-  EXPECT_EQ(true, internal::FindMiddleString(testContent2, 0, "prefix",
-                                             "suffix", &s, &e));
-  EXPECT_EQ(12, s);
-  EXPECT_EQ(23, e);
-
-  EXPECT_EQ(937, Cit()->GetMetricValue(testContent2, s, e));
-}
-
-TEST_F(PSIMemoryMetricsTest, InternalsB) {
-  Init(300);
-
-  int msome;
-  int mfull;
-  PSIMemoryMetrics::ParsePSIMemStatus stat;
-
-  stat = Cit()->ParseMetrics(kFileContents1, &msome, &mfull);
-
-  EXPECT_EQ(PSIMemoryMetrics::ParsePSIMemStatus::kSuccess, stat);
-  EXPECT_EQ(1510, msome);
-  EXPECT_EQ(323, mfull);
-}
-
-TEST_F(PSIMemoryMetricsTest, InternalsC) {
-  Init(60);
-
-  int msome;
-  int mfull;
-  PSIMemoryMetrics::ParsePSIMemStatus stat;
-
-  stat = Cit()->ParseMetrics(kFileContents1, &msome, &mfull);
-
-  EXPECT_EQ(PSIMemoryMetrics::ParsePSIMemStatus::kSuccess, stat);
-  EXPECT_EQ(506, msome);
-  EXPECT_EQ(1920, mfull);
-}
-
-TEST_F(PSIMemoryMetricsTest, InternalsD) {
-  Init(10);
-
-  int msome;
-  int mfull;
-  PSIMemoryMetrics::ParsePSIMemStatus stat;
-
-  stat = Cit()->ParseMetrics(kFileContents1, &msome, &mfull);
-
-  EXPECT_EQ(PSIMemoryMetrics::ParsePSIMemStatus::kSuccess, stat);
-  EXPECT_EQ(2310, msome);
-  EXPECT_EQ(900, mfull);
-}
-
-TEST_F(PSIMemoryMetricsTest, InternalsE) {
-  Init(10);
-
-  int msome;
-  int mfull;
-  PSIMemoryMetrics::ParsePSIMemStatus stat;
-
-  stat = Cit()->ParseMetrics(kFileContents2, &msome, &mfull);
-
-  EXPECT_EQ(PSIMemoryMetrics::ParsePSIMemStatus::kSuccess, stat);
-  EXPECT_EQ(2400, msome);
-  EXPECT_EQ(920, mfull);
-}
-
 }  // namespace ash
diff --git a/chrome/browser/autofill/autofill_interactive_uitest.cc b/chrome/browser/autofill/autofill_interactive_uitest.cc
index 3d02cc3..0b2e044a 100644
--- a/chrome/browser/autofill/autofill_interactive_uitest.cc
+++ b/chrome/browser/autofill/autofill_interactive_uitest.cc
@@ -86,6 +86,16 @@
 #include "ui/events/keycodes/keyboard_code_conversion.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(ENABLE_EXTENSIONS)
+// Includes for ChromeVox accessibility tests.
+#include "chrome/browser/ash/accessibility/accessibility_manager.h"
+#include "chrome/browser/ash/accessibility/speech_monitor.h"
+#include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "extensions/browser/browsertest_util.h"
+#include "ui/base/test/ui_controls.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(ENABLE_EXTENSIONS)
+
 using base::ASCIIToUTF16;
 using content::URLLoaderInterceptor;
 
@@ -3636,4 +3646,94 @@
                          AutofillDynamicFormReplacementInteractiveTest,
                          testing::Bool());
 
+// ChromeVox is only available on ChromeOS.
+#if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(ENABLE_EXTENSIONS)
+
+class AutofillChromeVoxTest : public AutofillInteractiveTestBase {
+ public:
+  AutofillChromeVoxTest() = default;
+  ~AutofillChromeVoxTest() override = default;
+
+  void TearDownOnMainThread() override {
+    // Unload the ChromeVox extension so the browser doesn't try to respond to
+    // in-flight requests during test shutdown. https://crbug.com/923090
+    ash::AccessibilityManager::Get()->EnableSpokenFeedback(false);
+    AutomationManagerAura::GetInstance()->Disable();
+  }
+
+  void EnableChromeVox() {
+    // Test setup.
+    // Enable ChromeVox, disable earcons and wait for key mappings to be
+    // fetched.
+    ASSERT_FALSE(ash::AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
+    // TODO(accessibility): fix console error/warnings and insantiate
+    // |console_observer_| here.
+
+    // Load ChromeVox and block until it's fully loaded.
+    ash::AccessibilityManager::Get()->EnableSpokenFeedback(true);
+    sm_.ExpectSpeechPattern("*");
+    sm_.Call([this]() { DisableEarcons(); });
+  }
+
+  void DisableEarcons() {
+    // Playing earcons from within a test is not only annoying if you're
+    // running the test locally, but seems to cause crashes
+    // (http://crbug.com/396507). Work around this by just telling
+    // ChromeVox to not ever play earcons (prerecorded sound effects).
+    extensions::browsertest_util::ExecuteScriptInBackgroundPageNoWait(
+        browser()->profile(), extension_misc::kChromeVoxExtensionId,
+        "ChromeVox.earcons.playEarcon = function() {};");
+  }
+
+  ash::test::SpeechMonitor sm_;
+};
+
+// Ensure that autofill suggestions are properly read out via ChromeVox.
+// This is a regressions test for crbug.com/1208913.
+IN_PROC_BROWSER_TEST_F(AutofillChromeVoxTest,
+                       TestNotificationOfAutofillDropdown) {
+  CreateTestProfile();
+
+  // Load the test page.
+  SetTestUrlResponse(kTestShippingFormString);
+  ASSERT_NO_FATAL_FAILURE(
+      ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl())));
+
+  EnableChromeVox();
+  content::EnableAccessibilityForWebContents(web_contents());
+
+  // The following contains a sequence of calls to
+  // sm_.ExpectSpeechPattern() and test_delegate()->Wait(). It is essential
+  // to first flush the expected speech patterns, otherwise the two functions
+  // start incompatible RunLoops.
+  sm_.ExpectSpeechPattern("Web Content");
+  sm_.Call([this]() {
+    content::WaitForAccessibilityTreeToContainNodeWithName(web_contents(),
+                                                           "First name:");
+    web_contents()->Focus();
+    test_delegate()->SetExpectations({ObservedUiEvents::kSuggestionShown});
+    FocusFieldByName("firstname");
+  });
+  sm_.ExpectSpeechPattern("First name:");
+  sm_.ExpectSpeechPattern("Edit text");
+  sm_.ExpectSpeechPattern("Region");
+  // Wait for suggestions popup to show up. This needs to happen before we
+  // simulate the cursor down key press.
+  sm_.Call([this]() { test_delegate()->Wait(); });
+  sm_.Call([this]() {
+    test_delegate()->SetExpectations({ObservedUiEvents::kPreviewFormData});
+    ASSERT_TRUE(
+        ui_controls::SendKeyPress(browser()->window()->GetNativeWindow(),
+                                  ui::VKEY_DOWN, false, false, false, false));
+  });
+  sm_.ExpectSpeechPattern("Autofill menu opened");
+  sm_.ExpectSpeechPattern("Milton 4120 Freidrich Lane");
+  sm_.ExpectSpeechPattern("List item");
+  sm_.ExpectSpeechPattern("1 of 2");
+  sm_.Call([this]() { test_delegate()->Wait(); });
+  sm_.Replay();
+}
+
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(ENABLE_EXTENSIONS)
+
 }  // namespace autofill
diff --git a/chrome/browser/browser_keyevents_browsertest.cc b/chrome/browser/browser_keyevents_browsertest.cc
index 3afc64c..7420f34 100644
--- a/chrome/browser/browser_keyevents_browsertest.cc
+++ b/chrome/browser/browser_keyevents_browsertest.cc
@@ -20,8 +20,6 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "content/public/browser/notification_registrar.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
@@ -94,14 +92,12 @@
 }
 
 // A class to help wait for the finish of a key event test.
-class TestFinishObserver : public content::NotificationObserver {
+class TestFinishObserver : public content::WebContentsObserver {
  public:
   explicit TestFinishObserver(content::WebContents* web_contents)
-      : finished_(false), waiting_(false) {
-    registrar_.Add(this,
-                   content::NOTIFICATION_DOM_OPERATION_RESPONSE,
-                   content::Source<content::WebContents>(web_contents));
-  }
+      : content::WebContentsObserver(web_contents),
+        finished_(false),
+        waiting_(false) {}
 
   TestFinishObserver(const TestFinishObserver&) = delete;
   TestFinishObserver& operator=(const TestFinishObserver&) = delete;
@@ -115,14 +111,11 @@
     return finished_;
   }
 
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override {
-    DCHECK(type == content::NOTIFICATION_DOM_OPERATION_RESPONSE);
-    content::Details<std::string> dom_op_result(details);
+  void DomOperationResponse(content::RenderFrameHost* render_frame_host,
+                            const std::string& dom_op_result) override {
     // We might receive responses for other script execution, but we only
     // care about the test finished message.
-    if (*dom_op_result.ptr() == "\"FINISHED\"") {
+    if (dom_op_result == "\"FINISHED\"") {
       finished_ = true;
       if (waiting_)
         base::RunLoop::QuitCurrentWhenIdleDeprecated();
@@ -132,7 +125,6 @@
  private:
   bool finished_;
   bool waiting_;
-  content::NotificationRegistrar registrar_;
 };
 
 class BrowserKeyEventsTest : public InProcessBrowserTest {
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 6a21319..80e960e 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -343,6 +343,10 @@
 void BindCommerceHintObserver(
     content::RenderFrameHost* const frame_host,
     mojo::PendingReceiver<cart::mojom::CommerceHintObserver> receiver) {
+  DCHECK(!frame_host->GetParentOrOuterDocument());
+  if (frame_host->GetParentOrOuterDocument())
+    return;
+
   // Cart is not available for non-signin single-profile users.
   Profile* profile = Profile::FromBrowserContext(
       frame_host->GetProcess()->GetBrowserContext());
@@ -548,6 +552,8 @@
       base::BindRepeating(&BindImageAnnotator));
 
 #if !defined(OS_ANDROID)
+  // We should not request this mojo interface's binding for the subframes in
+  // the renderer.
   if (base::FeatureList::IsEnabled(ntp_features::kNtpChromeCartModule) &&
       !render_frame_host->GetParent()) {
     map->Add<cart::mojom::CommerceHintObserver>(
diff --git a/chrome/browser/enterprise/util/affiliation.cc b/chrome/browser/enterprise/util/affiliation.cc
index 408fdb2..f84b266 100644
--- a/chrome/browser/enterprise/util/affiliation.cc
+++ b/chrome/browser/enterprise/util/affiliation.cc
@@ -10,10 +10,20 @@
 #include "chrome/browser/profiles/profile.h"
 #include "components/policy/core/common/cloud/affiliation.h"
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "components/policy/core/common/policy_loader_lacros.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
 namespace chrome {
 namespace enterprise_util {
 
 bool IsProfileAffiliated(Profile* profile) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  if (profile->IsMainProfile()) {
+    return policy::PolicyLoaderLacros::IsMainUserAffiliated();
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
   return policy::IsAffiliated(
       profile->GetProfilePolicyConnector()->user_affiliation_ids(),
       g_browser_process->browser_policy_connector()->device_affiliation_ids());
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow.cc b/chrome/browser/extensions/api/identity/web_auth_flow.cc
index 85dda1ca..d8f84a8 100644
--- a/chrome/browser/extensions/api/identity/web_auth_flow.cc
+++ b/chrome/browser/extensions/api/identity/web_auth_flow.cc
@@ -223,16 +223,14 @@
 
 void WebAuthFlow::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
-  // TODO(https://crbug.com/1218946): With MPArch there may be multiple main
-  // frames. This caller was converted automatically to the primary main frame
-  // to preserve its semantics. Follow up to confirm correctness.
   if (navigation_handle->IsInPrimaryMainFrame())
     BeforeUrlLoaded(navigation_handle->GetURL());
 }
 
 void WebAuthFlow::DidRedirectNavigation(
     content::NavigationHandle* navigation_handle) {
-  BeforeUrlLoaded(navigation_handle->GetURL());
+  if (navigation_handle->IsInPrimaryMainFrame())
+    BeforeUrlLoaded(navigation_handle->GetURL());
 }
 
 void WebAuthFlow::DidFinishNavigation(
@@ -240,9 +238,6 @@
   // Websites may create and remove <iframe> during the auth flow. In
   // particular, to integrate CAPTCHA tests. Chrome shouldn't abort the auth
   // flow if a navigation failed in a sub-frame. https://crbug.com/1049565.
-  // TODO(https://crbug.com/1218946): With MPArch there may be multiple main
-  // frames. This caller was converted automatically to the primary main frame
-  // to preserve its semantics. Follow up to confirm correctness.
   if (!navigation_handle->IsInPrimaryMainFrame())
     return;
 
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc b/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
new file mode 100644
index 0000000..45058d6a
--- /dev/null
+++ b/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
@@ -0,0 +1,150 @@
+// 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/extensions/api/identity/web_auth_flow.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/fenced_frame_test_util.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+class MockWebAuthFlowDelegate : public extensions::WebAuthFlow::Delegate {
+ public:
+  MOCK_METHOD(void, OnAuthFlowURLChange, (const GURL&), (override));
+  MOCK_METHOD(void, OnAuthFlowTitleChange, (const std::string&), (override));
+  MOCK_METHOD(void,
+              OnAuthFlowFailure,
+              (extensions::WebAuthFlow::Failure),
+              (override));
+};
+
+class WebAuthFlowBrowserTest : public InProcessBrowserTest {
+ public:
+  void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
+    ASSERT_TRUE(embedded_test_server()->Start());
+  }
+
+  void TearDownOnMainThread() override {
+    // Delete the web auth flow (uses DeleteSoon).
+    web_auth_flow_.release()->DetachDelegateAndDelete();
+    base::RunLoop().RunUntilIdle();
+    InProcessBrowserTest::TearDownOnMainThread();
+  }
+
+  void StartWebAuthFlow(const GURL& url) {
+    web_auth_flow_ = std::make_unique<extensions::WebAuthFlow>(
+        &mock_web_auth_flow_delegate_, browser()->profile(), url,
+        extensions::WebAuthFlow::INTERACTIVE,
+        extensions::WebAuthFlow::LAUNCH_WEB_AUTH_FLOW);
+    web_auth_flow_->Start();
+  }
+
+  content::WebContents* web_contents() {
+    DCHECK(web_auth_flow_);
+    return web_auth_flow_->web_contents();
+  }
+
+  MockWebAuthFlowDelegate& mock() { return mock_web_auth_flow_delegate_; }
+
+ private:
+  std::unique_ptr<extensions::WebAuthFlow> web_auth_flow_;
+  MockWebAuthFlowDelegate mock_web_auth_flow_delegate_;
+};
+
+IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest, OnAuthFlowURLChangeCalled) {
+  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
+
+  // Observer for waiting until a navigation to a url has finished.
+  content::TestNavigationObserver navigation_observer(auth_url);
+  navigation_observer.StartWatchingNewWebContents();
+
+  StartWebAuthFlow(auth_url);
+  // The delegate method OnAuthFlowURLChange should be called
+  // by DidStartNavigation.
+  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
+
+  navigation_observer.WaitForNavigationFinished();
+}
+
+IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest, OnAuthFlowFailureChangeCalled) {
+  // Navigate to a url that doesn't exist.
+  const GURL error_url = embedded_test_server()->GetURL("/error");
+
+  content::TestNavigationObserver navigation_observer(error_url);
+  navigation_observer.StartWatchingNewWebContents();
+
+  StartWebAuthFlow(error_url);
+  // The delegate method OnAuthFlowFailure should be called
+  // by DidFinishNavigation.
+  EXPECT_CALL(mock(), OnAuthFlowFailure(extensions::WebAuthFlow::LOAD_FAILED));
+
+  navigation_observer.WaitForNavigationFinished();
+}
+
+class WebAuthFlowFencedFrameTest : public WebAuthFlowBrowserTest {
+ public:
+  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
+    return fenced_frame_helper_;
+  }
+
+ private:
+  content::test::FencedFrameTestHelper fenced_frame_helper_;
+};
+
+IN_PROC_BROWSER_TEST_F(WebAuthFlowFencedFrameTest,
+                       FencedFrameNavigationSuccess) {
+  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
+
+  // Observer for waiting until loading stops. A fenced frame will be created
+  // after load has finished.
+  content::TestNavigationObserver navigation_observer(auth_url);
+  navigation_observer.set_wait_event(
+      content::TestNavigationObserver::WaitEvent::kLoadStopped);
+  navigation_observer.StartWatchingNewWebContents();
+
+  StartWebAuthFlow(auth_url);
+
+  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
+  navigation_observer.Wait();
+  testing::Mock::VerifyAndClearExpectations(&mock());
+
+  // Navigation for fenced frames should not affect to call the delegate methods
+  // in the WebAuthFlow.
+  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url)).Times(0);
+
+  // Create a fenced frame into the inner WebContents of the WebAuthFlow.
+  ASSERT_TRUE(fenced_frame_test_helper().CreateFencedFrame(
+      web_contents()->GetMainFrame(),
+      embedded_test_server()->GetURL("/fenced_frames/title1.html")));
+}
+
+IN_PROC_BROWSER_TEST_F(WebAuthFlowFencedFrameTest,
+                       FencedFrameNavigationFailure) {
+  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
+
+  // Observer for waiting until loading stops. A fenced frame will be created
+  // after load has finished.
+  content::TestNavigationObserver navigation_observer(auth_url);
+  navigation_observer.set_wait_event(
+      content::TestNavigationObserver::WaitEvent::kLoadStopped);
+  navigation_observer.StartWatchingNewWebContents();
+
+  StartWebAuthFlow(auth_url);
+
+  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
+  navigation_observer.Wait();
+  testing::Mock::VerifyAndClearExpectations(&mock());
+
+  // Navigation for fenced frames should not affect to call the delegate methods
+  // in the WebAuthFlow.
+  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url)).Times(0);
+  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
+
+  // Create a fenced frame into the inner WebContents of the WebAuthFlow.
+  ASSERT_TRUE(fenced_frame_test_helper().CreateFencedFrame(
+      web_contents()->GetMainFrame(), embedded_test_server()->GetURL("/error"),
+      net::Error::ERR_FAILED));
+}
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index c5f4c03..12550e9b 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -265,6 +265,11 @@
     "expiry_milestone": 97
   },
   {
+    "name": "arc-window-predictor",
+    "owners": [ "sstan" ],
+    "expiry_milestone": 110
+  },
+  {
     "name": "ash-advanced-screen-capture-settings",
     "owners": [ "afakhry", "gzadina", "fanafan" ],
     "expiry_milestone": 100
@@ -4846,11 +4851,6 @@
     "expiry_milestone": 99
   },
   {
-    "name": "safe-browsing-enhanced-protection-promo-android",
-    "owners": [ "bdea", "chrome-safebrowsing-core@google.com" ],
-    "expiry_milestone": 100
-  },
-  {
     "name": "safe-browsing-password-protection-for-signed-in-users",
     "owners": [
       "xinghuilu",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 77165478..f009c57 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3456,12 +3456,6 @@
 const char kSafeBrowsingClientSideDetectionAndroidDescription[] =
     "Enable DOM feature collection on Safe Browsing pings on Android";
 
-const char kEnhancedProtectionPromoAndroidName[] =
-    "Enable enhanced protection promo card on Android on the New Tab Page";
-const char kEnhancedProtectionPromoAndroidDescription[] =
-    "Enable enhanced protection promo card for users that have not signed up "
-    "for enhanced protection.";
-
 const char kScrollCaptureName[] = "Scroll Capture";
 const char kScrollCaptureDescription[] =
     "Enables scrolling screenshot capture for web contents.";
@@ -5022,6 +5016,10 @@
     "Enables the pre-load app window for "
     "ARC++ app during ARCVM booting stage on full restore process";
 
+extern const char kArcWindowPredictorName[] = "Enable ARC window predictor";
+extern const char kArcWindowPredictorDescription[] =
+    "Enables the window state and bounds predictor for ARC task windows";
+
 const char kArcInputOverlayName[] = "Enable ARC Input Overlay";
 const char kArcInputOverlayDescription[] =
     "Enables the input overlay feature for some Android game apps, "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 0c480d7..8c0fa89 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1988,9 +1988,6 @@
 extern const char kSafeBrowsingClientSideDetectionAndroidName[];
 extern const char kSafeBrowsingClientSideDetectionAndroidDescription[];
 
-extern const char kEnhancedProtectionPromoAndroidName[];
-extern const char kEnhancedProtectionPromoAndroidDescription[];
-
 extern const char kScrollCaptureName[];
 extern const char kScrollCaptureDescription[];
 
@@ -2901,6 +2898,9 @@
 extern const char kArcGhostWindowName[];
 extern const char kArcGhostWindowDescription[];
 
+extern const char kArcWindowPredictorName[];
+extern const char kArcWindowPredictorDescription[];
+
 extern const char kArcInputOverlayName[];
 extern const char kArcInputOverlayDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index d5d29c2..9897525 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -223,7 +223,6 @@
     &kDynamicColorAndroid,
     &kEnableDangerousDownloadDialog,
     &kEnableDuplicateDownloadDialog,
-    &kEnhancedProtectionPromoCard,
     &kExperimentsForAgsa,
     &kExploreSites,
     &kFixedUmaSessionResumeOrder,
@@ -613,9 +612,6 @@
 const base::Feature kEnableMixedContentDownloadDialog{
     "EnableMixedContentDownloadDialog", base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kEnhancedProtectionPromoCard{
-    "EnhancedProtectionPromoCard", base::FEATURE_DISABLED_BY_DEFAULT};
-
 const base::Feature kExperimentsForAgsa{"ExperimentsForAgsa",
                                         base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 895d26b..99d66cd 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -91,7 +91,6 @@
 extern const base::Feature kEnableDangerousDownloadDialog;
 extern const base::Feature kEnableDuplicateDownloadDialog;
 extern const base::Feature kEnableMixedContentDownloadDialog;
-extern const base::Feature kEnhancedProtectionPromoCard;
 extern const base::Feature kExperimentsForAgsa;
 extern const base::Feature kExploreSites;
 extern const base::Feature kFixedUmaSessionResumeOrder;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 90291c3..5bd2bf7 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -325,7 +325,6 @@
     public static final String ENABLE_AUTOMATIC_SNOOZE = "EnableAutomaticSnooze";
     public static final String ENABLE_DANGEROUS_DOWNLOAD_DIALOG = "EnableDangerousDownloadDialog";
     public static final String ENABLE_DUPLICATE_DOWNLOAD_DIALOG = "EnableDuplicateDownloadDialog";
-    public static final String ENHANCED_PROTECTION_PROMO_CARD = "EnhancedProtectionPromoCard";
     public static final String ELASTIC_OVERSCROLL = "ElasticOverscroll";
     public static final String EXPERIMENTS_FOR_AGSA = "ExperimentsForAgsa";
     public static final String EXPLICIT_LANGUAGE_ASK = "ExplicitLanguageAsk";
diff --git a/chrome/browser/geolocation/geolocation_browsertest.cc b/chrome/browser/geolocation/geolocation_browsertest.cc
index 64235af..5085b34 100644
--- a/chrome/browser/geolocation/geolocation_browsertest.cc
+++ b/chrome/browser/geolocation/geolocation_browsertest.cc
@@ -17,7 +17,6 @@
 #include "base/test/simple_test_clock.h"
 #include "base/time/clock.h"
 #include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -31,8 +30,6 @@
 #include "components/permissions/permission_request_manager.h"
 #include "components/permissions/test/permission_request_observer.h"
 #include "content/public/browser/navigation_controller.h"
-#include "content/public/browser/notification_details.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
@@ -65,7 +62,7 @@
 // Note: NavigateToURLBlockUntilNavigationsComplete doesn't seem to work for
 // multiple embedded iframes, as notifications seem to be 'batched'. Instead, we
 // load and wait one single frame here by calling a javascript function.
-class IFrameLoader : public content::NotificationObserver {
+class IFrameLoader : public content::WebContentsObserver {
  public:
   IFrameLoader(Browser* browser, int iframe_id, const GURL& url);
 
@@ -74,16 +71,14 @@
 
   ~IFrameLoader() override;
 
-  // content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // content::WebContentsObserver
+  void DidStopLoading() override;
+  void DomOperationResponse(content::RenderFrameHost* render_frame_host,
+                            const std::string& json_string) override;
 
   const GURL& iframe_url() const { return iframe_url_; }
 
  private:
-  content::NotificationRegistrar registrar_;
-
   // If true the navigation has completed.
   bool navigation_completed_;
 
@@ -94,6 +89,8 @@
 
   // The URL for the iframe we just loaded.
   GURL iframe_url_;
+  base::RunLoop run_loop;
+  base::OnceClosure quit_closure_;
 };
 
 IFrameLoader::IFrameLoader(Browser* browser, int iframe_id, const GURL& url)
@@ -101,20 +98,18 @@
       javascript_completed_(false) {
   content::WebContents* web_contents =
       browser->tab_strip_model()->GetActiveWebContents();
-  content::NavigationController* controller = &web_contents->GetController();
-  registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
-                 content::Source<content::NavigationController>(controller));
-  registrar_.Add(this, content::NOTIFICATION_DOM_OPERATION_RESPONSE,
-                 content::NotificationService::AllSources());
+  content::WebContentsObserver::Observe(web_contents);
   std::string script(base::StringPrintf(
       "window.domAutomationController.send(addIFrame(%d, \"%s\"));",
       iframe_id, url.spec().c_str()));
   web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
       base::UTF8ToUTF16(script), base::NullCallback());
-  content::RunMessageLoop();
+
+  quit_closure_ = run_loop.QuitWhenIdleClosure();
+  run_loop.Run();
 
   EXPECT_EQ(base::StringPrintf("\"%d\"", iframe_id), javascript_response_);
-  registrar_.RemoveAll();
+  content::WebContentsObserver::Observe(nullptr);
   // Now that we loaded the iframe, let's fetch its src.
   script = base::StringPrintf(
       "window.domAutomationController.send(getIFrameSrc(%d))", iframe_id);
@@ -124,18 +119,19 @@
 IFrameLoader::~IFrameLoader() {
 }
 
-void IFrameLoader::Observe(int type,
-                           const content::NotificationSource& source,
-                           const content::NotificationDetails& details) {
-  if (type == content::NOTIFICATION_LOAD_STOP) {
-    navigation_completed_ = true;
-  } else if (type == content::NOTIFICATION_DOM_OPERATION_RESPONSE) {
-    content::Details<std::string> dom_op_result(details);
-    javascript_response_ = *dom_op_result.ptr();
-    javascript_completed_ = true;
-  }
+void IFrameLoader::DidStopLoading() {
+  navigation_completed_ = true;
   if (javascript_completed_ && navigation_completed_)
-    base::RunLoop::QuitCurrentWhenIdleDeprecated();
+    std::move(quit_closure_).Run();
+}
+
+void IFrameLoader::DomOperationResponse(
+    content::RenderFrameHost* render_frame_host,
+    const std::string& json_string) {
+  javascript_response_ = json_string;
+  javascript_completed_ = true;
+  if (javascript_completed_ && navigation_completed_)
+    std::move(quit_closure_).Run();
 }
 
 }  // namespace
diff --git a/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc b/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
index 73df2eb..4baf1f4 100644
--- a/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
@@ -2,18 +2,33 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/barrier_closure.h"
+#include "base/bind.h"
+#include "base/callback_forward.h"
 #include "base/command_line.h"
 #include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/browser/extensions/api/desktop_capture/desktop_capture_api.h"
+#include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
 #include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
 #include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/sessions/tab_restore_service_factory.h"
+#include "chrome/browser/sessions/tab_restore_service_load_waiter.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/infobars/content/content_infobar_manager.h"
+#include "components/infobars/core/confirm_infobar_delegate.h"
+#include "components/infobars/core/infobar.h"
+#include "components/infobars/core/infobar_manager.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -22,13 +37,182 @@
 
 namespace {
 static const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+
+content::WebContents* GetWebContents(Browser* browser, int tab) {
+  return browser->tab_strip_model()->GetWebContentsAt(tab);
+}
+
+content::DesktopMediaID GetDesktopMediaIDForScreen() {
+  return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+                                 content::DesktopMediaID::kNullId);
+}
+
+content::DesktopMediaID GetDesktopMediaIDForTab(Browser* browser, int tab) {
+  content::RenderFrameHost* main_frame =
+      GetWebContents(browser, tab)->GetMainFrame();
+  return content::DesktopMediaID(
+      content::DesktopMediaID::TYPE_WEB_CONTENTS,
+      content::DesktopMediaID::kNullId,
+      content::WebContentsMediaCaptureId(main_frame->GetProcess()->GetID(),
+                                         main_frame->GetRoutingID()));
+}
+
+infobars::ContentInfoBarManager* GetInfoBarManager(Browser* browser, int tab) {
+  return infobars::ContentInfoBarManager::FromWebContents(
+      GetWebContents(browser, tab));
+}
+
+infobars::ContentInfoBarManager* GetInfoBarManager(
+    content::WebContents* contents) {
+  return infobars::ContentInfoBarManager::FromWebContents(contents);
+}
+
+ConfirmInfoBarDelegate* GetDelegate(Browser* browser, int tab) {
+  return static_cast<ConfirmInfoBarDelegate*>(
+      GetInfoBarManager(browser, tab)->infobar_at(0)->delegate());
+}
+
+class InfobarUIChangeObserver : public TabStripModelObserver {
+ public:
+  explicit InfobarUIChangeObserver(Browser* browser) : browser_{browser} {
+    for (int tab = 0; tab < browser_->tab_strip_model()->count(); ++tab) {
+      auto* contents = browser_->tab_strip_model()->GetWebContentsAt(tab);
+      observers_[contents] =
+          std::make_unique<InfoBarChangeObserver>(base::BindOnce(
+              &InfobarUIChangeObserver::EraseObserver, base::Unretained(this)));
+      GetInfoBarManager(contents)->AddObserver(observers_[contents].get());
+    }
+    browser_->tab_strip_model()->AddObserver(this);
+  }
+
+  ~InfobarUIChangeObserver() override {
+    for (auto& observer_iter : observers_) {
+      auto* contents = observer_iter.first;
+      auto* observer = observer_iter.second.get();
+
+      GetInfoBarManager(contents)->RemoveObserver(observer);
+    }
+    browser_->tab_strip_model()->RemoveObserver(this);
+    observers_.clear();
+  }
+
+  void ExpectCalls(size_t expected_changes) {
+    run_loop_ = std::make_unique<base::RunLoop>();
+    barrier_closure_ =
+        base::BarrierClosure(expected_changes, run_loop_->QuitClosure());
+    for (auto& observer : observers_) {
+      observer.second->SetCallback(barrier_closure_);
+    }
+  }
+
+  void Wait() { run_loop_->Run(); }
+
+  // TabStripModelObserver
+  void OnTabStripModelChanged(
+      TabStripModel* tab_strip_model,
+      const TabStripModelChange& change,
+      const TabStripSelectionChange& selection) override {
+    if (change.type() == TabStripModelChange::kInserted) {
+      for (const auto& contents_with_index : change.GetInsert()->contents) {
+        auto* contents = contents_with_index.contents;
+        if (observers_.find(contents) == observers_.end()) {
+          observers_[contents] = std::make_unique<InfoBarChangeObserver>(
+              base::BindOnce(&InfobarUIChangeObserver::EraseObserver,
+                             base::Unretained(this)));
+          GetInfoBarManager(contents)->AddObserver(observers_[contents].get());
+          if (!barrier_closure_.is_null()) {
+            observers_[contents]->SetCallback(barrier_closure_);
+          }
+        }
+      }
+    }
+  }
+  void TabChangedAt(content::WebContents* contents,
+                    int index,
+                    TabChangeType change_type) override {
+    if (observers_.find(contents) == observers_.end()) {
+      observers_[contents] =
+          std::make_unique<InfoBarChangeObserver>(base::BindOnce(
+              &InfobarUIChangeObserver::EraseObserver, base::Unretained(this)));
+      GetInfoBarManager(contents)->AddObserver(observers_[contents].get());
+      if (!barrier_closure_.is_null()) {
+        observers_[contents]->SetCallback(barrier_closure_);
+      }
+    }
+  }
+
+ private:
+  class InfoBarChangeObserver;
+
+ public:
+  void EraseObserver(InfoBarChangeObserver* observer) {
+    auto iter = std::find_if(observers_.begin(), observers_.end(),
+                             [observer](const auto& observer_iter) {
+                               return observer_iter.second.get() == observer;
+                             });
+    observers_.erase(iter);
+  }
+
+ private:
+  class InfoBarChangeObserver : public infobars::InfoBarManager::Observer {
+   public:
+    using ShutdownCallback = base::OnceCallback<void(InfoBarChangeObserver*)>;
+
+    explicit InfoBarChangeObserver(ShutdownCallback shutdown_callback)
+        : shutdown_callback_{std::move(shutdown_callback)} {}
+
+    ~InfoBarChangeObserver() override = default;
+
+    void SetCallback(base::RepeatingClosure change_closure) {
+      DCHECK(!change_closure.is_null());
+      change_closure_ = change_closure;
+    }
+
+    void OnInfoBarAdded(infobars::InfoBar* infobar) override {
+      DCHECK(!change_closure_.is_null());
+      change_closure_.Run();
+    }
+
+    void OnInfoBarRemoved(infobars::InfoBar* infobar, bool animate) override {
+      DCHECK(!change_closure_.is_null());
+      change_closure_.Run();
+    }
+
+    void OnInfoBarReplaced(infobars::InfoBar* old_infobar,
+                           infobars::InfoBar* new_infobar) override {
+      NOTREACHED();
+    }
+
+    void OnManagerShuttingDown(infobars::InfoBarManager* manager) override {
+      manager->RemoveObserver(this);
+      DCHECK(!shutdown_callback_.is_null());
+      std::move(shutdown_callback_).Run(this);
+    }
+
+   private:
+    base::RepeatingClosure change_closure_;
+    ShutdownCallback shutdown_callback_;
+  };
+
+  std::unique_ptr<base::RunLoop> run_loop_;
+  std::map<content::WebContents*, std::unique_ptr<InfoBarChangeObserver>>
+      observers_;
+  Browser* browser_;
+  base::RepeatingClosure barrier_closure_;
+};
+
 }  // namespace
 
 // Top-level integration test for WebRTC. Uses an actual desktop capture
-// extension to capture whole screen.
+// extension to capture the whole screen or a tab.
 class WebRtcDesktopCaptureBrowserTest : public WebRtcTestBase {
  public:
-  WebRtcDesktopCaptureBrowserTest() : left_tab_(nullptr), right_tab_(nullptr) {}
+  using MediaIDCallback = base::OnceCallback<content::DesktopMediaID()>;
+
+  WebRtcDesktopCaptureBrowserTest() {
+    extensions::DesktopCaptureChooseDesktopMediaFunction::
+        SetPickerFactoryForTests(&picker_factory_);
+  }
 
   void SetUpInProcessBrowserTestFixture() override {
     DetectErrorsInJavaScript();  // Look for errors in our rather complex js.
@@ -46,20 +230,69 @@
   }
 
  protected:
-  void DetectVideoAndHangUp() {
-    StartDetectingVideo(left_tab_, "remote-view");
-    StartDetectingVideo(right_tab_, "remote-view");
-#if !defined(OS_MAC)
-    // Video is choppy on Mac OS X. http://crbug.com/443542.
-    WaitForVideoToPlay(left_tab_);
-    WaitForVideoToPlay(right_tab_);
-#endif
-    HangUp(left_tab_);
-    HangUp(right_tab_);
+  void InitializeTabSharingForFirstTab(MediaIDCallback media_id_callback,
+                                       InfobarUIChangeObserver* observer) {
+    ASSERT_TRUE(embedded_test_server()->Start());
+    LoadDesktopCaptureExtension();
+    auto* first_tab = OpenTestPageInNewTab(kMainWebrtcTestHtmlPage);
+    OpenTestPageInNewTab(kMainWebrtcTestHtmlPage);
+
+    FakeDesktopMediaPickerFactory::TestFlags test_flags{
+        .expect_screens = true,
+        .expect_windows = true,
+        .expect_tabs = true,
+        .selected_source = std::move(media_id_callback).Run(),
+    };
+    picker_factory_.SetTestFlags(&test_flags, /*tests_count=*/1);
+
+    std::string stream_id = GetDesktopMediaStream(first_tab);
+    EXPECT_NE(stream_id, "");
+
+    LOG(INFO) << "Opened desktop media stream, got id " << stream_id;
+
+    const std::string constraints =
+        "{audio: false, video: {mandatory: {chromeMediaSource: 'desktop',"
+        "chromeMediaSourceId: '" +
+        stream_id + "'}}}";
+
+    // Should create 3 infobars if a tab (webcontents) is shared!
+    if (observer)
+      observer->ExpectCalls(3);
+
+    EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(
+        first_tab, constraints));
+
+    if (observer)
+      observer->Wait();
   }
 
-  raw_ptr<content::WebContents> left_tab_;
-  raw_ptr<content::WebContents> right_tab_;
+  void DetectVideoAndHangUp(content::WebContents* first_tab,
+                            content::WebContents* second_tab) {
+    StartDetectingVideo(first_tab, "remote-view");
+    StartDetectingVideo(second_tab, "remote-view");
+#if !defined(OS_MAC)
+    // Video is choppy on Mac OS X. http://crbug.com/443542.
+    WaitForVideoToPlay(first_tab);
+    WaitForVideoToPlay(second_tab);
+#endif
+    HangUp(first_tab);
+    HangUp(second_tab);
+  }
+
+  void RunP2PScreenshareWhileSharing(MediaIDCallback media_id_callback) {
+    InitializeTabSharingForFirstTab(std::move(media_id_callback), nullptr);
+    auto* first_tab = browser()->tab_strip_model()->GetWebContentsAt(1);
+    auto* second_tab = browser()->tab_strip_model()->GetWebContentsAt(2);
+    GetUserMediaAndAccept(second_tab);
+
+    SetupPeerconnectionWithLocalStream(first_tab);
+    SetupPeerconnectionWithLocalStream(second_tab);
+    NegotiateCall(first_tab, second_tab);
+    VerifyStatsGeneratedCallback(second_tab);
+    DetectVideoAndHangUp(first_tab, second_tab);
+  }
+
+  FakeDesktopMediaPickerFactory picker_factory_;
 };
 
 // TODO(crbug.com/796889): Enable on Mac when thread check crash is fixed.
@@ -68,25 +301,55 @@
 // of lacros-chrome is complete.
 // TODO(crbug.com/1225911): Test is flaky on Linux.
 IN_PROC_BROWSER_TEST_F(WebRtcDesktopCaptureBrowserTest,
-                       DISABLED_RunsScreenshareFromOneTabToAnother) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  LoadDesktopCaptureExtension();
-  left_tab_ = OpenTestPageInNewTab(kMainWebrtcTestHtmlPage);
-  std::string stream_id = GetDesktopMediaStream(left_tab_);
-  EXPECT_NE(stream_id, "");
+                       DISABLED_RunP2PScreenshareWhileSharingScreen) {
+  RunP2PScreenshareWhileSharing(base::BindOnce(GetDesktopMediaIDForScreen));
+}
 
-  LOG(INFO) << "Opened desktop media stream, got id " << stream_id;
+IN_PROC_BROWSER_TEST_F(WebRtcDesktopCaptureBrowserTest,
+                       RunP2PScreenshareWhileSharingTab) {
+  RunP2PScreenshareWhileSharing(
+      base::BindOnce(GetDesktopMediaIDForTab, base::Unretained(browser()), 2));
+}
 
-  const std::string constraints =
-      "{audio: false, video: {mandatory: {chromeMediaSource: 'desktop',"
-      "chromeMediaSourceId: '" +
-      stream_id + "'}}}";
-  EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(
-      left_tab_, constraints));
-  right_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
-  SetupPeerconnectionWithLocalStream(left_tab_);
-  SetupPeerconnectionWithLocalStream(right_tab_);
-  NegotiateCall(left_tab_, right_tab_);
-  VerifyStatsGeneratedCallback(right_tab_);
-  DetectVideoAndHangUp();
+IN_PROC_BROWSER_TEST_F(WebRtcDesktopCaptureBrowserTest,
+                       SwitchSharedTabBackAndForth) {
+  InfobarUIChangeObserver observer(browser());
+
+  InitializeTabSharingForFirstTab(
+      base::BindOnce(GetDesktopMediaIDForTab, base::Unretained(browser()), 2),
+      &observer);
+
+  // Should delete 3 infobars and create 3 new!
+  observer.ExpectCalls(6);
+  // Switch shared tab from 2 to 0.
+  GetDelegate(browser(), 0)->Cancel();
+  observer.Wait();
+
+  // Should delete 3 infobars and create 3 new!
+  observer.ExpectCalls(6);
+  // Switch shared tab from 0 to 2.
+  GetDelegate(browser(), 2)->Cancel();
+  observer.Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(WebRtcDesktopCaptureBrowserTest,
+                       CloseAndReopenNonSharedTab) {
+  InfobarUIChangeObserver observer(browser());
+
+  InitializeTabSharingForFirstTab(
+      base::BindOnce(GetDesktopMediaIDForTab, base::Unretained(browser()), 2),
+      &observer);
+
+  // Should delete 1 infobar.
+  observer.ExpectCalls(1);
+  // Close non-shared and non-sharing, i.e., unrelated tab
+  browser()->tab_strip_model()->CloseWebContentsAt(
+      0, TabStripModel::CloseTypes::CLOSE_CREATE_HISTORICAL_TAB);
+  observer.Wait();
+
+  // Should create 1 infobar.
+  observer.ExpectCalls(1);
+  // Restore tab
+  chrome::RestoreTab(browser());
+  observer.Wait();
 }
diff --git a/chrome/browser/password_manager/password_manager_captured_sites_interactive_uitest.cc b/chrome/browser/password_manager/password_manager_captured_sites_interactive_uitest.cc
index 7abf186..099ebd7 100644
--- a/chrome/browser/password_manager/password_manager_captured_sites_interactive_uitest.cc
+++ b/chrome/browser/password_manager/password_manager_captured_sites_interactive_uitest.cc
@@ -222,6 +222,8 @@
                                   kAutofillUseUnassociatedListedElements},
         {});
     command_line->AppendSwitch(autofill::switches::kShowAutofillSignatures);
+    captured_sites_test_utils::TestRecipeReplayer::SetUpHostResolverRules(
+        command_line);
     captured_sites_test_utils::TestRecipeReplayer::SetUpCommandLine(
         command_line);
   }
diff --git a/chrome/browser/policy/chrome_browser_policy_connector.cc b/chrome/browser/policy/chrome_browser_policy_connector.cc
index 9814e5b..85abf709 100644
--- a/chrome/browser/policy/chrome_browser_policy_connector.cc
+++ b/chrome/browser/policy/chrome_browser_policy_connector.cc
@@ -285,6 +285,7 @@
       base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT}),
       PolicyPerProfileFilter::kFalse);
+  device_account_policy_loader_ = loader.get();
   return std::make_unique<AsyncPolicyProvider>(GetSchemaRegistry(),
                                                std::move(loader));
 #elif defined(OS_POSIX) && !defined(OS_ANDROID)
diff --git a/chrome/browser/policy/chrome_browser_policy_connector.h b/chrome/browser/policy/chrome_browser_policy_connector.h
index 9bc7974c..d493b44 100644
--- a/chrome/browser/policy/chrome_browser_policy_connector.h
+++ b/chrome/browser/policy/chrome_browser_policy_connector.h
@@ -25,6 +25,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chrome/browser/lacros/device_settings_lacros.h"
+#include "components/policy/core/common/policy_loader_lacros.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
 class PrefService;
@@ -117,6 +118,10 @@
 
   // The device settings used in Lacros.
   crosapi::mojom::DeviceSettings* GetDeviceSettings() const;
+
+  PolicyLoaderLacros* device_account_policy_loader() {
+    return device_account_policy_loader_;
+  }
 #endif
 
  protected:
@@ -176,6 +181,8 @@
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   std::unique_ptr<DeviceSettingsLacros> device_settings_ = nullptr;
+  // Owned by |platform_provider_|.
+  PolicyLoaderLacros* device_account_policy_loader_ = nullptr;
 #endif
 
   // Holds a callback to |ChromeBrowserCloudManagementController::Init| so that
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
index b298716..9997f4e 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -283,11 +283,20 @@
       return false;
     case 'enableChromeVoxArcSupportForCurrentApp':
       chrome.accessibilityPrivate.setNativeChromeVoxArcSupportForCurrentApp(
-          true);
+          true, (response) => {});
       break;
     case 'disableChromeVoxArcSupportForCurrentApp':
       chrome.accessibilityPrivate.setNativeChromeVoxArcSupportForCurrentApp(
-          false);
+          false, (response) => {
+            if (response ===
+                chrome.accessibilityPrivate.SetNativeChromeVoxResponse
+                    .TALKBACK_NOT_INSTALLED) {
+              ChromeVox.braille.write(NavBraille.fromText(
+                  Msgs.getMsg('announce_install_talkback')));
+              ChromeVox.tts.speak(
+                  Msgs.getMsg('announce_install_talkback'), QueueMode.FLUSH);
+            }
+          });
       break;
     case 'showTtsSettings':
       chrome.accessibilityPrivate.openSettingsSubpage(
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
index 8efdd63..70689d6 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
@@ -673,6 +673,18 @@
         override = !!walker || override;
       }
 
+      // Autofill popup menu items are always announced on selection events,
+      // independent of focus.
+      // The AutofillPopupSeparatorView is intentionally omitted because it
+      // cannot be focused.
+      if (target.className === 'AutofillPopupSuggestionView' ||
+          target.className === 'PasswordPopupSuggestionView' ||
+          target.className === 'AutofillPopupFooterView' ||
+          target.className === 'AutofillPopupWarningView' ||
+          target.className === 'AutofillPopupBaseView') {
+        override = true;
+      }
+
       // The popup view associated with a datalist element does not descend
       // from the input with which it is associated.
       if (focus.role === RoleType.TEXT_FIELD_WITH_COMBO_BOX &&
diff --git a/chrome/browser/resources/chromeos/accessibility/strings/chromevox_strings.grdp b/chrome/browser/resources/chromeos/accessibility/strings/chromevox_strings.grdp
index 3eb5f53..b4a2fc7 100644
--- a/chrome/browser/resources/chromeos/accessibility/strings/chromevox_strings.grdp
+++ b/chrome/browser/resources/chromeos/accessibility/strings/chromevox_strings.grdp
@@ -3378,4 +3378,7 @@
   <message desc="Spoken to describe a &lt;abbr&gt; tag. For example, &lt;abbr title='uniform resource locator'&gt;URL/&lt;abbr&gt; would be spoken as 'URL, uniform resource locator, Abbreviation'" name="IDS_CHROMEVOX_TAG_ABBR" is_accessibility_with_no_ui="true">
     Abbreviation
   </message>
+  <message desc="Spoken message asking users to install the Accessibility Suite app from the Play Store when users tried to turn on Talkback without installing Accessibility Suite." name="IDS_CHROMEVOX_ANNOUNCE_INSTALL_TALKBACK" is_accessibility_with_no_ui="true">
+    TalkBack is currently not installed. Please install Android AccessibilitySuite through Play Store and try again.
+  </message>
 </grit-part>
diff --git a/chrome/browser/resources/settings/site_settings/all_sites.html b/chrome/browser/resources/settings/site_settings/all_sites.html
index 3389730..84b21dda 100644
--- a/chrome/browser/resources/settings/site_settings/all_sites.html
+++ b/chrome/browser/resources/settings/site_settings/all_sites.html
@@ -83,7 +83,8 @@
     <cr-lazy-render id="menu">
       <template>
         <cr-action-menu role-description="$i18n{menu}">
-          <button class="dropdown-item" role="menuitem"
+          <button hidden$="[[actionMenuModel_.isPartitioned]]"
+              class="dropdown-item" role="menuitem"
               on-click="onConfirmResetSettings_">
             $i18n{siteSettingsSiteGroupReset}
           </button>
diff --git a/chrome/browser/resources/settings/site_settings/all_sites.ts b/chrome/browser/resources/settings/site_settings/all_sites.ts
index 4930e99..fb06f49 100644
--- a/chrome/browser/resources/settings/site_settings/all_sites.ts
+++ b/chrome/browser/resources/settings/site_settings/all_sites.ts
@@ -44,6 +44,7 @@
   index: number,
   item: SiteGroup,
   origin: string,
+  isPartitioned: boolean,
   path: string,
   target: HTMLElement,
 };
@@ -459,7 +460,7 @@
   }
 
   onConfirmRemoveSite_(e: Event) {
-    const {index, actionScope, origin} = this.actionMenuModel_!;
+    const {index, actionScope, origin, isPartitioned} = this.actionMenuModel_!;
     const siteGroupToUpdate = this.filteredList_[index];
 
     const updatedSiteGroup: SiteGroup = {
@@ -470,17 +471,29 @@
     };
 
     if (actionScope === 'origin') {
-      this.browserProxy.recordAction(AllSitesAction2.REMOVE_ORIGIN);
-      this.browserProxy.clearOriginDataAndCookies(this.toUrl(origin)!.href);
-      this.resetPermissionsForOrigin_(origin);
+      if (isPartitioned) {
+        this.browserProxy.recordAction(
+            AllSitesAction2.REMOVE_ORIGIN_PARTITIONED);
+        this.browserProxy.clearPartitionedOriginDataAndCookies(
+            this.toUrl(origin)!.href, siteGroupToUpdate.etldPlus1);
 
-      updatedSiteGroup.origins =
-          siteGroupToUpdate.origins.filter(o => o.origin !== origin);
+      } else {
+        this.browserProxy.recordAction(AllSitesAction2.REMOVE_ORIGIN);
+        this.browserProxy.clearUnpartitionedOriginDataAndCookies(
+            this.toUrl(origin)!.href);
+        this.resetPermissionsForOrigin_(origin);
+      }
+      updatedSiteGroup.origins = siteGroupToUpdate.origins.filter(
+          o => (o.isPartitioned !== isPartitioned || o.origin !== origin));
+
+      console.log(updatedSiteGroup.origins);
       updatedSiteGroup.hasInstalledPWA =
           updatedSiteGroup.origins.some(o => o.isInstalled);
       updatedSiteGroup.numCookies -=
-          siteGroupToUpdate.origins.find(o => o.origin === origin)!.numCookies;
-
+          siteGroupToUpdate.origins
+              .find(
+                  o => o.isPartitioned === isPartitioned &&
+                      o.origin === origin)!.numCookies;
     } else {
       this.browserProxy.recordAction(AllSitesAction2.REMOVE_SITE_GROUP);
       this.browserProxy.clearEtldPlus1DataAndCookies(
@@ -624,6 +637,14 @@
     const singleOriginSite =
         !originScoped && this.actionMenuModel_.item.origins.length === 1;
 
+    if (this.actionMenuModel_.isPartitioned) {
+      assert(originScoped);
+      return loadTimeData.substituteString(this.i18n(
+          'siteSettingsRemoveSiteOriginPartitionedDialogTitle',
+          this.originRepresentation(this.actionMenuModel_.origin),
+          this.originRepresentation(this.actionMenuModel_.item.etldPlus1)));
+    }
+
     const numInstalledApps =
         this.actionMenuModel_.item.origins
             .filter(
@@ -790,6 +811,7 @@
         numCookies: siteGroupToUpdate.numCookies,
         hasPermissionSettings: false,
         isInstalled: false,
+        isPartitioned: false,
       };
       updatedSiteGroup.origins.push(originPlaceHolder);
       this.set('filteredList_.' + index, updatedSiteGroup);
@@ -835,7 +857,8 @@
    * @param origin The origin of the target origin that should be cleared.
    */
   private clearDataForOrigin_(index: number, origin: string) {
-    this.browserProxy.clearOriginDataAndCookies(this.toUrl(origin)!.href);
+    this.browserProxy.clearUnpartitionedOriginDataAndCookies(
+        this.toUrl(origin)!.href);
 
     const siteGroupToUpdate = this.filteredList_[index];
     const updatedSiteGroup: SiteGroup = {
diff --git a/chrome/browser/resources/settings/site_settings/constants.ts b/chrome/browser/resources/settings/site_settings/constants.ts
index 7edb50c..6915d09 100644
--- a/chrome/browser/resources/settings/site_settings/constants.ts
+++ b/chrome/browser/resources/settings/site_settings/constants.ts
@@ -139,6 +139,7 @@
   ENTER_SITE_DETAILS = 6,
   REMOVE_SITE_GROUP = 7,
   REMOVE_ORIGIN = 8,
+  REMOVE_ORIGIN_PARTITIONED = 9,
 }
 
 /**
diff --git a/chrome/browser/resources/settings/site_settings/site_entry.html b/chrome/browser/resources/settings/site_settings/site_entry.html
index 17b299c..43fea4e 100644
--- a/chrome/browser/resources/settings/site_settings/site_entry.html
+++ b/chrome/browser/resources/settings/site_settings/site_entry.html
@@ -102,7 +102,8 @@
               <template is="dom-repeat" items="[[siteGroup.origins]]">
                 <div class="list-item hr">
                   <div class="start row-aligned list-item origin-link"
-                       on-click="onOriginTap_" actionable>
+                       on-click="onOriginTap_"
+                       actionable$="[[!item.isPartitioned]]">
                     <site-favicon url="[[item.origin]]"></site-favicon>
                     <div class="site-representation middle text-elide">
                       <span id="originSiteRepresentation"
@@ -133,8 +134,13 @@
                           &nbsp;&middot;
                           [[originCookiesItem_(cookiesNum_.*, index)]]
                       </span>
+                      <span class="secondary" hidden$="[[!item.isPartitioned]]">
+                          &nbsp;&middot;
+                          $i18n{siteSettingsSiteEntryPartitionedLabel}
+                      </span>
                     </div>
                     <cr-icon-button class="subpage-arrow"
+                        hidden$="[[item.isPartitioned]]"
                         aria-labelledby$="originSiteRepresentation"
                         aria-roledescription=
                             "$i18n{subpageArrowRoleDescription}"
@@ -160,6 +166,7 @@
                           id="removeOriginButton"
                           title$="[[getRemoveOriginButtonTitle_(item.origin)]]"
                           data-origin$="[[item.origin]]" data-context="origin"
+                          data-partitioned$="[[item.isPartitioned]]"
                           on-click="onRemove_" focus-row-control
                           focus-type="remove">
                       </cr-icon-button>
diff --git a/chrome/browser/resources/settings/site_settings/site_entry.ts b/chrome/browser/resources/settings/site_settings/site_entry.ts
index 939fb79..aa6261b 100644
--- a/chrome/browser/resources/settings/site_settings/site_entry.ts
+++ b/chrome/browser/resources/settings/site_settings/site_entry.ts
@@ -179,7 +179,8 @@
       return false;
     }
     if (siteGroup.origins.length > 1 ||
-        siteGroup.numCookies > siteGroup.origins[0].numCookies) {
+        siteGroup.numCookies > siteGroup.origins[0].numCookies ||
+        siteGroup.origins.some(o => o.isPartitioned)) {
       return true;
     }
     return false;
@@ -360,6 +361,9 @@
    * A handler for selecting a site (by clicking on the origin).
    */
   private onOriginTap_(e: RepeaterEvent) {
+    if (this.siteGroup.origins[e.model.index].isPartitioned) {
+      return;
+    }
     this.navigateToSiteDetails_(this.siteGroup.origins[e.model.index].origin);
     this.browserProxy.recordAction(AllSitesAction2.ENTER_SITE_DETAILS);
     chrome.metricsPrivate.recordUserAction('AllSites_EnterSiteDetails');
@@ -407,6 +411,7 @@
       index: this.listIndex,
       item: this.siteGroup,
       origin: (e.target as HTMLElement).dataset['origin'],
+      isPartitioned: (e.target as HTMLElement).dataset['partitioned'],
       actionScope: (e.target as HTMLElement).dataset['context'],
     });
   }
@@ -417,6 +422,8 @@
       index: this.listIndex,
       item: this.siteGroup,
       origin: (e.target as HTMLElement).dataset['origin'],
+      isPartitioned:
+          (e.target as HTMLElement).dataset['partitioned'] !== undefined,
       actionScope: (e.target as HTMLElement).dataset['context'],
     });
   }
@@ -468,16 +475,22 @@
       (o1: OriginInfo, o2: OriginInfo) => number {
     if (sortMethod === SortMethod.MOST_VISITED) {
       return (origin1, origin2) => {
-        return origin2.engagement - origin1.engagement;
+        return (origin1.isPartitioned ? 1 : 0) -
+            (origin2.isPartitioned ? 1 : 0) ||
+            origin2.engagement - origin1.engagement;
       };
     } else if (sortMethod === SortMethod.STORAGE) {
       return (origin1, origin2) => {
-        return origin2.usage - origin1.usage ||
+        return (origin1.isPartitioned ? 1 : 0) -
+            (origin2.isPartitioned ? 1 : 0) ||
+            origin2.usage - origin1.usage ||
             origin2.numCookies - origin1.numCookies;
       };
     } else if (sortMethod === SortMethod.NAME) {
       return (origin1, origin2) => {
-        return origin1.origin.localeCompare(origin2.origin);
+        return (origin1.isPartitioned ? 1 : 0) -
+            (origin2.isPartitioned ? 1 : 0) ||
+            origin1.origin.localeCompare(origin2.origin);
       };
     }
     assertNotReached();
diff --git a/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.ts b/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.ts
index a9dc98ed..d135d6e 100644
--- a/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.ts
+++ b/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.ts
@@ -51,6 +51,7 @@
   numCookies: number,
   hasPermissionSettings: boolean,
   isInstalled: boolean,
+  isPartitioned: boolean
 };
 
 /**
@@ -424,10 +425,18 @@
   clearEtldPlus1DataAndCookies(etldPlus1: string): void;
 
   /**
-   * Clears all the web storage data and cookies for a given origin.
+   * Clears all the unpartitioned web storage data and cookies for a given
+   * origin.
    * @param origin The origin to clear data from.
    */
-  clearOriginDataAndCookies(origin: string): void;
+  clearUnpartitionedOriginDataAndCookies(origin: string): void;
+
+  /**
+   * Clears all the storage for |origin| which is partitioned on |etldPlus1|.
+   * @param origin The origin to clear data from.
+   * @param etldPlus1 The etld+1 which the data is partitioned for.
+   */
+  clearPartitionedOriginDataAndCookies(origin: string, etldPlus1: string): void;
 
   /**
    * Record All Sites Page action for metrics.
@@ -577,8 +586,12 @@
     chrome.send('clearEtldPlus1DataAndCookies', [etldPlus1]);
   }
 
-  clearOriginDataAndCookies(origin: string) {
-    chrome.send('clearUsage', [origin]);
+  clearUnpartitionedOriginDataAndCookies(origin: string) {
+    chrome.send('clearUnpartitionedUsage', [origin]);
+  }
+
+  clearPartitionedOriginDataAndCookies(origin: string, etldPlus1: string) {
+    chrome.send('clearPartitionedUsage', [origin, etldPlus1]);
   }
 
   recordAction(action: number) {
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index 1f02839..b117c4a 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -3098,7 +3098,7 @@
 }
 
 class SSLUITestWaitForDOMNotification : public SSLUITestIgnoreCertErrors,
-                                        public content::NotificationObserver {
+                                        public content::WebContentsObserver {
  public:
   SSLUITestWaitForDOMNotification()
       : SSLUITestIgnoreCertErrors(), run_loop_(nullptr) {}
@@ -3107,13 +3107,10 @@
       delete;
   SSLUITestWaitForDOMNotification& operator=(
       const SSLUITestWaitForDOMNotification&) = delete;
-
-  ~SSLUITestWaitForDOMNotification() override { registrar_.RemoveAll(); }
+  ~SSLUITestWaitForDOMNotification() override = default;
 
   void SetUpOnMainThread() override {
     SSLUITestIgnoreCertErrors::SetUpOnMainThread();
-    registrar_.Add(this, content::NOTIFICATION_DOM_OPERATION_RESPONSE,
-                   content::NotificationService::AllSources());
   }
 
   void set_expected_notification(const std::string& expected_notification) {
@@ -3121,22 +3118,20 @@
   }
 
   void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; }
+  void observe(content::WebContents* web_contents) {
+    content::WebContentsObserver::Observe(web_contents);
+  }
 
-  // content::NotificationObserver
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override {
+  // content::WebContentsObserver
+  void DomOperationResponse(content::RenderFrameHost* render_frame_host,
+                            const std::string& json_string) override {
     DCHECK(run_loop_);
-    if (type == content::NOTIFICATION_DOM_OPERATION_RESPONSE) {
-      content::Details<std::string> dom_op_result(details);
-      if (*dom_op_result.ptr() == expected_notification_) {
-        run_loop_->Quit();
-      }
+    if (json_string == expected_notification_) {
+      run_loop_->Quit();
     }
   }
 
  private:
-  content::NotificationRegistrar registrar_;
   std::string expected_notification_;
   raw_ptr<base::RunLoop> run_loop_;
 };
@@ -3180,6 +3175,7 @@
   // chain passes through HTTP, the page should be marked as mixed
   // content.
   set_expected_notification("\"mixed-image-loaded\"");
+  observe(tab);
   set_run_loop(&run_loop);
   ASSERT_TRUE(content::ExecuteScript(
       tab,
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninPromoController.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninPromoController.java
index 7595fc4..19fda325 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninPromoController.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninPromoController.java
@@ -160,8 +160,6 @@
         if (resetAfterMs <= 0 || lastShownTime <= 0) return;
 
         if (currentTime - lastShownTime >= resetAfterMs) {
-            SharedPreferencesManager.getInstance().writeInt(
-                    getPromoShowCountPreferenceName(SigninAccessPoint.NTP_CONTENT_SUGGESTIONS), 0);
             SharedPreferencesManager.getInstance().removeKey(
                     ChromePreferenceKeys.SIGNIN_PROMO_NTP_FIRST_SHOWN_TIME);
             SharedPreferencesManager.getInstance().removeKey(
@@ -192,13 +190,7 @@
     }
 
     private static boolean canShowNTPPromo() {
-        int maxImpressions = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
-                ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD, "MaxSigninPromoImpressions",
-                Integer.MAX_VALUE);
-        if (SharedPreferencesManager.getInstance().readInt(
-                    getPromoShowCountPreferenceName(SigninAccessPoint.NTP_CONTENT_SUGGESTIONS))
-                        >= maxImpressions
-                || timeElapsedSinceFirstShownExceedsLimit()) {
+        if (timeElapsedSinceFirstShownExceedsLimit()) {
             return false;
         }
 
@@ -249,8 +241,6 @@
             case SigninAccessPoint.BOOKMARK_MANAGER:
                 return ChromePreferenceKeys.SYNC_PROMO_SHOW_COUNT.createKey(
                         AccessPointId.BOOKMARKS);
-            case SigninAccessPoint.NTP_CONTENT_SUGGESTIONS:
-                return ChromePreferenceKeys.SYNC_PROMO_SHOW_COUNT.createKey(AccessPointId.NTP);
             case SigninAccessPoint.SETTINGS:
                 return ChromePreferenceKeys.SYNC_PROMO_SHOW_COUNT.createKey(AccessPointId.SETTINGS);
             default:
@@ -467,7 +457,8 @@
 
     /** Increases promo show count by one. */
     public void increasePromoShowCount() {
-        if (mAccessPoint != SigninAccessPoint.RECENT_TABS) {
+        if (mAccessPoint != SigninAccessPoint.RECENT_TABS
+                && mAccessPoint != SigninAccessPoint.NTP_CONTENT_SUGGESTIONS) {
             SharedPreferencesManager.getInstance().incrementInt(
                     getPromoShowCountPreferenceName(mAccessPoint));
         }
diff --git a/chrome/browser/ui/android/signin/junit/src/org/chromium/chrome/browser/ui/signin/SigninPromoControllerTest.java b/chrome/browser/ui/android/signin/junit/src/org/chromium/chrome/browser/ui/signin/SigninPromoControllerTest.java
index 38ffc33b..b932bac 100644
--- a/chrome/browser/ui/android/signin/junit/src/org/chromium/chrome/browser/ui/signin/SigninPromoControllerTest.java
+++ b/chrome/browser/ui/android/signin/junit/src/org/chromium/chrome/browser/ui/signin/SigninPromoControllerTest.java
@@ -79,9 +79,6 @@
         IdentityServicesProvider.setInstanceForTests(mock(IdentityServicesProvider.class));
         when(IdentityServicesProvider.get().getIdentityManager(Profile.getLastUsedRegularProfile()))
                 .thenReturn(mIdentityManagerMock);
-        mSharedPreferencesManager.writeInt(SigninPromoController.getPromoShowCountPreferenceName(
-                                                   SigninAccessPoint.NTP_CONTENT_SUGGESTIONS),
-                0);
         mSharedPreferencesManager.writeLong(
                 ChromePreferenceKeys.SIGNIN_PROMO_NTP_FIRST_SHOWN_TIME, 0L);
         mSharedPreferencesManager.writeLong(
@@ -134,40 +131,6 @@
     }
 
     @Test
-    public void shouldShowNTPSyncPromoWhenCountLimitIsNotExceeded() {
-        FeatureList.TestValues testValues = new FeatureList.TestValues();
-        testValues.addFieldTrialParamOverride(ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD,
-                "MaxSigninPromoImpressions", Integer.toString(MAX_SIGN_IN_PROMO_IMPRESSIONS));
-        testValues.addFeatureFlagOverride(ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD, true);
-        testValues.addFeatureFlagOverride(
-                ChromeFeatureList.FORCE_DISABLE_EXTENDED_SYNC_PROMOS, false);
-        FeatureList.setTestValues(testValues);
-        mSharedPreferencesManager.writeInt(SigninPromoController.getPromoShowCountPreferenceName(
-                                                   SigninAccessPoint.NTP_CONTENT_SUGGESTIONS),
-                MAX_SIGN_IN_PROMO_IMPRESSIONS - 1);
-
-        Assert.assertTrue(
-                SigninPromoController.canShowSyncPromo(SigninAccessPoint.NTP_CONTENT_SUGGESTIONS));
-    }
-
-    @Test
-    public void shouldHideNTPSyncPromoWhenCountLimitIsExceeded() {
-        FeatureList.TestValues testValues = new FeatureList.TestValues();
-        testValues.addFieldTrialParamOverride(ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD,
-                "MaxSigninPromoImpressions", Integer.toString(MAX_SIGN_IN_PROMO_IMPRESSIONS));
-        testValues.addFeatureFlagOverride(ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD, true);
-        testValues.addFeatureFlagOverride(
-                ChromeFeatureList.FORCE_DISABLE_EXTENDED_SYNC_PROMOS, false);
-        FeatureList.setTestValues(testValues);
-        mSharedPreferencesManager.writeInt(SigninPromoController.getPromoShowCountPreferenceName(
-                                                   SigninAccessPoint.NTP_CONTENT_SUGGESTIONS),
-                MAX_SIGN_IN_PROMO_IMPRESSIONS);
-
-        Assert.assertFalse(
-                SigninPromoController.canShowSyncPromo(SigninAccessPoint.NTP_CONTENT_SUGGESTIONS));
-    }
-
-    @Test
     public void shouldShowNTPSyncPromoWithoutTimeSinceFirstShownLimit() {
         final long firstShownTime =
                 System.currentTimeMillis() - TIME_SINCE_FIRST_SHOWN_LIMIT_MS - 1;
@@ -225,10 +188,6 @@
         Assert.assertEquals(lastShownTime,
                 SharedPreferencesManager.getInstance().readLong(
                         ChromePreferenceKeys.SIGNIN_PROMO_NTP_LAST_SHOWN_TIME));
-        Assert.assertEquals(MAX_SIGN_IN_PROMO_IMPRESSIONS,
-                SharedPreferencesManager.getInstance().readInt(
-                        SigninPromoController.getPromoShowCountPreferenceName(
-                                SigninAccessPoint.NTP_CONTENT_SUGGESTIONS)));
     }
 
     @Test
@@ -250,10 +209,6 @@
         Assert.assertEquals(lastShownTime,
                 SharedPreferencesManager.getInstance().readLong(
                         ChromePreferenceKeys.SIGNIN_PROMO_NTP_LAST_SHOWN_TIME));
-        Assert.assertEquals(MAX_SIGN_IN_PROMO_IMPRESSIONS,
-                SharedPreferencesManager.getInstance().readInt(
-                        SigninPromoController.getPromoShowCountPreferenceName(
-                                SigninAccessPoint.NTP_CONTENT_SUGGESTIONS)));
     }
 
     @Test
@@ -275,10 +230,6 @@
         Assert.assertEquals(0L,
                 SharedPreferencesManager.getInstance().readLong(
                         ChromePreferenceKeys.SIGNIN_PROMO_NTP_LAST_SHOWN_TIME));
-        Assert.assertEquals(0,
-                SharedPreferencesManager.getInstance().readInt(
-                        SigninPromoController.getPromoShowCountPreferenceName(
-                                SigninAccessPoint.NTP_CONTENT_SUGGESTIONS)));
     }
 
     @Test
@@ -302,9 +253,6 @@
     private void setNTPSyncPromoLimitsAndValues(
             long firstShownTime, long lastShownTime, int signinPromoResetAfterHours) {
         FeatureList.TestValues testValues = new FeatureList.TestValues();
-        testValues.addFieldTrialParamOverride(ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD,
-                "MaxSigninPromoImpressions", Integer.toString(MAX_SIGN_IN_PROMO_IMPRESSIONS));
-        testValues.addFeatureFlagOverride(ChromeFeatureList.ENHANCED_PROTECTION_PROMO_CARD, true);
         testValues.addFeatureFlagOverride(
                 ChromeFeatureList.FORCE_DISABLE_EXTENDED_SYNC_PROMOS, false);
         FeatureList.setTestValues(testValues);
@@ -314,10 +262,6 @@
         StartSurfaceConfiguration.SIGNIN_PROMO_NTP_RESET_AFTER_HOURS.setForTesting(
                 signinPromoResetAfterHours);
 
-        SharedPreferencesManager.getInstance().writeInt(
-                SigninPromoController.getPromoShowCountPreferenceName(
-                        SigninAccessPoint.NTP_CONTENT_SUGGESTIONS),
-                MAX_SIGN_IN_PROMO_IMPRESSIONS);
         SharedPreferencesManager.getInstance().writeLong(
                 ChromePreferenceKeys.SIGNIN_PROMO_NTP_FIRST_SHOWN_TIME, firstShownTime);
         SharedPreferencesManager.getInstance().writeLong(
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 65084496..05938324 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -770,14 +770,6 @@
         Chrome’s homepage
       </message>
 
-      <!-- Enhanced Protection Promo card -->
-      <message name="IDS_ENHANCED_PROTECTION_PROMO_TITLE" desc="The title displayed on homepage promo card, which guides the user to the chrome settings page to select the enhanced protection option.">
-        Get Chrome’s strongest security
-      </message>
-      <message name="IDS_ENHANCED_PROTECTION_PROMO_DESCRIPTION" desc="The description displayed on enhanced protection promo card, which guides the user to the chrome settings page to select the enhanced protection option.">
-        Enhanced protection does more to block phishing and malware
-      </message>
-
       <!-- Notifications preferences -->
       <message name="IDS_PREFS_NOTIFICATIONS" desc="Title for Notification preferences.">
         Notifications
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ENHANCED_PROTECTION_PROMO_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ENHANCED_PROTECTION_PROMO_DESCRIPTION.png.sha1
deleted file mode 100644
index 24ba182..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ENHANCED_PROTECTION_PROMO_DESCRIPTION.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-888b56743b22b1d76b9a0cd33645b49cd58e6a1a
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ENHANCED_PROTECTION_PROMO_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ENHANCED_PROTECTION_PROMO_TITLE.png.sha1
deleted file mode 100644
index 24ba182..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ENHANCED_PROTECTION_PROMO_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-888b56743b22b1d76b9a0cd33645b49cd58e6a1a
\ No newline at end of file
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.cc b/chrome/browser/ui/app_list/app_list_client_impl.cc
index bad11aa..9a2ae50 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl.cc
@@ -457,6 +457,11 @@
   return search_controller_.get();
 }
 
+void AppListClientImpl::SetSearchControllerForTest(
+    std::unique_ptr<app_list::SearchController> test_controller) {
+  search_controller_ = std::move(test_controller);
+}
+
 AppListModelUpdater* AppListClientImpl::GetModelUpdaterForTest() {
   return current_model_updater_;
 }
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.h b/chrome/browser/ui/app_list/app_list_client_impl.h
index d4efa84..bab7d710 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.h
+++ b/chrome/browser/ui/app_list/app_list_client_impl.h
@@ -151,6 +151,9 @@
 
   app_list::SearchController* search_controller();
 
+  void SetSearchControllerForTest(
+      std::unique_ptr<app_list::SearchController> test_controller);
+
   AppListModelUpdater* GetModelUpdaterForTest();
 
   // Initializes as if a new user logged in for testing.
diff --git a/chrome/browser/ui/app_list/app_list_sort_unittest.cc b/chrome/browser/ui/app_list/app_list_sort_unittest.cc
index c5974ff2..d207216 100644
--- a/chrome/browser/ui/app_list/app_list_sort_unittest.cc
+++ b/chrome/browser/ui/app_list/app_list_sort_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "ash/app_list/model/app_list_model.h"
 #include "ash/constants/ash_features.h"
+#include "base/containers/cxx20_erase_vector.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
 #include "chrome/browser/ui/app_list/app_list_test_util.h"
@@ -378,8 +379,7 @@
 
 // Verifies that merging two items to form a folder resets the nominal app list
 // sort order (if the app list is sorted at the time).
-// Disabled due to flakiness (https://crbug.com/1281828).
-TEST_F(TemporaryAppListSortTest, DISABLED_MergingItemsResetsSortOrder) {
+TEST_F(TemporaryAppListSortTest, MergingItemsResetsSortOrder) {
   RemoveAllExistingItems();
 
   std::vector<scoped_refptr<extensions::Extension>> apps;
@@ -405,26 +405,44 @@
 
   // Merge two items into a folder.
   ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
-  model_updater->model_for_test()->MergeItems(apps[8]->id(), apps[9]->id());
+  const std::string folder_id =
+      model_updater->model_for_test()->MergeItems(apps[8]->id(), apps[9]->id());
 
   // Verify that the app list is no longer considered sorted - new items are
   // added to the first position within the app list.
   EXPECT_FALSE(IsUnderTemporarySort());
   EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
-  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+  std::vector<std::string> ordered_names = GetOrderedNamesFromSyncableService();
+  // Note that newly created folder will have the same position as the item it
+  // was created from, so order of the original item and the folder may not be
+  // deterministic (both [folder, item], and [item, folder] are acceptable
+  // orders). Remove the folder from the list, and later test that the folder
+  // position is the same as the original item.
+  EXPECT_EQ(1u, base::EraseIf(ordered_names, [](const std::string& item) {
+              return item == "";
+            }));
+  EXPECT_EQ(ordered_names,
             std::vector<std::string>({"Item 0", "Item 1", "Item 2", "Item 3",
                                       "Item 4", "Item 5", "Item 6", "Item 7",
-                                      "Item 8", "", "Item 9"}));
+                                      "Item 8", "Item 9"}));
+  EXPECT_EQ(GetPositionFromSyncData(apps[8]->id()),
+            GetPositionFromSyncData(folder_id));
 
   scoped_refptr<extensions::Extension> new_app = MakeApp(
       "Item 10", GenerateId("new_install"), extensions::Extension::NO_FLAGS);
   InstallExtension(new_app.get());
 
   EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
-  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+  ordered_names = GetOrderedNamesFromSyncableService();
+  EXPECT_EQ(1u, base::EraseIf(ordered_names, [](const std::string& item) {
+              return item == "";
+            }));
+  EXPECT_EQ(ordered_names,
             std::vector<std::string>({"Item 10", "Item 0", "Item 1", "Item 2",
                                       "Item 3", "Item 4", "Item 5", "Item 6",
-                                      "Item 7", "Item 8", "", "Item 9"}));
+                                      "Item 7", "Item 8", "Item 9"}));
+  EXPECT_EQ(GetPositionFromSyncData(apps[8]->id()),
+            GetPositionFromSyncData(folder_id));
 }
 
 // Verifies that moving an item from a folder to root apps grid resets the
diff --git a/chrome/browser/ui/ash/shelf/app_service/exo_app_type_resolver.cc b/chrome/browser/ui/ash/shelf/app_service/exo_app_type_resolver.cc
index bb5282c..34b6ec79 100644
--- a/chrome/browser/ui/ash/shelf/app_service/exo_app_type_resolver.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/exo_app_type_resolver.cc
@@ -14,6 +14,7 @@
 #include "components/app_restore/window_properties.h"
 #include "components/exo/permission.h"
 #include "components/exo/shell_surface_util.h"
+#include "components/exo/window_properties.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/base/class_property.h"
 
@@ -40,6 +41,10 @@
     out_properties_container.SetProperty(
         exo::kPermissionKey,
         new exo::Permission(exo::Permission::Capability::kActivate));
+    // Only Lacros windows should allow restore/fullscreen to kick windows out
+    // of fullscreen.
+    out_properties_container.SetProperty(exo::kRestoreOrMaximizeExitsFullscreen,
+                                         true);
   } else if (borealis::BorealisWindowManager::IsBorealisWindowId(
                  params.app_id.empty() ? params.startup_id : params.app_id)) {
     // TODO(b/165865831): Stop using CROSTINI_APP for borealis windows.
diff --git a/chrome/browser/ui/ash/thumbnail_loader.cc b/chrome/browser/ui/ash/thumbnail_loader.cc
index 1c8e5c8..1c74be2 100644
--- a/chrome/browser/ui/ash/thumbnail_loader.cc
+++ b/chrome/browser/ui/ash/thumbnail_loader.cc
@@ -15,9 +15,8 @@
 #include "base/values.h"
 #include "chrome/browser/ash/file_manager/app_id.h"
 #include "chrome/browser/ash/file_manager/fileapi_util.h"
-#include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
-#include "chrome/browser/bitmap_fetcher/bitmap_fetcher_delegate.h"
 #include "chrome/browser/extensions/api/messaging/native_message_port.h"
+#include "chrome/browser/image_decoder/image_decoder.h"
 #include "chrome/browser/profiles/profile.h"
 #include "extensions/browser/api/messaging/channel_endpoint.h"
 #include "extensions/browser/api/messaging/message_service.h"
@@ -26,6 +25,7 @@
 #include "extensions/common/api/messaging/port_id.h"
 #include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/extension.h"
+#include "net/base/data_url.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/data_decoder/public/cpp/data_decoder.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -136,22 +136,26 @@
 }  // namespace
 
 // Converts a data URL to bitmap.
-class ThumbnailLoader::ThumbnailDecoder : public BitmapFetcherDelegate {
+class ThumbnailLoader::ThumbnailDecoder : public ImageDecoder::ImageRequest {
  public:
-  explicit ThumbnailDecoder(Profile* profile) : profile_(profile) {}
+  ThumbnailDecoder() = default;
 
   ThumbnailDecoder(const ThumbnailDecoder&) = delete;
   ThumbnailDecoder& operator=(const ThumbnailDecoder&) = delete;
   ~ThumbnailDecoder() override = default;
 
-  // BitmapFetcherDelegate:
-  void OnFetchComplete(const GURL& url, const SkBitmap* bitmap) override {
-    std::move(callback_).Run(bitmap, base::File::FILE_OK);
+  // ImageDecoder::ImageRequest:
+  void OnImageDecoded(const SkBitmap& bitmap) override {
+    std::move(callback_).Run(&bitmap, base::File::FILE_OK);
+  }
+
+  // ImageDecoder::ImageRequest:
+  void OnDecodeImageFailed() override {
+    std::move(callback_).Run(/*bitmap=*/nullptr, base::File::FILE_ERROR_FAILED);
   }
 
   void Start(const std::string& data, ThumbnailLoader::ImageCallback callback) {
     DCHECK(!callback_);
-    DCHECK(!bitmap_fetcher_);
 
     // The data sent from the image loader extension should be in form of a data
     // URL.
@@ -162,24 +166,19 @@
       return;
     }
 
+    std::string mime_type, charset, image_data;
+    if (!net::DataURL::Parse(data_url, &mime_type, &charset, &image_data)) {
+      std::move(callback).Run(/*bitmap=*/nullptr,
+                              base::File::FILE_ERROR_FAILED);
+      return;
+    }
+
     callback_ = std::move(callback);
-
-    // Note that the image downloader will not use network traffic for data
-    // URLs.
-    bitmap_fetcher_ = std::make_unique<BitmapFetcher>(
-        data_url, this, MISSING_TRAFFIC_ANNOTATION);
-
-    bitmap_fetcher_->Init(
-        /*referrer=*/std::string(), net::ReferrerPolicy::NEVER_CLEAR,
-        network::mojom::CredentialsMode::kOmit);
-
-    bitmap_fetcher_->Start(profile_->GetURLLoaderFactory().get());
+    ImageDecoder::Start(this, std::move(image_data));
   }
 
  private:
-  Profile* const profile_;
   ThumbnailLoader::ImageCallback callback_;
-  std::unique_ptr<BitmapFetcher> bitmap_fetcher_;
 };
 
 ThumbnailLoader::ThumbnailLoader(Profile* profile) : profile_(profile) {}
@@ -312,7 +311,7 @@
     return;
   }
 
-  auto thumbnail_decoder = std::make_unique<ThumbnailDecoder>(profile_);
+  auto thumbnail_decoder = std::make_unique<ThumbnailDecoder>();
   ThumbnailDecoder* thumbnail_decoder_ptr = thumbnail_decoder.get();
   thumbnail_decoders_.emplace(request_id, std::move(thumbnail_decoder));
   thumbnail_decoder_ptr->Start(
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
index 6f47b02..0c80dda 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/dcheck_is_on.h"
 #include "base/location.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -36,6 +37,11 @@
 #include "ui/views/focus/focus_manager.h"
 #include "ui/views/layout/fill_layout.h"
 
+#if DCHECK_IS_ON()
+#include "base/containers/fixed_flat_set.h"
+#include "base/strings/string_piece.h"
+#endif
+
 namespace autofill {
 
 int AutofillPopupBaseView::GetCornerRadius() {
@@ -186,6 +192,19 @@
     is_ax_menu_start_event_fired_ = true;
   }
   selected_view->GetViewAccessibility().SetPopupFocusOverride();
+#if DCHECK_IS_ON()
+  constexpr auto kDerivedClasses = base::MakeFixedFlatSet<base::StringPiece>(
+      {"AutofillPopupSuggestionView", "PasswordPopupSuggestionView",
+       "AutofillPopupFooterView", "AutofillPopupSeparatorView",
+       "AutofillPopupWarningView", "AutofillPopupBaseView"});
+  DCHECK(kDerivedClasses.contains(selected_view->GetClassName()))
+      << "If you add a new derived class from AutofillPopupRowView, add it "
+         "here and to onSelection(evt) in "
+         "chrome/browser/resources/chromeos/accessibility/chromevox/background/"
+         "desktop_automation_handler.js to ensure that ChromeVox announces "
+         "the item when selected. Missing class: "
+      << selected_view->GetClassName();
+#endif
   selected_view->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
 }
 
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
index 7810803..57fe55c2 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
@@ -202,6 +202,8 @@
   }
 
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
+    if (GetAccessibleName().empty())
+      node_data->SetNameExplicitlyEmpty();
     views::LabelButton::GetAccessibleNodeData(node_data);
     node_data->AddStringAttribute(
         ax::mojom::StringAttribute::kRoleDescription,
diff --git a/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_network_handler.cc b/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_network_handler.cc
index fe10869e..451bf39 100644
--- a/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_network_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_network_handler.cc
@@ -81,10 +81,14 @@
 void NetworkConfigMessageHandler::Initialize(base::Value::ConstListView args) {
   AllowJavascript();
 
-  // Notify the main dialog that the network dialog has been loaded.
+  // Check if the main dialog exists and notify that the network dialog has
+  // been loaded.
   auto* password_sync_manager = GetInSessionPasswordSyncManager();
-  password_sync_manager->get_reauth_dialog_for_testing()
-      ->OnNetworkDialogReadyForTesting();
+  LockScreenStartReauthDialog* start_reauth_dialog =
+      password_sync_manager->get_reauth_dialog_for_testing();
+  if (!start_reauth_dialog)
+    return;
+  start_reauth_dialog->OnNetworkDialogReadyForTesting();
 }
 
 void NetworkConfigMessageHandler::ShowNetworkDetails(
diff --git a/chrome/browser/ui/webui/policy/policy_ui_handler.cc b/chrome/browser/ui/webui/policy/policy_ui_handler.cc
index cc62e60b..7ea8ee3de 100644
--- a/chrome/browser/ui/webui/policy/policy_ui_handler.cc
+++ b/chrome/browser/ui/webui/policy/policy_ui_handler.cc
@@ -108,6 +108,7 @@
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chromeos/crosapi/mojom/policy_service.mojom.h"
 #include "chromeos/lacros/lacros_service.h"
+#include "components/policy/core/common/policy_loader_lacros.h"
 #endif
 
 #if defined(OS_MAC)
@@ -151,11 +152,15 @@
   if (!user)
     return;
   dict->SetBoolean("isAffiliated", user->IsAffiliated());
-#else   // BUILDFLAG(IS_CHROMEOS_ASH)
+#else
   // Don't show affiliation status if the browser isn't enrolled in CBCM.
-  if (!policy::BrowserDMTokenStorage::Get()->RetrieveDMToken().is_valid())
-    return;
-
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  if (!profile->IsMainProfile())
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+  {
+    if (!policy::BrowserDMTokenStorage::Get()->RetrieveDMToken().is_valid())
+      return;
+  }
   dict->SetBoolean("isAffiliated",
                    chrome::enterprise_util::IsProfileAffiliated(profile));
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
@@ -251,6 +256,29 @@
   raw_ptr<Profile> profile_;
 };
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// A cloud policy status provider for device account.
+class UserPolicyStatusProviderLacros : public policy::PolicyStatusProvider {
+ public:
+  UserPolicyStatusProviderLacros(policy::PolicyLoaderLacros* loader,
+                                 Profile* profile);
+
+  UserPolicyStatusProviderLacros(const UserPolicyStatusProviderLacros&) =
+      delete;
+  UserPolicyStatusProviderLacros& operator=(
+      const UserPolicyStatusProviderLacros&) = delete;
+
+  ~UserPolicyStatusProviderLacros() override;
+
+  // CloudPolicyCoreStatusProvider implementation.
+  void GetStatus(base::DictionaryValue* dict) override;
+
+ private:
+  raw_ptr<Profile> profile_;
+  raw_ptr<policy::PolicyLoaderLacros> loader_;
+};
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 // A cloud policy status provider for user policy on Chrome OS.
 class UserCloudPolicyStatusProviderChromeOS
@@ -436,6 +464,40 @@
   GetUserAffiliationStatus(dict, profile_);
 }
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+UserPolicyStatusProviderLacros::UserPolicyStatusProviderLacros(
+    policy::PolicyLoaderLacros* loader,
+    Profile* profile)
+    : profile_(profile), loader_(loader) {}
+
+UserPolicyStatusProviderLacros::~UserPolicyStatusProviderLacros() = default;
+
+void UserPolicyStatusProviderLacros::GetStatus(base::DictionaryValue* dict) {
+  em::PolicyData* policy = loader_->GetPolicyData();
+  if (!policy)
+    return;
+  GetStatusFromPolicyData(policy, dict);
+  ExtractDomainFromUsername(dict);
+  GetUserAffiliationStatus(dict, profile_);
+
+  // Get last fetched time from policy, since we have no refresh scheduler here.
+  base::Time last_refresh_time =
+      policy && policy->has_timestamp()
+          ? base::Time::FromJavaTime(policy->timestamp())
+          : base::Time();
+  dict->SetString("timeSinceLastRefresh",
+                  GetTimeSinceLastRefreshString(last_refresh_time));
+
+  // TODO(https://crbug.com/1243869): Pass this information from Ash through
+  // Mojo. Assume no error for now.
+  dict->SetBoolean("error", false);
+  dict->SetString("status",
+                  FormatStoreStatus(
+                      policy::CloudPolicyStore::STATUS_OK,
+                      policy::CloudPolicyValidatorBase::Status::VALIDATION_OK));
+}
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 UserCloudPolicyStatusProviderChromeOS::UserCloudPolicyStatusProviderChromeOS(
     policy::CloudPolicyCore* core,
@@ -751,6 +813,15 @@
   if (user_cloud_policy_manager) {
     user_status_provider_ = std::make_unique<UserCloudPolicyStatusProvider>(
         user_cloud_policy_manager->core(), profile);
+  } else {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    if (profile->IsMainProfile()) {
+      user_status_provider_ = std::make_unique<UserPolicyStatusProviderLacros>(
+          g_browser_process->browser_policy_connector()
+              ->device_account_policy_loader(),
+          profile);
+    }
+#endif
   }
 
   policy::MachineLevelUserCloudPolicyManager* manager =
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 24c7eee..492b701 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1979,6 +1979,8 @@
      IDS_SETTINGS_SITE_SETTINGS_ALL_SITES_SORT_METHOD_STORAGE},
     {"siteSettingsAllSitesSortMethodName",
      IDS_SETTINGS_SITE_SETTINGS_ALL_SITES_SORT_METHOD_NAME},
+    {"siteSettingsSiteEntryPartitionedLabel",
+     IDS_SETTINGS_SITE_SETTINGS_SITE_ENTRY_PARTITIONED_LABEL},
     {"siteSettingsSiteRepresentationSeparator",
      IDS_SETTINGS_SITE_SETTINGS_SITE_REPRESENTATION_SEPARATOR},
     {"siteSettingsAppProtocolHandlers",
@@ -2312,6 +2314,8 @@
      IDS_SETTINGS_SITE_SETTINGS_REMOVE_SITE_ORIGIN_DIALOG_TITLE},
     {"siteSettingsRemoveSiteOriginAppDialogTitle",
      IDS_SETTINGS_SITE_SETTINGS_REMOVE_SITE_ORIGIN_APP_DIALOG_TITLE},
+    {"siteSettingsRemoveSiteOriginPartitionedDialogTitle",
+     IDS_SETTINGS_SITE_SETTINGS_REMOVE_SITE_ORIGIN_PARTITIONED_DIALOG_TITLE},
     {"siteSettingsRemoveSiteGroupDialogTitle",
      IDS_SETTINGS_SITE_SETTINGS_REMOVE_SITE_GROUP_DIALOG_TITLE},
     {"siteSettingsRemoveSiteGroupAppDialogTitle",
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.cc b/chrome/browser/ui/webui/settings/site_settings_handler.cc
index e830ab2..52c1642 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.cc
@@ -112,7 +112,8 @@
   kEnterSiteDetails = 6,
   kRemoveSiteGroup = 7,
   kRemoveOrigin = 8,
-  kMaxValue = kRemoveOrigin,
+  kRemoveOriginPartitioned = 9,
+  kMaxValue = kRemoveOriginPartitioned,
 };
 
 // Return an appropriate API Permission ID for the given string name.
@@ -185,35 +186,44 @@
 }
 
 // Groups |url| into sets of eTLD+1s in |site_group_map|, assuming |url| is an
-// origin.
+// origin. The effective eTLD+1 is |partition_etld_plus1| is set, otherwise it
+// is the eTLD+1 of |url|.
 // There are three cases:
-// 1. The ETLD+1 of |url| is not yet in |site_group_map|. We add the ETLD+1
-//    to |site_group_map|. If the |url| is an ETLD+1 cookie origin, put a
-//    placeholder origin for the ETLD+1.
-// 2. The ETLD+1 of |url| is in |site_group_map|, and is equal to host of
+// 1. The effective eLTD+1 is not yet in |site_group_map|. We add the ETLD+1
+//    to |site_group_map|. If the |url| is an effective ETLD+1 cookie origin,
+//    put a placeholder origin for the ETLD+1.
+// 2. The effective ETLD+1 is in |site_group_map|, and is equal to host of
 //    |url|. This means case 1 has already happened and nothing more needs to
 //    be done.
-// 3. The ETLD+1 of |url| is in |site_group_map| and is different to host of
-//    |url|. For a cookies url, if a https origin with same host exists,
-//    nothing more needs to be done.
+// 3. The effective ETLD+1 is in |site_group_map| and is different to host of
+//    |url|. For a cookies url, if a https origin with same host and partitioned
+//    status exists, nothing more needs to be done.
 // In case 3, we try to add |url| to the set of origins for the ETLD+1. If an
 // existing origin is a placeholder, delete it, because the placeholder is no
 // longer needed.
 void CreateOrAppendSiteGroupEntry(
-    std::map<std::string, std::set<std::string>>* site_group_map,
+    std::map<std::string, std::set<std::pair<std::string, bool>>>*
+        site_group_map,
     const GURL& url,
-    bool url_is_origin_with_cookies = false) {
-  std::string etld_plus1_string =
-      net::registry_controlled_domains::GetDomainAndRegistry(
-          url, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
-  auto entry = site_group_map->find(etld_plus1_string);
+    bool url_is_origin_with_cookies = false,
+    absl::optional<std::string> partition_etld_plus1 = absl::nullopt) {
+  bool is_partitioned = partition_etld_plus1.has_value();
+  std::string effective_etld_plus1_string =
+      is_partitioned
+          ? partition_etld_plus1.value()
+          : net::registry_controlled_domains::GetDomainAndRegistry(
+                url,
+                net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+  auto entry = site_group_map->find(effective_etld_plus1_string);
   bool etld_plus1_cookie_url =
-      url_is_origin_with_cookies && url.host() == etld_plus1_string;
+      url_is_origin_with_cookies && url.host() == effective_etld_plus1_string;
 
   if (entry == site_group_map->end()) {
     // Case 1:
     std::string origin = etld_plus1_cookie_url ? kPlaceholder : url.spec();
-    site_group_map->emplace(etld_plus1_string, std::set<std::string>({origin}));
+    site_group_map->emplace(
+        effective_etld_plus1_string,
+        std::set<std::pair<std::string, bool>>({{origin, is_partitioned}}));
     return;
   }
   // Case 2:
@@ -226,11 +236,13 @@
     // version into the map.
     std::string https_url = std::string(url::kHttpsScheme) +
                             url::kStandardSchemeSeparator + url.host() + "/";
-    if (entry->second.find(https_url) != entry->second.end())
+    if (entry->second.find({https_url, is_partitioned}) !=
+        entry->second.end()) {
       return;
+    }
   }
-  entry->second.insert(url.spec());
-  auto placeholder = entry->second.find(kPlaceholder);
+  entry->second.insert({url.spec(), is_partitioned});
+  auto placeholder = entry->second.find({kPlaceholder, is_partitioned});
   if (placeholder != entry->second.end())
     entry->second.erase(placeholder);
 }
@@ -246,7 +258,8 @@
 // Converts a given |site_group_map| to a list of base::DictionaryValues, adding
 // the site engagement score for each origin.
 void ConvertSiteGroupMapToListValue(
-    const std::map<std::string, std::set<std::string>>& site_group_map,
+    const std::map<std::string, std::set<std::pair<std::string, bool>>>&
+        site_group_map,
     const std::set<std::string>& origin_permission_set,
     base::Value* list_value,
     Profile* profile) {
@@ -263,7 +276,9 @@
                       base::Value(entry.first));
     bool has_installed_pwa = false;
     base::Value origin_list(base::Value::Type::LIST);
-    for (const std::string& origin : entry.second) {
+    for (const auto& origin_is_partitioned : entry.second) {
+      const auto& origin = origin_is_partitioned.first;
+      bool is_partitioned = origin_is_partitioned.second;
       base::Value origin_object(base::Value::Type::DICTIONARY);
       // If origin is placeholder, create a http ETLD+1 origin for it.
       if (origin == kPlaceholder) {
@@ -274,6 +289,7 @@
       } else {
         origin_object.SetKey("origin", base::Value(origin));
       }
+      origin_object.SetKey("isPartitioned", base::Value(is_partitioned));
       origin_object.SetKey(
           "engagement",
           base::Value(engagement_service->GetScore(GURL(origin))));
@@ -344,7 +360,8 @@
 }
 
 void UpdateDataFromCookiesTree(
-    std::map<std::string, std::set<std::string>>* all_sites_map,
+    std::map<std::string, std::set<std::pair<std::string, bool>>>*
+        all_sites_map,
     std::map<std::string, int64_t>* origin_size_map,
     const GURL& origin,
     int64_t size) {
@@ -403,6 +420,82 @@
   NOTREACHED();
 }
 
+// Removes all nodes from |model| which match |origin| and |etld_plus1|. At
+// least one of |origin| or |etld_plus1| must be set. If only |origin| is set,
+// then unpartitioned storage for that origin is removed. If only |etld_plus1|
+// is set, then any unpartitioned storage which matches that etld + 1, or
+// partitioned storage where it is the partitioning site, is removed. If both
+// |origin| and |etld_plus1| is set, then only storage for |origin| partitioned
+// by |etld_plus1| is removed.
+void RemoveMatchingNodes(CookiesTreeModel* model,
+                         absl::optional<std::string> origin,
+                         absl::optional<std::string> etld_plus1) {
+  DCHECK(origin || etld_plus1);
+  std::vector<CookieTreeNode*> nodes_to_delete;
+
+  for (const auto& host_node : model->GetRoot()->children()) {
+    bool origin_matches =
+        origin &&
+        *origin == host_node->GetDetailedInfo().origin.GetURL().spec();
+
+    if (origin && !origin_matches) {
+      // If the origin is set, host nodes which do not match that origin cannot
+      // contain storage targeted for removal.
+      continue;
+    }
+
+    std::string host_node_etld_plus1 =
+        net::registry_controlled_domains::GetDomainAndRegistry(
+            base::UTF16ToUTF8(host_node->GetTitle()),
+            net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+
+    bool etld_plus1_matches = etld_plus1 && *etld_plus1 == host_node_etld_plus1;
+
+    for (const auto& storage_type_node : host_node->children()) {
+      if (storage_type_node->GetDetailedInfo().node_type !=
+          CookieTreeNode::DetailedInfo::TYPE_COOKIES) {
+        // Non cookie storage cannot (currently) be partitioned.
+        if (origin && etld_plus1) {
+          continue;
+        }
+
+        if (origin_matches || etld_plus1_matches) {
+          nodes_to_delete.push_back(storage_type_node.get());
+          continue;
+        }
+      } else {
+        // Every cookie must be inspected to confirm partition state.
+        // TODO(crbug.com/1271155): This is slow, and should be addressed when
+        // the CookiesTreeModel is deprecated.
+        for (const auto& cookie_node : storage_type_node->children()) {
+          const auto& cookie = cookie_node->GetDetailedInfo().cookie;
+          if (!cookie->IsPartitioned() &&
+              (origin_matches || etld_plus1_matches) &&
+              (!origin || !etld_plus1)) {
+            nodes_to_delete.push_back(cookie_node.get());
+            continue;
+          }
+          if (cookie->IsPartitioned()) {
+            const auto& partition_site =
+                cookie->PartitionKey()->site().GetURL().host();
+
+            // If an origin has been set, it must match the origin of the
+            // current node, which means it can be ignored.
+            DCHECK(!origin || origin_matches);
+
+            if (etld_plus1 && partition_site == *etld_plus1) {
+              nodes_to_delete.push_back(cookie_node.get());
+            }
+          }
+        }
+      }
+    }
+  }
+
+  for (auto* node : nodes_to_delete)
+    model->DeleteCookieNode(node);
+}
+
 }  // namespace
 
 SiteSettingsHandler::SiteSettingsHandler(Profile* profile)
@@ -419,8 +512,13 @@
       base::BindRepeating(&SiteSettingsHandler::HandleFetchUsageTotal,
                           base::Unretained(this)));
   web_ui()->RegisterDeprecatedMessageCallback(
-      "clearUsage", base::BindRepeating(&SiteSettingsHandler::HandleClearUsage,
-                                        base::Unretained(this)));
+      "clearUnpartitionedUsage",
+      base::BindRepeating(&SiteSettingsHandler::HandleClearUnpartitionedUsage,
+                          base::Unretained(this)));
+  web_ui()->RegisterDeprecatedMessageCallback(
+      "clearPartitionedUsage",
+      base::BindRepeating(&SiteSettingsHandler::HandleClearPartitionedUsage,
+                          base::Unretained(this)));
   web_ui()->RegisterDeprecatedMessageCallback(
       "setDefaultValueForContentType",
       base::BindRepeating(
@@ -671,19 +769,16 @@
   EnsureCookiesTreeModelCreated();
 }
 
-void SiteSettingsHandler::HandleClearUsage(const base::ListValue* args) {
+void SiteSettingsHandler::HandleClearUnpartitionedUsage(
+    const base::ListValue* args) {
   CHECK_EQ(1U, args->GetList().size());
   const std::string& origin = args->GetList()[0].GetString();
   GURL url(origin);
   if (!url.is_valid())
     return;
   AllowJavascript();
-  for (const auto& node : cookies_tree_model_->GetRoot()->children()) {
-    if (origin == node->GetDetailedInfo().origin.GetURL().spec()) {
-      cookies_tree_model_->DeleteCookieNode(node.get());
-      break;
-    }
-  }
+
+  RemoveMatchingNodes(cookies_tree_model_.get(), origin, absl::nullopt);
 
   // TODO(crbug.com/1271155, crbug.com/1268626): This is a temporary hack while
   // the CookiesTreeModel is deprecated. Currently cookies will fail to be
@@ -696,6 +791,15 @@
           url, GURL(), ContentSettingsType::CLIENT_HINTS, nullptr);
 }
 
+void SiteSettingsHandler::HandleClearPartitionedUsage(
+    const base::ListValue* args) {
+  CHECK_EQ(2U, args->GetList().size());
+  const std::string& origin = args->GetList()[0].GetString();
+  const std::string& etld_plus1 = args->GetList()[1].GetString();
+
+  RemoveMatchingNodes(cookies_tree_model_.get(), origin, etld_plus1);
+}
+
 void SiteSettingsHandler::HandleSetDefaultValueForContentType(
     const base::ListValue* args) {
   CHECK_EQ(2U, args->GetList().size());
@@ -891,7 +995,8 @@
 
 base::Value SiteSettingsHandler::PopulateCookiesAndUsageData(Profile* profile) {
   std::map<std::string, int64_t> origin_size_map;
-  std::map<std::string, int> origin_cookie_map;
+  std::map<std::pair<std::string, absl::optional<std::string>>, int>
+      origin_cookie_map;
   base::Value list_value(base::Value::Type::LIST);
 
   GetOriginStorage(&all_sites_map_, &origin_size_map);
@@ -905,7 +1010,8 @@
     int cookie_num = 0;
     const std::string& etld_plus1 =
         site_group.FindKey(kEffectiveTopLevelDomainPlus1Name)->GetString();
-    const auto& etld_plus1_cookie_num_it = origin_cookie_map.find(etld_plus1);
+    const auto& etld_plus1_cookie_num_it =
+        origin_cookie_map.find({etld_plus1, absl::nullopt});
     // Add the number of eTLD+1 scoped cookies.
     if (etld_plus1_cookie_num_it != origin_cookie_map.end())
       cookie_num = etld_plus1_cookie_num_it->second;
@@ -913,13 +1019,19 @@
     // numbers.
     for (base::Value& origin_info : origin_list->GetList()) {
       const std::string& origin = origin_info.FindKey("origin")->GetString();
-      const auto& size_info_it = origin_size_map.find(origin);
-      if (size_info_it != origin_size_map.end())
-        origin_info.SetKey(
-            "usage", base::Value(static_cast<double>(size_info_it->second)));
+      bool is_partitioned = origin_info.FindKey("isPartitioned")->GetBool();
+      if (!is_partitioned) {
+        // Only unpartitioned storage has a size.
+        const auto& size_info_it = origin_size_map.find(origin);
+        if (size_info_it != origin_size_map.end())
+          origin_info.SetKey(
+              "usage", base::Value(static_cast<double>(size_info_it->second)));
+      }
       GURL origin_url(origin);
-      const auto& origin_cookie_num_it =
-          origin_cookie_map.find(origin_url.host());
+      const auto& origin_cookie_num_it = origin_cookie_map.find(
+          {origin_url.host(),
+           (is_partitioned ? absl::optional<std::string>(etld_plus1)
+                           : absl::nullopt)});
       if (origin_cookie_num_it != origin_cookie_map.end()) {
         origin_info.SetKey(kNumCookies,
                            base::Value(origin_cookie_num_it->second));
@@ -1532,7 +1644,8 @@
 }
 
 void SiteSettingsHandler::GetOriginStorage(
-    std::map<std::string, std::set<std::string>>* all_sites_map,
+    std::map<std::string, std::set<std::pair<std::string, bool>>>*
+        all_sites_map,
     std::map<std::string, int64_t>* origin_size_map) {
   CHECK(cookies_tree_model_.get());
 
@@ -1546,38 +1659,57 @@
 }
 
 void SiteSettingsHandler::GetOriginCookies(
-    std::map<std::string, std::set<std::string>>* all_sites_map,
-    std::map<std::string, int>* origin_cookie_map) {
+    std::map<std::string, std::set<std::pair<std::string, bool>>>*
+        all_sites_map,
+    std::map<std::pair<std::string, absl::optional<std::string>>, int>*
+        origin_cookie_map) {
   CHECK(cookies_tree_model_.get());
   // Get sites that don't have data but have cookies.
   for (const auto& site : cookies_tree_model_->GetRoot()->children()) {
     GURL url = site->GetDetailedInfo().origin.GetURL();
-    (*origin_cookie_map)[url.host()] = site->NumberOfCookies();
-    CreateOrAppendSiteGroupEntry(all_sites_map, url,
-                                 /*url_is_origin_with_cookies = */ true);
+    if (!site->NumberOfCookies())
+      continue;
+
+    // Each cookie will need to be inspected to see if it is partitioned, so it
+    // may be associated with the appropriate eTLD+1.
+    // TODO (crbug.com/1271155): This is slow, the replacement for the
+    // CookiesTreeModel should improve this significantly.
+    for (const auto& site_child : site->children()) {
+      if (site_child->GetDetailedInfo().node_type !=
+          CookieTreeNode::DetailedInfo::TYPE_COOKIES) {
+        continue;
+      }
+
+      for (const auto& cookie : site_child->children()) {
+        const auto& detailed_info = cookie->GetDetailedInfo();
+        DCHECK(detailed_info.node_type ==
+               CookieTreeNode::DetailedInfo::TYPE_COOKIE);
+        DCHECK(detailed_info.cookie);
+
+        absl::optional<std::string> associated_etld_plus1 =
+            detailed_info.cookie->IsPartitioned()
+                ? absl::optional<std::string>(
+                      detailed_info.cookie->PartitionKey()
+                          ->site()
+                          .GetURL()
+                          .host())
+                : absl::nullopt;
+        CreateOrAppendSiteGroupEntry(all_sites_map, url,
+                                     /*url_is_origin_with_cookies = */ true,
+                                     associated_etld_plus1);
+        (*origin_cookie_map)[{url.host(), associated_etld_plus1}]++;
+      }
+    }
   }
 }
 
 void SiteSettingsHandler::HandleClearEtldPlus1DataAndCookies(
     const base::ListValue* args) {
   CHECK_EQ(1U, args->GetList().size());
-  const std::string& etld_plus1_string = args->GetList()[0].GetString();
+  const std::string& etld_plus1 = args->GetList()[0].GetString();
 
   AllowJavascript();
-  CookieTreeNode* parent = cookies_tree_model_->GetRoot();
-
-  // Find all the nodes that contain the given etld+1.
-  std::vector<CookieTreeNode*> nodes_to_delete;
-  for (const auto& node : parent->children()) {
-    std::string cookie_node_etld_plus1 =
-        net::registry_controlled_domains::GetDomainAndRegistry(
-            base::UTF16ToUTF8(node->GetTitle()),
-            net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
-    if (etld_plus1_string == cookie_node_etld_plus1)
-      nodes_to_delete.push_back(node.get());
-  }
-  for (auto* node : nodes_to_delete)
-    cookies_tree_model_->DeleteCookieNode(node);
+  RemoveMatchingNodes(cookies_tree_model_.get(), absl::nullopt, etld_plus1);
 }
 
 void SiteSettingsHandler::HandleRecordAction(const base::ListValue* args) {
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.h b/chrome/browser/ui/webui/settings/site_settings_handler.h
index 8047556..8ecbd74 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.h
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.h
@@ -127,7 +127,12 @@
   FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, ZoomLevels);
   FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest,
                            HandleClearEtldPlus1DataAndCookies);
-  FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, HandleClearUsage);
+  FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest,
+                           HandleClearUnpartitionedUsage);
+  FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest,
+                           ClearUnpartitionedClearsHints);
+  FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest,
+                           HandleClearPartitionedUsage);
   FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, CookieSettingDescription);
   FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, HandleGetFormattedBytes);
   FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest,
@@ -147,21 +152,26 @@
   // Calculates the data storage that has been used for each origin, and
   // stores the information in the |all_sites_map| and |origin_size_map|.
   void GetOriginStorage(
-      std::map<std::string, std::set<std::string>>* all_sites_map,
+      std::map<std::string, std::set<std::pair<std::string, bool>>>*
+          all_sites_map,
       std::map<std::string, int64_t>* origin_size_map);
 
   // Calculates the number of cookies for each etld+1 and each origin, and
   // stores the information in the |all_sites_map| and |origin_cookie_map|.
   void GetOriginCookies(
-      std::map<std::string, std::set<std::string>>* all_sites_map,
-      std::map<std::string, int>* origin_cookie_map);
+      std::map<std::string, std::set<std::pair<std::string, bool>>>*
+          all_sites_map,
+      std::map<std::pair<std::string, absl::optional<std::string>>, int>*
+          origin_cookie_map);
 
   // Asynchronously fetches the usage for a given origin. Replies back with
   // OnGetUsageInfo above.
   void HandleFetchUsageTotal(const base::ListValue* args);
 
   // Deletes the storage being used for a given host.
-  void HandleClearUsage(const base::ListValue* args);
+  void HandleClearUnpartitionedUsage(const base::ListValue* args);
+
+  void HandleClearPartitionedUsage(const base::ListValue* args);
 
   // Gets and sets the default value for a particular content settings type.
   void HandleSetDefaultValueForContentType(const base::ListValue* args);
@@ -303,7 +313,7 @@
   bool send_sites_list_ = false;
 
   // Populated every time the user reloads the All Sites page.
-  std::map<std::string, std::set<std::string>> all_sites_map_;
+  std::map<std::string, std::set<std::pair<std::string, bool>>> all_sites_map_;
 
   // Store the origins that has permission settings.
   std::set<std::string> origin_permission_set_;
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
index d8f30b8..a8e1168 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
@@ -478,7 +478,7 @@
     mock_browsing_data_cookie_helper->AddCookieSamples(
         GURL("http://example.com"), "A=1");
     mock_browsing_data_cookie_helper->AddCookieSamples(
-        GURL("http://www.example.com/"), "B=1");
+        GURL("https://www.example.com/"), "B=1");
     mock_browsing_data_cookie_helper->AddCookieSamples(
         GURL("http://abc.example.com"), "C=1");
     mock_browsing_data_cookie_helper->AddCookieSamples(
@@ -487,6 +487,22 @@
         GURL("http://google.com"), "B=1");
     mock_browsing_data_cookie_helper->AddCookieSamples(
         GURL("http://google.com.au"), "A=1");
+
+    mock_browsing_data_cookie_helper->AddCookieSamples(
+        GURL("https://www.example.com"),
+        "__Host-A=1; Path=/; Partitioned; Secure;",
+        net::CookiePartitionKey::FromURLForTesting(
+            GURL("https://google.com.au")));
+    mock_browsing_data_cookie_helper->AddCookieSamples(
+        GURL("https://www.another-example.com"),
+        "__Host-A=1; Path=/; Partitioned; Secure;",
+        net::CookiePartitionKey::FromURLForTesting(
+            GURL("https://google.com.au")));
+    mock_browsing_data_cookie_helper->AddCookieSamples(
+        GURL("https://www.example.com"),
+        "__Host-A=1; Path=/; Partitioned; Secure;",
+        net::CookiePartitionKey::FromURLForTesting(GURL("https://google.com")));
+
     mock_browsing_data_cookie_helper->Notify();
 
     handler()->SetCookiesTreeModelForTesting(
@@ -500,6 +516,17 @@
     return data.arg2()->GetList();
   }
 
+  std::vector<CookieTreeNode*> GetHostNodes(GURL url) {
+    std::vector<CookieTreeNode*> nodes;
+    for (const auto& host_node :
+         handler()->cookies_tree_model_->GetRoot()->children()) {
+      if (host_node->GetDetailedInfo().origin.GetURL() == url) {
+        nodes.push_back(host_node.get());
+      }
+    }
+    return nodes;
+  }
+
   // Content setting group name for the relevant ContentSettingsType.
   const std::string kNotifications;
   const std::string kCookies;
@@ -900,21 +927,33 @@
     ASSERT_TRUE(site_group.FindStringKey("etldPlus1"));
     ASSERT_EQ("google.com", *site_group.FindStringKey("etldPlus1"));
 
-    EXPECT_EQ(2, site_group.FindKey("numCookies")->GetDouble());
+    EXPECT_EQ(3, site_group.FindKey("numCookies")->GetDouble());
 
     const base::Value* origin_list = site_group.FindListKey("origins");
     ASSERT_TRUE(origin_list && origin_list->is_list());
 
-    EXPECT_EQ(1U, origin_list->GetList().size());
+    EXPECT_EQ(2U, origin_list->GetList().size());
 
-    const base::Value& origin_info = origin_list->GetList()[0];
-    ASSERT_TRUE(origin_info.is_dict());
+    const base::Value& partitioned_origin_info = origin_list->GetList()[0];
+    ASSERT_TRUE(partitioned_origin_info.is_dict());
+
+    EXPECT_EQ("https://www.example.com/",
+              partitioned_origin_info.FindKey("origin")->GetString());
+    EXPECT_EQ(0, partitioned_origin_info.FindKey("engagement")->GetDouble());
+    EXPECT_EQ(0, partitioned_origin_info.FindKey("usage")->GetDouble());
+    EXPECT_EQ(1, partitioned_origin_info.FindKey("numCookies")->GetDouble());
+    EXPECT_TRUE(partitioned_origin_info.FindKey("isPartitioned")->GetBool());
+
+    const base::Value& unpartitioned_origin_info = origin_list->GetList()[1];
+    ASSERT_TRUE(unpartitioned_origin_info.is_dict());
 
     EXPECT_EQ("https://www.google.com/",
-              origin_info.FindKey("origin")->GetString());
-    EXPECT_EQ(0, origin_info.FindKey("engagement")->GetDouble());
-    EXPECT_EQ(50000000000, origin_info.FindKey("usage")->GetDouble());
-    EXPECT_EQ(0, origin_info.FindKey("numCookies")->GetDouble());
+              unpartitioned_origin_info.FindKey("origin")->GetString());
+    EXPECT_EQ(0, unpartitioned_origin_info.FindKey("engagement")->GetDouble());
+    EXPECT_EQ(50000000000,
+              unpartitioned_origin_info.FindKey("usage")->GetDouble());
+    EXPECT_EQ(0, unpartitioned_origin_info.FindKey("numCookies")->GetDouble());
+    EXPECT_FALSE(unpartitioned_origin_info.FindKey("isPartitioned")->GetBool());
   }
 
   {
@@ -924,21 +963,39 @@
     ASSERT_TRUE(site_group.FindStringKey("etldPlus1"));
     ASSERT_EQ("google.com.au", *site_group.FindStringKey("etldPlus1"));
 
-    EXPECT_EQ(1, site_group.FindKey("numCookies")->GetDouble());
+    EXPECT_EQ(3, site_group.FindKey("numCookies")->GetDouble());
 
     const base::Value* origin_list = site_group.FindListKey("origins");
     ASSERT_TRUE(origin_list && origin_list->is_list());
 
-    EXPECT_EQ(1U, origin_list->GetList().size());
+    // The unpartitioned cookie set for google.com.au should be associated with
+    // the eTLD+1, and thus won't have an origin entry as other origin entries
+    // exist for the unpartitioned storage.
+    EXPECT_EQ(2U, origin_list->GetList().size());
 
-    const base::Value& origin_info = origin_list->GetList()[0];
-    ASSERT_TRUE(origin_info.is_dict());
+    const base::Value& partitioned_origin_one_info = origin_list->GetList()[0];
+    ASSERT_TRUE(partitioned_origin_one_info.is_dict());
 
-    EXPECT_EQ("http://google.com.au/",
-              origin_info.FindKey("origin")->GetString());
-    EXPECT_EQ(0, origin_info.FindKey("engagement")->GetDouble());
-    EXPECT_EQ(0, origin_info.FindKey("usage")->GetDouble());
-    EXPECT_EQ(1, origin_info.FindKey("numCookies")->GetDouble());
+    EXPECT_EQ("https://www.another-example.com/",
+              partitioned_origin_one_info.FindKey("origin")->GetString());
+    EXPECT_EQ(0,
+              partitioned_origin_one_info.FindKey("engagement")->GetDouble());
+    EXPECT_EQ(0, partitioned_origin_one_info.FindKey("usage")->GetDouble());
+    EXPECT_EQ(1,
+              partitioned_origin_one_info.FindKey("numCookies")->GetDouble());
+    EXPECT_TRUE(
+        partitioned_origin_one_info.FindKey("isPartitioned")->GetBool());
+
+    const base::Value& partitioned_origin_two_info = origin_list->GetList()[1];
+    EXPECT_EQ("https://www.example.com/",
+              partitioned_origin_two_info.FindKey("origin")->GetString());
+    EXPECT_EQ(0,
+              partitioned_origin_two_info.FindKey("engagement")->GetDouble());
+    EXPECT_EQ(0, partitioned_origin_two_info.FindKey("usage")->GetDouble());
+    EXPECT_EQ(1,
+              partitioned_origin_two_info.FindKey("numCookies")->GetDouble());
+    EXPECT_TRUE(
+        partitioned_origin_two_info.FindKey("isPartitioned")->GetBool());
   }
 }
 
@@ -978,17 +1035,15 @@
 
     ASSERT_TRUE(site_group.FindStringKey("etldPlus1"));
     ASSERT_EQ("google.com", *site_group.FindStringKey("etldPlus1"));
+    EXPECT_FALSE(site_group.FindKey("hasInstalledPWA")->GetBool());
 
     const base::Value* origin_list = site_group.FindListKey("origins");
     ASSERT_TRUE(origin_list);
 
-    const base::Value& origin_info = origin_list->GetList()[0];
-    ASSERT_TRUE(origin_info.is_dict());
-
-    EXPECT_EQ("https://www.google.com/",
-              origin_info.FindKey("origin")->GetString());
-    EXPECT_FALSE(site_group.FindKey("hasInstalledPWA")->GetBool());
-    EXPECT_FALSE(origin_info.FindKey("isInstalled")->GetBool());
+    for (const auto& origin_info : origin_list->GetList()) {
+      ASSERT_TRUE(origin_info.is_dict());
+      EXPECT_FALSE(origin_info.FindKey("isInstalled")->GetBool());
+    }
   }
 }
 
@@ -2389,7 +2444,7 @@
 TEST_F(SiteSettingsHandlerTest, HandleClearEtldPlus1DataAndCookies) {
   SetUpCookiesTreeModel();
 
-  EXPECT_EQ(22, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+  EXPECT_EQ(27, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
 
   auto verify_site_group = [](const base::Value& site_group,
                               std::string expected_etld_plus1) {
@@ -2408,7 +2463,29 @@
   args.Append("example.com");
   handler()->HandleClearEtldPlus1DataAndCookies(
       &base::Value::AsListValue(args));
-  EXPECT_EQ(11, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+
+  // All host nodes for non-secure example.com, and abc.example.com, which do
+  // not have any unpartitioned  storage, should have been removed.
+  ASSERT_EQ(0u, GetHostNodes(GURL("http://example.com")).size());
+  ASSERT_EQ(0u, GetHostNodes(GURL("http://abc.example.com")).size());
+
+  // Confirm that partitioned cookies for www.example.com have not been deleted,
+  auto remaining_host_nodes = GetHostNodes(GURL("https://www.example.com"));
+
+  // example.com storage partitioned on other sites should still remain.
+  ASSERT_EQ(1u, remaining_host_nodes.size());
+  ASSERT_EQ(1u, remaining_host_nodes[0]->children().size());
+  const auto& storage_node = remaining_host_nodes[0]->children()[0];
+  ASSERT_EQ(CookieTreeNode::DetailedInfo::TYPE_COOKIES,
+            storage_node->GetDetailedInfo().node_type);
+  ASSERT_EQ(2u, storage_node->children().size());
+  for (const auto& cookie_node : storage_node->children()) {
+    const auto& cookie = cookie_node->GetDetailedInfo().cookie;
+    EXPECT_EQ("www.example.com", cookie->Domain());
+    EXPECT_TRUE(cookie->IsPartitioned());
+  }
+
+  EXPECT_EQ(18, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
 
   storage_and_cookie_list = GetOnStorageFetchedSentListView();
   EXPECT_EQ(2U, storage_and_cookie_list.size());
@@ -2420,7 +2497,7 @@
   handler()->HandleClearEtldPlus1DataAndCookies(
       &base::Value::AsListValue(args));
 
-  EXPECT_EQ(4, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+  EXPECT_EQ(10, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
 
   storage_and_cookie_list = GetOnStorageFetchedSentListView();
   EXPECT_EQ(1U, storage_and_cookie_list.size());
@@ -2431,19 +2508,60 @@
 
   handler()->HandleClearEtldPlus1DataAndCookies(
       &base::Value::AsListValue(args));
-
-  EXPECT_EQ(1, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+  // No nodes representing storage partitioned on google.com.au should be
+  // present.
+  for (const auto& host_node :
+       handler()->cookies_tree_model_->GetRoot()->children()) {
+    for (const auto& storage_node : host_node->children()) {
+      if (storage_node->GetDetailedInfo().node_type !=
+          CookieTreeNode::DetailedInfo::TYPE_COOKIES) {
+        continue;
+      }
+      for (const auto& cookie_node : storage_node->children()) {
+        const auto& cookie = cookie_node->GetDetailedInfo().cookie;
+        if (cookie->IsPartitioned()) {
+          EXPECT_NE("google.com.au",
+                    cookie->PartitionKey()->site().GetURL().host());
+        }
+      }
+    }
+  }
 
   storage_and_cookie_list = GetOnStorageFetchedSentListView();
   EXPECT_EQ(0U, storage_and_cookie_list.size());
 }
 
-// TODO(crbug.com/1271155, crbug.com/1268626): Add Validation for cookies nodes
-// deleted correctly.
-TEST_F(SiteSettingsHandlerTest, HandleClearUsage) {
+TEST_F(SiteSettingsHandlerTest, HandleClearUnpartitionedUsage) {
   SetUpCookiesTreeModel();
 
-  EXPECT_EQ(22, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+  EXPECT_EQ(27, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+
+  base::Value args(base::Value::Type::LIST);
+  args.Append("https://www.example.com/");
+  handler()->HandleClearUnpartitionedUsage(&base::Value::AsListValue(args));
+
+  // Confirm that only the unpartitioned items for example.com have been
+  // cleared.
+  auto remaining_host_nodes = GetHostNodes(GURL("https://www.example.com"));
+
+  // There should only be partitioned cookie entries remaining for the site.
+  ASSERT_EQ(1u, remaining_host_nodes.size());
+  ASSERT_EQ(1u, remaining_host_nodes[0]->children().size());
+  const auto& storage_node = remaining_host_nodes[0]->children()[0];
+  ASSERT_EQ(CookieTreeNode::DetailedInfo::TYPE_COOKIES,
+            storage_node->GetDetailedInfo().node_type);
+  ASSERT_EQ(2u, storage_node->children().size());
+  for (const auto& cookie_node : storage_node->children()) {
+    const auto& cookie = cookie_node->GetDetailedInfo().cookie;
+    EXPECT_EQ("www.example.com", cookie->Domain());
+    EXPECT_TRUE(cookie->IsPartitioned());
+  }
+}
+
+TEST_F(SiteSettingsHandlerTest, ClearUnpartitionedClearsHints) {
+  // Confirm that when the user clears unpartitioned storage, client hints
+  // are also cleared.
+  SetUpCookiesTreeModel();
 
   GURL hosts[] = {GURL("https://example.com/"), GURL("https://google.com/")};
 
@@ -2470,10 +2588,10 @@
         base::Value::ToUniquePtrValue(client_hints_dictionary.Clone()));
   }
 
-  // Clear usage data.
+  // Clear unpartitioned usage data.
   base::Value args(base::Value::Type::LIST);
   args.Append("https://example.com/");
-  handler()->HandleClearUsage(&base::Value::AsListValue(args));
+  handler()->HandleClearUnpartitionedUsage(&base::Value::AsListValue(args));
 
   // Validate the client hint has been cleared.
   host_content_settings_map->GetSettingsForOneType(
@@ -2486,6 +2604,44 @@
   EXPECT_EQ(client_hints_dictionary, client_hints_settings.at(0).setting_value);
 }
 
+TEST_F(SiteSettingsHandlerTest, HandleClearPartitionedUsage) {
+  // Confirm that removing unpartitioned storage correctly removes the
+  // appropriate nodes.
+  SetUpCookiesTreeModel();
+  EXPECT_EQ(27, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+
+  base::Value args(base::Value::Type::LIST);
+  args.Append("https://www.example.com/");
+  args.Append("google.com");
+  handler()->HandleClearPartitionedUsage(&base::Value::AsListValue(args));
+
+  // This should have only removed cookies for embedded.com partitioned on
+  // google.com, leaving other cookies and storage untouched.
+  auto remaining_host_nodes = GetHostNodes(GURL("https://www.example.com"));
+  ASSERT_EQ(1u, remaining_host_nodes.size());
+
+  // Both cookies and local storage type nodes should remain.
+  ASSERT_EQ(2u, remaining_host_nodes[0]->children().size());
+
+  for (const auto& storage_node : remaining_host_nodes[0]->children()) {
+    if (storage_node->GetDetailedInfo().node_type ==
+        CookieTreeNode::DetailedInfo::TYPE_COOKIES) {
+      // Two cookies should remain, one unpartitioned and one partitioned on
+      // a different site.
+      ASSERT_EQ(2u, storage_node->children().size());
+      for (const auto& cookie_node : storage_node->children()) {
+        const auto& cookie = cookie_node->GetDetailedInfo().cookie;
+        if (cookie->IsPartitioned())
+          ASSERT_EQ("google.com.au",
+                    cookie->PartitionKey()->site().GetURL().host());
+      }
+    } else {
+      ASSERT_EQ(storage_node->GetDetailedInfo().node_type,
+                CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGES);
+    }
+  }
+}
+
 TEST_F(SiteSettingsHandlerTest, CookieSettingDescription) {
   const auto kBlocked = [](int num) {
     return l10n_util::GetPluralStringFUTF8(
diff --git a/chrome/browser/web_applications/isolated_app_browsertest.cc b/chrome/browser/web_applications/isolated_app_browsertest.cc
index c489c8c..ea279be 100644
--- a/chrome/browser/web_applications/isolated_app_browsertest.cc
+++ b/chrome/browser/web_applications/isolated_app_browsertest.cc
@@ -2,20 +2,40 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/barrier_closure.h"
+#include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
+#include "chrome/browser/gcm/gcm_profile_service_factory.h"
+#include "chrome/browser/notifications/notification_display_service_tester.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/push_messaging/push_messaging_app_identifier.h"
+#include "chrome/browser/push_messaging/push_messaging_constants.h"
+#include "chrome/browser/push_messaging/push_messaging_features.h"
+#include "chrome/browser/push_messaging/push_messaging_service_factory.h"
+#include "chrome/browser/push_messaging/push_messaging_service_impl.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/web_applications/app_browser_controller.h"
+#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
 #include "chrome/browser/web_applications/test/service_worker_registration_waiter.h"
 #include "chrome/browser/web_applications/test/web_app_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/gcm_driver/common/gcm_message.h"
+#include "components/gcm_driver/fake_gcm_profile_service.h"
+#include "components/permissions/permission_request_manager.h"
+#include "content/public/browser/push_messaging_service.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/service_worker_context.h"
+#include "content/public/browser/service_worker_running_info.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-forward.h"
 
 namespace web_app {
 
@@ -25,6 +45,125 @@
 const char kApp2Host[] = "app2.com";
 const char kNonAppHost[] = "nonapp.com";
 
+const int32_t kApplicationServerKeyLength = 65;
+// NIST P-256 public key made available to tests. Must be an uncompressed
+// point in accordance with SEC1 2.3.3.
+const uint8_t kApplicationServerKey[kApplicationServerKeyLength] = {
+    0x04, 0x55, 0x52, 0x6A, 0xA5, 0x6E, 0x8E, 0xAA, 0x47, 0x97, 0x36,
+    0x10, 0xC1, 0x66, 0x3C, 0x1E, 0x65, 0xBF, 0xA1, 0x7B, 0xEE, 0x48,
+    0xC9, 0xC6, 0xBB, 0xBF, 0x02, 0x18, 0x53, 0x72, 0x1D, 0x0C, 0x7B,
+    0xA9, 0xE3, 0x11, 0xB7, 0x03, 0x52, 0x21, 0xD3, 0x71, 0x90, 0x13,
+    0xA8, 0xC1, 0xCF, 0xED, 0x20, 0xF7, 0x1F, 0xD1, 0x7F, 0xF2, 0x76,
+    0xB6, 0x01, 0x20, 0xD8, 0x35, 0xA5, 0xD9, 0x3C, 0x43, 0xFD};
+
+std::string GetTestApplicationServerKey() {
+  std::string application_server_key(
+      kApplicationServerKey,
+      kApplicationServerKey + base::size(kApplicationServerKey));
+
+  return application_server_key;
+}
+
+class BaseServiceWorkerVersionWaiter
+    : public content::ServiceWorkerContextObserver {
+ public:
+  explicit BaseServiceWorkerVersionWaiter(
+      content::StoragePartition* storage_partition) {
+    DCHECK(storage_partition);
+
+    service_worker_context_ = storage_partition->GetServiceWorkerContext();
+    service_worker_context_->AddObserver(this);
+  }
+
+  BaseServiceWorkerVersionWaiter(const BaseServiceWorkerVersionWaiter&) =
+      delete;
+  BaseServiceWorkerVersionWaiter& operator=(
+      const BaseServiceWorkerVersionWaiter&) = delete;
+
+  ~BaseServiceWorkerVersionWaiter() override {
+    if (service_worker_context_)
+      service_worker_context_->RemoveObserver(this);
+  }
+
+ protected:
+  raw_ptr<content::ServiceWorkerContext> service_worker_context_;
+
+ private:
+  void OnDestruct(content::ServiceWorkerContext* context) override {
+    service_worker_context_->RemoveObserver(this);
+    service_worker_context_ = nullptr;
+  }
+};
+
+class ServiceWorkerVersionActivatedWaiter
+    : public BaseServiceWorkerVersionWaiter {
+ public:
+  ServiceWorkerVersionActivatedWaiter(
+      content::StoragePartition* storage_partition,
+      GURL& url)
+      : BaseServiceWorkerVersionWaiter(storage_partition), url_(url) {}
+
+  int64_t AwaitVersionActivated() { return future.Get(); }
+
+ private:
+  // content::ServiceWorkerContextObserver:
+  void OnVersionActivated(int64_t version_id, const GURL& scope) override {
+    if (content::ServiceWorkerContext::ScopeMatches(scope, url_)) {
+      future.SetValue(version_id);
+    }
+  }
+
+  const GURL url_;
+  base::test::TestFuture<int64_t> future;
+};
+
+class ServiceWorkerVersionStartedRunningWaiter
+    : public BaseServiceWorkerVersionWaiter {
+ public:
+  ServiceWorkerVersionStartedRunningWaiter(
+      content::StoragePartition* storage_partition,
+      int64_t version_id)
+      : BaseServiceWorkerVersionWaiter(storage_partition),
+        version_id_(version_id) {}
+
+  void AwaitVersionStartedRunning() { run_loop_.Run(); }
+
+ private:
+  // content::ServiceWorkerContextObserver:
+  void OnVersionStartedRunning(
+      int64_t version_id,
+      const content::ServiceWorkerRunningInfo& running_info) override {
+    if (version_id == version_id_) {
+      run_loop_.Quit();
+    }
+  }
+
+  const int64_t version_id_ = blink::mojom::kInvalidServiceWorkerVersionId;
+  base::RunLoop run_loop_;
+};
+
+class ServiceWorkerVersionStoppedRunningWaiter
+    : public BaseServiceWorkerVersionWaiter {
+ public:
+  ServiceWorkerVersionStoppedRunningWaiter(
+      content::StoragePartition* storage_partition,
+      int64_t version_id)
+      : BaseServiceWorkerVersionWaiter(storage_partition),
+        version_id_(version_id) {}
+
+  void AwaitVersionStoppedRunning() { run_loop_.Run(); }
+
+ private:
+  // content::ServiceWorkerContextObserver:
+  void OnVersionStoppedRunning(int64_t version_id) override {
+    if (version_id == version_id_) {
+      run_loop_.Quit();
+    }
+  }
+
+  const int64_t version_id_ = blink::mojom::kInvalidServiceWorkerVersionId;
+  base::RunLoop run_loop_;
+};
 }  // namespace
 
 class IsolatedAppBrowserTest : public WebAppControllerBrowserTest {
@@ -187,31 +326,167 @@
 
 class IsolatedAppBrowserServiceWorkerTest : public IsolatedAppBrowserTest {
  protected:
-  content::RenderFrameHost* InstallIsolatedAppAndWaitForServiceWorker(
-      const GURL& app_url) {
+  void SetUpOnMainThread() override {
+    IsolatedAppBrowserTest::SetUpOnMainThread();
+
+    app_url_ = https_server()->GetURL(kAppHost,
+                                      "/banners/isolated/service_worker.html");
+  }
+
+  int64_t InstallIsolatedAppAndWaitForServiceWorker() {
     InstallIsolatedApp(kAppHost);
 
-    auto* app_window = NavigateInNewWindowAndAwaitInstallabilityCheck(app_url);
-    auto* web_contents = app_window->tab_strip_model()->GetActiveWebContents();
-    auto* app_frame = web_contents->GetMainFrame();
-    EXPECT_NE(default_storage_partition(), app_frame->GetStoragePartition());
+    app_window_ = NavigateInNewWindowAndAwaitInstallabilityCheck(app_url_);
+    app_web_contents_ = app_window_->tab_strip_model()->GetActiveWebContents();
+    app_frame_ = app_web_contents_->GetMainFrame();
+    storage_partition_ = app_frame_->GetStoragePartition();
+    EXPECT_NE(default_storage_partition(), storage_partition_);
 
-    ServiceWorkerRegistrationWaiter waiter(app_frame->GetStoragePartition(),
-                                           app_url);
-    waiter.AwaitRegistration();
+    ServiceWorkerVersionActivatedWaiter version_activated_waiter(
+        storage_partition_, app_url_);
 
-    return app_frame;
+    return version_activated_waiter.AwaitVersionActivated();
   }
+
+  Browser* app_window_;
+  content::WebContents* app_web_contents_;
+  content::RenderFrameHost* app_frame_;
+  content::StoragePartition* storage_partition_;
+
+  GURL app_url_;
 };
 
 IN_PROC_BROWSER_TEST_F(IsolatedAppBrowserServiceWorkerTest,
                        ServiceWorkerPartitioned) {
-  const GURL app_url =
-      https_server()->GetURL(kAppHost, "/banners/isolated/service_worker.html");
-
-  auto* app_frame = InstallIsolatedAppAndWaitForServiceWorker(app_url);
+  InstallIsolatedAppAndWaitForServiceWorker();
   test::CheckServiceWorkerStatus(
-      app_url, app_frame->GetStoragePartition(),
+      app_url_, storage_partition_,
       content::ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
 }
+
+class IsolatedAppBrowserServiceWorkerPushTest
+    : public IsolatedAppBrowserServiceWorkerTest {
+ public:
+  IsolatedAppBrowserServiceWorkerPushTest()
+      : scoped_testing_factory_installer_(
+            base::BindRepeating(&gcm::FakeGCMProfileService::Build)) {}
+
+ protected:
+  void SetUpOnMainThread() override {
+    IsolatedAppBrowserServiceWorkerTest::SetUpOnMainThread();
+
+    notification_tester_ = std::make_unique<NotificationDisplayServiceTester>(
+        browser()->profile());
+  }
+
+  void SendMessageAndWaitUntilHandled(
+      content::BrowserContext* context,
+      const PushMessagingAppIdentifier& app_identifier,
+      const gcm::IncomingMessage& message) {
+    auto* push_service = PushMessagingServiceFactory::GetForProfile(context);
+    base::RunLoop run_loop;
+    base::RepeatingClosure quit_barrier =
+        base::BarrierClosure(2 /* num_closures */, run_loop.QuitClosure());
+    push_service->SetMessageCallbackForTesting(quit_barrier);
+    notification_tester_->SetNotificationAddedClosure(quit_barrier);
+    push_service->OnMessage(app_identifier.app_id(), message);
+    run_loop.Run();
+  }
+
+  PushMessagingAppIdentifier GetAppIdentifierForServiceWorkerRegistration(
+      int64_t service_worker_registration_id) {
+    GURL origin = url::Origin::Create(app_url_).GetURL();
+
+    PushMessagingAppIdentifier app_identifier =
+        PushMessagingAppIdentifier::FindByServiceWorker(
+            browser()->profile(), origin, service_worker_registration_id);
+    return app_identifier;
+  }
+
+  std::string RunScript(content::RenderFrameHost* app_frame,
+                        const std::string& script) {
+    std::string script_result;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(app_frame, script,
+                                                       &script_result));
+    return script_result;
+  }
+
+  std::unique_ptr<NotificationDisplayServiceTester> notification_tester_;
+
+ private:
+  gcm::GCMProfileServiceFactory::ScopedTestingFactoryInstaller
+      scoped_testing_factory_installer_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    IsolatedAppBrowserServiceWorkerPushTest,
+    ServiceWorkerPartitionedWhenWakingUpDuetoPushNotification) {
+  int64_t service_worker_version_id =
+      InstallIsolatedAppAndWaitForServiceWorker();
+
+  // Request and confirm permission to show notifications.
+  auto* permission_request_manager =
+      permissions::PermissionRequestManager::FromWebContents(app_web_contents_);
+  permission_request_manager->set_auto_response_for_test(
+      permissions::PermissionRequestManager::ACCEPT_ALL);
+  ASSERT_EQ("permission status - granted",
+            RunScript(app_frame_, "requestNotificationPermission()"));
+
+  // Subscribe to push notifications and retrieve the app identifier.
+  std::string push_messaging_endpoint =
+      RunScript(app_frame_, "documentSubscribePush()");
+  size_t last_slash = push_messaging_endpoint.rfind('/');
+  ASSERT_EQ(kPushMessagingGcmEndpoint,
+            push_messaging_endpoint.substr(0, last_slash + 1));
+  PushMessagingAppIdentifier app_identifier =
+      GetAppIdentifierForServiceWorkerRegistration(0LL);
+  EXPECT_FALSE(app_identifier.is_null());
+
+  // Close the browser and stop the ServiceWorker
+  ServiceWorkerVersionStoppedRunningWaiter version_stopped_waiter(
+      storage_partition_, service_worker_version_id);
+  CloseBrowserSynchronously(app_window_);
+  base::RunLoop run_loop;
+  storage_partition_->GetServiceWorkerContext()->StopAllServiceWorkers(
+      run_loop.QuitClosure());
+  run_loop.Run();
+  version_stopped_waiter.AwaitVersionStoppedRunning();
+
+  // Push a message to the ServiceWorker and make sure the service worker is
+  // started again.
+  ServiceWorkerVersionStartedRunningWaiter version_started_waiter(
+      storage_partition_, service_worker_version_id);
+
+  gcm::IncomingMessage message;
+  message.sender_id = GetTestApplicationServerKey();
+  message.raw_data = "test";
+  message.decrypted = true;
+  SendMessageAndWaitUntilHandled(browser()->profile(), app_identifier, message);
+
+  version_started_waiter.AwaitVersionStartedRunning();
+
+  // Verify that the ServiceWorker has received the push message and created
+  // a push notification, then click on it.
+  auto notifications = notification_tester_->GetDisplayedNotificationsForType(
+      NotificationHandler::Type::WEB_PERSISTENT);
+  EXPECT_EQ(notifications.size(), 1UL);
+
+  web_app::BrowserWaiter browser_waiter(nullptr);
+  notification_tester_->SimulateClick(NotificationHandler::Type::WEB_PERSISTENT,
+                                      notifications[0].id(), absl::nullopt,
+                                      absl::nullopt);
+
+  // Check that the click resulted in a new isolated web app window that runs in
+  // the same isolated non-default storage partition.
+  auto* new_app_window = browser_waiter.AwaitAdded();
+  auto* new_app_frame =
+      new_app_window->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
+  auto* new_storage_partition = new_app_frame->GetStoragePartition();
+  EXPECT_EQ(new_storage_partition, storage_partition_);
+  EXPECT_EQ(new_app_frame->GetWebExposedIsolationLevel(),
+            content::RenderFrameHost::WebExposedIsolationLevel::
+                kMaybeIsolatedApplication);
+  EXPECT_TRUE(AppBrowserController::IsWebApp(new_app_window));
+}
+
 }  // namespace web_app
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 429b372..e072d18 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1640109555-7330b9704534b468b8f03f344850f8ac3a456ec1.profdata
+chrome-linux-main-1640152356-7110f7b9e061caa182e80e91922d963a27259c43.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 3c10d9b..d1cae67 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1640109555-4f788d66531f70fd161dada09e32b5851b4fbaae.profdata
+chrome-mac-main-1640152356-8f297e453cce0188cadd2b4256a99a8b72186a5a.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 1a01874..4e81953 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1640120104-62f9b79b00a887290a876ff7e43bd61c1c1ee4e5.profdata
+chrome-win32-main-1640162853-e127f09e7d28173b7f76e48ecb02c54042744ba6.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index d6bfcc6..72066b2 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1640120104-5f892614cbc8389f8f2aaaa4f4b032ceaee1d505.profdata
+chrome-win64-main-1640162853-3c4bf97ec3b654f6ce6b7df852105d11837a41cc.profdata
diff --git a/chrome/common/extensions/api/accessibility_private.json b/chrome/common/extensions/api/accessibility_private.json
index 912f11a..e407365 100644
--- a/chrome/common/extensions/api/accessibility_private.json
+++ b/chrome/common/extensions/api/accessibility_private.json
@@ -242,6 +242,12 @@
         "type": "string",
         "enum": [ "previousParagraph", "previousSentence", "pause", "resume", "nextSentence", "nextParagraph", "exit", "changeSpeed" ],
         "description": "Actions that can be performed in the Select-to-speak panel."
+      },
+      {
+        "id": "SetNativeChromeVoxResponse",
+        "type": "string",
+        "enum": [ "success", "talkbackNotInstalled", "windowNotFound", "failure" ],
+        "description": "Response code for onNativeChromeVoxArcSupportResult"
       }
     ],
     "properties": {
@@ -423,7 +429,18 @@
             "type": "boolean",
             "description": "True for ChromeVox (native), false for TalkBack."
           }
-        ]
+        ],
+	"returns_async": {
+	  "name": "callback",
+	  "desctiprion": "Callback function.",
+	  "parameters": [
+            {
+	      "name": "response",
+	      "$ref": "SetNativeChromeVoxResponse",
+	      "description": "Return Success if successfully toggled. Return error description otherwise."
+	    }
+	  ]
+	}
       },
       {
         "name": "sendSyntheticKeyEvent",
diff --git a/chrome/renderer/cart/commerce_hint_agent.cc b/chrome/renderer/cart/commerce_hint_agent.cc
index 730538a..2535b07 100644
--- a/chrome/renderer/cart/commerce_hint_agent.cc
+++ b/chrome/renderer/cart/commerce_hint_agent.cc
@@ -233,6 +233,10 @@
     content::RenderFrame* render_frame) {
   // Connect to Mojo service on browser to notify commerce signals.
   mojo::Remote<mojom::CommerceHintObserver> observer;
+
+  // Subframes including fenced frames shouldn't be reached here.
+  DCHECK(render_frame->IsMainFrame() && !render_frame->IsInFencedFrameTree());
+
   render_frame->GetBrowserInterfaceBroker()->GetInterface(
       observer.BindNewPipeAndPassReceiver());
   return observer;
@@ -630,6 +634,9 @@
     : content::RenderFrameObserver(render_frame),
       content::RenderFrameObserverTracker<CommerceHintAgent>(render_frame) {
   DCHECK(render_frame);
+
+  // Subframes including fenced frames shouldn't be reached here.
+  DCHECK(render_frame->IsMainFrame() && !render_frame->IsInFencedFrameTree());
 }
 
 CommerceHintAgent::~CommerceHintAgent() = default;
diff --git a/chrome/renderer/cart/commerce_hint_agent_browsertest.cc b/chrome/renderer/cart/commerce_hint_agent_browsertest.cc
index 9d8ce3d..f86a0087 100644
--- a/chrome/renderer/cart/commerce_hint_agent_browsertest.cc
+++ b/chrome/renderer/cart/commerce_hint_agent_browsertest.cc
@@ -27,6 +27,7 @@
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/fenced_frame_test_util.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -166,7 +167,7 @@
   using FormSubmittedEntry = ukm::builders::Shopping_FormSubmitted;
   using XHREntry = ukm::builders::Shopping_WillSendRequest;
 
-  void SetUpInProcessBrowserTestFixture() override {
+  CommerceHintAgentTest() {
     scoped_feature_list_.InitWithFeaturesAndParameters(
         {{ntp_features::kNtpChromeCartModule,
           {{"product-skip-pattern", "(^|\\W)(?i)(skipped)(\\W|$)"},
@@ -1180,4 +1181,42 @@
 
   WaitForUmaBucketCount("Commerce.Carts.ExtractionTimedOut", 0, 2);
 }
+
+class CommerceHintAgentFencedFrameTest : public CommerceHintAgentTest {
+ public:
+  CommerceHintAgentFencedFrameTest() = default;
+  ~CommerceHintAgentFencedFrameTest() override = default;
+  CommerceHintAgentFencedFrameTest(const CommerceHintAgentFencedFrameTest&) =
+      delete;
+
+  CommerceHintAgentFencedFrameTest& operator=(
+      const CommerceHintAgentFencedFrameTest&) = delete;
+
+  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
+    return fenced_frame_helper_;
+  }
+
+ private:
+  content::test::FencedFrameTestHelper fenced_frame_helper_;
+};
+
+IN_PROC_BROWSER_TEST_F(CommerceHintAgentFencedFrameTest,
+                       VisitCartInFencedFrame) {
+  // For add-to-cart by URL, normally a URL in that domain has already been
+  // committed.
+  NavigateToURL("https://www.guitarcenter.com/cart.html");
+  WaitForUmaCount("Commerce.Carts.VisitCart", 1);
+
+  // Create a fenced frame.
+  GURL fenced_frame_url =
+      https_server_.GetURL("www.guitarcenter.com", "/cart.html");
+  content::RenderFrameHost* fenced_frame_host =
+      fenced_frame_test_helper().CreateFencedFrame(
+          web_contents()->GetMainFrame(), fenced_frame_url);
+  EXPECT_NE(nullptr, fenced_frame_host);
+
+  // Do not affect counts.
+  WaitForUmaCount("Commerce.Carts.VisitCart", 1);
+}
+
 }  // namespace
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 5f853f9..4c75b556 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -700,8 +700,11 @@
     new SearchBox(render_frame);
   }
 
+  // We should create CommerceHintAgent only for a primary main frame. A fenced
+  // frame is the main frame as well, so we should check if |render_frame|
+  // is the primary main frame.
   if (base::FeatureList::IsEnabled(ntp_features::kNtpChromeCartModule) &&
-      render_frame->IsMainFrame()) {
+      render_frame->IsMainFrame() && !render_frame->IsInFencedFrameTree()) {
     new cart::CommerceHintAgent(render_frame);
   }
 #endif  // !defined(OS_ANDROID)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index ef5489c..bd62f0b 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2574,6 +2574,7 @@
         "../browser/extensions/api/i18n/i18n_apitest.cc",
         "../browser/extensions/api/identity/identity_apitest.cc",
         "../browser/extensions/api/identity/identity_private_apitest.cc",
+        "../browser/extensions/api/identity/web_auth_flow_browsertest.cc",
         "../browser/extensions/api/idle/idle_apitest.cc",
         "../browser/extensions/api/idltest/idltest_apitest.cc",
         "../browser/extensions/api/image_writer_private/extractor_browsertest.cc",
@@ -8516,6 +8517,12 @@
       "//third_party/mesa_headers",
       "//ui/resources:ui_test_pak_data",
     ]
+    if (is_chromeos_ash && enable_extensions) {
+      # For ChromeVox tests.
+      data_deps += [ "//chrome/browser/resources/chromeos/accessibility:build" ]
+      data += [ "$root_out_dir/resources/chromeos/" ]
+    }
+
     if (is_linux || is_chromeos || is_win) {
       data_deps += [ "//chrome:packed_resources" ]
     }
diff --git a/chrome/test/data/banners/isolated/service_worker.html b/chrome/test/data/banners/isolated/service_worker.html
index 173fbab..4a6c0a8f 100644
--- a/chrome/test/data/banners/isolated/service_worker.html
+++ b/chrome/test/data/banners/isolated/service_worker.html
@@ -1,4 +1,16 @@
 <head>
   <title>Web app banner isolated service worker test page</title>
-  <script>navigator.serviceWorker.register('/banners/service_worker.js');</script>
+  <!--
+    TODO: The test hangs if we add this line.
+    <link rel="manifest" href="/banners/manifest_isolated.json">
+  -->
+  <script src="../../result_queue.js"></script>
+  <script src="../../push_messaging/push_constants.js"></script>
+  <script src="../../push_messaging/push_test.js"></script>
+  <script>
+    navigator.serviceWorker.register('/banners/isolated/service_worker.js');
+  </script>
 </head>
+<body>
+  Isolated Service Worker Test Page.
+</body>
diff --git a/chrome/test/data/banners/isolated/service_worker.js b/chrome/test/data/banners/isolated/service_worker.js
new file mode 100644
index 0000000..ee83f00
--- /dev/null
+++ b/chrome/test/data/banners/isolated/service_worker.js
@@ -0,0 +1,26 @@
+// 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.
+
+self.addEventListener('fetch', e => {
+  e.respondWith((async () => {
+    try {
+      return await fetch(e.request);
+    } catch (error) {
+      return new Response('Hello offline page');
+    }
+  })());
+});
+
+self.addEventListener('push', event => {
+  self.registration.showNotification('Hello world!', {
+    body: 'Test Notification',
+  });
+});
+
+self.addEventListener('notificationclick', event => {
+  event.notification.close();
+  event.waitUntil(self.clients.openWindow(
+    self.location.origin + '/banners/isolated/service_worker.html'
+  ));
+});
diff --git a/chrome/test/data/banners/isolated/service_worker.js.mock-http-headers b/chrome/test/data/banners/isolated/service_worker.js.mock-http-headers
new file mode 100644
index 0000000..5e92f2ea
--- /dev/null
+++ b/chrome/test/data/banners/isolated/service_worker.js.mock-http-headers
@@ -0,0 +1,4 @@
+HTTP/1.1 200 OK
+Content-Type: text/javascript
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/chrome/test/data/cart/cart.html.mock-http-headers b/chrome/test/data/cart/cart.html.mock-http-headers
new file mode 100644
index 0000000..263e89c4
--- /dev/null
+++ b/chrome/test/data/cart/cart.html.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 200 OK
+Supports-Loading-Mode: fenced-frame
\ No newline at end of file
diff --git a/chrome/test/data/extensions/desktop_capture/content.js b/chrome/test/data/extensions/desktop_capture/content.js
index ce66aea..3b3ffaf 100644
--- a/chrome/test/data/extensions/desktop_capture/content.js
+++ b/chrome/test/data/extensions/desktop_capture/content.js
@@ -10,7 +10,7 @@
     window.postMessage(request, "*");
 });
 
-window.addEventListener('message', function(message) {
+window.addEventListener('message', function(event) {
   if (event.source != window || !event.data) {
     return;
   }
diff --git a/chrome/test/data/webrtc/getusermedia.js b/chrome/test/data/webrtc/getusermedia.js
index dfcdad7f..d1860327 100644
--- a/chrome/test/data/webrtc/getusermedia.js
+++ b/chrome/test/data/webrtc/getusermedia.js
@@ -196,5 +196,5 @@
     }
   });
 
-  window.postMessage({desktopSourceTypes: ['window', 'screen']}, '*');
+  window.postMessage({desktopSourceTypes: ['window', 'screen', 'tab']}, '*');
 }
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/wrapup_repair_complete_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/wrapup_repair_complete_page_test.js
index 19934443..970425e 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/wrapup_repair_complete_page_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/wrapup_repair_complete_page_test.js
@@ -80,11 +80,6 @@
     const logsDialog = component.shadowRoot.querySelector('#logsDialog');
     assertTrue(!!logsDialog);
     assertFalse(logsDialog.open);
-
-    const batteryDialog =
-        component.shadowRoot.querySelector('#batteryCutDialog');
-    assertTrue(!!batteryDialog);
-    assertFalse(batteryDialog.open);
   });
 
   test('CanShutDown', async () => {
@@ -128,7 +123,7 @@
     assertTrue(logsDialog.open);
   });
 
-  test('BatteryCutDialogDisabledByDefault', async () => {
+  test('BatteryCutButtonDisabledByDefault', async () => {
     await initializeRepairCompletePage();
     const button = component.shadowRoot.querySelector('#batteryCutButton');
 
@@ -136,7 +131,7 @@
     assertTrue(button.disabled);
   });
 
-  test('PowerCableStateTrueDisablesBatteryCutDialog', async () => {
+  test('PowerCableStateTrueDisablesBatteryCutButton', async () => {
     await initializeRepairCompletePage();
     service.triggerPowerCableObserver(true, 0);
     await flushTasks();
@@ -146,7 +141,7 @@
     assertTrue(button.disabled);
   });
 
-  test('PowerCableStateFalseEnablesBatteryCutDialog', async () => {
+  test('PowerCableStateFalseEnablesBatteryCutButton', async () => {
     await initializeRepairCompletePage();
     service.triggerPowerCableObserver(false, 0);
     await flushTasks();
@@ -156,19 +151,6 @@
     assertFalse(button.disabled);
   });
 
-  test('OpensBatteryCutDialog', async () => {
-    await initializeRepairCompletePage();
-    // Trigger observation to enable button.
-    service.triggerPowerCableObserver(false, 0);
-    await flushTasks();
-    await clickButton('#batteryCutButton');
-
-    const batteryDialog =
-        component.shadowRoot.querySelector('#batteryCutDialog');
-    assertTrue(!!batteryDialog);
-    assertTrue(batteryDialog.open);
-  });
-
   test('DialogCloses', async () => {
     await initializeRepairCompletePage();
     await clickButton('#rmaLogButton');
@@ -179,11 +161,5 @@
     assertFalse(logsDialog.open);
 
     await clickButton('#batteryCutButton');
-    await clickButton('#closeBatteryDialogButton');
-
-    const batteryDialog =
-        component.shadowRoot.querySelector('#batteryCutDialog');
-    assertTrue(!!batteryDialog);
-    assertFalse(batteryDialog.open);
   });
 }
diff --git a/chrome/test/data/webui/cr_components/BUILD.gn b/chrome/test/data/webui/cr_components/BUILD.gn
index 26da3a6..91b65b3 100644
--- a/chrome/test/data/webui/cr_components/BUILD.gn
+++ b/chrome/test/data/webui/cr_components/BUILD.gn
@@ -46,9 +46,9 @@
                     rebase_path("$root_gen_dir/chrome/test/data/webui/tsc/*",
                                 target_gen_dir) ]
   in_files = [
-    "most_visited_focus_test.js",
-    "most_visited_test.js",
-    "most_visited_test_support.js",
+    "most_visited_focus_test.ts",
+    "most_visited_test.ts",
+    "most_visited_test_support.ts",
   ]
   deps = [ "//ui/webui/resources/cr_components/most_visited:build_ts" ]
   extra_deps = [ "..:generate_definitions" ]
diff --git a/chrome/test/data/webui/cr_components/most_visited_focus_test.js b/chrome/test/data/webui/cr_components/most_visited_focus_test.ts
similarity index 63%
rename from chrome/test/data/webui/cr_components/most_visited_focus_test.js
rename to chrome/test/data/webui/cr_components/most_visited_focus_test.ts
index bdb6f86c..bb3d1a8 100644
--- a/chrome/test/data/webui/cr_components/most_visited_focus_test.js
+++ b/chrome/test/data/webui/cr_components/most_visited_focus_test.ts
@@ -7,32 +7,24 @@
 import {MostVisitedBrowserProxy} from 'chrome://resources/cr_components/most_visited/browser_proxy.js';
 import {MostVisitedElement} from 'chrome://resources/cr_components/most_visited/most_visited.js';
 import {MostVisitedPageCallbackRouter, MostVisitedPageHandlerRemote} from 'chrome://resources/cr_components/most_visited/most_visited.mojom-webui.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {TextDirection} from 'chrome://resources/mojo/mojo/public/mojom/base/text_direction.mojom-webui.js';
 
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 import {eventToPromise} from 'chrome://webui-test/test_util.js';
 
-import {$$, assertFocus, keydown} from './most_visited_test_support.js';
+import {assertFocus, keydown} from './most_visited_test_support.js';
 
 suite('CrComponentsMostVisitedFocusTest', () => {
-  /** @type {!MostVisitedElement} */
-  let mostVisited;
+  let mostVisited: MostVisitedElement;
+  let callbackRouterRemote: MostVisitedPageCallbackRouter;
 
-  /** @extends {TestBrowserProxy} */
-  let callbackRouterRemote;
-
-  /** @return {!Array<!Element>} */
   function queryTiles() {
-    return Array.from(mostVisited.shadowRoot.querySelectorAll('.tile'));
+    return Array.from(
+        mostVisited.shadowRoot!.querySelectorAll<HTMLElement>('.tile'));
   }
 
-  /**
-   * @param {number} n
-   * @return {!Promise<void>}
-   */
-  async function addTiles(n) {
-    const tiles = Array(n).fill(0).map((x, i) => {
+  async function addTiles(n: number): Promise<void> {
+    const tiles = Array(n).fill(0).map((_x, i) => {
       const char = String.fromCharCode(i + /* 'a' */ 97);
       return {
         title: char,
@@ -52,10 +44,11 @@
     await tilesRendered;
   }
 
-  setup(/** @suppress {checkTypes} */ () => {
-    document.innerHTML = '';
+  setup(() => {
+    document.body.innerHTML = '';
 
-    const handler = TestBrowserProxy.fromClass(MostVisitedPageHandlerRemote);
+    const handler = TestBrowserProxy.fromClass(MostVisitedPageHandlerRemote) as
+        unknown as MostVisitedPageHandlerRemote;
     const callbackRouter = new MostVisitedPageCallbackRouter();
     MostVisitedBrowserProxy.setInstance(
         new MostVisitedBrowserProxy(handler, callbackRouter));
@@ -67,70 +60,70 @@
 
   test('right focuses on addShortcut', async () => {
     await addTiles(1);
-    const [tile] = queryTiles();
+    const tile = queryTiles()[0]!;
     tile.focus();
     keydown(tile, 'ArrowRight');
-    assertFocus($$(mostVisited, '#addShortcut'));
+    assertFocus(mostVisited.$.addShortcut);
   });
 
   test('right focuses on addShortcut when menu button focused', async () => {
     await addTiles(1);
-    const [tile] = queryTiles();
-    tile.querySelector('cr-icon-button').focus();
+    const tile = queryTiles()[0]!;
+    tile.querySelector('cr-icon-button')!.focus();
     keydown(tile, 'ArrowRight');
-    assertFocus($$(mostVisited, '#addShortcut'));
+    assertFocus(mostVisited.$.addShortcut);
   });
 
   test('right focuses next tile', async () => {
     await addTiles(2);
-    const [first, second] = queryTiles();
-    first.focus();
-    keydown(first, 'ArrowRight');
-    assertFocus(second);
+    const tiles = queryTiles();
+    tiles[0]!.focus();
+    keydown(tiles[0]!, 'ArrowRight');
+    assertFocus(tiles[1]!);
   });
 
   test('right focuses on next tile when menu button focused', async () => {
     await addTiles(2);
-    const [first, second] = queryTiles();
-    first.querySelector('cr-icon-button').focus();
-    keydown(first, 'ArrowRight');
-    assertFocus(second);
+    const tiles = queryTiles();
+    tiles[0]!.querySelector('cr-icon-button')!.focus();
+    keydown(tiles[0]!, 'ArrowRight');
+    assertFocus(tiles[1]!);
   });
 
   test('down focuses on addShortcut', async () => {
     await addTiles(1);
-    const [tile] = queryTiles();
+    const tile = queryTiles()[0]!;
     tile.focus();
     keydown(tile, 'ArrowDown');
-    assertFocus($$(mostVisited, '#addShortcut'));
+    assertFocus(mostVisited.$.addShortcut);
   });
 
   test('down focuses next tile', async () => {
     await addTiles(2);
-    const [first, second] = queryTiles();
-    first.focus();
-    keydown(first, 'ArrowDown');
-    assertFocus(second);
+    const tiles = queryTiles();
+    tiles[0]!.focus();
+    keydown(tiles[0]!, 'ArrowDown');
+    assertFocus(tiles[1]!);
   });
 
   test('up focuses on previous tile from addShortcut', async () => {
     await addTiles(1);
-    $$(mostVisited, '#addShortcut').focus();
-    keydown($$(mostVisited, '#addShortcut'), 'ArrowUp');
-    assertFocus(queryTiles()[0]);
+    mostVisited.$.addShortcut.focus();
+    keydown(mostVisited.$.addShortcut, 'ArrowUp');
+    assertFocus(queryTiles()[0]!);
   });
 
   test('up focuses on previous tile', async () => {
     await addTiles(2);
-    const [first, second] = queryTiles();
-    second.focus();
-    keydown(second, 'ArrowUp');
-    assertFocus(first);
+    const tiles = queryTiles();
+    tiles[1]!.focus();
+    keydown(tiles[1]!, 'ArrowUp');
+    assertFocus(tiles[0]!);
   });
 
   test('up/left does not change focus when on first tile', async () => {
     await addTiles(1);
-    const [tile] = queryTiles();
+    const tile = queryTiles()[0]!;
     tile.focus();
     keydown(tile, 'ArrowUp');
     assertFocus(tile);
@@ -139,10 +132,10 @@
 
   test('up/left/right/down addShortcut and no tiles', async () => {
     await addTiles(0);
-    $$(mostVisited, '#addShortcut').focus();
+    mostVisited.$.addShortcut.focus();
     for (const key of ['ArrowUp', 'ArrowLeft', 'ArrowRight', 'ArrowDown']) {
-      keydown($$(mostVisited, '#addShortcut'), key);
-      assertFocus($$(mostVisited, '#addShortcut'));
+      keydown(mostVisited.$.addShortcut, key);
+      assertFocus(mostVisited.$.addShortcut);
     }
   });
 });
diff --git a/chrome/test/data/webui/cr_components/most_visited_test.js b/chrome/test/data/webui/cr_components/most_visited_test.ts
similarity index 77%
rename from chrome/test/data/webui/cr_components/most_visited_test.js
rename to chrome/test/data/webui/cr_components/most_visited_test.ts
index f6b3e00e..4d7c5e0a 100644
--- a/chrome/test/data/webui/cr_components/most_visited_test.js
+++ b/chrome/test/data/webui/cr_components/most_visited_test.ts
@@ -6,76 +6,52 @@
 
 import {MostVisitedBrowserProxy} from 'chrome://resources/cr_components/most_visited/browser_proxy.js';
 import {MostVisitedElement} from 'chrome://resources/cr_components/most_visited/most_visited.js';
-import {MostVisitedPageCallbackRouter, MostVisitedPageHandlerRemote} from 'chrome://resources/cr_components/most_visited/most_visited.mojom-webui.js';
+import {MostVisitedPageCallbackRouter, MostVisitedPageHandlerRemote, MostVisitedTile} from 'chrome://resources/cr_components/most_visited/most_visited.mojom-webui.js';
 import {MostVisitedWindowProxy} from 'chrome://resources/cr_components/most_visited/window_proxy.js';
-import {CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
+import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import {isMac} from 'chrome://resources/js/cr.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {TextDirection} from 'chrome://resources/mojo/mojo/public/mojom/base/text_direction.mojom-webui.js';
-
 import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 import {eventToPromise, flushTasks} from 'chrome://webui-test/test_util.js';
 
 import {$$, assertNotStyle, assertStyle, keydown} from './most_visited_test_support.js';
 
-/** @type {!MostVisitedElement} */
-let mostVisited;
+let mostVisited: MostVisitedElement;
+let windowProxy: MostVisitedWindowProxy&TestBrowserProxy;
+let handler: MostVisitedPageHandlerRemote&TestBrowserProxy;
+let callbackRouterRemote: MostVisitedPageCallbackRouter;
+let mediaListenerWideWidth: FakeMediaQueryList;
+let mediaListenerMediumWidth: FakeMediaQueryList;
+let mediaListener: Function;
 
-/** @extends {TestBrowserProxy} */
-let windowProxy;
-
-/** @extends {TestBrowserProxy} */
-let handler;
-
-/** @extends {TestBrowserProxy} */
-let callbackRouterRemote;
-
-/** @type {!MediaQueryList} */
-let mediaListenerWideWidth;
-
-/** @type {!MediaQueryList} */
-let mediaListenerMediumWidth;
-
-/** @type {!Function} */
-let mediaListener;
-
-/**
- * @param {string} q
- * @return {!Array<!Element>}
- */
-function queryAll(q) {
-  return Array.from(mostVisited.shadowRoot.querySelectorAll(q));
+function queryAll<E extends Element = Element>(q: string): E[] {
+  return Array.from(mostVisited.shadowRoot!.querySelectorAll<E>(q));
 }
 
-/** @return {!Array<!Element>} */
-function queryTiles() {
-  return queryAll('.tile');
+function queryTiles(): HTMLAnchorElement[] {
+  return queryAll<HTMLAnchorElement>('.tile');
 }
 
-/** @return {!Array<!Element>} */
-function queryHiddenTiles() {
-  return queryAll('.tile[hidden]');
+function queryHiddenTiles(): HTMLAnchorElement[] {
+  return queryAll<HTMLAnchorElement>('.tile[hidden]');
 }
 
-/** @param {number} length */
-function assertTileLength(length) {
+function assertTileLength(length: number) {
   assertEquals(length, queryTiles().length);
 }
 
-/** @param {number} length */
-function assertHiddenTileLength(length) {
+function assertHiddenTileLength(length: number) {
   assertEquals(length, queryHiddenTiles().length);
 }
 
-/**
- * @param {number|!Array} n
- * @param {boolean=} customLinksEnabled
- * @param {boolean=} visible
- * @return {!Promise<void>}
- */
-async function addTiles(n, customLinksEnabled = true, visible = true) {
-  const tiles = Array.isArray(n) ? n : Array(n).fill(0).map((x, i) => {
+async function addTiles(
+    n: number|MostVisitedTile[], customLinksEnabled: boolean = true,
+    visible: boolean = true) {
+  const tiles = Array.isArray(n) ? n : Array(n).fill(0).map((_x, i) => {
     const char = String.fromCharCode(i + /* 'a' */ 97);
     return {
       title: char,
@@ -97,18 +73,17 @@
 }
 
 function assertAddShortcutHidden() {
-  assertTrue($$(mostVisited, '#addShortcut').hidden);
+  assertTrue(mostVisited.$.addShortcut.hidden);
 }
 
 function assertAddShortcutShown() {
-  assertFalse($$(mostVisited, '#addShortcut').hidden);
+  assertFalse(mostVisited.$.addShortcut.hidden);
 }
 
-/**
- * @suppress {checkTypes}
- */
 function createBrowserProxy() {
-  handler = TestBrowserProxy.fromClass(MostVisitedPageHandlerRemote);
+  handler = TestBrowserProxy.fromClass(MostVisitedPageHandlerRemote) as
+          unknown as MostVisitedPageHandlerRemote &
+      TestBrowserProxy;
   const callbackRouter = new MostVisitedPageCallbackRouter();
   MostVisitedBrowserProxy.setInstance(
       new MostVisitedBrowserProxy(handler, callbackRouter));
@@ -122,20 +97,29 @@
   }));
 }
 
-/**
- * @suppress {checkTypes}
- */
+class FakeMediaQueryList extends EventTarget implements MediaQueryList {
+  matches: boolean = false;
+  media: string;
+
+  constructor(query: string) {
+    super();
+    this.media = query;
+  }
+
+  addListener(listener: () => void) {
+    mediaListener = listener;
+  }
+
+  removeListener() {}
+  onchange() {}
+}
+
 function createWindowProxy() {
-  windowProxy = TestBrowserProxy.fromClass(MostVisitedWindowProxy);
-  windowProxy.setResultMapperFor('matchMedia', query => {
-    const mediaListenerList = /** @type {!MediaQueryList} */ ({
-      matches: false,  // Used to determine the screen width.
-      media: query,
-      addListener(listener) {
-        mediaListener = listener;
-      },
-      removeListener() {},
-    });
+  windowProxy = TestBrowserProxy.fromClass(MostVisitedWindowProxy) as unknown as
+          MostVisitedWindowProxy &
+      TestBrowserProxy;
+  windowProxy.setResultMapperFor('matchMedia', (query: string) => {
+    const mediaListenerList = new FakeMediaQueryList(query);
     if (query === '(min-width: 672px)') {
       mediaListenerWideWidth = mediaListenerList;
     } else if (query === '(min-width: 560px)') {
@@ -148,11 +132,7 @@
   MostVisitedWindowProxy.setInstance(windowProxy);
 }
 
-/**
- * @param {boolean} isWide
- * @param {boolean} isMedium
- */
-function updateScreenWidth(isWide, isMedium) {
+function updateScreenWidth(isWide: boolean, isMedium: boolean) {
   assertTrue(!!mediaListenerWideWidth);
   assertTrue(!!mediaListenerMediumWidth);
   mediaListenerWideWidth.matches = isWide;
@@ -169,8 +149,8 @@
 }
 
 suite('General', () => {
-  setup(/** @suppress {checkTypes} */ () => {
-    document.innerHTML = '';
+  setup(() => {
+    document.body.innerHTML = '';
 
     createBrowserProxy();
     createWindowProxy();
@@ -190,35 +170,34 @@
   });
 
   test('clicking on add shortcut opens dialog', () => {
-    assertFalse($$(mostVisited, '#dialog').open);
-    $$(mostVisited, '#addShortcut').click();
-    assertTrue($$(mostVisited, '#dialog').open);
+    assertFalse(mostVisited.$.dialog.open);
+    mostVisited.$.addShortcut.click();
+    assertTrue(mostVisited.$.dialog.open);
   });
 
   test('pressing enter when add shortcut has focus opens dialog', () => {
-    $$(mostVisited, '#addShortcut').focus();
-    assertFalse($$(mostVisited, '#dialog').open);
-    keydown($$(mostVisited, '#addShortcut'), 'Enter');
-    assertTrue($$(mostVisited, '#dialog').open);
+    mostVisited.$.addShortcut.focus();
+    assertFalse(mostVisited.$.dialog.open);
+    keydown(mostVisited.$.addShortcut, 'Enter');
+    assertTrue(mostVisited.$.dialog.open);
   });
 
   test('pressing space when add shortcut has focus opens dialog', () => {
-    $$(mostVisited, '#addShortcut').focus();
-    assertFalse($$(mostVisited, '#dialog').open);
-    $$(mostVisited, '#addShortcut').dispatchEvent(new KeyboardEvent('keydown', {
-      key: ' '
-    }));
-    $$(mostVisited, '#addShortcut').dispatchEvent(new KeyboardEvent('keyup', {
-      key: ' '
-    }));
-    assertTrue($$(mostVisited, '#dialog').open);
+    mostVisited.$.addShortcut.focus();
+    assertFalse(mostVisited.$.dialog.open);
+    mostVisited.$.addShortcut.dispatchEvent(
+        new KeyboardEvent('keydown', {key: ' '}));
+    mostVisited.$.addShortcut.dispatchEvent(
+        new KeyboardEvent('keyup', {key: ' '}));
+    assertTrue(mostVisited.$.dialog.open);
   });
 
   test('four tiles fit on one line with addShortcut', async () => {
     await addTiles(4);
     assertEquals(4, queryTiles().length);
     assertAddShortcutShown();
-    const tops = queryAll('a, #addShortcut').map(({offsetTop}) => offsetTop);
+    const tops = queryAll<HTMLElement>('a, #addShortcut')
+                     .map(({offsetTop}) => offsetTop);
     assertEquals(5, tops.length);
     tops.forEach(top => {
       assertEquals(tops[0], top);
@@ -229,7 +208,8 @@
     await addTiles(5);
     assertEquals(5, queryTiles().length);
     assertAddShortcutShown();
-    const tops = queryAll('a, #addShortcut').map(({offsetTop}) => offsetTop);
+    const tops = queryAll<HTMLElement>('a, #addShortcut')
+                     .map(({offsetTop}) => offsetTop);
     assertEquals(6, tops.length);
     const firstRowTop = tops[0];
     const secondRowTop = tops[3];
@@ -246,7 +226,8 @@
     await addTiles(9);
     assertEquals(9, queryTiles().length);
     assertAddShortcutShown();
-    const tops = queryAll('a, #addShortcut').map(({offsetTop}) => offsetTop);
+    const tops = queryAll<HTMLElement>('a, #addShortcut')
+                     .map(({offsetTop}) => offsetTop);
     assertEquals(10, tops.length);
     const firstRowTop = tops[0];
     const secondRowTop = tops[5];
@@ -263,7 +244,7 @@
     await addTiles(10);
     assertEquals(10, queryTiles().length);
     assertAddShortcutHidden();
-    const tops = queryAll('a:not([hidden])').map(a => a.offsetTop);
+    const tops = queryAll<HTMLElement>('a:not([hidden])').map(a => a.offsetTop);
     assertEquals(10, tops.length);
     const firstRowTop = tops[0];
     const secondRowTop = tops[5];
@@ -310,17 +291,17 @@
     assertEquals(1, queryTiles().length);
     assertEquals(0, queryAll('.tile[hidden]').length);
     assertTrue(mostVisited.hasAttribute('visible_'));
-    assertFalse($$(mostVisited, '#container').hidden);
+    assertFalse(mostVisited.$.container.hidden);
     await addTiles(1, /* customLinksEnabled */ true, /* visible */ false);
     assertEquals(1, queryTiles().length);
     assertEquals(0, queryAll('.tile[hidden]').length);
     assertFalse(mostVisited.hasAttribute('visible_'));
-    assertTrue($$(mostVisited, '#container').hidden);
+    assertTrue(mostVisited.$.container.hidden);
     await addTiles(1, /* customLinksEnabled */ true, /* visible */ true);
     assertEquals(1, queryTiles().length);
     assertEquals(0, queryAll('.tile[hidden]').length);
     assertTrue(mostVisited.hasAttribute('visible_'));
-    assertFalse($$(mostVisited, '#container').hidden);
+    assertFalse(mostVisited.$.container.hidden);
   });
 
   suite('test various widths', () => {
@@ -435,7 +416,7 @@
     await addTiles(1);
 
     // Act.
-    const tileLink = queryTiles()[0];
+    const tileLink = queryTiles()[0]!;
     // Prevent triggering a navigation, which would break the test.
     tileLink.href = '#';
     tileLink.click();
@@ -478,8 +459,8 @@
     });
   });
 
-  setup(/** @suppress {checkTypes} */ () => {
-    document.innerHTML = '';
+  setup(() => {
+    document.body.innerHTML = '';
 
     createBrowserProxy();
     createWindowProxy();
@@ -492,24 +473,20 @@
   });
 
   suite('add dialog', () => {
-    let dialog;
-    let inputName;
-
-    /** @type {!CrInputElement} */
-    let inputUrl;
-
-    let saveButton;
-    let cancelButton;
+    let dialog: CrDialogElement;
+    let inputName: CrInputElement;
+    let inputUrl: CrInputElement;
+    let saveButton: CrButtonElement;
+    let cancelButton: CrButtonElement;
 
     setup(() => {
-      dialog = $$(mostVisited, '#dialog');
-      inputName = $$(mostVisited, '#dialogInputName');
-      inputUrl =
-          /** @type {!CrInputElement} */ ($$(mostVisited, '#dialogInputUrl'));
-      saveButton = dialog.querySelector('.action-button');
-      cancelButton = dialog.querySelector('.cancel-button');
+      dialog = mostVisited.$.dialog;
+      inputName = $$<CrInputElement>(mostVisited, '#dialogInputName')!;
+      inputUrl = $$<CrInputElement>(mostVisited, '#dialogInputUrl')!;
+      saveButton = dialog.querySelector('.action-button')!;
+      cancelButton = dialog.querySelector('.cancel-button')!;
 
-      $$(mostVisited, '#addShortcut').click();
+      mostVisited.$.addShortcut.click();
       assertTrue(dialog.open);
     });
 
@@ -542,7 +519,7 @@
       inputName.value = 'name';
       inputUrl.value = 'url';
       cancelButton.click();
-      $$(mostVisited, '#addShortcut').click();
+      mostVisited.$.addShortcut.click();
       assertEquals('', inputName.value);
       assertEquals('', inputUrl.value);
     });
@@ -551,17 +528,17 @@
       inputUrl.value = 'url';
       const addCalled = handler.whenCalled('addMostVisitedTile');
       saveButton.click();
-      const [url, title] = await addCalled;
+      const [_url, title] = await addCalled;
       assertEquals('url', title);
     });
 
     test('toast shown on save', async () => {
       inputUrl.value = 'url';
-      assertFalse($$(mostVisited, '#toast').open);
+      assertFalse(mostVisited.$.toast.open);
       const addCalled = handler.whenCalled('addMostVisitedTile');
       saveButton.click();
       await addCalled;
-      assertTrue($$(mostVisited, '#toast').open);
+      assertTrue(mostVisited.$.toast.open);
     });
 
     test('toast has undo buttons when action successful', async () => {
@@ -572,7 +549,7 @@
       saveButton.click();
       await handler.whenCalled('addMostVisitedTile');
       await flushTasks();
-      assertFalse($$(mostVisited, '#undo').hidden);
+      assertFalse($$<HTMLElement>(mostVisited, '#undo')!.hidden);
     });
 
     test('toast has no undo buttons when action successful', async () => {
@@ -607,7 +584,7 @@
       inputUrl.value = 'url';
       const addCalled = handler.whenCalled('addMostVisitedTile');
       saveButton.click();
-      const [{url}, title] = await addCalled;
+      const [{url}, _title] = await addCalled;
       assertEquals('https://url/', url);
     });
 
@@ -667,41 +644,36 @@
 
   test('open edit dialog', async () => {
     await addTiles(2);
-    const actionMenu = $$(mostVisited, '#actionMenu');
-    const dialog = $$(mostVisited, '#dialog');
+    const actionMenu = mostVisited.$.actionMenu;
+    const dialog = mostVisited.$.dialog;
     assertFalse(actionMenu.open);
-    queryTiles()[0].querySelector('#actionMenuButton').click();
+    queryTiles()[0]!.querySelector<HTMLElement>('#actionMenuButton')!.click();
     assertTrue(actionMenu.open);
     assertFalse(dialog.open);
-    $$(mostVisited, '#actionMenuEdit').click();
+    $$<HTMLElement>(mostVisited, '#actionMenuEdit')!.click();
     assertFalse(actionMenu.open);
     assertTrue(dialog.open);
   });
 
   suite('edit dialog', () => {
-    let actionMenu;
-    let actionMenuButton;
-    let dialog;
-    let inputName;
-    let inputUrl;
-    let saveButton;
-    let cancelButton;
-    let tile;
+    let actionMenuButton: HTMLElement;
+    let inputName: CrInputElement;
+    let inputUrl: CrInputElement;
+    let saveButton: HTMLElement;
+    let tile: HTMLAnchorElement;
 
     setup(async () => {
-      actionMenu = $$(mostVisited, '#actionMenu');
-      dialog = $$(mostVisited, '#dialog');
-      inputName = $$(mostVisited, '#dialogInputName');
-      inputUrl = $$(mostVisited, '#dialogInputUrl');
-      saveButton = dialog.querySelector('.action-button');
-      cancelButton = dialog.querySelector('.cancel-button');
+      inputName = $$<CrInputElement>(mostVisited, '#dialogInputName')!;
+      inputUrl = $$<CrInputElement>(mostVisited, '#dialogInputUrl')!;
+
+      const dialog = mostVisited.$.dialog;
+      saveButton = dialog.querySelector('.action-button')!;
 
       await addTiles(2);
-      tile = queryTiles()[1];
-      actionMenuButton = /** @type {!CrActionMenuElement} */ (
-          tile.querySelector('#actionMenuButton'));
+      tile = queryTiles()[1]!;
+      actionMenuButton = tile.querySelector<HTMLElement>('#actionMenuButton')!;
       actionMenuButton.click();
-      $$(mostVisited, '#actionMenuEdit').click();
+      $$<HTMLElement>(mostVisited, '#actionMenuEdit')!.click();
     });
 
     test('edit a tile URL', async () => {
@@ -709,23 +681,23 @@
       const updateCalled = handler.whenCalled('updateMostVisitedTile');
       inputUrl.value = 'updated-url';
       saveButton.click();
-      const [url, newUrl, newTitle] = await updateCalled;
+      const [_url, newUrl, _newTitle] = await updateCalled;
       assertEquals('https://updated-url/', newUrl.url);
     });
 
     test('toast shown when tile editted', async () => {
       inputUrl.value = 'updated-url';
-      assertFalse($$(mostVisited, '#toast').open);
+      assertFalse(mostVisited.$.toast.open);
       saveButton.click();
       await handler.whenCalled('updateMostVisitedTile');
-      assertTrue($$(mostVisited, '#toast').open);
+      assertTrue(mostVisited.$.toast.open);
     });
 
     test('no toast when not editted', async () => {
-      assertFalse($$(mostVisited, '#toast').open);
+      assertFalse(mostVisited.$.toast.open);
       saveButton.click();
       await flushTasks();
-      assertFalse($$(mostVisited, '#toast').open);
+      assertFalse(mostVisited.$.toast.open);
     });
 
     test('edit a tile title', async () => {
@@ -733,7 +705,7 @@
       const updateCalled = handler.whenCalled('updateMostVisitedTile');
       inputName.value = 'updated name';
       saveButton.click();
-      const [url, newUrl, newTitle] = await updateCalled;
+      const [_url, _newUrl, newTitle] = await updateCalled;
       assertEquals('updated name', newTitle);
     });
 
@@ -744,10 +716,10 @@
       saveButton.click();
       // Reopen dialog and edit URL.
       actionMenuButton.click();
-      $$(mostVisited, '#actionMenuEdit').click();
+      $$<HTMLElement>(mostVisited, '#actionMenuEdit')!.click();
       inputUrl.value = 'updated-url';
       saveButton.click();
-      const [url, newUrl, newTitle] = await updateCalled;
+      const [_url, newUrl, _newTitle] = await updateCalled;
       assertEquals('https://updated-url/', newUrl.url);
     });
 
@@ -767,28 +739,29 @@
   });
 
   test('remove with action menu', async () => {
-    const actionMenu = $$(mostVisited, '#actionMenu');
-    const removeButton = $$(mostVisited, '#actionMenuRemove');
+    const actionMenu = mostVisited.$.actionMenu;
+    const removeButton = $$<HTMLElement>(mostVisited, '#actionMenuRemove')!;
     await addTiles(2);
-    const secondTile = queryTiles()[1];
-    const actionMenuButton = secondTile.querySelector('#actionMenuButton');
+    const secondTile = queryTiles()[1]!;
+    const actionMenuButton =
+        secondTile.querySelector<HTMLElement>('#actionMenuButton')!;
     assertFalse(actionMenu.open);
     actionMenuButton.click();
     assertTrue(actionMenu.open);
     const deleteCalled = handler.whenCalled('deleteMostVisitedTile');
-    assertFalse($$(mostVisited, '#toast').open);
+    assertFalse(mostVisited.$.toast.open);
     removeButton.click();
     assertFalse(actionMenu.open);
     assertEquals('https://b/', (await deleteCalled).url);
-    assertTrue($$(mostVisited, '#toast').open);
+    assertTrue(mostVisited.$.toast.open);
     // Toast buttons are visible.
     assertTrue(!!$$(mostVisited, '#undo'));
     assertTrue(!!$$(mostVisited, '#restore'));
   });
 
   test('remove query with action menu', async () => {
-    const actionMenu = $$(mostVisited, '#actionMenu');
-    const removeButton = $$(mostVisited, '#actionMenuRemove');
+    const actionMenu = mostVisited.$.actionMenu;
+    const removeButton = $$<HTMLElement>(mostVisited, '#actionMenuRemove')!;
     await addTiles([{
       title: 'title',
       titleDirection: TextDirection.LEFT_TO_RIGHT,
@@ -797,15 +770,16 @@
       titleSource: 0,
       isQueryTile: true,
     }]);
-    const actionMenuButton = queryTiles()[0].querySelector('#actionMenuButton');
+    const actionMenuButton =
+        queryTiles()[0]!.querySelector<HTMLElement>('#actionMenuButton')!;
     assertFalse(actionMenu.open);
     actionMenuButton.click();
     assertTrue(actionMenu.open);
     const deleteCalled = handler.whenCalled('deleteMostVisitedTile');
-    assertFalse($$(mostVisited, '#toast').open);
+    assertFalse(mostVisited.$.toast.open);
     removeButton.click();
     assertEquals('https://search-url/', (await deleteCalled).url);
-    assertTrue($$(mostVisited, '#toast').open);
+    assertTrue(mostVisited.$.toast.open);
     // Toast buttons are visible.
     assertTrue(!!$$(mostVisited, '#undo'));
     assertTrue(!!$$(mostVisited, '#restore'));
@@ -813,12 +787,13 @@
 
   test('remove with icon button (customLinksEnabled=false)', async () => {
     await addTiles(1, /* customLinksEnabled */ false);
-    const removeButton = queryTiles()[0].querySelector('#removeButton');
+    const removeButton =
+        queryTiles()[0]!.querySelector<HTMLElement>('#removeButton')!;
     const deleteCalled = handler.whenCalled('deleteMostVisitedTile');
-    assertFalse($$(mostVisited, '#toast').open);
+    assertFalse(mostVisited.$.toast.open);
     removeButton.click();
     assertEquals('https://a/', (await deleteCalled).url);
-    assertTrue($$(mostVisited, '#toast').open);
+    assertTrue(mostVisited.$.toast.open);
     // Toast buttons are visible.
     assertTrue(!!$$(mostVisited, '#undo'));
     assertTrue(!!$$(mostVisited, '#restore'));
@@ -835,12 +810,13 @@
           isQueryTile: true,
         }],
         /* customLinksEnabled */ false);
-    const removeButton = queryTiles()[0].querySelector('#removeButton');
+    const removeButton =
+        queryTiles()[0]!.querySelector<HTMLElement>('#removeButton')!;
     const deleteCalled = handler.whenCalled('deleteMostVisitedTile');
-    assertFalse($$(mostVisited, '#toast').open);
+    assertFalse(mostVisited.$.toast.open);
     removeButton.click();
     assertEquals('https://search-url/', (await deleteCalled).url);
-    assertTrue($$(mostVisited, '#toast').open);
+    assertTrue(mostVisited.$.toast.open);
     // Toast buttons are not visible.
     assertFalse(!!$$(mostVisited, '#undo'));
     assertFalse(!!$$(mostVisited, '#restore'));
@@ -848,27 +824,27 @@
 
   test('tile url is set to href of <a>', async () => {
     await addTiles(1);
-    const [tile] = queryTiles();
+    const tile = queryTiles()[0]!;
     assertEquals('https://a/', tile.href);
   });
 
   test('delete first tile', async () => {
     await addTiles(1);
-    const [tile] = queryTiles();
+    const tile = queryTiles()[0]!;
     const deleteCalled = handler.whenCalled('deleteMostVisitedTile');
-    assertFalse($$(mostVisited, '#toast').open);
+    assertFalse(mostVisited.$.toast.open);
     keydown(tile, 'Delete');
     assertEquals('https://a/', (await deleteCalled).url);
-    assertTrue($$(mostVisited, '#toast').open);
+    assertTrue(mostVisited.$.toast.open);
   });
 
   test('ctrl+z triggers undo and hides toast', async () => {
-    const toast = $$(mostVisited, '#toast');
+    const toast = mostVisited.$.toast;
     assertFalse(toast.open);
 
     // Add a tile and remove it to show the toast.
     await addTiles(1);
-    const [tile] = queryTiles();
+    const tile = queryTiles()[0]!;
     keydown(tile, 'Delete');
     await handler.whenCalled('deleteMostVisitedTile');
     assertTrue(toast.open);
@@ -885,18 +861,18 @@
   });
 
   test('ctrl+z does nothing if toast buttons are not showing', async () => {
-    const toast = $$(mostVisited, '#toast');
+    const toast = mostVisited.$.toast;
     assertFalse(toast.open);
 
     // A failed attempt at adding a shortcut to show the toast with no buttons.
     handler.setResultFor('addMostVisitedTile', Promise.resolve({
       success: false,
     }));
-    $$(mostVisited, '#addShortcut').click();
-    const dialog = $$(mostVisited, '#dialog');
-    const inputUrl = $$(mostVisited, '#dialogInputUrl');
+    mostVisited.$.addShortcut.click();
+    const inputUrl = $$<CrInputElement>(mostVisited, '#dialogInputUrl')!;
     inputUrl.value = 'url';
-    const saveButton = dialog.querySelector('.action-button');
+    const saveButton =
+        mostVisited.$.dialog.querySelector<HTMLElement>('.action-button')!;
     saveButton.click();
     await handler.whenCalled('addMostVisitedTile');
 
@@ -913,41 +889,43 @@
 
   test('toast restore defaults button', async () => {
     const wait = handler.whenCalled('restoreMostVisitedDefaults');
-    const toast = $$(mostVisited, '#toast');
+    const toast = mostVisited.$.toast;
     assertFalse(toast.open);
 
     // Add a tile and remove it to show the toast.
     await addTiles(1);
-    const [tile] = queryTiles();
+    const tile = queryTiles()[0]!;
     keydown(tile, 'Delete');
     await handler.whenCalled('deleteMostVisitedTile');
 
     assertTrue(toast.open);
-    toast.querySelector('#restore').click();
+    toast.querySelector<HTMLElement>('#restore')!.click();
     await wait;
     assertFalse(toast.open);
   });
 
   test('toast undo button', async () => {
     const wait = handler.whenCalled('undoMostVisitedTileAction');
-    const toast = $$(mostVisited, '#toast');
+    const toast = mostVisited.$.toast;
     assertFalse(toast.open);
 
     // Add a tile and remove it to show the toast.
     await addTiles(1);
-    const [tile] = queryTiles();
+    const tile = queryTiles()[0]!;
     keydown(tile, 'Delete');
     await handler.whenCalled('deleteMostVisitedTile');
 
     assertTrue(toast.open);
-    toast.querySelector('#undo').click();
+    toast.querySelector<HTMLElement>('#undo')!.click();
     await wait;
     assertFalse(toast.open);
   });
 
   test('drag first tile to second position', async () => {
     await addTiles(2);
-    const [first, second] = queryTiles();
+    const tiles = queryTiles();
+    const first = tiles[0]!;
+    const second = tiles[1]!;
     assertEquals('https://a/', first.href);
     assertTrue(first.draggable);
     assertEquals('https://b/', second.href);
@@ -968,13 +946,15 @@
     assertEquals('https://a/', url.url);
     assertEquals(1, newPos);
     const [newFirst, newSecond] = queryTiles();
-    assertEquals('https://b/', newFirst.href);
-    assertEquals('https://a/', newSecond.href);
+    assertEquals('https://b/', newFirst!.href);
+    assertEquals('https://a/', newSecond!.href);
   });
 
   test('drag second tile to first position', async () => {
     await addTiles(2);
-    const [first, second] = queryTiles();
+    const tiles = queryTiles();
+    const first = tiles[0]!;
+    const second = tiles[1]!;
     assertEquals('https://a/', first.href);
     assertTrue(first.draggable);
     assertEquals('https://b/', second.href);
@@ -995,13 +975,15 @@
     assertEquals('https://b/', url.url);
     assertEquals(0, newPos);
     const [newFirst, newSecond] = queryTiles();
-    assertEquals('https://b/', newFirst.href);
-    assertEquals('https://a/', newSecond.href);
+    assertEquals('https://b/', newFirst!.href);
+    assertEquals('https://a/', newSecond!.href);
   });
 
   test('most visited tiles cannot be reordered', async () => {
     await addTiles(2, /* customLinksEnabled= */ false);
-    const [first, second] = queryTiles();
+    const tiles = queryTiles();
+    const first = tiles[0]!;
+    const second = tiles[1]!;
     assertEquals('https://a/', first.href);
     assertTrue(first.draggable);
     assertEquals('https://b/', second.href);
@@ -1019,14 +1001,14 @@
     await flushTasks();
     assertEquals(0, handler.getCallCount('reorderMostVisitedTile'));
     const [newFirst, newSecond] = queryTiles();
-    assertEquals('https://a/', newFirst.href);
-    assertEquals('https://b/', newSecond.href);
+    assertEquals('https://a/', newFirst!.href);
+    assertEquals('https://b/', newSecond!.href);
   });
 });
 
 suite('Theming', () => {
-  setup(/** @suppress {checkTypes} */ () => {
-    document.innerHTML = '';
+  setup(() => {
+    document.body.innerHTML = '';
 
     createBrowserProxy();
     createWindowProxy();
@@ -1047,8 +1029,8 @@
       titleSource: 0,
       isQueryTile: false,
     }]);
-    const [tile] = queryTiles();
-    const titleElement = tile.querySelector('.tile-title');
+    const tile = queryTiles()[0]!;
+    const titleElement = tile.querySelector('.tile-title')!;
     assertEquals('rtl', window.getComputedStyle(titleElement).direction);
   });
 
@@ -1061,17 +1043,16 @@
       titleSource: 0,
       isQueryTile: false,
     }]);
-    const [tile] = queryTiles();
-    const titleElement = tile.querySelector('.tile-title');
+    const tile = queryTiles()[0]!;
+    const titleElement = tile.querySelector('.tile-title')!;
     assertEquals('ltr', window.getComputedStyle(titleElement).direction);
   });
 
   test('setting color styles tile color', () => {
     // Act.
-    $$(mostVisited, '#container')
-        .style.setProperty('--most-visited-text-color', 'blue');
-    $$(mostVisited, '#container')
-        .style.setProperty('--tile-background-color', 'red');
+    mostVisited.$.container.style.setProperty(
+        '--most-visited-text-color', 'blue');
+    mostVisited.$.container.style.setProperty('--tile-background-color', 'red');
 
     // Assert.
     queryAll('.tile-title').forEach(tile => {
diff --git a/chrome/test/data/webui/cr_components/most_visited_test_support.js b/chrome/test/data/webui/cr_components/most_visited_test_support.js
deleted file mode 100644
index a97976f..0000000
--- a/chrome/test/data/webui/cr_components/most_visited_test_support.js
+++ /dev/null
@@ -1,57 +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.
-
-import 'chrome://new-tab-page/strings.m.js';
-
-import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
-import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
-
-import {assertEquals, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
-
-/**
- * @param {!Element} element
- * @param {string} query
- * @return {!Element}
- */
-export function $$(element, query) {
-  return element.shadowRoot.querySelector(query);
-}
-
-/**
- * @param {!Element} element
- * @param {string} key
- */
-export function keydown(element, key) {
-  keyDownOn(element, '', [], key);
-}
-
-/**
- * Asserts the computed style value for an element.
- * @param {!Element} element The element.
- * @param {string} name The name of the style to assert.
- * @param {string} expected The expected style value.
- */
-export function assertStyle(element, name, expected) {
-  const actual = window.getComputedStyle(element).getPropertyValue(name).trim();
-  assertEquals(expected, actual);
-}
-
-/**
- * Asserts the computed style for an element is not value.
- * @param {!Element} element The element.
- * @param {string} name The name of the style to assert.
- * @param {string} not The value the style should not be.
- */
-export function assertNotStyle(element, name, not) {
-  const actual = window.getComputedStyle(element).getPropertyValue(name).trim();
-  assertNotEquals(not, actual);
-}
-
-/**
- * Asserts that an element is focused.
- * @param {!Element} element The element to test.
- */
-export function assertFocus(element) {
-  assertEquals(element, getDeepActiveElement());
-}
diff --git a/chrome/test/data/webui/cr_components/most_visited_test_support.ts b/chrome/test/data/webui/cr_components/most_visited_test_support.ts
new file mode 100644
index 0000000..bc5e4c8
--- /dev/null
+++ b/chrome/test/data/webui/cr_components/most_visited_test_support.ts
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://new-tab-page/strings.m.js';
+
+import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
+import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
+
+import {assertEquals, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
+
+export function $$<E extends Element = Element>(
+    element: HTMLElement, query: string): E {
+  return element.shadowRoot!.querySelector<E>(query)!;
+}
+
+export function keydown(element: Element, key: string) {
+  keyDownOn(element, 0, [], key);
+}
+
+/**
+ * Asserts the computed style value for an element.
+ * @param name The name of the style to assert.
+ * @param expected The expected style value.
+ */
+export function assertStyle(element: Element, name: string, expected: string) {
+  const actual = window.getComputedStyle(element).getPropertyValue(name).trim();
+  assertEquals(expected, actual);
+}
+
+/**
+ * Asserts the computed style for an element is not value.
+ * @param name The name of the style to assert.
+ * @param not The value the style should not be.
+ */
+export function assertNotStyle(element: Element, name: string, not: string) {
+  const actual = window.getComputedStyle(element).getPropertyValue(name).trim();
+  assertNotEquals(not, actual);
+}
+
+/**
+ * Asserts that an element is focused.
+ * @param element The element to test.
+ */
+export function assertFocus(element: Element) {
+  assertEquals(element, getDeepActiveElement());
+}
diff --git a/chrome/test/data/webui/cr_components/tsconfig_base.json b/chrome/test/data/webui/cr_components/tsconfig_base.json
index eeddfb3..371e43b 100644
--- a/chrome/test/data/webui/cr_components/tsconfig_base.json
+++ b/chrome/test/data/webui/cr_components/tsconfig_base.json
@@ -1,7 +1,6 @@
 {
   "extends": "../../../../../tools/typescript/tsconfig_base.json",
   "compilerOptions": {
-    "allowJs": true,
     "typeRoots": [
        "./../../../../../third_party/node/node_modules/@types"
     ]
diff --git a/chrome/test/data/webui/settings/all_sites_tests.ts b/chrome/test/data/webui/settings/all_sites_tests.ts
index a8b5ff67..3717f89 100644
--- a/chrome/test/data/webui/settings/all_sites_tests.ts
+++ b/chrome/test/data/webui/settings/all_sites_tests.ts
@@ -959,8 +959,54 @@
 
     assertEquals(
         siteGroup.origins[0].origin,
-        await browserProxy.whenCalled('clearOriginDataAndCookies'));
-    assertEquals(1, browserProxy.getCallCount('clearOriginDataAndCookies'));
+        await browserProxy.whenCalled(
+            'clearUnpartitionedOriginDataAndCookies'));
+
+    const [origin, types, setting] =
+        await browserProxy.whenCalled('setOriginPermissions');
+    assertEquals(origin, siteGroup.origins[0].origin);
+    assertEquals(types, null);  // Null affects all content types.
+    assertEquals(setting, ContentSetting.DEFAULT);
+
+    assertEquals(
+        1, browserProxy.getCallCount('clearUnpartitionedOriginDataAndCookies'));
+    assertEquals(1, browserProxy.getCallCount('setOriginPermissions'));
+    assertEquals(5, testElement.$.allSitesList.items![0].numCookies);
+  });
+
+  test('remove partitioned origin', async function() {
+    const siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+    siteGroup.origins[0].isPartitioned = true;
+    siteGroup.origins[0].numCookies = 1;
+    siteGroup.origins[1].numCookies = 2;
+    siteGroup.origins[2].numCookies = 3;
+    siteGroup.numCookies = 6;
+
+    testElement.siteGroupMap.set(
+        siteGroup.etldPlus1, JSON.parse(JSON.stringify(siteGroup)));
+    testElement.forceListUpdateForTesting();
+    flush();
+
+    // Remove the the partitioned entry, which will have been ordered to the
+    // bottom of the displayed origins.
+    const siteEntries =
+        testElement.$.listContainer.querySelectorAll('site-entry');
+    const originList = siteEntries[0]!.$.originList.get();
+    flush();
+    const originEntries = originList.querySelectorAll('.hr');
+    assertEquals(3, originEntries.length);
+    originEntries[2]!.querySelector<HTMLElement>(
+                         '#removeOriginButton')!.click();
+    confirmDialog();
+
+    const [origin, etldPlus1] =
+        await browserProxy.whenCalled('clearPartitionedOriginDataAndCookies');
+
+    assertEquals(siteGroup.origins[0].origin, origin);
+    assertEquals(siteGroup.etldPlus1, etldPlus1);
+    assertEquals(
+        1, browserProxy.getCallCount('clearPartitionedOriginDataAndCookies'));
+    assertEquals(0, browserProxy.getCallCount('setOriginPermissions'));
     assertEquals(5, testElement.$.allSitesList.items![0].numCookies);
   });
 
@@ -993,7 +1039,8 @@
     removeFirstOrigin();
     cancelDialog();
 
-    assertEquals(0, browserProxy.getCallCount('clearOriginDataAndCookies'));
+    assertEquals(
+        0, browserProxy.getCallCount('clearUnpartitionedOriginDataAndCookies'));
     assertEquals(0, browserProxy.getCallCount('setOriginPermissions'));
     assertEquals(6, testElement.$.allSitesList.items![0].numCookies);
   });
diff --git a/chrome/test/data/webui/settings/site_entry_tests.ts b/chrome/test/data/webui/settings/site_entry_tests.ts
index 7de992d..433f13e 100644
--- a/chrome/test/data/webui/settings/site_entry_tests.ts
+++ b/chrome/test/data/webui/settings/site_entry_tests.ts
@@ -10,11 +10,11 @@
 import {LocalDataBrowserProxyImpl, SiteEntryElement, SiteSettingsPrefsBrowserProxyImpl, SortMethod} from 'chrome://settings/lazy_load.js';
 import {Router, routes} from 'chrome://settings/settings.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {eventToPromise} from 'chrome://webui-test/test_util.js';
+import {eventToPromise, isChildVisible} from 'chrome://webui-test/test_util.js';
 
 import {TestLocalDataBrowserProxy} from './test_local_data_browser_proxy.js';
 import {TestSiteSettingsPrefsBrowserProxy} from './test_site_settings_prefs_browser_proxy.js';
-import {createSiteGroup} from './test_util.js';
+import { createOriginInfo,createSiteGroup} from './test_util.js';
 
 // clang-format on
 
@@ -456,6 +456,12 @@
     'https://www.example.com',
     'https://login.example.com',
   ]);
+  /**
+   * An example eTLD+1 Object with a single origin in it.
+   */
+  const TEST_SINGLE_SITE_GROUP = createSiteGroup('foo.com', [
+    'https://login.foo.com',
+  ]);
 
   /**
    * The mock proxy object to use during test.
@@ -528,4 +534,82 @@
     assertEquals(testElement.listIndex, index);
     assertEquals(undefined, origin);
   });
+
+  test('partitioned entry interaction', async function() {
+    // Clone this object to avoid propagating changes made in this test.
+    const testSiteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+
+    // Add a partitioned entry for an unrelated origin.
+    testSiteGroup.origins.push(
+        createOriginInfo('wwww.unrelated.com', {isPartitioned: true}));
+
+    testElement.siteGroup = testSiteGroup;
+    flush();
+    const collapseChild = testElement.$.originList.get();
+    testElement.$.toggleButton.click();
+    flush();
+
+    const originList = collapseChild.querySelectorAll('.hr');
+    assertEquals(4, originList.length);
+
+    // Partitioned entries should not be displaying a link arrow, while
+    // unpartitioned entries should.
+    assertTrue(isChildVisible(
+        originList[0]!, 'cr-icon-button', /*checkLightDom=*/ true));
+    assertFalse(isChildVisible(
+        originList[3]!, 'cr-icon-button', /*checkLightDom=*/ true));
+
+    // Removing a partitioned entry should fire the appropriate event.
+    const siteRemoved = eventToPromise('remove-site', testElement);
+    originList[3]!.querySelector<HTMLElement>('#removeOriginButton')!.click();
+    const siteRemovedEvent = await siteRemoved;
+
+    const args = siteRemovedEvent.detail;
+    const {actionScope, index, origin, isPartitioned} = args;
+    assertEquals('origin', actionScope);
+    assertEquals(testElement.listIndex, index);
+    assertEquals(testElement.siteGroup.origins[3]!.origin, origin);
+    assertTrue(isPartitioned);
+  });
+
+  test('partitioned entry prevents collapse', function() {
+    // If a siteGroup has a partitioned entry, even if it is the only entry,
+    // it should keep the site entry as a top level + collapse list.
+    const testSingleSite = JSON.parse(JSON.stringify(TEST_SINGLE_SITE_GROUP));
+    testSingleSite.origins[0].isPartitioned = true;
+
+    testElement.siteGroup = testSingleSite;
+    flush();
+    const collapseChild = testElement.$.originList.get();
+
+    // The toggle button should expand the collapse, rather than navigate.
+    const startingRoute = Router.getInstance().getCurrentRoute();
+    testElement.$.toggleButton.click();
+    flush();
+    assertEquals(startingRoute, Router.getInstance().getCurrentRoute());
+
+    const originList = collapseChild.querySelectorAll('.hr');
+    assertEquals(1, originList.length);
+  });
+
+  test('unpartitioned entry remains collapsed', async function() {
+    // Check that a single origin containing unpartitioned storage only is
+    // correctly collapsed.
+    testElement.siteGroup = JSON.parse(JSON.stringify(TEST_SINGLE_SITE_GROUP));
+    flush();
+    const collapseChild = testElement.$.originList.get();
+
+    const originList = collapseChild.querySelectorAll('.hr');
+    testElement.$.toggleButton.click();
+    flush();
+    assertEquals(0, originList.length);
+
+    // Clicking the toggleButton should navigate the page away, as there is
+    // only one entry.
+    testElement.$.toggleButton.click();
+    flush();
+    assertEquals(
+        routes.SITE_SETTINGS_SITE_DETAILS.path,
+        Router.getInstance().getCurrentRoute().path);
+  });
 });
diff --git a/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.ts b/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.ts
index b7c3ce9..7002b56 100644
--- a/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.ts
+++ b/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.ts
@@ -61,7 +61,8 @@
       'setProtocolHandlerDefault',
       'updateIncognitoStatus',
       'clearEtldPlus1DataAndCookies',
-      'clearOriginDataAndCookies',
+      'clearUnpartitionedOriginDataAndCookies',
+      'clearPartitionedOriginDataAndCookies',
       'recordAction',
       'getCookieSettingDescription',
       'getRecentSitePermissions',
@@ -580,8 +581,14 @@
   }
 
   /** @override */
-  clearOriginDataAndCookies(origin: string) {
-    this.methodCalled('clearOriginDataAndCookies', origin);
+  clearUnpartitionedOriginDataAndCookies(origin: string) {
+    this.methodCalled('clearUnpartitionedOriginDataAndCookies', origin);
+  }
+
+  /** @override */
+  clearPartitionedOriginDataAndCookies(origin: string, etldPlus1: string) {
+    this.methodCalled(
+        'clearPartitionedOriginDataAndCookies', [origin, etldPlus1]);
   }
 
   /** @override */
diff --git a/chrome/test/data/webui/settings/test_util.ts b/chrome/test/data/webui/settings/test_util.ts
index 0e0247a4..14d3c37 100644
--- a/chrome/test/data/webui/settings/test_util.ts
+++ b/chrome/test/data/webui/settings/test_util.ts
@@ -190,6 +190,7 @@
         numCookies: 0,
         hasPermissionSettings: false,
         isInstalled: false,
+        isPartitioned: false,
       },
       override || {});
 }
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 820b5a9..57215768 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-14409.0.0
\ No newline at end of file
+14411.0.0
\ No newline at end of file
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 22a5581..e0af001 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -2310,20 +2310,8 @@
       <message name="IDS_SHIMLESS_RMA_LOGS_SAVE_BUTTON" translateable="false" desc="Label for the logs dialog save to USB button. This triggers saving to the root of a USB drive with no option to select location or title of the logs. It is automatic as the file browser is not available to the shimless RMA app.">
         Save to USB
       </message>
-      <message name="IDS_SHIMLESS_BATTERY_CUTOFF_TITLE" translateable="false" desc="Title for the battery cutoff dialog for confirming or cancelling the cutoff action">
-        Battery Cutoff
-      </message>
-      <message name="IDS_SHIMLESS_BATTERY_SHUTOFF_UNPLUG_MESSAGE" translateable="false" desc="Message informing user they need to disconnect the power cable to perform battery shutoff">
-        Unplug your cord to cut off battery
-      </message>
-      <message name="IDS_SHIMLESS_BATTERY_SHUTOFF_SHUTDOWN_MESSAGE" translateable="false" desc="Message informing user the device is ready to be shut down and perform battery shutoff">
-        Shut down your device to complete battery cut off
-      </message>
-      <message name="IDS_SHIMLESS_BATTERY_SHUTOFF_CANCEL_BUTTON" translateable="false" desc="Label for the battery shutoff dialog cancel button">
-        Cancel
-      </message>
-      <message name="IDS_SHIMLESS_BATTERY_SHUTOFF_SHUTDOWN_BUTTON" translateable="false" desc="Label for the battery shutoff dialog shut down button.">
-        Shut down
+      <message name="IDS_SHIMLESS_BATTERY_SHUTOFF_TOOLTIP_TEXT" translateable="false" desc="Label for the battery shutoff tooltip.">
+        Unplug power to perform battery cutoff
       </message>
       <!-- Run calibration page -->
       <message name="IDS_SHIMLESS_RMA_RUN_CALIBRATION_PAGE_TITLE" translateable="false" desc="Title for the page shown when running component calibration steps.">
diff --git a/chromeos/profiles/atom.afdo.newest.txt b/chromeos/profiles/atom.afdo.newest.txt
index d4fcc29..39ddb27 100644
--- a/chromeos/profiles/atom.afdo.newest.txt
+++ b/chromeos/profiles/atom.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-atom-99-4744.1-1639392235-benchmark-99.0.4768.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-atom-99-4758.0-1639999136-benchmark-99.0.4777.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/bigcore.afdo.newest.txt b/chromeos/profiles/bigcore.afdo.newest.txt
index 6b956df..ec84f43 100644
--- a/chromeos/profiles/bigcore.afdo.newest.txt
+++ b/chromeos/profiles/bigcore.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-bigcore-99-4744.1-1639397500-benchmark-99.0.4768.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-bigcore-99-4758.0-1639999469-benchmark-99.0.4777.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index ef4ad5d..a0018a8 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-98-4744.1-1639393599-benchmark-98.0.4758.8-r1.orderfile.xz
+chromeos-chrome-orderfile-field-98-4744.1-1639393599-benchmark-98.0.4758.17-r1.orderfile.xz
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index c5ac07a..27454634 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -104,11 +104,12 @@
   # https://crbug.com/1279285: Flaky.
   "policy.AllowWakeLocks",
 
-  # https://crbug.com/1281255
-  "nacl.Pnacl",
-
   # https://crbug.com/1281802
   "graphics.TraceReplay",
+  "graphics.TraceReplay.glxgears_stable",
+
+  # https://crbug.com/1281645
+  "quicksettings.LockScreen",
 ]
 
 # To disable a specific test in lacros_all_tast_tests, add it the following
diff --git a/components/app_restore/features.cc b/components/app_restore/features.cc
index 9b114cc..494ef80 100644
--- a/components/app_restore/features.cc
+++ b/components/app_restore/features.cc
@@ -10,6 +10,9 @@
 const base::Feature kArcGhostWindow{"ArcGhostWindow",
                                     base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kArcWindowPredictor{"ArcWindowPredictor",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kFullRestore{"FullRestore",
                                  base::FEATURE_ENABLED_BY_DEFAULT};
 
@@ -18,6 +21,10 @@
          base::FeatureList::IsEnabled(kArcGhostWindow);
 }
 
+bool IsArcWindowPredictorEnabled() {
+  return base::FeatureList::IsEnabled(kArcWindowPredictor);
+}
+
 bool IsFullRestoreEnabled() {
   return base::FeatureList::IsEnabled(kFullRestore);
 }
diff --git a/components/app_restore/features.h b/components/app_restore/features.h
index 7528a13..a5025be 100644
--- a/components/app_restore/features.h
+++ b/components/app_restore/features.h
@@ -15,6 +15,10 @@
 // full restore process.
 COMPONENT_EXPORT(APP_RESTORE) extern const base::Feature kArcGhostWindow;
 
+// Enables the window state and bounds predictor and full ghost window for ARC++
+// apps.
+COMPONENT_EXPORT(APP_RESTORE) extern const base::Feature kArcWindowPredictor;
+
 // Enables the full restore feature. If this is enabled, we will restore apps
 // and app windows after a crash or reboot.
 COMPONENT_EXPORT(APP_RESTORE) extern const base::Feature kFullRestore;
diff --git a/components/browsing_data/content/canonical_cookie_hash.cc b/components/browsing_data/content/canonical_cookie_hash.cc
index fcaa43a6..c7c4c35 100644
--- a/components/browsing_data/content/canonical_cookie_hash.cc
+++ b/components/browsing_data/content/canonical_cookie_hash.cc
@@ -12,7 +12,11 @@
 size_t FastHash(const net::CanonicalCookie& cookie) {
   return base::PersistentHash(cookie.Name()) +
          3 * base::PersistentHash(cookie.Domain()) +
-         7 * base::PersistentHash(cookie.Path());
+         7 * base::PersistentHash(cookie.Path()) +
+         (cookie.IsPartitioned()
+              ? 13 * base::PersistentHash(
+                         cookie.PartitionKey()->site().GetURL().host())
+              : 0);
 }
 
 bool CanonicalCookieComparer::operator()(
@@ -20,7 +24,8 @@
     const net::CanonicalCookie& cookie2) const {
   return cookie1.Name() == cookie2.Name() &&
          cookie1.Domain() == cookie2.Domain() &&
-         cookie1.Path() == cookie2.Path();
+         cookie1.Path() == cookie2.Path() &&
+         cookie1.PartitionKey() == cookie2.PartitionKey();
 }
 
 }  // namespace canonical_cookie
diff --git a/components/browsing_data/content/mock_cookie_helper.cc b/components/browsing_data/content/mock_cookie_helper.cc
index 59657cc..cbf9789 100644
--- a/components/browsing_data/content/mock_cookie_helper.cc
+++ b/components/browsing_data/content/mock_cookie_helper.cc
@@ -29,26 +29,23 @@
 }
 
 void MockCookieHelper::DeleteCookie(const net::CanonicalCookie& cookie) {
-  std::string key = cookie.Name() + "=" + cookie.Value();
-  ASSERT_TRUE(base::Contains(cookies_, key));
-  cookies_[key] = false;
+  ASSERT_TRUE(base::Contains(cookies_, cookie));
+  cookies_[cookie] = false;
 }
 
-void MockCookieHelper::AddCookieSamples(const GURL& url,
-                                        const std::string& cookie_line) {
+void MockCookieHelper::AddCookieSamples(
+    const GURL& url,
+    const std::string& cookie_line,
+    absl::optional<net::CookiePartitionKey> cookie_partition_key) {
   std::unique_ptr<net::CanonicalCookie> cc(net::CanonicalCookie::Create(
       url, cookie_line, base::Time::Now(), absl::nullopt /* server_time */,
-      absl::nullopt /* cookie_partition_key */));
+      cookie_partition_key));
 
   if (cc.get()) {
-    for (const auto& cookie : cookie_list_) {
-      if (cookie.Name() == cc->Name() && cookie.Domain() == cc->Domain() &&
-          cookie.Path() == cc->Path()) {
-        return;
-      }
-    }
+    if (cookies_.count(*cc))
+      return;
     cookie_list_.push_back(*cc);
-    cookies_[cookie_line] = true;
+    cookies_[*cc] = true;
   }
 }
 
diff --git a/components/browsing_data/content/mock_cookie_helper.h b/components/browsing_data/content/mock_cookie_helper.h
index 6fcc9201..d334cfa 100644
--- a/components/browsing_data/content/mock_cookie_helper.h
+++ b/components/browsing_data/content/mock_cookie_helper.h
@@ -5,9 +5,10 @@
 #ifndef COMPONENTS_BROWSING_DATA_CONTENT_MOCK_COOKIE_HELPER_H_
 #define COMPONENTS_BROWSING_DATA_CONTENT_MOCK_COOKIE_HELPER_H_
 
-#include <map>
 #include <string>
+#include <unordered_map>
 
+#include "components/browsing_data/content/canonical_cookie_hash.h"
 #include "components/browsing_data/content/cookie_helper.h"
 #include "net/cookies/canonical_cookie.h"
 
@@ -30,7 +31,10 @@
   void DeleteCookie(const net::CanonicalCookie& cookie) override;
 
   // Adds some cookie samples.
-  void AddCookieSamples(const GURL& url, const std::string& cookie_line);
+  void AddCookieSamples(const GURL& url,
+                        const std::string& cookie_line,
+                        absl::optional<net::CookiePartitionKey>
+                            cookie_partition_key = absl::nullopt);
 
   // Notifies the callback.
   void Notify();
@@ -50,7 +54,11 @@
   net::CookieList cookie_list_;
 
   // Stores which cookies exist.
-  std::map<const std::string, bool> cookies_;
+  std::unordered_map<net::CanonicalCookie,
+                     bool,
+                     canonical_cookie::CanonicalCookieHasher,
+                     canonical_cookie::CanonicalCookieComparer>
+      cookies_;
 };
 
 }  // namespace browsing_data
diff --git a/components/exo/shell_surface.cc b/components/exo/shell_surface.cc
index 32da0d8..703c1de 100644
--- a/components/exo/shell_surface.cc
+++ b/components/exo/shell_surface.cc
@@ -13,10 +13,12 @@
 #include "ash/wm/window_state.h"
 #include "base/bind.h"
 #include "base/logging.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/trace_event/trace_event.h"
 #include "chromeos/ui/base/window_state_type.h"
 #include "components/exo/shell_surface_util.h"
+#include "components/exo/window_properties.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/cursor_client.h"
 #include "ui/aura/env.h"
@@ -154,14 +156,19 @@
   TRACE_EVENT0("exo", "ShellSurface::Maximize");
 
   if (!widget_) {
-    initial_show_state_ = ui::SHOW_STATE_MAXIMIZED;
+    if (initial_show_state_ != ui::SHOW_STATE_FULLSCREEN ||
+        ShouldExitFullscreenFromRestoreOrMaximized())
+      initial_show_state_ = ui::SHOW_STATE_MAXIMIZED;
     return;
   }
 
-  // Note: This will ask client to configure its surface even if already
-  // maximized.
-  ScopedConfigure scoped_configure(this, true);
-  widget_->Maximize();
+  if (!widget_->IsFullscreen() ||
+      ShouldExitFullscreenFromRestoreOrMaximized()) {
+    // Note: This will ask client to configure its surface even if already
+    // maximized.
+    ScopedConfigure scoped_configure(this, true);
+    widget_->Maximize();
+  }
 }
 
 void ShellSurface::Minimize() {
@@ -182,21 +189,30 @@
   TRACE_EVENT0("exo", "ShellSurface::Restore");
 
   if (!widget_) {
-    initial_show_state_ = ui::SHOW_STATE_NORMAL;
+    if (initial_show_state_ != ui::SHOW_STATE_FULLSCREEN ||
+        ShouldExitFullscreenFromRestoreOrMaximized())
+      initial_show_state_ = ui::SHOW_STATE_NORMAL;
     return;
   }
 
-  // Note: This will ask client to configure its surface even if not already
-  // maximized or minimized.
-  ScopedConfigure scoped_configure(this, true);
-  widget_->Restore();
+  if (!widget_->IsFullscreen() ||
+      ShouldExitFullscreenFromRestoreOrMaximized()) {
+    // Note: This will ask client to configure its surface even if already
+    // maximized.
+    ScopedConfigure scoped_configure(this, true);
+    widget_->Restore();
+  }
 }
 
 void ShellSurface::SetFullscreen(bool fullscreen) {
   TRACE_EVENT1("exo", "ShellSurface::SetFullscreen", "fullscreen", fullscreen);
 
   if (!widget_) {
-    initial_show_state_ = ui::SHOW_STATE_FULLSCREEN;
+    if (fullscreen) {
+      initial_show_state_ = ui::SHOW_STATE_FULLSCREEN;
+    } else if (initial_show_state_ == ui::SHOW_STATE_FULLSCREEN) {
+      initial_show_state_ = ui::SHOW_STATE_DEFAULT;
+    }
     return;
   }
 
diff --git a/components/exo/shell_surface_base.cc b/components/exo/shell_surface_base.cc
index 6edecb06..d549478 100644
--- a/components/exo/shell_surface_base.cc
+++ b/components/exo/shell_surface_base.cc
@@ -1537,6 +1537,14 @@
   return frame_view;
 }
 
+bool ShellSurfaceBase::ShouldExitFullscreenFromRestoreOrMaximized() {
+  if (widget_ && widget_->GetNativeWindow()) {
+    return widget_->GetNativeWindow()->GetProperty(
+        kRestoreOrMaximizeExitsFullscreen);
+  }
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // ShellSurfaceBase, private:
 
diff --git a/components/exo/shell_surface_base.h b/components/exo/shell_surface_base.h
index 71db71c..e23e5d2 100644
--- a/components/exo/shell_surface_base.h
+++ b/components/exo/shell_surface_base.h
@@ -337,6 +337,10 @@
   // without actually updating it.
   bool CalculateCanResize() const;
 
+  // Returns true if this surface will exit fullscreen from a restore or
+  // maximize request. Currently only true for Lacros.
+  bool ShouldExitFullscreenFromRestoreOrMaximized();
+
   views::Widget* widget_ = nullptr;
   bool movement_disabled_ = false;
   gfx::Point origin_;
diff --git a/components/exo/shell_surface_unittest.cc b/components/exo/shell_surface_unittest.cc
index 5e1ceee..6e68cc3 100644
--- a/components/exo/shell_surface_unittest.cc
+++ b/components/exo/shell_surface_unittest.cc
@@ -31,6 +31,7 @@
 #include "components/exo/test/exo_test_base.h"
 #include "components/exo/test/exo_test_helper.h"
 #include "components/exo/test/shell_surface_builder.h"
+#include "components/exo/window_properties.h"
 #include "components/exo/wm_helper.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/client/aura_constants.h"
@@ -252,6 +253,44 @@
   EXPECT_FALSE(shell_surface->CanMaximize());
 }
 
+TEST_F(ShellSurfaceTest, MaximizeFromFullscreen) {
+  std::unique_ptr<ShellSurface> shell_surface =
+      test::ShellSurfaceBuilder({256, 256})
+          .SetMaximumSize(gfx::Size(10, 10))
+          .BuildShellSurface();
+  // Act: Maximize after fullscreen
+  shell_surface->root_surface()->Commit();
+  shell_surface->SetFullscreen(true);
+  shell_surface->root_surface()->Commit();
+  shell_surface->Maximize();
+  shell_surface->root_surface()->Commit();
+
+  // Assert: Window should stay fullscreen.
+  EXPECT_TRUE(shell_surface->GetWidget()->IsFullscreen());
+}
+
+TEST_F(ShellSurfaceTest, MaximizeExitsFullscreen) {
+  std::unique_ptr<ShellSurface> shell_surface =
+      test::ShellSurfaceBuilder({256, 256})
+          .SetMaximumSize(gfx::Size(10, 10))
+          .BuildShellSurface();
+
+  // Act: Set window property kRestoreOrMaximizeExitsFullscreen
+  // then maximize after fullscreen
+  shell_surface->root_surface()->Commit();
+  shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
+      kRestoreOrMaximizeExitsFullscreen, true);
+  shell_surface->SetFullscreen(true);
+  shell_surface->root_surface()->Commit();
+  shell_surface->Maximize();
+  shell_surface->root_surface()->Commit();
+
+  // Assert: Window should exit fullscreen and be maximized.
+  EXPECT_TRUE(shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
+      kRestoreOrMaximizeExitsFullscreen));
+  EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
+}
+
 TEST_F(ShellSurfaceTest, Minimize) {
   gfx::Size buffer_size(256, 256);
   std::unique_ptr<Buffer> buffer(
@@ -305,6 +344,44 @@
       shell_surface->GetWidget()->GetWindowBoundsInScreen().size().ToString());
 }
 
+TEST_F(ShellSurfaceTest, RestoreFromFullscreen) {
+  std::unique_ptr<ShellSurface> shell_surface =
+      test::ShellSurfaceBuilder({256, 256})
+          .SetMaximumSize(gfx::Size(10, 10))
+          .BuildShellSurface();
+
+  // Act: Restore after fullscreen
+  shell_surface->SetFullscreen(true);
+  shell_surface->root_surface()->Commit();
+  shell_surface->Restore();
+  shell_surface->root_surface()->Commit();
+
+  // Assert: Window should stay fullscreen.
+  EXPECT_TRUE(shell_surface->GetWidget()->IsFullscreen());
+}
+
+TEST_F(ShellSurfaceTest, RestoreExitsFullscreen) {
+  std::unique_ptr<ShellSurface> shell_surface =
+      test::ShellSurfaceBuilder({256, 256})
+          .SetMaximumSize(gfx::Size(10, 10))
+          .BuildShellSurface();
+
+  // Act: Set window property kRestoreOrMaximizeExitsFullscreen
+  // then restore after fullscreen
+  shell_surface->root_surface()->Commit();
+  shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
+      kRestoreOrMaximizeExitsFullscreen, true);
+  shell_surface->SetFullscreen(true);
+  shell_surface->Restore();
+  shell_surface->root_surface()->Commit();
+
+  // Assert: Window should exit fullscreen and be restored.
+  EXPECT_TRUE(shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
+      kRestoreOrMaximizeExitsFullscreen));
+  EXPECT_EQ(gfx::Size(256, 256),
+            shell_surface->GetWidget()->GetWindowBoundsInScreen().size());
+}
+
 TEST_F(ShellSurfaceTest, HostWindowBoundsUpdatedAfterCommitWidget) {
   gfx::Size buffer_size(256, 256);
   std::unique_ptr<Buffer> buffer(
@@ -345,6 +422,33 @@
             shell_surface->GetWidget()->GetWindowBoundsInScreen().ToString());
 }
 
+TEST_F(ShellSurfaceTest, PreWidgetUnfullscreen) {
+  std::unique_ptr<ShellSurface> shell_surface =
+      test::ShellSurfaceBuilder({256, 256})
+          .SetNoCommit()
+          .SetMaximumSize(gfx::Size(10, 10))
+          .BuildShellSurface();
+  shell_surface->Maximize();
+  shell_surface->SetFullscreen(false);
+  EXPECT_EQ(shell_surface->GetWidget(), nullptr);
+  shell_surface->root_surface()->Commit();
+  EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
+}
+
+TEST_F(ShellSurfaceTest, PreWidgetMaximizeFromFullscreen) {
+  std::unique_ptr<ShellSurface> shell_surface =
+      test::ShellSurfaceBuilder({256, 256})
+          .SetNoCommit()
+          .SetMaximumSize(gfx::Size(10, 10))
+          .BuildShellSurface();
+  // Fullscreen -> Maximize for non Lacros surfaces should stay fullscreen
+  shell_surface->SetFullscreen(true);
+  shell_surface->Maximize();
+  EXPECT_EQ(shell_surface->GetWidget(), nullptr);
+  shell_surface->root_surface()->Commit();
+  EXPECT_TRUE(shell_surface->GetWidget()->IsFullscreen());
+}
+
 TEST_F(ShellSurfaceTest, SetTitle) {
   gfx::Size buffer_size(256, 256);
   std::unique_ptr<Buffer> buffer(
diff --git a/components/exo/window_properties.cc b/components/exo/window_properties.cc
index a5f6395..0043acf 100644
--- a/components/exo/window_properties.cc
+++ b/components/exo/window_properties.cc
@@ -8,4 +8,6 @@
 
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(std::string, kApplicationIdKey, nullptr)
 
+DEFINE_UI_CLASS_PROPERTY_KEY(bool, kRestoreOrMaximizeExitsFullscreen, false)
+
 }  // namespace exo
diff --git a/components/exo/window_properties.h b/components/exo/window_properties.h
index 303066c..8119b68 100644
--- a/components/exo/window_properties.h
+++ b/components/exo/window_properties.h
@@ -16,6 +16,10 @@
 // "org.chromium.lacros.<window-id>" for Lacros browser shell surfaces.
 extern const ui::ClassProperty<std::string*>* const kApplicationIdKey;
 
+// Whether Restore and Maximize should exit full screen for this window.
+// Currently only set to true for Lacros windows.
+extern const ui::ClassProperty<bool>* const kRestoreOrMaximizeExitsFullscreen;
+
 }  // namespace exo
 
 #endif  // COMPONENTS_EXO_WINDOW_PROPERTIES_H_
diff --git a/components/metrics/BUILD.gn b/components/metrics/BUILD.gn
index 8c36b62d..90c21ce4 100644
--- a/components/metrics/BUILD.gn
+++ b/components/metrics/BUILD.gn
@@ -156,6 +156,8 @@
   if (is_linux || is_chromeos) {
     sources += [
       "drive_metrics_provider_linux.cc",
+      "psi_memory_parser.h",
+      "psi_memory_parser_linux.cc",
       "system_memory_stats_recorder_linux.cc",
     ]
   }
@@ -513,7 +515,10 @@
   }
 
   if (is_linux || is_chromeos) {
-    sources += [ "serialization/serialization_utils_unittest.cc" ]
+    sources += [
+      "psi_memory_parser_linux_unittest.cc",
+      "serialization/serialization_utils_unittest.cc",
+    ]
     deps += [ ":serialization" ]
   }
 
diff --git a/components/metrics/psi_memory_parser.h b/components/metrics/psi_memory_parser.h
new file mode 100644
index 0000000..c1384dd
--- /dev/null
+++ b/components/metrics/psi_memory_parser.h
@@ -0,0 +1,114 @@
+// 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_METRICS_PSI_MEMORY_PARSER_H_
+#define COMPONENTS_METRICS_PSI_MEMORY_PARSER_H_
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/strings/string_piece.h"
+
+namespace metrics {
+
+// Items in internal are - as the name implies - NOT for outside consumption.
+// Defined here to allow access to unit test.
+namespace internal {
+
+// Finds the bounds for a substring of |content| which is sandwiched between
+// the given |prefix| and |suffix| indices. Search only considers
+// the portion of the string starting from |search_start|.
+// Returns false if the prefix and/or suffix are not found, true otherwise.
+// |start| and |end| are output parameters populated with the indices
+// for the middle string.
+bool FindMiddleString(const base::StringPiece& content,
+                      size_t search_start,
+                      const base::StringPiece& prefix,
+                      const base::StringPiece& suffix,
+                      size_t* start,
+                      size_t* end);
+
+}  // namespace internal
+
+// Values as logged in the histogram for memory pressure.
+constexpr int kMemPressureMin = 1;  // As 0 is for underflow.
+constexpr int kMemPressureExclusiveMax = 10000;
+constexpr int kMemPressureHistogramBuckets = 100;
+
+// Enumeration representing success and various failure modes for parsing PSI
+// memory data. These values are persisted to logs. Entries should not be
+// renumbered and numeric values should never be reused.
+enum class ParsePSIMemStatus {
+  kSuccess,
+  kReadFileFailed,
+  kUnexpectedDataFormat,
+  kInvalidMetricFormat,
+  kParsePSIValueFailed,
+  // Magic constant used by the histogram macros.
+  kMaxValue = kParsePSIValueFailed,
+};
+
+// PSIMemoryParser has logic to parse results from /proc/memory/pressure
+// in Linux, which can be used for memory pressure metrics.
+class PSIMemoryParser {
+ public:
+  explicit PSIMemoryParser(uint32_t period);
+  ~PSIMemoryParser();
+
+  // Parses PSI memory pressure from  |content|, for the currently configured
+  // metrics period (10, 60 or 300 seconds).
+  // The some and full values are output to |metricSome| and |metricFull|,
+  // respectively.
+  // Returns status of the parse operation - ParsePSIMemStatus::kSuccess
+  // or error code otherwise.
+  ParsePSIMemStatus ParseMetrics(const base::StringPiece& content,
+                                 int* metric_some,
+                                 int* metric_full);
+
+  // Raw buffer overload
+  ParsePSIMemStatus ParseMetrics(const uint8_t* content,
+                                 uint32_t len,
+                                 int* metric_some,
+                                 int* metric_full);
+
+  uint32_t GetPeriod() const;
+  void LogParseStatus(ParsePSIMemStatus stat);
+
+  PSIMemoryParser(const PSIMemoryParser&) = delete;
+  PSIMemoryParser& operator=(const PSIMemoryParser&) = delete;
+  PSIMemoryParser() = delete;
+
+ private:
+  // Friend it so it can see private members for testing
+  friend class PSIMemoryParserTest;
+  FRIEND_TEST_ALL_PREFIXES(PSIMemoryParserTest, CustomInterval);
+  FRIEND_TEST_ALL_PREFIXES(PSIMemoryParserTest, InvalidInterval);
+  FRIEND_TEST_ALL_PREFIXES(PSIMemoryParserTest, InternalsA);
+  FRIEND_TEST_ALL_PREFIXES(PSIMemoryParserTest, InternalsB);
+  FRIEND_TEST_ALL_PREFIXES(PSIMemoryParserTest, InternalsC);
+  FRIEND_TEST_ALL_PREFIXES(PSIMemoryParserTest, InternalsD);
+  FRIEND_TEST_ALL_PREFIXES(PSIMemoryParserTest, InternalsE);
+
+  ParsePSIMemStatus ParseMetricsInternal(const std::string& content,
+                                         int* metric_some,
+                                         int* metric_full);
+
+  // Retrieves one metric value from |content|, for the currently configured
+  // metrics category (10, 60 or 300 seconds).
+  // Only considers the substring between |start| (inclusive) and |end|
+  // (exclusive).
+  // Returns the floating-point string representation converted into an integer
+  // which has the value multiplied by 100 - (10.20 = 1020), for
+  // histogram usage.
+  int GetMetricValue(const base::StringPiece& content,
+                     size_t start,
+                     size_t end);
+
+  std::string metric_prefix_;
+  uint32_t period_;
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_PSI_MEMORY_PARSER_H_
diff --git a/components/metrics/psi_memory_parser_linux.cc b/components/metrics/psi_memory_parser_linux.cc
new file mode 100644
index 0000000..f5fddb0
--- /dev/null
+++ b/components/metrics/psi_memory_parser_linux.cc
@@ -0,0 +1,185 @@
+// 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/metrics/psi_memory_parser.h"
+
+#include <stddef.h>
+
+#include <cinttypes>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "components/metrics/metrics_log_store.h"
+
+namespace metrics {
+
+namespace {
+
+// Periods supported by standard Linux PSI metricvs.
+constexpr uint32_t kMinCollectionInterval = 10;
+constexpr uint32_t kMidCollectionInterval = 60;
+constexpr uint32_t kMaxCollectionInterval = 300;
+
+constexpr uint32_t kDefaultCollectionInterval = kMinCollectionInterval;
+
+// Name of the histogram that represents the success and various failure modes
+// for parsing PSI memory data.
+const char kParsePSIMemoryHistogramName[] = "ChromeOS.CWP.ParsePSIMemory";
+
+constexpr base::StringPiece kContentPrefixSome = "some";
+constexpr base::StringPiece kContentPrefixFull = "full";
+constexpr base::StringPiece kContentTerminator = " total=";
+constexpr base::StringPiece kMetricTerminator = " ";
+
+const char kMetricPrefixFormat[] = "avg%d=";
+
+}  // namespace
+
+PSIMemoryParser::PSIMemoryParser(uint32_t period)
+    : period_(kDefaultCollectionInterval) {
+  if (period == kMinCollectionInterval || period == kMidCollectionInterval ||
+      period == kMaxCollectionInterval) {
+    period_ = period;
+  } else {
+    LOG(WARNING) << "Ignoring invalid interval [" << period << "]";
+  }
+
+  metric_prefix_ = base::StringPrintf(kMetricPrefixFormat, period_);
+}
+
+PSIMemoryParser::~PSIMemoryParser() = default;
+
+uint32_t PSIMemoryParser::GetPeriod() const {
+  return period_;
+}
+
+int PSIMemoryParser::GetMetricValue(const base::StringPiece& content,
+                                    size_t start,
+                                    size_t end) {
+  size_t value_start;
+  size_t value_end;
+  if (!internal::FindMiddleString(content, start, metric_prefix_,
+                                  kMetricTerminator, &value_start,
+                                  &value_end)) {
+    return -1;
+  }
+  if (value_end > end) {
+    return -1;  // Out of bounds of the search area.
+  }
+
+  double n;
+  const base::StringPiece metric_value_text =
+      content.substr(value_start, value_end - value_start);
+  if (!base::StringToDouble(metric_value_text, &n)) {
+    return -1;  // Unable to convert string to number
+  }
+
+  // Want to multiply by 100, but to avoid integer truncation,
+  // do best-effort rounding.
+  const int preround = static_cast<int>(n * 1000);
+  return (preround + 5) / 10;
+}
+
+void PSIMemoryParser::LogParseStatus(ParsePSIMemStatus stat) {
+  constexpr int statCeiling =
+      static_cast<int>(ParsePSIMemStatus::kMaxValue) + 1;
+  base::UmaHistogramExactLinear(kParsePSIMemoryHistogramName,
+                                static_cast<int>(stat), statCeiling);
+}
+
+ParsePSIMemStatus PSIMemoryParser::ParseMetrics(
+    const base::StringPiece& content,
+    int* metric_some,
+    int* metric_full) {
+  size_t str_some_start;
+  size_t str_some_end;
+  size_t str_full_start;
+  size_t str_full_end;
+
+  // Example of content:
+  //  some avg10=0.00 avg60=0.00 avg300=0.00 total=417963
+  //  full avg10=0.00 avg60=0.00 avg300=0.00 total=205933
+  // we will pick one of the columns depending on the colleciton period set
+
+  DCHECK_NE(metric_some, nullptr);
+  DCHECK_NE(metric_full, nullptr);
+
+  if (!internal::FindMiddleString(content, 0, kContentPrefixSome,
+                                  kContentTerminator, &str_some_start,
+                                  &str_some_end)) {
+    return ParsePSIMemStatus::kUnexpectedDataFormat;
+  }
+
+  if (!internal::FindMiddleString(content,
+                                  str_some_end + kContentTerminator.length(),
+                                  kContentPrefixFull, kContentTerminator,
+                                  &str_full_start, &str_full_end)) {
+    return ParsePSIMemStatus::kUnexpectedDataFormat;
+  }
+
+  int compute_some = GetMetricValue(content, str_some_start, str_some_end);
+  if (compute_some < 0) {
+    return ParsePSIMemStatus::kInvalidMetricFormat;
+  }
+
+  int compute_full = GetMetricValue(content, str_full_start, str_full_end);
+  if (compute_full < 0) {
+    return ParsePSIMemStatus::kInvalidMetricFormat;
+  }
+
+  *metric_some = compute_some;
+  *metric_full = compute_full;
+
+  return ParsePSIMemStatus::kSuccess;
+}
+
+ParsePSIMemStatus PSIMemoryParser::ParseMetrics(const uint8_t* content,
+                                                uint32_t len,
+                                                int* metric_some,
+                                                int* metric_full) {
+  // The cast below is admittedly sneaky, but inherently safe because
+  // we are translating a const pointer into another const pointer,
+  // and the data sizes of the pointed object are the same.
+  const char* string_content = reinterpret_cast<const char*>(content);
+
+  return ParseMetrics(base::StringPiece(string_content, len), metric_some,
+                      metric_full);
+}
+
+namespace internal {
+
+bool FindMiddleString(const base::StringPiece& content,
+                      size_t search_start,
+                      const base::StringPiece& prefix,
+                      const base::StringPiece& suffix,
+                      size_t* start,
+                      size_t* end) {
+  DCHECK_NE(start, nullptr);
+  DCHECK_NE(end, nullptr);
+
+  size_t compute_start = content.find(prefix, search_start);
+  if (compute_start == std::string::npos) {
+    return false;
+  }
+  compute_start += prefix.length();
+
+  size_t compute_end = content.find(suffix, compute_start);
+  if (compute_end == std::string::npos) {
+    return false;
+  }
+
+  *start = compute_start;
+  *end = compute_end;
+
+  return true;
+}
+
+}  // namespace internal
+
+}  // namespace metrics
diff --git a/components/metrics/psi_memory_parser_linux_unittest.cc b/components/metrics/psi_memory_parser_linux_unittest.cc
new file mode 100644
index 0000000..0536975f
--- /dev/null
+++ b/components/metrics/psi_memory_parser_linux_unittest.cc
@@ -0,0 +1,163 @@
+// 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/metrics/psi_memory_parser.h"
+
+#include <memory>
+
+#include "base/files/file_util.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+// Just as the kernel outputs.
+const char kFileContents1[] =
+    "some avg10=23.10 avg60=5.06 avg300=15.10 total=417963\n"
+    "full avg10=9.00 avg60=19.20 avg300=3.23 total=205933\n";
+
+// Number of decimals not consistent, slightly malformed - but acceptable.
+const char kFileContents2[] =
+    "some avg10=24 avg60=5.06 avg300=15.10 total=417963\n"
+    "full avg10=9.2 avg60=19.20 avg300=3.23 total=205933\n";
+
+}  // namespace
+
+class PSIMemoryParserTest : public testing::Test {
+ public:
+  PSIMemoryParserTest() = default;
+  ~PSIMemoryParserTest() override = default;
+
+  void Init(uint32_t period) {
+    cit_ = std::make_unique<PSIMemoryParser>(period);
+  }
+
+  uint32_t GetPeriod() { return cit_->GetPeriod(); }
+  base::HistogramTester& Histograms() { return histogram_tester_; }
+  std::unique_ptr<PSIMemoryParser>& Cit() { return cit_; }
+  const std::string& GetMetricPrefix() { return cit_->metric_prefix_; }
+
+  void KillCit() { cit_.reset(); }
+
+ private:
+  std::unique_ptr<PSIMemoryParser> cit_;
+  base::HistogramTester histogram_tester_;
+};
+
+TEST_F(PSIMemoryParserTest, CustomInterval) {
+  Init(60u);
+
+  EXPECT_EQ(60u, GetPeriod());
+}
+
+TEST_F(PSIMemoryParserTest, InvalidInterval) {
+  Init(15u);
+
+  EXPECT_EQ(10u, GetPeriod());
+}
+
+TEST_F(PSIMemoryParserTest, InternalsA) {
+  Init(10u);
+
+  std::string testContent1 = "prefix" + GetMetricPrefix() + "9.37 suffix";
+  EXPECT_EQ(10u, GetPeriod());
+
+  size_t s = 0;
+  size_t e = 0;
+
+  EXPECT_EQ(false, internal::FindMiddleString(testContent1, 0, "nothere",
+                                              "suffix", &s, &e));
+
+  EXPECT_EQ(false, internal::FindMiddleString(testContent1, 0, "prefix",
+                                              "notthere", &s, &e));
+
+  EXPECT_EQ(true, internal::FindMiddleString(testContent1, 0, "prefix",
+                                             "suffix", &s, &e));
+  EXPECT_EQ(6u, s);
+  EXPECT_EQ(17u, e);
+
+  EXPECT_EQ(937, Cit()->GetMetricValue(testContent1, s, e));
+
+  std::string testContent2 = "extra " + testContent1;
+  EXPECT_EQ(true, internal::FindMiddleString(testContent2, 0, "prefix",
+                                             "suffix", &s, &e));
+  EXPECT_EQ(12u, s);
+  EXPECT_EQ(23u, e);
+
+  EXPECT_EQ(937, Cit()->GetMetricValue(testContent2, s, e));
+}
+
+TEST_F(PSIMemoryParserTest, InternalsB) {
+  Init(300);
+
+  int msome;
+  int mfull;
+  ParsePSIMemStatus stat;
+
+  stat = Cit()->ParseMetrics(kFileContents1, &msome, &mfull);
+
+  EXPECT_EQ(ParsePSIMemStatus::kSuccess, stat);
+  EXPECT_EQ(1510, msome);
+  EXPECT_EQ(323, mfull);
+}
+
+TEST_F(PSIMemoryParserTest, InternalsC) {
+  Init(60);
+
+  int msome;
+  int mfull;
+  ParsePSIMemStatus stat;
+
+  stat = Cit()->ParseMetrics(kFileContents1, &msome, &mfull);
+
+  EXPECT_EQ(ParsePSIMemStatus::kSuccess, stat);
+  EXPECT_EQ(506, msome);
+  EXPECT_EQ(1920, mfull);
+}
+
+TEST_F(PSIMemoryParserTest, InternalsD) {
+  Init(10);
+
+  int msome;
+  int mfull;
+  ParsePSIMemStatus stat;
+
+  stat = Cit()->ParseMetrics(kFileContents1, &msome, &mfull);
+
+  EXPECT_EQ(ParsePSIMemStatus::kSuccess, stat);
+  EXPECT_EQ(2310, msome);
+  EXPECT_EQ(900, mfull);
+}
+
+TEST_F(PSIMemoryParserTest, InternalsE) {
+  Init(10);
+
+  int msome;
+  int mfull;
+  ParsePSIMemStatus stat;
+
+  stat = Cit()->ParseMetrics(kFileContents2, &msome, &mfull);
+
+  EXPECT_EQ(ParsePSIMemStatus::kSuccess, stat);
+  EXPECT_EQ(2400, msome);
+  EXPECT_EQ(920, mfull);
+}
+
+TEST_F(PSIMemoryParserTest, ParseResultCounter) {
+  Init(10);
+
+  Cit()->LogParseStatus(ParsePSIMemStatus::kSuccess);
+  Cit()->LogParseStatus(ParsePSIMemStatus::kInvalidMetricFormat);
+  Cit()->LogParseStatus(ParsePSIMemStatus::kInvalidMetricFormat);
+
+  Histograms().ExpectBucketCount("ChromeOS.CWP.ParsePSIMemory",
+                                 ParsePSIMemStatus::kSuccess, 1);
+  Histograms().ExpectBucketCount("ChromeOS.CWP.ParsePSIMemory",
+                                 ParsePSIMemStatus::kInvalidMetricFormat, 2);
+}
+
+}  // namespace metrics
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc
index 65e8a3d..aa0d4ee 100644
--- a/components/password_manager/core/browser/login_database.cc
+++ b/components/password_manager/core/browser/login_database.cc
@@ -628,7 +628,7 @@
   return result;
 }
 
-#if defined(OS_MAC)
+#if defined(OS_MAC) || defined(OS_LINUX)
 // Fills |form| with necessary data required to be removed from the database
 // and returns it.
 PasswordForm GetFormForRemoval(sql::Statement& statement) {
@@ -1442,9 +1442,10 @@
 }
 
 DatabaseCleanupResult LoginDatabase::DeleteUndecryptableLogins() {
-#if defined(OS_MAC)
+#if defined(OS_MAC) || defined(OS_LINUX)
   TRACE_EVENT0("passwords", "LoginDatabase::DeleteUndecryptableLogins");
-  // If the Keychain is unavailable, don't delete any logins.
+  // If the Keychain in MacOS or the real secret key in Linux is unavailable,
+  // don't delete any logins.
   if (!OSCrypt::IsEncryptionAvailable()) {
     metrics_util::LogDeleteUndecryptableLoginsReturnValue(
         metrics_util::DeleteCorruptedPasswordsResult::kEncryptionUnavailable);
diff --git a/components/password_manager/core/browser/login_database_unittest.cc b/components/password_manager/core/browser/login_database_unittest.cc
index d9c3408..5ffb296 100644
--- a/components/password_manager/core/browser/login_database_unittest.cc
+++ b/components/password_manager/core/browser/login_database_unittest.cc
@@ -2078,8 +2078,9 @@
   base::HistogramTester histogram_tester;
   ASSERT_TRUE(db.Init());
 
-#if defined(OS_MAC)
+#if defined(OS_MAC) || (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMECAST))
   // Make sure that we can't get any logins when database is corrupted.
+  // Disabling the checks in chromecast because encryption is unavailable.
   std::vector<std::unique_ptr<PasswordForm>> result;
   EXPECT_FALSE(db.GetAutofillableLogins(&result));
   EXPECT_TRUE(result.empty());
@@ -2095,12 +2096,15 @@
   EXPECT_THAT(result, IsEmpty());
 
   RunUntilIdle();
+#elif (defined(OS_LINUX) && BUILDFLAG(IS_CHROMECAST))
+  EXPECT_EQ(DatabaseCleanupResult::kEncryptionUnavailable,
+            db.DeleteUndecryptableLogins());
 #else
   EXPECT_EQ(DatabaseCleanupResult::kSuccess, db.DeleteUndecryptableLogins());
 #endif
 
 // Check histograms.
-#if defined(OS_MAC)
+#if defined(OS_MAC) || (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMECAST))
   histogram_tester.ExpectUniqueSample("PasswordManager.CleanedUpPasswords", 2,
                                       1);
   histogram_tester.ExpectUniqueSample(
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge.cc b/components/password_manager/core/browser/sync/password_sync_bridge.cc
index a1fba1e..3a3b928 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge.cc
+++ b/components/password_manager/core/browser/sync/password_sync_bridge.cc
@@ -11,6 +11,7 @@
 #include "base/callback.h"
 #include "base/check_op.h"
 #include "base/containers/flat_map.h"
+#include "base/feature_list.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
@@ -180,6 +181,9 @@
   // Delete the local undecryptable copy when this is MacOS only.
 #if defined(OS_MAC)
   return true;
+#elif defined(OS_LINUX)
+  return base::FeatureList::IsEnabled(
+      features::kSyncUndecryptablePasswordsLinux);
 #else
   return false;
 #endif
@@ -314,7 +318,6 @@
   // 3. |F| exists in both the local and the remote models --> both versions
   //    should be merged by accepting the most recently created one, and update
   //    local and remote models accordingly.
-
   base::AutoReset<bool> processing_changes(&is_processing_remote_sync_changes_,
                                            true);
 
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge.h b/components/password_manager/core/browser/sync/password_sync_bridge.h
index e6e88403..b549e95 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge.h
+++ b/components/password_manager/core/browser/sync/password_sync_bridge.h
@@ -68,11 +68,11 @@
       const sync_pb::PasswordSpecificsData& password_data);
 
  private:
-  // On MacOS it may happen that some passwords cannot be decrypted due to
-  // modification of encryption key in Keychain (https://crbug.com/730625). This
-  // method deletes those logins from the store. So during merge, the data in
-  // sync will be added to the password store. This should be called during
-  // MergeSyncData().
+  // On MacOS or Linux it may happen that some passwords cannot be decrypted due
+  // to modification of encryption key in Keychain or Keyring
+  // (https://crbug.com/730625). This method deletes those logins from the
+  // store. So during merge, the data in sync will be added to the password
+  // store. This should be called during MergeSyncData().
   absl::optional<syncer::ModelError> CleanupPasswordStore();
 
   // Retrieves the storage keys of all unsynced passwords in the store.
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc b/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
index dc88bc1..4d00f22 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
+++ b/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
@@ -11,14 +11,17 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "base/feature_list.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "components/password_manager/core/browser/insecure_credentials_table.h"
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/password_store_sync.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "components/sync/base/client_tag_hash.h"
 #include "components/sync/model/data_batch.h"
 #include "components/sync/model/entity_change.h"
@@ -960,10 +963,15 @@
                             mock_password_store_sync(), base::DoNothing());
 }
 
-#if defined(OS_MAC)
+#if defined(OS_MAC) || defined(OS_LINUX)
 // Tests that in case ReadAllLogins() during initial merge returns encryption
 // service failure, the bridge would try to do a DB clean up.
 TEST_F(PasswordSyncBridgeTest, ShouldDeleteUndecryptableLoginsDuringMerge) {
+#if defined(OS_LINUX)
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kSyncUndecryptablePasswordsLinux);
+#endif
+
   ON_CALL(*mock_password_store_sync(), DeleteUndecryptableLogins())
       .WillByDefault(Return(DatabaseCleanupResult::kSuccess));
 
diff --git a/components/password_manager/core/common/password_manager_features.cc b/components/password_manager/core/common/password_manager_features.cc
index cd5b413a..a30673a5 100644
--- a/components/password_manager/core/common/password_manager_features.cc
+++ b/components/password_manager/core/common/password_manager_features.cc
@@ -134,6 +134,13 @@
 const base::Feature kSupportForAddPasswordsInSettings = {
     "SupportForAddPasswordsInSettings", base::FEATURE_DISABLED_BY_DEFAULT};
 
+#if defined(OS_LINUX)
+// When enabled, all undecryptable passwords are deleted from the local database
+// during initial sync flow.
+const base::Feature kSyncUndecryptablePasswordsLinux = {
+    "SyncUndecryptablePasswordsLinux", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
 // Treat heuritistics to find new password fields as reliable. This enables
 // password generation on more forms, but could lead to false positives.
 const base::Feature kTreatNewPasswordHeuristicsAsReliable = {
diff --git a/components/password_manager/core/common/password_manager_features.h b/components/password_manager/core/common/password_manager_features.h
index 8d183d8e..74618e62 100644
--- a/components/password_manager/core/common/password_manager_features.h
+++ b/components/password_manager/core/common/password_manager_features.h
@@ -41,6 +41,9 @@
 extern const base::Feature kReparseServerPredictionsFollowingFormChange;
 extern const base::Feature kSecondaryServerFieldPredictions;
 extern const base::Feature kSupportForAddPasswordsInSettings;
+#if defined(OS_LINUX)
+extern const base::Feature kSyncUndecryptablePasswordsLinux;
+#endif
 extern const base::Feature kTreatNewPasswordHeuristicsAsReliable;
 #if defined(OS_ANDROID)
 extern const base::Feature kUnifiedCredentialManagerDryRun;
diff --git a/components/policy/core/browser/webui/policy_status_provider.cc b/components/policy/core/browser/webui/policy_status_provider.cc
index 30b795e..dec7a88 100644
--- a/components/policy/core/browser/webui/policy_status_provider.cc
+++ b/components/policy/core/browser/webui/policy_status_provider.cc
@@ -63,6 +63,7 @@
     callback_.Run();
 }
 
+// static
 void PolicyStatusProvider::GetStatusFromCore(const CloudPolicyCore* core,
                                              base::DictionaryValue* dict) {
   const CloudPolicyStore* store = core->store();
@@ -73,17 +74,7 @@
   const std::u16string status = GetPolicyStatusFromStore(store, client);
 
   const em::PolicyData* policy = store->policy();
-  std::string client_id = policy ? policy->device_id() : std::string();
-  std::string username = policy ? policy->username() : std::string();
-
-  if (policy && policy->has_annotated_asset_id())
-    dict->SetString("assetId", policy->annotated_asset_id());
-  if (policy && policy->has_annotated_location())
-    dict->SetString("location", policy->annotated_location());
-  if (policy && policy->has_directory_api_id())
-    dict->SetString("directoryApiId", policy->directory_api_id());
-  if (policy && policy->has_gaia_id())
-    dict->SetString("gaiaId", policy->gaia_id());
+  GetStatusFromPolicyData(policy, dict);
 
   base::TimeDelta refresh_interval = base::Milliseconds(
       refresh_scheduler ? refresh_scheduler->GetActualRefreshDelay()
@@ -104,8 +95,6 @@
       "policiesPushAvailable",
       refresh_scheduler ? refresh_scheduler->invalidations_available() : false);
   dict->SetString("status", status);
-  dict->SetString("clientId", client_id);
-  dict->SetString("username", username);
   dict->SetString(
       "refreshInterval",
       ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION,
@@ -114,6 +103,26 @@
                   GetTimeSinceLastRefreshString(last_refresh_time));
 }
 
+// static
+void PolicyStatusProvider::GetStatusFromPolicyData(
+    const em::PolicyData* policy,
+    base::DictionaryValue* dict) {
+  std::string client_id = policy ? policy->device_id() : std::string();
+  std::string username = policy ? policy->username() : std::string();
+
+  if (policy && policy->has_annotated_asset_id())
+    dict->SetString("assetId", policy->annotated_asset_id());
+  if (policy && policy->has_annotated_location())
+    dict->SetString("location", policy->annotated_location());
+  if (policy && policy->has_directory_api_id())
+    dict->SetString("directoryApiId", policy->directory_api_id());
+  if (policy && policy->has_gaia_id())
+    dict->SetString("gaiaId", policy->gaia_id());
+
+  dict->SetString("clientId", client_id);
+  dict->SetString("username", username);
+}
+
 // CloudPolicyStore errors take precedence to show in the status message.
 // Other errors (such as transient policy fetching problems) get displayed
 // only if CloudPolicyStore is in STATUS_OK.
diff --git a/components/policy/core/browser/webui/policy_status_provider.h b/components/policy/core/browser/webui/policy_status_provider.h
index d0cb45592..0e6e97c 100644
--- a/components/policy/core/browser/webui/policy_status_provider.h
+++ b/components/policy/core/browser/webui/policy_status_provider.h
@@ -13,6 +13,10 @@
 class Time;
 }
 
+namespace enterprise_management {
+class PolicyData;
+}
+
 namespace policy {
 class CloudPolicyClient;
 class CloudPolicyCore;
@@ -37,6 +41,9 @@
 
   static void GetStatusFromCore(const CloudPolicyCore* core,
                                 base::DictionaryValue* dict);
+  static void GetStatusFromPolicyData(
+      const enterprise_management::PolicyData* policy,
+      base::DictionaryValue* dict);
 
  protected:
   void NotifyStatusChange();
diff --git a/components/policy/core/common/policy_loader_lacros.cc b/components/policy/core/common/policy_loader_lacros.cc
index e76ee56..40f5cdf 100644
--- a/components/policy/core/common/policy_loader_lacros.cc
+++ b/components/policy/core/common/policy_loader_lacros.cc
@@ -122,6 +122,7 @@
       per_profile_ == PolicyPerProfileFilter::kFalse) {
     *MainUserPolicyDataStorage() = *validator.policy_data();
   }
+  policy_data_ = std::move(validator.policy_data());
 
   return bundle;
 }
@@ -133,6 +134,13 @@
   Reload(true);
 }
 
+enterprise_management::PolicyData* PolicyLoaderLacros::GetPolicyData() {
+  if (!policy_fetch_response_ || !policy_data_)
+    return nullptr;
+
+  return policy_data_.get();
+}
+
 // static
 bool PolicyLoaderLacros::IsMainUserManaged() {
   return g_is_main_user_managed_;
diff --git a/components/policy/core/common/policy_loader_lacros.h b/components/policy/core/common/policy_loader_lacros.h
index 5e64f3f..706baf5 100644
--- a/components/policy/core/common/policy_loader_lacros.h
+++ b/components/policy/core/common/policy_loader_lacros.h
@@ -47,6 +47,10 @@
   // that is returned.
   std::unique_ptr<PolicyBundle> Load() override;
 
+  // Return the policy data object as received from Ash. Returns nullptr if
+  // initial load was not done yet.
+  enterprise_management::PolicyData* GetPolicyData();
+
   // LacrosChromeServiceDelegateImpl::Observer implementation.
   // Update and reload the policy with new data.
   void OnPolicyUpdated(
@@ -73,6 +77,9 @@
   // Serialized blob of PolicyFetchResponse object received from the server.
   absl::optional<std::vector<uint8_t>> policy_fetch_response_;
 
+  // The parsed policy objects received from Ash.
+  std::unique_ptr<enterprise_management::PolicyData> policy_data_;
+
   // Checks that the method is called on the right sequence.
   SEQUENCE_CHECKER(sequence_checker_);
 };
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 1ad38b5..6ab0d6c 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -29615,7 +29615,7 @@
     'DevicePciPeripheralDataAccessEnabled': 'device_pci_peripheral_data_access_enabled_v2.enabled',
     'DeviceRestrictedManagedGuestSessionEnabled': 'device_restricted_managed_guest_session_enabled.enabled',
     'DeviceLoginScreenPromptOnMultipleMatchingCertificates': 'login_screen_prompt_on_multiple_matching_certificates.value',
-    'KioskCRXManifestUpdateURLIgnored': 'kiosk_crx_manifest_update_url_ignored',
+    'KioskCRXManifestUpdateURLIgnored': 'kiosk_crx_manifest_update_url_ignored.value',
     'DeviceI18nShortcutsEnabled': 'device_i18n_shortcuts_enabled.enabled',
     'ChromadToCloudMigrationEnabled': 'chromad_to_cloud_migration_enabled.value',
     'DeviceLoginScreenWebUILazyLoading': 'login_web_ui_lazy_loading.enabled',
diff --git a/components/sync/protocol/proto_enum_conversions.cc b/components/sync/protocol/proto_enum_conversions.cc
index 71f77ee..a702f3f8 100644
--- a/components/sync/protocol/proto_enum_conversions.cc
+++ b/components/sync/protocol/proto_enum_conversions.cc
@@ -682,6 +682,19 @@
   return "";
 }
 
+const char* ProtoEnumToString(
+    sync_pb::WebauthnCredentialSpecifics::PaymentsSupport payments_support) {
+  ASSERT_ENUM_BOUNDS(sync_pb::WebauthnCredentialSpecifics, PaymentsSupport,
+                     NONE, FIRST_AND_THIRD_PARTY);
+  switch (payments_support) {
+    ENUM_CASE(sync_pb::WebauthnCredentialSpecifics, NONE);
+    ENUM_CASE(sync_pb::WebauthnCredentialSpecifics, FIRST_PARTY);
+    ENUM_CASE(sync_pb::WebauthnCredentialSpecifics, FIRST_AND_THIRD_PARTY);
+  }
+  NOTREACHED();
+  return "";
+}
+
 #undef ASSERT_ENUM_BOUNDS
 #undef ENUM_CASE
 
diff --git a/components/sync/protocol/proto_enum_conversions.h b/components/sync/protocol/proto_enum_conversions.h
index 9ae07b1..ac1ef6d 100644
--- a/components/sync/protocol/proto_enum_conversions.h
+++ b/components/sync/protocol/proto_enum_conversions.h
@@ -152,6 +152,10 @@
 const char* ProtoEnumToString(
     sync_pb::UserConsentTypes::AssistantActivityControlConsent::SettingType
         setting_type);
+
+const char* ProtoEnumToString(
+    sync_pb::WebauthnCredentialSpecifics::PaymentsSupport payments_support);
+
 }  // namespace syncer
 
 #endif  // COMPONENTS_SYNC_PROTOCOL_PROTO_ENUM_CONVERSIONS_H_
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index c129c1e..b922537 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -674,6 +674,7 @@
   VISIT(creation_time);
   VISIT(user_name);
   VISIT(user_display_name);
+  VISIT_ENUM(payments_support);
   // |private_key| is deliberately omitted to avoid including sensitive
   // information in debugging output, which might be included in bug reports
   // etc.
diff --git a/components/sync/protocol/webauthn_credential_specifics.proto b/components/sync/protocol/webauthn_credential_specifics.proto
index a258756..1838a3d3 100644
--- a/components/sync/protocol/webauthn_credential_specifics.proto
+++ b/components/sync/protocol/webauthn_credential_specifics.proto
@@ -115,6 +115,21 @@
   // different key and then stored in a future encrypted_private_key field.
   optional bytes private_key = 9;
 
+  // Credentials may optionally be used as part of Web Payments Secure Payment
+  // Confirmation[1]. This is opt-in at creation time.
+  //
+  // [1] https://www.w3.org/TR/secure-payment-confirmation/
+  enum PaymentsSupport {
+    // Indicates that this credential cannot be used to authenticate a payment.
+    NONE = 0;
+    // Indicates that the credential may authenticate a payment, but only for
+    // the same RP ID (i.e. same site) that created it.
+    FIRST_PARTY = 1;
+    // Indicates that the credential may authenticate a payment on any site.
+    FIRST_AND_THIRD_PARTY = 2;
+  };
+  optional PaymentsSupport payments_support = 10;
+
   // Support for CTAP 2.1 may require the addition of fields such as cred_blob
   // and hmac_secret_key in the future, but they are not currently supported.
 }
diff --git a/content/browser/back_forward_cache_features_browsertest.cc b/content/browser/back_forward_cache_features_browsertest.cc
index 62aea25..388e5071 100644
--- a/content/browser/back_forward_cache_features_browsertest.cc
+++ b/content/browser/back_forward_cache_features_browsertest.cc
@@ -1238,9 +1238,8 @@
       blink::scheduler::WebSchedulerTrackedFeature::kWebOTPService, FROM_HERE);
 }
 
-// crbug.com/1090223
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
-                       DISABLED_DoesNotCachePaymentManager) {
+                       DoesNotCachePaymentManager) {
   ASSERT_TRUE(CreateHttpsServer()->Start());
 
   // 1) Navigate to a page which includes PaymentManager functionality. Note
diff --git a/content/browser/direct_sockets/direct_sockets_open_browsertest.cc b/content/browser/direct_sockets/direct_sockets_open_browsertest.cc
index 89e22ed2..2984b59e 100644
--- a/content/browser/direct_sockets/direct_sockets_open_browsertest.cc
+++ b/content/browser/direct_sockets/direct_sockets_open_browsertest.cc
@@ -476,8 +476,9 @@
   EXPECT_EQ(expected_result, EvalJs(shell(), script));
 }
 
+// TODO(https://crbug.com/1282060): This test is flaky.
 IN_PROC_BROWSER_TEST_F(DirectSocketsOpenBrowserTest,
-                       OpenTcp_TransientActivation) {
+                       DISABLED_OpenTcp_TransientActivation) {
   EXPECT_TRUE(NavigateToURL(shell(), GetTestOpenPageURL()));
 
   MockNetworkContext mock_network_context(net::OK);
@@ -674,8 +675,9 @@
   EXPECT_EQ("openUdp succeeded", EvalJs(shell(), script));
 }
 
+// TODO(https://crbug.com/1282060): This test is flaky.
 IN_PROC_BROWSER_TEST_F(DirectSocketsOpenBrowserTest,
-                       OpenUdp_TransientActivation) {
+                       DISABLED_OpenUdp_TransientActivation) {
   EXPECT_TRUE(NavigateToURL(shell(), GetTestOpenPageURL()));
 
   const std::string script = base::StringPrintf(
diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc
index 17628b9..72a1b24 100644
--- a/content/browser/renderer_host/media/media_stream_manager.cc
+++ b/content/browser/renderer_host/media/media_stream_manager.cc
@@ -1249,18 +1249,12 @@
       if (request->state(type) == MEDIA_REQUEST_STATE_DONE)
         CloseDevice(type, session_id);
 
-      if (request->ui_proxy) {
-        const DesktopMediaID media_id = DesktopMediaID::Parse(device_it->id);
-        if (!media_id.is_null())
-          request->ui_proxy->OnDeviceStopped(request_it->first, media_id);
-      }
-
       device_it = devices->erase(device_it);
     }
 
     // If this request doesn't have any active devices after a device
     // has been stopped above, remove the request. Note that the request is
-    // only deleted if a device as been removed from |devices|.
+    // only deleted if a device has been removed from |devices|.
     if (devices->empty()) {
       std::string label = request_it->first;
       ++request_it;
@@ -1295,6 +1289,11 @@
             request->device_stopped_cb) {
           request->device_stopped_cb.Run(labeled_request.first, device);
         }
+        if (request->ui_proxy) {
+          const DesktopMediaID media_id = DesktopMediaID::Parse(device.id);
+          if (!media_id.is_null())
+            request->ui_proxy->OnDeviceStopped(labeled_request.first, media_id);
+        }
       }
     }
   }
@@ -2598,6 +2597,15 @@
   if (!request)
     return;
 
+  if (request->ui_proxy) {
+    for (const MediaStreamDevice& device : request->devices) {
+      const DesktopMediaID old_media_id = DesktopMediaID::Parse(device.id);
+      if (!old_media_id.is_null()) {
+        request->ui_proxy->OnDeviceStopped(label, old_media_id);
+      }
+    }
+  }
+
   SendLogMessage(base::StringPrintf(
       "ChangeMediaStreamSourceFromBrowser({label=%s})", label.c_str()));
 
diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h
index 4524061..87de9f3 100644
--- a/content/browser/renderer_host/media/media_stream_manager.h
+++ b/content/browser/renderer_host/media/media_stream_manager.h
@@ -442,11 +442,11 @@
   void HandleRequestDone(const std::string& label, DeviceRequest* request);
   // Stop the use of the device associated with |session_id| of type |type| in
   // all |requests_|. The device is removed from the request. If a request
-  /// doesn't use any devices as a consequence, the request is deleted.
+  // doesn't use any devices as a consequence, the request is deleted.
   void StopDevice(blink::mojom::MediaStreamType type,
                   const base::UnguessableToken& session_id);
-  // Calls the correct capture manager and close the device with |session_id|.
-  // All requests that uses the device are updated.
+  // Calls the correct capture manager and closes the device with |session_id|.
+  // All requests that use the device are updated.
   void CloseDevice(blink::mojom::MediaStreamType type,
                    const base::UnguessableToken& session_id);
   // Returns true if a request for devices has been completed and the devices
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 02440a91..7efec132 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -2083,8 +2083,7 @@
   // starting SiteInstance.
   starting_site_instance_ =
       frame_tree_node->current_frame_host()->GetSiteInstance();
-  site_info_ = GetSiteInfoForCommonParamsURL(
-      starting_site_instance_->GetWebExposedIsolationInfo());
+  site_info_ = GetSiteInfoForCommonParamsURL();
 
   // Compute the redirect chain.
   // TODO(clamy): Try to simplify this and have the redirects be part of
@@ -2623,8 +2622,7 @@
   RenderProcessHost* expected_process =
       site_instance->HasProcess() ? site_instance->GetProcess() : nullptr;
 
-  WillRedirectRequest(common_params_->referrer->url,
-                      ComputeWebExposedIsolationInfo(), expected_process);
+  WillRedirectRequest(common_params_->referrer->url, expected_process);
 }
 
 base::WeakPtr<NavigationRequest> NavigationRequest::GetWeakPtr() {
@@ -4745,14 +4743,12 @@
 }
 
 void NavigationRequest::UpdateSiteInfo(
-    const WebExposedIsolationInfo& web_exposed_isolation_info,
     RenderProcessHost* post_redirect_process) {
   int post_redirect_process_id = post_redirect_process
                                      ? post_redirect_process->GetID()
                                      : ChildProcessHost::kInvalidUniqueID;
 
-  SiteInfo new_site_info =
-      GetSiteInfoForCommonParamsURL(web_exposed_isolation_info);
+  SiteInfo new_site_info = GetSiteInfoForCommonParamsURL();
   if (new_site_info == site_info_ &&
       post_redirect_process_id == expected_render_process_host_id_) {
     return;
@@ -5481,12 +5477,11 @@
 
 void NavigationRequest::WillRedirectRequest(
     const GURL& new_referrer_url,
-    const WebExposedIsolationInfo& web_exposed_isolation_info,
     RenderProcessHost* post_redirect_process) {
   EnterChildTraceEvent("WillRedirectRequest", this, "url",
                        common_params_->url.possibly_invalid_spec());
   UpdateStateFollowingRedirect(new_referrer_url);
-  UpdateSiteInfo(web_exposed_isolation_info, post_redirect_process);
+  UpdateSiteInfo(post_redirect_process);
 
   if (IsSelfReferentialURL()) {
     SetState(CANCELING);
@@ -5653,13 +5648,8 @@
   }
 }
 
-SiteInfo NavigationRequest::GetSiteInfoForCommonParamsURL(
-    const WebExposedIsolationInfo& web_exposed_isolation_info) {
-  // We typically call this function when we don't yet have response headers, so
-  // the computed WebExposedIsolationInfo is not relevant. We override it with
-  // the value passed in.
+SiteInfo NavigationRequest::GetSiteInfoForCommonParamsURL() {
   UrlInfo url_info = GetUrlInfo();
-  url_info.web_exposed_isolation_info = web_exposed_isolation_info;
 
   // TODO(alexmos): Using |starting_site_instance_|'s IsolationContext may not
   // be correct for cross-BrowsingInstance redirects.
@@ -7255,7 +7245,8 @@
   console_messages_.clear();
 }
 
-WebExposedIsolationInfo NavigationRequest::ComputeWebExposedIsolationInfo() {
+absl::optional<WebExposedIsolationInfo>
+NavigationRequest::ComputeWebExposedIsolationInfo() {
   // If we are in an iframe, we inherit the isolation state of the top level
   // frame. This can be inferred from the main frame SiteInstance. Note that
   // Iframes have to pass COEP tests in |OnResponseStarted| before being loaded
@@ -7270,6 +7261,11 @@
         ->GetWebExposedIsolationInfo();
   }
 
+  // If we haven't yet received a definitive network response, it is too early
+  // to guess the isolation state.
+  if (state_ < WILL_PROCESS_RESPONSE)
+    return absl::nullopt;
+
   // We consider navigations to be cross-origin isolated if the response
   // asserts proper COOP and COEP headers.
   if (coop_status().current_coop().value !=
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 89db156..042fb6d 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -480,10 +480,7 @@
   // redirects. |post_redirect_process| is the renderer process that should
   // handle the navigation following the redirect if it can be handled by an
   // existing RenderProcessHost. Otherwise, it should be null.
-  // |web_exposed_isolation_info| is the new isolation info extracted from the
-  // redirect response.
-  void UpdateSiteInfo(const WebExposedIsolationInfo& web_exposed_isolation_info,
-                      RenderProcessHost* post_redirect_process);
+  void UpdateSiteInfo(RenderProcessHost* post_redirect_process);
 
   int nav_entry_id() const { return nav_entry_id_; }
 
@@ -1252,12 +1249,8 @@
   // no live process that can be used. In that case, a suitable renderer process
   // will be created at commit time.
   //
-  // |web_exposed_isolation_info| is the new isolation info extracted from the
-  // redirect response.
-  void WillRedirectRequest(
-      const GURL& new_referrer_url,
-      const WebExposedIsolationInfo& web_exposed_isolation_info,
-      RenderProcessHost* post_redirect_process);
+  void WillRedirectRequest(const GURL& new_referrer_url,
+                           RenderProcessHost* post_redirect_process);
 
   // Called when the URLRequest will fail.
   void WillFailRequest();
@@ -1286,8 +1279,7 @@
 
   // Helper function that computes the SiteInfo for |common_params_.url|.
   // Note: |site_info_| should only be updated with the result of this function.
-  SiteInfo GetSiteInfoForCommonParamsURL(
-      const WebExposedIsolationInfo& cross_origin_isolated_origin_status);
+  SiteInfo GetSiteInfoForCommonParamsURL();
 
   // Updates the state of the navigation handle after encountering a server
   // redirect.
@@ -1452,7 +1444,10 @@
 
   // Computes the web-exposed isolation information based on `coop_status_` and
   // current `frame_tree_node_` info.
-  WebExposedIsolationInfo ComputeWebExposedIsolationInfo();
+  // If the return result is nullopt, it means that the WebExposedIsolationInfo
+  // is not relevant or unknown. This can happen for example when we do not have
+  // a network response yet, or when going to an "about:blank" page.
+  absl::optional<WebExposedIsolationInfo> ComputeWebExposedIsolationInfo();
 
   // Never null. The pointee node owns this navigation request instance.
   FrameTreeNode* const frame_tree_node_;
diff --git a/content/browser/renderer_host/navigation_request_unittest.cc b/content/browser/renderer_host/navigation_request_unittest.cc
index d297d43..efb3437 100644
--- a/content/browser/renderer_host/navigation_request_unittest.cc
+++ b/content/browser/renderer_host/navigation_request_unittest.cc
@@ -116,8 +116,7 @@
                        base::Unretained(this)));
 
     GetNavigationRequest()->WillRedirectRequest(
-        GURL(), WebExposedIsolationInfo::CreateNonIsolated(),
-        nullptr /* post_redirect_process */);
+        GURL(), nullptr /* post_redirect_process */);
   }
 
   // Helper function to call WillFailRequest on |handle|. If this function
diff --git a/content/browser/renderer_host/navigator_unittest.cc b/content/browser/renderer_host/navigator_unittest.cc
index 779c258..c108fb1 100644
--- a/content/browser/renderer_host/navigator_unittest.cc
+++ b/content/browser/renderer_host/navigator_unittest.cc
@@ -91,8 +91,7 @@
       SiteInstance* candidate_instance) {
     return static_cast<SiteInstanceImpl*>(
         rfhm->ConvertToSiteInstance(
-                descriptor, static_cast<SiteInstanceImpl*>(candidate_instance),
-                false /* is_speculative */)
+                descriptor, static_cast<SiteInstanceImpl*>(candidate_instance))
             .get());
   }
 
diff --git a/content/browser/renderer_host/render_frame_host_delegate.h b/content/browser/renderer_host/render_frame_host_delegate.h
index 760a2dd..f0cf415de 100644
--- a/content/browser/renderer_host/render_frame_host_delegate.h
+++ b/content/browser/renderer_host/render_frame_host_delegate.h
@@ -602,7 +602,8 @@
   // Called when the renderer sends a response via DomAutomationController.
   // For example, `window.domAutomationController.send(foo())` sends the result
   // of foo() here.
-  virtual void DomOperationResponse(const std::string& json_string) {}
+  virtual void DomOperationResponse(RenderFrameHost* render_frame_host,
+                                    const std::string& json_string) {}
 
   virtual void OnCookiesAccessed(RenderFrameHostImpl* render_frame_host,
                                  const CookieAccessDetails& details) {}
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 008c1b1..8d2e57caf 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -6575,7 +6575,7 @@
 }
 
 void RenderFrameHostImpl::DomOperationResponse(const std::string& json_string) {
-  delegate_->DomOperationResponse(json_string);
+  delegate_->DomOperationResponse(this, json_string);
 }
 
 std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
@@ -10115,8 +10115,9 @@
 }
 
 bool RenderFrameHostImpl::IsNavigationSameSite(const UrlInfo& dest_url_info) {
-  if (GetSiteInstance()->GetWebExposedIsolationInfo() !=
-      dest_url_info.web_exposed_isolation_info) {
+  if (!WebExposedIsolationInfo::AreCompatible(
+          GetSiteInstance()->GetWebExposedIsolationInfo(),
+          dest_url_info.web_exposed_isolation_info)) {
     return false;
   }
   return GetSiteInstance()->IsNavigationSameSite(
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index dabd059..e3eaa7a 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -175,14 +175,7 @@
 
 bool IsSiteInstanceCompatibleWithWebExposedIsolation(
     SiteInstance* site_instance,
-    bool is_main_frame,
-    const UrlInfo& url_info,
-    bool is_speculative) {
-  // We do not want cross-origin-isolated have any impact on SiteInstances until
-  // we get an actual COOP value in a redirect or a final response.
-  if (is_speculative)
-    return true;
-
+    const UrlInfo& url_info) {
   // Note: The about blank case is to accommodate web tests that use COOP. They
   // expect an about:blank page to stay in process, and hang otherwise. In
   // general, it is safe to allow about:blank pages to stay in process, since
@@ -194,16 +187,9 @@
   SiteInstanceImpl* site_instance_impl =
       static_cast<SiteInstanceImpl*>(site_instance);
 
-  if (is_main_frame) {
-    return site_instance_impl->GetWebExposedIsolationInfo() ==
-           url_info.web_exposed_isolation_info;
-  }
-  // Subframes cannot swap BrowsingInstances, as a result they should either not
-  // load, for instance blocked by COEP, or inherit a compatible cross-origin
-  // isolated state.
-  DCHECK_EQ(site_instance_impl->IsCrossOriginIsolated(),
-            url_info.web_exposed_isolation_info.is_isolated());
-  return true;
+  return WebExposedIsolationInfo::AreCompatible(
+      site_instance_impl->GetWebExposedIsolationInfo(),
+      url_info.web_exposed_isolation_info);
 }
 
 // Helper for appending more information to the optional |reason| parameter
@@ -1519,8 +1505,7 @@
     bool is_same_document,
     bool cross_origin_opener_policy_mismatch,
     bool was_server_redirect,
-    bool should_replace_current_entry,
-    bool is_speculative) {
+    bool should_replace_current_entry) {
   const GURL& destination_url = destination_url_info.url;
   // A subframe must stay in the same BrowsingInstance as its parent.
   bool is_main_frame = frame_tree_node_->IsMainFrame();
@@ -1659,8 +1644,7 @@
   if (current_instance->HasSite() &&
       !render_frame_host_->IsNavigationSameSite(destination_url_info) &&
       !CanUseSourceSiteInstance(destination_url_info, source_instance,
-                                was_server_redirect, is_failure,
-                                is_speculative) &&
+                                was_server_redirect, is_failure) &&
       !is_for_isolated_error_page &&
       IsBrowsingInstanceSwapAllowedForPageTransition(transition,
                                                      destination_url) &&
@@ -1835,7 +1819,6 @@
     bool was_server_redirect,
     bool cross_origin_opener_policy_mismatch,
     bool should_replace_current_entry,
-    bool is_speculative,
     std::string* reason) {
   // On renderer-initiated navigations, when the frame initiating the navigation
   // and the frame being navigated differ, |source_instance| is set to the
@@ -1894,7 +1877,7 @@
           current_instance_impl, dest_instance, dest_url_info,
           dest_is_view_source_mode, transition, is_failure, is_reload,
           is_same_document, cross_origin_opener_policy_mismatch,
-          was_server_redirect, should_replace_current_entry, is_speculative);
+          was_server_redirect, should_replace_current_entry);
 
   TraceShouldSwapBrowsingInstanceResult(frame_tree_node_->frame_tree_node_id(),
                                         should_swap_result);
@@ -1918,15 +1901,14 @@
   SiteInstanceDescriptor new_instance_descriptor = DetermineSiteInstanceForURL(
       dest_url_info, source_instance, current_instance, dest_instance,
       transition, is_failure, dest_is_restore, dest_is_view_source_mode,
-      should_swap, was_server_redirect, is_speculative, reason);
+      should_swap, was_server_redirect, reason);
 
-  scoped_refptr<SiteInstance> new_instance = ConvertToSiteInstance(
-      new_instance_descriptor, candidate_instance, is_speculative);
+  scoped_refptr<SiteInstance> new_instance =
+      ConvertToSiteInstance(new_instance_descriptor, candidate_instance);
   SiteInstanceImpl* new_instance_impl =
       static_cast<SiteInstanceImpl*>(new_instance.get());
-  DCHECK(IsSiteInstanceCompatibleWithWebExposedIsolation(
-      new_instance_impl, frame_tree_node_->IsMainFrame(), dest_url_info,
-      is_speculative));
+  DCHECK(IsSiteInstanceCompatibleWithWebExposedIsolation(new_instance_impl,
+                                                         dest_url_info));
 
   // If |should_swap| is true, we must use a different SiteInstance than the
   // current one. If we didn't, we would have two RenderFrameHosts in the same
@@ -2090,7 +2072,6 @@
     bool dest_is_view_source_mode,
     bool force_browsing_instance_swap,
     bool was_server_redirect,
-    bool is_speculative,
     std::string* reason) {
   // Note that this function should return SiteInstance with
   // SiteInstanceRelation::UNRELATED relation to `current_instance` iff
@@ -2109,9 +2090,8 @@
     // when error pages are involved.
     if (IsSiteInstanceCompatibleWithErrorIsolation(
             dest_instance, *frame_tree_node_, is_failure)) {
-      if (IsSiteInstanceCompatibleWithWebExposedIsolation(
-              dest_instance, frame_tree_node_->IsMainFrame(), dest_url_info,
-              is_speculative)) {
+      if (IsSiteInstanceCompatibleWithWebExposedIsolation(dest_instance,
+                                                          dest_url_info)) {
         // TODO(nasko,creis): The check whether data: or about: URLs are allowed
         // to commit in the current process should be in IsSuitableForUrlInfo.
         // However, making this change has further implications and needs more
@@ -2155,9 +2135,8 @@
 
   // If a swap is required, we need to force the SiteInstance AND
   // BrowsingInstance to be different ones, using CreateForURL.
-  bool can_use_source_instance =
-      CanUseSourceSiteInstance(dest_url_info, source_instance,
-                               was_server_redirect, is_failure, is_speculative);
+  bool can_use_source_instance = CanUseSourceSiteInstance(
+      dest_url_info, source_instance, was_server_redirect, is_failure);
   if (force_browsing_instance_swap) {
     // In rare cases, `source_instance` maybe be already in another
     // BrowsingInstance from `current_instance` (e.g. see how the
@@ -2191,7 +2170,7 @@
     if (GetContentClient()->browser()->ShouldStayInParentProcessForNTP(
             dest_url_info.url, parent_site_instance)) {
       // NTP is considered non-isolated.
-      DCHECK(!dest_url_info.web_exposed_isolation_info.is_isolated());
+      DCHECK(!dest_url_info.IsIsolated());
       AppendReason(reason,
                    "DetermineSiteInstanceForURL => parent_site_instance");
       return SiteInstanceDescriptor(parent_site_instance);
@@ -2213,8 +2192,7 @@
   // restored or it's a Web UI as described below.
   // TODO(ahemery): In theory we should be able to go for an unused SiteInstance
   // with the same web exposed isolation status.
-  if (!current_instance_impl->HasSite() &&
-      !dest_url_info.web_exposed_isolation_info.is_isolated() &&
+  if (!current_instance_impl->HasSite() && !dest_url_info.IsIsolated() &&
       !current_instance_impl->IsCrossOriginIsolated()) {
     // If we've already created a SiteInstance for our destination, we don't
     // want to use this unused SiteInstance; use the existing one.  (We don't
@@ -2286,8 +2264,7 @@
   // Use the current SiteInstance for same site navigations.
   if (render_frame_host_->IsNavigationSameSite(dest_url_info) &&
       IsSiteInstanceCompatibleWithWebExposedIsolation(
-          render_frame_host_->GetSiteInstance(),
-          frame_tree_node_->IsMainFrame(), dest_url_info, is_speculative)) {
+          render_frame_host_->GetSiteInstance(), dest_url_info)) {
     AppendReason(reason, "DetermineSiteInstanceForURL / same-site-navigation");
     return SiteInstanceDescriptor(render_frame_host_->GetSiteInstance());
   }
@@ -2369,8 +2346,7 @@
   // BrowsingInstance, unless the destination URL's web-exposed isolated state
   // cannot be hosted by it.
   if (IsSiteInstanceCompatibleWithWebExposedIsolation(
-          render_frame_host_->GetSiteInstance(),
-          frame_tree_node_->IsMainFrame(), dest_url_info, is_speculative)) {
+          render_frame_host_->GetSiteInstance(), dest_url_info)) {
     AppendReason(reason,
                  "DetermineSiteInstanceForURL / fallback / coop-compatible");
     return SiteInstanceDescriptor(dest_url_info, SiteInstanceRelation::RELATED);
@@ -2421,18 +2397,15 @@
 
 scoped_refptr<SiteInstance> RenderFrameHostManager::ConvertToSiteInstance(
     const SiteInstanceDescriptor& descriptor,
-    SiteInstanceImpl* candidate_instance,
-    bool is_speculative) {
+    SiteInstanceImpl* candidate_instance) {
   SiteInstanceImpl* current_instance = render_frame_host_->GetSiteInstance();
 
   // If we are asked to return a related SiteInstance but the BrowsingInstance
   // has a different cross_origin_isolated state, something went wrong.
-  // This can be wrong for speculative frames, as the COOP mismatch mechanic has
-  // not yet been invoked.
-  CHECK(is_speculative ||
-        descriptor.relation != SiteInstanceRelation::RELATED ||
-        current_instance->IsCrossOriginIsolated() ==
-            descriptor.dest_url_info.web_exposed_isolation_info.is_isolated());
+  CHECK(descriptor.relation != SiteInstanceRelation::RELATED ||
+        WebExposedIsolationInfo::AreCompatible(
+            current_instance->GetWebExposedIsolationInfo(),
+            descriptor.dest_url_info.web_exposed_isolation_info));
 
   // Note: If the `candidate_instance` matches the descriptor, it will already
   // be set to `descriptor.existing_site_instance`.
@@ -2457,8 +2430,7 @@
   // check if the candidate matches.
   if (candidate_instance &&
       IsSiteInstanceCompatibleWithWebExposedIsolation(
-          candidate_instance, frame_tree_node_->IsMainFrame(),
-          descriptor.dest_url_info, is_speculative) &&
+          candidate_instance, descriptor.dest_url_info) &&
       !current_instance->IsRelatedSiteInstance(candidate_instance) &&
       candidate_instance->DoesSiteInfoForURLMatch(descriptor.dest_url_info)) {
     return candidate_instance;
@@ -2473,8 +2445,7 @@
     const UrlInfo& dest_url_info,
     SiteInstance* source_instance,
     bool was_server_redirect,
-    bool is_failure,
-    bool is_speculative) {
+    bool is_failure) {
   if (!source_instance)
     return false;
 
@@ -2506,9 +2477,8 @@
     return false;
   }
 
-  if (!IsSiteInstanceCompatibleWithWebExposedIsolation(
-          source_instance, frame_tree_node_->IsMainFrame(), dest_url_info,
-          is_speculative)) {
+  if (!IsSiteInstanceCompatibleWithWebExposedIsolation(source_instance,
+                                                       dest_url_info)) {
     return false;
   }
 
@@ -3062,10 +3032,7 @@
       request->GetRestoreType() == RestoreType::kRestored,
       request->commit_params().is_view_source, request->WasServerRedirect(),
       request->coop_status().require_browsing_instance_swap(),
-      request->common_params().should_replace_current_entry,
-      request->state() < NavigationRequest::NavigationState::
-                             WILL_REDIRECT_REQUEST /* is_speculative */,
-      reason);
+      request->common_params().should_replace_current_entry, reason);
 
   TRACE_EVENT_INSTANT(
       "navigation",
diff --git a/content/browser/renderer_host/render_frame_host_manager.h b/content/browser/renderer_host/render_frame_host_manager.h
index d2a65ba..f2a7e846 100644
--- a/content/browser/renderer_host/render_frame_host_manager.h
+++ b/content/browser/renderer_host/render_frame_host_manager.h
@@ -658,8 +658,7 @@
       bool is_same_document,
       bool cross_origin_opener_policy_mismatch,
       bool was_server_redirect,
-      bool should_replace_current_entry,
-      bool is_speculative);
+      bool should_replace_current_entry);
 
   ShouldSwapBrowsingInstance ShouldProactivelySwapBrowsingInstance(
       const UrlInfo& destination_url_info,
@@ -683,7 +682,6 @@
       bool was_server_redirect,
       bool cross_origin_opener_policy_mismatch,
       bool should_replace_current_entry,
-      bool is_speculative,
       std::string* reason);
 
   // Returns a descriptor of the appropriate SiteInstance object for the given
@@ -699,11 +697,6 @@
   // A is trying to change the src attribute of B, this will cause a navigation
   // where the source SiteInstance is A and B is the current SiteInstance.
   //
-  // `is_speculative` indicates that the SiteInstance is being computed for a
-  // speculative RenderFrameHost, which may change once response is received and
-  // a final RenderFrameHost/SiteInstance is computed. It is true at request
-  // start time, but false for redirects and at OnResponseStarted time.
-  //
   // This is a helper function for GetSiteInstanceForNavigation.
   SiteInstanceDescriptor DetermineSiteInstanceForURL(
       const UrlInfo& dest_url_info,
@@ -716,7 +709,6 @@
       bool dest_is_view_source_mode,
       bool force_browsing_instance_swap,
       bool was_server_redirect,
-      bool is_speculative,
       std::string* reason);
 
   // Returns true if a navigation to |dest_url| that uses the specified
@@ -744,18 +736,14 @@
   bool CanUseSourceSiteInstance(const UrlInfo& dest_url_info,
                                 SiteInstance* source_instance,
                                 bool was_server_redirect,
-                                bool is_failure,
-                                bool is_speculative);
+                                bool is_failure);
 
   // Converts a SiteInstanceDescriptor to the actual SiteInstance it describes.
   // If a |candidate_instance| is provided (is not nullptr) and it matches the
   // description, it is returned as is.
-  // |is_speculative| indicates whether we are computing a SiteInstance for a
-  // speculative RenderFrameHost or if have already received a response.
   scoped_refptr<SiteInstance> ConvertToSiteInstance(
       const SiteInstanceDescriptor& descriptor,
-      SiteInstanceImpl* candidate_instance,
-      bool is_speculative);
+      SiteInstanceImpl* candidate_instance);
 
   // Returns true if `candidate` is currently same site with `dest_url_info`.
   // This method is a special case for handling hosted apps in this object. Most
diff --git a/content/browser/site_info.cc b/content/browser/site_info.cc
index 2cd4ab8..9a113137 100644
--- a/content/browser/site_info.cc
+++ b/content/browser/site_info.cc
@@ -276,7 +276,7 @@
 
   if (url_info.url.SchemeIs(kChromeErrorScheme)) {
     // Error pages should never be cross origin isolated.
-    DCHECK(!url_info.web_exposed_isolation_info.is_isolated());
+    DCHECK(!url_info.IsIsolated());
     return CreateForErrorPage(storage_partition_config.value());
   }
   // We should only set |requires_origin_keyed_process| if we are actually
@@ -321,20 +321,22 @@
   return Create(isolation_context, UrlInfo::CreateForTesting(url));
 }
 
-SiteInfo::SiteInfo(const GURL& site_url,
-                   const GURL& process_lock_url,
-                   bool requires_origin_keyed_process,
-                   const StoragePartitionConfig storage_partition_config,
-                   const WebExposedIsolationInfo& web_exposed_isolation_info,
-                   bool is_guest,
-                   bool does_site_request_dedicated_process_for_coop,
-                   bool is_jit_disabled,
-                   bool is_pdf)
+SiteInfo::SiteInfo(
+    const GURL& site_url,
+    const GURL& process_lock_url,
+    bool requires_origin_keyed_process,
+    const StoragePartitionConfig storage_partition_config,
+    const absl::optional<WebExposedIsolationInfo>& web_exposed_isolation_info,
+    bool is_guest,
+    bool does_site_request_dedicated_process_for_coop,
+    bool is_jit_disabled,
+    bool is_pdf)
     : site_url_(site_url),
       process_lock_url_(process_lock_url),
       requires_origin_keyed_process_(requires_origin_keyed_process),
       storage_partition_config_(storage_partition_config),
-      web_exposed_isolation_info_(web_exposed_isolation_info),
+      web_exposed_isolation_info_(web_exposed_isolation_info.value_or(
+          WebExposedIsolationInfo::CreateNonIsolated())),
       is_guest_(is_guest),
       does_site_request_dedicated_process_for_coop_(
           does_site_request_dedicated_process_for_coop),
diff --git a/content/browser/site_info.h b/content/browser/site_info.h
index 02a2ae52..00ce015 100644
--- a/content/browser/site_info.h
+++ b/content/browser/site_info.h
@@ -112,15 +112,16 @@
   // SiteInfos, to help ensure all creation sites are updated accordingly when
   // new values are added. The private function MakeTie() should be updated
   // accordingly.
-  SiteInfo(const GURL& site_url,
-           const GURL& process_lock_url,
-           bool requires_origin_keyed_process,
-           const StoragePartitionConfig storage_partition_config,
-           const WebExposedIsolationInfo& web_exposed_isolation_info,
-           bool is_guest,
-           bool does_site_request_dedicated_process_for_coop,
-           bool is_jit_disabled,
-           bool is_pdf);
+  SiteInfo(
+      const GURL& site_url,
+      const GURL& process_lock_url,
+      bool requires_origin_keyed_process,
+      const StoragePartitionConfig storage_partition_config,
+      const absl::optional<WebExposedIsolationInfo>& web_exposed_isolation_info,
+      bool is_guest,
+      bool does_site_request_dedicated_process_for_coop,
+      bool is_jit_disabled,
+      bool is_pdf);
   SiteInfo() = delete;
   SiteInfo(const SiteInfo& rhs);
   ~SiteInfo();
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index d65dd30d..bf46068 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -142,7 +142,8 @@
   DCHECK(browser_context);
   // This will create a new SiteInstance and BrowsingInstance.
   scoped_refptr<BrowsingInstance> instance(new BrowsingInstance(
-      browser_context, url_info.web_exposed_isolation_info));
+      browser_context, url_info.web_exposed_isolation_info.value_or(
+                           WebExposedIsolationInfo::CreateNonIsolated())));
 
   // Note: The |allow_default_instance| value used here MUST match the value
   // used in DoesSiteForURLMatch().
@@ -166,7 +167,8 @@
   } else {
     // This will create a new SiteInstance and BrowsingInstance.
     scoped_refptr<BrowsingInstance> instance(new BrowsingInstance(
-        browser_context, url_info.web_exposed_isolation_info));
+        browser_context, url_info.web_exposed_isolation_info.value_or(
+                             WebExposedIsolationInfo::CreateNonIsolated())));
 
     // We do NOT want to allow the default site instance here because workers
     // need to be kept separate from other sites.
diff --git a/content/browser/url_info.cc b/content/browser/url_info.cc
index 7cf7749..95e560f 100644
--- a/content/browser/url_info.cc
+++ b/content/browser/url_info.cc
@@ -34,6 +34,12 @@
                      .WithStoragePartitionConfig(storage_partition_config));
 }
 
+bool UrlInfo::IsIsolated() const {
+  if (!web_exposed_isolation_info)
+    return false;
+  return web_exposed_isolation_info->is_isolated();
+}
+
 UrlInfoInit::UrlInfoInit(UrlInfoInit&) = default;
 
 UrlInfoInit::UrlInfoInit(const GURL& url)
@@ -70,7 +76,7 @@
 }
 
 UrlInfoInit& UrlInfoInit::WithWebExposedIsolationInfo(
-    const WebExposedIsolationInfo& web_exposed_isolation_info) {
+    absl::optional<WebExposedIsolationInfo> web_exposed_isolation_info) {
   web_exposed_isolation_info_ = web_exposed_isolation_info;
   return *this;
 }
diff --git a/content/browser/url_info.h b/content/browser/url_info.h
index 00ef477..31500c3 100644
--- a/content/browser/url_info.h
+++ b/content/browser/url_info.h
@@ -96,6 +96,10 @@
     return (origin_isolation_request & OriginIsolationRequest::kCOOP);
   }
 
+  // Returns whether this UrlInfo is for a page that should be cross-origin
+  // isolated.
+  bool IsIsolated() const;
+
   GURL url;
 
   // This field indicates whether the URL is requesting additional process
@@ -128,7 +132,10 @@
   // safely expose otherwise. "Cross-origin isolation", for example, requires
   // assertion of a Cross-Origin-Opener-Policy and
   // Cross-Origin-Embedder-Policy, and unlocks SharedArrayBuffer.
-  WebExposedIsolationInfo web_exposed_isolation_info;
+  // When we haven't yet been to the network or inherited properties that are
+  // sufficient to know the future isolation state - we are in a speculative
+  // state - this member will be empty.
+  absl::optional<WebExposedIsolationInfo> web_exposed_isolation_info;
 
   // Indicates that the URL directs to PDF content, which should be isolated
   // from other types of content.
@@ -153,7 +160,7 @@
   UrlInfoInit& WithStoragePartitionConfig(
       absl::optional<StoragePartitionConfig> storage_partition_config);
   UrlInfoInit& WithWebExposedIsolationInfo(
-      const WebExposedIsolationInfo& web_exposed_isolation_info);
+      absl::optional<WebExposedIsolationInfo> web_exposed_isolation_info);
   UrlInfoInit& WithIsPdf(bool is_pdf);
 
  private:
@@ -166,7 +173,7 @@
       UrlInfo::OriginIsolationRequest::kNone;
   url::Origin origin_;
   absl::optional<StoragePartitionConfig> storage_partition_config_;
-  WebExposedIsolationInfo web_exposed_isolation_info_;
+  absl::optional<WebExposedIsolationInfo> web_exposed_isolation_info_;
   bool is_pdf_ = false;
 
   // Any new fields should be added to the UrlInfoInit(UrlInfo) constructor.
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 9f26fb3c..e11e653 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -6150,9 +6150,11 @@
   delegate_->UnregisterProtocolHandler(source, protocol, url, user_gesture);
 }
 
-void WebContentsImpl::DomOperationResponse(const std::string& json_string) {
-  OPTIONAL_TRACE_EVENT1("content", "WebContentsImpl::DomOperationResponse",
-                        "json_string", json_string);
+void WebContentsImpl::DomOperationResponse(RenderFrameHost* render_frame_host,
+                                           const std::string& json_string) {
+  OPTIONAL_TRACE_EVENT2("content", "WebContentsImpl::DomOperationResponse",
+                        "render_frame_host", render_frame_host, "json_string",
+                        json_string);
 
   // TODO(lukasza): The notification below should probably indicate which
   // RenderFrameHostImpl the message is coming from. This can enable tests to
@@ -6162,6 +6164,9 @@
   NotificationService::current()->Notify(
       NOTIFICATION_DOM_OPERATION_RESPONSE, Source<WebContents>(this),
       Details<const std::string>(&json_string));
+
+  observers_.NotifyObservers(&WebContentsObserver::DomOperationResponse,
+                             render_frame_host, json_string);
 }
 
 void WebContentsImpl::SavableResourceLinksResponse(
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 1c40c72..8845a16 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -778,7 +778,8 @@
       const std::string& mime_type,
       network::mojom::RequestDestination request_destination,
       bool include_credentials) override;
-  void DomOperationResponse(const std::string& json_string) override;
+  void DomOperationResponse(RenderFrameHost* render_frame_host,
+                            const std::string& json_string) override;
   void SavableResourceLinksResponse(
       RenderFrameHostImpl* source,
       const std::vector<GURL>& resources_list,
diff --git a/content/browser/web_exposed_isolation_info.cc b/content/browser/web_exposed_isolation_info.cc
index 5cecf227..5106251 100644
--- a/content/browser/web_exposed_isolation_info.cc
+++ b/content/browser/web_exposed_isolation_info.cc
@@ -6,8 +6,18 @@
 
 #include <ostream>
 
+#include "base/notreached.h"
+
 namespace content {
 
+namespace {
+
+constexpr char kComparisonErrorMessage[] =
+    "You are comparing optional WebExposedIsolationInfo objects using "
+    "operator==, use WebExposedIsolationInfo::AreCompatible() instead.";
+
+}  // namespace
+
 // static
 WebExposedIsolationInfo WebExposedIsolationInfo::CreateNonIsolated() {
   return WebExposedIsolationInfo(absl::nullopt /* origin */,
@@ -24,6 +34,33 @@
   return WebExposedIsolationInfo(origin, true /* isolated_application */);
 }
 
+bool WebExposedIsolationInfo::AreCompatible(const WebExposedIsolationInfo& a,
+                                            const WebExposedIsolationInfo& b) {
+  return a == b;
+}
+
+bool WebExposedIsolationInfo::AreCompatible(
+    const absl::optional<WebExposedIsolationInfo>& a,
+    const WebExposedIsolationInfo& b) {
+  if (!a.has_value())
+    return true;
+  return AreCompatible(a.value(), b);
+}
+
+bool WebExposedIsolationInfo::AreCompatible(
+    const WebExposedIsolationInfo& a,
+    const absl::optional<WebExposedIsolationInfo>& b) {
+  return AreCompatible(b, a);
+}
+
+bool WebExposedIsolationInfo::AreCompatible(
+    const absl::optional<WebExposedIsolationInfo>& a,
+    const absl::optional<WebExposedIsolationInfo>& b) {
+  if (!a.has_value() || !b.has_value())
+    return true;
+  return AreCompatible(a.value(), b.value());
+}
+
 WebExposedIsolationInfo::WebExposedIsolationInfo(
     const absl::optional<url::Origin>& origin,
     bool isolated_application)
@@ -91,4 +128,23 @@
   out << "}";
   return out;
 }
+
+bool operator==(const absl::optional<WebExposedIsolationInfo>& a,
+                const absl::optional<WebExposedIsolationInfo>& b) {
+  NOTREACHED() << kComparisonErrorMessage;
+  return false;
+}
+
+bool operator==(const WebExposedIsolationInfo& a,
+                const absl::optional<WebExposedIsolationInfo>& b) {
+  NOTREACHED() << kComparisonErrorMessage;
+  return false;
+}
+
+bool operator==(const absl::optional<WebExposedIsolationInfo>& a,
+                const WebExposedIsolationInfo& b) {
+  NOTREACHED() << kComparisonErrorMessage;
+  return false;
+}
+
 }  // namespace content
diff --git a/content/browser/web_exposed_isolation_info.h b/content/browser/web_exposed_isolation_info.h
index 703517c4..463e976 100644
--- a/content/browser/web_exposed_isolation_info.h
+++ b/content/browser/web_exposed_isolation_info.h
@@ -36,6 +36,25 @@
   static WebExposedIsolationInfo CreateIsolatedApplication(
       const url::Origin& origin);
 
+  // These helpers make it easy to compare against an optional
+  // WebExposedIsolationInfo. This is useful because a navigation may return
+  // an empty WebExposedIsolationInfo to the process model, for example when
+  // we do not yet have a final network response. In that case it is considered
+  // compatible with any WebExposedIsolationInfo.
+  //
+  // In details, the underlying logic is as follow:
+  // - Two valid values are compared using the == operator.
+  // - Null and a valid value returns true.
+  // - Two null values returns true.
+  static bool AreCompatible(const WebExposedIsolationInfo& a,
+                            const WebExposedIsolationInfo& b);
+  static bool AreCompatible(const WebExposedIsolationInfo& a,
+                            const absl::optional<WebExposedIsolationInfo>& b);
+  static bool AreCompatible(const absl::optional<WebExposedIsolationInfo>& a,
+                            const WebExposedIsolationInfo& b);
+  static bool AreCompatible(const absl::optional<WebExposedIsolationInfo>& a,
+                            const absl::optional<WebExposedIsolationInfo>& b);
+
   WebExposedIsolationInfo(const WebExposedIsolationInfo& other);
   ~WebExposedIsolationInfo();
 
@@ -94,6 +113,17 @@
 
 CONTENT_EXPORT std::ostream& operator<<(std::ostream& out,
                                         const WebExposedIsolationInfo& info);
+
+// Disable these operators to avoid confusion with AreCompatible() functions.
+CONTENT_EXPORT bool operator==(
+    const absl::optional<WebExposedIsolationInfo>& a,
+    const absl::optional<WebExposedIsolationInfo>& b);
+CONTENT_EXPORT bool operator==(
+    const WebExposedIsolationInfo& a,
+    const absl::optional<WebExposedIsolationInfo>& b);
+CONTENT_EXPORT bool operator==(const absl::optional<WebExposedIsolationInfo>& a,
+                               const WebExposedIsolationInfo& b);
+
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_WEB_EXPOSED_ISOLATION_INFO_H_
diff --git a/content/browser/web_exposed_isolation_info_unittest.cc b/content/browser/web_exposed_isolation_info_unittest.cc
index f70ad5da..3273b04 100644
--- a/content/browser/web_exposed_isolation_info_unittest.cc
+++ b/content/browser/web_exposed_isolation_info_unittest.cc
@@ -89,4 +89,76 @@
   EXPECT_FALSE(appB < appA);
 }
 
+TEST_F(WebExposedIsolationInfoTest, AreCompatibleFunctions) {
+  url::Origin originA =
+      url::Origin::CreateFromNormalizedTuple("https", "aaa.example", 443);
+  url::Origin originB =
+      url::Origin::CreateFromNormalizedTuple("https", "bbb.example", 443);
+  WebExposedIsolationInfo nonIsolated =
+      WebExposedIsolationInfo::CreateNonIsolated();
+  WebExposedIsolationInfo isolatedA =
+      WebExposedIsolationInfo::CreateIsolated(originA);
+  WebExposedIsolationInfo isolatedB =
+      WebExposedIsolationInfo::CreateIsolated(originB);
+  WebExposedIsolationInfo appA =
+      WebExposedIsolationInfo::CreateIsolatedApplication(originA);
+  WebExposedIsolationInfo appB =
+      WebExposedIsolationInfo::CreateIsolatedApplication(originB);
+
+  absl::optional<WebExposedIsolationInfo> optionalEmpty = absl::nullopt;
+  EXPECT_TRUE(
+      WebExposedIsolationInfo::AreCompatible(optionalEmpty, nonIsolated));
+  EXPECT_TRUE(WebExposedIsolationInfo::AreCompatible(optionalEmpty, isolatedA));
+  EXPECT_TRUE(WebExposedIsolationInfo::AreCompatible(optionalEmpty, appA));
+  EXPECT_TRUE(
+      WebExposedIsolationInfo::AreCompatible(nonIsolated, optionalEmpty));
+  EXPECT_TRUE(WebExposedIsolationInfo::AreCompatible(isolatedA, optionalEmpty));
+  EXPECT_TRUE(WebExposedIsolationInfo::AreCompatible(appA, optionalEmpty));
+
+  absl::optional<WebExposedIsolationInfo> optionalNonIsolated =
+      WebExposedIsolationInfo::CreateNonIsolated();
+  EXPECT_TRUE(
+      WebExposedIsolationInfo::AreCompatible(optionalNonIsolated, nonIsolated));
+  EXPECT_FALSE(
+      WebExposedIsolationInfo::AreCompatible(optionalNonIsolated, isolatedA));
+  EXPECT_FALSE(
+      WebExposedIsolationInfo::AreCompatible(optionalNonIsolated, appA));
+  EXPECT_TRUE(
+      WebExposedIsolationInfo::AreCompatible(nonIsolated, optionalNonIsolated));
+  EXPECT_FALSE(
+      WebExposedIsolationInfo::AreCompatible(isolatedA, optionalNonIsolated));
+  EXPECT_FALSE(
+      WebExposedIsolationInfo::AreCompatible(appA, optionalNonIsolated));
+
+  absl::optional<WebExposedIsolationInfo> optionalIsolatedA =
+      WebExposedIsolationInfo::CreateIsolated(originA);
+  EXPECT_FALSE(
+      WebExposedIsolationInfo::AreCompatible(optionalIsolatedA, nonIsolated));
+  EXPECT_TRUE(
+      WebExposedIsolationInfo::AreCompatible(optionalIsolatedA, isolatedA));
+  EXPECT_FALSE(
+      WebExposedIsolationInfo::AreCompatible(optionalIsolatedA, isolatedB));
+  EXPECT_FALSE(WebExposedIsolationInfo::AreCompatible(optionalIsolatedA, appA));
+  EXPECT_FALSE(
+      WebExposedIsolationInfo::AreCompatible(nonIsolated, optionalIsolatedA));
+  EXPECT_TRUE(
+      WebExposedIsolationInfo::AreCompatible(isolatedA, optionalIsolatedA));
+  EXPECT_FALSE(
+      WebExposedIsolationInfo::AreCompatible(isolatedB, optionalIsolatedA));
+  EXPECT_FALSE(WebExposedIsolationInfo::AreCompatible(appA, optionalIsolatedA));
+
+  absl::optional<WebExposedIsolationInfo> optionalAppA =
+      WebExposedIsolationInfo::CreateIsolatedApplication(originA);
+  EXPECT_FALSE(
+      WebExposedIsolationInfo::AreCompatible(optionalAppA, nonIsolated));
+  EXPECT_FALSE(WebExposedIsolationInfo::AreCompatible(optionalAppA, isolatedA));
+  EXPECT_TRUE(WebExposedIsolationInfo::AreCompatible(optionalAppA, appA));
+  EXPECT_FALSE(WebExposedIsolationInfo::AreCompatible(optionalAppA, appB));
+  EXPECT_FALSE(
+      WebExposedIsolationInfo::AreCompatible(nonIsolated, optionalAppA));
+  EXPECT_FALSE(WebExposedIsolationInfo::AreCompatible(isolatedA, optionalAppA));
+  EXPECT_TRUE(WebExposedIsolationInfo::AreCompatible(appA, optionalAppA));
+  EXPECT_FALSE(WebExposedIsolationInfo::AreCompatible(appB, optionalAppA));
+}
+
 }  // namespace content
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityActionAndEventTracker.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityActionAndEventTracker.java
index fdc11b6..f87f596 100644
--- a/content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityActionAndEventTracker.java
+++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityActionAndEventTracker.java
@@ -23,17 +23,23 @@
     }
 
     public void addEvent(AccessibilityEvent event) {
-        mEvents.add(eventToString(event));
+        // In rare cases there may be a lingering event, so only add if the test is not complete.
+        if (!mTestComplete) {
+            mEvents.add(eventToString(event));
+        }
     }
 
     public void addAction(int action, Bundle arguments) {
-        mEvents.add(actionToString(action, arguments));
+        // In rare cases there may be a lingering action, so only add if the test is not complete.
+        if (!mTestComplete) {
+            mEvents.add(actionToString(action, arguments));
+        }
     }
 
     public String results() {
         StringBuilder results = new StringBuilder();
 
-        for (String event : mEvents) {
+        for (String event : new LinkedList<String>(mEvents)) {
             if (event != null && !event.isEmpty()) {
                 results.append(event);
                 results.append('\n');
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java
index 46b962e..b3596f01 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java
@@ -19,7 +19,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.Batch;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.FlakyTest;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.content_public.browser.test.ContentJUnit4ClassRunner;
@@ -32,7 +31,6 @@
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 @SuppressLint("VisibleForTests")
 @Batch(Batch.UNIT_TESTS)
-@DisabledTest(message = "https://crbug.com/1261677")
 public class WebContentsAccessibilityEventsTest {
     // File path that holds all the relevant tests.
     private static final String BASE_FILE_PATH = "content/test/data/accessibility/event/";
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTreeTest.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTreeTest.java
index 73c6766d..a08c5d7 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTreeTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTreeTest.java
@@ -207,6 +207,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaButton() {
         performAriaTest("aria-button.html");
     }
@@ -219,12 +220,14 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaCheckbox() {
         performAriaTest("aria-checkbox.html");
     }
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaChecked() {
         performAriaTest("aria-checked.html");
     }
@@ -261,6 +264,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaCombobox() {
         performAriaTest("aria-combobox.html");
     }
@@ -273,6 +277,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaComboboxUneditable() {
         performAriaTest("aria-combobox-uneditable.html");
     }
@@ -297,6 +302,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaCurrent() {
         performAriaTest("aria-current.html");
     }
@@ -405,6 +411,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaGridcell() {
         performAriaTest("aria-gridcell.html");
     }
@@ -459,6 +466,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaIllegalVal() {
         performAriaTest("aria-illegal-val.html");
     }
@@ -507,18 +515,21 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaListboxAriaSelected() {
         performAriaTest("aria-listbox-aria-selected.html");
     }
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaListboxDisabled() {
         performAriaTest("aria-listbox-disabled.html");
     }
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaListbox() {
         performAriaTest("aria-listbox.html");
     }
@@ -585,6 +596,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaMenuitemcheckbox() {
         performAriaTest("aria-menuitemcheckbox.html");
     }
@@ -603,6 +615,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaMenuitemradio() {
         performAriaTest("aria-menuitemradio.html");
     }
@@ -627,6 +640,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaMultiselectable() {
         performAriaTest("aria-multiselectable.html");
     }
@@ -651,12 +665,14 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaOptionComplexChildren() {
         performAriaTest("aria-option-complex-children.html");
     }
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaOption() {
         performAriaTest("aria-option.html");
     }
@@ -705,6 +721,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaPressed() {
         performAriaTest("aria-pressed.html");
     }
@@ -795,6 +812,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaSelected() {
         performAriaTest("aria-selected.html");
     }
@@ -807,6 +825,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaSetsize() {
         performAriaTest("aria-setsize.html");
     }
@@ -933,6 +952,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaTogglebutton() {
         performAriaTest("aria-togglebutton.html");
     }
@@ -963,6 +983,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_ariaTree() {
         performAriaTest("aria-tree.html");
     }
@@ -1035,6 +1056,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/1279785")
     public void test_toggleButtonExpandCollapse() {
         performAriaTest("toggle-button-expand-collapse.html");
     }
diff --git a/content/public/browser/media_stream_request.h b/content/public/browser/media_stream_request.h
index e4fd1d5..977faa5 100644
--- a/content/public/browser/media_stream_request.h
+++ b/content/public/browser/media_stream_request.h
@@ -100,7 +100,7 @@
       const DesktopMediaID& media_id,
       blink::mojom::MediaStreamStateChange new_state)>;
 
-  virtual ~MediaStreamUI() {}
+  virtual ~MediaStreamUI() = default;
 
   // Called when MediaStream capturing is started. Chrome layer can call |stop|
   // to stop the stream, or |source| to change the source of the stream, or
diff --git a/content/public/browser/notification_types.h b/content/public/browser/notification_types.h
index a666e09b..792b67d 100644
--- a/content/public/browser/notification_types.h
+++ b/content/public/browser/notification_types.h
@@ -103,6 +103,7 @@
   // Notification from WebContents that we have received a response from the
   // renderer in response to a dom automation controller action. The source is
   // the RenderViewHost, and the details is a string with the response.
+  // DEPRECATED: Use WebContentsObserver::DomOperationResponse()
   // TODO(https://crbug.com/1174774): Remove.
   NOTIFICATION_DOM_OPERATION_RESPONSE,
 
diff --git a/content/public/browser/web_contents_observer.h b/content/public/browser/web_contents_observer.h
index 5df6466..66e8be1e 100644
--- a/content/public/browser/web_contents_observer.h
+++ b/content/public/browser/web_contents_observer.h
@@ -384,6 +384,15 @@
   virtual void DocumentOnLoadCompletedInMainFrame(
       RenderFrameHost* render_frame_host) {}
 
+  // This method is invoked when we have received a response from the
+  // renderer in response to a dom automation controller action.
+  // For example, `window.domAutomationController.send(foo())` sends the
+  // result of foo() here.
+  // |json_string| is a string with the response which came from a specific
+  // |render_frame_host|.
+  virtual void DomOperationResponse(RenderFrameHost* render_frame_host,
+                                    const std::string& json_string) {}
+
   // This method is invoked when the document in the given frame finished
   // loading. At this point, scripts marked as defer were executed, and
   // content scripts marked "document_end" get injected into the frame.
diff --git a/content/public/test/javascript_test_observer.cc b/content/public/test/javascript_test_observer.cc
index 6675962e..2d4af73 100644
--- a/content/public/test/javascript_test_observer.cc
+++ b/content/public/test/javascript_test_observer.cc
@@ -5,7 +5,6 @@
 #include "content/public/test/javascript_test_observer.h"
 
 #include "base/run_loop.h"
-#include "content/public/browser/notification_types.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/test_utils.h"
@@ -28,15 +27,13 @@
   error_message_.clear();
 }
 
-JavascriptTestObserver::JavascriptTestObserver(
-    WebContents* web_contents, TestMessageHandler* handler)
-    : handler_(handler),
+JavascriptTestObserver::JavascriptTestObserver(WebContents* web_contents,
+                                               TestMessageHandler* handler)
+    : WebContentsObserver(web_contents),
+      handler_(handler),
       running_(false),
       finished_(false) {
   Reset();
-  registrar_.Add(this,
-                 NOTIFICATION_DOM_OPERATION_RESPONSE,
-                 Source<WebContents>(web_contents));
 }
 
 JavascriptTestObserver::~JavascriptTestObserver() {
@@ -60,16 +57,13 @@
   handler_->Reset();
 }
 
-void JavascriptTestObserver::Observe(
-    int type,
-    const NotificationSource& source,
-    const NotificationDetails& details) {
-  CHECK(type == NOTIFICATION_DOM_OPERATION_RESPONSE);
-  Details<std::string> dom_op_result(details);
+void JavascriptTestObserver::DomOperationResponse(
+    RenderFrameHost* render_frame_host,
+    const std::string& json_string) {
   // We might receive responses for other script execution, but we only
   // care about the test finished message.
   TestMessageHandler::MessageResponse response =
-      handler_->HandleMessage(*dom_op_result.ptr());
+      handler_->HandleMessage(json_string);
 
   if (response == TestMessageHandler::DONE) {
     EndTest();
diff --git a/content/public/test/javascript_test_observer.h b/content/public/test/javascript_test_observer.h
index 27d066b..3c3bb79 100644
--- a/content/public/test/javascript_test_observer.h
+++ b/content/public/test/javascript_test_observer.h
@@ -8,11 +8,9 @@
 #include <string>
 
 #include "base/memory/raw_ptr.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/web_contents_observer.h"
 
 namespace content {
-class WebContents;
 
 // Base class for handling a stream of automation messages produced by a
 // JavascriptTestObserver.
@@ -51,7 +49,7 @@
 
 // This class captures a stream of automation messages coming from a Javascript
 // test and dispatches them to a message handler.
-class JavascriptTestObserver : public NotificationObserver {
+class JavascriptTestObserver : public WebContentsObserver {
  public:
   // The observer does not own any arguments passed to it.  It is assumed that
   // the arguments will outlive all uses of the observer.
@@ -72,9 +70,8 @@
   // while Run() is pumping the message loop.
   void Reset();
 
-  void Observe(int type,
-               const NotificationSource& source,
-               const NotificationDetails& details) override;
+  void DomOperationResponse(RenderFrameHost* render_frame_host,
+                            const std::string& json_string) override;
 
  private:
   // This message did not signal the end of a test, keep going.
@@ -86,7 +83,6 @@
   raw_ptr<TestMessageHandler> handler_;
   bool running_;
   bool finished_;
-  NotificationRegistrar registrar_;
 };
 
 }  // namespace content
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index e5942512..2293ffc 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -738,7 +738,8 @@
     BindHostReceiver(compositing_mode_reporter_.BindNewPipeAndPassReceiver());
 
     compositing_mode_reporter_->AddCompositingModeWatcher(
-        compositing_mode_watcher_receiver_.BindNewPipeAndPassRemote());
+        compositing_mode_watcher_receiver_.BindNewPipeAndPassRemote(
+            main_thread_scheduler_->CompositorTaskRunner()));
   }
 
   variations_observer_ = std::make_unique<VariationsRenderThreadObserver>();
diff --git a/fuchsia/engine/browser/frame_impl_unittest.cc b/fuchsia/engine/browser/frame_impl_unittest.cc
index 111993f..cfc059e 100644
--- a/fuchsia/engine/browser/frame_impl_unittest.cc
+++ b/fuchsia/engine/browser/frame_impl_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "fuchsia/engine/browser/frame_impl.h"
 #include "base/strings/string_piece.h"
+#include "base/test/gtest_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using NavigationState = fuchsia::web::NavigationState;
@@ -97,3 +98,27 @@
   EXPECT_FALSE(difference.IsEmpty());
   EXPECT_TRUE(difference.is_main_document_loaded());
 }
+
+// Verifies that transitions from empty to non-empty states are handled.
+TEST(FrameImplUnitTest, DiffNavigationEntriesFromInitial) {
+  fuchsia::web::NavigationState difference;
+  NavigationState state1;
+  NavigationState state2 = CreateNavigationState(
+      GURL(kUrl1), kTitle1, fuchsia::web::PageType::NORMAL, true, true, false);
+
+  DiffNavigationEntries(state1, state2, &difference);
+  EXPECT_FALSE(difference.IsEmpty());
+
+  // Transitions from non-empty to empty (initial) state are DCHECK'd.
+  EXPECT_DCHECK_DEATH({ DiffNavigationEntries(state2, state1, &difference); });
+}
+
+// Verifies that differencing between two empty/initial states are handled.
+TEST(FrameImplUnitTest, DiffNavigationEntriesBothInitial) {
+  fuchsia::web::NavigationState difference;
+  NavigationState state1;
+  NavigationState state2;
+
+  DiffNavigationEntries(state1, state2, &difference);
+  EXPECT_TRUE(difference.IsEmpty());
+}
diff --git a/fuchsia/engine/browser/navigation_controller_impl.cc b/fuchsia/engine/browser/navigation_controller_impl.cc
index c48438e..9277bd7 100644
--- a/fuchsia/engine/browser/navigation_controller_impl.cc
+++ b/fuchsia/engine/browser/navigation_controller_impl.cc
@@ -141,6 +141,8 @@
 NavigationControllerImpl::GetVisibleNavigationState() const {
   content::NavigationEntry* const entry =
       web_contents_->GetController().GetVisibleEntry();
+  CHECK(entry);
+
   if (entry->IsInitialEntry())
     return fuchsia::web::NavigationState();
 
@@ -374,6 +376,13 @@
                            fuchsia::web::NavigationState* difference) {
   DCHECK(difference);
 
+  // |new_entry| should not be empty when the difference is between states
+  // pre- and post-navigation. It is possible for non-navigation events (e.g.
+  // Renderer-process teardown) to trigger notifications, in which case both
+  // states may be empty (i.e. both come from the "initial" NavigationEntry).
+  if (new_entry.IsEmpty() && old_entry.IsEmpty())
+    return;
+
   DCHECK(new_entry.has_title());
   if (!old_entry.has_title() || (new_entry.title() != old_entry.title())) {
     difference->set_title(new_entry.title());
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index a39b165..81cf752 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -2833,6 +2833,12 @@
       <message name="IDS_IOS_TOOLS_MENU_ENTERPRISE_LEARN_MORE" desc="Link displayed to allow the user to learn more why the browser is managed by Enterprise policies. Shown at the bottom of the overflow menu. [iOS only]">
         Learn More
       </message>
+      <message name="IDS_IOS_TOOLS_MENU_FOLLOW" desc="The iOS menu item for following web content. [iOS only]">
+        Follow
+      </message>
+      <message name="IDS_IOS_TOOLS_MENU_UNFOLLOW" desc="The iOS menu item for unfollowing web content. [iOS only]">
+        Unfollow
+      </message>
       <message name="IDS_IOS_TOOLS_MENU_TRANSLATE" desc="The iOS menu item for translating the current page [iOS only]" meaning="Open the translate infobar [Length: unlimited]">
         Translate
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_FOLLOW.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_FOLLOW.png.sha1
new file mode 100644
index 0000000..be0a067
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_FOLLOW.png.sha1
@@ -0,0 +1 @@
+8f864cc2cb1c57c73cc8d199dd6a0cb029089659
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_UNFOLLOW.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_UNFOLLOW.png.sha1
new file mode 100644
index 0000000..736b7772
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_UNFOLLOW.png.sha1
@@ -0,0 +1 @@
+b03fdc858cbcc78b14f2f379acd34d2cd9013e6e
\ No newline at end of file
diff --git a/ios/chrome/browser/ui/first_run/first_run_egtest.mm b/ios/chrome/browser/ui/first_run/first_run_egtest.mm
index 4c64432d..064b3fbb 100644
--- a/ios/chrome/browser/ui/first_run/first_run_egtest.mm
+++ b/ios/chrome/browser/ui/first_run/first_run_egtest.mm
@@ -529,7 +529,8 @@
 // Tests that the forced sign-in screen is shown when the policy is enabled.
 // If the user says no during the FRE, then they should be re-prompted at the
 // end of the FRE.
-- (void)testSignInScreenUIWhenForcedByPolicy {
+// TODO(crbug.com/1282047): Re-enable when fixed.
+- (void)DISABLED_testSignInScreenUIWhenForcedByPolicy {
   AppLaunchConfiguration config = self.appConfigurationForTestCase;
 
   // Configure the policy to force sign-in.
@@ -813,7 +814,8 @@
 
 // Checks that the user is signed in and that sync is turned on after the user
 // chooses to turn on sync.
-- (void)testSignInAndTurnOnSync {
+// TODO(crbug.com/1282047): Re-enable when fixed.
+- (void)DISABLED_testSignInAndTurnOnSync {
   FakeChromeIdentity* fakeIdentity = [SigninEarlGrey fakeIdentity1];
   [SigninEarlGrey addFakeIdentity:fakeIdentity];
 
diff --git a/ios/chrome/browser/ui/follow/follow_action_state.h b/ios/chrome/browser/ui/follow/follow_action_state.h
index dcc45ddf..ab5091b 100644
--- a/ios/chrome/browser/ui/follow/follow_action_state.h
+++ b/ios/chrome/browser/ui/follow/follow_action_state.h
@@ -15,7 +15,7 @@
   // "Follow" action is shown but disabled.
   FollowActionStateDisabled,
   // "Follow" action is shown and enabled.
-  FollowActionStateEnabld,
+  FollowActionStateEnabled,
 };
 
 #endif  // IOS_CHROME_BROWSER_UI_FOLLOW_FOLLOW_ACTION_STATE_H_
diff --git a/ios/chrome/browser/ui/follow/follow_util.mm b/ios/chrome/browser/ui/follow/follow_util.mm
index e362b41..783db52 100644
--- a/ios/chrome/browser/ui/follow/follow_util.mm
+++ b/ios/chrome/browser/ui/follow/follow_util.mm
@@ -37,7 +37,7 @@
     if (!browserState->IsOffTheRecord() &&
         authenticationService->GetPrimaryIdentity(
             signin::ConsentLevel::kSignin)) {
-      return FollowActionStateEnabld;
+      return FollowActionStateEnabled;
     }
     return FollowActionStateDisabled;
   }
diff --git a/ios/chrome/browser/ui/popup_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/BUILD.gn
index eca4022..a1850d7 100644
--- a/ios/chrome/browser/ui/popup_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/BUILD.gn
@@ -78,7 +78,10 @@
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/default_promo:utils",
+    "//ios/chrome/browser/ui/follow:enums",
+    "//ios/chrome/browser/ui/follow:utils",
     "//ios/chrome/browser/ui/list_model",
+    "//ios/chrome/browser/ui/ntp:feature_flags",
     "//ios/chrome/browser/ui/ntp_tile_views:constants",
     "//ios/chrome/browser/ui/popup_menu/cells",
     "//ios/chrome/browser/ui/popup_menu/overflow_menu",
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
index 14ec91af..29cf02cf 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
@@ -16,6 +16,7 @@
     "resources:overflow_menu_action_bookmark",
     "resources:overflow_menu_action_edit_bookmark",
     "resources:overflow_menu_action_find_in_page",
+    "resources:overflow_menu_action_follow",
     "resources:overflow_menu_action_help",
     "resources:overflow_menu_action_incognito",
     "resources:overflow_menu_action_new_tab",
@@ -58,6 +59,7 @@
     "//ios/chrome/browser/ui/browser_container:ui",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/default_promo:utils",
+    "//ios/chrome/browser/ui/follow:enums",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/web",
     "//ios/chrome/browser/web/font_size",
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.h b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.h
index 1f553b6d..e869459 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.h
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.h
@@ -8,10 +8,12 @@
 #import <UIKit/UIKit.h>
 
 #import "ios/chrome/browser/ui/browser_container/browser_container_consumer.h"
+#import "ios/chrome/browser/ui/follow/follow_action_state.h"
 
 namespace bookmarks {
 class BookmarkModel;
 }
+
 @protocol ApplicationCommands;
 @protocol BrowserCommands;
 class BrowserPolicyConnectorIOS;
@@ -64,6 +66,9 @@
 // The current browser policy connector.
 @property(nonatomic, assign) BrowserPolicyConnectorIOS* browserPolicyConnector;
 
+// The follow action state. e.g. If the property value is FollowActionStateHide,
+// "Follow" action should be hidden in the overflow menu.
+@property(nonatomic, assign) FollowActionState followActionState;
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_POPUP_MENU_OVERFLOW_MENU_OVERFLOW_MENU_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm
index 061fee0..2ce40695f 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm
@@ -155,6 +155,7 @@
 
 @property(nonatomic, strong) OverflowMenuAction* addBookmarkAction;
 @property(nonatomic, strong) OverflowMenuAction* editBookmarkAction;
+@property(nonatomic, strong) OverflowMenuAction* followAction;
 @property(nonatomic, strong) OverflowMenuAction* readLaterAction;
 @property(nonatomic, strong) OverflowMenuAction* translateAction;
 @property(nonatomic, strong) OverflowMenuAction* requestDesktopAction;
@@ -378,6 +379,15 @@
                                                  actions:@[]
                                                   footer:nil];
 
+  if (self.followActionState != FollowActionStateHidden) {
+    // TODO(crbug.com/1264872): Show follow/unfollow according to website follow
+    // status.
+    self.followAction = CreateOverflowMenuAction(
+        IDS_IOS_TOOLS_MENU_FOLLOW, @"overflow_menu_action_follow", ^{
+          [weakSelf updateFollowStatus:YES];
+        });
+  }
+
   self.addBookmarkAction = CreateOverflowMenuAction(
       IDS_IOS_TOOLS_MENU_ADD_TO_BOOKMARKS, @"overflow_menu_action_bookmark", ^{
         [weakSelf addOrEditBookmark];
@@ -519,6 +529,12 @@
     self.findInPageAction, self.textZoomAction
   ];
 
+  // Add the follow action to the page action group if it is exists.
+  if (self.followAction) {
+    self.pageActionsGroup.actions = [@[ self.followAction ]
+        arrayByAddingObjectsFromArray:self.pageActionsGroup.actions];
+  }
+
   // Set footer (on last section), if any.
   if (_browserPolicyConnector &&
       _browserPolicyConnector->HasMachineLevelPolicies()) {
@@ -538,7 +554,10 @@
   // which is paused while overlays are displayed over the web content area.
   self.readLaterAction.enabled =
       !self.webContentAreaShowingOverlay && [self isCurrentURLWebURL];
-
+  if (self.followAction) {
+    self.followAction.enabled =
+        self.followActionState == FollowActionStateEnabled ? YES : NO;
+  }
   BOOL bookmarkEnabled =
       [self isCurrentURLWebURL] && [self isEditBookmarksEnabled];
   self.addBookmarkAction.enabled = bookmarkEnabled;
@@ -837,6 +856,12 @@
                                                   GURL(kChromeUINewTabURL))];
 }
 
+// Dismisses the menu and and updates the follow status of the website.
+- (void)updateFollowStatus:(BOOL)newStatus {
+  [self.dispatcher dismissPopupMenuAnimated:YES];
+  // TODO(crbug.com/1264872): implement.
+}
+
 // Dismisses the menu and adds the current page as a bookmark or opens the
 // bookmark edit screen if the current page is bookmarked.
 - (void)addOrEditBookmark {
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/BUILD.gn b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/BUILD.gn
index 80dadaad..0cfae2a04 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/BUILD.gn
@@ -28,6 +28,14 @@
   ]
 }
 
+imageset("overflow_menu_action_follow") {
+  sources = [
+    "overflow_menu_action_follow.imageset/Contents.json",
+    "overflow_menu_action_follow.imageset/overflow_menu_action_follow@2x.png",
+    "overflow_menu_action_follow.imageset/overflow_menu_action_follow@3x.png",
+  ]
+}
+
 imageset("overflow_menu_action_help") {
   sources = [
     "overflow_menu_action_help.imageset/Contents.json",
@@ -124,6 +132,14 @@
   ]
 }
 
+imageset("overflow_menu_action_unfollow") {
+  sources = [
+    "overflow_menu_action_unfollow.imageset/Contents.json",
+    "overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@2x.png",
+    "overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@3x.png",
+  ]
+}
+
 imageset("overflow_menu_destination_bookmarks") {
   sources = [
     "overflow_menu_destination_bookmarks.imageset/Contents.json",
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/Contents.json b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/Contents.json
new file mode 100644
index 0000000..dd3658aa
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "overflow_menu_action_follow@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "overflow_menu_action_follow@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties": {
+    "template-rendering-intent": "template"
+  }
+}
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@2x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@2x.png
new file mode 100644
index 0000000..1d817e4
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@3x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@3x.png
new file mode 100644
index 0000000..0c7b3d7
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/Contents.json b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/Contents.json
new file mode 100644
index 0000000..102574a
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "overflow_menu_action_unfollow@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "overflow_menu_action_unfollow@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties": {
+    "template-rendering-intent": "template"
+  }
+}
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@2x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@2x.png
new file mode 100644
index 0000000..a9790d97
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@3x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@3x.png
new file mode 100644
index 0000000..ab4acb6
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
index 1765292..098df92f 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
@@ -23,6 +23,9 @@
 #import "ios/chrome/browser/ui/commands/command_dispatcher.h"
 #import "ios/chrome/browser/ui/commands/find_in_page_commands.h"
 #import "ios/chrome/browser/ui/commands/popup_menu_commands.h"
+#import "ios/chrome/browser/ui/follow/follow_action_state.h"
+#import "ios/chrome/browser/ui/follow/follow_util.h"
+#import "ios/chrome/browser/ui/ntp/new_tab_page_feature.h"
 #import "ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h"
 #import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.h"
 #import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_swift.h"
@@ -37,6 +40,7 @@
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/browser/url_loading/url_loading_browser_agent.h"
 #import "ios/chrome/browser/web/web_navigation_browser_agent.h"
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -320,6 +324,12 @@
           overlayPresenter;
       self.overflowMenuMediator.browserPolicyConnector =
           GetApplicationContext()->GetBrowserPolicyConnector();
+      self.overflowMenuMediator.followActionState =
+          IsWebChannelsEnabled()
+              ? GetFollowActionState(
+                    self.browser->GetWebStateList()->GetActiveWebState(),
+                    self.browser->GetBrowserState())
+              : FollowActionStateHidden;
 
       // Replace the content blocker's consumer with the overflow menu mediator.
       self.contentBlockerMediator.consumer = self.overflowMenuMediator;
diff --git a/ios/chrome/browser/ui/settings/content_settings/BUILD.gn b/ios/chrome/browser/ui/settings/content_settings/BUILD.gn
index f330360..06f2b87 100644
--- a/ios/chrome/browser/ui/settings/content_settings/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/content_settings/BUILD.gn
@@ -9,8 +9,13 @@
     "block_popups_table_view_controller.mm",
     "content_settings_table_view_controller.h",
     "content_settings_table_view_controller.mm",
+    "default_page_mode_coordinator.h",
+    "default_page_mode_coordinator.mm",
+    "default_page_mode_mediator.h",
+    "default_page_mode_mediator.mm",
   ]
   deps = [
+    ":content_settings_ui",
     "//base",
     "//components/content_settings/core/browser",
     "//components/content_settings/core/common",
@@ -20,8 +25,10 @@
     "//ios/chrome/browser:pref_names",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/content_settings",
+    "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/net:crurl",
     "//ios/chrome/browser/ui:feature_flags",
+    "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/ntp:feature_flags",
     "//ios/chrome/browser/ui/settings:constants",
     "//ios/chrome/browser/ui/settings:settings_root",
@@ -37,6 +44,20 @@
   ]
 }
 
+source_set("content_settings_ui") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "default_page_mode_consumer.h",
+    "default_page_mode_table_view_controller.h",
+    "default_page_mode_table_view_controller.mm",
+  ]
+  deps = [
+    "//ios/chrome/browser/ui/settings:settings_root",
+    "//ios/chrome/browser/ui/table_view:utils",
+    "//ios/chrome/browser/ui/table_view/cells",
+  ]
+}
+
 source_set("unit_tests") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
@@ -51,6 +72,7 @@
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/browser_state:test_support",
     "//ios/chrome/browser/content_settings",
+    "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/ui/table_view:test_support",
     "//ios/chrome/browser/ui/table_view/cells",
     "//ios/web/public/test",
diff --git a/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.h b/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.h
index f5ff342..53719f6 100644
--- a/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.h
+++ b/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.h
@@ -8,16 +8,15 @@
 #import "ios/chrome/browser/ui/settings/settings_controller_protocol.h"
 #import "ios/chrome/browser/ui/settings/settings_root_table_view_controller.h"
 
-class ChromeBrowserState;
+class Browser;
 
 // Controller for the UI that allows the user to change content settings like
 // blocking popups.
 @interface ContentSettingsTableViewController
     : SettingsRootTableViewController <SettingsControllerProtocol>
 
-// The designated initializer. |browserState| must not be nil.
-- (instancetype)initWithBrowserState:(ChromeBrowserState*)browserState
-    NS_DESIGNATED_INITIALIZER;
+// The designated initializer. |browser| must not be null.
+- (instancetype)initWithBrowser:(Browser*)browser NS_DESIGNATED_INITIALIZER;
 - (instancetype)initWithStyle:(UITableViewStyle)style NS_UNAVAILABLE;
 @end
 
diff --git a/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.mm
index c1735252..574ae05 100644
--- a/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.mm
@@ -15,11 +15,13 @@
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/content_settings/host_content_settings_map_factory.h"
+#import "ios/chrome/browser/main/browser.h"
 #include "ios/chrome/browser/pref_names.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_feature.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_cell.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h"
 #import "ios/chrome/browser/ui/settings/content_settings/block_popups_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/content_settings/default_page_mode_coordinator.h"
 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 #import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
 #import "ios/chrome/browser/ui/settings/utils/content_setting_backed_boolean.h"
@@ -55,6 +57,7 @@
   ItemTypeSettingsBlockPopups = kItemTypeEnumZero,
   ItemTypeSettingsComposeEmail,
   ItemTypeSettingsShowLinkPreview,
+  ItemTypeSettingsDefaultSiteMode,
 };
 
 }  // namespace
@@ -67,6 +70,7 @@
   TableViewDetailIconItem* _blockPopupsDetailItem;
   TableViewDetailIconItem* _composeEmailDetailItem;
   TableViewMultiDetailTextItem* _openedInAnotherWindowItem;
+  TableViewDetailIconItem* _defaultSiteMode;
 }
 
 // PrefBackedBoolean for "Show Link Preview" setting state.
@@ -75,6 +79,10 @@
 // The item related to the switch for the "Show Link Preview" setting.
 @property(nonatomic, strong) SettingsSwitchItem* linkPreviewItem;
 
+// The coordinator showing the view to choose the defaultMode.
+@property(nonatomic, strong)
+    DefaultPageModeCoordinator* defaultModeViewController;
+
 // Helpers to create collection view items.
 - (id)blockPopupsItem;
 - (id)composeEmailItem;
@@ -82,17 +90,19 @@
 @end
 
 @implementation ContentSettingsTableViewController {
-  ChromeBrowserState* _browserState;  // weak
+  Browser* _browser;  // weak
 }
 
-- (instancetype)initWithBrowserState:(ChromeBrowserState*)browserState {
-  DCHECK(browserState);
+- (instancetype)initWithBrowser:(Browser*)browser {
+  DCHECK(browser);
 
   self = [super initWithStyle:ChromeTableViewStyle()];
   if (self) {
-    _browserState = browserState;
+    _browser = browser;
     self.title = l10n_util::GetNSString(IDS_IOS_CONTENT_SETTINGS_TITLE);
 
+    ChromeBrowserState* browserState = browser->GetBrowserState();
+
     HostContentSettingsMap* settingsMap =
         ios::HostContentSettingsMapFactory::GetForBrowserState(browserState);
     _disablePopupsSetting = [[ContentSettingBackedBoolean alloc]
@@ -102,7 +112,7 @@
     [_disablePopupsSetting setObserver:self];
 
     _linkPreviewEnabled = [[PrefBackedBoolean alloc]
-        initWithPrefService:_browserState->GetPrefs()
+        initWithPrefService:browserState->GetPrefs()
                    prefName:prefs::kLinkPreviewEnabled];
     [_linkPreviewEnabled setObserver:self];
   }
@@ -167,6 +177,11 @@
     [model addItem:[self linkPreviewItem]
         toSectionWithIdentifier:SectionIdentifierSettings];
   }
+
+  if (base::FeatureList::IsEnabled(kAddSettingForDefaultPageMode)) {
+    [model addItem:[self defaultSiteMode]
+        toSectionWithIdentifier:SectionIdentifierSettings];
+  }
 }
 
 #pragma mark - SettingsControllerProtocol
@@ -181,6 +196,16 @@
 
 #pragma mark - ContentSettingsTableViewController
 
+- (TableViewItem*)defaultSiteMode {
+  _defaultSiteMode = [[TableViewDetailIconItem alloc]
+      initWithType:ItemTypeSettingsDefaultSiteMode];
+  NSString* subtitle = @"TEST - Mobile";
+  _defaultSiteMode.text = @"TEST - Default Mode";
+  _defaultSiteMode.detailText = subtitle;
+  _defaultSiteMode.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+  return _defaultSiteMode;
+}
+
 - (TableViewItem*)blockPopupsItem {
   _blockPopupsDetailItem = [[TableViewDetailIconItem alloc]
       initWithType:ItemTypeSettingsBlockPopups];
@@ -276,7 +301,7 @@
     case ItemTypeSettingsBlockPopups: {
       BlockPopupsTableViewController* controller =
           [[BlockPopupsTableViewController alloc]
-              initWithBrowserState:_browserState];
+              initWithBrowserState:_browser->GetBrowserState()];
       controller.dispatcher = self.dispatcher;
       [self.navigationController pushViewController:controller animated:YES];
       break;
@@ -298,6 +323,12 @@
       }
       break;
     }
+    case ItemTypeSettingsDefaultSiteMode: {
+      self.defaultModeViewController = [[DefaultPageModeCoordinator alloc]
+          initWithBaseNavigationController:self.navigationController
+                                   browser:_browser];
+      [self.defaultModeViewController start];
+    }
   }
   [tableView deselectRowAtIndexPath:indexPath animated:YES];
 }
diff --git a/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller_unittest.mm
index 3295a73..a26588db 100644
--- a/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller_unittest.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.h"
 
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_controller_test.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ios/web/public/test/web_task_environment.h"
@@ -20,20 +21,17 @@
 class ContentSettingsTableViewControllerTest
     : public ChromeTableViewControllerTest {
  protected:
-  void SetUp() override {
-    ChromeTableViewControllerTest::SetUp();
-    TestChromeBrowserState::Builder test_cbs_builder;
-    chrome_browser_state_ = test_cbs_builder.Build();
-  }
+  ContentSettingsTableViewControllerTest()
+      : browser_(std::make_unique<TestBrowser>()) {}
 
   ChromeTableViewController* InstantiateController() override {
     return [[ContentSettingsTableViewController alloc]
-        initWithBrowserState:chrome_browser_state_.get()];
+        initWithBrowser:browser_.get()];
   }
 
  private:
   web::WebTaskEnvironment task_environment_;
-  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
+  std::unique_ptr<TestBrowser> browser_;
 };
 
 // Tests that there are 2 items in Content Settings.
diff --git a/ios/chrome/browser/ui/settings/content_settings/default_page_mode_consumer.h b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_consumer.h
new file mode 100644
index 0000000..32861e0
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_consumer.h
@@ -0,0 +1,25 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_CONSUMER_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_CONSUMER_H_
+
+#import <UIKit/UIKit.h>
+
+// The mode in which pages should be loaded.
+typedef NS_ENUM(NSUInteger, DefaultPageMode) {
+  DefaultPageModeMobile,
+  DefaultPageModeDesktop,
+};
+
+// Consumer protocol for the screen allowing the user to choose the default mode
+// (Desktop/Mobile) for loading pages.
+@protocol DefaultPageModeConsumer
+
+// Sets the mode in which pages are loaded by default.
+- (void)setDefaultPageMode:(DefaultPageMode)mode;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_CONSUMER_H_
diff --git a/ios/chrome/browser/ui/settings/content_settings/default_page_mode_coordinator.h b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_coordinator.h
new file mode 100644
index 0000000..200f452
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_coordinator.h
@@ -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.
+
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_COORDINATOR_H_
+
+#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
+
+// Coordinator to display the screen allowing the user to choose the default
+// mode (Desktop/Mobile) for loading pages.
+@interface DefaultPageModeCoordinator : ChromeCoordinator
+
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+                                   browser:(Browser*)browser NS_UNAVAILABLE;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/settings/content_settings/default_page_mode_coordinator.mm b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_coordinator.mm
new file mode 100644
index 0000000..d76ec8c
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_coordinator.mm
@@ -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.
+
+#import "ios/chrome/browser/ui/settings/content_settings/default_page_mode_coordinator.h"
+
+#import "ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator.h"
+#import "ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller.h"
+#import "ios/chrome/browser/ui/table_view/table_view_utils.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface DefaultPageModeCoordinator ()
+
+@property(nonatomic, strong) DefaultPageModeTableViewController* viewController;
+@property(nonatomic, strong) DefaultPageModeMediator* mediator;
+
+@end
+
+@implementation DefaultPageModeCoordinator
+
+@synthesize baseNavigationController = _baseNavigationController;
+
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser {
+  self = [super initWithBaseViewController:navigationController
+                                   browser:browser];
+  if (self) {
+    _baseNavigationController = navigationController;
+  }
+  return self;
+}
+
+- (void)start {
+  self.viewController = [[DefaultPageModeTableViewController alloc]
+      initWithStyle:ChromeTableViewStyle()];
+  self.mediator =
+      [[DefaultPageModeMediator alloc] initWithConsumer:self.viewController];
+  [self.baseNavigationController pushViewController:self.viewController
+                                           animated:YES];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator.h b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator.h
new file mode 100644
index 0000000..a14653f
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator.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 IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_MEDIATOR_H_
+
+#import <Foundation/Foundation.h>
+
+@protocol DefaultPageModeConsumer;
+
+// Mediator for the screen allowing the user to choose the default mode
+// (Desktop/Mobile) for loading pages.
+@interface DefaultPageModeMediator : NSObject
+
+- (instancetype)initWithConsumer:(id<DefaultPageModeConsumer>)consumer
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator.mm b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator.mm
new file mode 100644
index 0000000..8c7cc3c
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator.mm
@@ -0,0 +1,29 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator.h"
+
+#import "ios/chrome/browser/ui/settings/content_settings/default_page_mode_consumer.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface DefaultPageModeMediator ()
+
+@property(nonatomic, weak) id<DefaultPageModeConsumer> consumer;
+
+@end
+
+@implementation DefaultPageModeMediator
+
+- (instancetype)initWithConsumer:(id<DefaultPageModeConsumer>)consumer {
+  self = [super init];
+  if (self) {
+    _consumer = consumer;
+  }
+  return self;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller.h b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller.h
new file mode 100644
index 0000000..5670228
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller.h
@@ -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.
+
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_TABLE_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_TABLE_VIEW_CONTROLLER_H_
+
+#import "ios/chrome/browser/ui/settings/content_settings/default_page_mode_consumer.h"
+#import "ios/chrome/browser/ui/settings/settings_controller_protocol.h"
+#import "ios/chrome/browser/ui/settings/settings_root_table_view_controller.h"
+
+// ViewController for the screen allowing the user to choose the default mode
+// (Desktop/Mobile) for loading pages.
+@interface DefaultPageModeTableViewController
+    : SettingsRootTableViewController <DefaultPageModeConsumer,
+                                       SettingsControllerProtocol>
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_CONTENT_SETTINGS_DEFAULT_PAGE_MODE_TABLE_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller.mm b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller.mm
new file mode 100644
index 0000000..e0949fa
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller.mm
@@ -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.
+
+#import "ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller.h"
+
+#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation DefaultPageModeTableViewController
+
+#pragma mark - DefaultPageModeConsumer
+
+- (void)setDefaultPageMode:(DefaultPageMode)mode {
+  // TODO(crbug.com/1276922): change the selected cell based on the mode.
+}
+
+#pragma mark - SettingsControllerProtocol
+
+- (void)reportDismissalUserAction {
+  // TODO(crbug.com/1276922): Add UserAction recording.
+}
+
+- (void)reportBackUserAction {
+  // TODO(crbug.com/1276922): Add UserAction recording.
+}
+
+@end
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
index be0024f..0a371003 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -1257,8 +1257,8 @@
     }
     case SettingsItemTypeContentSettings:
       base::RecordAction(base::UserMetricsAction("Settings.ContentSettings"));
-      controller = [[ContentSettingsTableViewController alloc]
-          initWithBrowserState:_browserState];
+      controller =
+          [[ContentSettingsTableViewController alloc] initWithBrowser:_browser];
       break;
     case SettingsItemTypeBandwidth:
       base::RecordAction(base::UserMetricsAction("Settings.Bandwidth"));
diff --git a/media/audio/audio_encoders_unittest.cc b/media/audio/audio_encoders_unittest.cc
index 0be36ca..cf56840 100644
--- a/media/audio/audio_encoders_unittest.cc
+++ b/media/audio/audio_encoders_unittest.cc
@@ -76,8 +76,8 @@
     encoder_ = std::make_unique<AudioOpusEncoder>();
 
     bool called_done = false;
-    AudioEncoder::StatusCB done_cb =
-        base::BindLambdaForTesting([&](Status error) {
+    AudioEncoder::EncoderStatusCB done_cb =
+        base::BindLambdaForTesting([&](EncoderStatus error) {
           if (!error.is_ok())
             FAIL() << error.message();
           called_done = true;
@@ -100,7 +100,7 @@
     audio_source_.OnMoreData(base::TimeDelta(), timestamp, 0, audio_bus.get());
 
     bool called_done = false;
-    auto done_cb = base::BindLambdaForTesting([&](Status error) {
+    auto done_cb = base::BindLambdaForTesting([&](EncoderStatus error) {
       if (!error.is_ok())
         FAIL() << error.message();
       called_done = true;
@@ -155,7 +155,7 @@
     }
 
     bool flush_done = false;
-    auto done_cb = base::BindLambdaForTesting([&](Status error) {
+    auto done_cb = base::BindLambdaForTesting([&](EncoderStatus error) {
       if (!error.is_ok())
         FAIL() << error.message();
       flush_done = true;
@@ -248,7 +248,7 @@
   EXPECT_EQ(3u, timestamps.size());
   EXPECT_EQ(ts2, timestamps[2]);
 
-  encoder()->Flush(base::BindOnce([](Status error) {
+  encoder()->Flush(base::BindOnce([](EncoderStatus error) {
     if (!error.is_ok())
       FAIL() << error.message();
   }));
@@ -304,7 +304,7 @@
   // them before we destroy the encoder. Flushing should trigger the encode
   // callback and we should be able to decode the resulting encoded frames.
   if (total_frames > frames_in_60_ms) {
-    encoder()->Flush(base::BindOnce([](Status error) {
+    encoder()->Flush(base::BindOnce([](EncoderStatus error) {
       if (!error.is_ok())
         FAIL() << error.message();
     }));
diff --git a/media/audio/audio_opus_encoder.cc b/media/audio/audio_opus_encoder.cc
index 3b65eb4..e9a84c5 100644
--- a/media/audio/audio_opus_encoder.cc
+++ b/media/audio/audio_opus_encoder.cc
@@ -12,8 +12,7 @@
 #include "base/numerics/checked_math.h"
 #include "base/strings/stringprintf.h"
 #include "media/base/bind_to_current_loop.h"
-#include "media/base/status.h"
-#include "media/base/status_codes.h"
+#include "media/base/encoder_status.h"
 #include "media/base/timestamp_constants.h"
 
 namespace media {
@@ -109,26 +108,26 @@
 
 void AudioOpusEncoder::Initialize(const Options& options,
                                   OutputCB output_callback,
-                                  StatusCB done_cb) {
+                                  EncoderStatusCB done_cb) {
   DCHECK(!output_callback.is_null());
   DCHECK(!done_cb.is_null());
 
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (opus_encoder_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeTwice);
+    std::move(done_cb).Run(EncoderStatus::Codes::kEncoderInitializeTwice);
     return;
   }
 
   options_ = options;
   input_params_ = CreateInputParams(options);
   if (!input_params_.IsValid()) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializationError);
+    std::move(done_cb).Run(EncoderStatus::Codes::kEncoderInitializationError);
     return;
   }
 
   converted_params_ = CreateOpusCompatibleParams(input_params_);
   if (!input_params_.IsValid()) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializationError);
+    std::move(done_cb).Run(EncoderStatus::Codes::kEncoderInitializationError);
     return;
   }
 
@@ -155,7 +154,7 @@
       converted_params_.frames_per_buffer()));
 
   output_cb_ = BindToCurrentLoop(std::move(output_callback));
-  std::move(done_cb).Run(OkStatus());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
 AudioOpusEncoder::~AudioOpusEncoder() = default;
@@ -207,7 +206,7 @@
 
 void AudioOpusEncoder::Encode(std::unique_ptr<AudioBus> audio_bus,
                               base::TimeTicks capture_time,
-                              StatusCB done_cb) {
+                              EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(audio_bus->channels(), input_params_.channels());
   DCHECK(!done_cb.is_null());
@@ -216,7 +215,7 @@
   current_done_cb_ = BindToCurrentLoop(std::move(done_cb));
   if (!opus_encoder_) {
     std::move(current_done_cb_)
-        .Run(StatusCode::kEncoderInitializeNeverCompleted);
+        .Run(EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
@@ -229,16 +228,17 @@
   if (!current_done_cb_.is_null()) {
     // Is |current_done_cb_| is null, it means OnFifoOutput() has already
     // reported an error.
-    std::move(current_done_cb_).Run(OkStatus());
+    std::move(current_done_cb_).Run(EncoderStatus::Codes::kOk);
   }
 }
 
-void AudioOpusEncoder::Flush(StatusCB done_cb) {
+void AudioOpusEncoder::Flush(EncoderStatusCB done_cb) {
   DCHECK(!done_cb.is_null());
 
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (!opus_encoder_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(done_cb).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
@@ -248,7 +248,7 @@
   if (!current_done_cb_.is_null()) {
     // Is |current_done_cb_| is null, it means OnFifoOutput() has already
     // reported an error.
-    std::move(current_done_cb_).Run(OkStatus());
+    std::move(current_done_cb_).Run(EncoderStatus::Codes::kOk);
   }
 }
 
@@ -267,7 +267,8 @@
 
   if (result < 0 && !current_done_cb_.is_null()) {
     std::move(current_done_cb_)
-        .Run(Status(StatusCode::kEncoderFailedEncode, opus_strerror(result)));
+        .Run(EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                           opus_strerror(result)));
     return;
   }
 
@@ -296,7 +297,7 @@
 
 // Creates and returns the libopus encoder instance. Returns nullptr if the
 // encoder creation fails.
-StatusOr<OwnedOpusEncoder> AudioOpusEncoder::CreateOpusEncoder() {
+EncoderStatus::Or<OwnedOpusEncoder> AudioOpusEncoder::CreateOpusEncoder() {
   int opus_result;
   OwnedOpusEncoder encoder(
       opus_encoder_create(converted_params_.sample_rate(),
@@ -305,8 +306,8 @@
       OpusEncoderDeleter);
 
   if (opus_result < 0) {
-    return Status(
-        StatusCode::kEncoderInitializationError,
+    return EncoderStatus(
+        EncoderStatus::Codes::kEncoderInitializationError,
         base::StringPrintf(
             "Couldn't init Opus encoder: %s, sample rate: %d, channels: %d",
             opus_strerror(opus_result), converted_params_.sample_rate(),
@@ -317,8 +318,8 @@
       options_.bitrate.has_value() ? options_.bitrate.value() : OPUS_AUTO;
   if (encoder &&
       opus_encoder_ctl(encoder.get(), OPUS_SET_BITRATE(bitrate)) != OPUS_OK) {
-    return Status(
-        StatusCode::kEncoderInitializationError,
+    return EncoderStatus(
+        EncoderStatus::Codes::kEncoderInitializationError,
         base::StringPrintf("Failed to set Opus bitrate: %d", bitrate));
   }
 
diff --git a/media/audio/audio_opus_encoder.h b/media/audio/audio_opus_encoder.h
index c11c5c0..74fc000 100644
--- a/media/audio/audio_opus_encoder.h
+++ b/media/audio/audio_opus_encoder.h
@@ -33,13 +33,13 @@
   // AudioEncoder:
   void Initialize(const Options& options,
                   OutputCB output_callback,
-                  StatusCB done_cb) override;
+                  EncoderStatusCB done_cb) override;
 
   void Encode(std::unique_ptr<AudioBus> audio_bus,
               base::TimeTicks capture_time,
-              StatusCB done_cb) override;
+              EncoderStatusCB done_cb) override;
 
-  void Flush(StatusCB done_cb) override;
+  void Flush(EncoderStatusCB done_cb) override;
 
   static constexpr int kMinBitrate = 6000;
 
@@ -50,7 +50,7 @@
 
   CodecDescription PrepareExtraData();
 
-  StatusOr<OwnedOpusEncoder> CreateOpusEncoder();
+  EncoderStatus::Or<OwnedOpusEncoder> CreateOpusEncoder();
 
   AudioParameters input_params_;
 
@@ -81,7 +81,7 @@
 
   // Callback for reporting completion and status of the current Flush() or
   // Encoder()
-  StatusCB current_done_cb_;
+  EncoderStatusCB current_done_cb_;
 
   // True if the next output needs to have extra_data in it, only happens once.
   bool need_to_emit_extra_data_ = true;
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index 86557c5a..2c5c2e06 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -158,6 +158,7 @@
     "djb2.cc",
     "djb2.h",
     "eme_constants.h",
+    "encoder_status.h",
     "encryption_pattern.cc",
     "encryption_pattern.h",
     "encryption_scheme.cc",
diff --git a/media/base/async_destroy_video_encoder.h b/media/base/async_destroy_video_encoder.h
index ffe5f55..6debc10 100644
--- a/media/base/async_destroy_video_encoder.h
+++ b/media/base/async_destroy_video_encoder.h
@@ -39,7 +39,7 @@
   void Initialize(VideoCodecProfile profile,
                   const Options& options,
                   OutputCB output_cb,
-                  StatusCB done_cb) override {
+                  EncoderStatusCB done_cb) override {
     DCHECK(wrapped_encoder_);
     wrapped_encoder_->Initialize(profile, options, std::move(output_cb),
                                  std::move(done_cb));
@@ -47,20 +47,20 @@
 
   void Encode(scoped_refptr<VideoFrame> frame,
               bool key_frame,
-              StatusCB done_cb) override {
+              EncoderStatusCB done_cb) override {
     DCHECK(wrapped_encoder_);
     wrapped_encoder_->Encode(std::move(frame), key_frame, std::move(done_cb));
   }
 
   void ChangeOptions(const Options& options,
                      OutputCB output_cb,
-                     StatusCB done_cb) override {
+                     EncoderStatusCB done_cb) override {
     DCHECK(wrapped_encoder_);
     wrapped_encoder_->ChangeOptions(options, std::move(output_cb),
                                     std::move(done_cb));
   }
 
-  void Flush(StatusCB done_cb) override {
+  void Flush(EncoderStatusCB done_cb) override {
     DCHECK(wrapped_encoder_);
     wrapped_encoder_->Flush(std::move(done_cb));
   }
diff --git a/media/base/audio_encoder.h b/media/base/audio_encoder.h
index 3d49b56..c94c137 100644
--- a/media/base/audio_encoder.h
+++ b/media/base/audio_encoder.h
@@ -14,8 +14,8 @@
 #include "media/base/audio_bus.h"
 #include "media/base/audio_codecs.h"
 #include "media/base/audio_parameters.h"
+#include "media/base/encoder_status.h"
 #include "media/base/media_export.h"
-#include "media/base/status.h"
 #include "media/base/timestamp_constants.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -85,7 +85,7 @@
                                    absl::optional<CodecDescription>)>;
 
   // Signature of the callback to report errors.
-  using StatusCB = base::OnceCallback<void(Status error)>;
+  using EncoderStatusCB = base::OnceCallback<void(EncoderStatus error)>;
 
   AudioEncoder();
   AudioEncoder(const AudioEncoder&) = delete;
@@ -99,7 +99,7 @@
   // No AudioEncoder calls should be made before |done_cb| is executed.
   virtual void Initialize(const Options& options,
                           OutputCB output_cb,
-                          StatusCB done_cb) = 0;
+                          EncoderStatusCB done_cb) = 0;
 
   // Requests contents of |audio_bus| to be encoded.
   // |capture_time| is a media time at the end of the audio piece in the
@@ -115,12 +115,12 @@
   // including before Encode() returns.
   virtual void Encode(std::unique_ptr<AudioBus> audio_bus,
                       base::TimeTicks capture_time,
-                      StatusCB done_cb) = 0;
+                      EncoderStatusCB done_cb) = 0;
 
   // Some encoders may choose to buffer audio frames before they encode them.
   // Requests all outputs for already encoded frames to be
   // produced via |output_cb| and calls |done_cb| after that.
-  virtual void Flush(StatusCB done_cb) = 0;
+  virtual void Flush(EncoderStatusCB done_cb) = 0;
 
  protected:
   Options options_;
diff --git a/media/base/encoder_status.h b/media/base/encoder_status.h
new file mode 100644
index 0000000..3ec701c
--- /dev/null
+++ b/media/base/encoder_status.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 MEDIA_BASE_ENCODER_STATUS_H_
+#define MEDIA_BASE_ENCODER_STATUS_H_
+
+#include "media/base/status.h"
+
+namespace media {
+
+struct EncoderStatusTraits {
+  enum class Codes : StatusCodeType {
+    kOk = 0,
+    kEncoderInitializeNeverCompleted = 1,
+    kEncoderInitializeTwice = 2,
+    kEncoderFailedEncode = 3,
+    kEncoderUnsupportedProfile = 4,
+    kEncoderUnsupportedCodec = 5,
+    kEncoderUnsupportedConfig = 6,
+    kEncoderInitializationError = 7,
+    kEncoderFailedFlush = 8,
+    kEncoderMojoConnectionError = 9,
+  };
+  static constexpr StatusGroupType Group() { return "EncoderStatusCodes"; }
+  static constexpr Codes DefaultEnumValue() { return Codes::kOk; }
+};
+
+using EncoderStatus = TypedStatus<EncoderStatusTraits>;
+
+}  // namespace media
+
+#endif  // MEDIA_BASE_ENCODER_STATUS_H_
diff --git a/media/base/media_log.cc b/media/base/media_log.cc
index 6cb08ed6..93e712d8 100644
--- a/media/base/media_log.cc
+++ b/media/base/media_log.cc
@@ -7,7 +7,6 @@
 #include <utility>
 
 #include "base/atomic_sequence_num.h"
-#include "base/json/json_writer.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_util.h"
 #include "base/values.h"
@@ -63,13 +62,6 @@
   AddLogRecord(std::move(record));
 }
 
-void MediaLog::NotifyError(Status status) {
-  DCHECK(!status.is_ok());
-  std::string output_str;
-  base::JSONWriter::Write(MediaSerialize(status), &output_str);
-  AddMessage(MediaLogMessageLevel::kERROR, output_str);
-}
-
 void MediaLog::OnWebMediaPlayerDestroyedLocked() {}
 void MediaLog::OnWebMediaPlayerDestroyed() {
   AddEvent<MediaLogEvent::kWebMediaPlayerDestroyed>();
diff --git a/media/base/media_log.h b/media/base/media_log.h
index b6cf2bf..01db50f7 100644
--- a/media/base/media_log.h
+++ b/media/base/media_log.h
@@ -14,6 +14,7 @@
 #include <utility>
 
 #include "base/gtest_prod_util.h"
+#include "base/json/json_writer.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
@@ -95,7 +96,13 @@
   void NotifyError(PipelineStatus status);
 
   // Notify a non-ok Status. This method Should _not_ be given an OK status.
-  void NotifyError(Status status);
+  template <typename T>
+  void NotifyError(const TypedStatus<T>& status) {
+    DCHECK(!status.is_ok());
+    std::string output_str;
+    base::JSONWriter::Write(MediaSerialize(status), &output_str);
+    AddMessage(MediaLogMessageLevel::kERROR, output_str);
+  }
 
   // Notify the media log that the player is destroyed. Some implementations
   // will want to change event handling based on this.
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index 2a115e4d..4f2e98c 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -294,17 +294,17 @@
               Initialize,
               (const AudioEncoder::Options& options,
                AudioEncoder::OutputCB output_cb,
-               AudioEncoder::StatusCB done_cb),
+               AudioEncoder::EncoderStatusCB done_cb),
               (override));
 
   MOCK_METHOD(void,
               Encode,
               (std::unique_ptr<AudioBus> audio_bus,
                base::TimeTicks capture_time,
-               AudioEncoder::StatusCB done_cb),
+               AudioEncoder::EncoderStatusCB done_cb),
               (override));
 
-  MOCK_METHOD(void, Flush, (AudioEncoder::StatusCB done_cb), (override));
+  MOCK_METHOD(void, Flush, (AudioEncoder::EncoderStatusCB done_cb), (override));
 
   // A function for mocking destructor calls
   MOCK_METHOD(void, OnDestruct, ());
@@ -325,24 +325,24 @@
               (VideoCodecProfile profile,
                const VideoEncoder::Options& options,
                VideoEncoder::OutputCB output_cb,
-               VideoEncoder::StatusCB done_cb),
+               VideoEncoder::EncoderStatusCB done_cb),
               (override));
 
   MOCK_METHOD(void,
               Encode,
               (scoped_refptr<VideoFrame> frame,
                bool key_frame,
-               VideoEncoder::StatusCB done_cb),
+               VideoEncoder::EncoderStatusCB done_cb),
               (override));
 
   MOCK_METHOD(void,
               ChangeOptions,
               (const VideoEncoder::Options& options,
                VideoEncoder::OutputCB output_cb,
-               VideoEncoder::StatusCB done_cb),
+               VideoEncoder::EncoderStatusCB done_cb),
               (override));
 
-  MOCK_METHOD(void, Flush, (VideoEncoder::StatusCB done_cb), (override));
+  MOCK_METHOD(void, Flush, (VideoEncoder::EncoderStatusCB done_cb), (override));
 
   // A function for mocking destructor calls
   MOCK_METHOD(void, Dtor, ());
diff --git a/media/base/offloading_audio_encoder.cc b/media/base/offloading_audio_encoder.cc
index 8dad7381..eafa2f0 100644
--- a/media/base/offloading_audio_encoder.cc
+++ b/media/base/offloading_audio_encoder.cc
@@ -34,7 +34,7 @@
 
 void OffloadingAudioEncoder::Initialize(const Options& options,
                                         OutputCB output_cb,
-                                        StatusCB done_cb) {
+                                        EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   work_runner_->PostTask(
       FROM_HERE, base::BindOnce(&AudioEncoder::Initialize,
@@ -45,7 +45,7 @@
 
 void OffloadingAudioEncoder::Encode(std::unique_ptr<AudioBus> audio_bus,
                                     base::TimeTicks capture_time,
-                                    StatusCB done_cb) {
+                                    EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   work_runner_->PostTask(
       FROM_HERE, base::BindOnce(&AudioEncoder::Encode,
@@ -54,7 +54,7 @@
                                 WrapCallback(std::move(done_cb))));
 }
 
-void OffloadingAudioEncoder::Flush(StatusCB done_cb) {
+void OffloadingAudioEncoder::Flush(EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   work_runner_->PostTask(
       FROM_HERE, base::BindOnce(&AudioEncoder::Flush,
@@ -73,4 +73,4 @@
   return base::BindPostTask(callback_runner_, std::move(cb));
 }
 
-}  // namespace media
\ No newline at end of file
+}  // namespace media
diff --git a/media/base/offloading_audio_encoder.h b/media/base/offloading_audio_encoder.h
index 39f4218..63a2f70 100644
--- a/media/base/offloading_audio_encoder.h
+++ b/media/base/offloading_audio_encoder.h
@@ -39,13 +39,13 @@
 
   void Initialize(const Options& options,
                   OutputCB output_cb,
-                  StatusCB done_cb) override;
+                  EncoderStatusCB done_cb) override;
 
   void Encode(std::unique_ptr<AudioBus> audio_bus,
               base::TimeTicks capture_time,
-              StatusCB done_cb) override;
+              EncoderStatusCB done_cb) override;
 
-  void Flush(StatusCB done_cb) override;
+  void Flush(EncoderStatusCB done_cb) override;
 
  private:
   template <class T>
diff --git a/media/base/offloading_audio_encoder_unittest.cc b/media/base/offloading_audio_encoder_unittest.cc
index 76d7e362..467ce41 100644
--- a/media/base/offloading_audio_encoder_unittest.cc
+++ b/media/base/offloading_audio_encoder_unittest.cc
@@ -60,19 +60,20 @@
         EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
         called_output = true;
       });
-  AudioEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
-    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
-    called_done = true;
-  });
+  AudioEncoder::EncoderStatusCB done_cb =
+      base::BindLambdaForTesting([&](EncoderStatus s) {
+        EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+        called_done = true;
+      });
 
   EXPECT_CALL(*mock_audio_encoder_, Initialize(_, _, _))
       .WillOnce(Invoke([this](const AudioEncoder::Options& options,
                               AudioEncoder::OutputCB output_cb,
-                              AudioEncoder::StatusCB done_cb) {
+                              AudioEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
         AudioParameters params;
         EncodedAudioBuffer buf(params, nullptr, 0, base::TimeTicks());
-        std::move(done_cb).Run(Status());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 
         // Usually |output_cb| is not called by Initialize() but for this
         // test it doesn't matter. We only care about a task runner used
@@ -89,17 +90,18 @@
 
 TEST_F(OffloadingAudioEncoderTest, Encode) {
   bool called_done = false;
-  AudioEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
-    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
-    called_done = true;
-  });
+  AudioEncoder::EncoderStatusCB done_cb =
+      base::BindLambdaForTesting([&](EncoderStatus s) {
+        EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+        called_done = true;
+      });
 
   EXPECT_CALL(*mock_audio_encoder_, Encode(_, _, _))
       .WillOnce(Invoke([this](std::unique_ptr<AudioBus> audio_bus,
                               base::TimeTicks capture_time,
-                              AudioEncoder::StatusCB done_cb) {
+                              AudioEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
-        std::move(done_cb).Run(Status());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
   base::TimeTicks ts;
@@ -110,15 +112,16 @@
 
 TEST_F(OffloadingAudioEncoderTest, Flush) {
   bool called_done = false;
-  AudioEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
-    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
-    called_done = true;
-  });
+  AudioEncoder::EncoderStatusCB done_cb =
+      base::BindLambdaForTesting([&](EncoderStatus s) {
+        EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+        called_done = true;
+      });
 
   EXPECT_CALL(*mock_audio_encoder_, Flush(_))
-      .WillOnce(Invoke([this](AudioEncoder::StatusCB done_cb) {
+      .WillOnce(Invoke([this](AudioEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
-        std::move(done_cb).Run(Status());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
   offloading_encoder_->Flush(std::move(done_cb));
diff --git a/media/base/offloading_video_encoder.cc b/media/base/offloading_video_encoder.cc
index 935cf74..737927f 100644
--- a/media/base/offloading_video_encoder.cc
+++ b/media/base/offloading_video_encoder.cc
@@ -37,7 +37,7 @@
 void OffloadingVideoEncoder::Initialize(VideoCodecProfile profile,
                                         const Options& options,
                                         OutputCB output_cb,
-                                        StatusCB done_cb) {
+                                        EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   work_runner_->PostTask(
       FROM_HERE,
@@ -49,7 +49,7 @@
 
 void OffloadingVideoEncoder::Encode(scoped_refptr<VideoFrame> frame,
                                     bool key_frame,
-                                    StatusCB done_cb) {
+                                    EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   work_runner_->PostTask(
       FROM_HERE,
@@ -60,7 +60,7 @@
 
 void OffloadingVideoEncoder::ChangeOptions(const Options& options,
                                            OutputCB output_cb,
-                                           StatusCB done_cb) {
+                                           EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   work_runner_->PostTask(
       FROM_HERE, base::BindOnce(&VideoEncoder::ChangeOptions,
@@ -69,7 +69,7 @@
                                 WrapCallback(std::move(done_cb))));
 }
 
-void OffloadingVideoEncoder::Flush(StatusCB done_cb) {
+void OffloadingVideoEncoder::Flush(EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   work_runner_->PostTask(
       FROM_HERE, base::BindOnce(&VideoEncoder::Flush,
diff --git a/media/base/offloading_video_encoder.h b/media/base/offloading_video_encoder.h
index 6000c4c..8b296d61 100644
--- a/media/base/offloading_video_encoder.h
+++ b/media/base/offloading_video_encoder.h
@@ -40,17 +40,17 @@
   void Initialize(VideoCodecProfile profile,
                   const Options& options,
                   OutputCB output_cb,
-                  StatusCB done_cb) override;
+                  EncoderStatusCB done_cb) override;
 
   void Encode(scoped_refptr<VideoFrame> frame,
               bool key_frame,
-              StatusCB done_cb) override;
+              EncoderStatusCB done_cb) override;
 
   void ChangeOptions(const Options& options,
                      OutputCB output_cb,
-                     StatusCB done_cb) override;
+                     EncoderStatusCB done_cb) override;
 
-  void Flush(StatusCB done_cb) override;
+  void Flush(EncoderStatusCB done_cb) override;
 
  private:
   template <class T>
diff --git a/media/base/offloading_video_encoder_unittest.cc b/media/base/offloading_video_encoder_unittest.cc
index c6374ad6..c5afc02 100644
--- a/media/base/offloading_video_encoder_unittest.cc
+++ b/media/base/offloading_video_encoder_unittest.cc
@@ -62,18 +62,19 @@
         EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
         called_output = true;
       });
-  VideoEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
-    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
-    called_done = true;
-  });
+  VideoEncoder::EncoderStatusCB done_cb =
+      base::BindLambdaForTesting([&](EncoderStatus s) {
+        EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+        called_done = true;
+      });
 
   EXPECT_CALL(*mock_video_encoder_, Initialize(_, _, _, _))
       .WillOnce(Invoke([this](VideoCodecProfile profile,
                               const VideoEncoder::Options& options,
                               VideoEncoder::OutputCB output_cb,
-                              VideoEncoder::StatusCB done_cb) {
+                              VideoEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
-        std::move(done_cb).Run(Status());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
         std::move(output_cb).Run(VideoEncoderOutput(), {});
       }));
 
@@ -86,16 +87,17 @@
 
 TEST_F(OffloadingVideoEncoderTest, Encode) {
   bool called_done = false;
-  VideoEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
-    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
-    called_done = true;
-  });
+  VideoEncoder::EncoderStatusCB done_cb =
+      base::BindLambdaForTesting([&](EncoderStatus s) {
+        EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+        called_done = true;
+      });
 
   EXPECT_CALL(*mock_video_encoder_, Encode(_, _, _))
       .WillOnce(Invoke([this](scoped_refptr<VideoFrame> frame, bool key_frame,
-                              VideoEncoder::StatusCB done_cb) {
+                              VideoEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
-        std::move(done_cb).Run(Status());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
   offloading_encoder_->Encode(nullptr, false, std::move(done_cb));
@@ -106,10 +108,11 @@
 TEST_F(OffloadingVideoEncoderTest, ChangeOptions) {
   bool called_done = false;
   VideoEncoder::Options options;
-  VideoEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
-    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
-    called_done = true;
-  });
+  VideoEncoder::EncoderStatusCB done_cb =
+      base::BindLambdaForTesting([&](EncoderStatus s) {
+        EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+        called_done = true;
+      });
 
   VideoEncoder::OutputCB output_cb = base::BindRepeating(
       [](VideoEncoderOutput, absl::optional<VideoEncoder::CodecDescription>) {
@@ -118,9 +121,9 @@
   EXPECT_CALL(*mock_video_encoder_, ChangeOptions(_, _, _))
       .WillOnce(Invoke([this](const VideoEncoder::Options& options,
                               VideoEncoder::OutputCB output_cb,
-                              VideoEncoder::StatusCB done_cb) {
+                              VideoEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
-        std::move(done_cb).Run(Status());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
   offloading_encoder_->ChangeOptions(options, std::move(output_cb),
@@ -131,15 +134,16 @@
 
 TEST_F(OffloadingVideoEncoderTest, Flush) {
   bool called_done = false;
-  VideoEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
-    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
-    called_done = true;
-  });
+  VideoEncoder::EncoderStatusCB done_cb =
+      base::BindLambdaForTesting([&](EncoderStatus s) {
+        EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+        called_done = true;
+      });
 
   EXPECT_CALL(*mock_video_encoder_, Flush(_))
-      .WillOnce(Invoke([this](VideoEncoder::StatusCB done_cb) {
+      .WillOnce(Invoke([this](VideoEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
-        std::move(done_cb).Run(Status());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
   offloading_encoder_->Flush(std::move(done_cb));
diff --git a/media/base/status.h b/media/base/status.h
index 5a5f414..3b6a1b5 100644
--- a/media/base/status.h
+++ b/media/base/status.h
@@ -24,6 +24,15 @@
 struct StructTraits;
 }  // namespace mojo
 
+#define POST_STATUS_AND_RETURN_ON_FAILURE(eval_to_status, cb, ret) \
+  do {                                                             \
+    const auto EVALUATED = (eval_to_status);                       \
+    if (!EVALUATED.is_ok()) {                                      \
+      cb.Run(std::move(EVALUATED));                                \
+      return ret;                                                  \
+    }                                                              \
+  } while (0)
+
 namespace media {
 
 // See media/base/status.md for details and instructions for
@@ -359,6 +368,9 @@
  private:
   std::unique_ptr<internal::StatusData> data_;
 
+  // Let the status sink talk about the internal data.
+  friend class StatusSink;
+
   template <typename StatusEnum, typename DataView>
   friend struct mojo::StructTraits;
 
diff --git a/media/base/status_codes.h b/media/base/status_codes.h
index 3ac087a..d48bd8e 100644
--- a/media/base/status_codes.h
+++ b/media/base/status_codes.h
@@ -30,6 +30,7 @@
   kAborted = 0x0001,
   kInvalidArgument = 0x0002,
   kKeyFrameRequired = 0x0003,
+  kWrappedError = 0x0004,
 
   // Decoder Errors: 0x01
   kDecoderInitializeNeverCompleted = 0x0101,
@@ -64,17 +65,6 @@
   kMojoDecoderNoConnection = 0x0404,
   kMojoDecoderDeletedWithoutInitialization = 0x0405,
 
-  // Encoder Error: 0x06
-  kEncoderInitializeNeverCompleted = 0x0601,
-  kEncoderInitializeTwice = 0x0602,
-  kEncoderFailedEncode = 0x0603,
-  kEncoderUnsupportedProfile = 0x0604,
-  kEncoderUnsupportedCodec = 0x0605,
-  kEncoderUnsupportedConfig = 0x0606,
-  kEncoderInitializationError = 0x0607,
-  kEncoderFailedFlush = 0x0608,
-  kEncoderMojoConnectionError = 0x0609,
-
   // Format Errors: 0x08
   kH264ParsingError = 0x0801,
   kH264BufferTooSmall = 0x0802,
diff --git a/media/base/video_encoder.h b/media/base/video_encoder.h
index 40464d3..15f0d5b2 100644
--- a/media/base/video_encoder.h
+++ b/media/base/video_encoder.h
@@ -8,8 +8,8 @@
 #include "base/callback.h"
 #include "base/time/time.h"
 #include "media/base/bitrate.h"
+#include "media/base/encoder_status.h"
 #include "media/base/media_export.h"
-#include "media/base/status.h"
 #include "media/base/svc_scalability_mode.h"
 #include "media/base/video_codecs.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -77,13 +77,13 @@
                                    absl::optional<CodecDescription>)>;
 
   // Callback to report success and errors in encoder calls.
-  using StatusCB = base::OnceCallback<void(Status error)>;
+  using EncoderStatusCB = base::OnceCallback<void(EncoderStatus error)>;
 
   struct PendingEncode {
     PendingEncode();
     PendingEncode(PendingEncode&&);
     ~PendingEncode();
-    StatusCB done_callback;
+    EncoderStatusCB done_callback;
     scoped_refptr<VideoFrame> frame;
     bool key_frame;
   };
@@ -103,7 +103,7 @@
   virtual void Initialize(VideoCodecProfile profile,
                           const Options& options,
                           OutputCB output_cb,
-                          StatusCB done_cb) = 0;
+                          EncoderStatusCB done_cb) = 0;
 
   // Requests a |frame| to be encoded. The status of the encoder and the frame
   // are returned via the provided callback |done_cb|.
@@ -119,7 +119,7 @@
   // and harvest the outputs.
   virtual void Encode(scoped_refptr<VideoFrame> frame,
                       bool key_frame,
-                      StatusCB done_cb) = 0;
+                      EncoderStatusCB done_cb) = 0;
 
   // Adjust encoder options and the output callback for future frames, executing
   // the |done_cb| upon completion.
@@ -130,11 +130,11 @@
   // for it to finish.
   virtual void ChangeOptions(const Options& options,
                              OutputCB output_cb,
-                             StatusCB done_cb) = 0;
+                             EncoderStatusCB done_cb) = 0;
 
   // Requests all outputs for already encoded frames to be
   // produced via |output_cb| and calls |dene_cb| after that.
-  virtual void Flush(StatusCB done_cb) = 0;
+  virtual void Flush(EncoderStatusCB done_cb) = 0;
 };
 
 }  // namespace media
diff --git a/media/mojo/clients/mojo_audio_encoder.cc b/media/mojo/clients/mojo_audio_encoder.cc
index 2909457..66fd091 100644
--- a/media/mojo/clients/mojo_audio_encoder.cc
+++ b/media/mojo/clients/mojo_audio_encoder.cc
@@ -26,12 +26,13 @@
 
 void MojoAudioEncoder::Initialize(const Options& options,
                                   OutputCB output_cb,
-                                  StatusCB done_cb) {
+                                  EncoderStatusCB done_cb) {
   DCHECK(output_cb);
   DCHECK(done_cb);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (remote_encoder_.is_bound() || client_receiver_.is_bound()) {
-    PostStatusCallback(std::move(done_cb), StatusCode::kEncoderInitializeTwice);
+    PostStatusCallback(std::move(done_cb),
+                       EncoderStatus::Codes::kEncoderInitializeTwice);
     return;
   }
 
@@ -39,7 +40,7 @@
 
   if (!remote_encoder_.is_bound() || !remote_encoder_.is_connected()) {
     PostStatusCallback(std::move(done_cb),
-                       StatusCode::kEncoderInitializationError);
+                       EncoderStatus::Codes::kEncoderInitializationError);
     return;
   }
 
@@ -52,13 +53,14 @@
 
 void MojoAudioEncoder::Encode(std::unique_ptr<AudioBus> audio_bus,
                               base::TimeTicks capture_time,
-                              StatusCB done_cb) {
+                              EncoderStatusCB done_cb) {
   DCHECK(audio_bus);
   DCHECK(done_cb);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!remote_encoder_.is_bound() || !remote_encoder_.is_connected()) {
-    PostStatusCallback(std::move(done_cb), StatusCode::kEncoderFailedEncode);
+    PostStatusCallback(std::move(done_cb),
+                       EncoderStatus::Codes::kEncoderFailedEncode);
     return;
   }
 
@@ -70,12 +72,13 @@
                           WrapCallbackAsPending(std::move(done_cb)));
 }
 
-void MojoAudioEncoder::Flush(StatusCB done_cb) {
+void MojoAudioEncoder::Flush(EncoderStatusCB done_cb) {
   DCHECK(done_cb);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!remote_encoder_.is_bound() || !remote_encoder_.is_connected()) {
-    PostStatusCallback(std::move(done_cb), StatusCode::kEncoderFailedFlush);
+    PostStatusCallback(std::move(done_cb),
+                       EncoderStatus::Codes::kEncoderFailedFlush);
     return;
   }
 
@@ -92,24 +95,24 @@
 }
 
 void MojoAudioEncoder::CallAndReleaseCallback(PendingCallbackHandle handle,
-                                              const Status& status) {
+                                              const EncoderStatus& status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!pending_callbacks_.empty()) {
-    StatusCB callback = std::move(*handle);
+    EncoderStatusCB callback = std::move(*handle);
     pending_callbacks_.erase(handle);
     std::move(callback).Run(status);
   }
 }
 
-MojoAudioEncoder::WrappedStatusCB MojoAudioEncoder::WrapCallbackAsPending(
-    StatusCB callback) {
+MojoAudioEncoder::WrappedEncoderStatusCB
+MojoAudioEncoder::WrapCallbackAsPending(EncoderStatusCB callback) {
   PendingCallbackHandle handle =
       pending_callbacks_.insert(pending_callbacks_.end(), std::move(callback));
   return base::BindOnce(&MojoAudioEncoder::CallAndReleaseCallback, weak_this_,
                         handle);
 }
 
-void MojoAudioEncoder::CallAndReleaseAllPendingCallbacks(Status status) {
+void MojoAudioEncoder::CallAndReleaseAllPendingCallbacks(EncoderStatus status) {
   for (auto& callback : pending_callbacks_)
     PostStatusCallback(std::move(callback), status);
   pending_callbacks_.clear();
@@ -125,12 +128,14 @@
 void MojoAudioEncoder::OnConnectionError() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!remote_encoder_.is_connected());
-  CallAndReleaseAllPendingCallbacks(StatusCode::kEncoderMojoConnectionError);
+  CallAndReleaseAllPendingCallbacks(
+      EncoderStatus::Codes::kEncoderMojoConnectionError);
   weak_factory_.InvalidateWeakPtrs();
   remote_encoder_.reset();
 }
 
-void MojoAudioEncoder::PostStatusCallback(StatusCB callback, Status status) {
+void MojoAudioEncoder::PostStatusCallback(EncoderStatusCB callback,
+                                          EncoderStatus status) {
   runner_->PostTask(FROM_HERE,
                     base::BindOnce(std::move(callback), std::move(status)));
 }
diff --git a/media/mojo/clients/mojo_audio_encoder.h b/media/mojo/clients/mojo_audio_encoder.h
index bba5974..d8f4633 100644
--- a/media/mojo/clients/mojo_audio_encoder.h
+++ b/media/mojo/clients/mojo_audio_encoder.h
@@ -35,13 +35,13 @@
   // media::AudioEncoder implementation.
   void Initialize(const Options& options,
                   OutputCB output_cb,
-                  StatusCB done_cb) final;
+                  EncoderStatusCB done_cb) final;
 
   void Encode(std::unique_ptr<AudioBus> audio_bus,
               base::TimeTicks capture_time,
-              StatusCB done_cb) final;
+              EncoderStatusCB done_cb) final;
 
-  void Flush(StatusCB done_cb) final;
+  void Flush(EncoderStatusCB done_cb) final;
 
   // AudioEncoderClient implementation.
   void OnEncodedBufferReady(media::EncodedAudioBuffer buffer,
@@ -51,23 +51,24 @@
   // Using std::list here for stable iterators, so we can add and remove
   // pending callbacks without worry and nuke them all at once if need be
   // if Mojo connection error occurs.
-  using PendingCallbacksList = std::list<StatusCB>;
+  using PendingCallbacksList = std::list<EncoderStatusCB>;
   using PendingCallbackHandle = PendingCallbacksList::iterator;
 
-  // It is different from regular StatusCB because mojo only gives us
+  // It is different from regular EncoderStatusCB because mojo only gives us
   // `const Status&` instead of `Status`.
-  using WrappedStatusCB = base::OnceCallback<void(const Status& error)>;
+  using WrappedEncoderStatusCB =
+      base::OnceCallback<void(const EncoderStatus& error)>;
 
   void CallAndReleaseCallback(PendingCallbackHandle handle,
-                              const Status& status);
-  void CallAndReleaseAllPendingCallbacks(Status status)
+                              const EncoderStatus& status);
+  void CallAndReleaseAllPendingCallbacks(EncoderStatus status)
       VALID_CONTEXT_REQUIRED(sequence_checker_);
-  WrappedStatusCB WrapCallbackAsPending(StatusCB callback)
+  WrappedEncoderStatusCB WrapCallbackAsPending(EncoderStatusCB callback)
       VALID_CONTEXT_REQUIRED(sequence_checker_);
 
   void BindRemote();
   void OnConnectionError();
-  void PostStatusCallback(StatusCB callback, Status status);
+  void PostStatusCallback(EncoderStatusCB callback, EncoderStatus status);
 
   SEQUENCE_CHECKER(sequence_checker_);
   mojo::PendingRemote<mojom::AudioEncoder> pending_remote_encoder_
diff --git a/media/mojo/clients/mojo_audio_encoder_unittest.cc b/media/mojo/clients/mojo_audio_encoder_unittest.cc
index e0302c6..c9ddb9f 100644
--- a/media/mojo/clients/mojo_audio_encoder_unittest.cc
+++ b/media/mojo/clients/mojo_audio_encoder_unittest.cc
@@ -106,7 +106,8 @@
     return (ticks - base::TimeTicks()).InMilliseconds();
   }
 
-  AudioEncoder::StatusCB ValidatingStatusCB(base::Location loc = FROM_HERE) {
+  AudioEncoder::EncoderStatusCB ValidatingStatusCB(
+      base::Location loc = FROM_HERE) {
     struct CallEnforcer {
       bool called = false;
       std::string location;
@@ -117,7 +118,7 @@
     auto enforcer = std::make_unique<CallEnforcer>();
     enforcer->location = loc.ToString();
     return base::BindLambdaForTesting(
-        [this, enforcer{std::move(enforcer)}](Status s) {
+        [this, enforcer{std::move(enforcer)}](EncoderStatus s) {
           EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
           EXPECT_TRUE(s.is_ok()) << " Callback created: " << enforcer->location
                                  << " Error: " << s.message();
@@ -149,16 +150,16 @@
   EXPECT_CALL(*mock_audio_encoder_, Initialize(_, _, _))
       .WillOnce(Invoke([this](const AudioEncoder::Options& options,
                               AudioEncoder::OutputCB output_cb,
-                              AudioEncoder::StatusCB done_cb) {
+                              AudioEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(service_task_runner_->RunsTasksInCurrentSequence());
-        std::move(done_cb).Run(OkStatus());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
   AudioEncoder::OutputCB output_cb = base::BindLambdaForTesting(
       [&](EncodedAudioBuffer output,
           absl::optional<AudioEncoder::CodecDescription>) { FAIL(); });
 
-  auto done_cb = base::BindLambdaForTesting([&, this](Status s) {
+  auto done_cb = base::BindLambdaForTesting([&, this](EncoderStatus s) {
     EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
     EXPECT_TRUE(s.is_ok());
     run_loop.QuitWhenIdle();
@@ -175,17 +176,18 @@
   EXPECT_CALL(*mock_audio_encoder_, Initialize(_, _, _))
       .WillOnce(Invoke([](const AudioEncoder::Options& options,
                           AudioEncoder::OutputCB output_cb,
-                          AudioEncoder::StatusCB done_cb) {
-        std::move(done_cb).Run(StatusCode::kEncoderInitializationError);
+                          AudioEncoder::EncoderStatusCB done_cb) {
+        std::move(done_cb).Run(
+            EncoderStatus::Codes::kEncoderInitializationError);
       }));
 
   AudioEncoder::OutputCB output_cb = base::BindLambdaForTesting(
       [&](EncodedAudioBuffer output,
           absl::optional<AudioEncoder::CodecDescription>) { FAIL(); });
 
-  auto done_cb = base::BindLambdaForTesting([&, this](Status s) {
+  auto done_cb = base::BindLambdaForTesting([&, this](EncoderStatus s) {
     EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
-    EXPECT_EQ(s.code(), StatusCode::kEncoderInitializationError);
+    EXPECT_EQ(s.code(), EncoderStatus::Codes::kEncoderInitializationError);
     run_loop.QuitWhenIdle();
   });
 
@@ -201,17 +203,17 @@
   EXPECT_CALL(*mock_audio_encoder_, Initialize(_, _, _))
       .WillRepeatedly(Invoke([](const AudioEncoder::Options& options,
                                 AudioEncoder::OutputCB output_cb,
-                                AudioEncoder::StatusCB done_cb) {
-        std::move(done_cb).Run(OkStatus());
+                                AudioEncoder::EncoderStatusCB done_cb) {
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
-  auto expect_ok = base::BindLambdaForTesting([&](Status s) {
+  auto expect_ok = base::BindLambdaForTesting([&](EncoderStatus s) {
     EXPECT_TRUE(s.is_ok());
     good_init_run_loop.QuitWhenIdle();
   });
 
-  auto expect_error = base::BindLambdaForTesting([&](Status s) {
-    EXPECT_EQ(s.code(), StatusCode::kEncoderInitializeTwice);
+  auto expect_error = base::BindLambdaForTesting([&](EncoderStatus s) {
+    EXPECT_EQ(s.code(), EncoderStatus::Codes::kEncoderInitializeTwice);
     failed_initi_run_loop.QuitWhenIdle();
   });
 
@@ -233,18 +235,18 @@
   EXPECT_CALL(*mock_audio_encoder_, Initialize(_, _, _))
       .WillOnce(Invoke([&, this](const AudioEncoder::Options& options,
                                  AudioEncoder::OutputCB output_cb,
-                                 AudioEncoder::StatusCB done_cb) {
+                                 AudioEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(service_task_runner_->RunsTasksInCurrentSequence());
         service_output_cb = std::move(output_cb);
-        std::move(done_cb).Run(OkStatus());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
   EXPECT_CALL(*mock_audio_encoder_, Encode(_, _, _))
       .WillRepeatedly(Invoke([&, this](std::unique_ptr<AudioBus> audio_bus,
                                        base::TimeTicks capture_time,
-                                       AudioEncoder::StatusCB done_cb) {
+                                       AudioEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(service_task_runner_->RunsTasksInCurrentSequence());
-        std::move(done_cb).Run(OkStatus());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 
         int64_t input_number = ToMilliseconds(capture_time);
         EXPECT_LE(input_number, input_count);
@@ -313,17 +315,17 @@
   EXPECT_CALL(*mock_audio_encoder_, Initialize(_, _, _))
       .WillOnce(Invoke([&](const AudioEncoder::Options& options,
                            AudioEncoder::OutputCB output_cb,
-                           AudioEncoder::StatusCB done_cb) {
+                           AudioEncoder::EncoderStatusCB done_cb) {
         service_output_cb = std::move(output_cb);
-        std::move(done_cb).Run(OkStatus());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
   EXPECT_CALL(*mock_audio_encoder_, Encode(_, _, _))
       .WillRepeatedly(Invoke([&, this](std::unique_ptr<AudioBus> audio_bus,
                                        base::TimeTicks capture_time,
-                                       AudioEncoder::StatusCB done_cb) {
+                                       AudioEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(service_task_runner_->RunsTasksInCurrentSequence());
-        std::move(done_cb).Run(OkStatus());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 
         AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
                                CHANNEL_LAYOUT_DISCRETE, 8000, 1);
@@ -361,16 +363,16 @@
   EXPECT_CALL(*mock_audio_encoder_, Initialize(_, _, _))
       .WillOnce(Invoke([&](const AudioEncoder::Options& options,
                            AudioEncoder::OutputCB output_cb,
-                           AudioEncoder::StatusCB done_cb) {
+                           AudioEncoder::EncoderStatusCB done_cb) {
         service_output_cb = std::move(output_cb);
-        std::move(done_cb).Run(OkStatus());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
   EXPECT_CALL(*mock_audio_encoder_, Encode(_, _, _))
       .WillRepeatedly(Invoke([&](std::unique_ptr<AudioBus> audio_bus,
                                  base::TimeTicks capture_time,
-                                 AudioEncoder::StatusCB done_cb) {
-        std::move(done_cb).Run(OkStatus());
+                                 AudioEncoder::EncoderStatusCB done_cb) {
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 
         AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
                                CHANNEL_LAYOUT_DISCRETE, options.sample_rate,
@@ -381,9 +383,9 @@
       }));
 
   EXPECT_CALL(*mock_audio_encoder_, Flush(_))
-      .WillRepeatedly(Invoke([&, this](AudioEncoder::StatusCB done_cb) {
+      .WillRepeatedly(Invoke([&, this](AudioEncoder::EncoderStatusCB done_cb) {
         EXPECT_TRUE(service_task_runner_->RunsTasksInCurrentSequence());
-        std::move(done_cb).Run(OkStatus());
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
 
   AudioEncoder::OutputCB output_cb = base::BindLambdaForTesting(
@@ -400,7 +402,7 @@
         ValidatingStatusCB());
   }
 
-  AudioEncoder::StatusCB flush_cb = base::BindLambdaForTesting([&](Status s) {
+  auto flush_cb = base::BindLambdaForTesting([&](EncoderStatus s) {
     EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
     EXPECT_TRUE(s.is_ok());
     EXPECT_EQ(output_count, input_count);
@@ -416,24 +418,24 @@
 TEST_F(MojoAudioEncoderTest, MojoErrorCallsAllDoneCallbacks) {
   base::RunLoop run_loop;
   AudioEncoder::Options options = MakeOptions();
-  std::vector<AudioEncoder::StatusCB> done_callbacks;
+  std::vector<AudioEncoder::EncoderStatusCB> done_callbacks;
   const int input_count = 5;
   int error_count = 0;
   EXPECT_CALL(*mock_audio_encoder_, Initialize(_, _, _))
       .WillOnce(Invoke([&](const AudioEncoder::Options& options,
                            AudioEncoder::OutputCB output_cb,
-                           AudioEncoder::StatusCB done_cb) {
-        std::move(done_cb).Run(OkStatus());
+                           AudioEncoder::EncoderStatusCB done_cb) {
+        std::move(done_cb).Run(EncoderStatus::Codes::kOk);
       }));
   EXPECT_CALL(*mock_audio_encoder_, Encode(_, _, _))
       .WillRepeatedly(Invoke([&](std::unique_ptr<AudioBus> audio_bus,
                                  base::TimeTicks capture_time,
-                                 AudioEncoder::StatusCB done_cb) {
+                                 AudioEncoder::EncoderStatusCB done_cb) {
         done_callbacks.push_back(std::move(done_cb));
       }));
 
   EXPECT_CALL(*mock_audio_encoder_, Flush(_))
-      .WillOnce(Invoke([&](AudioEncoder::StatusCB done_cb) {
+      .WillOnce(Invoke([&](AudioEncoder::EncoderStatusCB done_cb) {
         done_callbacks.push_back(std::move(done_cb));
         service_task_runner_->DeleteSoon(FROM_HERE, std::move(receiver_));
       }));
@@ -443,16 +445,16 @@
 
   for (int i = 0; i < input_count; i++) {
     auto ts = FromMilliseconds(i);
-    auto done_cb = base::BindLambdaForTesting([&](Status s) {
-      EXPECT_EQ(s.code(), StatusCode::kEncoderMojoConnectionError);
+    auto done_cb = base::BindLambdaForTesting([&](EncoderStatus s) {
+      EXPECT_EQ(s.code(), EncoderStatus::Codes::kEncoderMojoConnectionError);
       error_count++;
     });
     mojo_audio_encoder_->Encode(
         MakeInput(i, options.channels, options.sample_rate), ts,
         std::move(done_cb));
   }
-  AudioEncoder::StatusCB flush_cb = base::BindLambdaForTesting([&](Status s) {
-    EXPECT_EQ(s.code(), StatusCode::kEncoderMojoConnectionError);
+  auto flush_cb = base::BindLambdaForTesting([&](EncoderStatus s) {
+    EXPECT_EQ(s.code(), EncoderStatus::Codes::kEncoderMojoConnectionError);
     run_loop.QuitWhenIdle();
   });
 
diff --git a/media/mojo/mojom/BUILD.gn b/media/mojo/mojom/BUILD.gn
index fd5ec68..16e65798 100644
--- a/media/mojo/mojom/BUILD.gn
+++ b/media/mojo/mojom/BUILD.gn
@@ -364,6 +364,10 @@
           mojom = "media.mojom.Status"
           cpp = "::media::Status"
         },
+        {
+          mojom = "media.mojom.EncoderStatus"
+          cpp = "::media::EncoderStatus"
+        },
       ]
       traits_headers = [ "status_mojom_traits.h" ]
       traits_sources = [ "status_mojom_traits.cc" ]
diff --git a/media/mojo/mojom/audio_encoder.mojom b/media/mojo/mojom/audio_encoder.mojom
index 6dd927b..1d131ea 100644
--- a/media/mojo/mojom/audio_encoder.mojom
+++ b/media/mojo/mojom/audio_encoder.mojom
@@ -51,17 +51,17 @@
   // This must be called only once before any other Encode() and Flush().
   // Returns errors as |status|.
   Initialize(pending_associated_remote<AudioEncoderClient> client,
-             AudioEncoderConfig config) => (Status status);
+             AudioEncoderConfig config) => (EncoderStatus status);
 
   // Requests contents of audio |buffer| to be encoded, encoded results
   // produced via AudioEncoderClient.EncodedBufferReady().
   // Returns errors as |status|.
-  Encode(AudioBuffer buffer) => (Status status);
+  Encode(AudioBuffer buffer) => (EncoderStatus status);
 
   // Requests all outputs for already encoded frames to be
   // produced via AudioEncoderClient.EncodedBufferReady().
   // Returns errors as |status|.
-  Flush() => (Status status);
+  Flush() => (EncoderStatus status);
 };
 
 // A complimentary interface for AudioEncoder, bound
diff --git a/media/mojo/mojom/media_types.mojom b/media/mojo/mojom/media_types.mojom
index 6504313..4020d63 100644
--- a/media/mojo/mojom/media_types.mojom
+++ b/media/mojo/mojom/media_types.mojom
@@ -502,6 +502,10 @@
   StatusData? internal;
 };
 
+struct EncoderStatus {
+  StatusData? internal;
+};
+
 // Types of media stream, categorised by the media stream's source.
 // The enum values are emitted to metrics. Do not reorder.
 enum MediaStreamType {
diff --git a/media/mojo/mojom/status_mojom_traits.h b/media/mojo/mojom/status_mojom_traits.h
index 5a742d5..116cf34 100644
--- a/media/mojo/mojom/status_mojom_traits.h
+++ b/media/mojo/mojom/status_mojom_traits.h
@@ -7,6 +7,7 @@
 
 #include "base/containers/span.h"
 #include "base/values.h"
+#include "media/base/encoder_status.h"
 #include "media/base/ipc/media_param_traits.h"
 #include "media/base/status.h"
 #include "media/mojo/mojom/media_types.mojom.h"
diff --git a/media/mojo/services/mojo_audio_encoder_service.cc b/media/mojo/services/mojo_audio_encoder_service.cc
index c85b428..f110c86 100644
--- a/media/mojo/services/mojo_audio_encoder_service.cc
+++ b/media/mojo/services/mojo_audio_encoder_service.cc
@@ -28,7 +28,7 @@
     const AudioEncoderConfig& config,
     InitializeCallback callback) {
   if (client_.is_bound()) {
-    std::move(callback).Run(StatusCode::kEncoderInitializeTwice);
+    std::move(callback).Run(EncoderStatus::Codes::kEncoderInitializeTwice);
     return;
   }
 
@@ -43,7 +43,8 @@
 void MojoAudioEncoderService::Encode(mojom::AudioBufferPtr buffer,
                                      EncodeCallback callback) {
   if (!client_.is_bound() || !client_.is_connected()) {
-    std::move(callback).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(callback).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
@@ -57,7 +58,8 @@
 
 void MojoAudioEncoderService::Flush(FlushCallback callback) {
   if (!client_.is_bound() || !client_.is_connected()) {
-    std::move(callback).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(callback).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
@@ -65,7 +67,8 @@
                                  std::move(callback)));
 }
 
-void MojoAudioEncoderService::OnDone(MojoDoneCallback callback, Status error) {
+void MojoAudioEncoderService::OnDone(MojoDoneCallback callback,
+                                     EncoderStatus error) {
   std::move(callback).Run(error);
 }
 
diff --git a/media/mojo/services/mojo_audio_encoder_service.h b/media/mojo/services/mojo_audio_encoder_service.h
index 31ab1243..b1d31b47 100644
--- a/media/mojo/services/mojo_audio_encoder_service.h
+++ b/media/mojo/services/mojo_audio_encoder_service.h
@@ -42,8 +42,9 @@
   void Flush(FlushCallback callback) final;
 
  private:
-  using MojoDoneCallback = base::OnceCallback<void(const media::Status&)>;
-  void OnDone(MojoDoneCallback callback, Status error);
+  using MojoDoneCallback =
+      base::OnceCallback<void(const media::EncoderStatus&)>;
+  void OnDone(MojoDoneCallback callback, EncoderStatus error);
   void OnOutput(EncodedAudioBuffer output,
                 absl::optional<media::AudioEncoder::CodecDescription> desc);
 
diff --git a/media/video/av1_video_encoder.cc b/media/video/av1_video_encoder.cc
index c1a669f..6e4c619 100644
--- a/media/video/av1_video_encoder.cc
+++ b/media/video/av1_video_encoder.cc
@@ -54,14 +54,15 @@
   return desired_threads;
 }
 
-Status SetUpAomConfig(const VideoEncoder::Options& opts,
-                      aom_codec_enc_cfg_t* config) {
+EncoderStatus SetUpAomConfig(const VideoEncoder::Options& opts,
+                             aom_codec_enc_cfg_t* config) {
   if (opts.frame_size.width() <= 0 || opts.frame_size.height() <= 0)
-    return Status(StatusCode::kEncoderUnsupportedConfig,
-                  "Negative width or height values.");
+    return EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedConfig,
+                         "Negative width or height values.");
 
   if (!opts.frame_size.GetCheckedArea().IsValid())
-    return Status(StatusCode::kEncoderUnsupportedConfig, "Frame is too large.");
+    return EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedConfig,
+                         "Frame is too large.");
 
   config->g_profile = 0;  // main
   config->g_input_bit_depth = 8;
@@ -114,10 +115,10 @@
   config->g_h = opts.frame_size.height();
 
   if (opts.scalability_mode.has_value()) {
-    return Status(StatusCode::kEncoderUnsupportedConfig,
-                  "Unsupported number of temporal layers.");
+    return EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedConfig,
+                         "Unsupported number of temporal layers.");
   }
-  return OkStatus();
+  return EncoderStatus::Codes::kOk;
 }
 
 }  // namespace
@@ -127,17 +128,17 @@
 void Av1VideoEncoder::Initialize(VideoCodecProfile profile,
                                  const Options& options,
                                  OutputCB output_cb,
-                                 StatusCB done_cb) {
+                                 EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeTwice);
+    std::move(done_cb).Run(EncoderStatus::Codes::kEncoderInitializeTwice);
     return;
   }
   profile_ = profile;
   if (profile < AV1PROFILE_MIN || profile > AV1PROFILE_MAX) {
-    auto status = Status(StatusCode::kEncoderUnsupportedProfile)
-                      .WithData("profile", profile);
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedProfile)
+            .WithData("profile", profile));
     return;
   }
 
@@ -146,18 +147,15 @@
   auto error = aom_codec_enc_config_default(aom_codec_av1_cx(), &config_,
                                             AOM_USAGE_REALTIME);
   if (error != AOM_CODEC_OK) {
-    auto status = Status(StatusCode::kEncoderInitializationError,
-                         "Failed to get default AOM config.")
-                      .WithData("error_code", error);
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Failed to get default AOM config.")
+            .WithData("error_code", error));
     return;
   }
 
-  Status status = SetUpAomConfig(options, &config_);
-  if (!status.is_ok()) {
-    std::move(done_cb).Run(status);
-    return;
-  }
+  POST_STATUS_AND_RETURN_ON_FAILURE(SetUpAomConfig(options, &config_),
+                                    std::move(done_cb), );
 
   // Initialize an encoder instance.
   aom_codec_unique_ptr codec(new aom_codec_ctx_t, FreeCodecCtx);
@@ -165,26 +163,26 @@
   aom_codec_flags_t flags = 0;
   error = aom_codec_enc_init(codec.get(), aom_codec_av1_cx(), &config_, flags);
   if (error != AOM_CODEC_OK) {
-    status = Status(StatusCode::kEncoderInitializationError,
-                    "aom_codec_enc_init() failed.")
-                 .WithData("error_code", error)
-                 .WithData("error_message", aom_codec_err_to_string(error));
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "aom_codec_enc_init() failed.")
+            .WithData("error_code", error)
+            .WithData("error_message", aom_codec_err_to_string(error)));
     return;
   }
   DCHECK_NE(codec->name, nullptr);
 
-#define CALL_AOM_CONTROL(key, value)                                           \
-  do {                                                                         \
-    error = aom_codec_control(codec.get(), (key), (value));                    \
-    if (error != AOM_CODEC_OK) {                                               \
-      status = Status(StatusCode::kEncoderInitializationError,                 \
-                      "Setting " #key " failed.")                              \
-                   .WithData("error_code", error)                              \
-                   .WithData("error_message", aom_codec_err_to_string(error)); \
-      std::move(done_cb).Run(status);                                          \
-      return;                                                                  \
-    }                                                                          \
+#define CALL_AOM_CONTROL(key, value)                                       \
+  do {                                                                     \
+    error = aom_codec_control(codec.get(), (key), (value));                \
+    if (error != AOM_CODEC_OK) {                                           \
+      std::move(done_cb).Run(                                              \
+          EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError, \
+                        "Setting " #key " failed.")                        \
+              .WithData("error_code", error)                               \
+              .WithData("error_message", aom_codec_err_to_string(error))); \
+      return;                                                              \
+    }                                                                      \
   } while (false)
 
   CALL_AOM_CONTROL(AV1E_SET_ROW_MT, 1);
@@ -227,21 +225,23 @@
   originally_configured_size_ = options.frame_size;
   output_cb_ = BindToCurrentLoop(std::move(output_cb));
   codec_ = std::move(codec);
-  std::move(done_cb).Run(OkStatus());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
 void Av1VideoEncoder::Encode(scoped_refptr<VideoFrame> frame,
                              bool key_frame,
-                             StatusCB done_cb) {
+                             EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (!codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(done_cb).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
   if (!frame) {
-    std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode,
-                                  "No frame provided for encoding."));
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                      "No frame provided for encoding."));
     return;
   }
 
@@ -253,12 +253,12 @@
                           frame->format() == PIXEL_FORMAT_ARGB;
   if ((!frame->IsMappable() && !frame->HasGpuMemoryBuffer()) ||
       !supported_format) {
-    Status status =
-        Status(StatusCode::kEncoderFailedEncode, "Unexpected frame format.")
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                      "Unexpected frame format.")
             .WithData("IsMappable", frame->IsMappable())
             .WithData("HasGpuMemoryBuffer", frame->HasGpuMemoryBuffer())
-            .WithData("format", frame->format());
-    std::move(done_cb).Run(std::move(status));
+            .WithData("format", frame->format()));
     return;
   }
 
@@ -266,8 +266,8 @@
     frame = ConvertToMemoryMappedFrame(frame);
     if (!frame) {
       std::move(done_cb).Run(
-          Status(StatusCode::kEncoderFailedEncode,
-                 "Convert GMB frame to MemoryMappedFrame failed."));
+          EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                        "Convert GMB frame to MemoryMappedFrame failed."));
       return;
     }
   }
@@ -278,16 +278,20 @@
         is_yuv ? frame->format() : PIXEL_FORMAT_I420, options_.frame_size,
         gfx::Rect(options_.frame_size), options_.frame_size,
         frame->timestamp());
-    Status status;
+
     if (resized_frame) {
-      status = ConvertAndScaleFrame(*frame, *resized_frame, resize_buf_);
+      Status conv_status =
+          ConvertAndScaleFrame(*frame, *resized_frame, resize_buf_);
+      if (!conv_status.is_ok()) {
+        std::move(done_cb).Run(
+            EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode)
+                .AddCause(std::move(conv_status)));
+        return;
+      }
     } else {
-      status = Status(StatusCode::kEncoderFailedEncode,
-                      "Can't allocate a resized frame.");
-    }
-    if (!status.is_ok()) {
-      std::move(done_cb).Run(std::move(status));
-      return;
+      std::move(done_cb).Run(
+          EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                        "Can't allocate a resized frame."));
     }
     frame = std::move(resized_frame);
   }
@@ -324,24 +328,26 @@
         base::StringPrintf("AOM encoding error: %s (%d)",
                            aom_codec_error_detail(codec_.get()), codec_->err);
     DLOG(ERROR) << msg;
-    std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode, msg));
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode, msg));
     return;
   }
   DrainOutputs(frame->timestamp(), frame->ColorSpace());
-  std::move(done_cb).Run(OkStatus());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
 void Av1VideoEncoder::ChangeOptions(const Options& options,
                                     OutputCB output_cb,
-                                    StatusCB done_cb) {
+                                    EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (!codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(done_cb).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
   // TODO(crbug.com/1208280) Try to actually adjust setting instead of
   // immediately dismissing configuration change.
-  std::move(done_cb).Run(StatusCode::kEncoderUnsupportedConfig);
+  std::move(done_cb).Run(EncoderStatus::Codes::kEncoderUnsupportedConfig);
 }
 
 base::TimeDelta Av1VideoEncoder::GetFrameDuration(const VideoFrame& frame) {
@@ -382,10 +388,11 @@
 
 Av1VideoEncoder::~Av1VideoEncoder() = default;
 
-void Av1VideoEncoder::Flush(StatusCB done_cb) {
+void Av1VideoEncoder::Flush(EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (!codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(done_cb).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
@@ -396,11 +403,12 @@
         base::StringPrintf("AOM encoding error: %s (%d)",
                            aom_codec_error_detail(codec_.get()), codec_->err);
     DLOG(ERROR) << msg;
-    std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode, msg));
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode, msg));
     return;
   }
   DrainOutputs(base::TimeDelta(), gfx::ColorSpace());
-  std::move(done_cb).Run(OkStatus());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
 }  // namespace media
diff --git a/media/video/av1_video_encoder.h b/media/video/av1_video_encoder.h
index 3474ecc..7a10b01 100644
--- a/media/video/av1_video_encoder.h
+++ b/media/video/av1_video_encoder.h
@@ -27,14 +27,14 @@
   void Initialize(VideoCodecProfile profile,
                   const Options& options,
                   OutputCB output_cb,
-                  StatusCB done_cb) override;
+                  EncoderStatusCB done_cb) override;
   void Encode(scoped_refptr<VideoFrame> frame,
               bool key_frame,
-              StatusCB done_cb) override;
+              EncoderStatusCB done_cb) override;
   void ChangeOptions(const Options& options,
                      OutputCB output_cb,
-                     StatusCB done_cb) override;
-  void Flush(StatusCB done_cb) override;
+                     EncoderStatusCB done_cb) override;
+  void Flush(EncoderStatusCB done_cb) override;
 
  private:
   base::TimeDelta GetFrameDuration(const VideoFrame& frame);
diff --git a/media/video/openh264_video_encoder.cc b/media/video/openh264_video_encoder.cc
index dbba576..afb348e 100644
--- a/media/video/openh264_video_encoder.cc
+++ b/media/video/openh264_video_encoder.cc
@@ -21,8 +21,8 @@
 
 namespace {
 
-Status SetUpOpenH264Params(const VideoEncoder::Options& options,
-                           SEncParamExt* params) {
+void SetUpOpenH264Params(const VideoEncoder::Options& options,
+                         SEncParamExt* params) {
   params->bEnableFrameSkip = false;
   params->iPaddingFlag = 0;
   params->iComplexityMode = MEDIUM_COMPLEXITY;
@@ -70,8 +70,6 @@
   params->sSpatialLayers[0].iVideoHeight = params->iPicHeight;
   params->sSpatialLayers[0].iVideoWidth = params->iPicWidth;
   params->sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_SINGLE_SLICE;
-
-  return Status();
 }
 }  // namespace
 
@@ -100,63 +98,57 @@
 void OpenH264VideoEncoder::Initialize(VideoCodecProfile profile,
                                       const Options& options,
                                       OutputCB output_cb,
-                                      StatusCB done_cb) {
+                                      EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeTwice);
+    std::move(done_cb).Run(EncoderStatus::Codes::kEncoderInitializeTwice);
     return;
   }
 
   profile_ = profile;
   if (profile != H264PROFILE_BASELINE) {
-    auto status =
-        Status(StatusCode::kEncoderInitializationError, "Unsupported profile");
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Unsupported profile"));
     return;
   }
 
   ISVCEncoder* raw_codec = nullptr;
   if (WelsCreateSVCEncoder(&raw_codec) != 0) {
-    auto status = Status(StatusCode::kEncoderInitializationError,
-                         "Failed to create OpenH264 encoder.");
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Failed to create OpenH264 encoder."));
     return;
   }
   svc_encoder_unique_ptr codec(raw_codec, ISVCEncoderDeleter());
   raw_codec = nullptr;
 
-  Status status;
-
   SEncParamExt params = {};
   if (int err = codec->GetDefaultParams(&params)) {
-    status = Status(StatusCode::kEncoderInitializationError,
-                    "Failed to get default params.")
-                 .WithData("error", err);
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Failed to get default params.")
+            .WithData("error", err));
     return;
   }
 
-  status = SetUpOpenH264Params(options, &params);
-  if (!status.is_ok()) {
-    std::move(done_cb).Run(status);
-    return;
-  }
+  SetUpOpenH264Params(options, &params);
 
   if (int err = codec->InitializeExt(&params)) {
-    status = Status(StatusCode::kEncoderInitializationError,
-                    "Failed to initialize OpenH264 encoder.")
-                 .WithData("error", err);
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Failed to initialize OpenH264 encoder.")
+            .WithData("error", err));
     return;
   }
   codec.get_deleter().MarkInitialized();
 
   int video_format = EVideoFormatType::videoFormatI420;
   if (int err = codec->SetOption(ENCODER_OPTION_DATAFORMAT, &video_format)) {
-    status = Status(StatusCode::kEncoderInitializationError,
-                    "Failed to set data format for OpenH264 encoder")
-                 .WithData("error", err);
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Failed to set data format for OpenH264 encoder")
+            .WithData("error", err));
     return;
   }
 
@@ -166,12 +158,12 @@
   options_ = options;
   output_cb_ = BindToCurrentLoop(std::move(output_cb));
   codec_ = std::move(codec);
-  std::move(done_cb).Run(OkStatus());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
-Status OpenH264VideoEncoder::DrainOutputs(const SFrameBSInfo& frame_info,
-                                          base::TimeDelta timestamp,
-                                          gfx::ColorSpace color_space) {
+EncoderStatus OpenH264VideoEncoder::DrainOutputs(const SFrameBSInfo& frame_info,
+                                                 base::TimeDelta timestamp,
+                                                 gfx::ColorSpace color_space) {
   VideoEncoderOutput result;
   result.key_frame = (frame_info.eFrameType == videoFrameTypeIDR);
   result.timestamp = timestamp;
@@ -197,13 +189,13 @@
     if (result.temporal_id == -1)
       result.temporal_id = layer_info.uiTemporalId;
     else if (result.temporal_id != layer_info.uiTemporalId)
-      return Status(StatusCode::kEncoderFailedEncode);
+      return EncoderStatus::Codes::kEncoderFailedEncode;
 
     size_t layer_len = 0;
     for (int nal_idx = 0; nal_idx < layer_info.iNalCount; ++nal_idx)
       layer_len += layer_info.pNalLengthInByte[nal_idx];
     if (written_size + layer_len > total_chunk_size)
-      return Status(StatusCode::kEncoderFailedEncode);
+      return EncoderStatus::Codes::kEncoderFailedEncode;
 
     memcpy(gather_buffer + written_size, layer_info.pBsBuf, layer_len);
     written_size += layer_len;
@@ -214,7 +206,7 @@
     result.size = total_chunk_size;
 
     output_cb_.Run(std::move(result), absl::optional<CodecDescription>());
-    return OkStatus();
+    return EncoderStatus::Codes::kOk;
   }
 
   size_t converted_output_size = 0;
@@ -225,7 +217,8 @@
       &converted_output_size);
 
   if (!status.is_ok())
-    return status;
+    return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode)
+        .AddCause(std::move(status));
 
   result.size = converted_output_size;
 
@@ -234,28 +227,29 @@
     const auto& config = h264_converter_->GetCurrentConfig();
     desc = CodecDescription();
     if (!config.Serialize(desc.value())) {
-      return Status(StatusCode::kEncoderFailedEncode,
-                    "Failed to serialize AVC decoder config");
+      return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                           "Failed to serialize AVC decoder config");
     }
   }
 
   output_cb_.Run(std::move(result), std::move(desc));
-  return OkStatus();
+  return EncoderStatus::Codes::kOk;
 }
 
 void OpenH264VideoEncoder::Encode(scoped_refptr<VideoFrame> frame,
                                   bool key_frame,
-                                  StatusCB done_cb) {
-  Status status;
+                                  EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (!codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(done_cb).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
   if (!frame) {
-    std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode,
-                                  "No frame provided for encoding."));
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                      "No frame provided for encoding."));
     return;
   }
   const bool supported_format = frame->format() == PIXEL_FORMAT_NV12 ||
@@ -266,11 +260,11 @@
                                 frame->format() == PIXEL_FORMAT_ARGB;
   if ((!frame->IsMappable() && !frame->HasGpuMemoryBuffer()) ||
       !supported_format) {
-    status =
-        Status(StatusCode::kEncoderFailedEncode, "Unexpected frame format.")
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                      "Unexpected frame format.")
             .WithData("IsMappable", frame->IsMappable())
-            .WithData("format", frame->format());
-    std::move(done_cb).Run(std::move(status));
+            .WithData("format", frame->format()));
     return;
   }
 
@@ -278,8 +272,8 @@
     frame = ConvertToMemoryMappedFrame(frame);
     if (!frame) {
       std::move(done_cb).Run(
-          Status(StatusCode::kEncoderFailedEncode,
-                 "Convert GMB frame to MemoryMappedFrame failed."));
+          EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                        "Convert GMB frame to MemoryMappedFrame failed."));
       return;
     }
   }
@@ -290,14 +284,17 @@
     auto i420_frame = frame_pool_.CreateFrame(
         PIXEL_FORMAT_I420, options_.frame_size, gfx::Rect(options_.frame_size),
         options_.frame_size, frame->timestamp());
-    if (i420_frame) {
-      status = ConvertAndScaleFrame(*frame, *i420_frame, conversion_buffer_);
-    } else {
-      status = Status(StatusCode::kEncoderFailedEncode,
-                      "Can't allocate an I420 frame.");
+    if (!i420_frame) {
+      std::move(done_cb).Run(
+          EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                        "Can't allocate an I420 frame."));
+      return;
     }
+    auto status = ConvertAndScaleFrame(*frame, *i420_frame, conversion_buffer_);
     if (!status.is_ok()) {
-      std::move(done_cb).Run(std::move(status));
+      std::move(done_cb).Run(
+          EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode)
+              .AddCause(std::move(status)));
       return;
     }
     frame = std::move(i420_frame);
@@ -323,7 +320,8 @@
   if (key_frame) {
     if (int err = codec_->ForceIntraFrame(true)) {
       std::move(done_cb).Run(
-          Status(StatusCode::kEncoderFailedEncode, "Can't make keyframe.")
+          EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                        "Can't make keyframe.")
               .WithData("error", err));
       return;
     }
@@ -332,47 +330,44 @@
   SFrameBSInfo frame_info = {};
   TRACE_EVENT0("media", "OpenH264::EncodeFrame");
   if (int err = codec_->EncodeFrame(&picture, &frame_info)) {
-    std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode,
-                                  "Failed to encode using OpenH264.")
-                               .WithData("error", err));
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                      "Failed to encode using OpenH264.")
+            .WithData("error", err));
     return;
   }
 
-  status = DrainOutputs(frame_info, frame->timestamp(), frame->ColorSpace());
-  std::move(done_cb).Run(std::move(status));
+  std::move(done_cb).Run(
+      DrainOutputs(frame_info, frame->timestamp(), frame->ColorSpace()));
 }
 
 void OpenH264VideoEncoder::ChangeOptions(const Options& options,
                                          OutputCB output_cb,
-                                         StatusCB done_cb) {
+                                         EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (!codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(done_cb).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
-  Status status;
   SEncParamExt params = {};
   if (int err = codec_->GetDefaultParams(&params)) {
-    status = Status(StatusCode::kEncoderInitializationError,
-                    "Failed to get default params.")
-                 .WithData("error", err);
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Failed to get default params.")
+            .WithData("error", err));
     return;
   }
 
-  status = SetUpOpenH264Params(options, &params);
-  if (!status.is_ok()) {
-    std::move(done_cb).Run(status);
-    return;
-  }
+  SetUpOpenH264Params(options, &params);
 
   if (int err =
           codec_->SetOption(ENCODER_OPTION_SVC_ENCODE_PARAM_EXT, &params)) {
-    status = Status(StatusCode::kEncoderInitializationError,
-                    "OpenH264 encoder failed to set new SEncParamExt.")
-                 .WithData("error", err);
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "OpenH264 encoder failed to set new SEncParamExt.")
+            .WithData("error", err));
     return;
   }
 
@@ -385,18 +380,19 @@
   options_ = options;
   if (!output_cb.is_null())
     output_cb_ = BindToCurrentLoop(std::move(output_cb));
-  std::move(done_cb).Run(OkStatus());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
-void OpenH264VideoEncoder::Flush(StatusCB done_cb) {
+void OpenH264VideoEncoder::Flush(EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (!codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(done_cb).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
   // Nothing to do really.
-  std::move(done_cb).Run(OkStatus());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
 }  // namespace media
diff --git a/media/video/openh264_video_encoder.h b/media/video/openh264_video_encoder.h
index f3fb9cb..fc48a8d 100644
--- a/media/video/openh264_video_encoder.h
+++ b/media/video/openh264_video_encoder.h
@@ -26,19 +26,19 @@
   void Initialize(VideoCodecProfile profile,
                   const Options& options,
                   OutputCB output_cb,
-                  StatusCB done_cb) override;
+                  EncoderStatusCB done_cb) override;
   void Encode(scoped_refptr<VideoFrame> frame,
               bool key_frame,
-              StatusCB done_cb) override;
+              EncoderStatusCB done_cb) override;
   void ChangeOptions(const Options& options,
                      OutputCB output_cb,
-                     StatusCB done_cb) override;
-  void Flush(StatusCB done_cb) override;
+                     EncoderStatusCB done_cb) override;
+  void Flush(EncoderStatusCB done_cb) override;
 
  private:
-  Status DrainOutputs(const SFrameBSInfo& frame_info,
-                      base::TimeDelta timestamp,
-                      gfx::ColorSpace color_space);
+  EncoderStatus DrainOutputs(const SFrameBSInfo& frame_info,
+                             base::TimeDelta timestamp,
+                             gfx::ColorSpace color_space);
 
   class ISVCEncoderDeleter {
    public:
diff --git a/media/video/software_video_encoder_test.cc b/media/video/software_video_encoder_test.cc
index 70c9155..2c5618f 100644
--- a/media/video/software_video_encoder_test.cc
+++ b/media/video/software_video_encoder_test.cc
@@ -100,7 +100,7 @@
     }
 
     EXPECT_NE(decoder_, nullptr);
-    decoder_->Initialize(config, false, nullptr, ValidatingStatusCB(),
+    decoder_->Initialize(config, false, nullptr, DecoderStatusCB(),
                          std::move(output_cb), base::NullCallback());
     RunUntilIdle();
   }
@@ -187,7 +187,28 @@
     }
   }
 
-  VideoEncoder::StatusCB ValidatingStatusCB(base::Location loc = FROM_HERE) {
+  VideoEncoder::EncoderStatusCB ValidatingStatusCB(
+      base::Location loc = FROM_HERE) {
+    struct CallEnforcer {
+      bool called = false;
+      std::string location;
+      ~CallEnforcer() {
+        EXPECT_TRUE(called) << "Callback created: " << location;
+      }
+    };
+    auto enforcer = std::make_unique<CallEnforcer>();
+    enforcer->location = loc.ToString();
+    return base::BindLambdaForTesting(
+        [enforcer{std::move(enforcer)}](EncoderStatus s) {
+          EXPECT_TRUE(s.is_ok())
+              << " Callback created: " << enforcer->location
+              << " Code: " << std::hex << static_cast<StatusCodeType>(s.code())
+              << " Error: " << s.message();
+          enforcer->called = true;
+        });
+  }
+
+  VideoDecoder::DecodeCB DecoderStatusCB(base::Location loc = FROM_HERE) {
     struct CallEnforcer {
       bool called = false;
       std::string location;
@@ -403,7 +424,7 @@
             DecoderBuffer::FromArray(std::move(output.data), output.size);
         buffer->set_timestamp(output.timestamp);
         buffer->set_is_key_frame(output.key_frame);
-        decoder_->Decode(std::move(buffer), ValidatingStatusCB());
+        decoder_->Decode(std::move(buffer), DecoderStatusCB());
       });
 
   VideoDecoder::OutputCB decoder_output_cb =
diff --git a/media/video/video_encode_accelerator_adapter.cc b/media/video/video_encode_accelerator_adapter.cc
index 01919d9..2044980 100644
--- a/media/video/video_encode_accelerator_adapter.cc
+++ b/media/video/video_encode_accelerator_adapter.cc
@@ -140,7 +140,7 @@
 void VideoEncodeAcceleratorAdapter::Initialize(VideoCodecProfile profile,
                                                const Options& options,
                                                OutputCB output_cb,
-                                               StatusCB done_cb) {
+                                               EncoderStatusCB done_cb) {
   DCHECK(!accelerator_task_runner_->RunsTasksInCurrentSequence());
   accelerator_task_runner_->PostTask(
       FROM_HERE,
@@ -155,34 +155,34 @@
     VideoCodecProfile profile,
     const Options& options,
     OutputCB output_cb,
-    StatusCB done_cb) {
+    EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(accelerator_sequence_checker_);
   if (state_ != State::kNotInitialized) {
-    auto status = Status(StatusCode::kEncoderInitializeTwice,
-                         "Encoder has already been initialized.");
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializeTwice,
+                      "Encoder has already been initialized."));
     return;
   }
 
   accelerator_ = gpu_factories_->CreateVideoEncodeAccelerator();
   if (!accelerator_) {
-    auto status = Status(StatusCode::kEncoderInitializationError,
-                         "Failed to create video encode accelerator.");
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Failed to create video encode accelerator."));
     return;
   }
 
   if (options.frame_size.width() <= 0 || options.frame_size.height() <= 0) {
-    auto status = Status(StatusCode::kEncoderUnsupportedConfig,
-                         "Negative width or height values.");
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedConfig,
+                      "Negative width or height values."));
     return;
   }
 
   if (!options.frame_size.GetCheckedArea().IsValid()) {
-    auto status =
-        Status(StatusCode::kEncoderUnsupportedConfig, "Frame is too large.");
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedConfig,
+                      "Frame is too large."));
     return;
   }
 
@@ -198,7 +198,7 @@
   }
 #endif  // BUILDFLAG(USE_PROPRIETARY_CODECS)
 
-  std::move(done_cb).Run(Status());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 
   // The accelerator will be initialized for real once we have the first frame.
 }
@@ -218,10 +218,9 @@
   const bool supported_format =
       format == PIXEL_FORMAT_NV12 || format == PIXEL_FORMAT_I420 || is_rgb;
   if (!supported_format) {
-    auto status =
-        Status(StatusCode::kEncoderFailedEncode, "Unexpected frame format.")
-            .WithData("frame", first_frame->AsHumanReadableString());
-    InitCompleted(std::move(status));
+    InitCompleted(EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                                "Unexpected frame format.")
+                      .WithData("frame", first_frame->AsHumanReadableString()));
     return;
   }
 
@@ -244,9 +243,9 @@
 #endif
 
   if (!accelerator_->Initialize(vea_config, this)) {
-    auto status = Status(StatusCode::kEncoderInitializationError,
-                         "Failed to initialize video encode accelerator.");
-    InitCompleted(status);
+    InitCompleted(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Failed to initialize video encode accelerator."));
     return;
   }
 
@@ -256,7 +255,7 @@
 
 void VideoEncodeAcceleratorAdapter::Encode(scoped_refptr<VideoFrame> frame,
                                            bool key_frame,
-                                           StatusCB done_cb) {
+                                           EncoderStatusCB done_cb) {
   DCHECK(!accelerator_task_runner_->RunsTasksInCurrentSequence());
   accelerator_task_runner_->PostTask(
       FROM_HERE,
@@ -268,7 +267,7 @@
 void VideoEncodeAcceleratorAdapter::EncodeOnAcceleratorThread(
     scoped_refptr<VideoFrame> frame,
     bool key_frame,
-    StatusCB done_cb) {
+    EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(accelerator_sequence_checker_);
 
   if (state_ == State::kWaitingForFirstFrame ||
@@ -284,9 +283,9 @@
   }
 
   if (state_ != State::kReadyToEncode) {
-    auto status =
-        Status(StatusCode::kEncoderFailedEncode, "Encoder can't encode now.");
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                      "Encoder can't encode now."));
     return;
   }
 
@@ -306,16 +305,18 @@
   if (input_buffer_preference_ == InputBufferKind::CpuMemBuf)
     use_gpu_buffer = false;
 
-  StatusOr<scoped_refptr<VideoFrame>> result(nullptr);
+  EncoderStatus::Or<scoped_refptr<VideoFrame>> result(nullptr);
   if (use_gpu_buffer)
     result = PrepareGpuFrame(input_coded_size_, frame);
   else
     result = PrepareCpuFrame(input_coded_size_, frame);
 
   if (result.has_error()) {
-    auto status = std::move(result).error();
-    status.WithData("frame", frame->AsHumanReadableString());
-    std::move(done_cb).Run(std::move(status).AddHere());
+    std::move(done_cb).Run(
+        std::move(result)
+            .error()
+            .WithData("frame", frame->AsHumanReadableString())
+            .AddHere());
     return;
   }
 
@@ -336,7 +337,7 @@
 
 void VideoEncodeAcceleratorAdapter::ChangeOptions(const Options& options,
                                                   OutputCB output_cb,
-                                                  StatusCB done_cb) {
+                                                  EncoderStatusCB done_cb) {
   DCHECK(!accelerator_task_runner_->RunsTasksInCurrentSequence());
   accelerator_task_runner_->PostTask(
       FROM_HERE,
@@ -349,15 +350,16 @@
 void VideoEncodeAcceleratorAdapter::ChangeOptionsOnAcceleratorThread(
     const Options options,
     OutputCB output_cb,
-    StatusCB done_cb) {
+    EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(accelerator_sequence_checker_);
   DCHECK(active_encodes_.empty());
   DCHECK(pending_encodes_.empty());
   DCHECK_EQ(state_, State::kReadyToEncode);
 
   if (options.frame_size != options_.frame_size) {
-    auto status = Status(StatusCode::kEncoderInitializationError,
-                         "Resolution change is not supported.");
+    auto status =
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Resolution change is not supported.");
     std::move(done_cb).Run(status);
     return;
   }
@@ -386,10 +388,10 @@
   options_ = options;
   if (!output_cb.is_null())
     output_cb_ = std::move(output_cb);
-  std::move(done_cb).Run(Status());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
-void VideoEncodeAcceleratorAdapter::Flush(StatusCB done_cb) {
+void VideoEncodeAcceleratorAdapter::Flush(EncoderStatusCB done_cb) {
   DCHECK(!accelerator_task_runner_->RunsTasksInCurrentSequence());
   accelerator_task_runner_->PostTask(
       FROM_HERE,
@@ -397,24 +399,24 @@
                      base::Unretained(this), WrapCallback(std::move(done_cb))));
 }
 
-void VideoEncodeAcceleratorAdapter::FlushOnAcceleratorThread(StatusCB done_cb) {
+void VideoEncodeAcceleratorAdapter::FlushOnAcceleratorThread(
+    EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(accelerator_sequence_checker_);
   if (state_ == State::kWaitingForFirstFrame) {
     // Nothing to do since we haven't actually initialized yet.
-    std::move(done_cb).Run(Status());
+    std::move(done_cb).Run(EncoderStatus::Codes::kOk);
     return;
   }
 
   if (state_ != State::kReadyToEncode && state_ != State::kInitializing) {
-    auto status =
-        Status(StatusCode::kEncoderFailedFlush, "Encoder can't flush now");
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(EncoderStatus(
+        EncoderStatus::Codes::kEncoderFailedFlush, "Encoder can't flush now"));
     return;
   }
 
   if (active_encodes_.empty() && pending_encodes_.empty()) {
     // No active or pending encodes, nothing to flush.
-    std::move(done_cb).Run(Status());
+    std::move(done_cb).Run(EncoderStatus::Codes::kOk);
     return;
   }
 
@@ -449,7 +451,7 @@
   output_handle_holder_ = output_pool_->MaybeAllocateBuffer(output_buffer_size);
 
   if (!output_handle_holder_) {
-    InitCompleted(Status(StatusCode::kEncoderInitializationError));
+    InitCompleted(EncoderStatus::Codes::kEncoderInitializationError);
     return;
   }
 
@@ -458,7 +460,7 @@
   // There is always one output buffer.
   accelerator_->UseOutputBitstreamBuffer(
       BitstreamBuffer(0, region.Duplicate(), region.GetSize()));
-  InitCompleted(Status());
+  InitCompleted(EncoderStatus::Codes::kOk);
 }
 
 void VideoEncodeAcceleratorAdapter::BitstreamBufferReady(
@@ -543,7 +545,7 @@
   for (auto it = active_encodes_.begin(); it != active_encodes_.end(); ++it) {
     if ((*it)->timestamp == result.timestamp) {
       result.color_space = (*it)->color_space;
-      std::move((*it)->done_callback).Run(Status());
+      std::move((*it)->done_callback).Run(EncoderStatus::Codes::kOk);
       active_encodes_.erase(it);
       erased_active_encode = true;
       break;
@@ -561,8 +563,8 @@
     VideoEncodeAccelerator::Error error) {
   if (state_ == State::kInitializing) {
     InitCompleted(
-        Status(StatusCode::kEncoderInitializationError,
-               "VideoEncodeAccelerator encountered an error")
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "VideoEncodeAccelerator encountered an error")
             .WithData("VideoEncodeAccelerator::Error", int32_t{error}));
     return;
   }
@@ -573,10 +575,10 @@
   // Report the error to all encoding-done callbacks
   for (auto& encode : active_encodes_) {
     auto status =
-        Status(StatusCode::kEncoderFailedEncode,
-               "VideoEncodeAccelerator encountered an error")
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                      "VideoEncodeAccelerator encountered an error")
             .WithData("VideoEncodeAccelerator::Error", int32_t{error});
-    std::move(encode->done_callback).Run(Status());
+    std::move(encode->done_callback).Run(EncoderStatus::Codes::kOk);
   }
   active_encodes_.clear();
   state_ = State::kNotInitialized;
@@ -585,7 +587,7 @@
 void VideoEncodeAcceleratorAdapter::NotifyEncoderInfoChange(
     const VideoEncoderInfo& info) {}
 
-void VideoEncodeAcceleratorAdapter::InitCompleted(Status status) {
+void VideoEncodeAcceleratorAdapter::InitCompleted(EncoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(accelerator_sequence_checker_);
 
   if (!status.is_ok()) {
@@ -630,8 +632,9 @@
   if (!pending_flush_)
     return;
 
-  auto status = success ? Status() : Status(StatusCode::kEncoderFailedFlush);
-  std::move(pending_flush_->done_callback).Run(status);
+  std::move(pending_flush_->done_callback)
+      .Run(success ? EncoderStatus::Codes::kOk
+                   : EncoderStatus::Codes::kEncoderFailedFlush);
   pending_flush_.reset();
   state_ = State::kReadyToEncode;
 }
@@ -646,13 +649,13 @@
 
 // Copy a frame into a shared mem buffer and resize it as the same time. Input
 // frames can I420, NV12, or RGB -- they'll be converted to I420 if needed.
-StatusOr<scoped_refptr<VideoFrame>>
+EncoderStatus::Or<scoped_refptr<VideoFrame>>
 VideoEncodeAcceleratorAdapter::PrepareCpuFrame(
     const gfx::Size& size,
     scoped_refptr<VideoFrame> src_frame) {
   auto handle = input_pool_->MaybeAllocateBuffer(input_buffer_size_);
   if (!handle)
-    return Status(StatusCode::kEncoderFailedEncode);
+    return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode);
 
   const base::UnsafeSharedMemoryRegion& region = handle->GetRegion();
   const base::WritableSharedMemoryMapping& mapping = handle->GetMapping();
@@ -666,7 +669,7 @@
       src_frame->timestamp());
 
   if (!shared_frame || !mapped_src_frame)
-    return Status(StatusCode::kEncoderFailedEncode);
+    return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode);
 
   shared_frame->BackWithSharedMemory(&region);
   // Keep the SharedMemoryHolder until the frame is destroyed so that the
@@ -677,14 +680,15 @@
   auto status =
       ConvertAndScaleFrame(*mapped_src_frame, *shared_frame, resize_buf_);
   if (!status.is_ok())
-    return std::move(status).AddHere();
+    return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode)
+        .AddCause(std::move(status));
 
   return shared_frame;
 }
 
 // Copy a frame into a GPU buffer and resize it as the same time. Input frames
 // can I420, NV12, or RGB -- they'll be converted to NV12 if needed.
-StatusOr<scoped_refptr<VideoFrame>>
+EncoderStatus::Or<scoped_refptr<VideoFrame>>
 VideoEncodeAcceleratorAdapter::PrepareGpuFrame(
     const gfx::Size& size,
     scoped_refptr<VideoFrame> src_frame) {
@@ -702,7 +706,7 @@
       gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE);
 
   if (!gmb)
-    return Status(StatusCode::kEncoderFailedEncode);
+    return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode);
   gmb->SetColorSpace(src_frame->ColorSpace());
 
   gpu::MailboxHolder empty_mailboxes[media::VideoFrame::kMaxPlanes];
@@ -724,12 +728,13 @@
                               ? ConvertToMemoryMappedFrame(src_frame)
                               : src_frame;
   if (!mapped_gpu_frame || !mapped_src_frame)
-    return Status(StatusCode::kEncoderFailedEncode);
+    return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode);
 
   auto status =
       ConvertAndScaleFrame(*mapped_src_frame, *mapped_gpu_frame, resize_buf_);
   if (!status.is_ok())
-    return std::move(status).AddHere();
+    return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode)
+        .AddCause(std::move(status));
 
   return gpu_frame;
 }
diff --git a/media/video/video_encode_accelerator_adapter.h b/media/video/video_encode_accelerator_adapter.h
index 2eb96b8..30a4ef5 100644
--- a/media/video/video_encode_accelerator_adapter.h
+++ b/media/video/video_encode_accelerator_adapter.h
@@ -51,14 +51,14 @@
   void Initialize(VideoCodecProfile profile,
                   const Options& options,
                   OutputCB output_cb,
-                  StatusCB done_cb) override;
+                  EncoderStatusCB done_cb) override;
   void Encode(scoped_refptr<VideoFrame> frame,
               bool key_frame,
-              StatusCB done_cb) override;
+              EncoderStatusCB done_cb) override;
   void ChangeOptions(const Options& options,
                      OutputCB output_cb,
-                     StatusCB done_cb) override;
-  void Flush(StatusCB done_cb) override;
+                     EncoderStatusCB done_cb) override;
+  void Flush(EncoderStatusCB done_cb) override;
 
   // VideoEncodeAccelerator::Client implementation
   void RequireBitstreamBuffers(unsigned int input_count,
@@ -87,32 +87,32 @@
     PendingOp();
     ~PendingOp();
 
-    StatusCB done_callback;
+    EncoderStatusCB done_callback;
     base::TimeDelta timestamp;
     gfx::ColorSpace color_space;
   };
 
   void FlushCompleted(bool success);
-  void InitCompleted(Status status);
+  void InitCompleted(EncoderStatus status);
   void InitializeOnAcceleratorThread(VideoCodecProfile profile,
                                      const Options& options,
                                      OutputCB output_cb,
-                                     StatusCB done_cb);
+                                     EncoderStatusCB done_cb);
   void InitializeInternalOnAcceleratorThread();
   void EncodeOnAcceleratorThread(scoped_refptr<VideoFrame> frame,
                                  bool key_frame,
-                                 StatusCB done_cb);
-  void FlushOnAcceleratorThread(StatusCB done_cb);
+                                 EncoderStatusCB done_cb);
+  void FlushOnAcceleratorThread(EncoderStatusCB done_cb);
   void ChangeOptionsOnAcceleratorThread(const Options options,
                                         OutputCB output_cb,
-                                        StatusCB done_cb);
+                                        EncoderStatusCB done_cb);
 
   template <class T>
   T WrapCallback(T cb);
-  StatusOr<scoped_refptr<VideoFrame>> PrepareGpuFrame(
+  EncoderStatus::Or<scoped_refptr<VideoFrame>> PrepareGpuFrame(
       const gfx::Size& size,
       scoped_refptr<VideoFrame> src_frame);
-  StatusOr<scoped_refptr<VideoFrame>> PrepareCpuFrame(
+  EncoderStatus::Or<scoped_refptr<VideoFrame>> PrepareCpuFrame(
       const gfx::Size& size,
       scoped_refptr<VideoFrame> src_frame);
 
diff --git a/media/video/video_encode_accelerator_adapter_test.cc b/media/video/video_encode_accelerator_adapter_test.cc
index 68a5ccd..6147b03 100644
--- a/media/video/video_encode_accelerator_adapter_test.cc
+++ b/media/video/video_encode_accelerator_adapter_test.cc
@@ -147,7 +147,8 @@
     }
   }
 
-  VideoEncoder::StatusCB ValidatingStatusCB(base::Location loc = FROM_HERE) {
+  VideoEncoder::EncoderStatusCB ValidatingStatusCB(
+      base::Location loc = FROM_HERE) {
     struct CallEnforcer {
       bool called = false;
       std::string location;
@@ -158,7 +159,7 @@
     auto enforcer = std::make_unique<CallEnforcer>();
     enforcer->location = loc.ToString();
     return base::BindLambdaForTesting(
-        [this, enforcer{std::move(enforcer)}](Status s) {
+        [this, enforcer{std::move(enforcer)}](EncoderStatus s) {
           EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
           EXPECT_TRUE(s.is_ok()) << " Callback created: " << enforcer->location
                                  << " Error: " << s.message();
@@ -298,7 +299,7 @@
   auto frame =
       CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));
   adapter()->Encode(frame, true, ValidatingStatusCB());
-  adapter()->Flush(base::BindLambdaForTesting([&](Status s) {
+  adapter()->Flush(base::BindLambdaForTesting([&](EncoderStatus s) {
     EXPECT_TRUE(s.is_ok());
     EXPECT_EQ(outputs_count, 1);
   }));
@@ -315,8 +316,9 @@
         outputs_count++;
       });
 
-  VideoEncoder::StatusCB expect_error_done_cb =
-      base::BindLambdaForTesting([&](Status s) { EXPECT_FALSE(s.is_ok()); });
+  VideoEncoder::EncoderStatusCB expect_error_done_cb =
+      base::BindLambdaForTesting(
+          [&](EncoderStatus s) { EXPECT_FALSE(s.is_ok()); });
 
   vea()->SetEncodingCallback(base::BindLambdaForTesting(
       [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
diff --git a/media/video/video_encoder_fallback.cc b/media/video/video_encoder_fallback.cc
index 6c631f1..0197d62 100644
--- a/media/video/video_encoder_fallback.cc
+++ b/media/video/video_encoder_fallback.cc
@@ -25,7 +25,7 @@
 void VideoEncoderFallback::Initialize(VideoCodecProfile profile,
                                       const Options& options,
                                       OutputCB output_cb,
-                                      StatusCB done_cb) {
+                                      EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   init_done_cb_ = std::move(done_cb);
@@ -33,7 +33,7 @@
   profile_ = profile;
   options_ = options;
   auto done_callback = [](base::WeakPtr<VideoEncoderFallback> self,
-                          Status status) {
+                          EncoderStatus status) {
     if (!self)
       return;
     if (status.is_ok()) {
@@ -53,7 +53,7 @@
 VideoEncoder::PendingEncode VideoEncoderFallback::MakePendingEncode(
     scoped_refptr<VideoFrame> frame,
     bool key_frame,
-    StatusCB done_cb) {
+    EncoderStatusCB done_cb) {
   PendingEncode result;
   result.done_callback = std::move(done_cb);
   result.frame = std::move(frame);
@@ -63,7 +63,7 @@
 
 void VideoEncoderFallback::Encode(scoped_refptr<VideoFrame> frame,
                                   bool key_frame,
-                                  StatusCB done_cb) {
+                                  EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (use_fallback_) {
@@ -77,7 +77,7 @@
   }
 
   auto done_callback = [](base::WeakPtr<VideoEncoderFallback> self,
-                          PendingEncode args, Status status) {
+                          PendingEncode args, EncoderStatus status) {
     if (!self)
       return;
     DCHECK(self->encoder_);
@@ -96,20 +96,20 @@
 
 void VideoEncoderFallback::ChangeOptions(const Options& options,
                                          OutputCB output_cb,
-                                         StatusCB done_cb) {
+                                         EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   options_ = options;
   if (encoder_)
     encoder_->ChangeOptions(options, std::move(output_cb), std::move(done_cb));
 }
 
-void VideoEncoderFallback::Flush(StatusCB done_cb) {
+void VideoEncoderFallback::Flush(EncoderStatusCB done_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (encoder_)
     encoder_->Flush(std::move(done_cb));
 }
 
-void VideoEncoderFallback::FallbackInitCompleted(Status status) {
+void VideoEncoderFallback::FallbackInitCompleted(EncoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(encoder_);
   if (init_done_cb_)
@@ -133,8 +133,9 @@
   use_fallback_ = true;
   encoder_ = std::move(create_fallback_cb_).Run();
   if (!encoder_) {
-    std::move(init_done_cb_).Run(StatusCode::kEncoderInitializationError);
-    FallbackInitCompleted(StatusCode::kEncoderInitializationError);
+    std::move(init_done_cb_)
+        .Run(EncoderStatus::Codes::kEncoderInitializationError);
+    FallbackInitCompleted(EncoderStatus::Codes::kEncoderInitializationError);
     return;
   }
 
@@ -153,7 +154,7 @@
     encoder_ = std::move(create_fallback_cb_).Run();
     if (!encoder_) {
       std::move(args.done_callback)
-          .Run(StatusCode::kEncoderInitializationError);
+          .Run(EncoderStatus::Codes::kEncoderInitializationError);
       return;
     }
 
@@ -179,4 +180,4 @@
   output_cb_.Run(std::move(output), std::move(desc));
 }
 
-}  // namespace media
\ No newline at end of file
+}  // namespace media
diff --git a/media/video/video_encoder_fallback.h b/media/video/video_encoder_fallback.h
index 0072441..324d6b3 100644
--- a/media/video/video_encoder_fallback.h
+++ b/media/video/video_encoder_fallback.h
@@ -33,22 +33,22 @@
   void Initialize(VideoCodecProfile profile,
                   const Options& options,
                   OutputCB output_cb,
-                  StatusCB done_cb) override;
+                  EncoderStatusCB done_cb) override;
   void Encode(scoped_refptr<VideoFrame> frame,
               bool key_frame,
-              StatusCB done_cb) override;
+              EncoderStatusCB done_cb) override;
   void ChangeOptions(const Options& options,
                      OutputCB output_cb,
-                     StatusCB done_cb) override;
-  void Flush(StatusCB done_cb) override;
+                     EncoderStatusCB done_cb) override;
+  void Flush(EncoderStatusCB done_cb) override;
 
  private:
   void FallbackInitialize();
   void FallbackEncode(PendingEncode args);
-  void FallbackInitCompleted(Status status);
+  void FallbackInitCompleted(EncoderStatus status);
   PendingEncode MakePendingEncode(scoped_refptr<VideoFrame> frame,
                                   bool key_frame,
-                                  StatusCB done_cb);
+                                  EncoderStatusCB done_cb);
   void CallOutput(VideoEncoderOutput output,
                   absl::optional<CodecDescription> desc);
 
@@ -66,7 +66,7 @@
   std::vector<std::unique_ptr<PendingEncode>> encodes_to_retry_;
 
   CreateFallbackCB create_fallback_cb_;
-  StatusCB init_done_cb_;
+  EncoderStatusCB init_done_cb_;
   OutputCB output_cb_;
   VideoCodecProfile profile_;
   Options options_;
diff --git a/media/video/video_encoder_fallback_test.cc b/media/video/video_encoder_fallback_test.cc
index ab534bc..8787118 100644
--- a/media/video/video_encoder_fallback_test.cc
+++ b/media/video/video_encoder_fallback_test.cc
@@ -51,13 +51,15 @@
 
   bool FallbackHappened() { return !secondary_video_encoder_holder_; }
 
-  void RunStatusCallbackAync(VideoEncoder::StatusCB callback,
-                             StatusCode code = StatusCode::kOk) {
+  void RunStatusCallbackAync(
+      VideoEncoder::EncoderStatusCB callback,
+      EncoderStatus::Codes code = EncoderStatus::Codes::kOk) {
     base::BindPostTask(callback_runner_, std::move(callback)).Run(code);
   }
 
-  VideoEncoder::StatusCB ValidatingStatusCB(StatusCode code = StatusCode::kOk,
-                                            base::Location loc = FROM_HERE) {
+  VideoEncoder::EncoderStatusCB ValidatingStatusCB(
+      EncoderStatus::Codes code = EncoderStatus::Codes::kOk,
+      base::Location loc = FROM_HERE) {
     struct CallEnforcer {
       bool called = false;
       std::string location;
@@ -68,7 +70,7 @@
     auto enforcer = std::make_unique<CallEnforcer>();
     enforcer->location = loc.ToString();
     return BindToCurrentLoop(base::BindLambdaForTesting(
-        [code, this, enforcer{std::move(enforcer)}](Status s) {
+        [code, this, enforcer{std::move(enforcer)}](EncoderStatus s) {
           EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
           EXPECT_EQ(s.code(), code)
               << " Callback created: " << enforcer->location;
@@ -98,7 +100,7 @@
       .WillOnce(Invoke([&, this](VideoCodecProfile profile,
                                  const VideoEncoder::Options& options,
                                  VideoEncoder::OutputCB output_cb,
-                                 VideoEncoder::StatusCB done_cb) {
+                                 VideoEncoder::EncoderStatusCB done_cb) {
         saved_output_cb = std::move(output_cb);
         RunStatusCallbackAync(std::move(done_cb));
       }));
@@ -106,7 +108,7 @@
   EXPECT_CALL(*main_video_encoder_, Encode(_, _, _))
       .WillRepeatedly(
           Invoke([&, this](scoped_refptr<VideoFrame> frame, bool key_frame,
-                           VideoEncoder::StatusCB done_cb) {
+                           VideoEncoder::EncoderStatusCB done_cb) {
             VideoEncoderOutput output;
             output.timestamp = frame->timestamp();
             saved_output_cb.Run(std::move(output), {});
@@ -145,9 +147,10 @@
       .WillOnce(Invoke([&, this](VideoCodecProfile profile,
                                  const VideoEncoder::Options& options,
                                  VideoEncoder::OutputCB output_cb,
-                                 VideoEncoder::StatusCB done_cb) {
-        RunStatusCallbackAync(std::move(done_cb),
-                              StatusCode::kEncoderInitializationError);
+                                 VideoEncoder::EncoderStatusCB done_cb) {
+        RunStatusCallbackAync(
+            std::move(done_cb),
+            EncoderStatus::Codes::kEncoderInitializationError);
       }));
 
   // Initialize() on the second encoder should succeed
@@ -155,7 +158,7 @@
       .WillOnce(Invoke([&, this](VideoCodecProfile profile,
                                  const VideoEncoder::Options& options,
                                  VideoEncoder::OutputCB output_cb,
-                                 VideoEncoder::StatusCB done_cb) {
+                                 VideoEncoder::EncoderStatusCB done_cb) {
         saved_output_cb = std::move(output_cb);
         RunStatusCallbackAync(std::move(done_cb));
       }));
@@ -164,7 +167,7 @@
   EXPECT_CALL(*secondary_video_encoder_, Encode(_, _, _))
       .WillRepeatedly(
           Invoke([&, this](scoped_refptr<VideoFrame> frame, bool key_frame,
-                           VideoEncoder::StatusCB done_cb) {
+                           VideoEncoder::EncoderStatusCB done_cb) {
             VideoEncoderOutput output;
             output.timestamp = frame->timestamp();
             saved_output_cb.Run(std::move(output), {});
@@ -204,7 +207,7 @@
       .WillOnce(Invoke([&, this](VideoCodecProfile profile,
                                  const VideoEncoder::Options& options,
                                  VideoEncoder::OutputCB output_cb,
-                                 VideoEncoder::StatusCB done_cb) {
+                                 VideoEncoder::EncoderStatusCB done_cb) {
         primary_output_cb = std::move(output_cb);
         RunStatusCallbackAync(std::move(done_cb));
       }));
@@ -214,7 +217,7 @@
       .WillOnce(Invoke([&, this](VideoCodecProfile profile,
                                  const VideoEncoder::Options& options,
                                  VideoEncoder::OutputCB output_cb,
-                                 VideoEncoder::StatusCB done_cb) {
+                                 VideoEncoder::EncoderStatusCB done_cb) {
         secondary_output_cb = std::move(output_cb);
         RunStatusCallbackAync(std::move(done_cb));
       }));
@@ -223,27 +226,27 @@
 
   // Start failing encodes after half of the frames.
   EXPECT_CALL(*main_video_encoder_, Encode(_, _, _))
-      .WillRepeatedly(
-          Invoke([&, this](scoped_refptr<VideoFrame> frame, bool key_frame,
-                           VideoEncoder::StatusCB done_cb) {
-            EXPECT_TRUE(frame);
-            EXPECT_TRUE(done_cb);
-            if (frame->timestamp() > encoder_switch_time) {
-              std::move(done_cb).Run(StatusCode::kEncoderFailedEncode);
-              return;
-            }
+      .WillRepeatedly(Invoke([&, this](scoped_refptr<VideoFrame> frame,
+                                       bool key_frame,
+                                       VideoEncoder::EncoderStatusCB done_cb) {
+        EXPECT_TRUE(frame);
+        EXPECT_TRUE(done_cb);
+        if (frame->timestamp() > encoder_switch_time) {
+          std::move(done_cb).Run(EncoderStatus::Codes::kEncoderFailedEncode);
+          return;
+        }
 
-            VideoEncoderOutput output;
-            output.timestamp = frame->timestamp();
-            primary_output_cb.Run(std::move(output), {});
-            RunStatusCallbackAync(std::move(done_cb));
-          }));
+        VideoEncoderOutput output;
+        output.timestamp = frame->timestamp();
+        primary_output_cb.Run(std::move(output), {});
+        RunStatusCallbackAync(std::move(done_cb));
+      }));
 
   // All encodes should come to the secondary encoder.
   EXPECT_CALL(*secondary_video_encoder_, Encode(_, _, _))
       .WillRepeatedly(
           Invoke([&, this](scoped_refptr<VideoFrame> frame, bool key_frame,
-                           VideoEncoder::StatusCB done_cb) {
+                           VideoEncoder::EncoderStatusCB done_cb) {
             EXPECT_TRUE(frame);
             EXPECT_TRUE(done_cb);
             EXPECT_GT(frame->timestamp(), encoder_switch_time);
@@ -282,9 +285,9 @@
       .WillOnce(Invoke([&, this](VideoCodecProfile profile,
                                  const VideoEncoder::Options& options,
                                  VideoEncoder::OutputCB output_cb,
-                                 VideoEncoder::StatusCB done_cb) {
+                                 VideoEncoder::EncoderStatusCB done_cb) {
         RunStatusCallbackAync(std::move(done_cb),
-                              StatusCode::kEncoderUnsupportedProfile);
+                              EncoderStatus::Codes::kEncoderUnsupportedProfile);
       }));
 
   // Initialize() on the second encoder should also fail
@@ -292,30 +295,31 @@
       .WillOnce(Invoke([&, this](VideoCodecProfile profile,
                                  const VideoEncoder::Options& options,
                                  VideoEncoder::OutputCB output_cb,
-                                 VideoEncoder::StatusCB done_cb) {
+                                 VideoEncoder::EncoderStatusCB done_cb) {
         RunStatusCallbackAync(std::move(done_cb),
-                              StatusCode::kEncoderUnsupportedCodec);
+                              EncoderStatus::Codes::kEncoderUnsupportedCodec);
       }));
 
   EXPECT_CALL(*main_video_encoder_, Encode(_, _, _))
       .WillRepeatedly(
           Invoke([&, this](scoped_refptr<VideoFrame> frame, bool key_frame,
-                           VideoEncoder::StatusCB done_cb) {
-            RunStatusCallbackAync(std::move(done_cb),
-                                  StatusCode::kEncoderInitializeNeverCompleted);
+                           VideoEncoder::EncoderStatusCB done_cb) {
+            RunStatusCallbackAync(
+                std::move(done_cb),
+                EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
           }));
 
   fallback_encoder_->Initialize(
       profile, options, std::move(output_cb),
-      ValidatingStatusCB(StatusCode::kEncoderUnsupportedCodec));
+      ValidatingStatusCB(EncoderStatus::Codes::kEncoderUnsupportedCodec));
 
   for (int i = 0; i < kFrameCount; i++) {
     auto frame = VideoFrame::CreateFrame(PIXEL_FORMAT_I420, kFrameSize,
                                          gfx::Rect(kFrameSize), kFrameSize,
                                          base::Seconds(i));
-    auto done_callback = base::BindLambdaForTesting([this](Status s) {
+    auto done_callback = base::BindLambdaForTesting([this](EncoderStatus s) {
       EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
-      EXPECT_EQ(s.code(), StatusCode::kEncoderUnsupportedCodec);
+      EXPECT_EQ(s.code(), EncoderStatus::Codes::kEncoderUnsupportedCodec);
       callback_runner_->DeleteSoon(FROM_HERE, std::move(fallback_encoder_));
     });
     fallback_encoder_->Encode(frame, true, std::move(done_callback));
@@ -325,4 +329,4 @@
   EXPECT_TRUE(FallbackHappened());
 }
 
-}  // namespace media
\ No newline at end of file
+}  // namespace media
diff --git a/media/video/vpx_video_encoder.cc b/media/video/vpx_video_encoder.cc
index 523bdf42..e80e08a 100644
--- a/media/video/vpx_video_encoder.cc
+++ b/media/video/vpx_video_encoder.cc
@@ -82,14 +82,15 @@
   return desired_threads;
 }
 
-Status SetUpVpxConfig(const VideoEncoder::Options& opts,
-                      vpx_codec_enc_cfg_t* config) {
+EncoderStatus SetUpVpxConfig(const VideoEncoder::Options& opts,
+                             vpx_codec_enc_cfg_t* config) {
   if (opts.frame_size.width() <= 0 || opts.frame_size.height() <= 0)
-    return Status(StatusCode::kEncoderUnsupportedConfig,
-                  "Negative width or height values.");
+    return EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedConfig,
+                         "Negative width or height values.");
 
   if (!opts.frame_size.GetCheckedArea().IsValid())
-    return Status(StatusCode::kEncoderUnsupportedConfig, "Frame is too large.");
+    return EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedConfig,
+                         "Frame is too large.");
 
   config->g_pass = VPX_RC_ONE_PASS;
   config->g_lag_in_frames = 0;
@@ -131,7 +132,7 @@
   config->g_h = opts.frame_size.height();
 
   if (!opts.scalability_mode)
-    return Status();
+    return EncoderStatus::Codes::kOk;
 
   switch (opts.scalability_mode.value()) {
     case SVCScalabilityMode::kL1T2:
@@ -183,12 +184,12 @@
       config->g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
       break;
     default: {
-      return Status(StatusCode::kEncoderUnsupportedConfig,
-                    "Unsupported number of temporal layers.");
+      return EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedConfig,
+                           "Unsupported number of temporal layers.");
     }
   }
 
-  return Status();
+  return EncoderStatus::Codes::kOk;
 }
 
 vpx_svc_extra_cfg_t MakeSvcExtraConfig(const vpx_codec_enc_cfg_t& config) {
@@ -203,21 +204,21 @@
   return result;
 }
 
-Status ReallocateVpxImageIfNeeded(vpx_image_t* vpx_image,
-                                  const vpx_img_fmt fmt,
-                                  int width,
-                                  int height) {
+EncoderStatus ReallocateVpxImageIfNeeded(vpx_image_t* vpx_image,
+                                         const vpx_img_fmt fmt,
+                                         int width,
+                                         int height) {
   if (vpx_image->fmt != fmt || static_cast<int>(vpx_image->w) != width ||
       static_cast<int>(vpx_image->h) != height) {
     vpx_img_free(vpx_image);
     if (vpx_image != vpx_img_alloc(vpx_image, fmt, width, height, 1)) {
-      return Status(StatusCode::kEncoderFailedEncode,
-                    "Invalid format or frame size.");
+      return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                           "Invalid format or frame size.");
     }
     vpx_image->bit_depth = (fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 16 : 8;
   }
   // else no-op since the image don't need to change format.
-  return Status();
+  return EncoderStatus::Codes::kOk;
 }
 
 void FreeCodecCtx(vpx_codec_ctx_t* codec_ctx) {
@@ -237,10 +238,10 @@
 void VpxVideoEncoder::Initialize(VideoCodecProfile profile,
                                  const Options& options,
                                  OutputCB output_cb,
-                                 StatusCB done_cb) {
+                                 EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeTwice);
+    std::move(done_cb).Run(EncoderStatus::Codes::kEncoderInitializeTwice);
     return;
   }
   profile_ = profile;
@@ -254,17 +255,19 @@
     is_vp9 = true;
     iface = vpx_codec_vp9_cx();
   } else {
-    auto status = Status(StatusCode::kEncoderUnsupportedProfile)
-                      .WithData("profile", profile);
+    auto status =
+        EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedProfile)
+            .WithData("profile", profile);
     std::move(done_cb).Run(status);
     return;
   }
 
   auto vpx_error = vpx_codec_enc_config_default(iface, &codec_config_, 0);
   if (vpx_error != VPX_CODEC_OK) {
-    auto status = Status(StatusCode::kEncoderInitializationError,
-                         "Failed to get default VPX config.")
-                      .WithData("vpx_error", vpx_error);
+    auto status =
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Failed to get default VPX config.")
+            .WithData("vpx_error", vpx_error);
     std::move(done_cb).Run(status);
     return;
   }
@@ -294,7 +297,7 @@
       break;
   }
 
-  Status status = SetUpVpxConfig(options, &codec_config_);
+  auto status = SetUpVpxConfig(options, &codec_config_);
   if (!status.is_ok()) {
     std::move(done_cb).Run(status);
     return;
@@ -310,8 +313,8 @@
         "VPX encoder initialization error: %s %s",
         vpx_codec_err_to_string(vpx_error), codec->err_detail);
     DLOG(ERROR) << msg;
-    status = Status(StatusCode::kEncoderInitializationError, msg);
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError, msg));
     return;
   }
 
@@ -327,17 +330,17 @@
         base::StringPrintf("VPX encoder VP8E_SET_CPUUSED error: %s",
                            vpx_codec_err_to_string(vpx_error));
     DLOG(ERROR) << msg;
-    status = Status(StatusCode::kEncoderInitializationError, msg);
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError, msg));
     return;
   }
 
   if (&vpx_image_ != vpx_img_alloc(&vpx_image_, img_fmt,
                                    options.frame_size.width(),
                                    options.frame_size.height(), 1)) {
-    status = Status(StatusCode::kEncoderInitializationError,
-                    "Invalid format or frame size.");
-    std::move(done_cb).Run(status);
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Invalid format or frame size."));
     return;
   }
   vpx_image_.bit_depth = bits_for_storage;
@@ -364,7 +367,8 @@
             base::StringPrintf("Can't activate SVC encoding: %s",
                                vpx_codec_err_to_string(vpx_error));
         DLOG(ERROR) << msg;
-        status = Status(StatusCode::kEncoderInitializationError, msg);
+        status = EncoderStatus(
+            EncoderStatus::Codes::kEncoderInitializationError, msg);
         std::move(done_cb).Run(status);
         return;
       }
@@ -379,21 +383,23 @@
   originally_configured_size_ = options.frame_size;
   output_cb_ = BindToCurrentLoop(std::move(output_cb));
   codec_ = std::move(codec);
-  std::move(done_cb).Run(Status());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
 void VpxVideoEncoder::Encode(scoped_refptr<VideoFrame> frame,
                              bool key_frame,
-                             StatusCB done_cb) {
+                             EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (!codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(done_cb).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
   if (!frame) {
-    std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode,
-                                  "No frame provided for encoding."));
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                      "No frame provided for encoding."));
     return;
   }
   bool supported_format = frame->format() == PIXEL_FORMAT_NV12 ||
@@ -404,11 +410,11 @@
                           frame->format() == PIXEL_FORMAT_ARGB;
   if ((!frame->IsMappable() && !frame->HasGpuMemoryBuffer()) ||
       !supported_format) {
-    Status status =
-        Status(StatusCode::kEncoderFailedEncode, "Unexpected frame format.")
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                      "Unexpected frame format.")
             .WithData("IsMappable", frame->IsMappable())
-            .WithData("format", frame->format());
-    std::move(done_cb).Run(std::move(status));
+            .WithData("format", frame->format()));
     return;
   }
 
@@ -416,8 +422,8 @@
     frame = ConvertToMemoryMappedFrame(frame);
     if (!frame) {
       std::move(done_cb).Run(
-          Status(StatusCode::kEncoderFailedEncode,
-                 "Convert GMB frame to MemoryMappedFrame failed."));
+          EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                        "Convert GMB frame to MemoryMappedFrame failed."));
       return;
     }
   }
@@ -428,15 +434,20 @@
         is_yuv ? frame->format() : PIXEL_FORMAT_I420, options_.frame_size,
         gfx::Rect(options_.frame_size), options_.frame_size,
         frame->timestamp());
-    Status status;
-    if (resized_frame) {
-      status = ConvertAndScaleFrame(*frame, *resized_frame, resize_buf_);
-    } else {
-      status = Status(StatusCode::kEncoderFailedEncode,
-                      "Can't allocate a resized frame.");
+
+    if (!resized_frame) {
+      std::move(done_cb).Run(
+          EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode,
+                        "Can't allocate a resized frame"));
+      return;
     }
-    if (!status.is_ok()) {
-      std::move(done_cb).Run(std::move(status));
+
+    auto convert_status =
+        ConvertAndScaleFrame(*frame, *resized_frame, resize_buf_);
+    if (!convert_status.is_ok()) {
+      std::move(done_cb).Run(
+          EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode)
+              .AddCause(std::move(convert_status)));
       return;
     }
     frame = std::move(resized_frame);
@@ -468,7 +479,7 @@
       vpx_img_fmt_t fmt = frame->format() == PIXEL_FORMAT_NV12
                               ? VPX_IMG_FMT_NV12
                               : VPX_IMG_FMT_I420;
-      Status status = ReallocateVpxImageIfNeeded(
+      EncoderStatus status = ReallocateVpxImageIfNeeded(
           &vpx_image_, fmt, codec_config_.g_w, codec_config_.g_h);
       if (!status.is_ok()) {
         std::move(done_cb).Run(status);
@@ -539,22 +550,23 @@
                                          vpx_codec_err_to_string(vpx_error),
                                          vpx_codec_error_detail(codec_.get()));
     DLOG(ERROR) << msg;
-    Status status = Status(StatusCode::kEncoderFailedEncode, msg)
-                        .WithData("vpx_error", vpx_error);
-    std::move(done_cb).Run(std::move(status));
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode, msg)
+            .WithData("vpx_error", vpx_error));
     return;
   }
 
   DrainOutputs(temporal_id, frame->timestamp(), frame->ColorSpace());
-  std::move(done_cb).Run(Status());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
 void VpxVideoEncoder::ChangeOptions(const Options& options,
                                     OutputCB output_cb,
-                                    StatusCB done_cb) {
+                                    EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (!codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(done_cb).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
@@ -578,8 +590,8 @@
     auto new_area = options.frame_size.GetCheckedArea();
     DCHECK(old_area.IsValid());
     if (!new_area.IsValid() || new_area.ValueOrDie() > old_area.ValueOrDie()) {
-      auto status = Status(
-          StatusCode::kEncoderUnsupportedConfig,
+      auto status = EncoderStatus(
+          EncoderStatus::Codes::kEncoderUnsupportedConfig,
           "libvpx/VP8 doesn't support dynamically increasing frame area");
       std::move(done_cb).Run(std::move(status));
       return;
@@ -588,8 +600,8 @@
     // VP9 resize restrictions
     if (options.frame_size.width() > originally_configured_size_.width() ||
         options.frame_size.height() > originally_configured_size_.height()) {
-      auto status = Status(
-          StatusCode::kEncoderUnsupportedConfig,
+      auto status = EncoderStatus(
+          EncoderStatus::Codes::kEncoderUnsupportedConfig,
           "libvpx/VP9 doesn't support dynamically increasing frame dimentions");
       std::move(done_cb).Run(std::move(status));
       return;
@@ -624,8 +636,8 @@
     if (!output_cb.is_null())
       output_cb_ = BindToCurrentLoop(std::move(output_cb));
   } else {
-    status = Status(StatusCode::kEncoderUnsupportedConfig,
-                    "Failed to set new VPX config")
+    status = EncoderStatus(EncoderStatus::Codes::kEncoderUnsupportedConfig,
+                           "Failed to set new VPX config")
                  .WithData("vpx_error", error);
   }
 
@@ -659,10 +671,11 @@
   vpx_img_free(&vpx_image_);
 }
 
-void VpxVideoEncoder::Flush(StatusCB done_cb) {
+void VpxVideoEncoder::Flush(EncoderStatusCB done_cb) {
   done_cb = BindToCurrentLoop(std::move(done_cb));
   if (!codec_) {
-    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    std::move(done_cb).Run(
+        EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
     return;
   }
 
@@ -672,13 +685,13 @@
                                          vpx_codec_err_to_string(vpx_error),
                                          vpx_codec_error_detail(codec_.get()));
     DLOG(ERROR) << msg;
-    Status status = Status(StatusCode::kEncoderFailedEncode, msg)
-                        .WithData("vpx_error", vpx_error);
+    auto status = EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode, msg)
+                      .WithData("vpx_error", vpx_error);
     std::move(done_cb).Run(std::move(status));
     return;
   }
   DrainOutputs(0, base::TimeDelta(), gfx::ColorSpace());
-  std::move(done_cb).Run(Status());
+  std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
 void VpxVideoEncoder::DrainOutputs(int temporal_id,
diff --git a/media/video/vpx_video_encoder.h b/media/video/vpx_video_encoder.h
index 1b69d51..a807af4 100644
--- a/media/video/vpx_video_encoder.h
+++ b/media/video/vpx_video_encoder.h
@@ -27,14 +27,14 @@
   void Initialize(VideoCodecProfile profile,
                   const Options& options,
                   OutputCB output_cb,
-                  StatusCB done_cb) override;
+                  EncoderStatusCB done_cb) override;
   void Encode(scoped_refptr<VideoFrame> frame,
               bool key_frame,
-              StatusCB done_cb) override;
+              EncoderStatusCB done_cb) override;
   void ChangeOptions(const Options& options,
                      OutputCB output_cb,
-                     StatusCB done_cb) override;
-  void Flush(StatusCB done_cb) override;
+                     EncoderStatusCB done_cb) override;
+  void Flush(EncoderStatusCB done_cb) override;
 
  private:
   base::TimeDelta GetFrameDuration(const VideoFrame& frame);
diff --git a/remoting/base/oauth_token_getter.h b/remoting/base/oauth_token_getter.h
index 061bbc9..d356ede 100644
--- a/remoting/base/oauth_token_getter.h
+++ b/remoting/base/oauth_token_getter.h
@@ -36,7 +36,7 @@
   // This structure contains information required to perform authorization
   // with the authorization server.
   struct OAuthAuthorizationCredentials {
-    // |login| is used to valdiate |refresh_token| match.
+    // |login| is used to validate |refresh_token| match.
     // |is_service_account| should be True if the OAuth refresh token is for a
     // service account, False for a user account, to allow the correct client-ID
     // to be used.
@@ -69,7 +69,7 @@
 
     ~OAuthIntermediateCredentials();
 
-    // Code used to check out a access token from the authrozation service.
+    // Code used to exchange for an access token from the authorization service.
     std::string authorization_code;
 
     // Override uri for oauth redirect. This is used for client accounts only
diff --git a/remoting/base/result.h b/remoting/base/result.h
index d045fbb..6775e0d 100644
--- a/remoting/base/result.h
+++ b/remoting/base/result.h
@@ -29,7 +29,7 @@
 // Result()
 //   Only present when the success value is default constructible. Default
 //   constructs the success value. This is useful for situations like IPC
-//   deserialization where a default-costructed instance is created and the
+//   deserialization where a default-constructed instance is created and the
 //   actual value is filled in later. In general, prefer using the
 //   Result(kSuccessTag) constructor to be explicit.
 //
diff --git a/remoting/client/display/drawable.h b/remoting/client/display/drawable.h
index daf316c..140935c8 100644
--- a/remoting/client/display/drawable.h
+++ b/remoting/client/display/drawable.h
@@ -39,7 +39,7 @@
     CURSOR = 300,
   };
 
-  // A higher Z Index shiould be draw ontop of a lower z index. Elements with
+  // A higher Z Index should be draw ontop of a lower z index. Elements with
   // the same Z Index should draw in order inserted into the renderer.
   virtual int GetZIndex() = 0;
 };
diff --git a/remoting/client/input/keyboard_interpreter.cc b/remoting/client/input/keyboard_interpreter.cc
index 3ff991f..e1d3de2 100644
--- a/remoting/client/input/keyboard_interpreter.cc
+++ b/remoting/client/input/keyboard_interpreter.cc
@@ -58,7 +58,7 @@
   }
 
   base::queue<KeyEvent> keys;
-  // TODO(nicholss): Handle modifers.
+  // TODO(nicholss): Handle modifiers.
   // Key press.
   keys.push({static_cast<uint32_t>(ui::DomCode::BACKSPACE), true});
 
diff --git a/remoting/client/input/text_keyboard_input_strategy.cc b/remoting/client/input/text_keyboard_input_strategy.cc
index 04a1bc96..b5287f80 100644
--- a/remoting/client/input/text_keyboard_input_strategy.cc
+++ b/remoting/client/input/text_keyboard_input_strategy.cc
@@ -20,7 +20,7 @@
 
 void TextKeyboardInputStrategy::HandleTextEvent(const std::string& text,
                                                 uint8_t modifiers) {
-  // TODO(nicholss): Handle modifers.
+  // TODO(nicholss): Handle modifiers.
   input_injector_->SendTextEvent(text);
 }
 
diff --git a/remoting/client/notification/notification_client.cc b/remoting/client/notification/notification_client.cc
index 48ccfb64..403d403 100644
--- a/remoting/client/notification/notification_client.cc
+++ b/remoting/client/notification/notification_client.cc
@@ -12,6 +12,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/strings/stringize_macros.h"
+#include "base/system/sys_info.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -195,6 +196,7 @@
           std::make_unique<GstaticJsonFetcher>(network_task_runner),
           kCurrentPlatform,
           kCurrentVersion,
+          base::SysInfo::OperatingSystemVersion(),
 #if defined(OS_ANDROID)
           // GetApplicationLocale() returns empty string on Android since we
           // don't pack any .pak file into the apk, so we need to get the locale
@@ -211,11 +213,13 @@
 NotificationClient::NotificationClient(std::unique_ptr<JsonFetcher> fetcher,
                                        const std::string& current_platform,
                                        const std::string& current_version,
+                                       const std::string& current_os_version,
                                        const std::string& locale,
                                        bool should_ignore_dev_messages)
     : fetcher_(std::move(fetcher)),
       current_platform_(current_platform),
       current_version_(current_version),
+      current_os_version_(current_os_version),
       locale_(locale),
       should_ignore_dev_messages_(should_ignore_dev_messages) {
   VLOG(1) << "Platform: " << current_platform_
@@ -309,6 +313,21 @@
     return absl::nullopt;
   }
 
+  // OS version check is not performed if |os_version| is not specified.
+  std::string os_version_spec_string;
+  if (FindKeyAndGet(rule, "os_version", &os_version_spec_string)) {
+    VersionRange os_version_range(os_version_spec_string);
+    if (!os_version_range.IsValid()) {
+      LOG(ERROR) << "Invalid OS version range: " << os_version_spec_string;
+      return absl::nullopt;
+    }
+    if (!os_version_range.ContainsVersion(current_os_version_)) {
+      VLOG(1) << "Current OS version " << current_os_version_
+              << " not in range " << os_version_spec_string;
+      return absl::nullopt;
+    }
+  }
+
   if (!ShouldShowNotificationForUser(user_email, percent)) {
     VLOG(1) << "User is not selected for notification";
     return absl::nullopt;
diff --git a/remoting/client/notification/notification_client.h b/remoting/client/notification/notification_client.h
index ad77fc8c..32dfcab 100644
--- a/remoting/client/notification/notification_client.h
+++ b/remoting/client/notification/notification_client.h
@@ -51,6 +51,7 @@
   NotificationClient(std::unique_ptr<JsonFetcher> fetcher,
                      const std::string& current_platform,
                      const std::string& current_version,
+                     const std::string& current_os_version,
                      const std::string& locale,
                      bool should_ignore_dev_messages);
 
@@ -75,6 +76,7 @@
   std::unique_ptr<JsonFetcher> fetcher_;
   std::string current_platform_;
   std::string current_version_;
+  std::string current_os_version_;
   std::string locale_;
   bool should_ignore_dev_messages_;
 };
diff --git a/remoting/client/notification/notification_client_unittest.cc b/remoting/client/notification/notification_client_unittest.cc
index 76f63d7..ec370658 100644
--- a/remoting/client/notification/notification_client_unittest.cc
+++ b/remoting/client/notification/notification_client_unittest.cc
@@ -26,6 +26,7 @@
 constexpr char kTestEmail[] = "test@example.com";
 constexpr char kTestPlatform[] = "IOS";
 constexpr char kTestVersion[] = "76.0.3809.13";
+constexpr char kTestOsVersion[] = "15.1";
 constexpr char kTestLocale[] = "zh-CN";
 
 class MockJsonFetcher : public JsonFetcher {
@@ -99,9 +100,9 @@
   void Reset(bool should_ignore_dev_messages) {
     auto fetcher = std::make_unique<MockJsonFetcher>();
     fetcher_ = fetcher.get();
-    client_ = base::WrapUnique(
-        new NotificationClient(std::move(fetcher), kTestPlatform, kTestVersion,
-                               kTestLocale, should_ignore_dev_messages));
+    client_ = base::WrapUnique(new NotificationClient(
+        std::move(fetcher), kTestPlatform, kTestVersion, kTestOsVersion,
+        kTestLocale, should_ignore_dev_messages));
   }
 
   raw_ptr<MockJsonFetcher> fetcher_;
@@ -159,6 +160,36 @@
   client_->GetNotification(kTestEmail, callback.Get());
 }
 
+TEST_F(NotificationClientTest, OsVersionNotMatched) {
+  base::Value rule = CreateDefaultRule();
+  rule.SetStringKey("os_version", "(-15.1)");
+  base::Value rules(base::Value::Type::LIST);
+  rules.Append(std::move(rule));
+  EXPECT_CALL(*fetcher_, FetchJsonFile("notification/rules.json"))
+      .WillOnce(ReturnByMove(std::move(rules)));
+
+  base::MockCallback<NotificationClient::NotificationCallback> callback;
+  EXPECT_CALL(callback, Run(NoMessage()));
+  client_->GetNotification(kTestEmail, callback.Get());
+}
+
+TEST_F(NotificationClientTest, OsVersionMatched) {
+  base::Value rule = CreateDefaultRule();
+  rule.SetStringKey("os_version", "[15-)");
+  base::Value rules(base::Value::Type::LIST);
+  rules.Append(std::move(rule));
+  EXPECT_CALL(*fetcher_, FetchJsonFile("notification/rules.json"))
+      .WillOnce(ReturnByMove(std::move(rules)));
+  EXPECT_CALL(*fetcher_, FetchJsonFile("notification/message_text.json"))
+      .WillOnce(ReturnByMove(CreateDefaultTranslations("message")));
+  EXPECT_CALL(*fetcher_, FetchJsonFile("notification/link_text.json"))
+      .WillOnce(ReturnByMove(CreateDefaultTranslations("link")));
+
+  base::MockCallback<NotificationClient::NotificationCallback> callback;
+  EXPECT_CALL(callback, Run(MessageMatches(CreateDefaultNotification())));
+  client_->GetNotification(kTestEmail, callback.Get());
+}
+
 TEST_F(NotificationClientTest, UserNotSelected) {
   base::Value rule = CreateDefaultRule();
   rule.SetIntKey("percent", 0);
diff --git a/remoting/client/ui/desktop_viewport.h b/remoting/client/ui/desktop_viewport.h
index af264ac..b0ae9e3 100644
--- a/remoting/client/ui/desktop_viewport.h
+++ b/remoting/client/ui/desktop_viewport.h
@@ -79,7 +79,7 @@
 
   // Registers the callback to be called once the transformation has changed.
   // run_immediately: If true and the viewport is ready to be used, the callback
-  //     will be called immedately with the transformation matrix.
+  // will be called immediately with the transformation matrix.
   void RegisterOnTransformationChangedCallback(
       const TransformationCallback& callback,
       bool run_immediately);
diff --git a/remoting/codec/webrtc_video_encoder_gpu.cc b/remoting/codec/webrtc_video_encoder_gpu.cc
index 629ab03..0dde5cc 100644
--- a/remoting/codec/webrtc_video_encoder_gpu.cc
+++ b/remoting/codec/webrtc_video_encoder_gpu.cc
@@ -93,7 +93,7 @@
 // 3. In BeginInitialization(), the Core instance constructs the
 //      VideoEncodeAccelerator using the saved dimensions from the DesktopFrame.
 //      If the VideoEncodeAccelerator is constructed successfully, the state is
-//      set to INITIALIZING. If not, the state isset to INIITALIZATION_ERROR.
+//      set to INITIALIZING. If not, the state isset to INITIALIZATION_ERROR.
 // 4. Some time later, the VideoEncodeAccelerator sets itself up and is ready
 //      to encode. At this point, it calls the Core instance's
 //      RequireBitstreamBuffers() method. Once bitstream buffers are allocated,
diff --git a/remoting/host/audio_silence_detector.cc b/remoting/host/audio_silence_detector.cc
index 6b4d0351d..400f6c4 100644
--- a/remoting/host/audio_silence_detector.cc
+++ b/remoting/host/audio_silence_detector.cc
@@ -44,7 +44,7 @@
   bool silent_packet = true;
   // Potentially this loop can be optimized (e.g. using SSE or adding special
   // case for threshold_==0), but it's not worth worrying about because the
-  // amount of data it processes is relaively small.
+  // amount of data it processes is relatively small.
   for (int i = 0; i < samples_count; ++i) {
     if (abs(samples[i]) > threshold_) {
       silent_packet = false;
diff --git a/remoting/host/curtain_mode_mac.cc b/remoting/host/curtain_mode_mac.cc
index 0a36a0f..5000f697 100644
--- a/remoting/host/curtain_mode_mac.cc
+++ b/remoting/host/curtain_mode_mac.cc
@@ -67,7 +67,7 @@
 }
 
 // Used to detach the current session from the local console and disconnect
-// the connnection if it gets re-attached.
+// the connection if it gets re-attached.
 //
 // Because the switch-in handler can only called on the main (UI) thread, this
 // class installs the handler and detaches the current session from the console
@@ -135,7 +135,7 @@
   // Activate curtain asynchronously since it has to be done on the UI thread.
   // Because the curtain activation is asynchronous, it is possible that
   // the connection will not be curtained for a brief moment. This seems to be
-  // unaviodable as long as the curtain enforcement depends on processing of
+  // unavoidable as long as the curtain enforcement depends on processing of
   // the switch-in notifications.
   ui_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&SessionWatcher::ActivateCurtain, this));
diff --git a/remoting/host/desktop_resizer.h b/remoting/host/desktop_resizer.h
index 478837d..8f414a8 100644
--- a/remoting/host/desktop_resizer.h
+++ b/remoting/host/desktop_resizer.h
@@ -41,7 +41,7 @@
 
   // Restore the original desktop resolution. The caller must provide the
   // original resolution of the desktop, as returned by |GetCurrentResolution|,
-  // as a hint. However, implementaions are free to ignore this. For example,
+  // as a hint. However, implementations are free to ignore this. For example,
   // virtual hosts will typically ignore it to avoid unnecessary resizes.
   virtual void RestoreResolution(const ScreenResolution& original) = 0;
 };
diff --git a/remoting/host/file_transfer/file_transfer_message_handler_unittest.cc b/remoting/host/file_transfer/file_transfer_message_handler_unittest.cc
index bc248e2..89e317e 100644
--- a/remoting/host/file_transfer/file_transfer_message_handler_unittest.cc
+++ b/remoting/host/file_transfer/file_transfer_message_handler_unittest.cc
@@ -296,7 +296,7 @@
   fake_pipe_->ClosePipe();
 
   const base::queue<std::string>& sent_messages = fake_pipe_->sent_messages();
-  // First is the sucess message, second should be a protocol error.
+  // First is the success message, second should be a protocol error.
   ASSERT_EQ(2ul, sent_messages.size());
   protocol::FileTransfer response;
   response.ParseFromString(sent_messages.back());
diff --git a/remoting/host/host_status_observer.h b/remoting/host/host_status_observer.h
index 349a749..56165ab 100644
--- a/remoting/host/host_status_observer.h
+++ b/remoting/host/host_status_observer.h
@@ -27,7 +27,7 @@
   // A new client is authenticated.
   virtual void OnClientAuthenticated(const std::string& jid) {}
 
-  // All channels for an autheticated client are connected.
+  // All channels for an authenticated client are connected.
   virtual void OnClientConnected(const std::string& jid) {}
 
   // An authenticated client is disconnected.
diff --git a/remoting/host/input_monitor/local_hotkey_input_monitor.h b/remoting/host/input_monitor/local_hotkey_input_monitor.h
index 49cb375..26af09cd 100644
--- a/remoting/host/input_monitor/local_hotkey_input_monitor.h
+++ b/remoting/host/input_monitor/local_hotkey_input_monitor.h
@@ -18,7 +18,7 @@
 namespace remoting {
 
 // Monitors the local input to notify about keyboard hotkeys. If implemented for
-// the platform, catches the disconnection keyboard shortcut (Ctlr-Alt-Esc) and
+// the platform, catches the disconnection keyboard shortcut (Ctrl-Alt-Esc) and
 // invokes |disconnect_callback| when this key combination is pressed.
 class LocalHotkeyInputMonitor {
  public:
diff --git a/remoting/host/native_messaging/log_message_handler.cc b/remoting/host/native_messaging/log_message_handler.cc
index 15c155dd..5c6a0b5c 100644
--- a/remoting/host/native_messaging/log_message_handler.cc
+++ b/remoting/host/native_messaging/log_message_handler.cc
@@ -83,7 +83,7 @@
 
   // This method is always called under the global lock, so post a task to
   // handle the log message, even if we're already on the correct thread.
-  // This alows the lock to be released quickly.
+  // This allows the lock to be released quickly.
   //
   // Note that this means that LOG(FATAL) messages will be lost because the
   // process will exit before the message is sent to the client.
diff --git a/remoting/host/security_key/security_key_ipc_client_unittest.cc b/remoting/host/security_key/security_key_ipc_client_unittest.cc
index 4e8af29..56398bd3 100644
--- a/remoting/host/security_key/security_key_ipc_client_unittest.cc
+++ b/remoting/host/security_key/security_key_ipc_client_unittest.cc
@@ -327,7 +327,7 @@
       mojo::NamedPlatformChannel::ServerNameFromUTF8(
           kNonexistentIpcChannelName));
 
-  // Attempt to establish the conection (should fail since the IPC channel does
+  // Attempt to establish the connection (should fail since the IPC channel does
   // not exist).
   security_key_ipc_client_.EstablishIpcConnection(
       base::BindOnce(&SecurityKeyIpcClientTest::ConnectionStateHandler,
diff --git a/remoting/host/setup/test_util.cc b/remoting/host/setup/test_util.cc
index ad80af6..5b82005 100644
--- a/remoting/host/setup/test_util.cc
+++ b/remoting/host/setup/test_util.cc
@@ -36,4 +36,4 @@
 #endif
 }
 
-}  // namepsace remoting
+}  // namespace remoting
diff --git a/remoting/host/win/elevated_native_messaging_host.h b/remoting/host/win/elevated_native_messaging_host.h
index 40eb5e4d..f251b15 100644
--- a/remoting/host/win/elevated_native_messaging_host.h
+++ b/remoting/host/win/elevated_native_messaging_host.h
@@ -62,7 +62,7 @@
   // Handle of the parent window.
   intptr_t parent_window_handle_;
 
-  // Indicates whether the launched process should be elevated when lauinched.
+  // Indicates whether the launched process should be elevated when launched.
   // Note: Binaries with uiaccess run at a higher UIPI level than the launching
   // process so they still need to be launched and controlled by this class but
   // do not require traditional elevation to function.
diff --git a/remoting/host/win/worker_process_launcher_unittest.cc b/remoting/host/win/worker_process_launcher_unittest.cc
index f9843f2..3829f025 100644
--- a/remoting/host/win/worker_process_launcher_unittest.cc
+++ b/remoting/host/win/worker_process_launcher_unittest.cc
@@ -370,8 +370,8 @@
   PROCESS_INFORMATION temp_process_info = {};
   ASSERT_TRUE(CreateProcess(nullptr,
                             notepad,
-                            nullptr,   // default process attibutes
-                            nullptr,   // default thread attibutes
+                            nullptr,   // default process attributes
+                            nullptr,   // default thread attributes
                             FALSE,  // do not inherit handles
                             CREATE_SUSPENDED,
                             nullptr,   // no environment
diff --git a/remoting/ios/display/gl_display_handler.mm b/remoting/ios/display/gl_display_handler.mm
index 57e2c9921..cb39fadc 100644
--- a/remoting/ios/display/gl_display_handler.mm
+++ b/remoting/ios/display/gl_display_handler.mm
@@ -125,7 +125,7 @@
     eagl_context_ =
         [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
     if (!eagl_context_) {
-      LOG(WARNING) << "Failed to create GLES3 context. Atempting to create "
+      LOG(WARNING) << "Failed to create GLES3 context. Attempting to create "
                    << "GLES2 context.";
       eagl_context_ =
           [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
diff --git a/remoting/signaling/fake_signal_strategy.h b/remoting/signaling/fake_signal_strategy.h
index 753deba..da7cbbe 100644
--- a/remoting/signaling/fake_signal_strategy.h
+++ b/remoting/signaling/fake_signal_strategy.h
@@ -28,7 +28,7 @@
   using PeerCallback = base::RepeatingCallback<void(
       std::unique_ptr<jingle_xmpp::XmlElement> message)>;
 
-  // Calls ConenctTo() to connect |peer1| and |peer2|. Both |peer1| and |peer2|
+  // Calls ConnectTo() to connect |peer1| and |peer2|. Both |peer1| and |peer2|
   // must belong to the current thread.
   static void Connect(FakeSignalStrategy* peer1, FakeSignalStrategy* peer2);
 
diff --git a/sandbox/win/src/handle_closer_agent.cc b/sandbox/win/src/handle_closer_agent.cc
index 0b0de36..a0bb656 100644
--- a/sandbox/win/src/handle_closer_agent.cc
+++ b/sandbox/win/src/handle_closer_agent.cc
@@ -4,14 +4,13 @@
 
 #include "sandbox/win/src/handle_closer_agent.h"
 
-#include <limits.h>
 #include <stddef.h>
-#include <cstdint>
 
 #include "base/check.h"
 #include "base/win/static_constants.h"
-#include "base/win/win_util.h"
+#include "base/win/windows_version.h"
 #include "sandbox/win/src/win_utils.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace sandbox {
 
@@ -143,56 +142,45 @@
 }
 
 bool HandleCloserAgent::CloseHandles() {
-  DWORD handle_count = UINT_MAX;
-  const int kInvalidHandleThreshold = 100;
-  const size_t kHandleOffset = 4;  // Handles are always a multiple of 4.
-
-  if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count))
-    return false;
-
   // Skip closing these handles when Application Verifier is in use in order to
   // avoid invalid-handle exceptions.
   if (GetModuleHandleA(base::win::kApplicationVerifierDllName))
     return true;
+  // If the accurate handle enumeration fails then fallback to the old brute
+  // force approach. This should only happen on Windows 7.
+  absl::optional<ProcessHandleMap> handle_map = GetCurrentProcessHandles();
+  if (!handle_map) {
+    DCHECK(base::win::GetVersion() < base::win::Version::WIN8);
+    handle_map = GetCurrentProcessHandlesWin7();
+  }
 
-  uint32_t handle_value = 0;
-  int invalid_count = 0;
+  if (!handle_map)
+    return false;
 
-  // Keep incrementing until we hit the number of handles reported by
-  // GetProcessHandleCount(). If we hit a very long sequence of invalid
-  // handles we assume that we've run past the end of the table.
-  while (handle_count && invalid_count < kInvalidHandleThreshold) {
-    handle_value += kHandleOffset;
-    HANDLE handle = base::win::Uint32ToHandle(handle_value);
-    std::wstring type_name;
-    if (!GetTypeNameFromHandle(handle, &type_name)) {
-      ++invalid_count;
+  for (const HandleMap::value_type& handle_to_close : handles_to_close_) {
+    ProcessHandleMap::iterator result = handle_map->find(handle_to_close.first);
+    if (result == handle_map->end())
       continue;
-    }
-
-    --handle_count;
-    // Check if we're looking for this type of handle.
-    HandleMap::iterator result = handles_to_close_.find(type_name);
-    if (result == handles_to_close_.end())
-      continue;
-
-    HandleMap::mapped_type& names = result->second;
-    // Empty set means close all handles of this type; otherwise check name.
-    if (!names.empty()) {
-      std::wstring handle_name;
-      // Move on to the next handle if this name doesn't match.
-      if (!GetPathFromHandle(handle, &handle_name) ||
-          !names.count(handle_name)) {
-        continue;
+    const HandleMap::mapped_type& names = handle_to_close.second;
+    for (HANDLE handle : result->second) {
+      // Empty set means close all handles of this type; otherwise check name.
+      if (!names.empty()) {
+        std::wstring handle_name;
+        // Move on to the next handle if this name doesn't match.
+        if (!GetPathFromHandle(handle, &handle_name) ||
+            !names.count(handle_name)) {
+          continue;
+        }
       }
-    }
 
-    if (!::SetHandleInformation(handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0))
-      return false;
-    if (!::CloseHandle(handle))
-      return false;
-    // Attempt to stuff this handle with a new dummy Event.
-    AttemptToStuffHandleSlot(handle, result->first);
+      // If we can't unprotect or close the handle we should keep going.
+      if (!::SetHandleInformation(handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0))
+        continue;
+      if (!::CloseHandle(handle))
+        continue;
+      // Attempt to stuff this handle with a new dummy Event.
+      AttemptToStuffHandleSlot(handle, result->first);
+    }
   }
 
   return true;
diff --git a/sandbox/win/src/nt_internals.h b/sandbox/win/src/nt_internals.h
index 79784aa..f9847fc 100644
--- a/sandbox/win/src/nt_internals.h
+++ b/sandbox/win/src/nt_internals.h
@@ -313,7 +313,8 @@
 // Partial definition only:
 typedef enum _PROCESSINFOCLASS {
   ProcessBasicInformation = 0,
-  ProcessExecuteFlags = 0x22
+  ProcessExecuteFlags = 0x22,
+  ProcessHandleTable = 0x3A
 } PROCESSINFOCLASS;
 
 // For the structure documentation, see
diff --git a/sandbox/win/src/win_utils.cc b/sandbox/win/src/win_utils.cc
index 364b2c3..611127d 100644
--- a/sandbox/win/src/win_utils.cc
+++ b/sandbox/win/src/win_utils.cc
@@ -20,6 +20,7 @@
 #include "base/numerics/safe_math.h"
 #include "base/strings/string_util.h"
 #include "base/win/pe_image.h"
+#include "base/win/win_util.h"
 #include "sandbox/win/src/internal_types.h"
 #include "sandbox/win/src/nt_internals.h"
 #include "sandbox/win/src/sandbox_nt_util.h"
@@ -561,6 +562,72 @@
   return base_address;
 }
 
+absl::optional<ProcessHandleMap> GetCurrentProcessHandles() {
+  NtQueryInformationProcessFunction query_information_process = nullptr;
+  ResolveNTFunctionPtr("NtQueryInformationProcess", &query_information_process);
+  if (!query_information_process)
+    return absl::nullopt;
+
+  DWORD handle_count;
+  if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count))
+    return absl::nullopt;
+
+  // The system call will return only handles up to the buffer size so add a
+  // margin of error of an additional 1000 handles.
+  std::vector<char> buffer((handle_count + 1000) * sizeof(uint32_t));
+  DWORD return_length;
+  NTSTATUS status = query_information_process(
+      ::GetCurrentProcess(), ProcessHandleTable, buffer.data(),
+      static_cast<ULONG>(buffer.size()), &return_length);
+
+  if (!NT_SUCCESS(status)) {
+    ::SetLastError(GetLastErrorFromNtStatus(status));
+    return absl::nullopt;
+  }
+  DCHECK(buffer.size() >= return_length);
+  DCHECK((buffer.size() % sizeof(uint32_t)) == 0);
+  ProcessHandleMap handle_map;
+  const uint32_t* handle_values = reinterpret_cast<uint32_t*>(buffer.data());
+  size_t count = return_length / sizeof(uint32_t);
+  for (size_t index = 0; index < count; ++index) {
+    HANDLE handle = base::win::Uint32ToHandle(handle_values[index]);
+    std::wstring type_name;
+    if (GetTypeNameFromHandle(handle, &type_name))
+      handle_map[type_name].push_back(handle);
+  }
+  return handle_map;
+}
+
+absl::optional<ProcessHandleMap> GetCurrentProcessHandlesWin7() {
+  DWORD handle_count = UINT_MAX;
+  const int kInvalidHandleThreshold = 100;
+  const size_t kHandleOffset = 4;  // Handles are always a multiple of 4.
+
+  if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count))
+    return absl::nullopt;
+  ProcessHandleMap handle_map;
+
+  uint32_t handle_value = 0;
+  int invalid_count = 0;
+
+  // Keep incrementing until we hit the number of handles reported by
+  // GetProcessHandleCount(). If we hit a very long sequence of invalid
+  // handles we assume that we've run past the end of the table.
+  while (handle_count && invalid_count < kInvalidHandleThreshold) {
+    handle_value += kHandleOffset;
+    HANDLE handle = base::win::Uint32ToHandle(handle_value);
+    std::wstring type_name;
+    if (!GetTypeNameFromHandle(handle, &type_name)) {
+      ++invalid_count;
+      continue;
+    }
+
+    --handle_count;
+    handle_map[type_name].push_back(handle);
+  }
+  return handle_map;
+}
+
 }  // namespace sandbox
 
 void ResolveNTFunctionPtr(const char* name, void* ptr) {
diff --git a/sandbox/win/src/win_utils.h b/sandbox/win/src/win_utils.h
index ce96c0f..2acbe034 100644
--- a/sandbox/win/src/win_utils.h
+++ b/sandbox/win/src/win_utils.h
@@ -7,11 +7,14 @@
 
 #include <stdlib.h>
 
+#include <map>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/cxx17_backports.h"
 #include "base/win/windows_types.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace sandbox {
 
@@ -22,6 +25,9 @@
 const wchar_t kNTDevicePrefix[] = L"\\Device\\";
 const size_t kNTDevicePrefixLen = base::size(kNTDevicePrefix) - 1;
 
+// List of handles mapped to their kernel object type name.
+using ProcessHandleMap = std::map<std::wstring, std::vector<HANDLE>>;
+
 // Basic implementation of a singleton which calls the destructor
 // when the exe is shutting down or the DLL is being unloaded.
 template <typename Derived>
@@ -116,6 +122,18 @@
 // the base address. This should only be called on new, suspended processes.
 void* GetProcessBaseAddress(HANDLE process);
 
+// Returns a map of handles open in the current process. The call will only
+// works on Windows 8+. The map is keyed by the kernel object type name. If
+// querying the handles fails an empty optional value is returned. Note that
+// unless all threads are suspended in the process the valid handles could
+// change between the return of the list and when you use them.
+absl::optional<ProcessHandleMap> GetCurrentProcessHandles();
+
+// Fallback function for GetCurrentProcessHandles. Should only be needed on
+// Windows 7 which doesn't support the API to query all process handles. This
+// uses a brute force method to get the process handles.
+absl::optional<ProcessHandleMap> GetCurrentProcessHandlesWin7();
+
 }  // namespace sandbox
 
 // Resolves a function name in NTDLL to a function pointer. The second parameter
diff --git a/sandbox/win/src/win_utils_unittest.cc b/sandbox/win/src/win_utils_unittest.cc
index a84db93..4469da4c 100644
--- a/sandbox/win/src/win_utils_unittest.cc
+++ b/sandbox/win/src/win_utils_unittest.cc
@@ -8,6 +8,7 @@
 
 #include <psapi.h>
 
+#include <algorithm>
 #include <vector>
 
 #include "base/files/file_path.h"
@@ -20,6 +21,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/scoped_process_information.h"
+#include "base/win/windows_version.h"
 #include "sandbox/win/src/nt_internals.h"
 #include "sandbox/win/tests/common/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -80,6 +82,35 @@
   EXPECT_TRUE(base::EqualsCaseInsensitiveASCII(type_name, expected_type));
 }
 
+void FindHandle(const ProcessHandleMap& handle_map,
+                const wchar_t* type_name,
+                const base::win::ScopedHandle& handle) {
+  ProcessHandleMap::const_iterator entry = handle_map.find(type_name);
+  ASSERT_NE(handle_map.end(), entry);
+  const std::vector<HANDLE>& handles = entry->second;
+  EXPECT_NE(handles.cend(),
+            std::find(handles.cbegin(), handles.cend(), handle.Get()));
+}
+
+void TestCurrentProcessHandles(absl::optional<ProcessHandleMap> (*func)()) {
+  std::wstring random_name = GetRandomName();
+  ASSERT_FALSE(random_name.empty());
+  base::win::ScopedHandle event_handle(
+      ::CreateEvent(nullptr, FALSE, FALSE, random_name.c_str()));
+  ASSERT_TRUE(event_handle.IsValid());
+  std::wstring pipe_name = L"\\\\.\\pipe\\" + random_name;
+  base::win::ScopedHandle pipe_handle(::CreateNamedPipe(
+      pipe_name.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE,
+      PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_USE_DEFAULT_WAIT, nullptr));
+  ASSERT_TRUE(pipe_handle.IsValid());
+
+  absl::optional<ProcessHandleMap> handle_map = func();
+  ASSERT_TRUE(handle_map);
+  EXPECT_LE(2U, handle_map->size());
+  FindHandle(*handle_map, L"Event", event_handle);
+  FindHandle(*handle_map, L"File", pipe_handle);
+}
+
 }  // namespace
 
 TEST(WinUtils, IsReparsePoint) {
@@ -296,4 +327,14 @@
   CompareHandleType(pipe_handle, L"File");
 }
 
+TEST(WinUtils, GetCurrentProcessHandles) {
+  if (base::win::GetVersion() < base::win::Version::WIN8) {
+    ASSERT_FALSE(GetCurrentProcessHandles());
+    EXPECT_EQ(DWORD{ERROR_INVALID_PARAMETER}, ::GetLastError());
+  } else {
+    TestCurrentProcessHandles(GetCurrentProcessHandles);
+  }
+  TestCurrentProcessHandles(GetCurrentProcessHandlesWin7);
+}
+
 }  // namespace sandbox
diff --git a/testing/scripts/test_traffic_annotation_auditor.py b/testing/scripts/test_traffic_annotation_auditor.py
index cae6b87..2d47de1 100755
--- a/testing/scripts/test_traffic_annotation_auditor.py
+++ b/testing/scripts/test_traffic_annotation_auditor.py
@@ -10,13 +10,14 @@
 
 import json
 import os
+import re
 import sys
 import tempfile
 
 
 import common
 
-SHEET_CONFIG = {
+WINDOWS_SHEET_CONFIG = {
   "spreadsheet_id": "1TmBr9jnf1-hrjntiVBzT9EtkINGrtoBYFMWad2MBeaY",
   "annotations_sheet_name": "Annotations",
   "changes_sheet_name": "Changes Stats",
@@ -25,63 +26,106 @@
 }
 
 
+CHROMEOS_SHEET_CONFIG = {
+  "spreadsheet_id": "1928goWKy6LVdF9Nl5nV1OD260YC10dHsdrnHEGdGsg8",
+  "annotations_sheet_name": "Annotations",
+  "changes_sheet_name": "Changes Stats",
+  "silent_change_columns": [],
+  "last_update_column_name": "Last Update",
+}
+
 def is_windows():
   return os.name == 'nt'
 
+def is_chromeos(build_path):
+  current_platform = get_current_platform_from_gn_args(build_path)
+  return current_platform == "chromeos"
+
+
+def get_sheet_config(build_path):
+  if is_windows():
+    return WINDOWS_SHEET_CONFIG
+  if is_chromeos(build_path):
+    return CHROMEOS_SHEET_CONFIG
+  return None
+
+
+def get_current_platform_from_gn_args(build_path):
+  if sys.platform.startswith("linux") and build_path is not None:
+    try:
+      with open(os.path.join(build_path, "args.gn")) as f:
+        gn_args = f.read()
+      if not gn_args:
+        logger.info("Could not retrieve args.gn")
+
+      pattern = re.compile(r"^\s*target_os\s*=\s*\"chromeos\"\s*$",
+                           re.MULTILINE)
+      if pattern.search(gn_args):
+        return "chromeos"
+
+    except(valueError, OSError) as e:
+      logger.info(e)
+
+  return None
 
 def main_run(args):
   annotations_file = tempfile.NamedTemporaryFile()
   annotations_filename = annotations_file.name
   annotations_file.close()
 
+  build_path = os.path.join(args.paths['checkout'], 'out', args.build_config_fs)
   command_line = [
       sys.executable,
       os.path.join(common.SRC_DIR, 'tools', 'traffic_annotation', 'scripts',
                    'traffic_annotation_auditor_tests.py'),
       '--build-path',
-      os.path.join(args.paths['checkout'], 'out', args.build_config_fs),
+      build_path,
       '--annotations-file',
       annotations_filename,
   ]
   rc = common.run_command(command_line)
 
-  # Update the Google Sheets on success, but only on the Windows trybot.
-  if rc == 0 and is_windows():
-    print("Tests succeeded. Updating annotations sheet...")
-
-    config_file = tempfile.NamedTemporaryFile(delete=False)
-    json.dump(SHEET_CONFIG, config_file, indent=4)
-    config_filename = config_file.name
-    config_file.close()
-
-    command_line = [
-      'vpython.bat',
-      os.path.join(common.SRC_DIR, 'tools', 'traffic_annotation', 'scripts',
-                   'update_annotations_sheet.py'),
-      '--force',
-      '--config-file',
-      config_filename,
-      '--annotations-file',
-      annotations_filename,
-    ]
-    rc = common.run_command(command_line)
-
-    try:
-      os.remove(config_filename)
-    except OSError:
-      pass
-
+  # Update the Google Sheets on success, but only on the Windows and ChromeOS
+  # trybot.
+  sheet_config = get_sheet_config(build_path)
   try:
-    os.remove(annotations_filename)
-  except OSError:
-    pass
+    if rc == 0 and sheet_config is not None:
+      print("Tests succeeded. Updating annotations sheet...")
 
-  failures = ['Please refer to stdout for errors.'] if rc else []
-  common.record_local_script_results(
-      'test_traffic_annotation_auditor', args.output, failures, True)
+      config_file = tempfile.NamedTemporaryFile(delete=False)
+      json.dump(sheet_config, config_file, indent=4)
+      config_filename = config_file.name
+      config_file.close()
+
+      command_line = [
+        'vpython.bat',
+        os.path.join(common.SRC_DIR, 'tools', 'traffic_annotation', 'scripts',
+                   'update_annotations_sheet.py'),
+        '--force',
+        '--config-file',
+        config_filename,
+        '--annotations-file',
+        annotations_filename,
+      ]
+      rc = common.run_command(command_line)
+    else:
+      print("Test failed without updating the annotations sheet.")
+  except (valueError, OSError) as e:
+    print("Error updating the annotations sheet", e)
+  finally:
+    cleanup_file(annotations_filename)
+    cleanup_file(config_filename)
+    failures = ['Please refer to stdout for errors.'] if rc else []
+    common.record_local_script_results(
+       'test_traffic_annotation_auditor', args.output, failures, True)
 
   return rc
 
+def cleanup_file(filename):
+  try:
+    os.remove(filename)
+  except OSError:
+    print("Could not remove file: ", filename)
 
 def main_compile_targets(args):
   json.dump(['traffic_annotation_proto'], args.output)
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index bc58700..f9d5f10e 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3658,25 +3658,6 @@
             ]
         }
     ],
-    "EnhancedProtectionPromoCard": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "params": {
-                        "MaxEnhancedProtectionPromoImpressions": "5",
-                        "MaxSigninPromoImpressions": "5"
-                    },
-                    "enable_features": [
-                        "EnhancedProtectionPromoCard"
-                    ]
-                }
-            ]
-        }
-    ],
     "ExperimentalAccessibilityLabels": [
         {
             "platforms": [
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
index 3007e96..3f35ca9 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
+++ b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
@@ -803,6 +803,7 @@
                 // chrome/android/java/proguard.flags instead.
                 sb.append('  ignore_proguard_configs = true\n')
                 break
+            case 'androidx_biometric_biometric':
             case 'com_google_android_gms_play_services_base':
                 sb.append('  bytecode_rewriter_target = "//build/android/bytecode:fragment_activity_replacer"\n')
                 break
diff --git a/third_party/blink/public/common/media/video_capture.h b/third_party/blink/public/common/media/video_capture.h
index a78c52b..a408b88 100644
--- a/third_party/blink/public/common/media/video_capture.h
+++ b/third_party/blink/public/common/media/video_capture.h
@@ -62,6 +62,7 @@
   VIDEO_CAPTURE_STATE_STOPPING,
   VIDEO_CAPTURE_STATE_STOPPED,
   VIDEO_CAPTURE_STATE_ERROR,
+  VIDEO_CAPTURE_STATE_ERROR_SYSTEM_PERMISSIONS_DENIED,
   VIDEO_CAPTURE_STATE_ENDED,
   VIDEO_CAPTURE_STATE_LAST = VIDEO_CAPTURE_STATE_ENDED
 };
diff --git a/third_party/blink/public/web/modules/mediastream/media_stream_video_source.h b/third_party/blink/public/web/modules/mediastream/media_stream_video_source.h
index a82f5757..0f87646 100644
--- a/third_party/blink/public/web/modules/mediastream/media_stream_video_source.h
+++ b/third_party/blink/public/web/modules/mediastream/media_stream_video_source.h
@@ -319,7 +319,7 @@
   // in the context of the callback. If gUM fails, the implementation will
   // simply drop the references to the blink source and track which will lead
   // to this object being deleted.
-  void FinalizeAddPendingTracks();
+  void FinalizeAddPendingTracks(mojom::MediaStreamRequestResult result);
 
   // Actually adds |track| to this source, provided the source has started.
   void FinalizeAddTrack(MediaStreamVideoTrack* track,
diff --git a/third_party/blink/renderer/core/animation/color_property_functions.cc b/third_party/blink/renderer/core/animation/color_property_functions.cc
index 432e4e14..8b5d6b8 100644
--- a/third_party/blink/renderer/core/animation/color_property_functions.cc
+++ b/third_party/blink/renderer/core/animation/color_property_functions.cc
@@ -158,6 +158,9 @@
     case CSSPropertyID::kTextDecorationColor:
       style.SetTextDecorationColor(style_color);
       return;
+    case CSSPropertyID::kTextEmphasisColor:
+      style.SetTextEmphasisColor(style_color);
+      return;
     case CSSPropertyID::kColumnRuleColor:
       style.SetColumnRuleColor(style_color);
       return;
@@ -214,6 +217,9 @@
     case CSSPropertyID::kTextDecorationColor:
       style.SetInternalVisitedTextDecorationColor(style_color);
       return;
+    case CSSPropertyID::kTextEmphasisColor:
+      style.SetInternalVisitedTextEmphasisColor(style_color);
+      return;
     case CSSPropertyID::kColumnRuleColor:
       style.SetInternalVisitedColumnRuleColor(style_color);
       return;
diff --git a/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc b/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc
index 247de328..4719ada 100644
--- a/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc
+++ b/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc
@@ -224,6 +224,7 @@
       case CSSPropertyID::kOutlineColor:
       case CSSPropertyID::kStopColor:
       case CSSPropertyID::kTextDecorationColor:
+      case CSSPropertyID::kTextEmphasisColor:
       case CSSPropertyID::kColumnRuleColor:
       case CSSPropertyID::kWebkitTextStrokeColor:
         applicable_types->push_back(
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index a5ef8d4..7f1a1db 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -4985,6 +4985,7 @@
     {
       name: "text-emphasis-color",
       property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal", "ColorIncludingFallback"],
+      interpolable: true,
       inherited: true,
       field_group: "*",
       field_template: "external",
@@ -4992,6 +4993,8 @@
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
       computed_style_custom_functions: ["getter"],
+      keywords: ["currentcolor"],
+      typedom_types: ["Keyword"],
       converter: "ConvertStyleColor",
       style_builder_template: "color",
       valid_for_marker: true,
@@ -5002,7 +5005,7 @@
       property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal"],
       inherited: true,
       field_group: "*",
-      field_size: 2,
+      field_size: 3,
       field_template: "primitive",
       default_value: "TextEmphasisPosition::kOverRight",
       type_name: "TextEmphasisPosition",
diff --git a/third_party/blink/renderer/core/css/css_property_equality.cc b/third_party/blink/renderer/core/css/css_property_equality.cc
index 0d0d448..b4749b7 100644
--- a/third_party/blink/renderer/core/css/css_property_equality.cc
+++ b/third_party/blink/renderer/core/css/css_property_equality.cc
@@ -389,6 +389,8 @@
       return a.MathDepth() == b.MathDepth();
     case CSSPropertyID::kAccentColor:
       return a.AccentColor() == b.AccentColor();
+    case CSSPropertyID::kTextEmphasisColor:
+      return a.TextEmphasisColor() == b.TextEmphasisColor();
     default:
       NOTREACHED();
       return true;
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index 72a7c9f..98becd7 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -8229,11 +8229,10 @@
   }
   if (!over_under)
     return nullptr;
-  if (!left_right)
-    left_right = CSSIdentifierValue::Create(CSSValueID::kRight);
   CSSValueList* list = CSSValueList::CreateSpaceSeparated();
   list->Append(*over_under);
-  list->Append(*left_right);
+  if (left_right)
+    list->Append(*left_right);
   return list;
 }
 
@@ -8243,6 +8242,9 @@
     bool allow_visited_style) const {
   CSSValueList* list = CSSValueList::CreateSpaceSeparated();
   switch (style.GetTextEmphasisPosition()) {
+    case blink::TextEmphasisPosition::kOver:
+      list->Append(*CSSIdentifierValue::Create(CSSValueID::kOver));
+      break;
     case blink::TextEmphasisPosition::kOverRight:
       list->Append(*CSSIdentifierValue::Create(CSSValueID::kOver));
       list->Append(*CSSIdentifierValue::Create(CSSValueID::kRight));
@@ -8251,6 +8253,9 @@
       list->Append(*CSSIdentifierValue::Create(CSSValueID::kOver));
       list->Append(*CSSIdentifierValue::Create(CSSValueID::kLeft));
       break;
+    case blink::TextEmphasisPosition::kUnder:
+      list->Append(*CSSIdentifierValue::Create(CSSValueID::kUnder));
+      break;
     case blink::TextEmphasisPosition::kUnderRight:
       list->Append(*CSSIdentifierValue::Create(CSSValueID::kUnder));
       list->Append(*CSSIdentifierValue::Create(CSSValueID::kRight));
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
index f7ff8aed..6f704a1 100644
--- a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
@@ -1731,6 +1731,13 @@
     const CSSValue& value) {
   const auto& list = To<CSSValueList>(value);
   CSSValueID first = To<CSSIdentifierValue>(list.Item(0)).GetValueID();
+  if (list.length() < 2) {
+    if (first == CSSValueID::kOver)
+      return TextEmphasisPosition::kOver;
+    if (first == CSSValueID::kUnder)
+      return TextEmphasisPosition::kUnder;
+    return TextEmphasisPosition::kOverRight;
+  }
   CSSValueID second = To<CSSIdentifierValue>(list.Item(1)).GetValueID();
   if (first == CSSValueID::kOver && second == CSSValueID::kRight)
     return TextEmphasisPosition::kOverRight;
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow.h b/third_party/blink/renderer/core/layout/layout_block_flow.h
index f134e673f4..ed30ea5 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow.h
+++ b/third_party/blink/renderer/core/layout/layout_block_flow.h
@@ -363,7 +363,10 @@
   // Implementation detail: At some point in the future there should be no flow
   // threads. Callers that only want to know if this is a fragmentation context
   // root (and don't depend on flow threads) should call this method.
-  bool IsFragmentationContextRoot() const { return MultiColumnFlowThread(); }
+  bool IsFragmentationContextRoot() const {
+    NOT_DESTROYED();
+    return MultiColumnFlowThread();
+  }
 
   void AddVisualOverflowFromInlineChildren();
 
diff --git a/third_party/blink/renderer/core/layout/layout_document_transition_content.h b/third_party/blink/renderer/core/layout/layout_document_transition_content.h
index 33dc27b..d4c42d9 100644
--- a/third_party/blink/renderer/core/layout/layout_document_transition_content.h
+++ b/third_party/blink/renderer/core/layout/layout_document_transition_content.h
@@ -19,6 +19,7 @@
   ~LayoutDocumentTransitionContent() override;
 
   const char* GetName() const override {
+    NOT_DESTROYED();
     return "LayoutDocumentTransitionContent";
   }
   void OnIntrinsicSizeUpdated(const LayoutSize& intrinsic_size);
diff --git a/third_party/blink/renderer/core/layout/layout_grid.h b/third_party/blink/renderer/core/layout/layout_grid.h
index 425ae33..c7da127 100644
--- a/third_party/blink/renderer/core/layout/layout_grid.h
+++ b/third_party/blink/renderer/core/layout/layout_grid.h
@@ -99,11 +99,13 @@
 
   wtf_size_t AutoRepeatCountForDirection(
       GridTrackSizingDirection direction) const final {
+    NOT_DESTROYED();
     return base::checked_cast<wtf_size_t>(grid_->AutoRepeatTracks(direction));
   }
 
   wtf_size_t ExplicitGridStartForDirection(
       GridTrackSizingDirection direction) const final {
+    NOT_DESTROYED();
     return base::checked_cast<wtf_size_t>(grid_->ExplicitGridStart(direction));
   }
 
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 32d201e4..9ca6575 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -317,10 +317,6 @@
 #endif
 #define NOT_DESTROYED() CheckIsNotDestroyed()
 
-#if DCHECK_IS_ON()
-  bool IsDestroyed() const { return is_destroyed_; }
-#endif
-
   // Returns the name of the layout object.
   virtual const char* GetName() const = 0;
 
@@ -1490,6 +1486,7 @@
     return bitfields_.IsAtomicInlineLevel();
   }
   bool IsBlockInInline() const {
+    NOT_DESTROYED();
     return IsAnonymous() && !IsInline() && !IsFloatingOrOutOfFlowPositioned() &&
            Parent() && Parent()->IsLayoutInline();
   }
@@ -3158,6 +3155,7 @@
   // Whether this object's Node has a blocking wheel event handler on itself or
   // an ancestor.
   bool InsideBlockingWheelEventHandler() const {
+    NOT_DESTROYED();
     return bitfields_.InsideBlockingWheelEventHandler();
   }
   // Mark this object as having a |InsideBlockingWheelEventHandler| changed, and
@@ -3166,12 +3164,15 @@
   void MarkBlockingWheelEventHandlerChanged();
   void MarkDescendantBlockingWheelEventHandlerChanged();
   bool BlockingWheelEventHandlerChanged() const {
+    NOT_DESTROYED();
     return bitfields_.BlockingWheelEventHandlerChanged();
   }
   bool DescendantBlockingWheelEventHandlerChanged() const {
+    NOT_DESTROYED();
     return bitfields_.DescendantBlockingWheelEventHandlerChanged();
   }
   void UpdateInsideBlockingWheelEventHandler(bool inside) {
+    NOT_DESTROYED();
     bitfields_.SetInsideBlockingWheelEventHandler(inside);
   }
 
@@ -3469,9 +3470,11 @@
   }
 
   bool IsLayoutNGObjectForCanvasFormattedText() const {
+    NOT_DESTROYED();
     return bitfields_.IsLayoutNGObjectForCanvasFormattedText();
   }
   void SetIsLayoutNGObjectForCanvasFormattedText(bool b) {
+    NOT_DESTROYED();
     bitfields_.SetIsLayoutNGObjectForCanvasFormattedText(b);
   }
 
@@ -3492,17 +3495,21 @@
   }
 
   bool ShouldSkipNextLayoutShiftTracking() const {
+    NOT_DESTROYED();
     return bitfields_.ShouldSkipNextLayoutShiftTracking();
   }
   void SetShouldSkipNextLayoutShiftTracking(bool b) {
+    NOT_DESTROYED();
     bitfields_.SetShouldSkipNextLayoutShiftTracking(b);
   }
 
   bool ShouldAssumePaintOffsetTranslationForLayoutShiftTracking() const {
+    NOT_DESTROYED();
     return bitfields_
         .ShouldAssumePaintOffsetTranslationForLayoutShiftTracking();
   }
   void SetShouldAssumePaintOffsetTranslationForLayoutShiftTracking(bool b) {
+    NOT_DESTROYED();
     bitfields_.SetShouldAssumePaintOffsetTranslationForLayoutShiftTracking(b);
   }
 
@@ -3742,6 +3749,7 @@
   }
 
   void SetMightTraversePhysicalFragments(bool b) {
+    NOT_DESTROYED();
     bitfields_.SetMightTraversePhysicalFragments(b);
   }
 
@@ -4243,7 +4251,6 @@
                          HasNonCollapsedBorderDecoration);
 
     // True at start of |Destroy()| before calling |WillBeDestroyed()|.
-    // TODO(yukiy): Remove this bitfield
     ADD_BOOLEAN_BITFIELD(being_destroyed_, BeingDestroyed);
 
     // From LayoutListMarkerImage
@@ -4425,6 +4432,7 @@
  private:
   friend class LineLayoutItem;
   friend class LocalFrameView;
+  friend class SubtreeLayoutScope;
 
   scoped_refptr<const ComputedStyle> style_;
 
diff --git a/third_party/blink/renderer/core/layout/layout_replaced.h b/third_party/blink/renderer/core/layout/layout_replaced.h
index 590253c..27f026f 100644
--- a/third_party/blink/renderer/core/layout/layout_replaced.h
+++ b/third_party/blink/renderer/core/layout/layout_replaced.h
@@ -81,7 +81,10 @@
     NOT_DESTROYED();
     return false;
   }
-  virtual bool DrawsBackgroundOntoContentLayer() const { return false; }
+  virtual bool DrawsBackgroundOntoContentLayer() const {
+    NOT_DESTROYED();
+    return false;
+  }
   virtual void PaintReplaced(const PaintInfo&,
                              const PhysicalOffset& paint_offset) const {
     NOT_DESTROYED();
diff --git a/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h b/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h
index f366e95..4e52568 100644
--- a/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h
+++ b/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h
@@ -25,10 +25,19 @@
 
   explicit LayoutNGCustom(Element*);
 
-  const char* GetName() const override { return "LayoutNGCustom"; }
-  bool CreatesNewFormattingContext() const override { return true; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGCustom";
+  }
+  bool CreatesNewFormattingContext() const override {
+    NOT_DESTROYED();
+    return true;
+  }
 
-  bool IsLoaded() { return state_ != kUnloaded; }
+  bool IsLoaded() {
+    NOT_DESTROYED();
+    return state_ != kUnloaded;
+  }
 
   void AddChild(LayoutObject* new_child, LayoutObject* before_child) override;
   void RemoveChild(LayoutObject* child) override;
@@ -37,11 +46,13 @@
 
   PaginationBreakability GetPaginationBreakability(
       FragmentationEngine) const final {
+    NOT_DESTROYED();
     return kForbidBreaks;
   }
 
  private:
   bool IsOfType(LayoutObjectType type) const override {
+    NOT_DESTROYED();
     return type == kLayoutObjectNGCustom || LayoutNGBlockFlow::IsOfType(type);
   }
 
diff --git a/third_party/blink/renderer/core/layout/ng/flex/layout_ng_flexible_box.h b/third_party/blink/renderer/core/layout/ng/flex/layout_ng_flexible_box.h
index 2ed02092..13d9bcf 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/layout_ng_flexible_box.h
+++ b/third_party/blink/renderer/core/layout/ng/flex/layout_ng_flexible_box.h
@@ -35,9 +35,18 @@
 
   void UpdateBlockLayout(bool relayout_children) override;
 
-  bool IsFlexibleBoxIncludingDeprecatedAndNG() const final { return true; }
-  bool IsFlexibleBoxIncludingNG() const final { return true; }
-  const char* GetName() const override { return "LayoutNGFlexibleBox"; }
+  bool IsFlexibleBoxIncludingDeprecatedAndNG() const final {
+    NOT_DESTROYED();
+    return true;
+  }
+  bool IsFlexibleBoxIncludingNG() const final {
+    NOT_DESTROYED();
+    return true;
+  }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGFlexibleBox";
+  }
 
   DevtoolsFlexInfo LayoutForDevtools();
 
@@ -47,6 +56,7 @@
   void RemoveChild(LayoutObject*) override;
 
   bool IsOfType(LayoutObjectType type) const override {
+    NOT_DESTROYED();
     return type == kLayoutObjectNGFlexibleBox ||
            LayoutNGMixin<LayoutBlock>::IsOfType(type);
   }
diff --git a/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.h b/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.h
index c230b19..10b78ec 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.h
+++ b/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.h
@@ -20,7 +20,10 @@
 
   void UpdateBlockLayout(bool relayout_children) override;
 
-  const char* GetName() const override { return "LayoutNGGrid"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGGrid";
+  }
 
   const LayoutNGGridInterface* ToLayoutNGGridInterface() const final;
 
@@ -46,6 +49,7 @@
 
  protected:
   bool IsOfType(LayoutObjectType type) const override {
+    NOT_DESTROYED();
     return type == kLayoutObjectNGGrid ||
            LayoutNGMixin<LayoutBlock>::IsOfType(type);
   }
diff --git a/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_combine.h b/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_combine.h
index 48a0dfe..094aa96 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_combine.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_combine.h
@@ -28,8 +28,14 @@
   String GetTextContent() const;
 
   // Compressed font
-  const Font& CompressedFont() const { return compressed_font_.value(); }
-  bool UsesCompressedFont() const { return compressed_font_.has_value(); }
+  const Font& CompressedFont() const {
+    NOT_DESTROYED();
+    return compressed_font_.value();
+  }
+  bool UsesCompressedFont() const {
+    NOT_DESTROYED();
+    return compressed_font_.has_value();
+  }
   void SetCompressedFont(const Font& font);
 
   // Scaling
@@ -62,7 +68,10 @@
 
   void ResetLayout();
   void SetScaleX(float new_scale_x);
-  bool UsesScaleX() const { return scale_x_.has_value(); }
+  bool UsesScaleX() const {
+    NOT_DESTROYED();
+    return scale_x_.has_value();
+  }
 
   // Painting
   // |AdjustText{Left,Top}()| are called within affine transformed
@@ -91,7 +100,10 @@
 
  private:
   bool IsOfType(LayoutObjectType) const override;
-  const char* GetName() const override { return "LayoutNGTextCombine"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGTextCombine";
+  }
 
   // Helper functions for scaling.
   PhysicalOffset ApplyScaleX(const PhysicalOffset& offset) const;
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h
index 18c3a21..aa4c6a3 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h
@@ -25,7 +25,10 @@
 
   void UpdateBlockLayout(bool relayout_children) override;
 
-  const char* GetName() const override { return "LayoutNGBlockFlow"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGBlockFlow";
+  }
 
  protected:
   bool IsOfType(LayoutObjectType) const override;
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_button.h b/third_party/blink/renderer/core/layout/ng/layout_ng_button.h
index a853861..6064ed3 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_button.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_button.h
@@ -15,18 +15,25 @@
   ~LayoutNGButton() override;
   void Trace(Visitor*) const override;
 
-  const char* GetName() const override { return "LayoutNGButton"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGButton";
+  }
   void AddChild(LayoutObject* new_child,
                 LayoutObject* before_child = nullptr) override;
   void RemoveChild(LayoutObject*) override;
-  void RemoveLeftoverAnonymousBlock(LayoutBlock*) override {}
-  bool CreatesAnonymousWrapper() const override { return true; }
+  void RemoveLeftoverAnonymousBlock(LayoutBlock*) override { NOT_DESTROYED(); }
+  bool CreatesAnonymousWrapper() const override {
+    NOT_DESTROYED();
+    return true;
+  }
 
  private:
   void UpdateAnonymousChildStyle(const LayoutObject* child,
                                  ComputedStyle& child_style) const override;
 
   bool IsOfType(LayoutObjectType type) const override {
+    NOT_DESTROYED();
     return type == kLayoutObjectNGButton || LayoutNGFlexibleBox::IsOfType(type);
   }
 
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h b/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h
index 2e18729..ac872ba0 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h
@@ -14,12 +14,18 @@
  public:
   explicit LayoutNGFieldset(Element*);
 
-  const char* GetName() const override { return "LayoutNGFieldset"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGFieldset";
+  }
 
   void AddChild(LayoutObject* new_child,
                 LayoutObject* before_child = nullptr) override;
 
-  bool CreatesNewFormattingContext() const final { return true; }
+  bool CreatesNewFormattingContext() const final {
+    NOT_DESTROYED();
+    return true;
+  }
 
   LayoutBlock* FindAnonymousFieldsetContentBox() const;
 
@@ -34,7 +40,10 @@
                        const PhysicalOffset& accumulated_offset,
                        HitTestAction hit_test_action) override;
 
-  bool AllowsNonVisibleOverflow() const override { return false; }
+  bool AllowsNonVisibleOverflow() const override {
+    NOT_DESTROYED();
+    return false;
+  }
   // Override to forward to the anonymous fieldset content box.
   LayoutUnit ScrollWidth() const override;
   LayoutUnit ScrollHeight() const override;
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h b/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h
index 39d2967..6bcfc62a 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h
@@ -23,7 +23,10 @@
 
   void UpdateBlockLayout(bool relayout_children) override;
 
-  const char* GetName() const override { return "LayoutNGProgress"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGProgress";
+  }
 
  protected:
   bool IsOfType(LayoutObjectType type) const override;
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_as_block.h b/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_as_block.h
index 1797f6d..d319d6a 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_as_block.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_as_block.h
@@ -24,7 +24,10 @@
   explicit LayoutNGRubyAsBlock(Element*);
   ~LayoutNGRubyAsBlock() override;
 
-  const char* GetName() const override { return "LayoutNGRubyAsBlock"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGRubyAsBlock";
+  }
   void UpdateBlockLayout(bool relayout_children) override;
 };
 
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_base.h b/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_base.h
index 41e2f4e..e5b174f 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_base.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_base.h
@@ -22,7 +22,10 @@
   explicit LayoutNGRubyBase();
   ~LayoutNGRubyBase() override;
 
-  const char* GetName() const override { return "LayoutNGRubyBase"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGRubyBase";
+  }
   void UpdateBlockLayout(bool relayout_children) override;
 };
 
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_run.h b/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_run.h
index 5dcb636..9b6052b 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_run.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_run.h
@@ -22,7 +22,10 @@
   explicit LayoutNGRubyRun();
   ~LayoutNGRubyRun() override;
 
-  const char* GetName() const override { return "LayoutNGRubyRun"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGRubyRun";
+  }
   void UpdateBlockLayout(bool relayout_children) override;
 };
 
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_text.h b/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_text.h
index a51b923..e0b7a31 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_text.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_ruby_text.h
@@ -22,7 +22,10 @@
   explicit LayoutNGRubyText(Element* element);
   ~LayoutNGRubyText() override;
 
-  const char* GetName() const override { return "LayoutNGRubyText"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGRubyText";
+  }
   void UpdateBlockLayout(bool relayout_children) override;
 };
 
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_view.h b/third_party/blink/renderer/core/layout/ng/layout_ng_view.h
index b21ee63d..068db64 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_view.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_view.h
@@ -22,7 +22,10 @@
 
   void UpdateBlockLayout(bool relayout_children) override;
 
-  const char* GetName() const override { return "LayoutNGView"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGView";
+  }
 
  protected:
   bool IsOfType(LayoutObjectType) const override;
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h
index ebef26d1..e6231a1 100644
--- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h
+++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h
@@ -16,11 +16,15 @@
  public:
   explicit LayoutNGListItem(Element*);
 
-  ListItemOrdinal& Ordinal() { return ordinal_; }
+  ListItemOrdinal& Ordinal() {
+    NOT_DESTROYED();
+    return ordinal_;
+  }
 
   int Value() const;
 
   LayoutObject* Marker() const {
+    NOT_DESTROYED();
     Element* list_item = To<Element>(GetNode());
     return list_item->PseudoElementLayoutObject(kPseudoIdMarker);
   }
@@ -33,7 +37,10 @@
 
   static const LayoutObject* FindSymbolMarkerLayoutText(const LayoutObject*);
 
-  const char* GetName() const override { return "LayoutNGListItem"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGListItem";
+  }
 
  private:
   bool IsOfType(LayoutObjectType) const override;
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h b/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h
index 3c49cad..a0165a8 100644
--- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h
+++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h
@@ -25,12 +25,21 @@
 
   void WillCollectInlines() override;
 
-  const char* GetName() const override { return "LayoutNGOutsideListMarker"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGOutsideListMarker";
+  }
 
   bool NeedsOccupyWholeLine() const;
 
-  const ListMarker& Marker() const { return list_marker_; }
-  ListMarker& Marker() { return list_marker_; }
+  const ListMarker& Marker() const {
+    NOT_DESTROYED();
+    return list_marker_;
+  }
+  ListMarker& Marker() {
+    NOT_DESTROYED();
+    return list_marker_;
+  }
 
   PaginationBreakability GetPaginationBreakability(
       FragmentationEngine engine) const final;
diff --git a/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.h b/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.h
index d2cde00..49cbc878 100644
--- a/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.h
+++ b/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.h
@@ -13,7 +13,10 @@
  public:
   explicit LayoutNGMathMLBlock(Element*);
 
-  const char* GetName() const override { return "LayoutNGMathMLBlock"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGMathMLBlock";
+  }
 
  private:
   void UpdateBlockLayout(bool relayout_children) final;
@@ -25,6 +28,7 @@
 
   PaginationBreakability GetPaginationBreakability(
       FragmentationEngine) const final {
+    NOT_DESTROYED();
     return kForbidBreaks;
   }
 };
diff --git a/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block_flow.h b/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block_flow.h
index a16ddc3..e8e6893 100644
--- a/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block_flow.h
+++ b/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block_flow.h
@@ -15,17 +15,25 @@
  public:
   explicit LayoutNGMathMLBlockFlow(Element*);
 
-  const char* GetName() const final { return "LayoutNGMathMLBlockFlow"; }
+  const char* GetName() const final {
+    NOT_DESTROYED();
+    return "LayoutNGMathMLBlockFlow";
+  }
 
  private:
   bool IsOfType(LayoutObjectType) const final;
   bool IsChildAllowed(LayoutObject*, const ComputedStyle&) const final {
+    NOT_DESTROYED();
     return true;
   }
-  bool CreatesNewFormattingContext() const final { return true; }
+  bool CreatesNewFormattingContext() const final {
+    NOT_DESTROYED();
+    return true;
+  }
 
   PaginationBreakability GetPaginationBreakability(
       FragmentationEngine) const final {
+    NOT_DESTROYED();
     return kForbidBreaks;
   }
 };
diff --git a/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h b/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h
index 8d20fed..d56ceeb 100644
--- a/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h
+++ b/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h
@@ -178,18 +178,20 @@
       unsigned absolute_column_index) const final;
 
   // NG does not need this method. Sections are not cached.
-  void RecalcSectionsIfNeeded() const final {}
+  void RecalcSectionsIfNeeded() const final { NOT_DESTROYED(); }
 
   // Not used by NG. Legacy caches sections.
   void ForceSectionsRecalc() final { NOT_DESTROYED(); }
 
   // Used in paint for printing. Should not be needed by NG.
   LayoutUnit RowOffsetFromRepeatingFooter() const final {
+    NOT_DESTROYED();
     NOTIMPLEMENTED();  // OK, never used.
     return LayoutUnit();
   }
   // Used in paint for printing. Should not be needed by NG.
   LayoutUnit RowOffsetFromRepeatingHeader() const final {
+    NOT_DESTROYED();
     NOTIMPLEMENTED();  // OK, never used.
     return LayoutUnit();
   }
@@ -206,16 +208,19 @@
 
   // Following methods are called during printing, not in TablesNG.
   LayoutNGTableSectionInterface* TopNonEmptySectionInterface() const final {
+    NOT_DESTROYED();
     NOTREACHED();
     return nullptr;
   }
 
   LayoutNGTableSectionInterface* BottomSectionInterface() const final {
+    NOT_DESTROYED();
     NOTREACHED();
     return nullptr;
   }
 
   LayoutNGTableSectionInterface* BottomNonEmptySectionInterface() const final {
+    NOT_DESTROYED();
     NOTREACHED();
     return nullptr;
   }
diff --git a/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_caption.h b/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_caption.h
index f62eaa2..5857216 100644
--- a/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_caption.h
+++ b/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_caption.h
@@ -26,7 +26,10 @@
 
   void UpdateBlockLayout(bool relayout_children) override;
 
-  const char* GetName() const override { return "LayoutNGTableCaption"; }
+  const char* GetName() const override {
+    NOT_DESTROYED();
+    return "LayoutNGTableCaption";
+  }
 
  private:
   // Legacy-only API.
diff --git a/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h b/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h
index 5c567cf6..9d62944 100644
--- a/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h
+++ b/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h
@@ -30,7 +30,10 @@
 
   // LayoutBlock methods start.
 
-  void UpdateBlockLayout(bool relayout_children) override { NOTREACHED(); }
+  void UpdateBlockLayout(bool relayout_children) override {
+    NOT_DESTROYED();
+    NOTREACHED();
+  }
 
   const char* GetName() const override {
     NOT_DESTROYED();
diff --git a/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h b/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h
index ceb2b58..73d97141 100644
--- a/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h
+++ b/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h
@@ -26,7 +26,10 @@
 
   // LayoutBlock methods start.
 
-  void UpdateBlockLayout(bool relayout_children) override { NOTREACHED(); }
+  void UpdateBlockLayout(bool relayout_children) override {
+    NOT_DESTROYED();
+    NOTREACHED();
+  }
 
   const char* GetName() const override {
     NOT_DESTROYED();
diff --git a/third_party/blink/renderer/core/layout/subtree_layout_scope.cc b/third_party/blink/renderer/core/layout/subtree_layout_scope.cc
index 8af6504e..39d2695 100644
--- a/third_party/blink/renderer/core/layout/subtree_layout_scope.cc
+++ b/third_party/blink/renderer/core/layout/subtree_layout_scope.cc
@@ -51,7 +51,7 @@
     // have been marked for layout. Skip such LayoutObject to avoid that
     // NOT_DESTROYED() triggers a DCHECK failure in AssertLaidOut() or
     // AssertFragmentTree().
-    if (layout_object->IsDestroyed()) {
+    if (layout_object->is_destroyed_) {
       DCHECK(RuntimeEnabledFeatures::CSSContainerQueriesEnabled());
       continue;
     }
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_container.h b/third_party/blink/renderer/core/layout/svg/layout_svg_container.h
index 4d800dc0..2e61743 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_container.h
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_container.h
@@ -89,7 +89,10 @@
     NOT_DESTROYED();
     return &content_.Children();
   }
-  SVGContentContainer& Content() { return content_; }
+  SVGContentContainer& Content() {
+    NOT_DESTROYED();
+    return content_;
+  }
 
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
diff --git a/third_party/blink/renderer/core/paint/text_painter_base.cc b/third_party/blink/renderer/core/paint/text_painter_base.cc
index c24cb3c..9f4c827 100644
--- a/third_party/blink/renderer/core/paint/text_painter_base.cc
+++ b/third_party/blink/renderer/core/paint/text_painter_base.cc
@@ -54,17 +54,12 @@
 
   if (!font_data || emphasis_mark.IsNull()) {
     emphasis_mark_offset_ = 0;
-  } else if ((horizontal_ && (position == TextEmphasisPosition::kOverRight ||
-                              position == TextEmphasisPosition::kOverLeft)) ||
-             (!horizontal_ &&
-              (position == TextEmphasisPosition::kOverRight ||
-               position == TextEmphasisPosition::kUnderRight))) {
+  } else if ((horizontal_ && IsOver(position)) ||
+             (!horizontal_ && IsRight(position))) {
     emphasis_mark_offset_ = -font_data->GetFontMetrics().Ascent() -
                             font_.EmphasisMarkDescent(emphasis_mark);
   } else {
-    DCHECK(position == TextEmphasisPosition::kUnderRight ||
-           position == TextEmphasisPosition::kUnderLeft ||
-           position == TextEmphasisPosition::kOverLeft);
+    DCHECK(!IsOver(position) || position == TextEmphasisPosition::kOverLeft);
     emphasis_mark_offset_ = font_data->GetFontMetrics().Descent() +
                             font_.EmphasisMarkAscent(emphasis_mark);
   }
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index 4a66636d..99b9dbf 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -1730,22 +1730,11 @@
 
 LineLogicalSide ComputedStyle::GetTextEmphasisLineLogicalSide() const {
   TextEmphasisPosition position = GetTextEmphasisPosition();
-  if (IsHorizontalWritingMode()) {
-    return position == TextEmphasisPosition::kOverRight ||
-                   position == TextEmphasisPosition::kOverLeft
-               ? LineLogicalSide::kOver
-               : LineLogicalSide::kUnder;
-  }
-  if (GetWritingMode() != WritingMode::kSidewaysLr) {
-    return position == TextEmphasisPosition::kOverRight ||
-                   position == TextEmphasisPosition::kUnderRight
-               ? LineLogicalSide::kOver
-               : LineLogicalSide::kUnder;
-  }
-  return position == TextEmphasisPosition::kOverLeft ||
-                 position == TextEmphasisPosition::kUnderLeft
-             ? LineLogicalSide::kOver
-             : LineLogicalSide::kUnder;
+  if (IsHorizontalWritingMode())
+    return IsOver(position) ? LineLogicalSide::kOver : LineLogicalSide::kUnder;
+  if (GetWritingMode() != WritingMode::kSidewaysLr)
+    return IsRight(position) ? LineLogicalSide::kOver : LineLogicalSide::kUnder;
+  return IsLeft(position) ? LineLogicalSide::kOver : LineLogicalSide::kUnder;
 }
 
 CSSAnimationData& ComputedStyle::AccessAnimations() {
diff --git a/third_party/blink/renderer/core/style/computed_style_constants.h b/third_party/blink/renderer/core/style/computed_style_constants.h
index 8f4fa0a..9d324c12 100644
--- a/third_party/blink/renderer/core/style/computed_style_constants.h
+++ b/third_party/blink/renderer/core/style/computed_style_constants.h
@@ -314,12 +314,31 @@
 };
 
 enum class TextEmphasisPosition : unsigned {
+  kOver,  // Same as kOverRight
   kOverRight,
   kOverLeft,
+  kUnder,  // Same as kUnderRight
   kUnderRight,
   kUnderLeft,
 };
 
+inline bool IsOver(TextEmphasisPosition position) {
+  return position == TextEmphasisPosition::kOver ||
+         position == TextEmphasisPosition::kOverRight ||
+         position == TextEmphasisPosition::kOverLeft;
+}
+
+inline bool IsRight(TextEmphasisPosition position) {
+  return position == TextEmphasisPosition::kOver ||
+         position == TextEmphasisPosition::kOverRight ||
+         position == TextEmphasisPosition::kUnder ||
+         position == TextEmphasisPosition::kUnderRight;
+}
+
+inline bool IsLeft(TextEmphasisPosition position) {
+  return !IsRight(position);
+}
+
 enum class LineLogicalSide {
   kOver,
   kUnder,
diff --git a/third_party/blink/renderer/modules/mediacapturefromelement/canvas_capture_handler.cc b/third_party/blink/renderer/modules/mediacapturefromelement/canvas_capture_handler.cc
index 8b7a301..70c92ad 100644
--- a/third_party/blink/renderer/modules/mediacapturefromelement/canvas_capture_handler.cc
+++ b/third_party/blink/renderer/modules/mediacapturefromelement/canvas_capture_handler.cc
@@ -215,7 +215,7 @@
       std::make_unique<CanvasCaptureHandlerDelegate>(new_frame_callback);
   DCHECK(delegate_);
   ask_for_new_frame_ = true;
-  running_callback.Run(true);
+  running_callback.Run(RunState::kRunning);
 }
 
 void CanvasCaptureHandler::RequestRefreshFrame() {
diff --git a/third_party/blink/renderer/modules/mediacapturefromelement/canvas_capture_handler_unittest.cc b/third_party/blink/renderer/modules/mediacapturefromelement/canvas_capture_handler_unittest.cc
index adf53c7..fc28f85 100644
--- a/third_party/blink/renderer/modules/mediacapturefromelement/canvas_capture_handler_unittest.cc
+++ b/third_party/blink/renderer/modules/mediacapturefromelement/canvas_capture_handler_unittest.cc
@@ -18,6 +18,7 @@
 #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_source.h"
 #include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
+#include "third_party/blink/renderer/platform/video_capture/video_capturer_source.h"
 #include "third_party/skia/include/core/SkImage.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 #include "ui/gfx/geometry/size.h"
@@ -85,7 +86,10 @@
   }
 
   MOCK_METHOD1(DoOnRunning, void(bool));
-  void OnRunning(bool state) { DoOnRunning(state); }
+  void OnRunning(blink::RunState run_state) {
+    bool state = (run_state == blink::RunState::kRunning) ? true : false;
+    DoOnRunning(state);
+  }
 
   // Verify returned frames.
   static scoped_refptr<StaticBitmapImage> GenerateTestImage(bool opaque,
diff --git a/third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source.cc b/third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source.cc
index 206dc99c..3b6bb2d 100644
--- a/third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source.cc
+++ b/third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source.cc
@@ -80,7 +80,7 @@
 
   running_callback_ = running_callback;
   if (!web_media_player_ || !web_media_player_->HasVideo()) {
-    running_callback_.Run(false);
+    running_callback_.Run(RunState::kStopped);
     return;
   }
 
@@ -91,7 +91,7 @@
                std::min(static_cast<float>(media::limits::kMaxFramesPerSecond),
                         params.requested_format.frame_rate));
 
-  running_callback_.Run(true);
+  running_callback_.Run(RunState::kRunning);
   task_runner_->PostTask(
       FROM_HERE, WTF::Bind(&HtmlVideoElementCapturerSource::sendNewFrame,
                            weak_factory_.GetWeakPtr()));
diff --git a/third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source_unittest.cc b/third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source_unittest.cc
index 9ff809e..a3f9a0b 100644
--- a/third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source_unittest.cc
+++ b/third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source_unittest.cc
@@ -140,7 +140,10 @@
   }
 
   MOCK_METHOD1(DoOnRunning, void(bool));
-  void OnRunning(bool state) { DoOnRunning(state); }
+  void OnRunning(blink::RunState run_state) {
+    bool state = (run_state == blink::RunState::kRunning) ? true : false;
+    DoOnRunning(state);
+  }
 
   void SetVideoPlayerOpacity(bool opacity) {
     web_media_player_->is_video_opaque_ = opacity;
@@ -159,6 +162,18 @@
 // and its inner object(s). This is a non trivial sequence.
 TEST_F(HTMLVideoElementCapturerSourceTest, ConstructAndDestruct) {}
 
+TEST_F(HTMLVideoElementCapturerSourceTest, EmptyWebMediaPlayerFailsCapture) {
+  web_media_player_.reset();
+  EXPECT_CALL(*this, DoOnRunning(false)).Times(1);
+
+  html_video_capturer_->StartCapture(
+      media::VideoCaptureParams(),
+      WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnDeliverFrame,
+                         base::Unretained(this)),
+      WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnRunning,
+                         base::Unretained(this)));
+}
+
 // Checks that the usual sequence of GetPreferredFormats() ->
 // StartCapture() -> StopCapture() works as expected and let it capture two
 // frames, that are tested for format vs the expected source opacity.
diff --git a/third_party/blink/renderer/modules/mediastream/local_video_capturer_source.cc b/third_party/blink/renderer/modules/mediastream/local_video_capturer_source.cc
index db6b4fe4..987c5782 100644
--- a/third_party/blink/renderer/modules/mediastream/local_video_capturer_source.cc
+++ b/third_party/blink/renderer/modules/mediastream/local_video_capturer_source.cc
@@ -104,18 +104,24 @@
     OnLog("LocalVideoCapturerSource::OnStateUpdate discarding state update.");
     return;
   }
+  RunState run_state =
+      (state == VIDEO_CAPTURE_STATE_ERROR_SYSTEM_PERMISSIONS_DENIED)
+          ? RunState::kSystemPermissionsError
+          : RunState::kStopped;
+
   auto* frame = LocalFrame::FromFrameToken(frame_token_);
   switch (state) {
     case VIDEO_CAPTURE_STATE_STARTED:
       OnLog(
           "LocalVideoCapturerSource::OnStateUpdate signaling to "
           "consumer that source is now running.");
-      running_callback_.Run(true);
+      running_callback_.Run(RunState::kRunning);
       break;
 
     case VIDEO_CAPTURE_STATE_STOPPING:
     case VIDEO_CAPTURE_STATE_STOPPED:
     case VIDEO_CAPTURE_STATE_ERROR:
+    case VIDEO_CAPTURE_STATE_ERROR_SYSTEM_PERMISSIONS_DENIED:
     case VIDEO_CAPTURE_STATE_ENDED:
       std::move(release_device_cb_).Run();
       release_device_cb_ =
@@ -126,7 +132,7 @@
       OnLog(
           "LocalVideoCapturerSource::OnStateUpdate signaling to "
           "consumer that source is no longer running.");
-      running_callback_.Run(false);
+      running_callback_.Run(run_state);
       break;
 
     case VIDEO_CAPTURE_STATE_STARTING:
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.cc
index 3ff92d3..3ffa30f1 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.cc
@@ -146,7 +146,7 @@
   // Force state update for nondevice sources, since they do not
   // automatically update state after StopCapture().
   if (device().type == mojom::blink::MediaStreamType::NO_SERVICE)
-    OnRunStateChanged(capture_params_, false);
+    OnRunStateChanged(capture_params_, RunState::kStopped);
 }
 
 void MediaStreamVideoCapturerSource::RestartSourceImpl(
@@ -215,8 +215,9 @@
 
 void MediaStreamVideoCapturerSource::OnRunStateChanged(
     const media::VideoCaptureParams& new_capture_params,
-    bool is_running) {
+    RunState run_state) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  bool is_running = (run_state == RunState::kRunning);
   switch (state_) {
     case kStarting:
       source_->OnLog("MediaStreamVideoCapturerSource sending OnStartDone");
@@ -226,8 +227,12 @@
         OnStartDone(mojom::blink::MediaStreamRequestResult::OK);
       } else {
         state_ = kStopped;
-        OnStartDone(
-            mojom::blink::MediaStreamRequestResult::TRACK_START_FAILURE_VIDEO);
+        auto result = (run_state == RunState::kSystemPermissionsError)
+                          ? mojom::blink::MediaStreamRequestResult::
+                                SYSTEM_PERMISSION_DENIED
+                          : mojom::blink::MediaStreamRequestResult::
+                                TRACK_START_FAILURE_VIDEO;
+        OnStartDone(result);
       }
       break;
     case kStarted:
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.h b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.h
index 5253ec9..a1d50c0 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.h
@@ -21,6 +21,7 @@
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-blink.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_source.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/platform/video_capture/video_capturer_source.h"
 
 namespace blink {
 
@@ -96,7 +97,7 @@
 
   // Method to bind as RunningCallback in VideoCapturerSource::StartCapture().
   void OnRunStateChanged(const media::VideoCaptureParams& new_capture_params,
-                         bool is_running);
+                         RunState run_state);
 
   mojom::blink::MediaStreamDispatcherHost* GetMediaStreamDispatcherHost();
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
index fb88c51..136c2124 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
@@ -56,9 +56,9 @@
   }
   void StopCapture() override { MockStopCapture(); }
   void SetRunning(bool is_running) {
+    RunState run_state = is_running ? RunState::kRunning : RunState::kStopped;
     PostCrossThreadTask(*scheduler::GetSingleThreadTaskRunnerForTesting(),
-                        FROM_HERE,
-                        CrossThreadBindOnce(running_cb_, is_running));
+                        FROM_HERE, CrossThreadBindOnce(running_cb_, run_state));
   }
   const media::VideoCaptureParams& capture_params() const {
     return capture_params_;
@@ -163,8 +163,9 @@
     EXPECT_EQ(String(source.Id()), stream_source_id_);
   }
   void OnStarted(bool result) {
+    RunState run_state = result ? RunState::kRunning : RunState::kStopped;
     video_capturer_source_->OnRunStateChanged(delegate_->capture_params(),
-                                              result);
+                                              run_state);
   }
 
   void SetStopCaptureFlag() { stop_capture_flag_ = true; }
@@ -189,8 +190,8 @@
   Persistent<MediaStreamSource> stream_source_;
   MockMojoMediaStreamDispatcherHost mock_dispatcher_host_;
   MediaStreamVideoCapturerSource*
-      video_capturer_source_;               // owned by |stream_source_|.
-  MockVideoCapturerSource* delegate_;       // owned by |source_|.
+      video_capturer_source_;          // owned by |stream_source_|.
+  MockVideoCapturerSource* delegate_;  // owned by |source_|.
   String stream_source_id_;
   bool source_stopped_;
   bool stop_capture_flag_ = false;
@@ -238,7 +239,7 @@
   EXPECT_CALL(mock_delegate(), MockStopCapture());
   WebMediaStreamTrack track =
       StartSource(VideoTrackAdapterSettings(), absl::nullopt, false, 0.0);
-  running_cb.Run(true);
+  running_cb.Run(RunState::kRunning);
 
   base::RunLoop run_loop;
   base::TimeTicks reference_capture_time =
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_source.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_source.cc
index e8c7437a..370c224 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_source.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_source.cc
@@ -104,9 +104,13 @@
       // and OnRestartDone().
       break;
     }
-    case ENDED:
+    case ENDED: {
+      FinalizeAddPendingTracks(
+          mojom::blink::MediaStreamRequestResult::TRACK_START_FAILURE_VIDEO);
+      break;
+    }
     case STARTED: {
-      FinalizeAddPendingTracks();
+      FinalizeAddPendingTracks(mojom::blink::MediaStreamRequestResult::OK);
       break;
     }
   }
@@ -259,7 +263,7 @@
   } else {
     state_ = STARTED;
     StartFrameMonitoring();
-    FinalizeAddPendingTracks();
+    FinalizeAddPendingTracks(mojom::blink::MediaStreamRequestResult::OK);
   }
   DCHECK(restart_callback_);
 
@@ -299,7 +303,7 @@
   if (did_restart) {
     state_ = STARTED;
     StartFrameMonitoring();
-    FinalizeAddPendingTracks();
+    FinalizeAddPendingTracks(mojom::blink::MediaStreamRequestResult::OK);
   } else {
     state_ = STOPPED_FOR_RESTART;
   }
@@ -427,20 +431,15 @@
 
   // This object can be deleted after calling FinalizeAddPendingTracks. See
   // comment in the header file.
-  FinalizeAddPendingTracks();
+  FinalizeAddPendingTracks(result);
 }
 
-void MediaStreamVideoSource::FinalizeAddPendingTracks() {
+void MediaStreamVideoSource::FinalizeAddPendingTracks(
+    mojom::blink::MediaStreamRequestResult result) {
   DCHECK(GetTaskRunner()->BelongsToCurrentThread());
   Vector<PendingTrackInfo> pending_track_descriptors;
   pending_track_descriptors.swap(pending_tracks_);
   for (auto& track_info : pending_track_descriptors) {
-    auto result = mojom::blink::MediaStreamRequestResult::OK;
-    if (state_ != STARTED) {
-      result =
-          mojom::blink::MediaStreamRequestResult::TRACK_START_FAILURE_VIDEO;
-    }
-
     if (result == mojom::blink::MediaStreamRequestResult::OK) {
       GetTrackAdapter()->AddTrack(
           track_info.track, track_info.frame_callback,
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc b/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc
index 48d4157..e45470a 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc
@@ -182,7 +182,7 @@
       WrapCrossThreadPersistent(active_config_.Get()), reset_count_));
 
   auto done_callback = [](AudioEncoder* self, media::AudioCodec codec,
-                          Request* req, media::Status status) {
+                          Request* req, media::EncoderStatus status) {
     if (!self || self->reset_count_ != req->reset_count) {
       req->EndTracing(/*aborted=*/true);
       return;
@@ -190,7 +190,7 @@
     DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
     if (!status.is_ok()) {
       self->HandleError(
-          self->logger_->MakeException("Encoding error.", status));
+          self->logger_->MakeException("Encoding error.", std::move(status)));
     } else {
       base::UmaHistogramEnumeration("Blink.WebCodecs.AudioEncoder.Codec",
                                     codec);
@@ -227,7 +227,7 @@
   DCHECK(data);
 
   auto done_callback = [](AudioEncoder* self, Request* req,
-                          media::Status status) {
+                          media::EncoderStatus status) {
     if (!self || self->reset_count_ != req->reset_count) {
       req->EndTracing(/*aborted=*/true);
       return;
@@ -235,7 +235,7 @@
     DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
     if (!status.is_ok()) {
       self->HandleError(
-          self->logger_->MakeException("Encoding error.", status));
+          self->logger_->MakeException("Encoding error.", std::move(status)));
     }
 
     req->EndTracing();
@@ -244,12 +244,11 @@
 
   if (data->channel_count() != active_config_->options.channels ||
       data->sample_rate() != active_config_->options.sample_rate) {
-    media::Status error(media::StatusCode::kEncoderFailedEncode);
-    error.WithData("channels", data->channel_count());
-    error.WithData("sampleRate", data->sample_rate());
-
     HandleError(logger_->MakeException(
-        "Input audio buffer is incompatible with codec parameters", error));
+        "Input audio buffer is incompatible with codec parameters",
+        media::EncoderStatus(media::EncoderStatus::Codes::kEncoderFailedEncode)
+            .WithData("channels", data->channel_count())
+            .WithData("sampleRate", data->sample_rate())));
 
     request->EndTracing();
 
diff --git a/third_party/blink/renderer/modules/webcodecs/encoder_base.cc b/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
index 7b2268d..faed4d8 100644
--- a/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
+++ b/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
@@ -64,8 +64,8 @@
   auto* context = ExecutionContext::From(script_state);
   callback_runner_ = context->GetTaskRunner(TaskType::kInternalMediaRealTime);
 
-  logger_ = std::make_unique<CodecLogger<media::Status>>(GetExecutionContext(),
-                                                         callback_runner_);
+  logger_ = std::make_unique<CodecLogger<media::EncoderStatus>>(
+      GetExecutionContext(), callback_runner_);
 
   media::MediaLog* log = logger_->log();
   logger_->SendPlayerNameInformation(*context, Traits::GetName());
@@ -308,7 +308,7 @@
   DCHECK_EQ(request->type, Request::Type::kFlush);
 
   auto done_callback = [](EncoderBase<Traits>* self, Request* req,
-                          media::Status status) {
+                          media::EncoderStatus status) {
     DCHECK(req);
     DCHECK(req->resolver);
 
@@ -328,7 +328,7 @@
       req->resolver.Release()->Resolve();
     } else {
       self->HandleError(
-          self->logger_->MakeException("Flushing error.", status));
+          self->logger_->MakeException("Flushing error.", std::move(status)));
       req->resolver.Release()->Reject();
     }
     req->EndTracing();
diff --git a/third_party/blink/renderer/modules/webcodecs/encoder_base.h b/third_party/blink/renderer/modules/webcodecs/encoder_base.h
index e351422..9c1cd31 100644
--- a/third_party/blink/renderer/modules/webcodecs/encoder_base.h
+++ b/third_party/blink/renderer/modules/webcodecs/encoder_base.h
@@ -7,8 +7,8 @@
 
 #include <memory>
 
+#include "media/base/encoder_status.h"
 #include "media/base/media_log.h"
-#include "media/base/status.h"
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_codec_state.h"
@@ -137,7 +137,7 @@
 
   void TraceQueueSizes() const;
 
-  std::unique_ptr<CodecLogger<media::Status>> logger_;
+  std::unique_ptr<CodecLogger<media::EncoderStatus>> logger_;
 
   std::unique_ptr<MediaEncoderType> media_encoder_;
 
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
index 31f31babb..cc09d0a 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
@@ -84,8 +84,8 @@
 namespace WTF {
 
 template <>
-struct CrossThreadCopier<media::Status>
-    : public CrossThreadCopierPassThrough<media::Status> {
+struct CrossThreadCopier<media::EncoderStatus>
+    : public CrossThreadCopierPassThrough<media::EncoderStatus> {
   STATIC_ONLY(CrossThreadCopier);
 };
 
@@ -582,9 +582,10 @@
   if (!media_encoder_) {
     HandleError(logger_->MakeException(
         "Encoder creation error.",
-        media::Status(media::StatusCode::kEncoderInitializationError,
-                      "Unable to create encoder (most likely unsupported "
-                      "codec/acceleration requirement combination)")));
+        media::EncoderStatus(
+            media::EncoderStatus::Codes::kEncoderInitializationError,
+            "Unable to create encoder (most likely unsupported "
+            "codec/acceleration requirement combination)")));
     request->EndTracing();
     return;
   }
@@ -596,7 +597,8 @@
       WrapCrossThreadPersistent(active_config_.Get()), reset_count_));
 
   auto done_callback = [](VideoEncoder* self, Request* req,
-                          media::VideoCodec codec, media::Status status) {
+                          media::VideoCodec codec,
+                          media::EncoderStatus status) {
     if (!self || self->reset_count_ != req->reset_count) {
       req->EndTracing(/*aborted=*/true);
       return;
@@ -606,7 +608,7 @@
 
     if (!status.is_ok()) {
       self->HandleError(self->logger_->MakeException(
-          "Encoder initialization error.", status));
+          "Encoder initialization error.", std::move(status)));
     } else {
       UMA_HISTOGRAM_ENUMERATION("Blink.WebCodecs.VideoEncoder.Codec", codec,
                                 media::VideoCodec::kMaxValue);
@@ -658,7 +660,7 @@
   request->StartTracingVideoEncode(keyframe);
 
   auto done_callback = [](VideoEncoder* self, Request* req,
-                          media::Status status) {
+                          media::EncoderStatus status) {
     if (!self || self->reset_count_ != req->reset_count) {
       req->EndTracing(/*aborted=*/true);
       return;
@@ -668,7 +670,7 @@
     self->active_encodes_--;
     if (!status.is_ok()) {
       self->HandleError(
-          self->logger_->MakeException("Encoding error.", status));
+          self->logger_->MakeException("Encoding error.", std::move(status)));
     }
     req->EndTracing();
     self->ProcessRequests();
@@ -695,27 +697,27 @@
       // This will execute shortly after CopyRGBATextureToVideoFrame()
       // completes. |blocking_request_in_progress_| = true will ensure that
       // HasPendingActivity() keeps the VideoEncoder alive long enough.
-      auto blit_done_callback = [](VideoEncoder* self, bool keyframe,
-                                   uint32_t reset_count,
-                                   base::TimeDelta timestamp,
-                                   media::VideoFrameMetadata metadata,
-                                   media::VideoEncoder::StatusCB done_callback,
-                                   scoped_refptr<media::VideoFrame> frame) {
-        if (!self || self->reset_count_ != reset_count || !frame)
-          return;
+      auto blit_done_callback =
+          [](VideoEncoder* self, bool keyframe, uint32_t reset_count,
+             base::TimeDelta timestamp, media::VideoFrameMetadata metadata,
+             media::VideoEncoder::EncoderStatusCB done_callback,
+             scoped_refptr<media::VideoFrame> frame) {
+            if (!self || self->reset_count_ != reset_count || !frame)
+              return;
 
-        // CopyRGBATextureToVideoFrame() operates on mailboxes and not frames,
-        // so we must manually copy over properties relevant to the encoder.
-        frame->set_timestamp(timestamp);
-        frame->set_metadata(metadata);
+            // CopyRGBATextureToVideoFrame() operates on mailboxes and not
+            // frames, so we must manually copy over properties relevant to the
+            // encoder.
+            frame->set_timestamp(timestamp);
+            frame->set_metadata(metadata);
 
-        DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
-        --self->requested_encodes_;
-        self->blocking_request_in_progress_ = false;
-        self->media_encoder_->Encode(std::move(frame), keyframe,
-                                     std::move(done_callback));
-        self->ProcessRequests();
-      };
+            DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
+            --self->requested_encodes_;
+            self->blocking_request_in_progress_ = false;
+            self->media_encoder_->Encode(std::move(frame), keyframe,
+                                         std::move(done_callback));
+            self->ProcessRequests();
+          };
 
       auto origin = frame->metadata().texture_origin_is_top_left
                         ? kTopLeft_GrSurfaceOrigin
@@ -778,12 +780,13 @@
   }
 
   if (!frame) {
-    auto status = media::Status(media::StatusCode::kEncoderFailedEncode,
-                                "Can't readback frame textures.");
     callback_runner_->PostTask(
         FROM_HERE, ConvertToBaseOnceCallback(CrossThreadBindOnce(
                        done_callback, WrapCrossThreadWeakPersistent(this),
-                       WrapCrossThreadPersistent(request), std::move(status))));
+                       WrapCrossThreadPersistent(request),
+                       media::EncoderStatus(
+                           media::EncoderStatus::Codes::kEncoderFailedEncode,
+                           "Can't readback frame textures."))));
     return;
   }
 
@@ -835,7 +838,7 @@
   request->StartTracing();
 
   auto reconf_done_callback = [](VideoEncoder* self, Request* req,
-                                 media::Status status) {
+                                 media::EncoderStatus status) {
     if (!self || self->reset_count_ != req->reset_count) {
       req->EndTracing(/*aborted=*/true);
       return;
@@ -859,7 +862,7 @@
 
   auto flush_done_callback = [](VideoEncoder* self, Request* req,
                                 decltype(reconf_done_callback) reconf_callback,
-                                media::Status status) {
+                                media::EncoderStatus status) {
     if (!self || self->reset_count_ != req->reset_count) {
       req->EndTracing(/*aborted=*/true);
       return;
@@ -867,7 +870,7 @@
     DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
     if (!status.is_ok()) {
       self->HandleError(self->logger_->MakeException(
-          "Encoder initialization error.", status));
+          "Encoder initialization error.", std::move(status)));
       self->blocking_request_in_progress_ = false;
       req->EndTracing();
       return;
@@ -1010,7 +1013,8 @@
 
   auto done_callback = [](std::unique_ptr<media::VideoEncoder> sw_encoder,
                           ScriptPromiseResolver* resolver,
-                          VideoEncoderSupport* support, media::Status status) {
+                          VideoEncoderSupport* support,
+                          media::EncoderStatus status) {
     support->setSupported(status.is_ok());
     resolver->Resolve(support);
     DeleteLater(resolver->GetScriptState(), std::move(sw_encoder));
diff --git a/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc b/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
index 2739ebf..d4414ab 100644
--- a/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
+++ b/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
@@ -687,6 +687,11 @@
       OnLog("VideoCaptureImpl is in error state.");
       state_update_cb.Run(blink::VIDEO_CAPTURE_STATE_ERROR);
       return;
+    case VIDEO_CAPTURE_STATE_ERROR_SYSTEM_PERMISSIONS_DENIED:
+      OnLog("VideoCaptureImpl is in system permissions error state.");
+      state_update_cb.Run(
+          blink::VIDEO_CAPTURE_STATE_ERROR_SYSTEM_PERMISSIONS_DENIED);
+      return;
     case VIDEO_CAPTURE_STATE_PAUSED:
     case VIDEO_CAPTURE_STATE_RESUMED:
       // The internal |state_| is never set to PAUSED/RESUMED since
@@ -760,12 +765,19 @@
   if (result->which() ==
       media::mojom::blink::VideoCaptureResult::Tag::ERROR_CODE) {
     DVLOG(1) << __func__ << " Failed with an error.";
-    OnLog("VideoCaptureImpl changing state to VIDEO_CAPTURE_STATE_ERROR");
+    if (result->get_error_code() ==
+        media::VideoCaptureError::kWinMediaFoundationSystemPermissionDenied) {
+      state_ = VIDEO_CAPTURE_STATE_ERROR_SYSTEM_PERMISSIONS_DENIED;
+      OnLog(
+          "VideoCaptureImpl changing state to "
+          "VIDEO_CAPTURE_STATE_ERROR_SYSTEM_PERMISSIONS_DENIED");
+    } else {
+      state_ = VIDEO_CAPTURE_STATE_ERROR;
+      OnLog("VideoCaptureImpl changing state to VIDEO_CAPTURE_STATE_ERROR");
+    }
     for (const auto& client : clients_)
-      client.second.state_update_cb.Run(blink::VIDEO_CAPTURE_STATE_ERROR);
+      client.second.state_update_cb.Run(state_);
     clients_.clear();
-    state_ = VIDEO_CAPTURE_STATE_ERROR;
-
     RecordStartOutcomeUMA(start_timedout_ ? VideoCaptureStartOutcome::kTimedout
                                           : VideoCaptureStartOutcome::kFailed);
     return;
diff --git a/third_party/blink/renderer/platform/video_capture/video_capture_impl_test.cc b/third_party/blink/renderer/platform/video_capture/video_capture_impl_test.cc
index 759fae8..3047776 100644
--- a/third_party/blink/renderer/platform/video_capture/video_capture_impl_test.cc
+++ b/third_party/blink/renderer/platform/video_capture/video_capture_impl_test.cc
@@ -759,6 +759,18 @@
                                       VideoCaptureStartOutcome::kStarted, 1);
 }
 
+TEST_F(VideoCaptureImplTest, WinSystemPermissionsErrorUpdatesCorrectState) {
+  EXPECT_CALL(*this,
+              OnStateUpdate(
+                  blink::VIDEO_CAPTURE_STATE_ERROR_SYSTEM_PERMISSIONS_DENIED));
+  video_capture_impl_->OnStateChanged(
+      media::mojom::blink::VideoCaptureResult::NewErrorCode(
+          media::VideoCaptureError::kWinMediaFoundationSystemPermissionDenied));
+
+  StartCapture(0, params_small_);
+  StopCapture(0);
+}
+
 TEST_F(VideoCaptureImplTest, BufferReceivedBeforeOnStarted) {
   const int kArbitraryBufferId = 16;
 
diff --git a/third_party/blink/renderer/platform/video_capture/video_capturer_source.h b/third_party/blink/renderer/platform/video_capture/video_capturer_source.h
index fdc0d72..73d7dc5 100644
--- a/third_party/blink/renderer/platform/video_capture/video_capturer_source.h
+++ b/third_party/blink/renderer/platform/video_capture/video_capturer_source.h
@@ -20,6 +20,12 @@
 
 namespace blink {
 
+enum class RunState {
+  kRunning = 0,
+  kStopped,
+  kSystemPermissionsError,
+};
+
 // VideoCapturerSource is an interface representing the source for captured
 // video.  An implementation will periodically call the frame callback with new
 // video frames.
@@ -27,7 +33,7 @@
  public:
   virtual ~VideoCapturerSource();
 
-  using RunningCallback = base::RepeatingCallback<void(bool)>;
+  using RunningCallback = base::RepeatingCallback<void(RunState)>;
 
   // Returns formats that are preferred and can currently be used. May be empty
   // if no formats are available or known.
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 747f215..7d42846 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6889,6 +6889,9 @@
 # Sheriff 2021-01-20
 crbug.com/1168522 external/wpt/focus/iframe-activeelement-after-focusing-out-iframes.html [ Failure Pass ]
 
+# Temporarily skipped to land crrev.com/c/3352118
+crbug.com/1282076 http/tests/devtools/console/console-message-format.js [ Skip ]
+
 # DevTools roll
 crbug.com/1050549 http/tests/devtools/console/console-correct-suggestions.js [ Failure Pass ]
 
@@ -6950,7 +6953,6 @@
 
 # Sheriff 2021-02-18
 crbug.com/1179772 [ Win7 ] http/tests/devtools/console/console-preserve-log-x-process-navigation.js [ Failure Pass ]
-crbug.com/1179857 [ Linux ] http/tests/inspector-protocol/dom/dom-getFrameOwner.js [ Failure Pass ]
 
 # Sheriff 2021-02-19
 crbug.com/1180227 [ Mac ] virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaStream-default-feature-policy.https.html [ Crash Failure Pass ]
@@ -8239,3 +8241,8 @@
 crbug.com/1281782 [ Mac ] virtual/no-alloc-direct-call/external/wpt/html/canvas/offscreen/compositing/2d.composite.globalAlpha.default.html [ Crash Pass ]
 crbug.com/1281782 [ Mac ] virtual/no-alloc-direct-call/external/wpt/html/canvas/offscreen/drawing-images-to-the-canvas/2d.drawImage.5arg.html [ Crash Pass ]
 crbug.com/1281792 external/wpt/event-timing/min-duration-threshold.html [ Failure Pass ]
+
+# Sheriff 2021-12-22
+crbug.com/1179857 [ Linux ] http/tests/inspector-protocol/dom/dom-getFrameOwner.js [ Failure Pass ]
+crbug.com/1179857 [ Mac10.13 ] http/tests/inspector-protocol/dom/dom-getFrameOwner.js [ Failure Pass ]
+crbug.com/1179857 [ Win ] http/tests/inspector-protocol/dom/dom-getFrameOwner.js [ Pass Timeout ]
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 8833ae3..534b3f1ab 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
@@ -1099,6 +1099,13 @@
        {}
       ]
      ],
+     "nested-balanced-monolithic-multicol-crash.html": [
+      "082bf70691c955e1ba7fac9e2c077c71a7d52f84",
+      [
+       null,
+       {}
+      ]
+     ],
      "nested-floated-multicol-with-monolithic-child-crash.html": [
       "f48ae9ff73faf1df5f9ff7247c64391f0c97d006",
       [
@@ -283904,6 +283911,10 @@
          "c50eddd41faba2ecc8928e459288fe612b999170",
          []
         ],
+        "child-navigates-parent-cross-origin-inner.html": [
+         "72b92c8061a995a27442fe071cb78d8af4a42261",
+         []
+        ],
         "child-navigates-parent-destination.html": [
          "bb8ba4e6989597efeb1326c9acddbcfe64074d10",
          []
@@ -311440,6 +311451,20 @@
         []
        ]
       },
+      "secure-context": {
+       "sender.html": [
+        "05e58822a862378b736af1113e2101a4fd587e73",
+        []
+       ],
+       "window.html": [
+        "071a507cb33e95e5b9cfc9a0bbccadab0983efee",
+        []
+       ]
+      },
+      "secure-context-service-worker.js": [
+       "5ba99f075362ea30cb14ca4edf51af594d98917a",
+       []
+      ],
       "service-worker-csp-worker.py": [
        "35a46964a7871a7ab85d4b0b181e5ff2e3f496fc",
        []
@@ -417500,6 +417525,24 @@
          {}
         ]
        ],
+       "child-navigates-parent-cross-origin.window.js": [
+        "c5bed0fd4c9afa85010242bfe6a7085587a3327c",
+        [
+         "html/browsers/browsing-the-web/navigating-across-documents/child-navigates-parent-cross-origin.window.html",
+         {
+          "script_metadata": [
+           [
+            "script",
+            "/common/get-host-info.sub.js"
+           ],
+           [
+            "script",
+            "resources/wait-for-messages.js"
+           ]
+          ]
+         }
+        ]
+       ],
        "child-navigates-parent-same-origin.window.js": [
         "a40c412029b4b28ae26691deff0286dfff3ccbcd",
         [
@@ -498370,6 +498413,13 @@
        {}
       ]
      ],
+     "secure-context.https.html": [
+      "666a5d378760b54fd0f4fa86f9db7a05e2be5b5a",
+      [
+       null,
+       {}
+      ]
+     ],
      "service-worker-csp-connect.https.html": [
       "226f4a40e4e9ab40bbeeac4e874208af3106d2da",
       [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/change-inline-color.html b/third_party/blink/web_tests/external/wpt/css/css-break/change-inline-color.html
index c4334af..9da2c8e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-break/change-inline-color.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/change-inline-color.html
@@ -1,17 +1,20 @@
 <!DOCTYPE html>
-<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
-<link rel="help" href="https://www.w3.org/TR/css-break-3/">
-<link rel="match" href="change-inline-color-ref.html">
-<p>The word PASS should be seen below.</p>
-<div style="columns:4; width:4em; text-align:center; column-gap:0; orphans:1; widows:1; color:white;">
-  <span id="span">
-    P A S S
-  </span>
-</div>
-<script>
-  requestAnimationFrame(()=> {
+<html class="reftest-wait">
+  <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+  <link rel="help" href="https://www.w3.org/TR/css-break-3/">
+  <link rel="match" href="change-inline-color-ref.html">
+  <p>The word PASS should be seen below.</p>
+  <div style="columns:4; width:4em; text-align:center; column-gap:0; orphans:1; widows:1; color:white;">
+    <span id="span">
+      P A S S
+    </span>
+  </div>
+  <script>
+    requestAnimationFrame(()=> {
       requestAnimationFrame(()=> {
-          span.style.color = "green";
+        span.style.color = "green";
+        document.documentElement.classList.remove("reftest-wait");
       });
-  });
-</script>
+    });
+  </script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-in-animation-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-in-animation-expected.txt
index a389ccd..10967acc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-in-animation-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-in-animation-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 94 tests; 86 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 94 tests; 88 PASS, 6 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Animation of font in ::marker
 PASS Animation of font-family in ::marker
 PASS Animation of font-feature-settings in ::marker
@@ -36,7 +36,7 @@
 PASS Animation of word-spacing in ::marker
 PASS Animation of text-decoration-skip-ink in ::marker
 FAIL Animation of text-emphasis in ::marker assert_equals: expected "triangle rgb(50, 100, 100)" but got ""
-FAIL Animation of text-emphasis-color in ::marker assert_equals: expected "rgb(50, 100, 100)" but got "rgb(100, 0, 200)"
+PASS Animation of text-emphasis-color in ::marker
 PASS Animation of text-emphasis-position in ::marker
 PASS Animation of text-emphasis-style in ::marker
 PASS Animation of text-shadow in ::marker
@@ -83,7 +83,7 @@
 PASS Transition of word-spacing in ::marker
 PASS Transition of text-decoration-skip-ink in ::marker
 FAIL Transition of text-emphasis in ::marker assert_equals: expected "triangle rgb(50, 100, 100)" but got ""
-FAIL Transition of text-emphasis-color in ::marker assert_equals: expected "rgb(50, 100, 100)" but got "rgb(100, 0, 200)"
+PASS Transition of text-emphasis-color in ::marker
 PASS Transition of text-emphasis-position in ::marker
 PASS Transition of text-emphasis-style in ::marker
 PASS Transition of text-shadow in ::marker
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-emphasis-position-computed-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-emphasis-position-computed-expected.txt
deleted file mode 100644
index c242701..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-emphasis-position-computed-expected.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a testharness.js-based test.
-FAIL Property text-emphasis-position value 'over' assert_equals: expected "over" but got "over right"
-FAIL Property text-emphasis-position value 'under' assert_equals: expected "under" but got "under right"
-PASS Property text-emphasis-position value 'over right'
-PASS Property text-emphasis-position value 'over left'
-PASS Property text-emphasis-position value 'under right'
-PASS Property text-emphasis-position value 'under left'
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/text-emphasis-color-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/text-emphasis-color-expected.txt
deleted file mode 100644
index 772c71bb..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/text-emphasis-color-expected.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-This is a testharness.js-based test.
-PASS Can set 'text-emphasis-color' to CSS-wide keywords
-PASS Can set 'text-emphasis-color' to var() references
-FAIL Can set 'text-emphasis-color' to the 'currentcolor' keyword Failed to execute 'set' on 'StylePropertyMap': Invalid type for property
-PASS Setting 'text-emphasis-color' to a length throws TypeError
-PASS Setting 'text-emphasis-color' to a percent throws TypeError
-PASS Setting 'text-emphasis-color' to a time throws TypeError
-PASS Setting 'text-emphasis-color' to an angle throws TypeError
-PASS Setting 'text-emphasis-color' to a flexible length throws TypeError
-PASS Setting 'text-emphasis-color' to a number throws TypeError
-PASS Setting 'text-emphasis-color' to a position throws TypeError
-PASS Setting 'text-emphasis-color' to a URL throws TypeError
-PASS Setting 'text-emphasis-color' to a transform throws TypeError
-PASS 'text-emphasis-color' does not supported 'red'
-PASS 'text-emphasis-color' does not supported '#bbff00'
-PASS 'text-emphasis-color' does not supported 'rgb(255, 255, 128)'
-PASS 'text-emphasis-color' does not supported 'hsl(50, 33%, 25%)'
-PASS 'text-emphasis-color' does not supported 'transparent'
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/secure-context-service-worker.js b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/secure-context-service-worker.js
new file mode 100644
index 0000000..5ba99f07
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/secure-context-service-worker.js
@@ -0,0 +1,21 @@
+self.addEventListener('fetch', event => {
+    let url = new URL(event.request.url);
+    if (url.pathname.indexOf('sender.html') != -1) {
+        event.respondWith(new Response(
+            "<script>window.parent.postMessage('interception', '*');</script>",
+            { headers: { 'Content-Type': 'text/html'} }
+        ));
+    } else if (url.pathname.indexOf('report') != -1) {
+        self.clients.matchAll().then(clients => {
+            for (client of clients) {
+                client.postMessage(url.searchParams.get('result'));
+            }
+        });
+        event.respondWith(
+            new Response(
+                '<script>window.close()</script>',
+                { headers: { 'Content-Type': 'text/html'} }
+            )
+        );
+    }
+});
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/secure-context/sender.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/secure-context/sender.html
new file mode 100644
index 0000000..05e5882
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/secure-context/sender.html
@@ -0,0 +1 @@
+<script>window.parent.postMessage('network', '*');</script>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/secure-context/window.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/secure-context/window.html
new file mode 100644
index 0000000..071a507
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/secure-context/window.html
@@ -0,0 +1,15 @@
+<body>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="../test-helpers.sub.js"></script>
+<script>
+const HTTPS_PREFIX = get_host_info().HTTPS_ORIGIN + base_path();
+
+window.onmessage = event => {
+    window.location = HTTPS_PREFIX + 'report?result=' + event.data;
+};
+
+const frame = document.createElement('iframe');
+frame.src = HTTPS_PREFIX + 'sender.html';
+document.body.appendChild(frame);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/secure-context.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/secure-context.https.html
new file mode 100644
index 0000000..666a5d3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/secure-context.https.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Ensure service worker is bypassed in insecure contexts</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+// This test checks that an HTTPS iframe embedded in an HTTP document is not
+// loaded via a service worker, since it's not a secure context. To that end, we
+// first register a service worker, wait for its activation, and create an
+// iframe that is controlled by said service worker. We use the iframe as a
+// way to receive messages from the service worker.
+// The bulk of the test begins by opening an HTTP window with the noopener
+// option, installing a message event handler, and embedding an HTTPS iframe. If
+// the browser behaves correctly then the iframe will be loaded from the network
+// and will contain a script that posts a message to the parent window,
+// informing it that it was loaded from the network. If, however, the iframe is
+// intercepted, the service worker will return a page with a script that posts a
+// message to the parent window, informing it that it was intercepted.
+// Upon getting either result, the window will report the result to the service
+// worker by navigating to a reporting URL. The service worker will then inform
+// all clients about the result, including the controlled iframe from the
+// beginning of the test. The message event handler will verify that the result
+// is as expected, concluding the test.
+promise_test(t => {
+    const SCRIPT = "resources/secure-context-service-worker.js";
+    const SCOPE = "resources/";
+    const HTTP_IFRAME_URL = get_host_info().HTTP_ORIGIN + base_path() + SCOPE + "secure-context/window.html";
+    return service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+        .then(registration => {
+            t.add_cleanup(() => {
+                return registration.unregister();
+            });
+            return wait_for_state(t, registration.installing, 'activated');
+        })
+        .then(() => {
+            return with_iframe(SCOPE + "blank.html");
+        })
+        .then(iframe => {
+            t.add_cleanup(() => {
+                iframe.remove();
+            });
+            return new Promise(resolve => {
+                iframe.contentWindow.navigator.serviceWorker.onmessage = t.step_func(event => {
+                    assert_equals(event.data, 'network');
+                    resolve();
+                });
+                window.open(HTTP_IFRAME_URL, 'MyWindow', 'noopener');
+            });
+        });
+})
+
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/accumulation-per-property-002-expected.txt b/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/accumulation-per-property-002-expected.txt
index b508e5ab..b477cd5 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/accumulation-per-property-002-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/accumulation-per-property-002-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 273 tests; 255 PASS, 18 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 273 tests; 261 PASS, 12 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Setup
 PASS isolation (type: discrete) has testAccumulation function
 PASS isolation: "isolate" onto "auto"
@@ -187,12 +187,12 @@
 PASS text-decoration-style: "dotted" onto "solid"
 PASS text-decoration-style: "solid" onto "dotted"
 PASS text-emphasis-color (type: color) has testAccumulation function
-FAIL text-emphasis-color supports animating as color of rgb() with overflowed  from and to values assert_equals: The value should be rgb(255, 128, 128) at 0ms expected "rgb(255, 128, 128)" but got "rgb(255, 0, 0)"
-FAIL text-emphasis-color supports animating as color of #RGB assert_equals: The value should be rgb(255, 128, 128) at 0ms expected "rgb(255, 128, 128)" but got "rgb(255, 0, 0)"
-FAIL text-emphasis-color supports animating as color of hsl() assert_equals: The value should be rgb(255, 128, 128) at 0ms expected "rgb(255, 128, 128)" but got "rgb(255, 0, 0)"
-FAIL text-emphasis-color supports animating as color of #RGBa assert_equals: The value should be rgb(230, 128, 128) at 0ms expected "rgb(230, 128, 128)" but got "rgba(255, 0, 0, 0.4)"
-FAIL text-emphasis-color supports animating as color of rgba() assert_equals: The value should be rgb(230, 128, 128) at 0ms expected "rgb(230, 128, 128)" but got "rgba(255, 0, 0, 0.4)"
-FAIL text-emphasis-color supports animating as color of hsla() assert_equals: The value should be rgb(230, 128, 128) at 0ms expected "rgb(230, 128, 128)" but got "rgba(255, 0, 0, 0.4)"
+PASS text-emphasis-color supports animating as color of rgb() with overflowed  from and to values
+PASS text-emphasis-color supports animating as color of #RGB
+PASS text-emphasis-color supports animating as color of hsl()
+PASS text-emphasis-color supports animating as color of #RGBa
+PASS text-emphasis-color supports animating as color of rgba()
+PASS text-emphasis-color supports animating as color of hsla()
 PASS text-emphasis-position (type: discrete) has testAccumulation function
 PASS text-emphasis-position: "under left" onto "over right"
 PASS text-emphasis-position: "over right" onto "under left"
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/addition-per-property-002-expected.txt b/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/addition-per-property-002-expected.txt
index 2047aeaa..9a5c636 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/addition-per-property-002-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/addition-per-property-002-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 269 tests; 254 PASS, 15 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 269 tests; 260 PASS, 9 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Setup
 PASS isolation (type: discrete) has testAddition function
 PASS isolation: "isolate" onto "auto"
@@ -187,12 +187,12 @@
 PASS text-decoration-style: "dotted" onto "solid"
 PASS text-decoration-style: "solid" onto "dotted"
 PASS text-emphasis-color (type: color) has testAddition function
-FAIL text-emphasis-color supports animating as color of rgb() with overflowed  from and to values assert_equals: The value should be rgb(255, 128, 128) at 0ms expected "rgb(255, 128, 128)" but got "rgb(255, 0, 0)"
-FAIL text-emphasis-color supports animating as color of #RGB assert_equals: The value should be rgb(255, 128, 128) at 0ms expected "rgb(255, 128, 128)" but got "rgb(255, 0, 0)"
-FAIL text-emphasis-color supports animating as color of hsl() assert_equals: The value should be rgb(255, 128, 128) at 0ms expected "rgb(255, 128, 128)" but got "rgb(255, 0, 0)"
-FAIL text-emphasis-color supports animating as color of #RGBa assert_equals: The value should be rgb(230, 128, 128) at 0ms expected "rgb(230, 128, 128)" but got "rgba(255, 0, 0, 0.4)"
-FAIL text-emphasis-color supports animating as color of rgba() assert_equals: The value should be rgb(230, 128, 128) at 0ms expected "rgb(230, 128, 128)" but got "rgba(255, 0, 0, 0.4)"
-FAIL text-emphasis-color supports animating as color of hsla() assert_equals: The value should be rgb(230, 128, 128) at 0ms expected "rgb(230, 128, 128)" but got "rgba(255, 0, 0, 0.4)"
+PASS text-emphasis-color supports animating as color of rgb() with overflowed  from and to values
+PASS text-emphasis-color supports animating as color of #RGB
+PASS text-emphasis-color supports animating as color of hsl()
+PASS text-emphasis-color supports animating as color of #RGBa
+PASS text-emphasis-color supports animating as color of rgba()
+PASS text-emphasis-color supports animating as color of hsla()
 PASS text-emphasis-position (type: discrete) has testAddition function
 PASS text-emphasis-position: "under left" onto "over right"
 PASS text-emphasis-position: "over right" onto "under left"
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/interpolation-per-property-002-expected.txt b/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/interpolation-per-property-002-expected.txt
index 9e3c434..c4a07ae 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/interpolation-per-property-002-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-animations/animation-model/animation-types/interpolation-per-property-002-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 311 PASS, 18 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 317 PASS, 12 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Setup
 PASS isolation (type: discrete) has testInterpolation function
 PASS isolation uses discrete animation when animating between "auto" and "isolate" with linear easing
@@ -224,12 +224,12 @@
 PASS text-decoration-style uses discrete animation when animating between "solid" and "dotted" with effect easing
 PASS text-decoration-style uses discrete animation when animating between "solid" and "dotted" with keyframe easing
 PASS text-emphasis-color (type: color) has testInterpolation function
-FAIL text-emphasis-color supports animating as color of rgb() assert_equals: The value should be rgb(128, 0, 128) at 500ms expected "rgb(128, 0, 128)" but got "rgb(0, 0, 255)"
-FAIL text-emphasis-color supports animating as color of #RGB assert_equals: The value should be rgb(128, 0, 128) at 500ms expected "rgb(128, 0, 128)" but got "rgb(0, 0, 255)"
-FAIL text-emphasis-color supports animating as color of hsl() assert_equals: The value should be rgb(128, 0, 128) at 500ms expected "rgb(128, 0, 128)" but got "rgb(0, 0, 255)"
-FAIL text-emphasis-color supports animating as color of #RGBa assert_equals: The value should be rgba(85, 0, 170, 0.6) at 500ms expected "rgba(85, 0, 170, 0.6)" but got "rgba(0, 0, 255, 0.8)"
-FAIL text-emphasis-color supports animating as color of rgba() assert_equals: The value should be rgba(85, 0, 170, 0.6) at 500ms expected "rgba(85, 0, 170, 0.6)" but got "rgba(0, 0, 255, 0.8)"
-FAIL text-emphasis-color supports animating as color of hsla() assert_equals: The value should be rgba(85, 0, 170, 0.6) at 500ms expected "rgba(85, 0, 170, 0.6)" but got "rgba(0, 0, 255, 0.8)"
+PASS text-emphasis-color supports animating as color of rgb()
+PASS text-emphasis-color supports animating as color of #RGB
+PASS text-emphasis-color supports animating as color of hsl()
+PASS text-emphasis-color supports animating as color of #RGBa
+PASS text-emphasis-color supports animating as color of rgba()
+PASS text-emphasis-color supports animating as color of hsla()
 PASS text-emphasis-position (type: discrete) has testInterpolation function
 PASS text-emphasis-position uses discrete animation when animating between "over right" and "under left" with linear easing
 PASS text-emphasis-position uses discrete animation when animating between "over right" and "under left" with effect easing
diff --git a/third_party/blink/web_tests/fast/css/parsing-text-emphasis-expected.txt b/third_party/blink/web_tests/fast/css/parsing-text-emphasis-expected.txt
index baa628c6..e2a04c0 100644
--- a/third_party/blink/web_tests/fast/css/parsing-text-emphasis-expected.txt
+++ b/third_party/blink/web_tests/fast/css/parsing-text-emphasis-expected.txt
@@ -8,12 +8,12 @@
 
 PASS: '-webkit-text-emphasis-position: initial;' parsed as ['', 'initial', '', '']
 PASS: '-webkit-text-emphasis-position: inherit;' parsed as ['', 'inherit', '', '']
-PASS: '-webkit-text-emphasis-position: over;' parsed as ['', 'over right', '', '']
+PASS: '-webkit-text-emphasis-position: over;' parsed as ['', 'over', '', '']
 PASS: '-webkit-text-emphasis-position: over right;' parsed as ['', 'over right', '', '']
 PASS: '-webkit-text-emphasis-position: right over;' parsed as ['', 'over right', '', '']
 PASS: '-webkit-text-emphasis-position: over left;' parsed as ['', 'over left', '', '']
 PASS: '-webkit-text-emphasis-position: left over;' parsed as ['', 'over left', '', '']
-PASS: '-webkit-text-emphasis-position: under;' parsed as ['', 'under right', '', '']
+PASS: '-webkit-text-emphasis-position: under;' parsed as ['', 'under', '', '']
 PASS: '-webkit-text-emphasis-position: under right;' parsed as ['', 'under right', '', '']
 PASS: '-webkit-text-emphasis-position: right under;' parsed as ['', 'under right', '', '']
 PASS: '-webkit-text-emphasis-position: under left;' parsed as ['', 'under left', '', '']
diff --git a/third_party/blink/web_tests/fast/css/parsing-text-emphasis.html b/third_party/blink/web_tests/fast/css/parsing-text-emphasis.html
index 4bab566..67777bd 100644
--- a/third_party/blink/web_tests/fast/css/parsing-text-emphasis.html
+++ b/third_party/blink/web_tests/fast/css/parsing-text-emphasis.html
@@ -36,12 +36,12 @@
     log("");
     test('-webkit-text-emphasis-position: initial;', '', 'initial', '');
     test('-webkit-text-emphasis-position: inherit;', '', 'inherit', '');
-    test('-webkit-text-emphasis-position: over;', '', 'over right', '');
+    test('-webkit-text-emphasis-position: over;', '', 'over', '');
     test('-webkit-text-emphasis-position: over right;', '', 'over right', '');
     test('-webkit-text-emphasis-position: right over;', '', 'over right', '');
     test('-webkit-text-emphasis-position: over left;', '', 'over left', '');
     test('-webkit-text-emphasis-position: left over;', '', 'over left', '');
-    test('-webkit-text-emphasis-position: under;', '', 'under right', '');
+    test('-webkit-text-emphasis-position: under;', '', 'under', '');
     test('-webkit-text-emphasis-position: under right;', '', 'under right', '');
     test('-webkit-text-emphasis-position: right under;', '', 'under right', '');
     test('-webkit-text-emphasis-position: under left;', '', 'under left', '');
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/service-workers/service-worker/resource-timing-fetch-variants.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/service-workers/service-worker/resource-timing-fetch-variants.https-expected.txt
new file mode 100644
index 0000000..ed296ab9
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/service-workers/service-worker/resource-timing-fetch-variants.https-expected.txt
@@ -0,0 +1,8 @@
+This is a testharness.js-based test.
+PASS Redirects done from within a service-worker should not be exposed to client ResourceTiming
+PASS Connection info from within a service-worker should not be exposed to client ResourceTiming
+PASS requestStart should never be before fetchStart
+PASS Delay from within service-worker (after internal fetching) should be accessible through `responseStart`
+PASS Delay from within service-worker (before internal fetching) should be measured before responseStart in the client ResourceTiming entry
+Harness: the test ran to completion.
+
diff --git a/third_party/closure_compiler/externs/accessibility_private.js b/third_party/closure_compiler/externs/accessibility_private.js
index da4fdd9..b295563 100644
--- a/third_party/closure_compiler/externs/accessibility_private.js
+++ b/third_party/closure_compiler/externs/accessibility_private.js
@@ -317,6 +317,16 @@
 };
 
 /**
+ * @enum {string}
+ */
+chrome.accessibilityPrivate.SetNativeChromeVoxResponse = {
+  SUCCESS: 'success',
+  TALKBACK_NOT_INSTALLED: 'talkbackNotInstalled',
+  WINDOW_NOT_FOUND: 'windowNotFound',
+  FAILURE: 'failure',
+};
+
+/**
  * Property to indicate whether event source should default to touch.
  * @type {number}
  */
@@ -410,8 +420,11 @@
 /**
  * Sets current ARC app to use native ARC support.
  * @param {boolean} enabled True for ChromeVox (native), false for TalkBack.
+ * @param {function(!chrome.accessibilityPrivate.SetNativeChromeVoxResponse):
+ *     void} callback
  */
-chrome.accessibilityPrivate.setNativeChromeVoxArcSupportForCurrentApp = function(enabled) {};
+chrome.accessibilityPrivate.setNativeChromeVoxArcSupportForCurrentApp =
+    function(enabled, callback) {};
 
 /**
  * Sends a fabricated key event.
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 2091e503..f854fa4 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -19578,6 +19578,7 @@
 </action>
 
 <action name="NewTabPage.Promo.EnhancedProtectionPromo.Accepted">
+  <obsolete>Removed from code in 2021-12.</obsolete>
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <description>
@@ -19586,6 +19587,7 @@
 </action>
 
 <action name="NewTabPage.Promo.EnhancedProtectionPromo.Dismissed">
+  <obsolete>Removed from code in 2021-12.</obsolete>
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index d82e049..6ada213 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -53980,6 +53980,7 @@
   <int value="870664435" label="DownloadProgressInfoBar:disabled"/>
   <int value="871435095" label="EnableAggregatedMlAppRanking:disabled"/>
   <int value="871713352" label="ImprovedLanguageSettings:enabled"/>
+  <int value="872509587" label="ArcWindowPredictor:enabled"/>
   <int value="873323760" label="MediaFoundationClearPlayback:disabled"/>
   <int value="876349279" label="WebViewExtraHeadersSameDomainOnly:enabled"/>
   <int value="876879670" label="OfflinePagesInDownloadHomeOpenInCct:enabled"/>
@@ -55679,6 +55680,7 @@
   <int value="2127648677" label="OmniboxExperimentalSuggestScoring:disabled"/>
   <int value="2129184006" label="NTPOfflinePageDownloadSuggestions:enabled"/>
   <int value="2129524809" label="PasswordCheck:disabled"/>
+  <int value="2129639191" label="ArcWindowPredictor:disabled"/>
   <int value="2129814401" label="SyncTrustedVaultPassphrasePromo:enabled"/>
   <int value="2129929643" label="enable-use-zoom-for-dsf"/>
   <int value="2132335798" label="EcheSWA:disabled"/>
@@ -92600,6 +92602,7 @@
   <int value="6" label="Enter Site Details"/>
   <int value="7" label="Remove Site Group"/>
   <int value="8" label="Remove Origin"/>
+  <int value="9" label="Remove Origin Partitioned"/>
 </enum>
 
 <enum name="WebsiteSettingsDiscoverabilityAction">
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index ccdd1b26..80fd9be 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -616,6 +616,10 @@
     means &quot;no pressure&quot; - 0%.
   </summary>
   <token key="PType">
+    <variant name="ArcFull"
+        summary="Percentage of time ARC was fully halted waiting on memory"/>
+    <variant name="ArcSome"
+        summary="Percentage of ARC had some blocking on memory"/>
     <variant name="Full"
         summary="Percentage of time fully halted waiting on memory operations"/>
     <variant name="Some"
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index 689ec02..3c7b3391 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -2367,7 +2367,7 @@
 </histogram>
 
 <histogram name="Enterprise.UserPolicyChromeOS.InitialFetch.OAuth2Error"
-    enum="GoogleServiceAuthError" expires_after="2021-12-26">
+    enum="GoogleServiceAuthError" expires_after="2022-03-01">
   <owner>igorcov@chromium.org</owner>
   <owner>asumaneev@google.com</owner>
   <summary>Service error during OAuth2 access token fetch.</summary>
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index 13fe97f..87280ddb 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -1627,7 +1627,7 @@
 
 <histogram
     name="Prerender.Experimental.CrossOriginRedirectionDomain{PrerenderTriggerType}"
-    enum="PrerenderCrossOriginRedirectionDomain" expires_after="2022-01-27">
+    enum="PrerenderCrossOriginRedirectionDomain" expires_after="2022-03-27">
   <owner>lingqi@chromium.org</owner>
   <owner>nhiroki@chromium.org</owner>
   <owner>src/content/browser/prerender/OWNERS</owner>
@@ -1645,7 +1645,7 @@
 <histogram
     name="Prerender.Experimental.CrossOriginRedirectionProtocolChange{PrerenderTriggerType}"
     enum="PrerenderCrossOriginRedirectionProtocolChange"
-    expires_after="2022-01-27">
+    expires_after="2022-03-27">
   <owner>lingqi@chromium.org</owner>
   <owner>nhiroki@chromium.org</owner>
   <owner>src/content/browser/prerender/OWNERS</owner>
@@ -1700,7 +1700,7 @@
 
 <histogram
     name="Prerender.Experimental.PrerenderCrossOriginRedirectionMismatch{PrerenderTriggerType}"
-    enum="PrerenderCrossOriginRedirectionMismatch" expires_after="2022-01-27">
+    enum="PrerenderCrossOriginRedirectionMismatch" expires_after="2022-03-27">
   <owner>lingqi@chromium.org</owner>
   <owner>nhiroki@chromium.org</owner>
   <owner>src/content/browser/prerender/OWNERS</owner>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index 68b0e50ba..5649e31 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -1355,6 +1355,9 @@
 
 <histogram name="NewTabPage.Promo.EnhancedProtectionPromo"
     enum="AndroidEnhancedProtectionPromoAction" expires_after="2022-05-15">
+  <obsolete>
+    Removed from code in 2021-12.
+  </obsolete>
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-core@google.com</owner>
   <summary>
@@ -1366,6 +1369,9 @@
 <histogram
     name="NewTabPage.Promo.EnhancedProtectionPromo.ImpressionUntilAction"
     units="units" expires_after="2022-05-22">
+  <obsolete>
+    Removed from code in 2021-12.
+  </obsolete>
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-core@google.com</owner>
   <summary>
@@ -1378,6 +1384,9 @@
 <histogram
     name="NewTabPage.Promo.EnhancedProtectionPromo.ImpressionUntilDismissal"
     units="units" expires_after="2022-05-29">
+  <obsolete>
+    Removed from code in 2021-12.
+  </obsolete>
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 6f4f4ee..87022dd 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -8439,7 +8439,9 @@
 </histogram>
 
 <histogram name="Linux.Wayland.Shell" enum="LinuxWaylandShellName"
-    expires_after="2021-12-31">
+    expires_after="never">
+<!-- expires-never: Needed to measure Linux ecosystem. -->
+
   <owner>rjkroege@chromium.org</owner>
   <owner>adunaev@igalia.com</owner>
   <summary>
diff --git a/tools/traffic_annotation/auditor/chromeos/safe_list.txt b/tools/traffic_annotation/auditor/chromeos/safe_list.txt
index d30a6fbc..40f349f 100644
--- a/tools/traffic_annotation/auditor/chromeos/safe_list.txt
+++ b/tools/traffic_annotation/auditor/chromeos/safe_list.txt
@@ -2,7 +2,6 @@
 # Please do not add any new safelist.
 #
 missing,chrome/browser/ash/enhanced_network_tts/enhanced_network_tts_impl.cc
-missing,chrome/browser/ui/ash/thumbnail_loader.cc
 missing,chrome/browser/ash/printing/server_printers_fetcher.cc
 missing,chrome/browser/ash/customization/customization_wallpaper_downloader.cc
 missing,ash/components/device_activity/device_activity_client.cc
@@ -11,14 +10,12 @@
 all,ash/webui/projector_app/projector_xhr_sender.cc
 all,ash/components/geolocation/simple_geolocation_request.cc
 all,ash/wallpaper/wallpaper_controller_impl.cc
-all,ash/components/timezone/timezone_request.cc
 all,ash/components/trial_group/trial_group_checker.cc
 all,chrome/browser/ash/net/network_portal_detector_impl.cc
 all,chrome/browser/ash/policy/uploading/system_log_uploader.cc
 all,chrome/browser/ui/ash/assistant/search_and_assistant_enabled_checker.cc
 all,chromeos/geolocation/simple_geolocation_request.cc
 all,components/quirks/quirks_client.cc
-all,chromeos/timezone/timezone_request.cc
 all,chromeos/services/device_sync/cryptauth_client_impl.cc
 all,chrome/browser/supervised_user/child_accounts/family_info_fetcher.cc
 all,chrome/services/sharing/nearby/platform/webrtc.cc
diff --git a/tools/traffic_annotation/scripts/update_annotations_sheet.py b/tools/traffic_annotation/scripts/update_annotations_sheet.py
index c290a28..4d89ae29 100755
--- a/tools/traffic_annotation/scripts/update_annotations_sheet.py
+++ b/tools/traffic_annotation/scripts/update_annotations_sheet.py
@@ -64,8 +64,10 @@
       verbose: bool
           Flag requesting dump of details of actions.
     """
+    print("Getting credential to update annotations report.")
     self.service = self._InitializeService(
         self._GetCredentials(credentials_file_path, client_secret_file_path))
+    print("Successfully got credential to update annotations report.")
     self.spreadsheet_id = spreadsheet_id
     self.annotations_sheet_name = annotations_sheet_name
     self.changes_sheet_name = changes_sheet_name
@@ -211,6 +213,7 @@
     Returns:
       bool Flag specifying if everything was OK or not.
     """
+    print("Generating updates for report.")
     sheet_contents = self.LoadAnnotationsSheet()
     if not sheet_contents:
       print("Could not read previous content.")
@@ -300,6 +303,7 @@
     |self.required_cell_updates| to the sheet.
     """
     # Insert/Remove rows.
+    print("Applying updates for the report.")
     if self.required_row_updates:
       self.service.spreadsheets().batchUpdate(
           spreadsheetId=self.spreadsheet_id,
@@ -377,6 +381,7 @@
       help='Shows the configurations help.')
   args = parser.parse_args()
 
+  print("Updating annotations sheet.")
   if args.config_help:
     PrintConfigHelp()
     return 0
@@ -401,6 +406,7 @@
       client_secret_file_path = config.get("client_secret_file_path", None),
       verbose = args.verbose)
   if not sheet_editor.GenerateUpdates(file_content):
+    print("Error generating updates for file content.")
     return -1
 
   if sheet_editor.required_cell_updates or sheet_editor.required_row_updates:
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index c5d4dd53..556bc46 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -351,4 +351,5 @@
  <item id="wallpaper_backdrop_images_info" added_in_milestone="99" content_hash_code="0010428c" os_list="chromeos" file_path="chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc" />
  <item id="wallpaper_backdrop_surprise_me_image" added_in_milestone="99" content_hash_code="0383b9db" os_list="chromeos" file_path="chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc" />
  <item id="nearby_connections_wifi_lan" added_in_milestone="99" content_hash_code="06b30010" os_list="chromeos" file_path="chrome/services/sharing/nearby/platform/wifi_lan_medium.cc" />
+ <item id="timezone_lookup" added_in_milestone="98" content_hash_code="01e64e71" os_list="chromeos" file_path="ash/components/timezone/timezone_request.cc" />
 </annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index 9b126c2c..f5a07e5b 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -79,6 +79,7 @@
       <traffic_annotation unique_id="supervised_users_denylist"/>
       <traffic_annotation unique_id="tachyon_ice_config_fetcher"/>
       <traffic_annotation unique_id="terms_of_service_fetch"/>
+      <traffic_annotation unique_id="timezone_lookup" />
       <traffic_annotation unique_id="url_icon_source_fetch"/>
       <traffic_annotation unique_id="wallpaper_backdrop_collection_names"/>
       <traffic_annotation unique_id="wallpaper_backdrop_images_info"/>
diff --git a/ui/base/ime/ash/ime_bridge.cc b/ui/base/ime/ash/ime_bridge.cc
index 5771b1f..31d7556 100644
--- a/ui/base/ime/ash/ime_bridge.cc
+++ b/ui/base/ime/ash/ime_bridge.cc
@@ -75,12 +75,6 @@
 }
 
 // static.
-void IMEBridge::Shutdown() {
-  delete g_ime_bridge;
-  g_ime_bridge = nullptr;
-}
-
-// static.
 IMEBridge* IMEBridge::Get() {
   if (!g_ime_bridge) {
     g_ime_bridge = new IMEBridge();
diff --git a/ui/base/ime/ash/ime_bridge.h b/ui/base/ime/ash/ime_bridge.h
index c3147f8..b864352 100644
--- a/ui/base/ime/ash/ime_bridge.h
+++ b/ui/base/ime/ash/ime_bridge.h
@@ -26,14 +26,8 @@
   IMEBridge& operator=(const IMEBridge&) = delete;
   ~IMEBridge();
 
-  // Releases the global instance.
-  // TODO(crbug/1279743): This is a stateful global. Make it into true global
-  // singleton first, then use dependency injection instead in the next step.
-  static void Shutdown();
-
-  // Constructs the global instance (if not available yet) then returns it.
-  // TODO(crbug/1279743): This is a stateful global. Make it into true global
-  // singleton first, then use dependency injection instead in the next step.
+  // Constructs the global singleton (if not available yet) then returns it.
+  // TODO(crbug/1279743): Use dependency injection instead of global singleton.
   static IMEBridge* Get();
 
   // Returns current InputContextHandler. This function returns nullptr if input
diff --git a/ui/base/ime/ash/input_method_ash_unittest.cc b/ui/base/ime/ash/input_method_ash_unittest.cc
index ffdefa8..e943f98 100644
--- a/ui/base/ime/ash/input_method_ash_unittest.cc
+++ b/ui/base/ime/ash/input_method_ash_unittest.cc
@@ -243,7 +243,6 @@
     IMEBridge::Get()->SetCandidateWindowHandler(nullptr);
     mock_ime_engine_handler_.reset();
     mock_ime_candidate_window_handler_.reset();
-    IMEBridge::Shutdown();
     ash::input_method::InputMethodManager::Shutdown();
 
     ResetFlags();
diff --git a/ui/base/ime/ash/input_method_manager.cc b/ui/base/ime/ash/input_method_manager.cc
index 816ae1a..b2e5117 100644
--- a/ui/base/ime/ash/input_method_manager.cc
+++ b/ui/base/ime/ash/input_method_manager.cc
@@ -34,8 +34,6 @@
 
 // static
 void InputMethodManager::Shutdown() {
-  DCHECK(g_input_method_manager)
-      << "InputMethodManager() is not initialized.";
   delete g_input_method_manager;
   g_input_method_manager = nullptr;
 }
diff --git a/ui/base/ime/init/input_method_initializer.cc b/ui/base/ime/init/input_method_initializer.cc
index 82d387f..8ace875 100644
--- a/ui/base/ime/init/input_method_initializer.cc
+++ b/ui/base/ime/init/input_method_initializer.cc
@@ -9,9 +9,8 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "ui/base/ime/ash/ime_bridge.h"
-#elif defined(USE_AURA) && (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && defined(USE_AURA) && \
+    (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
 #include "base/check.h"
 #include "ui/base/ime/linux/fake_input_method_context_factory.h"
 #elif defined(OS_WIN)
@@ -37,9 +36,7 @@
 }
 
 void ShutdownInputMethod() {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  IMEBridge::Shutdown();
-#elif defined(OS_WIN)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && defined(OS_WIN)
   TSFBridge::Shutdown();
 #endif
 }
@@ -63,9 +60,8 @@
 }
 
 void ShutdownInputMethodForTesting() {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  IMEBridge::Shutdown();
-#elif defined(USE_AURA) && (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && defined(USE_AURA) && \
+    (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
   const LinuxInputMethodContextFactory* factory =
       LinuxInputMethodContextFactory::instance();
   CHECK(!factory || factory == g_linux_input_method_context_factory_for_testing)
diff --git a/ui/gtk/gdk.sigs b/ui/gtk/gdk.sigs
index 7a393ed..81e35f9 100644
--- a/ui/gtk/gdk.sigs
+++ b/ui/gtk/gdk.sigs
@@ -34,3 +34,4 @@
 void gdk_display_notify_startup_complete(GdkDisplay* display, const char* startup_id);
 guint32 gdk_keyval_to_unicode(guint keyval);
 gdouble gdk_screen_get_resolution(GdkScreen *screen);
+guint gdk_unicode_to_keyval(guint32 wc);
diff --git a/ui/gtk/wayland/gtk_ui_platform_wayland.cc b/ui/gtk/wayland/gtk_ui_platform_wayland.cc
index 4ce6921..4172497c 100644
--- a/ui/gtk/wayland/gtk_ui_platform_wayland.cc
+++ b/ui/gtk/wayland/gtk_ui_platform_wayland.cc
@@ -36,6 +36,10 @@
 
 GdkModifierType GtkUiPlatformWayland::GetGdkKeyEventState(
     const ui::KeyEvent& key_event) {
+  // We first reconstruct the state that was stored as a property by
+  // ui::WaylandEventSource. It is incomplete, however, and includes only the
+  // modifier state. Therefore, we compute and add the group manually, if
+  // possible.
   const ui::Event::Properties* properties = key_event.properties();
   if (!properties)
     return static_cast<GdkModifierType>(0);
@@ -50,12 +54,46 @@
     flags |= value << bitshift;
     bitshift += 8;
   }
-  return ExtractGdkEventStateFromKeyEventFlags(flags);
+  auto state = ExtractGdkEventStateFromKeyEventFlags(flags);
+
+  // We use the default group 0 in the following three cases:
+  //  - we are using GTK 3 (gdk_display_map_keycode() is only available in
+  //    GTK 4);
+  //  - the pressed/released key is not a character (e.g. a modifier);
+  //  - no entry in |keyvals| matches |keyval|.
+  unsigned int group = 0;
+
+  if (gtk::GtkCheckVersion(4) && key_event.GetDomKey().IsCharacter()) {
+    guint keycode =
+        GetKeyEventProperty(key_event, ui::kPropertyKeyboardHwKeyCode);
+    guint keyval = gdk_unicode_to_keyval(key_event.GetDomKey().ToCharacter());
+    GdkKeymapKey* keys;
+    guint* keyvals;
+    int n_entries;
+
+    gdk_display_map_keycode(GetDefaultGdkDisplay(), keycode, &keys, &keyvals,
+                            &n_entries);
+
+    for (int i = 0; i < n_entries; ++i) {
+      if (keyvals[i] == keyval) {
+        group = keys[i].group;
+        break;
+      }
+    }
+
+    g_free(keys);
+    g_free(keyvals);
+  }
+
+  // As per Section 2.2.2 "Computing A State Field from an XKB State" of the XKB
+  // protocol specification.
+  return static_cast<GdkModifierType>(state | (group << 13));
 }
 
 int GtkUiPlatformWayland::GetGdkKeyEventGroup(const ui::KeyEvent& key_event) {
   auto state = GetGdkKeyEventState(key_event);
-  // See XkbGroupForCoreState() in //ui/events/x/x11_event_translation.cc.
+  // As per Section 2.2.2 "Computing A State Field from an XKB State" of the XKB
+  // protocol specification.
   return (state >> 13) & 0x3;
 }
 
@@ -99,6 +137,12 @@
   gtk_window_present(window);
 }
 
+GdkDisplay* GtkUiPlatformWayland::GetDefaultGdkDisplay() {
+  if (!default_display_)
+    default_display_ = gdk_display_get_default();
+  return default_display_;
+}
+
 void GtkUiPlatformWayland::OnHandleSetTransient(GtkWidget* widget,
                                                 const std::string& handle) {
   char* parent = const_cast<char*>(handle.c_str());
diff --git a/ui/gtk/wayland/gtk_ui_platform_wayland.h b/ui/gtk/wayland/gtk_ui_platform_wayland.h
index 4e2f35d..4f495d3 100644
--- a/ui/gtk/wayland/gtk_ui_platform_wayland.h
+++ b/ui/gtk/wayland/gtk_ui_platform_wayland.h
@@ -36,12 +36,14 @@
   bool PreferGtkIme() override;
 
  private:
+  GdkDisplay* GetDefaultGdkDisplay();
   // Called when xdg-foreign exports a parent window passed in
   // SetGtkWidgetTransientFor.
   void OnHandleSetTransient(GtkWidget* widget, const std::string& handle);
   void OnHandleForward(base::OnceCallback<void(std::string)> callback,
                        const std::string& handle);
 
+  GdkDisplay* default_display_ = nullptr;
   base::WeakPtrFactory<GtkUiPlatformWayland> weak_factory_{this};
 };
 
diff --git a/ui/webui/resources/cr_components/most_visited/most_visited.ts b/ui/webui/resources/cr_components/most_visited/most_visited.ts
index 0f2bac6..0f0f6d76 100644
--- a/ui/webui/resources/cr_components/most_visited/most_visited.ts
+++ b/ui/webui/resources/cr_components/most_visited/most_visited.ts
@@ -82,6 +82,7 @@
 export interface MostVisitedElement {
   $: {
     actionMenu: CrActionMenuElement,
+    container: HTMLElement,
     dialog: CrDialogElement,
     toast: CrToastElement,
     addShortcut: HTMLElement,