diff --git a/DEPS b/DEPS
index ab5c111..4bb63a2 100644
--- a/DEPS
+++ b/DEPS
@@ -245,7 +245,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '42bfd747a7fe1d0967a2eca420e6718c95883a70',
+  'skia_revision': '96860d3bbabc87df814cb7d815a97ea1019f1a04',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -253,7 +253,7 @@
   # 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': '161643215f933bef404e110a54334de0e1585c43',
+  'angle_revision': '2caa9d4fe4f1b2f4451e76ab22fd130cda996776',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # 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': '1655fc6e5b1b7db015e2b0fc9dfe75f6b0034388',
+  'devtools_frontend_revision': 'd334223c396efdbc905206c862d539c05f07fa46',
   # 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': '1351d4bbd8d5b2e0942299c98d243f07aec4dbf2',
+  'dawn_revision': 'fec381741e7fbeb581f1ea0af08b11552680669b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -404,7 +404,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'libcxxabi_revision':    '7d7912617f159a10bcaaf0867f140469b2a77536',
+  'libcxxabi_revision':    'fd0ef6db30debb29f90f276e9aa30db5c61fd93b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -540,31 +540,6 @@
       'condition': 'checkout_mac',
   },
 
-  # A somewhat recent Chromium-branded updater build. (x86_64)
-  'src/third_party/updater/chromium_mac_amd64': {
-      'dep_type': 'cipd',
-      'condition': 'checkout_mac',
-      'packages': [
-        {
-          'package': 'chromium/third_party/updater/chromium_mac_amd64',
-          'version': '1RmnK4JbmordT5NJbib3mfagpHvrO-oJaTW4HKsYgmAC',
-        },
-      ],
-  },
-
-  # A somewhat recent Chromium-branded updater build. (ARM64)
-  'src/third_party/updater/chromium_mac_arm64': {
-      'dep_type': 'cipd',
-      'condition': 'checkout_mac',
-      'packages': [
-        {
-          'package': 'chromium/third_party/updater/chromium_mac_arm64',
-          'version': '-o_WPHLr3JzvHdsTl_E0AgEV2D2--sBNqVT_F74hvOIC',
-        },
-      ],
-  },
-
-
   'src/tools/clang/dsymutil': {
     'packages': [
       {
@@ -580,7 +555,7 @@
     'packages': [
       {
         'package': 'chromium/chrome/test/data/autofill/captured_sites',
-        'version': 'zsM3SDEc4Fyck78R0QlSk1JVBonLatv_-2ONSoFUJ3IC',
+        'version': 'K7ZyaXaQqhwPpEXSZzXLlmZZC14m_PGMenrARj2bJlIC',
       }
     ],
     'condition': 'checkout_chromium_autofill_test_dependencies',
@@ -642,7 +617,7 @@
     Var('chromium_git') + '/external/github.com/toji/webvr.info.git' + '@' + 'c58ae99b9ff9e2aa4c524633519570bf33536248',
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'ed52cd24ed88168a014780720690306e15cf8dd5',
+    'url': Var('chromium_git') + '/website.git' + '@' + '92cd1107e1b24460b14643d38b79f911b9fa67a1',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -1020,7 +995,7 @@
   },
 
   'src/third_party/cast_core/public/src':
-    Var('chromium_git') + '/cast_core/public' + '@' + '2c1308094df97d5fcc84d6c40405c6ecb807301b',
+    Var('chromium_git') + '/cast_core/public' + '@' + '1c3981386ac760d99f38e25f8cc0ee6c68c47f4d',
 
   'src/third_party/catapult':
     Var('chromium_git') + '/catapult.git' + '@' + Var('catapult_revision'),
@@ -1069,7 +1044,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'ac54b8de82ddde0ad9749d161d65a23bdf4c4246',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'b8f1ca305854ce7fe7d9310dd3c166b68db369e1',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1452,7 +1427,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '585716d40f6593c3e04780d670e1e95fcce3052a',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'bda2a707d51d84cdea4d381e39bcc1561c9cc95a',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1670,10 +1645,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'b1f3776e4913637221733a4da09f3339e783b771',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '8bb4ca2f5b2d8325566514e4d8c10b7eaddea37a',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '7c696b0954c5a8a2ecbd3ee7517d92dc8b1c22a1',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '6053903ab919c9eb1b4280a595b94913bb97eb8f',
+    Var('webrtc_git') + '/src.git' + '@' + '45b3536a43acac37c71b57a837ef4a7a1f86fbbf',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1743,7 +1718,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b5d43418a88554eeb0257b80a5a2fda770ccf904',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@bb22aea3a7169901e3a416c03cd8b72b3c7b9cbd',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/docs/web-platform-compatibility.md b/android_webview/docs/web-platform-compatibility.md
index fb86c541..66abe96 100644
--- a/android_webview/docs/web-platform-compatibility.md
+++ b/android_webview/docs/web-platform-compatibility.md
@@ -480,7 +480,7 @@
 *** aside
 All common code is only compiled once for both WebView and Chrome for Android,
 for unavoidable packaging reasons, so there is no `WEBVIEW` or similar macro
-defined. Code can use `#if defined(OS_ANDROID)` to exclude it from other
+defined. Code can use `#if BUILDFLAG(IS_ANDROID)` to exclude it from other
 platforms if desired, but this includes Chrome for Android.
 ***
 
diff --git a/android_webview/renderer/aw_content_renderer_client.cc b/android_webview/renderer/aw_content_renderer_client.cc
index 17263f1..5f95fd8 100644
--- a/android_webview/renderer/aw_content_renderer_client.cc
+++ b/android_webview/renderer/aw_content_renderer_client.cc
@@ -175,6 +175,8 @@
     content::RenderFrame* render_frame,
     const blink::WebURLError& error,
     const std::string& http_method,
+    content::mojom::AlternativeErrorPageOverrideInfoPtr
+        alternative_error_page_info,
     std::string* error_html) {
   AwSafeBrowsingErrorPageControllerDelegateImpl::Get(render_frame)
       ->PrepareForErrorPage();
diff --git a/android_webview/renderer/aw_content_renderer_client.h b/android_webview/renderer/aw_content_renderer_client.h
index f9a78a55..c4c58b4 100644
--- a/android_webview/renderer/aw_content_renderer_client.h
+++ b/android_webview/renderer/aw_content_renderer_client.h
@@ -46,6 +46,8 @@
   void PrepareErrorPage(content::RenderFrame* render_frame,
                         const blink::WebURLError& error,
                         const std::string& http_method,
+                        content::mojom::AlternativeErrorPageOverrideInfoPtr
+                            alternative_error_page_info,
                         std::string* error_html) override;
   uint64_t VisitedLinkHash(const char* canonical_url, size_t length) override;
   bool IsLinkVisited(uint64_t link_hash) override;
diff --git a/android_webview/tools/run_cts.py b/android_webview/tools/run_cts.py
index 39d90a0..9e1d045 100755
--- a/android_webview/tools/run_cts.py
+++ b/android_webview/tools/run_cts.py
@@ -20,7 +20,6 @@
     os.path.dirname(__file__), os.pardir, os.pardir, 'build', 'android'))
 # pylint: disable=wrong-import-position,import-error
 import devil_chromium  # pylint: disable=unused-import
-from devil.android import device_utils
 from devil.android.ndk import abis
 from devil.android.sdk import version_codes
 from devil.android.tools import script_common
@@ -327,7 +326,6 @@
       # Start the emulator w/ -writable-system s.t. we can remount the system
       # partition r/w and install our own webview provider.
       emulator_instance.Start(writable_system=True)
-      device_utils.DeviceUtils(emulator_instance.serial).WaitUntilFullyBooted()
 
     devices = script_common.GetDevices(args.devices, args.denylist_file)
     device = devices[0]
diff --git a/ash/app_list/views/app_list_item_view.cc b/ash/app_list/views/app_list_item_view.cc
index c848bae..adee406 100644
--- a/ash/app_list/views/app_list_item_view.cc
+++ b/ash/app_list/views/app_list_item_view.cc
@@ -778,17 +778,25 @@
   if (drag_state_ != DragState::kNone)
     return;
 
-  // TODO(ginko) focus and selection should be unified.
   if ((grid_delegate_->IsSelectedView(this) || HasFocus()) &&
       (view_delegate_->KeyboardTraversalEngaged() ||
        waiting_for_context_menu_options_ || IsShowingAppMenu())) {
     cc::PaintFlags flags;
     flags.setAntiAlias(true);
-    if (view_delegate_->KeyboardTraversalEngaged()) {
+    // Clamshell ProductivityLauncher always has keyboard traversal engaged, so
+    // explicitly check HasFocus() before drawing focus ring. This allows
+    // right-click "selected" apps to avoid drawing the focus ring.
+    const bool draw_focus_ring =
+        features::IsProductivityLauncherEnabled() &&
+                !view_delegate_->IsInTabletMode()
+            ? HasFocus()
+            : view_delegate_->KeyboardTraversalEngaged();
+    if (draw_focus_ring) {
       flags.setColor(AppListColorProvider::Get()->GetFocusRingColor());
       flags.setStyle(cc::PaintFlags::kStroke_Style);
       flags.setStrokeWidth(kFocusRingWidth);
     } else {
+      // Draw a background highlight ("selected" in the UI spec).
       const AppListColorProvider* color_provider = AppListColorProvider::Get();
       const SkColor bg_color = grid_delegate_->IsInFolder()
                                    ? color_provider->GetFolderBackgroundColor()
diff --git a/ash/app_list/views/search_result_page_view.cc b/ash/app_list/views/search_result_page_view.cc
index d7654e3..517c180 100644
--- a/ash/app_list/views/search_result_page_view.cc
+++ b/ash/app_list/views/search_result_page_view.cc
@@ -41,6 +41,7 @@
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/views/accessibility/view_accessibility.h"
+#include "ui/views/animation/animation_builder.h"
 #include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/scroll_view.h"
@@ -61,8 +62,8 @@
 constexpr int kSeparatorPadding = 12;
 constexpr int kSeparatorThickness = 1;
 
-// The height of the search box in this page.
-constexpr int kSearchBoxHeight = 56;
+// The height of the active search box in this page.
+constexpr int kActiveSearchBoxHeight = 56;
 
 // The spacing between search box bottom and separator line.
 // Add 1 pixel spacing so that the search bbox bottom will not paint over
@@ -80,6 +81,14 @@
 // result page changes are delayed.
 constexpr base::TimeDelta kNotifyA11yDelay = base::Milliseconds(1500);
 
+// The duration of the search result page view expanding animation.
+constexpr base::TimeDelta kExpandingSearchResultDuration =
+    base::Milliseconds(200);
+
+// The duration of the search result page view closing animation.
+constexpr base::TimeDelta kClosingSearchResultDuration =
+    base::Milliseconds(100);
+
 // A container view that ensures the card background and the shadow are painted
 // in the correct order.
 class SearchCardView : public views::View {
@@ -130,10 +139,10 @@
   void Paint(gfx::Canvas* canvas, views::View* view) const override {
     canvas->DrawColor(get_color());
     gfx::Rect bounds = view->GetContentsBounds();
-    if (bounds.height() <= kSearchBoxHeight)
+    if (bounds.height() <= kActiveSearchBoxHeight)
       return;
     // Draw a separator between SearchBoxView and SearchResultPageView.
-    bounds.set_y(kSearchBoxHeight + kSearchBoxBottomSpacing);
+    bounds.set_y(kActiveSearchBoxHeight + kSearchBoxBottomSpacing);
     bounds.set_height(kSeparatorThickness);
     canvas->FillRect(bounds, AppListColorProvider::Get()->GetSeparatorColor());
   }
@@ -193,15 +202,15 @@
   // controller so we do not need to construct new ones here.
   if (features::IsProductivityLauncherEnabled()) {
     contents_view_->SetBorder(views::CreateEmptyBorder(gfx::Insets(
-        kSearchBoxHeight + kSearchBoxBottomSpacing + kSeparatorThickness, 0, 0,
-        0)));
+        kActiveSearchBoxHeight + kSearchBoxBottomSpacing + kSeparatorThickness,
+        0, 0, 0)));
     AddChildView(contents_view_);
   } else {
     auto scroller = std::make_unique<views::ScrollView>();
     // Leaves a placeholder area for the search box and the separator below it.
     scroller->SetBorder(views::CreateEmptyBorder(gfx::Insets(
-        kSearchBoxHeight + kSearchBoxBottomSpacing + kSeparatorThickness, 0, 0,
-        0)));
+        kActiveSearchBoxHeight + kSearchBoxBottomSpacing + kSeparatorThickness,
+        0, 0, 0)));
     scroller->SetDrawOverflowIndicator(false);
     scroller->SetContents(base::WrapUnique(contents_view_));
     // Setting clip height is necessary to make ScrollView take into account its
@@ -299,18 +308,28 @@
   int adjusted_height = std::min(
       std::max(kMinHeight,
                productivity_launcher_search_view_->TabletModePreferredHeight() +
-                   kSearchBoxHeight + kSearchBoxBottomSpacing +
+                   kActiveSearchBoxHeight + kSearchBoxBottomSpacing +
                    kSeparatorThickness),
       AppListPage::contents_view()->height());
   return gfx::Size(kWidth, adjusted_height);
 }
 
 void SearchResultPageView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
-  // The clip rect set for page state animations needs to be reset when the
-  // bounds change because page size change invalidates the previous bounds.
-  // This allows content to properly follow target bounds when screen rotates.
-  if (previous_bounds.size() != bounds().size())
+  if (previous_bounds.size() != bounds().size()) {
+    // If the clip rect is currently animating, then animate from the current
+    // clip rect bounds to the newly set bounds.
+    if (features::IsProductivityLauncherEnabled() &&
+        layer()->GetAnimator()->is_animating()) {
+      AnimateBetweenBounds(layer()->clip_rect(), gfx::Rect(bounds().size()));
+      return;
+    }
+
+    // The clip rect set for page state animations needs to be reset when the
+    // bounds change because page size change invalidates the previous bounds.
+    // This allows content to properly follow target bounds when screen
+    // rotates.
     layer()->SetClipRect(gfx::Rect());
+  }
 }
 
 void SearchResultPageView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
@@ -352,7 +371,9 @@
   bool should_show_page_view = false;
   if (features::IsProductivityLauncherEnabled()) {
     should_show_page_view = ShouldShowSearchResultView();
-    SetVisible(should_show_page_view);
+    AnimateToSearchResultsState(should_show_page_view
+                                    ? SearchResultsState::kExpanded
+                                    : SearchResultsState::kActive);
   } else {
     bool should_show_search_result_view = ShouldShowSearchResultView();
     for (auto* container : result_container_views_) {
@@ -370,11 +391,11 @@
         search_result_tile_item_list_view_->num_results() &&
         search_result_list_view_->num_results() &&
         ShouldShowSearchResultView());
+    AppListPage::contents_view()
+        ->GetSearchBoxView()
+        ->OnResultContainerVisibilityChanged(should_show_page_view);
   }
   Layout();
-  AppListPage::contents_view()
-      ->GetSearchBoxView()
-      ->OnResultContainerVisibilityChanged(should_show_page_view);
 }
 
 void SearchResultPageView::SelectedResultChanged() {
@@ -454,6 +475,118 @@
       selected_view->GetViewAccessibility().GetUniqueId().Get());
 }
 
+void SearchResultPageView::AnimateToSearchResultsState(
+    SearchResultsState to_state) {
+  // The search results page is only visible in expanded state. Exit early when
+  // transitioning between states where results UI is invisible.
+  if (current_search_results_state_ != SearchResultsState::kExpanded &&
+      to_state != SearchResultsState::kExpanded) {
+    SetVisible(false);
+    current_search_results_state_ = to_state;
+    return;
+  }
+
+  gfx::Rect from_rect =
+      GetPageBoundsForResultState(current_search_results_state_);
+  gfx::Rect to_rect = GetPageBoundsForResultState(to_state);
+
+  if (to_state == SearchResultsState::kExpanded) {
+    // Set bounds here because this is a result opening transition. We avoid
+    // setting bounds for closing transitions because then the animation would
+    // be hidden, instead set the bounds for closing transitions once the
+    // animation has completed.
+    SetBoundsRect(to_rect);
+    AppListPage::contents_view()
+        ->GetSearchBoxView()
+        ->OnResultContainerVisibilityChanged(true);
+  }
+
+  current_search_results_state_ = to_state;
+  AnimateBetweenBounds(from_rect, to_rect);
+}
+
+void SearchResultPageView::AnimateBetweenBounds(const gfx::Rect& from_rect,
+                                                const gfx::Rect& to_rect) {
+  if (from_rect == to_rect)
+    return;
+
+  gfx::Rect clip_rect = from_rect;
+  clip_rect -= to_rect.OffsetFromOrigin();
+  layer()->SetClipRect(clip_rect);
+  view_shadow_.reset();
+
+  views::AnimationBuilder()
+      .SetPreemptionStrategy(
+          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
+      .OnEnded(
+          base::BindOnce(&SearchResultPageView::OnAnimationBetweenBoundsEnded,
+                         base::Unretained(this)))
+      .Once()
+      .SetDuration((from_rect.height() < to_rect.height())
+                       ? kExpandingSearchResultDuration
+                       : kClosingSearchResultDuration)
+      .SetClipRect(layer(), gfx::Rect(to_rect.size()),
+                   gfx::Tween::FAST_OUT_SLOW_IN)
+      .SetRoundedCorners(
+          layer(),
+          gfx::RoundedCornersF(GetCornerRadiusForSearchResultsState(
+              current_search_results_state_)),
+          gfx::Tween::FAST_OUT_SLOW_IN);
+}
+
+void SearchResultPageView::OnAnimationBetweenBoundsEnded() {
+  view_shadow_ =
+      std::make_unique<ViewShadow>(this, kSearchBoxSearchResultShadowElevation);
+  view_shadow_->SetRoundedCornerRadius(
+      GetCornerRadiusForSearchResultsState(current_search_results_state_));
+
+  // To keep the animation visible for closing transitions from expanded search
+  // results, bounds are set here once the animation completes.
+  SetBoundsRect(GetPageBoundsForResultState(current_search_results_state_));
+
+  // Avoid visible overlap with the search box when the search results are not
+  // expanded.
+  if (current_search_results_state_ != SearchResultsState::kExpanded) {
+    SetVisible(false);
+    AppListPage::contents_view()
+        ->GetSearchBoxView()
+        ->OnResultContainerVisibilityChanged(false);
+  }
+}
+
+gfx::Rect SearchResultPageView::GetPageBoundsForResultState(
+    SearchResultsState state) const {
+  AppListState app_list_state = (state == SearchResultsState::kClosed)
+                                    ? AppListState::kStateApps
+                                    : AppListState::kStateSearchResults;
+  const ContentsView* const contents_view = AppListPage::contents_view();
+  const gfx::Rect contents_bounds = contents_view->GetContentsBounds();
+
+  gfx::Rect final_bounds =
+      GetPageBoundsForState(app_list_state, contents_bounds,
+                            contents_view->GetSearchBoxBounds(app_list_state));
+
+  // Ensure the height is set according to |state|, because
+  // GetPageBoundForState() returns a height according to |app_list_state| which
+  // does not account for kActive search result state.
+  if (state == SearchResultsState::kActive)
+    final_bounds.set_height(kActiveSearchBoxHeight);
+
+  return final_bounds;
+}
+
+int SearchResultPageView::GetCornerRadiusForSearchResultsState(
+    SearchResultsState state) {
+  switch (state) {
+    case SearchResultsState::kClosed:
+      return kSearchBoxBorderCornerRadius;
+    case SearchResultsState::kActive:
+      return kExpandedSearchBoxCornerRadiusForProductivityLauncher;
+    case SearchResultsState::kExpanded:
+      return kExpandedSearchBoxCornerRadiusForProductivityLauncher;
+  }
+}
+
 void SearchResultPageView::OnActiveAppListModelsChanged(
     AppListModel* model,
     SearchModel* search_model) {
@@ -609,7 +742,8 @@
     Layout();
 
   animator.Run(default_offset, layer());
-  animator.Run(default_offset, view_shadow_->shadow()->shadow_layer());
+  if (view_shadow_)
+    animator.Run(default_offset, view_shadow_->shadow()->shadow_layer());
   SearchResultPageAnchoredDialog* search_page_dialog =
       dialog_controller_->dialog();
   if (search_page_dialog) {
@@ -652,48 +786,62 @@
     return;
   }
 
-  const ContentsView* const contents_view = AppListPage::contents_view();
-  const gfx::Rect contents_bounds = contents_view->GetContentsBounds();
-  const gfx::Rect from_rect =
-      GetPageBoundsForState(from_state, contents_bounds,
-                            contents_view->GetSearchBoxBounds(from_state));
-  const gfx::Rect to_rect = GetPageBoundsForState(
-      to_state, contents_bounds, contents_view->GetSearchBoxBounds(to_state));
-  if (from_rect == to_rect)
-    return;
+  if (features::IsProductivityLauncherEnabled()) {
+    SearchResultsState to_result_state;
+    if (to_state == AppListState::kStateApps) {
+      to_result_state = SearchResultsState::kClosed;
+    } else {
+      to_result_state = ShouldShowSearchResultView()
+                            ? SearchResultsState::kExpanded
+                            : SearchResultsState::kActive;
+    }
 
-  const int to_radius =
-      contents_view->GetSearchBoxView()->GetSearchBoxBorderCornerRadiusForState(
-          to_state);
+    AnimateToSearchResultsState(to_result_state);
+  } else {
+    const ContentsView* const contents_view = AppListPage::contents_view();
+    const gfx::Rect contents_bounds = contents_view->GetContentsBounds();
+    const gfx::Rect from_rect =
+        GetPageBoundsForState(from_state, contents_bounds,
+                              contents_view->GetSearchBoxBounds(from_state));
+    const gfx::Rect to_rect = GetPageBoundsForState(
+        to_state, contents_bounds, contents_view->GetSearchBoxBounds(to_state));
+    if (from_rect == to_rect)
+      return;
 
-  // Here does the following animations;
-  // - clip-rect, so it looks like expanding from |from_rect| to |to_rect|.
-  // - rounded-rect
-  // - transform of the shadow
-  SetBoundsRect(to_rect);
-  gfx::Rect clip_rect = from_rect;
-  clip_rect -= to_rect.OffsetFromOrigin();
-  layer()->SetClipRect(clip_rect);
-  {
-    auto settings = contents_view->CreateTransitionAnimationSettings(layer());
-    layer()->SetClipRect(gfx::Rect(to_rect.size()));
-    // This changes the shadow's corner immediately while this corner bounds
-    // gradually. This would be fine because this would be unnoticeable to
-    // users.
-    view_shadow_->SetRoundedCornerRadius(to_radius);
-  }
+    const int to_radius =
+        contents_view->GetSearchBoxView()
+            ->GetSearchBoxBorderCornerRadiusForState(to_state);
 
-  // Animate the shadow's bounds through transform.
-  {
-    gfx::Transform transform;
-    transform.Translate(from_rect.origin() - to_rect.origin());
-    transform.Scale(static_cast<float>(from_rect.width()) / to_rect.width(),
-                    static_cast<float>(from_rect.height()) / to_rect.height());
-    view_shadow_->shadow()->layer()->SetTransform(transform);
+    // Here does the following animations;
+    // - clip-rect, so it looks like expanding from |from_rect| to |to_rect|.
+    // - rounded-rect
+    // - transform of the shadow
+    SetBoundsRect(to_rect);
+    gfx::Rect clip_rect = from_rect;
+    clip_rect -= to_rect.OffsetFromOrigin();
+    layer()->SetClipRect(clip_rect);
+    {
+      auto settings = contents_view->CreateTransitionAnimationSettings(layer());
+      layer()->SetClipRect(gfx::Rect(to_rect.size()));
+      // This changes the shadow's corner immediately while this corner bounds
+      // gradually. This would be fine because this would be unnoticeable to
+      // users.
+      view_shadow_->SetRoundedCornerRadius(to_radius);
+    }
 
-    auto settings = contents_view->CreateTransitionAnimationSettings(
-        view_shadow_->shadow()->layer());
-    view_shadow_->shadow()->layer()->SetTransform(gfx::Transform());
+    // Animate the shadow's bounds through transform.
+    {
+      gfx::Transform transform;
+      transform.Translate(from_rect.origin() - to_rect.origin());
+      transform.Scale(
+          static_cast<float>(from_rect.width()) / to_rect.width(),
+          static_cast<float>(from_rect.height()) / to_rect.height());
+      view_shadow_->shadow()->layer()->SetTransform(transform);
+
+      auto settings = contents_view->CreateTransitionAnimationSettings(
+          view_shadow_->shadow()->layer());
+      view_shadow_->shadow()->layer()->SetTransform(gfx::Transform());
+    }
   }
 }
 
@@ -715,7 +863,7 @@
 }
 
 gfx::Size SearchResultPageView::GetPreferredSearchBoxSize() const {
-  static gfx::Size size = gfx::Size(kWidth, kSearchBoxHeight);
+  static gfx::Size size = gfx::Size(kWidth, kActiveSearchBoxHeight);
   return size;
 }
 
diff --git a/ash/app_list/views/search_result_page_view.h b/ash/app_list/views/search_result_page_view.h
index 26c75fd..45eba40 100644
--- a/ash/app_list/views/search_result_page_view.h
+++ b/ash/app_list/views/search_result_page_view.h
@@ -132,6 +132,10 @@
   SearchResultListView* GetSearchResultListViewForTest();
 
  private:
+  // All possible states for the search results page. Used with productivity
+  // launcher.
+  enum class SearchResultsState { kClosed, kActive, kExpanded };
+
   // Separator between SearchResultContainerView.
   class HorizontalSeparator;
 
@@ -167,6 +171,23 @@
   // selected search result view.
   void NotifySelectedResultChanged();
 
+  // Animates from the current search results state to the `target_state`. Used
+  // with productivity launcher.
+  void AnimateToSearchResultsState(SearchResultsState target_state);
+
+  // Transitions between `from_rect` and `to_rect` by animating the clip rect.
+  void AnimateBetweenBounds(const gfx::Rect& from_rect,
+                            const gfx::Rect& to_rect);
+
+  // Called when the clip rect animation between bounds has ended.
+  void OnAnimationBetweenBoundsEnded();
+
+  // Get the page bounds according to the input SearchResultsState.
+  gfx::Rect GetPageBoundsForResultState(SearchResultsState state) const;
+
+  // Get the corner radius associated with the SearchResultsState.
+  int GetCornerRadiusForSearchResultsState(SearchResultsState state);
+
   template <typename T>
   T* AddSearchResultContainerView(std::unique_ptr<T> result_container) {
     auto* result = result_container.get();
@@ -216,6 +237,10 @@
   // containers.
   int last_search_result_count_ = 0;
 
+  // The currently shown search results state. Used with productivity launcher.
+  SearchResultsState current_search_results_state_ =
+      SearchResultsState::kClosed;
+
   std::unique_ptr<ViewShadow> view_shadow_;
 
   // The controller that manages dialogs modal to the search results page.
diff --git a/ash/components/fwupd/firmware_update_manager.cc b/ash/components/fwupd/firmware_update_manager.cc
index 699fdb28..2e8f2348 100644
--- a/ash/components/fwupd/firmware_update_manager.cc
+++ b/ash/components/fwupd/firmware_update_manager.cc
@@ -4,7 +4,6 @@
 
 #include "ash/components/fwupd/firmware_update_manager.h"
 
-#include <algorithm>
 #include <utility>
 
 #include "ash/public/cpp/fwupd_download_client.h"
@@ -250,7 +249,7 @@
     const std::string& device_id,
     PrepareForUpdateCallback callback) {
   DCHECK(!device_id.empty());
-
+  inflight_update_id_ = device_id;
   mojo::PendingRemote<firmware_update::mojom::InstallController>
       pending_remote = install_controller_receiver_.BindNewPipeAndPassRemote();
   install_controller_receiver_.set_disconnect_handler(base::BindOnce(
@@ -258,11 +257,6 @@
   std::move(callback).Run(std::move(pending_remote));
 }
 
-void FirmwareUpdateManager::FetchInProgressUpdate(
-    FetchInProgressUpdateCallback callback) {
-  std::move(callback).Run(mojo::Clone(inflight_update_));
-}
-
 // Query all updates for all devices.
 void FirmwareUpdateManager::RequestAllUpdates() {
   DCHECK(!HasPendingUpdates());
@@ -406,14 +400,6 @@
     return;
   }
 
-  DCHECK(inflight_update_.is_null());
-  for (const auto& update : updates_) {
-    if (update->device_id == device_id) {
-      inflight_update_ = mojo::Clone(update);
-      break;
-    }
-  }
-
   chromeos::FwupdClient::Get()->InstallUpdate(
       device_id, std::move(file_descriptor), options);
 
@@ -465,18 +451,11 @@
 void FirmwareUpdateManager::OnInstallResponse(bool success) {
   auto state = success ? firmware_update::mojom::UpdateState::kSuccess
                        : firmware_update::mojom::UpdateState::kFailed;
-  // Success or Fail states are both considered 100% done.
   auto update = ash::firmware_update::mojom::InstallationProgress::New(
       /**percentage=*/100, state);
-
-  // If the firmware update app is closed, the observer is no longer bound.
-  if (update_progress_observer_.is_bound()) {
-    update_progress_observer_->OnStatusChanged(std::move(update));
-  }
-
-  // Any updates are completed at this point, reset all cached.
+  update_progress_observer_->OnStatusChanged(std::move(update));
   ResetInstallState();
-  inflight_update_.reset();
+  inflight_update_id_.clear();
 }
 
 void FirmwareUpdateManager::BindInterface(
@@ -508,6 +487,8 @@
 
 void FirmwareUpdateManager::BeginUpdate(const std::string& device_id,
                                         const base::FilePath& filepath) {
+  DCHECK(!inflight_update_id_.empty());
+  DCHECK(inflight_update_id_ == device_id);
   DCHECK(!filepath.empty());
 
   if (!IsValidFirmwarePatchFile(filepath)) {
diff --git a/ash/components/fwupd/firmware_update_manager.h b/ash/components/fwupd/firmware_update_manager.h
index bbcbff4..ea5961e2 100644
--- a/ash/components/fwupd/firmware_update_manager.h
+++ b/ash/components/fwupd/firmware_update_manager.h
@@ -49,8 +49,6 @@
   void PrepareForUpdate(const std::string& device_id,
                         PrepareForUpdateCallback callback) override;
 
-  void FetchInProgressUpdate(FetchInProgressUpdateCallback callback) override;
-
   // firmware_update::mojom::InstallController
   void BeginUpdate(const std::string& device_id,
                    const base::FilePath& filepath) override;
@@ -155,8 +153,8 @@
   // Only used for testing if StartInstall() queries to a fake URL.
   std::string fake_url_for_testing_;
 
-  // The device update that is currently inflight.
-  firmware_update::mojom::FirmwareUpdatePtr inflight_update_;
+  // The device ID of the device that is currently being updated.
+  std::string inflight_update_id_;
 
   // Remotes for tracking observers that will be notified of changes to the
   // list of firmware updates.
diff --git a/ash/components/fwupd/firmware_update_manager_unittest.cc b/ash/components/fwupd/firmware_update_manager_unittest.cc
index 2ab42da..10297d7 100644
--- a/ash/components/fwupd/firmware_update_manager_unittest.cc
+++ b/ash/components/fwupd/firmware_update_manager_unittest.cc
@@ -577,14 +577,6 @@
   EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
       .WillRepeatedly(Invoke(this, &FirmwareUpdateManagerTest::OnMethodCalled));
 
-  dbus_responses_.push_back(CreateOneDeviceResponse());
-  dbus_responses_.push_back(CreateOneUpdateResponse());
-
-  FakeUpdateObserver update_observer;
-  SetupObserver(&update_observer);
-
-  base::RunLoop().RunUntilIdle();
-
   dbus_responses_.push_back(dbus::Response::CreateEmpty());
 
   std::string fake_url = "https://faketesturl/";
diff --git a/ash/components/phonehub/recent_apps_interaction_handler_impl.cc b/ash/components/phonehub/recent_apps_interaction_handler_impl.cc
index c9ffdf54..52e1572d 100644
--- a/ash/components/phonehub/recent_apps_interaction_handler_impl.cc
+++ b/ash/components/phonehub/recent_apps_interaction_handler_impl.cc
@@ -6,7 +6,7 @@
 
 #include "ash/components/phonehub/notification.h"
 #include "ash/components/phonehub/pref_names.h"
-#include "base/logging.h"
+#include "chromeos/components/multidevice/logging/logging.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 
@@ -115,6 +115,7 @@
 void RecentAppsInteractionHandlerImpl::
     LoadRecentAppMetadataListFromPrefIfNeed() {
   if (!has_loaded_prefs_) {
+    PA_LOG(INFO) << "LoadRecentAppMetadataListFromPref";
     const base::Value* recent_apps_history_pref =
         pref_service_->GetList(prefs::kRecentAppsHistory);
     for (const auto& value : recent_apps_history_pref->GetList()) {
@@ -128,6 +129,7 @@
 }
 
 void RecentAppsInteractionHandlerImpl::SaveRecentAppMetadataListToPref() {
+  PA_LOG(INFO) << "SaveRecentAppMetadataListToPref";
   size_t num_recent_apps_to_display =
       std::min(recent_app_metadata_list_.size(), kMaxMostRecentApps);
   std::vector<base::Value> app_metadata_value_list;
@@ -146,8 +148,10 @@
 
 void RecentAppsInteractionHandlerImpl::OnHostStatusChanged(
     const HostStatusWithDevice& host_device_with_status) {
-  if (host_device_with_status.first != HostStatus::kHostVerified)
+  if (host_device_with_status.first != HostStatus::kHostVerified) {
+    PA_LOG(INFO) << "ClearRecentAppMetadataListAndPref";
     ClearRecentAppMetadataListAndPref();
+  }
 }
 
 void RecentAppsInteractionHandlerImpl::OnNotificationAccessChanged() {
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index a0f6e39..b7febb49 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -433,7 +433,7 @@
 
 // Enables dragging and dropping an existing window to new desk in overview.
 const base::Feature kDragWindowToNewDesk{"DragWindowToNewDesk",
-                                         base::FEATURE_DISABLED_BY_DEFAULT};
+                                         base::FEATURE_ENABLED_BY_DEFAULT};
 
 // If enabled, DriveFS will be used for Drive sync.
 const base::Feature kDriveFs{"DriveFS", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/ash/public/cpp/app_list/app_list_types.cc b/ash/public/cpp/app_list/app_list_types.cc
index 0a6fda33..fe6897f9 100644
--- a/ash/public/cpp/app_list/app_list_types.cc
+++ b/ash/public/cpp/app_list/app_list_types.cc
@@ -119,6 +119,21 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
+std::ostream& operator<<(std::ostream& os, AppListState state) {
+  switch (state) {
+    case AppListState::kStateApps:
+      return os << "StateApps";
+    case AppListState::kStateSearchResults:
+      return os << "SearchResults";
+    case AppListState::kStateStart_DEPRECATED:
+      return os << "Start_DEPRECATED";
+    case AppListState::kStateEmbeddedAssistant:
+      return os << "EmbeddedAssistant";
+    case AppListState::kInvalidState:
+      return os << "InvalidState";
+  }
+}
+
 std::ostream& operator<<(std::ostream& os, AppListBubblePage page) {
   switch (page) {
     case AppListBubblePage::kNone:
diff --git a/ash/public/cpp/app_list/app_list_types.h b/ash/public/cpp/app_list/app_list_types.h
index 1a3a0a0..9db41b5f 100644
--- a/ash/public/cpp/app_list/app_list_types.h
+++ b/ash/public/cpp/app_list/app_list_types.h
@@ -195,6 +195,9 @@
   kStateLast = kInvalidState,  // Don't use over IPC
 };
 
+ASH_PUBLIC_EXPORT std::ostream& operator<<(std::ostream& os,
+                                           AppListState state);
+
 // Sub-pages of the app list bubble (with ProductivityLauncher).
 enum class AppListBubblePage {
   // Used at startup and when the app list bubble is not visible. Allows
diff --git a/ash/public/cpp/system_tray_client.h b/ash/public/cpp/system_tray_client.h
index 7c9bf73a..a6f3b51 100644
--- a/ash/public/cpp/system_tray_client.h
+++ b/ash/public/cpp/system_tray_client.h
@@ -74,9 +74,6 @@
   // loaded.
   virtual void ShowAboutChromeOS() = 0;
 
-  // Shows the Chromebook help app.
-  virtual void ShowHelp() = 0;
-
   // Shows accessibility help.
   virtual void ShowAccessibilityHelp() = 0;
 
@@ -92,9 +89,6 @@
   // Shows the settings related to the stylus tool palette.
   virtual void ShowPaletteSettings() = 0;
 
-  // Shows information about public account mode.
-  virtual void ShowPublicAccountInfo() = 0;
-
   // Shows information about enterprise enrolled devices.
   virtual void ShowEnterpriseInfo() = 0;
 
diff --git a/ash/public/cpp/test/test_system_tray_client.cc b/ash/public/cpp/test/test_system_tray_client.cc
index bb828b2..ab473df 100644
--- a/ash/public/cpp/test/test_system_tray_client.cc
+++ b/ash/public/cpp/test/test_system_tray_client.cc
@@ -54,8 +54,6 @@
 
 void TestSystemTrayClient::ShowAboutChromeOS() {}
 
-void TestSystemTrayClient::ShowHelp() {}
-
 void TestSystemTrayClient::ShowAccessibilityHelp() {}
 
 void TestSystemTrayClient::ShowAccessibilitySettings() {}
@@ -70,8 +68,6 @@
   show_os_settings_privacy_and_security_count_++;
 }
 
-void TestSystemTrayClient::ShowPublicAccountInfo() {}
-
 void TestSystemTrayClient::ShowEnterpriseInfo() {}
 
 void TestSystemTrayClient::ShowNetworkConfigure(const std::string& network_id) {
diff --git a/ash/public/cpp/test/test_system_tray_client.h b/ash/public/cpp/test/test_system_tray_client.h
index 19b1815..7103cbb 100644
--- a/ash/public/cpp/test/test_system_tray_client.h
+++ b/ash/public/cpp/test/test_system_tray_client.h
@@ -40,14 +40,12 @@
   void ShowTetherNetworkSettings() override;
   void ShowWifiSyncSettings() override;
   void ShowAboutChromeOS() override;
-  void ShowHelp() override;
   void ShowAccessibilityHelp() override;
   void ShowAccessibilitySettings() override;
   void ShowGestureEducationHelp() override;
   void ShowPaletteHelp() override;
   void ShowPaletteSettings() override;
   void ShowPrivacyAndSecuritySettings() override;
-  void ShowPublicAccountInfo() override;
   void ShowEnterpriseInfo() override;
   void ShowNetworkConfigure(const std::string& network_id) override;
   void ShowNetworkCreate(const std::string& type) override;
diff --git a/ash/quick_pair/common/BUILD.gn b/ash/quick_pair/common/BUILD.gn
index e8833f1e..4fcf37b 100644
--- a/ash/quick_pair/common/BUILD.gn
+++ b/ash/quick_pair/common/BUILD.gn
@@ -69,7 +69,6 @@
   deps = [
     "//ash/services/quick_pair/public/mojom",
     "//base/test:test_support",
-    "//components/image_fetcher/core",
     "//services/network/public/cpp",
     "//testing/gtest",
   ]
diff --git a/ash/quick_pair/common/device.cc b/ash/quick_pair/common/device.cc
index aedc7f6..3b3d325c 100644
--- a/ash/quick_pair/common/device.cc
+++ b/ash/quick_pair/common/device.cc
@@ -7,6 +7,7 @@
 #include <ostream>
 
 #include "ash/quick_pair/common/protocol.h"
+#include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
 
 namespace {
@@ -16,10 +17,15 @@
                              const std::string& ble_address,
                              const absl::optional<std::string>& classic_address,
                              const ash::quick_pair::Protocol& protocol) {
-  stream << "[Device: metadata_id=" << metadata_id
-         << ", ble_address=" << ble_address
-         << ", class_address=" << classic_address.value_or("null")
-         << ", protocol=" << protocol << "]";
+  stream << "[Device: metadata_id=" << metadata_id;
+
+  // We can only include PII from the device in verbose logging.
+  if (VLOG_IS_ON(/*verbose_level=*/1)) {
+    stream << ", ble_address=" << ble_address
+           << ", class_address=" << classic_address.value_or("null");
+  }
+
+  stream << ", protocol=" << protocol << "]";
   return stream;
 }
 
diff --git a/ash/quick_pair/common/mock_quick_pair_browser_delegate.h b/ash/quick_pair/common/mock_quick_pair_browser_delegate.h
index 745329f2..90c01dc 100644
--- a/ash/quick_pair/common/mock_quick_pair_browser_delegate.h
+++ b/ash/quick_pair/common/mock_quick_pair_browser_delegate.h
@@ -8,7 +8,6 @@
 #include "ash/quick_pair/common/quick_pair_browser_delegate.h"
 #include "base/component_export.h"
 #include "base/memory/scoped_refptr.h"
-#include "components/image_fetcher/core/image_fetcher.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -17,7 +16,7 @@
 
 namespace signin {
 class IdentityManager;
-}  // namespace signin
+}
 
 namespace ash {
 namespace quick_pair {
@@ -35,10 +34,6 @@
               (),
               (override));
   MOCK_METHOD(signin::IdentityManager*, GetIdentityManager, (), (override));
-  MOCK_METHOD(std::unique_ptr<image_fetcher::ImageFetcher>,
-              GetImageFetcher,
-              (),
-              (override));
   MOCK_METHOD(PrefService*, GetActivePrefService, (), (override));
   MOCK_METHOD(void,
               RequestService,
diff --git a/ash/quick_pair/common/quick_pair_browser_delegate.h b/ash/quick_pair/common/quick_pair_browser_delegate.h
index cc23e02..f3791f1 100644
--- a/ash/quick_pair/common/quick_pair_browser_delegate.h
+++ b/ash/quick_pair/common/quick_pair_browser_delegate.h
@@ -12,10 +12,6 @@
 
 class PrefService;
 
-namespace image_fetcher {
-class ImageFetcher;
-}  // namespace image_fetcher
-
 namespace network {
 class SharedURLLoaderFactory;
 }  // namespace network
@@ -46,8 +42,6 @@
   // Returns a pointer to the IdentityManager for the active user.
   virtual signin::IdentityManager* GetIdentityManager() = 0;
 
-  virtual std::unique_ptr<image_fetcher::ImageFetcher> GetImageFetcher() = 0;
-
   // For accessing prefs of the active user.
   virtual PrefService* GetActivePrefService() = 0;
 
diff --git a/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.cc b/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.cc
index 7c51cca..e170f00 100644
--- a/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.cc
+++ b/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.cc
@@ -128,8 +128,7 @@
   RecordGattConnectionResult(/*success=*/!error_code.has_value());
 
   if (error_code) {
-    QP_LOG(WARNING) << "Error creating GATT connection to device at address:["
-                    << device_address_ << "].";
+    QP_LOG(WARNING) << "Error creating GATT connection to device.";
     RecordGattConnectionErrorCode(error_code.value());
     NotifyInitializedError(PairFailure::kCreateGattConnection);
   } else {
diff --git a/ash/quick_pair/repository/BUILD.gn b/ash/quick_pair/repository/BUILD.gn
index cecb5d82..3f3de4c 100644
--- a/ash/quick_pair/repository/BUILD.gn
+++ b/ash/quick_pair/repository/BUILD.gn
@@ -19,8 +19,6 @@
     "fast_pair/device_metadata_fetcher.h",
     "fast_pair/fast_pair_image_decoder.cc",
     "fast_pair/fast_pair_image_decoder.h",
-    "fast_pair/fast_pair_image_decoder_impl.cc",
-    "fast_pair/fast_pair_image_decoder_impl.h",
     "fast_pair/footprints_fetcher.cc",
     "fast_pair/footprints_fetcher.h",
     "fast_pair/pairing_metadata.cc",
@@ -69,8 +67,6 @@
   sources = [
     "fake_fast_pair_repository.cc",
     "fake_fast_pair_repository.h",
-    "fast_pair/mock_fast_pair_image_decoder.cc",
-    "fast_pair/mock_fast_pair_image_decoder.h",
     "mock_fast_pair_repository.cc",
     "mock_fast_pair_repository.h",
     "mock_http_fetcher.cc",
diff --git a/ash/quick_pair/repository/fast_pair/device_id_map.cc b/ash/quick_pair/repository/fast_pair/device_id_map.cc
index 3d8c3a8..1ebdddd 100644
--- a/ash/quick_pair/repository/fast_pair/device_id_map.cc
+++ b/ash/quick_pair/repository/fast_pair/device_id_map.cc
@@ -187,9 +187,7 @@
   const device::BluetoothDevice* device =
       bluetooth_adapter_->GetDevice(address);
   if (!device) {
-    QP_LOG(WARNING) << __func__
-                    << ": Can't find matching bluetooth device for address: " +
-                           address;
+    QP_LOG(WARNING) << __func__ << ": Can't find matching bluetooth device";
     return absl::nullopt;
   }
   return device->GetIdentifier();
diff --git a/ash/quick_pair/repository/fast_pair/device_image_store.cc b/ash/quick_pair/repository/fast_pair/device_image_store.cc
index 9475b47..e0ffe1ab 100644
--- a/ash/quick_pair/repository/fast_pair/device_image_store.cc
+++ b/ash/quick_pair/repository/fast_pair/device_image_store.cc
@@ -5,16 +5,12 @@
 #include "ash/quick_pair/repository/fast_pair/device_image_store.h"
 
 #include "ash/quick_pair/common/logging.h"
-#include "ash/quick_pair/proto/fastpair.pb.h"
-#include "ash/quick_pair/proto/fastpair_data.pb.h"
-#include "ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h"
 #include "ash/shell.h"
 #include "chromeos/services/bluetooth_config/public/cpp/device_image_info.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "ui/base/webui/web_ui_util.h"
-#include "url/gurl.h"
 
 namespace ash {
 namespace quick_pair {
@@ -30,58 +26,30 @@
   registry->RegisterDictionaryPref(kDeviceImageStorePref);
 }
 
-DeviceImageStore::DeviceImageStore(FastPairImageDecoder* image_decoder)
-    : image_decoder_(image_decoder) {}
+DeviceImageStore::DeviceImageStore() = default;
 
 DeviceImageStore::~DeviceImageStore() = default;
 
-void DeviceImageStore::FetchDeviceImages(
+void DeviceImageStore::SaveDeviceImages(
     const std::string& model_id,
     DeviceMetadata* device_metadata,
-    FetchDeviceImagesCallback on_images_saved_callback) {
+    SaveDeviceImagesCallback on_images_saved_callback) {
   DCHECK(device_metadata);
   DeviceImageInfo& images = model_id_to_images_[model_id];
 
   if (images.default_image().empty() && !device_metadata->image().IsEmpty()) {
     SaveImageAsBase64(model_id, DeviceImageType::kDefault,
-                      on_images_saved_callback, device_metadata->image());
+                      std::move(on_images_saved_callback),
+                      device_metadata->image());
   } else {
-    on_images_saved_callback.Run(std::make_pair(
-        DeviceImageType::kDefault, FetchDeviceImagesResult::kSkipped));
-  }
-
-  nearby::fastpair::TrueWirelessHeadsetImages true_wireless_images =
-      device_metadata->GetDetails().true_wireless_images();
-
-  if (images.left_bud_image().empty() &&
-      true_wireless_images.has_left_bud_url()) {
-    DecodeImage(model_id, DeviceImageType::kLeftBud,
-                true_wireless_images.left_bud_url(), on_images_saved_callback);
-  } else {
-    on_images_saved_callback.Run(std::make_pair(
-        DeviceImageType::kLeftBud, FetchDeviceImagesResult::kSkipped));
-  }
-
-  if (images.right_bud_image().empty() &&
-      true_wireless_images.has_right_bud_url()) {
-    DecodeImage(model_id, DeviceImageType::kRightBud,
-                true_wireless_images.right_bud_url(), on_images_saved_callback);
-  } else {
-    on_images_saved_callback.Run(std::make_pair(
-        DeviceImageType::kRightBud, FetchDeviceImagesResult::kSkipped));
-  }
-
-  if (images.case_image().empty() && true_wireless_images.has_case_url()) {
-    DecodeImage(model_id, DeviceImageType::kCase,
-                true_wireless_images.case_url(), on_images_saved_callback);
-  } else {
-    on_images_saved_callback.Run(std::make_pair(
-        DeviceImageType::kCase, FetchDeviceImagesResult::kSkipped));
+    QP_LOG(WARNING) << __func__ << ": No default device image to save.";
+    std::move(on_images_saved_callback).Run(SaveDeviceImagesResult::kFailure);
   }
 }
 
 bool DeviceImageStore::PersistDeviceImages(const std::string& model_id) {
-  DeviceImageInfo& images = model_id_to_images_[model_id];
+  chromeos::bluetooth_config::DeviceImageInfo& images =
+      model_id_to_images_[model_id];
 
   if (!DeviceImageInfoHasImages(images)) {
     QP_LOG(WARNING)
@@ -131,7 +99,8 @@
     LoadPersistedImagesFromPrefs();
   }
 
-  DeviceImageInfo& images = model_id_to_images_[model_id];
+  chromeos::bluetooth_config::DeviceImageInfo& images =
+      model_id_to_images_[model_id];
 
   if (!DeviceImageInfoHasImages(images)) {
     return absl::nullopt;
@@ -149,8 +118,9 @@
       local_state->GetDictionary(kDeviceImageStorePref);
   for (std::pair<const std::string&, const base::Value&> record :
        device_image_store->DictItems()) {
-    absl::optional<DeviceImageInfo> images =
-        DeviceImageInfo::FromDictionaryValue(record.second);
+    absl::optional<chromeos::bluetooth_config::DeviceImageInfo> images =
+        chromeos::bluetooth_config::DeviceImageInfo::FromDictionaryValue(
+            record.second);
     if (!images) {
       QP_LOG(WARNING) << __func__
                       << ": Failed to load persisted images from prefs.";
@@ -166,55 +136,26 @@
          !images.right_bud_image_.empty() || !images.case_image_.empty();
 }
 
-void DeviceImageStore::DecodeImage(
-    const std::string& model_id,
-    DeviceImageType image_type,
-    const std::string& image_url,
-    FetchDeviceImagesCallback on_images_saved_callback) {
-  DCHECK(image_decoder_);
-  image_decoder_->DecodeImageFromUrl(
-      GURL(image_url), /*resize_to_notification_size=*/true,
-      base::BindOnce(&DeviceImageStore::SaveImageAsBase64,
-                     weak_ptr_factory_.GetWeakPtr(), model_id, image_type,
-                     std::move(on_images_saved_callback)));
-}
-
 void DeviceImageStore::SaveImageAsBase64(
     const std::string& model_id,
     DeviceImageType image_type,
-    FetchDeviceImagesCallback on_images_saved_callback,
+    SaveDeviceImagesCallback on_images_saved_callback,
     gfx::Image image) {
-  if (image.IsEmpty()) {
-    QP_LOG(WARNING) << __func__ << ": Failed to fetch device image.";
-    std::move(on_images_saved_callback)
-        .Run(std::make_pair(image_type, FetchDeviceImagesResult::kFailure));
-    return;
-  }
-
   // Encode the image as a base64 data URL.
   std::string encoded_image = webui::GetBitmapDataUrl(image.AsBitmap());
 
   // Save the image in the correct path.
   if (image_type == DeviceImageType::kDefault) {
     model_id_to_images_[model_id].default_image_ = encoded_image;
-  } else if (image_type == DeviceImageType::kLeftBud) {
-    model_id_to_images_[model_id].left_bud_image_ = encoded_image;
-  } else if (image_type == DeviceImageType::kRightBud) {
-    model_id_to_images_[model_id].right_bud_image_ = encoded_image;
-  } else if (image_type == DeviceImageType::kCase) {
-    model_id_to_images_[model_id].case_image_ = encoded_image;
   } else {
     QP_LOG(WARNING) << __func__
                     << ": Can't save device image to invalid image field.";
-    std::move(on_images_saved_callback)
-        .Run(std::make_pair(DeviceImageType::kNotSupportedType,
-                            FetchDeviceImagesResult::kFailure));
+    std::move(on_images_saved_callback).Run(SaveDeviceImagesResult::kFailure);
     return;
   }
 
   // Once successfully saved, return success.
-  std::move(on_images_saved_callback)
-      .Run(std::make_pair(image_type, FetchDeviceImagesResult::kSuccess));
+  std::move(on_images_saved_callback).Run(SaveDeviceImagesResult::kSuccess);
 }
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/repository/fast_pair/device_image_store.h b/ash/quick_pair/repository/fast_pair/device_image_store.h
index 3afdd7fb..55ce6d6 100644
--- a/ash/quick_pair/repository/fast_pair/device_image_store.h
+++ b/ash/quick_pair/repository/fast_pair/device_image_store.h
@@ -20,8 +20,6 @@
 namespace ash {
 namespace quick_pair {
 
-class FastPairImageDecoder;
-
 // Saves any discovered device images in a flat_map model_id_to_images_.
 // Images are saved in DeviceImageInfo objects and are loaded from prefs on
 // creation. Images can be persisted to prefs (i.e. on device pair) or
@@ -36,42 +34,30 @@
   // to keep track of pending downloads.
   // kNotSupportedType is an error state that signifies that the image is not a
   // supported type.
-  enum class DeviceImageType {
-    kNotSupportedType = 0,
-    kDefault = 1,
-    kLeftBud = 2,
-    kRightBud = 3,
-    kCase = 4
-  };
+  enum class DeviceImageType { kNotSupportedType = 0, kDefault = 1 };
 
-  // Corresponds to the status of a FetchDeviceImages call.
-  enum class FetchDeviceImagesResult {
-    kSuccess = 0,
-    kFailure = 1,
-    // Skipped refers to when an image was already saved or there is no matching
-    // URL to attempt a download.
-    kSkipped = 2
-  };
+  // Corresponds to the status of a SaveDeviceImages call.
+  enum class SaveDeviceImagesResult { kSuccess = 0, kFailure = 1 };
 
-  // Returns the type of image that was was fetched and the result, i.e.
-  // DeviceImageType::kDefault and FetchDeviceImagesResult::kSuccess after
-  // successfully saving a default image.
-  using FetchDeviceImagesCallback = base::RepeatingCallback<void(
-      std::pair<DeviceImageType, FetchDeviceImagesResult>)>;
+  // Returns the type of image that was was saved, i.e.
+  // DeviceImageInfo::DeviceImageType::kDefault for default image, on success.
+  // If images already exist or there are any errors, return empty kNone.
+  using SaveDeviceImagesCallback =
+      base::OnceCallback<void(SaveDeviceImagesResult)>;
 
   // Registers preferences used by this class in the provided |registry|.
   static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
 
-  explicit DeviceImageStore(FastPairImageDecoder* image_decoder);
+  DeviceImageStore();
   DeviceImageStore(const DeviceImageStore&) = delete;
   DeviceImageStore& operator=(const DeviceImageStore&) = delete;
   ~DeviceImageStore();
 
   // Saves the device images stored in |device_metadata| to model_id_to_images_,
   // mapped to by |model_id|, if there are images.
-  void FetchDeviceImages(const std::string& model_id,
-                         DeviceMetadata* device_metadata,
-                         FetchDeviceImagesCallback on_images_saved_callback);
+  void SaveDeviceImages(const std::string& model_id,
+                        DeviceMetadata* device_metadata,
+                        SaveDeviceImagesCallback on_images_saved_callback);
 
   // Persists the DeviceImageInfo for |model_id| in model_id_to_images_
   // to local state prefs. Returns true if images were persisted, false
@@ -96,14 +82,6 @@
   bool DeviceImageInfoHasImages(
       const chromeos::bluetooth_config::DeviceImageInfo& images) const;
 
-  // Wrapper around a call to FastPairImageDecoder's DecodeImage. Downloads
-  // and decodes the image at |image_url|, then passes the |model_id|,
-  // |image_type|, and decoded image to SaveImageAsBase64.
-  void DecodeImage(const std::string& model_id,
-                   DeviceImageType image_type,
-                   const std::string& image_url,
-                   FetchDeviceImagesCallback on_images_saved_callback);
-
   // Callee ensures |image| is not empty. Encodes |image| as a base64 data URL
   // and saves it to the DeviceImageInfo belonging to |model_id| in field
   // |image_type|. Invokes |on_images_saved_callback| with the
@@ -111,7 +89,7 @@
   // otherwise.
   void SaveImageAsBase64(const std::string& model_id,
                          DeviceImageType image_type,
-                         FetchDeviceImagesCallback on_images_saved_callback,
+                         SaveDeviceImagesCallback on_images_saved_callback,
                          gfx::Image image);
 
   // Maps from model IDs to images stored in DeviceImageInfo.
@@ -119,8 +97,6 @@
       model_id_to_images_;
   // Used to lazily load images from prefs.
   bool loaded_images_from_prefs_ = false;
-  FastPairImageDecoder* image_decoder_;
-  base::WeakPtrFactory<DeviceImageStore> weak_ptr_factory_{this};
 };
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/repository/fast_pair/device_image_store_unittest.cc b/ash/quick_pair/repository/fast_pair/device_image_store_unittest.cc
index b464899..43a132f 100644
--- a/ash/quick_pair/repository/fast_pair/device_image_store_unittest.cc
+++ b/ash/quick_pair/repository/fast_pair/device_image_store_unittest.cc
@@ -5,13 +5,10 @@
 #include "ash/quick_pair/repository/fast_pair/device_image_store.h"
 
 #include "ash/quick_pair/proto/fastpair.pb.h"
-#include "ash/quick_pair/proto/fastpair_data.pb.h"
 #include "ash/quick_pair/repository/fast_pair/device_metadata.h"
-#include "ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "base/callback_helpers.h"
-#include "base/test/gmock_callback_support.h"
 #include "base/test/mock_callback.h"
 #include "chromeos/services/bluetooth_config/public/cpp/device_image_info.h"
 #include "components/prefs/pref_service.h"
@@ -23,178 +20,64 @@
 namespace {
 
 constexpr char kTestModelId[] = "ABC123";
-constexpr char kTestLeftBudUrl[] = "left_bud";
-constexpr char kTestRightBudUrl[] = "right_bud";
-constexpr char kTestCaseUrl[] = "case";
 
 }  // namespace
 
 namespace ash {
 namespace quick_pair {
 
-using ::base::test::RunOnceCallback;
+// Alias DeviceImageInfo for convenience.
 using chromeos::bluetooth_config::DeviceImageInfo;
-using ::testing::_;
-using ::testing::Return;
 
 class DeviceImageStoreTest : public AshTestBase {
  public:
   void SetUp() override {
     AshTestBase::SetUp();
 
-    nearby::fastpair::TrueWirelessHeadsetImages true_wireless_images;
-    true_wireless_images.set_left_bud_url(kTestLeftBudUrl);
-    true_wireless_images.set_right_bud_url(kTestRightBudUrl);
-    true_wireless_images.set_case_url(kTestCaseUrl);
-
-    nearby::fastpair::Device device;
-    *device.mutable_true_wireless_images() = true_wireless_images;
-
     nearby::fastpair::GetObservedDeviceResponse response;
-    *response.mutable_device() = device;
-
     test_image_ = gfx::test::CreateImage(100, 100);
-    device_metadata_ = std::make_unique<DeviceMetadata>(response, test_image_);
+    device_metadata_ =
+        std::make_unique<DeviceMetadata>(std::move(response), test_image_);
 
-    mock_decoder_ = std::make_unique<MockFastPairImageDecoder>();
-    // On call to DecodeImage, run the third argument callback with test_image_.
-    ON_CALL(*mock_decoder_, DecodeImageFromUrl(_, _, _))
-        .WillByDefault(RunOnceCallback<2>(test_image_));
-
-    device_image_store_ =
-        std::make_unique<DeviceImageStore>(mock_decoder_.get());
+    device_image_store_ = std::make_unique<DeviceImageStore>();
   }
 
  protected:
   std::unique_ptr<DeviceMetadata> device_metadata_;
   gfx::Image test_image_;
-  std::unique_ptr<MockFastPairImageDecoder> mock_decoder_;
   std::unique_ptr<DeviceImageStore> device_image_store_;
 };
 
-TEST_F(DeviceImageStoreTest, FetchDeviceImagesValidDefaultOnly) {
-  base::MockCallback<DeviceImageStore::FetchDeviceImagesCallback> callback;
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kDefault,
-                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kLeftBud,
-                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kRightBud,
-                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kCase,
-                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
-      .Times(1);
+TEST_F(DeviceImageStoreTest, SaveDeviceImagesValid) {
+  base::MockCallback<
+      base::OnceCallback<void(DeviceImageStore::SaveDeviceImagesResult)>>
+      callback;
+  EXPECT_CALL(callback,
+              Run(DeviceImageStore::SaveDeviceImagesResult::kSuccess));
 
-  // Only include the default image in the metadata.
-  nearby::fastpair::GetObservedDeviceResponse response;
-  DeviceMetadata default_only_metadata =
-      DeviceMetadata(std::move(response), test_image_);
-  device_image_store_->FetchDeviceImages(kTestModelId, &default_only_metadata,
-                                         callback.Get());
+  device_image_store_->SaveDeviceImages(kTestModelId, device_metadata_.get(),
+                                        callback.Get());
 }
 
-TEST_F(DeviceImageStoreTest, FetchDeviceImagesInvalidDefaultOnly) {
-  base::MockCallback<DeviceImageStore::FetchDeviceImagesCallback> callback;
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kDefault,
-                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kLeftBud,
-                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kRightBud,
-                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kCase,
-                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
-      .Times(1);
+TEST_F(DeviceImageStoreTest, SaveDeviceImagesInvalidDeviceImage) {
+  base::MockCallback<
+      base::OnceCallback<void(DeviceImageStore::SaveDeviceImagesResult)>>
+      callback;
+  EXPECT_CALL(callback,
+              Run(DeviceImageStore::SaveDeviceImagesResult::kFailure));
 
-  // Include only an empty default image in the metadata.
   nearby::fastpair::GetObservedDeviceResponse response;
   DeviceMetadata empty_image_metadata =
       DeviceMetadata(std::move(response), gfx::Image());
-  device_image_store_->FetchDeviceImages(kTestModelId, &empty_image_metadata,
-                                         callback.Get());
-}
 
-TEST_F(DeviceImageStoreTest, FetchDeviceImagesValidTrueWireless) {
-  base::MockCallback<DeviceImageStore::FetchDeviceImagesCallback> callback;
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kDefault,
-                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kLeftBud,
-                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kRightBud,
-                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kCase,
-                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
-      .Times(1);
-
-  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
-                                         callback.Get());
-}
-
-TEST_F(DeviceImageStoreTest, FetchDeviceImagesInvalidTrueWireless) {
-  base::MockCallback<DeviceImageStore::FetchDeviceImagesCallback> callback;
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kDefault,
-                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kLeftBud,
-                         DeviceImageStore::FetchDeviceImagesResult::kFailure)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kRightBud,
-                         DeviceImageStore::FetchDeviceImagesResult::kFailure)))
-      .Times(1);
-  EXPECT_CALL(
-      callback,
-      Run(std::make_pair(DeviceImageStore::DeviceImageType::kCase,
-                         DeviceImageStore::FetchDeviceImagesResult::kFailure)))
-      .Times(1);
-
-  // Simulate an error during download/decode by returning an empty image.
-  ON_CALL(*mock_decoder_, DecodeImageFromUrl(_, _, _))
-      .WillByDefault(RunOnceCallback<2>(gfx::Image()));
-  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
-                                         callback.Get());
+  device_image_store_->SaveDeviceImages(kTestModelId, &empty_image_metadata,
+                                        callback.Get());
 }
 
 TEST_F(DeviceImageStoreTest, PersistDeviceImagesValid) {
   // First, save the device images to memory.
-  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
-                                         base::DoNothing());
+  device_image_store_->SaveDeviceImages(kTestModelId, device_metadata_.get(),
+                                        base::DoNothing());
   EXPECT_TRUE(device_image_store_->PersistDeviceImages(kTestModelId));
 
   // Validate that the images are persisted to prefs.
@@ -218,8 +101,8 @@
 
 TEST_F(DeviceImageStoreTest, EvictDeviceImagesValid) {
   // First, persist the device images to disk.
-  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
-                                         base::DoNothing());
+  device_image_store_->SaveDeviceImages(kTestModelId, device_metadata_.get(),
+                                        base::DoNothing());
   EXPECT_TRUE(device_image_store_->PersistDeviceImages(kTestModelId));
   EXPECT_TRUE(device_image_store_->EvictDeviceImages(kTestModelId));
 
@@ -240,8 +123,8 @@
 
 TEST_F(DeviceImageStoreTest, EvictDeviceImagesInvalidDoubleFree) {
   // First, persist the device images to disk.
-  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
-                                         base::DoNothing());
+  device_image_store_->SaveDeviceImages(kTestModelId, device_metadata_.get(),
+                                        base::DoNothing());
   EXPECT_TRUE(device_image_store_->PersistDeviceImages(kTestModelId));
   EXPECT_TRUE(device_image_store_->EvictDeviceImages(kTestModelId));
 
@@ -250,8 +133,8 @@
 }
 
 TEST_F(DeviceImageStoreTest, GetImagesForDeviceModelValid) {
-  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
-                                         base::DoNothing());
+  device_image_store_->SaveDeviceImages(kTestModelId, device_metadata_.get(),
+                                        base::DoNothing());
 
   absl::optional<DeviceImageInfo> images =
       device_image_store_->GetImagesForDeviceModel(kTestModelId);
@@ -259,19 +142,10 @@
 
   const std::string default_image = images->default_image();
   EXPECT_FALSE(default_image.empty());
-  EXPECT_EQ(default_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));
 
-  const std::string left_bud_image = images->left_bud_image();
-  EXPECT_FALSE(left_bud_image.empty());
-  EXPECT_EQ(left_bud_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));
-
-  const std::string right_bud_image = images->right_bud_image();
-  EXPECT_FALSE(right_bud_image.empty());
-  EXPECT_EQ(right_bud_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));
-
-  const std::string case_image = images->case_image();
-  EXPECT_FALSE(case_image.empty());
-  EXPECT_EQ(case_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));
+  std::string expected_encoded_image =
+      webui::GetBitmapDataUrl(test_image_.AsBitmap());
+  EXPECT_EQ(default_image, expected_encoded_image);
 }
 
 TEST_F(DeviceImageStoreTest, GetImagesForDeviceModelInvalidUninitialized) {
@@ -282,8 +156,8 @@
 }
 
 TEST_F(DeviceImageStoreTest, GetImagesForDeviceModelInvalidNotAdded) {
-  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
-                                         base::DoNothing());
+  device_image_store_->SaveDeviceImages(kTestModelId, device_metadata_.get(),
+                                        base::DoNothing());
   // Look for a model ID that wasn't added.
   absl::optional<DeviceImageInfo> images =
       device_image_store_->GetImagesForDeviceModel("DEF456");
@@ -292,32 +166,14 @@
 
 TEST_F(DeviceImageStoreTest, LoadPersistedImagesFromPrefs) {
   // First, persist the device images to disk.
-  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
-                                         base::DoNothing());
+  device_image_store_->SaveDeviceImages(kTestModelId, device_metadata_.get(),
+                                        base::DoNothing());
   device_image_store_->PersistDeviceImages(kTestModelId);
 
   // A new/restarted DeviceImageStore instance should load persisted images
   // from prefs.
-  DeviceImageStore new_device_image_store(mock_decoder_.get());
-  absl::optional<DeviceImageInfo> images =
-      new_device_image_store.GetImagesForDeviceModel(kTestModelId);
-  EXPECT_TRUE(images);
-
-  const std::string default_image = images->default_image();
-  EXPECT_FALSE(default_image.empty());
-  EXPECT_EQ(default_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));
-
-  const std::string left_bud_image = images->left_bud_image();
-  EXPECT_FALSE(left_bud_image.empty());
-  EXPECT_EQ(left_bud_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));
-
-  const std::string right_bud_image = images->right_bud_image();
-  EXPECT_FALSE(right_bud_image.empty());
-  EXPECT_EQ(right_bud_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));
-
-  const std::string case_image = images->case_image();
-  EXPECT_FALSE(case_image.empty());
-  EXPECT_EQ(case_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));
+  DeviceImageStore new_device_image_store;
+  EXPECT_TRUE(new_device_image_store.GetImagesForDeviceModel(kTestModelId));
 }
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.cc b/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.cc
index c75454c..7631a30 100644
--- a/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.cc
+++ b/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.cc
@@ -1,15 +1,118 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
+// Copyright 2021 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h"
 
+#include "ash/quick_pair/common/logging.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "components/image_fetcher/core/image_fetcher.h"
+#include "components/image_fetcher/core/request_metadata.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/data_decoder/public/cpp/decode_image.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_skia_operations.h"
+
+namespace {
+
+constexpr char kImageFetcherUmaClientName[] = "FastPair";
+
+// Needs to stay in sync with |kLargeImageMaxHeight| declared in
+// ui/message_center/views/notification_view_md.cc.
+const int kMaxNotificationHeight = 218;
+
+// TODO(crbug.com/1226117) Update policy from Nearby to Fast Pair.
+constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
+    net::DefineNetworkTrafficAnnotation("fast_pair", R"(
+        semantics {
+          sender: "Get Fast Pair Device Image Data from Google"
+          description:
+            "Fast Pair can provide device images to be used in notifications "
+            "for corresponding Fast Pair devices. For a given image url, "
+            "Google's servers will return the image data in bytes to be "
+            "futher decoded here."
+          trigger: "A notification is being triggered for a Fast Pair device."
+          data: "Image pixels and URLs. No user identifier is sent along with "
+                "the data."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: NO
+          setting:
+            "This feature is only enabled for signed-in users who enable "
+            "Nearby Share"
+          chrome_policy {
+            BrowserSignin {
+              policy_options {mode: MANDATORY}
+              BrowserSignin: 0
+            }
+          }
+        })");
+
+int CalculateScaledWidth(int width, int height) {
+  return (kMaxNotificationHeight * width) / height;
+}
+
+void ToImage(DecodeImageCallback on_image_decoded_callback,
+             const SkBitmap& bitmap) {
+  if (bitmap.empty()) {
+    QP_LOG(WARNING) << "Failed to decode image";
+    std::move(on_image_decoded_callback).Run(gfx::Image());
+    return;
+  }
+  gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
+
+  if (image.height() > kMaxNotificationHeight) {
+    image = gfx::ImageSkiaOperations::CreateResizedImage(
+        image, skia::ImageOperations::RESIZE_BEST,
+        gfx::Size(CalculateScaledWidth(image.width(), image.height()),
+                  kMaxNotificationHeight));
+  }
+
+  std::move(on_image_decoded_callback).Run(gfx::Image(image));
+}
+
+}  // namespace
+
 namespace ash {
 namespace quick_pair {
 
-FastPairImageDecoder::FastPairImageDecoder() = default;
+FastPairImageDecoder::FastPairImageDecoder(
+    std::unique_ptr<image_fetcher::ImageFetcher> fetcher)
+    : fetcher_(std::move(fetcher)) {}
 
 FastPairImageDecoder::~FastPairImageDecoder() = default;
 
+void FastPairImageDecoder::DecodeImage(
+    const GURL& image_url,
+    DecodeImageCallback on_image_decoded_callback) {
+  fetcher_->FetchImageData(
+      image_url,
+      base::BindOnce(&FastPairImageDecoder::OnImageDataFetched,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     std::move(on_image_decoded_callback)),
+      image_fetcher::ImageFetcherParams(kTrafficAnnotation,
+                                        kImageFetcherUmaClientName));
+}
+
+void FastPairImageDecoder::DecodeImage(
+    const std::vector<uint8_t>& encoded_image_bytes,
+    DecodeImageCallback on_image_decoded_callback) {
+  data_decoder::DecodeImageIsolated(
+      encoded_image_bytes, data_decoder::mojom::ImageCodec::kDefault,
+      /*shrink_to_fit=*/false, data_decoder::kDefaultMaxSizeInBytes,
+      /*desired_image_frame_size=*/gfx::Size(),
+      base::BindOnce(&ToImage, std::move(on_image_decoded_callback)));
+}
+
+void FastPairImageDecoder::OnImageDataFetched(
+    DecodeImageCallback on_image_decoded_callback,
+    const std::string& image_data,
+    const image_fetcher::RequestMetadata& request_metadata) {
+  DecodeImage(std::vector<uint8_t>(image_data.begin(), image_data.end()),
+              std::move(on_image_decoded_callback));
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h b/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h
index 766e311b..f8f1af9 100644
--- a/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h
+++ b/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h
@@ -12,6 +12,13 @@
 #include "ui/gfx/image/image.h"
 #include "url/gurl.h"
 
+using DecodeImageCallback = base::OnceCallback<void(gfx::Image)>;
+
+namespace image_fetcher {
+class ImageFetcher;
+struct RequestMetadata;
+}  // namespace image_fetcher
+
 namespace ash {
 namespace quick_pair {
 
@@ -20,19 +27,27 @@
 // given url or from given bytes of image data.
 class FastPairImageDecoder {
  public:
-  using DecodeImageCallback = base::OnceCallback<void(gfx::Image)>;
+  explicit FastPairImageDecoder(
+      std::unique_ptr<image_fetcher::ImageFetcher> fetcher);
+  FastPairImageDecoder(const FastPairImageDecoder&) = delete;
+  FastPairImageDecoder& operator=(const FastPairImageDecoder&) = delete;
+  ~FastPairImageDecoder();
 
-  FastPairImageDecoder();
-  virtual ~FastPairImageDecoder();
+  void DecodeImage(const GURL& image_url,
+                   DecodeImageCallback on_image_decoded_callback);
 
-  virtual void DecodeImageFromUrl(
-      const GURL& image_url,
-      bool resize_to_notification_size,
-      DecodeImageCallback on_image_decoded_callback) = 0;
+  void DecodeImage(const std::vector<uint8_t>& encoded_image_bytes,
+                   DecodeImageCallback on_image_decoded_callback);
 
-  virtual void DecodeImage(const std::vector<uint8_t>& encoded_image_bytes,
-                           bool resize_to_notification_size,
-                           DecodeImageCallback on_image_decoded_callback) = 0;
+ private:
+  // ImageDataFetcher callback
+  void OnImageDataFetched(
+      DecodeImageCallback on_image_decoded_callback,
+      const std::string& image_data,
+      const image_fetcher::RequestMetadata& request_metadata);
+
+  std::unique_ptr<image_fetcher::ImageFetcher> fetcher_;
+  base::WeakPtrFactory<FastPairImageDecoder> weak_ptr_factory_{this};
 };
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder_impl.cc b/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder_impl.cc
deleted file mode 100644
index 9bb2d982..0000000
--- a/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder_impl.cc
+++ /dev/null
@@ -1,135 +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/quick_pair/repository/fast_pair/fast_pair_image_decoder_impl.h"
-
-#include "ash/quick_pair/common/logging.h"
-#include "ash/quick_pair/common/quick_pair_browser_delegate.h"
-#include "base/bind.h"
-#include "base/callback.h"
-#include "components/image_fetcher/core/image_fetcher.h"
-#include "components/image_fetcher/core/image_fetcher_impl.h"
-#include "components/image_fetcher/core/request_metadata.h"
-#include "net/traffic_annotation/network_traffic_annotation.h"
-#include "services/data_decoder/public/cpp/decode_image.h"
-#include "ui/gfx/image/image_skia.h"
-#include "ui/gfx/image/image_skia_operations.h"
-
-namespace {
-
-constexpr char kImageFetcherUmaClientName[] = "FastPair";
-
-// Needs to stay in sync with |kLargeImageMaxHeight| declared in
-// ui/message_center/views/notification_view_md.cc.
-const int kMaxNotificationHeight = 218;
-
-// TODO(b/207589416) Update policy from Nearby to Fast Pair.
-constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
-    net::DefineNetworkTrafficAnnotation("fast_pair", R"(
-        semantics {
-          sender: "Get Fast Pair Device Image Data from Google"
-          description:
-            "Fast Pair can provide device images to be used in notifications "
-            "for corresponding Fast Pair devices. For a given image url, "
-            "Google's servers will return the image data in bytes to be "
-            "futher decoded here."
-          trigger: "A notification is being triggered for a Fast Pair device."
-          data: "Image pixels and URLs. No user identifier is sent along with "
-                "the data."
-          destination: GOOGLE_OWNED_SERVICE
-        }
-        policy {
-          cookies_allowed: NO
-          setting:
-            "This feature is only enabled for signed-in users who enable "
-            "Nearby Share"
-          chrome_policy {
-            BrowserSignin {
-              policy_options {mode: MANDATORY}
-              BrowserSignin: 0
-            }
-          }
-        })");
-
-int CalculateScaledWidth(int width, int height) {
-  return (kMaxNotificationHeight * width) / height;
-}
-
-void ToImage(DecodeImageCallback on_image_decoded_callback,
-             bool resize_to_notification_size,
-             const SkBitmap& bitmap) {
-  if (bitmap.empty()) {
-    QP_LOG(WARNING) << "Call to DecodeImageIsolated returned null.";
-    std::move(on_image_decoded_callback).Run(gfx::Image());
-    return;
-  }
-  gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
-
-  if (resize_to_notification_size && image.height() > kMaxNotificationHeight) {
-    image = gfx::ImageSkiaOperations::CreateResizedImage(
-        image, skia::ImageOperations::RESIZE_BEST,
-        gfx::Size(CalculateScaledWidth(image.width(), image.height()),
-                  kMaxNotificationHeight));
-  }
-
-  std::move(on_image_decoded_callback).Run(gfx::Image(image));
-}
-
-}  // namespace
-
-namespace ash {
-namespace quick_pair {
-
-FastPairImageDecoderImpl::FastPairImageDecoderImpl() = default;
-
-FastPairImageDecoderImpl::~FastPairImageDecoderImpl() = default;
-
-void FastPairImageDecoderImpl::DecodeImageFromUrl(
-    const GURL& image_url,
-    bool resize_to_notification_size,
-    DecodeImageCallback on_image_decoded_callback) {
-  if (!fetcher_ && !LoadImageFetcher()) {
-    QP_LOG(INFO) << __func__ << " Could not load image fetcher. ";
-    return;
-  }
-
-  fetcher_->FetchImageData(
-      image_url,
-      base::BindOnce(&FastPairImageDecoderImpl::OnImageDataFetched,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     std::move(on_image_decoded_callback),
-                     resize_to_notification_size),
-      image_fetcher::ImageFetcherParams(kTrafficAnnotation,
-                                        kImageFetcherUmaClientName));
-}
-
-void FastPairImageDecoderImpl::DecodeImage(
-    const std::vector<uint8_t>& encoded_image_bytes,
-    bool resize_to_notification_size,
-    DecodeImageCallback on_image_decoded_callback) {
-  data_decoder::DecodeImageIsolated(
-      encoded_image_bytes, data_decoder::mojom::ImageCodec::kDefault,
-      /*shrink_to_fit=*/false, data_decoder::kDefaultMaxSizeInBytes,
-      /*desired_image_frame_size=*/gfx::Size(),
-      base::BindOnce(&ToImage, std::move(on_image_decoded_callback),
-                     resize_to_notification_size));
-}
-
-void FastPairImageDecoderImpl::OnImageDataFetched(
-    DecodeImageCallback on_image_decoded_callback,
-    bool resize_to_notification_size,
-    const std::string& image_data,
-    const image_fetcher::RequestMetadata& request_metadata) {
-  DecodeImage(std::vector<uint8_t>(image_data.begin(), image_data.end()),
-              resize_to_notification_size,
-              std::move(on_image_decoded_callback));
-}
-
-bool FastPairImageDecoderImpl::LoadImageFetcher() {
-  fetcher_ = QuickPairBrowserDelegate::Get()->GetImageFetcher();
-  return !!fetcher_;
-}
-
-}  // namespace quick_pair
-}  // namespace ash
diff --git a/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder_impl.h b/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder_impl.h
deleted file mode 100644
index 005fe653..0000000
--- a/ash/quick_pair/repository/fast_pair/fast_pair_image_decoder_impl.h
+++ /dev/null
@@ -1,62 +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_QUICK_PAIR_REPOSITORY_FAST_PAIR_FAST_PAIR_IMAGE_DECODER_IMPL_H_
-#define ASH_QUICK_PAIR_REPOSITORY_FAST_PAIR_FAST_PAIR_IMAGE_DECODER_IMPL_H_
-
-#include <memory>
-
-#include "ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h"
-#include "base/callback.h"
-#include "base/memory/weak_ptr.h"
-#include "ui/gfx/image/image.h"
-#include "url/gurl.h"
-
-using DecodeImageCallback = base::OnceCallback<void(gfx::Image)>;
-
-namespace image_fetcher {
-class ImageFetcher;
-struct RequestMetadata;
-}  // namespace image_fetcher
-
-namespace ash {
-namespace quick_pair {
-
-// The FastPairImageDecoderImpl decodes and returns images for the device used
-// in the notifications. FastPairImageDecoderImpl can decode images from either
-// a given url or from given bytes of image data.
-class FastPairImageDecoderImpl : public FastPairImageDecoder {
- public:
-  FastPairImageDecoderImpl();
-  FastPairImageDecoderImpl(const FastPairImageDecoderImpl&) = delete;
-  FastPairImageDecoderImpl& operator=(const FastPairImageDecoderImpl&) = delete;
-  ~FastPairImageDecoderImpl() override;
-
-  void DecodeImageFromUrl(
-      const GURL& image_url,
-      bool resize_to_notification_size,
-      DecodeImageCallback on_image_decoded_callback) override;
-
-  void DecodeImage(const std::vector<uint8_t>& encoded_image_bytes,
-                   bool resize_to_notification_size,
-                   DecodeImageCallback on_image_decoded_callback) override;
-
- private:
-  // ImageDataFetcher callback
-  void OnImageDataFetched(
-      DecodeImageCallback on_image_decoded_callback,
-      bool resize_to_notification_size,
-      const std::string& image_data,
-      const image_fetcher::RequestMetadata& request_metadata);
-
-  bool LoadImageFetcher();
-
-  std::unique_ptr<image_fetcher::ImageFetcher> fetcher_;
-  base::WeakPtrFactory<FastPairImageDecoderImpl> weak_ptr_factory_{this};
-};
-
-}  // namespace quick_pair
-}  // namespace ash
-
-#endif  // ASH_QUICK_PAIR_REPOSITORY_FAST_PAIR_FAST_PAIR_IMAGE_DECODER_IMPL_H_
diff --git a/ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.cc b/ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.cc
deleted file mode 100644
index e20c533a..0000000
--- a/ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.cc
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.h"
-
-namespace ash {
-namespace quick_pair {
-
-MockFastPairImageDecoder::MockFastPairImageDecoder() = default;
-
-MockFastPairImageDecoder::~MockFastPairImageDecoder() = default;
-
-}  // namespace quick_pair
-}  // namespace ash
diff --git a/ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.h b/ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.h
deleted file mode 100644
index 7b39c191..0000000
--- a/ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.h
+++ /dev/null
@@ -1,39 +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_QUICK_PAIR_REPOSITORY_FAST_PAIR_MOCK_FAST_PAIR_IMAGE_DECODER_H_
-#define ASH_QUICK_PAIR_REPOSITORY_FAST_PAIR_MOCK_FAST_PAIR_IMAGE_DECODER_H_
-
-#include "ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace ash {
-namespace quick_pair {
-
-class MockFastPairImageDecoder : public FastPairImageDecoder {
- public:
-  MockFastPairImageDecoder();
-  MockFastPairImageDecoder(const MockFastPairImageDecoder&) = delete;
-  MockFastPairImageDecoder& operator=(const MockFastPairImageDecoder&) = delete;
-  ~MockFastPairImageDecoder() override;
-
-  MOCK_METHOD(void,
-              DecodeImageFromUrl,
-              (const GURL& image_url,
-               bool resize_to_notification_size,
-               DecodeImageCallback on_image_decoded_callback),
-              (override));
-
-  MOCK_METHOD(void,
-              DecodeImage,
-              (const std::vector<uint8_t>& encoded_image_bytes,
-               bool resize_to_notification_size,
-               DecodeImageCallback on_image_decoded_callback),
-              (override));
-};
-
-}  // namespace quick_pair
-}  // namespace ash
-
-#endif  // ASH_QUICK_PAIR_REPOSITORY_FAST_PAIR_MOCK_FAST_PAIR_IMAGE_DECODER_H_
diff --git a/ash/quick_pair/repository/fast_pair_repository_impl.cc b/ash/quick_pair/repository/fast_pair_repository_impl.cc
index 972b1ce..7e6da0f 100644
--- a/ash/quick_pair/repository/fast_pair_repository_impl.cc
+++ b/ash/quick_pair/repository/fast_pair_repository_impl.cc
@@ -11,7 +11,7 @@
 #include "ash/quick_pair/repository/fast_pair/device_id_map.h"
 #include "ash/quick_pair/repository/fast_pair/device_image_store.h"
 #include "ash/quick_pair/repository/fast_pair/device_metadata_fetcher.h"
-#include "ash/quick_pair/repository/fast_pair/fast_pair_image_decoder_impl.h"
+#include "ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h"
 #include "ash/quick_pair/repository/fast_pair/footprints_fetcher.h"
 #include "ash/quick_pair/repository/fast_pair/proto_conversions.h"
 #include "ash/quick_pair/repository/fast_pair/saved_device_registry.h"
@@ -21,6 +21,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "chromeos/services/bluetooth_config/public/cpp/device_image_info.h"
+#include "components/image_fetcher/core/image_fetcher.h"
 #include "device/bluetooth/bluetooth_device.h"
 
 namespace ash {
@@ -30,10 +31,10 @@
     : FastPairRepository(),
       device_metadata_fetcher_(std::make_unique<DeviceMetadataFetcher>()),
       footprints_fetcher_(std::make_unique<FootprintsFetcher>()),
-      image_decoder_(std::make_unique<FastPairImageDecoderImpl>()),
+      image_decoder_(std::make_unique<FastPairImageDecoder>(
+          std::unique_ptr<image_fetcher::ImageFetcher>())),
       device_id_map_(std::make_unique<DeviceIdMap>()),
-      device_image_store_(
-          std::make_unique<DeviceImageStore>(image_decoder_.get())),
+      device_image_store_(std::make_unique<DeviceImageStore>()),
       saved_device_registry_(std::make_unique<SavedDeviceRegistry>()),
       footprints_last_updated_(base::Time::UnixEpoch()) {}
 
@@ -77,7 +78,6 @@
 
   image_decoder_->DecodeImage(
       binary_data,
-      /*resize_to_notification_size=*/true,
       base::BindOnce(&FastPairRepositoryImpl::OnImageDecoded,
                      weak_ptr_factory_.GetWeakPtr(), normalized_model_id,
                      std::move(callback), *response));
@@ -260,8 +260,8 @@
   QP_LOG(INFO) << __func__
                << ": Completing fetching device images for model ID "
                << hex_model_id;
-  device_image_store_->FetchDeviceImages(hex_model_id, device_metadata,
-                                         base::DoNothing());
+  device_image_store_->SaveDeviceImages(hex_model_id, device_metadata,
+                                        base::DoNothing());
 }
 
 bool FastPairRepositoryImpl::PersistDeviceImages(scoped_refptr<Device> device) {
diff --git a/ash/webui/OWNERS b/ash/webui/OWNERS
index ac65600..a7791a94 100644
--- a/ash/webui/OWNERS
+++ b/ash/webui/OWNERS
@@ -1,3 +1,5 @@
 # Code is migrating here from //chromeos/components, so share owners.
 # See go/lacros-directory-migration
 file://chromeos/components/OWNERS
+
+file://ash/webui/PLATFORM_OWNERS
\ No newline at end of file
diff --git a/ash/webui/system_apps/PLATFORM_OWNERS b/ash/webui/PLATFORM_OWNERS
similarity index 100%
rename from ash/webui/system_apps/PLATFORM_OWNERS
rename to ash/webui/PLATFORM_OWNERS
diff --git a/ash/webui/camera_app_ui/resources/js/device/device_info_updater.ts b/ash/webui/camera_app_ui/resources/js/device/device_info_updater.ts
index ff4d35b..2fa0233 100644
--- a/ash/webui/camera_app_ui/resources/js/device/device_info_updater.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/device_info_updater.ts
@@ -111,10 +111,10 @@
           this.pendingDevicesInfo.map((d) => assertExists(d.v3Info));
       this.photoPreferrer.updateDevicesInfo(this.camera3DevicesInfo);
       this.videoPreferrer.updateDevicesInfo(this.camera3DevicesInfo);
-      this.deviceChangeListeners.forEach((l) => l(this));
     } else {
       this.camera3DevicesInfo = null;
     }
+    this.deviceChangeListeners.forEach((l) => l(this));
   }
 
   /**
diff --git a/ash/webui/camera_app_ui/resources/js/js.gni b/ash/webui/camera_app_ui/resources/js/js.gni
index aba5c65..f17c591 100644
--- a/ash/webui/camera_app_ui/resources/js/js.gni
+++ b/ash/webui/camera_app_ui/resources/js/js.gni
@@ -72,6 +72,7 @@
   "views/camera_intent.ts",
   "views/camera.ts",
   "views/camera/camera_manager.ts",
+  "views/camera/camera_operation.ts",
   "views/camera/layout.ts",
   "views/camera/mode/index.ts",
   "views/camera/mode/mode_base.ts",
@@ -86,6 +87,7 @@
   "views/camera/preview.ts",
   "views/camera/scan_options.ts",
   "views/camera/timertick.ts",
+  "views/camera/type.ts",
   "views/camera/video_encoder_options.ts",
   "views/crop_document.ts",
   "views/dialog.ts",
diff --git a/ash/webui/camera_app_ui/resources/js/main.ts b/ash/webui/camera_app_ui/resources/js/main.ts
index 4cf068e..cbd85e6 100644
--- a/ash/webui/camera_app_ui/resources/js/main.ts
+++ b/ash/webui/camera_app_ui/resources/js/main.ts
@@ -76,11 +76,11 @@
     this.intent = intent;
 
     this.photoPreferrer = new PhotoConstraintsPreferrer(async () => {
-      await this.cameraView.start();
+      await this.cameraView.cameraManager.reconfigure();
     });
 
     this.videoPreferrer = new VideoConstraintsPreferrer(async () => {
-      await this.cameraView.start();
+      await this.cameraView.cameraManager.reconfigure();
     });
 
     this.infoUpdater =
@@ -96,7 +96,8 @@
       } else {
         return new Camera(
             this.galleryButton, this.infoUpdater, this.photoPreferrer,
-            this.videoPreferrer, mode, this.perfLogger, facing);
+            this.videoPreferrer, this.perfLogger, facing,
+            /* modeConstraints= */ {default: mode});
       }
     })();
 
@@ -250,8 +251,8 @@
       } else {
         // CCA must get camera usage for completing its initialization when
         // first launched.
-        await this.cameraView.initialize();
         notifyCameraResourceReady();
+        await this.cameraView.initialize();
         cameraResourceInitialized.signal();
       }
     };
@@ -264,10 +265,11 @@
 
     const startCamera = (async () => {
       await cameraResourceInitialized.wait();
-      const isSuccess = await this.cameraView.start();
+      const isSuccess = await this.cameraView.cameraManager.requestResume();
 
       if (isSuccess) {
-        const aspectRatio = this.cameraView.getPreviewAspectRatio();
+        const {aspectRatio} =
+            this.cameraView.cameraManager.getPreviewResolution();
         const {width, height} = getDefaultWindowSize(aspectRatio);
         window.resizeTo(width, height);
       }
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera.ts b/ash/webui/camera_app_ui/resources/js/views/camera.ts
index 93ae445f..dce69eb 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera.ts
@@ -6,15 +6,12 @@
 import {
   assert,
   assertInstanceof,
-  assertString,
 } from '../assert.js';
-import {Camera3DeviceInfo} from '../device/camera3_device_info.js';
 import {
   PhotoConstraintsPreferrer,
   VideoConstraintsPreferrer,
 } from '../device/constraints_preferrer.js';
 import {DeviceInfoUpdater} from '../device/device_info_updater.js';
-import {StreamConstraints} from '../device/stream_constraints.js';
 import * as dom from '../dom.js';
 import * as error from '../error.js';
 import {Flag} from '../flag.js';
@@ -54,21 +51,16 @@
 import {Layout} from './camera/layout.js';
 import {
   getDefaultScanCorners,
-  Modes,
-  PhotoHandler,
   PhotoResult,
-  ScanHandler,
   setAvc1Parameters,
-  Video,
-  VideoHandler,
   VideoResult,
 } from './camera/mode/index.js';
 import {PortraitResult} from './camera/mode/portrait.js';
 import {GifResult} from './camera/mode/video.js';
 import {Options} from './camera/options.js';
-import {Preview} from './camera/preview.js';
 import {ScanOptions} from './camera/scan_options.js';
 import * as timertick from './camera/timertick.js';
+import {CameraViewUI, ModeConstraints} from './camera/type.js';
 import {VideoEncoderOptions} from './camera/video_encoder_options.js';
 import {CropDocument} from './crop_document.js';
 import {Dialog} from './dialog.js';
@@ -79,31 +71,9 @@
 import {WarningType} from './warning.js';
 
 /**
- * Thrown when app window suspended during stream reconfiguration.
- */
-class CameraSuspendedError extends Error {
-  /**
-   * @param message Error message.
-   */
-  constructor(message = 'Camera suspended.') {
-    super(message);
-    this.name = this.constructor.name;
-  }
-}
-
-interface ConfigureCandidate {
-  deviceId: string;
-  mode: Mode;
-  captureResolution: Resolution;
-  constraints: StreamConstraints;
-  videoSnapshotResolution: Resolution;
-}
-
-/**
  * Camera-view controller.
  */
-export class Camera extends View implements VideoHandler, PhotoHandler,
-                                            ScanHandler {
+export class Camera extends View implements CameraViewUI {
   private readonly cropDocument = new CropDocument();
   private readonly docModeDialogView =
       new Dialog(ViewName.DOCUMENT_MODE_DIALOG);
@@ -116,16 +86,6 @@
 
   private readonly scanOptions: ScanOptions;
 
-  /**
-   * Video preview for the camera.
-   */
-  private readonly preview: Preview;
-
-  /**
-   * Options for the camera.
-   */
-  private readonly options: Options;
-
   readonly cameraManager: CameraManager;
 
   private readonly videoEncoderOptions =
@@ -138,25 +98,14 @@
   private outputVideoRotation = 0;
 
   /**
-   * The preferred device id for camera reconfiguration.
-   */
-  private deviceId: string|null = null;
-
-  /**
    * Device id of video device of active preview stream. Sets to null when
    * preview become inactive.
    */
   private activeDeviceId: string|null = null;
 
-  /**
-   * Modes for the camera.
-   */
-  private readonly modes: Modes;
-
   protected readonly review = new review.Review();
   protected facingMode: Facing;
   protected shutterType = metrics.ShutterType.UNKNOWN;
-  private retryStartTimeout: number|null = null;
 
   /**
    * Promise for the camera stream configuration process. It's resolved to
@@ -172,22 +121,15 @@
   private take: Promise<void>|null = null;
 
   private readonly openPTZPanel = dom.get('#open-ptz-panel', HTMLButtonElement);
-  private readonly configureCompleteListeners = new Set<() => void>();
-
-  /**
-   * Preview constraints saved for temporarily close/restore preview
-   * before/after |ScanHandler| review document result.
-   */
-  private constraints: StreamConstraints|null = null;
 
   constructor(
       protected readonly resultSaver: ResultSaver,
       private readonly infoUpdater: DeviceInfoUpdater,
       photoPreferrer: PhotoConstraintsPreferrer,
       videoPreferrer: VideoConstraintsPreferrer,
-      protected readonly defaultMode: Mode,
-      private readonly perfLogger: PerfLogger,
+      readonly perfLogger: PerfLogger,
       facing: Facing|null,
+      modeConstraints: ModeConstraints,
   ) {
     super(ViewName.CAMERA);
 
@@ -200,20 +142,19 @@
       new View(ViewName.FLASH),
     ];
 
-    this.cameraManager = new CameraManager(() => this.start());
+    this.cameraManager = new CameraManager(
+        infoUpdater, perfLogger, photoPreferrer, videoPreferrer, this,
+        modeConstraints);
 
-    this.preview = new Preview(this.cameraManager, async () => {
-      await this.start();
-    });
+    this.scanOptions = new ScanOptions(this.cameraManager);
 
-    this.scanOptions =
-        new ScanOptions((point) => this.preview.setPointOfInterest(point));
+    // Options for the camera.
+    // Put it here for it controls the UI visually under camera view but it
+    // currently won't interact with the view. To prevent typescript checker
+    // complainting about the unused reference, it's left here without any
+    // reference point to it.
+    new Options(this.infoUpdater, this.cameraManager);
 
-    this.options = new Options(infoUpdater, () => this.switchCamera());
-
-    this.modes = new Modes(
-        this.defaultMode, photoPreferrer, videoPreferrer, () => this.start(),
-        this);
 
     this.facingMode = facing ?? Facing.NOT_SET;
 
@@ -249,14 +190,12 @@
 
     dom.get('#video-snapshot', HTMLButtonElement)
         .addEventListener('click', () => {
-          const videoMode = assertInstanceof(this.modes.current, Video);
-          videoMode.takeSnapshot();
+          this.cameraManager.takeVideoSnapshot();
         });
 
     const pauseShutter = dom.get('#pause-recordvideo', HTMLButtonElement);
     pauseShutter.addEventListener('click', () => {
-      const videoMode = assertInstanceof(this.modes.current, Video);
-      videoMode.togglePaused();
+      this.cameraManager.toggleVideoRecordingPause();
     });
 
     // TODO(shik): Tune the timing for playing video shutter button animation.
@@ -274,6 +213,12 @@
       offLabel: I18nString.RECORD_VIDEO_PAUSE_BUTTON,
     });
 
+    this.cameraManager.registerCameraUI({
+      onConfigureComplete: () => {
+        nav.close(ViewName.WARNING, WarningType.NO_CAMERA);
+        this.updateActiveCamera(this.cameraManager.getDeviceId());
+      },
+    });
     this.initOpenPTZPanel();
   }
 
@@ -284,10 +229,10 @@
     await this.cameraManager.initialize();
 
     state.addObserver(state.State.ENABLE_FULL_SIZED_VIDEO_SNAPSHOT, () => {
-      this.start();
+      this.cameraManager.reconfigure();
     });
     state.addObserver(state.State.ENABLE_MULTISTREAM_RECORDING, () => {
-      this.start();
+      this.cameraManager.reconfigure();
     });
 
     this.initVideoEncoderOptions();
@@ -297,9 +242,9 @@
   private initOpenPTZPanel() {
     this.openPTZPanel.addEventListener('click', () => {
       nav.open(ViewName.PTZ_PANEL, new PTZPanelOptions({
-                 stream: this.preview.stream,
-                 vidPid: this.preview.getVidPid(),
-                 resetPTZ: () => this.preview.resetPTZ(),
+                 stream: this.cameraManager.getPreviewVideo().getStream(),
+                 vidPid: this.cameraManager.getVidPid(),
+                 resetPTZ: () => this.cameraManager.resetPTZ(),
                }));
       highlight(false);
     });
@@ -319,28 +264,32 @@
       newFeatureToast.focus();
     };
 
-    this.addConfigureCompleteListener(async () => {
-      const ptzToastKey = 'isPTZToastShown';
-      if (!state.get(state.State.ENABLE_PTZ) ||
-          state.get(state.State.IS_NEW_FEATURE_TOAST_SHOWN) ||
-          localStorage.getBool(ptzToastKey)) {
-        highlight(false);
-        return;
-      }
-      localStorage.set(ptzToastKey, true);
-      state.set(state.State.IS_NEW_FEATURE_TOAST_SHOWN, true);
-      highlight(true);
+    this.cameraManager.registerCameraUI({
+      onConfigureComplete: () => {
+        const ptzToastKey = 'isPTZToastShown';
+        if (!state.get(state.State.ENABLE_PTZ) ||
+            state.get(state.State.IS_NEW_FEATURE_TOAST_SHOWN) ||
+            localStorage.getBool(ptzToastKey)) {
+          highlight(false);
+          return;
+        }
+        localStorage.set(ptzToastKey, true);
+        state.set(state.State.IS_NEW_FEATURE_TOAST_SHOWN, true);
+        highlight(true);
+      },
     });
   }
 
   private initVideoEncoderOptions() {
     const options = this.videoEncoderOptions;
-    this.addConfigureCompleteListener(() => {
-      if (state.get(Mode.VIDEO)) {
-        const {width, height, frameRate} =
-            this.preview.stream.getVideoTracks()[0].getSettings();
-        options.updateValues(new Resolution(width, height), frameRate);
-      }
+    this.cameraManager.registerCameraUI({
+      onConfigureComplete: () => {
+        if (state.get(Mode.VIDEO)) {
+          const {width, height, frameRate} =
+              this.cameraManager.getPreviewVideo().getVideoSettings();
+          options.updateValues(new Resolution(width, height), frameRate);
+        }
+      },
     });
     options.initialize();
   }
@@ -372,18 +321,18 @@
 
     const docModeDialogKey = 'isDocModeDialogShown';
     if (!localStorage.getBool(docModeDialogKey)) {
-      const checkShowDialog = () => {
-        if (!state.get(Mode.SCAN) ||
-            !this.scanOptions.isDocumentModeEanbled()) {
-          return;
-        }
-        this.removeConfigureCompleteListener(checkShowDialog);
-        localStorage.set(docModeDialogKey, true);
-        const message = loadTimeData.getI18nMessage(
-            I18nString.DOCUMENT_MODE_DIALOG_INTRO_TITLE);
-        nav.open(ViewName.DOCUMENT_MODE_DIALOG, {message});
-      };
-      this.addConfigureCompleteListener(checkShowDialog);
+      this.cameraManager.registerCameraUI({
+        onConfigureComplete: () => {
+          if (localStorage.getBool(docModeDialogKey) || !state.get(Mode.SCAN) ||
+              !this.scanOptions.isDocumentModeEanbled()) {
+            return;
+          }
+          localStorage.set(docModeDialogKey, true);
+          const message = loadTimeData.getI18nMessage(
+              I18nString.DOCUMENT_MODE_DIALOG_INTRO_TITLE);
+          nav.open(ViewName.DOCUMENT_MODE_DIALOG, {message});
+        },
+      });
     }
 
     // When entering document mode, refocus to shutter button for letting user
@@ -400,14 +349,6 @@
     this.scanOptions.onChange = checkRefocus;
   }
 
-  private addConfigureCompleteListener(listener: () => void) {
-    this.configureCompleteListeners.add(listener);
-  }
-
-  private removeConfigureCompleteListener(listener: () => void) {
-    this.configureCompleteListeners.delete(listener);
-  }
-
   getSubViews(): View[] {
     return this.subViews;
   }
@@ -465,7 +406,7 @@
         // what we need to rotate the captured video with.
         this.outputVideoRotation = (360 - cameraFrameRotation) % 360;
         await timertick.start();
-        const captureDone = await this.modes.current.startCapture();
+        const captureDone = await this.cameraManager.startCapture();
         await captureDone();
       } catch (e) {
         hasError = true;
@@ -491,15 +432,10 @@
    */
   private endTake(): Promise<void> {
     timertick.cancel();
-    this.modes.current.stopCapture();
+    this.cameraManager.stopCapture();
     return Promise.resolve(this.take);
   }
 
-  getPreviewAspectRatio(): number {
-    const {videoWidth, videoHeight} = this.preview.getVideoElement();
-    return videoWidth / videoHeight;
-  }
-
   private async checkPhotoResult<T>(pendingPhotoResult: Promise<T>):
       Promise<T> {
     try {
@@ -642,17 +578,11 @@
   protected async prepareReview(doReview: () => Promise<void>): Promise<void> {
     // Because the review view will cover the whole camera view, prepare for
     // temporarily turn off camera by stopping preview.
-    this.constraints = this.preview.getConstraints();
-    await this.preview.close();
-    await this.scanOptions.detachPreview();
+    await this.cameraManager.requestSuspend();
     try {
       await doReview();
     } finally {
-      assert(this.constraints !== null);
-      await this.modes.prepareDevice();
-      await this.preview.open(this.constraints);
-      this.modes.current.updatePreview(this.preview.getVideo());
-      await this.scanOptions.attachPreview(this.preview.getVideoElement());
+      await this.cameraManager.requestResume();
     }
   }
 
@@ -804,15 +734,9 @@
     return this.resultSaver.startSaveVideo(this.outputVideoRotation);
   }
 
-  getPreviewVideo(): HTMLVideoElement {
-    const video = this.preview.getVideoElement();
-    assertInstanceof(video, HTMLVideoElement);
-    return video;
-  }
-
   playShutterEffect(): void {
     sound.play(dom.get('#sound-shutter', HTMLAudioElement));
-    animate.play(this.preview.getVideoElement());
+    animate.play(this.cameraManager.getPreviewVideo().video);
   }
 
   async onGifCaptureDone({name, gifSaver, resolution, duration}: GifResult):
@@ -896,7 +820,8 @@
 
   handlingKey(key: string): boolean {
     if (key === 'Ctrl-R') {
-      toast.showDebugMessage(this.preview.toString());
+      toast.showDebugMessage(
+          this.cameraManager.getPreviewResolution().toString());
       return true;
     }
     if ((key === 'AudioVolumeUp' || key === 'AudioVolumeDown') &&
@@ -911,164 +836,6 @@
     return false;
   }
 
-  /**
-   * Switches to the next available camera device.
-   */
-  private switchCamera(): Promise<void>|null {
-    if (state.get(PerfEvent.CAMERA_SWITCHING) ||
-        state.get(state.State.CAMERA_CONFIGURING) ||
-        !state.get(state.State.STREAMING) || state.get(state.State.TAKING)) {
-      return null;
-    }
-    state.set(PerfEvent.CAMERA_SWITCHING, true);
-    const devices = this.infoUpdater.getDevicesInfo();
-    let index = devices.findIndex((entry) => entry.deviceId === this.deviceId);
-    if (index === -1) {
-      index = 0;
-    }
-    if (devices.length > 0) {
-      index = (index + 1) % devices.length;
-      this.deviceId = devices[index].deviceId;
-    }
-    return (async () => {
-      const isSuccess = await this.start();
-      state.set(PerfEvent.CAMERA_SWITCHING, false, {hasError: !isSuccess});
-    })();
-  }
-
-  protected async getModeCandidates(deviceId: string|null): Promise<Mode[]> {
-    const supportedModes = await this.modes.getSupportedModes(deviceId);
-    return this.modes.getModeCandidates().filter(
-        (m) => supportedModes.includes(m));
-  }
-
-  /**
-   * Gets the video device ids sorted by preference.
-   */
-  private getDeviceIdCandidates(): string[] {
-    let devices: Array<Camera3DeviceInfo|MediaDeviceInfo>;
-    /**
-     * Object mapping from device id to facing. Set to null for fake cameras.
-     */
-    let facings: Record<string, Facing>|null = null;
-
-    const camera3Info = this.infoUpdater.getCamera3DevicesInfo();
-    if (camera3Info) {
-      devices = camera3Info;
-      facings = {};
-      for (const {deviceId, facing} of camera3Info) {
-        facings[deviceId] = facing;
-      }
-    } else {
-      devices = this.infoUpdater.getDevicesInfo();
-    }
-
-    const preferredFacing = this.facingMode === Facing.NOT_SET ?
-        util.getDefaultFacing() :
-        this.facingMode;
-    // Put the selected video device id first.
-    const sorted = devices.map((device) => device.deviceId).sort((a, b) => {
-      if (a === b) {
-        return 0;
-      }
-      if (this.deviceId ? a === this.deviceId :
-                          (facings && facings[a] === preferredFacing)) {
-        return -1;
-      }
-      return 1;
-    });
-    return sorted;
-  }
-
-  private async *
-      getConfigurationCandidates(): AsyncIterable<ConfigureCandidate> {
-    const deviceOperator = await DeviceOperator.getInstance();
-
-    for (const deviceId of this.getDeviceIdCandidates()) {
-      for (const mode of await this.getModeCandidates(deviceId)) {
-        let resolCandidates;
-        let photoRs;
-        if (deviceOperator !== null) {
-          resolCandidates = this.modes.getResolutionCandidates(mode, deviceId);
-          photoRs = await deviceOperator.getPhotoResolutions(deviceId);
-        } else {
-          resolCandidates =
-              this.modes.getFakeResolutionCandidates(mode, deviceId);
-          photoRs = resolCandidates.map((c) => c.resolution);
-        }
-        const maxResolution =
-            photoRs.reduce((maxR, r) => r.area > maxR.area ? r : maxR);
-        for (const {
-               resolution: captureResolution,
-               previewCandidates,
-             } of resolCandidates) {
-          const videoSnapshotResolution =
-              state.get(state.State.ENABLE_FULL_SIZED_VIDEO_SNAPSHOT) ?
-              maxResolution :
-              captureResolution;
-          for (const constraints of previewCandidates) {
-            yield {
-              deviceId,
-              mode,
-              captureResolution,
-              constraints,
-              videoSnapshotResolution,
-            };
-          }
-        }
-      }
-    }
-  }
-
-  /**
-   * Stops camera and tries to start camera stream again if possible.
-   * @return Promise resolved to whether start camera successfully.
-   */
-  async start(): Promise<boolean> {
-    // To prevent multiple callers enter this function at the same time, wait
-    // until previous caller resets configuring to null.
-    while (this.configuring !== null) {
-      if (!await this.configuring) {
-        // Retry will be kicked out soon.
-        return false;
-      }
-    }
-    state.set(state.State.CAMERA_CONFIGURING, true);
-    this.configuring = (async () => {
-      try {
-        if (state.get(state.State.TAKING)) {
-          await this.endTake();
-        }
-      } finally {
-        await this.stopStreams();
-      }
-      return this.startInternal();
-    })();
-    return this.configuring;
-  }
-
-  /**
-   * Checks if PTZ can be enabled.
-   */
-  private async checkEnablePTZ(c: ConfigureCandidate): Promise<void> {
-    const enablePTZ = await (async () => {
-      if (!this.preview.isSupportPTZ()) {
-        return false;
-      }
-      const modeSupport = state.get(state.State.USE_FAKE_CAMERA) ||
-          this.modes.isSupportPTZ(
-              c.mode,
-              c.captureResolution,
-              this.preview.getResolution(),
-          );
-      if (!modeSupport) {
-        await this.preview.resetPTZ();
-        return false;
-      }
-      return true;
-    })();
-    state.set(state.State.ENABLE_PTZ, enablePTZ);
-  }
 
   /**
    * Updates |this.activeDeviceId|.
@@ -1084,120 +851,4 @@
       speak(I18nString.STATUS_MSG_CAMERA_SWITCHED, info.label);
     }
   }
-
-  /**
-   * Starts camera configuration process.
-   * @return Resolved to boolean for whether the configuration is succeeded or
-   *     kicks out another round of reconfiguration.
-   */
-  private async startInternal(): Promise<boolean> {
-    try {
-      await this.infoUpdater.lockDeviceInfo(async () => {
-        if (this.cameraManager.shouldSuspended()) {
-          throw new CameraSuspendedError();
-        }
-        const congfigureSucceed = await (async () => {
-          const deviceOperator = await DeviceOperator.getInstance();
-          state.set(state.State.USE_FAKE_CAMERA, deviceOperator === null);
-
-          for await (const c of this.getConfigurationCandidates()) {
-            if (this.cameraManager.shouldSuspended()) {
-              throw new CameraSuspendedError();
-            }
-            this.modes.setCaptureParams(
-                c.mode, c.constraints, c.captureResolution,
-                c.videoSnapshotResolution);
-            try {
-              await this.modes.prepareDevice();
-              const factory = this.modes.getModeFactory(c.mode);
-              const stream = await this.preview.open(c.constraints);
-              this.facingMode = this.preview.getFacing();
-              const currentDeviceId = assertString(this.preview.getDeviceId());
-
-              await this.checkEnablePTZ(c);
-              this.options.updateValues(
-                  stream, currentDeviceId, this.facingMode);
-              factory.setPreviewVideo(this.preview.getVideo());
-              factory.setFacing(this.facingMode);
-              await this.modes.updateModeSelectionUI(c.deviceId);
-              await this.modes.updateMode(
-                  factory, stream, this.facingMode, c.deviceId);
-              await this.scanOptions.attachPreview(
-                  this.preview.getVideoElement());
-              for (const l of this.configureCompleteListeners) {
-                l();
-              }
-              nav.close(ViewName.WARNING, WarningType.NO_CAMERA);
-              this.updateActiveCamera(currentDeviceId);
-
-              return true;
-            } catch (e) {
-              await this.stopStreams();
-
-              let errorToReport = e;
-              // Since OverconstrainedError is not an Error instance.
-              if (e instanceof OverconstrainedError) {
-                errorToReport =
-                    new Error(`${e.message} (constraint = ${e.constraint})`);
-                errorToReport.name = 'OverconstrainedError';
-              } else if (e.name === 'NotReadableError') {
-                // TODO(b/187879603): Remove this hacked once we understand more
-                // about such error.
-                // We cannot get the camera facing from stream since it might
-                // not be successfully opened. Therefore, we asked the camera
-                // facing via Mojo API.
-                let facing = Facing.NOT_SET;
-                if (deviceOperator !== null) {
-                  facing = await deviceOperator.getCameraFacing(c.deviceId);
-                }
-                errorToReport = new Error(`${e.message} (facing = ${facing})`);
-                errorToReport.name = 'NotReadableError';
-              }
-              error.reportError(
-                  ErrorType.START_CAMERA_FAILURE, ErrorLevel.ERROR,
-                  assertInstanceof(errorToReport, Error));
-            }
-          }
-
-          return false;
-        })();
-
-        if (!congfigureSucceed) {
-          throw new CameraSuspendedError();
-        }
-      });
-      this.configuring = null;
-      state.set(state.State.CAMERA_CONFIGURING, false);
-
-      return true;
-    } catch (e) {
-      this.activeDeviceId = null;
-      if (!(e instanceof CameraSuspendedError)) {
-        error.reportError(
-            ErrorType.START_CAMERA_FAILURE, ErrorLevel.ERROR,
-            assertInstanceof(e, Error));
-        nav.open(ViewName.WARNING, WarningType.NO_CAMERA);
-      }
-      // Schedule to retry.
-      if (this.retryStartTimeout) {
-        clearTimeout(this.retryStartTimeout);
-        this.retryStartTimeout = null;
-      }
-      this.retryStartTimeout = setTimeout(() => {
-        this.configuring = this.startInternal();
-      }, 100);
-
-      this.perfLogger.interrupt();
-      return false;
-    }
-  }
-
-  /**
-   * Stop extra stream and preview stream.
-   */
-  private async stopStreams() {
-    await this.modes.clear();
-    await this.preview.close();
-    await this.scanOptions.detachPreview();
-  }
 }
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/camera_manager.ts b/ash/webui/camera_app_ui/resources/js/views/camera/camera_manager.ts
index a51434b..68ac3abf 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera/camera_manager.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/camera_manager.ts
@@ -5,22 +5,73 @@
 import {
   assertInstanceof,
 } from '../../assert.js';
+import {
+  PhotoConstraintsPreferrer,
+  VideoConstraintsPreferrer,
+} from '../../device/constraints_preferrer.js';
+import {DeviceInfoUpdater} from '../../device/device_info_updater.js';
 import * as error from '../../error.js';
+import {Point} from '../../geometry.js';
 import {ChromeHelper} from '../../mojo/chrome_helper.js';
 import {ScreenState} from '../../mojo/type.js';
+import * as nav from '../../nav.js';
+import {PerfLogger} from '../../perf.js';
 import * as state from '../../state.js';
 import {
   ErrorLevel,
   ErrorType,
+  Facing,
   Mode,
+  PerfEvent,
+  PreviewVideo,
+  Resolution,
+  ViewName,
 } from '../../type.js';
+import * as util from '../../util.js';
+import {WaitableEvent} from '../../waitable_event.js';
 import {windowController} from '../../window_controller.js';
+import {WarningType} from '.././warning.js';
+
+import {EventListener, OperationScheduler} from './camera_operation.js';
+import {Preview} from './preview.js';
+import {CameraViewUI, ModeConstraints} from './type.js';
+
+export interface CameraUI {
+  onConfigureComplete(): void|Promise<void>;
+}
+
+class ResumeStateWatchdog {
+  private trialDone: WaitableEvent<boolean>;
+  private succeed = false;
+
+  constructor(private readonly doReconfigure: () => Promise<boolean>) {
+    this.start();
+  }
+
+  private async start() {
+    while (!this.succeed) {
+      this.trialDone = new WaitableEvent<boolean>();
+      await util.sleep(100);
+      this.succeed = await this.doReconfigure();
+      this.trialDone.signal(this.succeed);
+    }
+  }
+
+  /**
+   * Waits for the next unfinished reconfigure result.
+   *
+   * @return The reconfigure is succeed or failed.
+   */
+  async waitNextReconfigure(): Promise<boolean> {
+    return this.trialDone.wait();
+  }
+}
 
 /**
  * Manges usage of all camera operations.
  * TODO(b/209726472): Move more camera logic in camera view to here.
  */
-export class CameraManager {
+export class CameraManager implements EventListener {
   private hasExternalScreen = false;
   private screenOffAuto = false;
   /**
@@ -36,13 +87,42 @@
 
   private suspendRequested = false;
 
-  constructor(private readonly doReconfiguration: () => Promise<boolean>) {
+  private readonly scheduler: OperationScheduler;
+
+  private watchdog: ResumeStateWatchdog|null = null;
+
+  private readonly cameraUIs: CameraUI[] = [];
+
+  private readonly preview: Preview;
+
+  constructor(
+      private readonly infoUpdater: DeviceInfoUpdater,
+      private readonly perfLogger: PerfLogger,
+      photoPreferrer: PhotoConstraintsPreferrer,
+      videoPreferrer: VideoConstraintsPreferrer,
+      cameraViewUI: CameraViewUI,
+      modeConstraints: ModeConstraints,
+  ) {
+    this.preview = new Preview(() => this.lastScreenOnTime, async () => {
+      await this.reconfigure();
+    });
+
+    this.scheduler = new OperationScheduler(
+        this.infoUpdater,
+        {onConfigureComplete: () => this.onConfigureComplete()},
+        this.preview,
+        cameraViewUI,
+        photoPreferrer,
+        videoPreferrer,
+        modeConstraints,
+    );
+
     // Monitor the states to stop camera when locked/minimized.
     const idleDetector = new IdleDetector();
     idleDetector.addEventListener('change', () => {
       this.locked = idleDetector.screenState === 'locked';
       if (this.locked) {
-        this.doReconfiguration();
+        this.reconfigure();
       }
     });
     idleDetector.start().catch((e) => {
@@ -54,11 +134,52 @@
     document.addEventListener('visibilitychange', () => {
       const recording = state.get(state.State.TAKING) && state.get(Mode.VIDEO);
       if (this.isTabletBackground() && !recording) {
-        this.doReconfiguration();
+        this.reconfigure();
       }
     });
   }
 
+  getDeviceId(): string {
+    return this.scheduler.reconfigurer.deviceId;
+  }
+
+  getFacing(): Facing {
+    return this.scheduler.reconfigurer.facing;
+  }
+
+  getPreviewVideo(): PreviewVideo {
+    return this.preview.getVideo();
+  }
+
+  getAudioTrack(): MediaStreamTrack {
+    return this.getPreviewVideo().getStream().getAudioTracks()[0];
+  }
+
+  /**
+   * USB camera vid:pid identifier of the opened stream.
+   *
+   * @return Identifier formatted as "vid:pid" or null for non-USB camera.
+   */
+  getVidPid(): string|null {
+    return this.preview.getVidPid();
+  }
+
+  getPreviewResolution(): Resolution {
+    const {video} = this.getPreviewVideo();
+    const {videoWidth, videoHeight} = video;
+    return new Resolution(videoWidth, videoHeight);
+  }
+
+  async onConfigureComplete(): Promise<void> {
+    for (const ui of this.cameraUIs) {
+      await ui.onConfigureComplete();
+    }
+  }
+
+  registerCameraUI(ui: CameraUI): void {
+    this.cameraUIs.push(ui);
+  }
+
   /**
    * @return Whether window is put to background in tablet mode.
    */
@@ -84,7 +205,7 @@
 
     const handleScreenStateChange = () => {
       if (this.screenOff) {
-        this.doReconfiguration();
+        this.reconfigure();
       } else {
         this.lastScreenOnTime = performance.now();
       }
@@ -110,28 +231,127 @@
     const hasExternalScreen =
         await helper.initExternalScreenMonitor(updateExternalScreen);
     updateExternalScreen(hasExternalScreen);
-  }
 
-  getLastScreenOnTime(): number {
-    return this.lastScreenOnTime;
+    await this.scheduler.initialize();
   }
 
   requestSuspend(): Promise<boolean> {
     state.set(state.State.SUSPEND, true);
     this.suspendRequested = true;
-    return this.doReconfiguration();
+    return this.reconfigure();
   }
 
-  requestResume(): void {
+  requestResume(): Promise<boolean> {
     state.set(state.State.SUSPEND, false);
     this.suspendRequested = false;
+    if (this.watchdog !== null) {
+      return this.watchdog.waitNextReconfigure();
+    }
+    return this.reconfigure();
+  }
+
+  /**
+   * Switches to the next available camera device.
+   */
+  switchCamera(): Promise<void>|null {
+    if (state.get(PerfEvent.CAMERA_SWITCHING) ||
+        state.get(state.State.CAMERA_CONFIGURING) ||
+        !state.get(state.State.STREAMING)) {
+      return null;
+    }
+    state.set(PerfEvent.CAMERA_SWITCHING, true);
+    const devices = this.infoUpdater.getDevicesInfo();
+    let index = devices.findIndex(
+        (entry) => entry.deviceId === this.scheduler.reconfigurer.deviceId);
+    if (index === -1) {
+      index = 0;
+    }
+    if (devices.length > 0) {
+      index = (index + 1) % devices.length;
+      this.scheduler.reconfigurer.deviceId = devices[index].deviceId;
+    }
+    return (async () => {
+      const isSuccess = await this.reconfigure();
+      state.set(PerfEvent.CAMERA_SWITCHING, false, {hasError: !isSuccess});
+    })();
+  }
+
+  /**
+   * Apply point of interest to the stream.
+   *
+   * @param point The point in normalize coordidate system, which means both
+   *     |x| and |y| are in range [0, 1).
+   */
+  setPointOfInterest(point: Point): Promise<void> {
+    return this.preview.setPointOfInterest(point);
+  }
+
+  resetPTZ(): Promise<void> {
+    return this.preview.resetPTZ();
   }
 
   /**
    * Whether app window is suspended.
    */
-  shouldSuspended(): boolean {
+  private shouldSuspend(): boolean {
     return this.locked || windowController.isMinimized() ||
         this.suspendRequested || this.screenOff || this.isTabletBackground();
   }
+
+  startCapture(): Promise<() => Promise<void>> {
+    return this.scheduler.startCapture();
+  }
+
+  stopCapture(): void {
+    this.scheduler.stopCapture();
+  }
+
+  takeVideoSnapshot(): void {
+    this.scheduler.takeVideoSnapshot();
+  }
+
+  toggleVideoRecordingPause(): void {
+    this.scheduler.toggleVideoRecordingPause();
+  }
+
+  async reconfigure(): Promise<boolean> {
+    if (this.watchdog !== null) {
+      if (!await this.watchdog.waitNextReconfigure()) {
+        return false;
+      }
+      // The watchdog.waitNextReconfigure() only return the most recent
+      // reconfigure result which may not reflect the setting before calling it.
+      // Thus still fallthrough here to start another reconfigure.
+    }
+
+    return this.doReconfigure();
+  }
+
+  private async doReconfigure(): Promise<boolean> {
+    state.set(state.State.CAMERA_CONFIGURING, true);
+    this.scheduler.reconfigurer.setShouldSuspend(this.shouldSuspend());
+    try {
+      if (!(await this.scheduler.reconfigure())) {
+        throw new Error('camera suspended');
+      }
+    } catch (e) {
+      if (this.watchdog === null) {
+        if (!this.shouldSuspend()) {
+          // Suspension is caused by unexpected error, show the camera failure
+          // view.
+          // TODO(b/209726472): Move nav out of this module.
+          nav.open(ViewName.WARNING, WarningType.NO_CAMERA);
+        }
+        this.watchdog = new ResumeStateWatchdog(() => this.doReconfigure());
+      }
+      this.perfLogger.interrupt();
+      return false;
+    }
+
+    // TODO(b/209726472): Move nav out of this module.
+    nav.close(ViewName.WARNING);
+    this.watchdog = null;
+    state.set(state.State.CAMERA_CONFIGURING, false);
+    return true;
+  }
 }
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/camera_operation.ts b/ash/webui/camera_app_ui/resources/js/views/camera/camera_operation.ts
new file mode 100644
index 0000000..f9fd331
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/camera_operation.ts
@@ -0,0 +1,449 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {
+  assert,
+  assertInstanceof,
+  assertString,
+} from '../../assert.js';
+import {Camera3DeviceInfo} from '../../device/camera3_device_info.js';
+import {
+  PhotoConstraintsPreferrer,
+  VideoConstraintsPreferrer,
+} from '../../device/constraints_preferrer.js';
+import {DeviceInfoUpdater} from '../../device/device_info_updater.js';
+import {StreamConstraints} from '../../device/stream_constraints.js';
+import {StreamManager} from '../../device/stream_manager.js';
+import * as error from '../../error.js';
+import {DeviceOperator} from '../../mojo/device_operator.js';
+import * as state from '../../state.js';
+import {
+  ErrorLevel,
+  ErrorType,
+  Facing,
+  Mode,
+  Resolution,
+} from '../../type.js';
+import * as util from '../../util.js';
+import {CancelableEvent, WaitableEvent} from '../../waitable_event.js';
+
+import {Modes, Video} from './mode/index.js';
+import {Preview} from './preview.js';
+import {CameraViewUI, ModeConstraints} from './type.js';
+
+
+interface ConfigureCandidate {
+  deviceId: string;
+  mode: Mode;
+  captureResolution: Resolution;
+  constraints: StreamConstraints;
+  videoSnapshotResolution: Resolution;
+}
+
+export interface EventListener {
+  onConfigureComplete(): Promise<void>;
+}
+
+class CameraInfo {
+  readonly devicesInfo: Array<MediaDeviceInfo>;
+  readonly camera3DevicesInfo: Array<Camera3DeviceInfo>|null;
+
+  private readonly idToDeviceInfo: Map<string, MediaDeviceInfo>;
+  private readonly idToCamera3DeviceInfo: Map<string, Camera3DeviceInfo>|null;
+
+  constructor(updater: DeviceInfoUpdater) {
+    this.devicesInfo = updater.getDevicesInfo();
+    this.camera3DevicesInfo = updater.getCamera3DevicesInfo();
+    this.idToDeviceInfo = new Map(this.devicesInfo.map((d) => [d.deviceId, d]));
+    this.idToCamera3DeviceInfo = this.camera3DevicesInfo &&
+        new Map(this.camera3DevicesInfo.map((d) => [d.deviceId, d]));
+  }
+
+  getDeviceInfo(deviceId: string): MediaDeviceInfo {
+    const info = this.idToDeviceInfo.get(deviceId);
+    assert(info !== undefined);
+    return info;
+  }
+
+  getCamera3DeviceInfo(deviceId: string): Camera3DeviceInfo|null {
+    if (this.idToCamera3DeviceInfo === null) {
+      return null;
+    }
+    const info = this.idToCamera3DeviceInfo.get(deviceId);
+    return assertInstanceof(info, Camera3DeviceInfo);
+  }
+}
+
+class Reconfigurer {
+  // Preferred value for reconfiguring.
+  public facing = Facing.NOT_SET;
+  public deviceId: string|null = null;
+
+  private shouldSuspend = false;
+
+  constructor(
+      private readonly preview: Preview, private readonly modes: Modes,
+      private readonly modeConstraints: ModeConstraints,
+      private readonly postConfigure: () => Promise<void>) {}
+
+  setShouldSuspend(value: boolean) {
+    this.shouldSuspend = value;
+  }
+
+  /**
+   * Gets the video device ids sorted by preference.
+   */
+  private getDeviceIdCandidates(cameraInfo: CameraInfo): string[] {
+    let devices: Array<Camera3DeviceInfo|MediaDeviceInfo>;
+    /**
+     * Object mapping from device id to facing. Set to null for fake cameras.
+     */
+    let facings: Record<string, Facing>|null = null;
+
+    const camera3Info = cameraInfo.camera3DevicesInfo;
+    if (camera3Info !== null) {
+      devices = camera3Info;
+      facings = {};
+      for (const {deviceId, facing} of camera3Info) {
+        facings[deviceId] = facing;
+      }
+    } else {
+      devices = cameraInfo.devicesInfo;
+    }
+
+    const preferredFacing =
+        this.facing === Facing.NOT_SET ? util.getDefaultFacing() : this.facing;
+    // Put the selected video device id first.
+    const sorted = devices.map((device) => device.deviceId).sort((a, b) => {
+      if (a === b) {
+        return 0;
+      }
+      if (this.deviceId ? a === this.deviceId :
+                          (facings && facings[a] === preferredFacing)) {
+        return -1;
+      }
+      return 1;
+    });
+    return sorted;
+  }
+
+  private async getModeCandidates(deviceId: string|null): Promise<Mode[]> {
+    const supportedModes = await this.modes.getSupportedModes(deviceId);
+    if (this.modeConstraints.exact !== undefined) {
+      assert(supportedModes.includes(this.modeConstraints.exact));
+      return [this.modeConstraints.exact];
+    }
+    const modes = this.modes.getModeCandidates().filter(
+        (m) => supportedModes.includes(m));
+    return modes;
+  }
+
+  private async *
+      getConfigurationCandidates(cameraInfo: CameraInfo):
+          AsyncIterable<ConfigureCandidate> {
+    const deviceOperator = await DeviceOperator.getInstance();
+
+    for (const deviceId of this.getDeviceIdCandidates(cameraInfo)) {
+      for (const mode of await this.getModeCandidates(deviceId)) {
+        let resolCandidates;
+        let photoRs;
+        if (deviceOperator !== null) {
+          resolCandidates = this.modes.getResolutionCandidates(mode, deviceId);
+          photoRs = await deviceOperator.getPhotoResolutions(deviceId);
+        } else {
+          resolCandidates =
+              this.modes.getFakeResolutionCandidates(mode, deviceId);
+          photoRs = resolCandidates.map((c) => c.resolution);
+        }
+        const maxResolution =
+            photoRs.reduce((maxR, r) => r.area > maxR.area ? r : maxR);
+        for (const {
+               resolution: captureResolution,
+               previewCandidates,
+             } of resolCandidates) {
+          const videoSnapshotResolution =
+              state.get(state.State.ENABLE_FULL_SIZED_VIDEO_SNAPSHOT) ?
+              maxResolution :
+              captureResolution;
+          for (const constraints of previewCandidates) {
+            yield {
+              deviceId,
+              mode,
+              captureResolution,
+              constraints,
+              videoSnapshotResolution,
+            };
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Checks if PTZ can be enabled.
+   */
+  private async checkEnablePTZ(c: ConfigureCandidate): Promise<void> {
+    const enablePTZ = await (async () => {
+      if (!this.preview.isSupportPTZ()) {
+        return false;
+      }
+      const modeSupport = state.get(state.State.USE_FAKE_CAMERA) ||
+          this.modes.isSupportPTZ(
+              c.mode,
+              c.captureResolution,
+              this.preview.getResolution(),
+          );
+      if (!modeSupport) {
+        await this.preview.resetPTZ();
+        return false;
+      }
+      return true;
+    })();
+    state.set(state.State.ENABLE_PTZ, enablePTZ);
+  }
+
+  async start(cameraInfo: CameraInfo): Promise<boolean> {
+    await this.stopStreams();
+    return this.startConfigure(cameraInfo);
+  }
+
+  /**
+   * @return If the reconfiguration finished successfully.
+   */
+  async startConfigure(cameraInfo: CameraInfo): Promise<boolean> {
+    if (this.shouldSuspend) {
+      return false;
+    }
+
+    const deviceOperator = await DeviceOperator.getInstance();
+    state.set(state.State.USE_FAKE_CAMERA, deviceOperator === null);
+
+    for await (const c of this.getConfigurationCandidates(cameraInfo)) {
+      if (this.shouldSuspend) {
+        return false;
+      }
+      this.modes.setCaptureParams(
+          c.mode, c.constraints, c.captureResolution,
+          c.videoSnapshotResolution);
+      try {
+        await this.modes.prepareDevice();
+        const factory = this.modes.getModeFactory(c.mode);
+        const stream = await this.preview.open(c.constraints);
+        const facing = this.preview.getFacing();
+        const deviceId = assertString(this.preview.getDeviceId());
+
+        await this.checkEnablePTZ(c);
+        factory.setPreviewVideo(this.preview.getVideo());
+        factory.setFacing(facing);
+        await this.modes.updateModeSelectionUI(c.deviceId);
+        await this.modes.updateMode(factory, stream, facing, deviceId);
+        this.facing = facing;
+        this.deviceId = deviceId;
+        await this.postConfigure();
+
+        return true;
+      } catch (e) {
+        await this.stopStreams();
+
+        let errorToReport = e;
+        // Since OverconstrainedError is not an Error instance.
+        if (e instanceof OverconstrainedError) {
+          errorToReport =
+              new Error(`${e.message} (constraint = ${e.constraint})`);
+          errorToReport.name = 'OverconstrainedError';
+        } else if (e.name === 'NotReadableError') {
+          // TODO(b/187879603): Remove this hacked once we understand more
+          // about such error.
+          // We cannot get the camera facing from stream since it might
+          // not be successfully opened. Therefore, we asked the camera
+          // facing via Mojo API.
+          let facing = Facing.NOT_SET;
+          if (deviceOperator !== null) {
+            facing = await deviceOperator.getCameraFacing(c.deviceId);
+          }
+          errorToReport = new Error(`${e.message} (facing = ${facing})`);
+          errorToReport.name = 'NotReadableError';
+        }
+        error.reportError(
+            ErrorType.START_CAMERA_FAILURE, ErrorLevel.ERROR,
+            assertInstanceof(errorToReport, Error));
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Stop extra stream and preview stream.
+   */
+  private async stopStreams() {
+    await this.modes.clear();
+    await this.preview.close();
+  }
+}
+
+class Capturer {
+  constructor(private readonly modes: Modes) {}
+
+  async start(): Promise<() => Promise<void>> {
+    return this.modes.current.startCapture();
+  }
+
+  stop() {
+    this.modes.current.stopCapture();
+  }
+
+  takeVideoSnapshot() {
+    if (this.modes.current instanceof Video) {
+      this.modes.current.takeSnapshot();
+    }
+  }
+
+  toggleVideoRecordingPause() {
+    if (this.modes.current instanceof Video) {
+      this.modes.current.togglePaused();
+    }
+  }
+}
+
+enum OperationType {
+  RECONFIGURE = 'reconfigure',
+  CAPTURE = 'capture',
+}
+
+export class OperationScheduler {
+  private cameraInfo: CameraInfo|null = null;
+  private pendingUpdateInfo: CameraInfo|null = null;
+  private firstInfoUpdate = new WaitableEvent();
+
+  readonly reconfigurer: Reconfigurer;
+  readonly capturer: Capturer;
+  private ongoingOperationType: OperationType|null = null;
+  private pendingReconfigureWaiters: CancelableEvent<boolean>[] = [];
+
+  constructor(
+      private readonly infoUpdater: DeviceInfoUpdater,
+      private readonly listener: EventListener,
+      preview: Preview,
+      cameraViewUI: CameraViewUI,
+      photoPreferrer: PhotoConstraintsPreferrer,
+      videoPreferrer: VideoConstraintsPreferrer,
+      modeConstraints: ModeConstraints,
+  ) {
+    const defaultMode =
+        modeConstraints.exact ?? modeConstraints.default ?? Mode.PHOTO;
+    const modes = new Modes(
+        defaultMode, photoPreferrer, videoPreferrer,
+        async () => this.reconfigure(), cameraViewUI);
+    this.reconfigurer = new Reconfigurer(
+        preview,
+        modes,
+        modeConstraints,
+        async () => this.listener.onConfigureComplete(),
+    );
+    this.capturer = new Capturer(modes);
+    this.infoUpdater.addDeviceChangeListener(async (updater) => {
+      const info = new CameraInfo(updater);
+      if (this.ongoingOperationType !== null) {
+        this.pendingUpdateInfo = info;
+        return;
+      }
+      this.doUpdate(info);
+    });
+  }
+
+  async initialize(): Promise<void> {
+    await StreamManager.getInstance().deviceUpdate();
+    await this.firstInfoUpdate.wait();
+  }
+
+  private doUpdate(cameraInfo: CameraInfo) {
+    if (this.cameraInfo === null) {
+      this.firstInfoUpdate.signal();
+    }
+    this.cameraInfo = cameraInfo;
+  }
+
+  async reconfigure(): Promise<boolean> {
+    if (this.ongoingOperationType !== null) {
+      const event = new CancelableEvent<boolean>();
+      this.pendingReconfigureWaiters.push(event);
+      return event.wait();
+    }
+    return this.startReconfigure();
+  }
+
+  takeVideoSnapshot(): void {
+    if (this.ongoingOperationType === OperationType.CAPTURE) {
+      this.capturer.takeVideoSnapshot();
+    }
+  }
+
+  toggleVideoRecordingPause(): void {
+    if (this.ongoingOperationType === OperationType.CAPTURE) {
+      this.capturer.toggleVideoRecordingPause();
+    }
+  }
+
+  private clearPendingReconfigureWaiters() {
+    for (const waiter of this.pendingReconfigureWaiters) {
+      waiter.signal(false);
+    }
+    this.pendingReconfigureWaiters = [];
+  }
+
+  private finishOperation(): void {
+    this.ongoingOperationType = null;
+
+    // Check pending operations.
+    if (this.pendingUpdateInfo !== null) {
+      this.doUpdate(this.pendingUpdateInfo);
+      this.pendingUpdateInfo = null;
+    }
+    if (this.pendingReconfigureWaiters.length !== 0) {
+      const starting = this.startReconfigure();
+      for (const waiter of this.pendingReconfigureWaiters) {
+        waiter.signalAs(starting);
+      }
+      this.pendingReconfigureWaiters = [];
+    }
+  }
+
+  async startCapture(): Promise<() => Promise<void>>|null {
+    if (this.ongoingOperationType !== null) {
+      return null;
+    }
+    this.ongoingOperationType = OperationType.CAPTURE;
+
+    try {
+      return await this.capturer.start();
+    } finally {
+      this.finishOperation();
+    }
+  }
+
+  stopCapture(): void {
+    if (this.ongoingOperationType === OperationType.CAPTURE) {
+      this.capturer.stop();
+    }
+  }
+
+  private async startReconfigure(): Promise<boolean> {
+    assert(this.ongoingOperationType === null);
+    this.ongoingOperationType = OperationType.RECONFIGURE;
+
+    const cameraInfo = assertInstanceof(this.cameraInfo, CameraInfo);
+    try {
+      const succeed = await this.reconfigurer.start(cameraInfo);
+      if (!succeed) {
+        this.clearPendingReconfigureWaiters();
+      }
+      return succeed;
+    } catch (e) {
+      this.clearPendingReconfigureWaiters();
+      throw e;
+    } finally {
+      this.finishOperation();
+    }
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/mode/index.ts b/ash/webui/camera_app_ui/resources/js/views/camera/mode/index.ts
index 74248ced..83419fe 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera/mode/index.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/mode/index.ts
@@ -53,6 +53,9 @@
  */
 export type DoSwitchMode = () => Promise<boolean>;
 
+export type CaptureHandler =
+    PhotoHandler&VideoHandler&PortraitHandler&ScanHandler;
+
 /**
  * Parameters for capture settings.
  */
@@ -129,7 +132,7 @@
       photoPreferrer: PhotoConstraintsPreferrer,
       videoPreferrer: VideoConstraintsPreferrer,
       private readonly doSwitchMode: DoSwitchMode,
-      handler: PhotoHandler&PortraitHandler&ScanHandler&VideoHandler,
+      handler: CaptureHandler,
   ) {
     /**
      * Returns a set of general constraints for fake cameras.
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/mode/mode_base.ts b/ash/webui/camera_app_ui/resources/js/views/camera/mode/mode_base.ts
index 41e36bd..253104fb 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera/mode/mode_base.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/mode/mode_base.ts
@@ -90,11 +90,6 @@
   }
 
   /**
-   * Updates preview video currently in used.
-   */
-  abstract updatePreview(previewVideo: PreviewVideo): void;
-
-  /**
    * Initiates video/photo capture operation under this mode.
    */
   protected abstract start(): Promise<() => Promise<void>>;
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/options.ts b/ash/webui/camera_app_ui/resources/js/views/camera/options.ts
index 9eaad8e6..70c0c4f 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera/options.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/options.ts
@@ -11,11 +11,12 @@
 import * as state from '../../state.js';
 import {Facing, ViewName} from '../../type.js';
 import * as util from '../../util.js';
+import {CameraManager, CameraUI} from './camera_manager.js';
 
 /**
  * Creates a controller for the options of Camera view.
  */
-export class Options {
+export class Options implements CameraUI {
   private readonly toggleMic = dom.get('#toggle-mic', HTMLInputElement);
   private readonly toggleMirror = dom.get('#toggle-mirror', HTMLInputElement);
 
@@ -39,11 +40,15 @@
    */
   constructor(
       private readonly infoUpdater: DeviceInfoUpdater,
-      private readonly doSwitchDevice: () => Promise<void>| null,
+      private readonly cameraManager: CameraManager,
   ) {
+    this.cameraManager.registerCameraUI(this);
     dom.get('#switch-device', HTMLButtonElement)
         .addEventListener('click', () => {
-          const switching = this.doSwitchDevice();
+          if (state.get(state.State.TAKING)) {
+            return;
+          }
+          const switching = this.cameraManager.switchCamera();
           if (switching !== null) {
             animate.play(dom.get('#switch-device', HTMLElement));
           }
@@ -71,14 +76,10 @@
     });
   }
 
-  /**
-   * Updates the options' values for the current constraints and stream.
-   * @param stream Current Stream in use.
-   */
-  updateValues(stream: MediaStream, deviceId: string, facing: Facing): void {
-    this.videoDeviceId = deviceId;
-    this.updateMirroring(facing);
-    this.audioTrack = stream.getAudioTracks()[0];
+  onConfigureComplete(): void {
+    this.videoDeviceId = this.cameraManager.getDeviceId();
+    this.updateMirroring(this.cameraManager.getFacing());
+    this.audioTrack = this.cameraManager.getAudioTrack();
     this.updateAudioByMic();
   }
 
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/preview.ts b/ash/webui/camera_app_ui/resources/js/views/camera/preview.ts
index 3e2db5b..622e666 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera/preview.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/preview.ts
@@ -42,9 +42,6 @@
 import {WaitableEvent} from '../../waitable_event.js';
 import {windowController} from '../../window_controller.js';
 
-// eslint-disable-next-line no-unused-vars
-import {CameraManager} from './camera_manager.js';
-
 /**
  * Creates a controller for the video preview of Camera view.
  */
@@ -97,7 +94,7 @@
    * @param onNewStreamNeeded Callback to request new stream.
    */
   constructor(
-      private readonly cameraManager: CameraManager,
+      private readonly getLastScreenOnTime: () => number,
       private readonly onNewStreamNeeded: () => Promise<void>) {
     window.addEventListener('resize', () => this.onWindowStatusChanged());
 
@@ -297,8 +294,7 @@
     // TODO(b/173679752): Removes this workaround after fix delay on
     // kernel side.
     if (loadTimeData.getBoard() === 'zork') {
-      const screenOnTime =
-          performance.now() - this.cameraManager.getLastScreenOnTime();
+      const screenOnTime = performance.now() - this.getLastScreenOnTime();
       const delay = 2500 - screenOnTime;
       if (delay > 0) {
         await util.sleep(delay);
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/scan_options.ts b/ash/webui/camera_app_ui/resources/js/views/camera/scan_options.ts
index 8be6eae..68eeeb53 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera/scan_options.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/scan_options.ts
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {assert, assertInstanceof} from '../../assert.js';
+import {assert} from '../../assert.js';
 import * as barcodeChip from '../../barcode_chip.js';
 import * as dom from '../../dom.js';
-import {Point} from '../../geometry.js';
 import {sendBarcodeEnabledEvent} from '../../metrics.js';
 import {BarcodeScanner} from '../../models/barcode.js';
 import * as state from '../../state.js';
-import {Mode} from '../../type.js';
+import {Mode, PreviewVideo} from '../../type.js';
 import {assertEnumVariant} from '../../util.js';
 
+import {CameraManager, CameraUI} from './camera_manager.js';
 import {DocumentCornerOverlay} from './document_corner_overlay.js';
 
 enum ScanType {
@@ -32,7 +32,7 @@
 /**
  * Controller for the scan options of Camera view.
  */
-export class ScanOptions {
+export class ScanOptions implements CameraUI {
   /**
    * Togglable barcode option in photo mode.
    */
@@ -42,10 +42,7 @@
   private readonly scanOptions =
       [...dom.getAll('#scan-modes-group [data-scantype]', HTMLInputElement)];
 
-  /**
-   * Whether preview have attached as scan frame source.
-   */
-  private previewAttached = false;
+  private video: PreviewVideo|null = null;
 
   /**
    * May be null if preview is not ready.
@@ -67,9 +64,11 @@
    * @param updatePointOfInterest function to update point of interest on the
    *     stream.
    */
-  constructor(updatePointOfInterest: (point: Point) => Promise<void>) {
-    this.documentCornerOverylay =
-        new DocumentCornerOverlay(updatePointOfInterest);
+  constructor(private readonly cameraManager: CameraManager) {
+    this.cameraManager.registerCameraUI(this);
+
+    this.documentCornerOverylay = new DocumentCornerOverlay(
+        (p) => this.cameraManager.setPointOfInterest(p));
 
     [this.photoBarcodeOption, ...this.scanOptions].forEach((opt) => {
       opt.addEventListener('click', (evt) => {
@@ -92,6 +91,31 @@
   }
 
   /**
+   * Whether preview have attached as scan frame source.
+   */
+  private previewAvailable(): boolean {
+    return this.video?.isExpired() === false;
+  }
+
+  // Overrides |CameraUI|.
+  async onConfigureComplete(): Promise<void> {
+    assert(!this.previewAvailable());
+
+    this.video = this.cameraManager.getPreviewVideo();
+    this.barcodeScanner = new BarcodeScanner(this.video.video, (value) => {
+      barcodeChip.show(value);
+    });
+    const {deviceId} = this.video.getVideoSettings();
+    this.documentCornerOverylay.attach(deviceId);
+    const scanType = state.get(Mode.SCAN) ? this.getToggledScanOption() : null;
+    (async () => {
+      await this.video.onExpired;
+      this.detachPreview();
+    })();
+    await this.updateOption(scanType);
+  }
+
+  /**
    * @return Returns scan type of checked radio buttons in scan type option
    *     groups.
    */
@@ -101,23 +125,6 @@
                                      getScanTypeFromElement(checkedEl);
   }
 
-  /**
-   * Attaches to preview video as source of frames to be scanned.
-   */
-  async attachPreview(video: HTMLVideoElement): Promise<void> {
-    assert(!this.previewAttached);
-    this.barcodeScanner = new BarcodeScanner(video, (value) => {
-      barcodeChip.show(value);
-    });
-    const {deviceId} = assertInstanceof(video.srcObject, MediaStream)
-                           .getVideoTracks()[0]
-                           .getSettings();
-    this.documentCornerOverylay.attach(deviceId);
-    this.previewAttached = true;
-    const scanType = state.get(Mode.SCAN) ? this.getToggledScanOption() : null;
-    await this.updateOption(scanType);
-  }
-
   isDocumentModeEanbled(): boolean {
     return this.documentCornerOverylay.isEnabled();
   }
@@ -127,7 +134,7 @@
    *     enabled.
    */
   private async updateOption(scanType: ScanType|null) {
-    if (!this.previewAttached) {
+    if (!this.previewAvailable()) {
       return;
     }
     assert(this.barcodeScanner !== null);
@@ -169,12 +176,11 @@
   /**
    * Stops all scanner and detach from current preview.
    */
-  async detachPreview(): Promise<void> {
+  private async detachPreview(): Promise<void> {
     if (this.barcodeScanner !== null) {
       this.stopBarcodeScanner();
       this.barcodeScanner = null;
     }
     await this.documentCornerOverylay.detach();
-    this.previewAttached = false;
   }
 }
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/type.ts b/ash/webui/camera_app_ui/resources/js/views/camera/type.ts
new file mode 100644
index 0000000..2c6c7b8
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/type.ts
@@ -0,0 +1,14 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {Mode} from '../../type.js';
+
+import {CaptureHandler} from './mode/index.js';
+
+export interface ModeConstraints {
+  exact?: Mode;
+  default?: Mode;
+}
+
+export type CameraViewUI = CaptureHandler;
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts b/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts
index a54eeda2..fe22101 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts
@@ -72,8 +72,8 @@
             assertNotReached();
           },
         },
-        infoUpdater, photoPreferrer, videoPreferrer, mode, perfLogger,
-        /* facing= */ null);
+        infoUpdater, photoPreferrer, videoPreferrer, perfLogger,
+        /* facing= */ null, /* modeConstraints= */ {exact: mode});
   }
 
   private reviewIntentResult(metricArgs: MetricArgs): Promise<void> {
@@ -133,8 +133,4 @@
     await this.reviewIntentResult(
         {resolution: videoResult.resolution, duration: videoResult.duration});
   }
-
-  protected async getModeCandidates(): Promise<Mode[]> {
-    return [this.defaultMode];
-  }
 }
diff --git a/ash/webui/camera_app_ui/resources/js/waitable_event.ts b/ash/webui/camera_app_ui/resources/js/waitable_event.ts
index 3160a0b..0365bcd 100644
--- a/ash/webui/camera_app_ui/resources/js/waitable_event.ts
+++ b/ash/webui/camera_app_ui/resources/js/waitable_event.ts
@@ -10,12 +10,14 @@
   // The field is definitely assigned in the constructor since the argument to
   // the Promise constructor is called immediately, but TypeScript can't
   // recognize that. Disable the check by adding "!" to the property name.
-  private resolve!: (val: T) => void;
+  protected resolve!: (val: T) => void;
+  protected reject!: (val: Error) => void;
   private promise: Promise<T>;
 
   constructor() {
-    this.promise = new Promise((resolve) => {
+    this.promise = new Promise((resolve, reject) => {
       this.resolve = resolve;
+      this.reject = reject;
     });
   }
 
@@ -57,3 +59,14 @@
     return Promise.race([this.promise, timeoutPromise]);
   }
 }
+
+export class CancelableEvent<T> extends WaitableEvent<T> {
+  signalError(e: Error): void {
+    this.reject(e);
+  }
+
+  signalAs(promise: Promise<T>): void {
+    promise.then((v) => this.resolve(v));
+    promise.catch((e) => this.reject(e));
+  }
+}
diff --git a/ash/webui/firmware_update_ui/mojom/firmware_update.mojom b/ash/webui/firmware_update_ui/mojom/firmware_update.mojom
index b41e167..da8e651 100644
--- a/ash/webui/firmware_update_ui/mojom/firmware_update.mojom
+++ b/ash/webui/firmware_update_ui/mojom/firmware_update.mojom
@@ -85,10 +85,6 @@
   // remote.
   PrepareForUpdate(string device_id) =>
     (pending_remote<InstallController>? controller);
-
-  // Returns the update if there is a firmware update in progress. If not update
-  // is in progress, |update| will be empty.
-  FetchInProgressUpdate() => (FirmwareUpdate? update);
 };
 
 // Enables clients to begin the install flow and receive progress updates.
diff --git a/ash/webui/firmware_update_ui/resources/fake_update_provider.js b/ash/webui/firmware_update_ui/resources/fake_update_provider.js
index c9ff387..cca8b1c 100644
--- a/ash/webui/firmware_update_ui/resources/fake_update_provider.js
+++ b/ash/webui/firmware_update_ui/resources/fake_update_provider.js
@@ -24,9 +24,6 @@
     /** @private {?Promise} */
     this.observePeripheralUpdatesPromise_ = null;
 
-    /** @private {?FirmwareUpdate} */
-    this.inflight_update_ = null;
-
     this.registerObservables();
   }
 
@@ -42,10 +39,6 @@
         });
   }
 
-  fetchInProgressUpdate() {
-    return new Promise((resolve) => resolve({update: this.inflight_update_}));
-  }
-
   /**
    * @param {string} deviceId
    * @return {!Promise}
@@ -67,14 +60,6 @@
   }
 
   /**
-   * Sets the inflight update.
-   * @param {!FirmwareUpdate} update
-   */
-  setInflightUpdate(update) {
-    this.inflight_update_ = update;
-  }
-
-  /**
    * Returns the promise for the most recent peripheral updates observation.
    * @return {?Promise}
    */
diff --git a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html
index b88771f..fdffecad 100644
--- a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html
+++ b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html
@@ -28,8 +28,7 @@
   }
 </style>
 <template is="dom-if"
-    if="[[shouldShowUpdateDialog_(installationProgress.*,
-        isInitiallyInflight_)]]" restamp>
+    if="[[shouldShowUpdateDialog_(installationProgress.*)]]" restamp>
   <cr-dialog id="updateDialog" show-on-attach
       on-close="closeDialog_">
     <div slot="title" id="updateDialogTitle" class="firmware-dialog-title-font">
diff --git a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js
index 2390208..9ec1a54 100644
--- a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js
+++ b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js
@@ -55,7 +55,7 @@
 
   static get properties() {
     return {
-      /** @type {?FirmwareUpdate} */
+      /** @type {!FirmwareUpdate} */
       update: {
         type: Object,
       },
@@ -63,21 +63,14 @@
       /** @type {!InstallationProgress} */
       installationProgress: {
         type: Object,
-        value: {percentage: 0, state: UpdateState.kIdle},
-      },
-
-      /** @private {boolean} */
-      isInitiallyInflight_: {
-        value: false,
       },
 
       /** @type {!DialogContent} */
       dialogContent: {
         type: Object,
         value: initialDialogContent,
-        computed: 'computeDialogContent_(installationProgress.*,' +
-            'isInitiallyInflight_)',
-      },
+        computed: 'computeDialogContent_(installationProgress.*)',
+      }
     };
   }
 
@@ -98,7 +91,6 @@
      */
     this.openUpdateDialog_ = (e) => {
       this.update = e.detail.update;
-      this.isInitiallyInflight_ = e.detail.inflight;
       this.prepareForUpdate_();
     };
   }
@@ -121,11 +113,9 @@
 
   /** @protected */
   closeDialog_() {
-    this.isInitiallyInflight_ = false;
     // Resetting |installationProgress| triggers a call to
     // |shouldShowUpdateDialog_|.
     this.installationProgress = {percentage: 0, state: UpdateState.kIdle};
-    this.update = null;
   }
 
   /** @protected */
@@ -138,11 +128,11 @@
     }
     this.installController_ =
         /**@type {InstallControllerRemote} */ (response.controller);
-    this.bindReceiverAndMaybeStartUpdate_();
+    this.beginUpdate_();
   }
 
   /** @protected */
-  bindReceiverAndMaybeStartUpdate_() {
+  beginUpdate_() {
     /** @protected {?UpdateProgressObserverReceiver} */
     this.updateProgressObserverReceiver_ = new UpdateProgressObserverReceiver(
         /**
@@ -152,12 +142,8 @@
 
     this.installController_.addObserver(
         this.updateProgressObserverReceiver_.$.bindNewPipeAndPassRemote());
-
-    // Only start new updates, inflight updates will be observed instead.
-    if (!this.isInitiallyInflight_) {
-      this.installController_.beginUpdate(
-          this.update.deviceId, this.update.filepath);
-    }
+    this.installController_.beginUpdate(
+        this.update.deviceId, this.update.filepath);
   }
 
   /**
@@ -165,16 +151,6 @@
    * @return {boolean}
    */
   shouldShowUpdateDialog_() {
-    if (!this.update) {
-      return false;
-    }
-
-    // Handles the case in which an update is in progress on app load, but has
-    // yet to receive an progress update callback.
-    if (this.isInitiallyInflight_) {
-      return true;
-    }
-
     /** @type {!Array<!UpdateState>} */
     const activeDialogStates = [
       UpdateState.kUpdating,
@@ -182,7 +158,6 @@
       UpdateState.kFailed,
       UpdateState.kSuccess,
     ];
-    // Show dialog is there is an update in progress.
     return activeDialogStates.includes(this.installationProgress.state) ||
         this.installationProgress.percentage > 0;
   }
@@ -275,21 +250,12 @@
 
   /** @return {!DialogContent} */
   computeDialogContent_() {
-    // No update in progress.
-    if (!this.isInitiallyInflight_ && !this.update) {
-      return initialDialogContent;
-    }
-
     if (inactiveDialogStates.includes(this.installationProgress.state) ||
         this.isDeviceRestarting_()) {
       return this.createDialogContentObj_(UpdateState.kRestarting);
     }
 
-    // Regular case: Update is in progress, started from the same instance of
-    // which the app launched.
-    // Edge case: App launch with an update in progress, but no progress
-    // callback has been called yet.
-    if (this.isInitiallyInflight_ || this.isUpdateInProgress_()) {
+    if (this.isUpdateInProgress_()) {
       return this.createDialogContentObj_(UpdateState.kUpdating);
     }
 
diff --git a/ash/webui/firmware_update_ui/resources/peripheral_updates_list.html b/ash/webui/firmware_update_ui/resources/peripheral_updates_list.html
index b504504..f95a49f8 100644
--- a/ash/webui/firmware_update_ui/resources/peripheral_updates_list.html
+++ b/ash/webui/firmware_update_ui/resources/peripheral_updates_list.html
@@ -6,9 +6,7 @@
   </template>
   <dom-repeat id="updateList" items="[[firmwareUpdates_]]" as="update">
     <template>
-      <update-card update="[[update]]"
-          disabled="[[!hasCheckedInitialInflightProgress_]]">
-      </update-card>
+      <update-card update="[[update]]"></update-card>
     </template>
   </dom-repeat>
 </div>
diff --git a/ash/webui/firmware_update_ui/resources/peripheral_updates_list.js b/ash/webui/firmware_update_ui/resources/peripheral_updates_list.js
index 4a9b9857..0873cf4 100644
--- a/ash/webui/firmware_update_ui/resources/peripheral_updates_list.js
+++ b/ash/webui/firmware_update_ui/resources/peripheral_updates_list.js
@@ -35,12 +35,6 @@
         type: Array,
         value: () => [],
       },
-
-      /** @protected */
-      hasCheckedInitialInflightProgress_: {
-        type: Boolean,
-        value: false,
-      },
     };
   }
 
@@ -73,19 +67,6 @@
    */
   onUpdateListChanged(firmwareUpdates) {
     this.firmwareUpdates_ = firmwareUpdates;
-
-    if (!this.hasCheckedInitialInflightProgress_) {
-      this.updateProvider_.fetchInProgressUpdate().then(result => {
-        if (result.update) {
-          this.dispatchEvent(new CustomEvent('open-update-dialog', {
-            bubbles: true,
-            composed: true,
-            detail: {update: result.update, inflight: true}
-          }));
-        }
-        this.hasCheckedInitialInflightProgress_ = true;
-      });
-    }
   }
 
   /**
diff --git a/ash/webui/firmware_update_ui/resources/update_card.html b/ash/webui/firmware_update_ui/resources/update_card.html
index 45fe5b4..02225f5 100644
--- a/ash/webui/firmware_update_ui/resources/update_card.html
+++ b/ash/webui/firmware_update_ui/resources/update_card.html
@@ -47,8 +47,7 @@
       [[computeDeviceDescription_(update.deviceDescription)]]
     </div>
   </div>
-  <cr-button id="updateButton" on-click="onUpdateButtonClicked_"
-      disabled="[[disabled]]">
+  <cr-button id="updateButton" on-click="onUpdateButtonClicked_">
     [[i18n('updateButton')]]
   </cr-button>
 </div>
diff --git a/ash/webui/firmware_update_ui/resources/update_card.js b/ash/webui/firmware_update_ui/resources/update_card.js
index 0e18c60..543ee13 100644
--- a/ash/webui/firmware_update_ui/resources/update_card.js
+++ b/ash/webui/firmware_update_ui/resources/update_card.js
@@ -45,10 +45,6 @@
       update: {
         type: Object,
       },
-
-      disabled: {
-        type: Boolean,
-      },
     };
   }
 
@@ -62,11 +58,9 @@
 
   /** @protected */
   onUpdateButtonClicked_() {
-    this.dispatchEvent(new CustomEvent('open-update-dialog', {
-      bubbles: true,
-      composed: true,
-      detail: {update: this.update, inflight: false}
-    }));
+    this.dispatchEvent(new CustomEvent(
+        'open-update-dialog',
+        {bubbles: true, composed: true, detail: {update: this.update}}));
   }
 
   /**
diff --git a/ash/webui/sample_system_web_app_ui/OWNERS b/ash/webui/sample_system_web_app_ui/OWNERS
index 47d56c85..44a1559 100644
--- a/ash/webui/sample_system_web_app_ui/OWNERS
+++ b/ash/webui/sample_system_web_app_ui/OWNERS
@@ -1 +1 @@
-file://ash/webui/system_apps/PLATFORM_OWNERS
+file://ash/webui/PLATFORM_OWNERS
diff --git a/ash/webui/system_apps/OWNERS b/ash/webui/system_apps/OWNERS
index 706259cf..b598e5c 100644
--- a/ash/webui/system_apps/OWNERS
+++ b/ash/webui/system_apps/OWNERS
@@ -1,3 +1,3 @@
-file://ash/webui/system_apps/PLATFORM_OWNERS
+file://ash/webui/PLATFORM_OWNERS
 
 tapted@chromium.org
diff --git a/ash/webui/system_extensions_internals_ui/OWNERS b/ash/webui/system_extensions_internals_ui/OWNERS
index 47d56c85..44a1559 100644
--- a/ash/webui/system_extensions_internals_ui/OWNERS
+++ b/ash/webui/system_extensions_internals_ui/OWNERS
@@ -1 +1 @@
-file://ash/webui/system_apps/PLATFORM_OWNERS
+file://ash/webui/PLATFORM_OWNERS
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index ed88c0d..2186181 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -1624,20 +1624,6 @@
   const auto* desks_bar_view = overview_grid->desks_bar_view();
   ASSERT_TRUE(desks_bar_view);
 
-  // Drag it and drop it on the new desk button. Nothing happens, it should be
-  // returned back to its original target bounds.
-  DragItemToPoint(overview_item,
-                  desks_bar_view->expanded_state_new_desk_button()
-                      ->inner_button()
-                      ->GetBoundsInScreen()
-                      .CenterPoint(),
-                  GetEventGenerator(),
-                  /*by_touch_gestures=*/GetParam());
-  EXPECT_TRUE(overview_controller->InOverviewSession());
-  EXPECT_EQ(1u, overview_grid->size());
-  EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds());
-  EXPECT_TRUE(DoesActiveDeskContainWindow(window.get()));
-
   // Drag it and drop it on the center of the bottom of the display. Also,
   // nothing should happen.
   DragItemToPoint(overview_item,
diff --git a/base/compiler_specific.h b/base/compiler_specific.h
index 32cb0d9..faf56a1 100644
--- a/base/compiler_specific.h
+++ b/base/compiler_specific.h
@@ -108,18 +108,6 @@
 #define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment)))
 #endif
 
-// Annotate a function indicating the caller must examine the return value.
-// Use like:
-//   int foo() WARN_UNUSED_RESULT;
-// To explicitly ignore a result, see |ignore_result()| in
-// base/ignore_result.h.
-#undef WARN_UNUSED_RESULT
-#if defined(COMPILER_GCC) || defined(__clang__)
-#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
-#else
-#define WARN_UNUSED_RESULT
-#endif
-
 // In case the compiler supports it NO_UNIQUE_ADDRESS evaluates to the C++20
 // attribute [[no_unique_address]]. This allows annotating data members so that
 // they need not have an address distinct from all other non-static data members
diff --git a/base/containers/span.h b/base/containers/span.h
index 6b78ecc..1739f90 100644
--- a/base/containers/span.h
+++ b/base/containers/span.h
@@ -225,10 +225,6 @@
 //   sized container (e.g. std::vector) requires an explicit conversion (in the
 //   C++20 draft this is simply UB)
 //
-// Differences from [span.obs]:
-// - empty() is marked with WARN_UNUSED_RESULT instead of [[nodiscard]]
-//   ([[nodiscard]] is a C++17 feature)
-//
 // Furthermore, all constructors and methods are marked noexcept due to the lack
 // of exceptions in Chromium.
 //
diff --git a/base/fuchsia/filtered_service_directory.h b/base/fuchsia/filtered_service_directory.h
index 2808647..178c347 100644
--- a/base/fuchsia/filtered_service_directory.h
+++ b/base/fuchsia/filtered_service_directory.h
@@ -12,15 +12,14 @@
 #include <lib/zx/channel.h>
 
 #include "base/base_export.h"
-#include "base/compiler_specific.h"
 #include "base/strings/string_piece.h"
 
 // TODO(crbug.com/1196525): Remove once Chromecast calls are checking results.
 #include "build/chromecast_buildflags.h"
 #if BUILDFLAG(IS_CHROMECAST)
-#define MAYBE_WARN_UNUSED_RESULT
+#define MAYBE_NODISCARD
 #else
-#define MAYBE_WARN_UNUSED_RESULT WARN_UNUSED_RESULT
+#define MAYBE_NODISCARD [[nodiscard]]
 #endif
 
 namespace base {
@@ -39,13 +38,12 @@
   ~FilteredServiceDirectory();
 
   // Adds the specified service to the list of allowed services.
-  zx_status_t AddService(base::StringPiece service_name)
-      MAYBE_WARN_UNUSED_RESULT;
+  MAYBE_NODISCARD zx_status_t AddService(base::StringPiece service_name);
 
   // Connects a directory client. The directory can be passed to a sandboxed
   // process to be used for /svc namespace.
-  zx_status_t ConnectClient(fidl::InterfaceRequest<::fuchsia::io::Directory>
-                                dir_request) MAYBE_WARN_UNUSED_RESULT;
+  MAYBE_NODISCARD zx_status_t
+  ConnectClient(fidl::InterfaceRequest<::fuchsia::io::Directory> dir_request);
 
   // Accessor for the OutgoingDirectory, used to add handlers for services
   // in addition to those provided from |directory| via AddService().
diff --git a/base/json/json_parser.rs b/base/json/json_parser.rs
index 68eb770..841d1c83 100644
--- a/base/json/json_parser.rs
+++ b/base/json/json_parser.rs
@@ -70,7 +70,7 @@
 fn decode_json(
     json: &[u8],
     options: ffi::JsonOptions,
-    mut value_slot: ValueSlotRef,
+    value_slot: ValueSlotRef,
 ) -> Result<(), serde_jsonrc::Error> {
     let mut to_parse = json;
     if to_parse.len() >= 3 && to_parse[0..3] == UTF8_BOM {
@@ -94,7 +94,7 @@
     // absl::optional<base::Value> and also count the outermost dict,
     // therefore we start with -2 to match C++ behavior.
     let result =
-        deserializer.deserialize_any(ValueVisitor::new(&mut value_slot, options.max_depth - 2))?;
+        deserializer.deserialize_any(ValueVisitor::new(value_slot, options.max_depth - 2))?;
     deserializer.end()?;
     Ok(result)
 }
diff --git a/base/task/sequence_manager/task_queue.h b/base/task/sequence_manager/task_queue.h
index e45d64c..a8cd700a 100644
--- a/base/task/sequence_manager/task_queue.h
+++ b/base/task/sequence_manager/task_queue.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 
-#include "base/compiler_specific.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/common/checked_lock.h"
 #include "base/task/sequence_manager/lazy_now.h"
@@ -423,8 +422,8 @@
   // not be a null callback. Must be called on the thread this task queue is
   // associated with, and the handle returned must be destroyed on the same
   // thread.
-  std::unique_ptr<OnTaskPostedCallbackHandle> AddOnTaskPostedHandler(
-      OnTaskPostedHandler handler) WARN_UNUSED_RESULT;
+  [[nodiscard]] std::unique_ptr<OnTaskPostedCallbackHandle>
+  AddOnTaskPostedHandler(OnTaskPostedHandler handler);
 
   // Set a callback to fill trace event arguments associated with the task
   // execution.
diff --git a/base/task/sequence_manager/task_queue_impl.h b/base/task/sequence_manager/task_queue_impl.h
index c976e50..c1b9de7e 100644
--- a/base/task/sequence_manager/task_queue_impl.h
+++ b/base/task/sequence_manager/task_queue_impl.h
@@ -15,7 +15,6 @@
 #include <vector>
 
 #include "base/callback.h"
-#include "base/compiler_specific.h"
 #include "base/containers/flat_map.h"
 #include "base/containers/intrusive_heap.h"
 #include "base/memory/raw_ptr.h"
@@ -268,8 +267,8 @@
   // deadlocks. For example, PostTask should not be called directly and
   // ScopedDeferTaskPosting::PostOrDefer should be used instead. `handler` must
   // not be a null callback.
-  std::unique_ptr<TaskQueue::OnTaskPostedCallbackHandle> AddOnTaskPostedHandler(
-      OnTaskPostedHandler handler) WARN_UNUSED_RESULT;
+  [[nodiscard]] std::unique_ptr<TaskQueue::OnTaskPostedCallbackHandle>
+  AddOnTaskPostedHandler(OnTaskPostedHandler handler);
 
   // Set a callback to fill trace event arguments associated with the task
   // execution.
diff --git a/base/values_deserialization.rs b/base/values_deserialization.rs
index 697702d4..ee2f0937 100644
--- a/base/values_deserialization.rs
+++ b/base/values_deserialization.rs
@@ -24,7 +24,7 @@
 /// What type of `base::Value` container we're deserializing into.
 enum DeserializationTarget<'elem, 'container> {
     /// Deserialize into a brand new root `base::Value`.
-    NewValue { slot: &'elem mut ValueSlotRef<'container> },
+    NewValue { slot: ValueSlotRef<'container> },
     /// Deserialize by appending to a list.
     List { list: &'elem mut ListValueRef<'container> },
     /// Deserialize by setting a dictionary key.
@@ -64,7 +64,7 @@
     /// `base::Value`, then this visitor can be passed to serde deserialization
     /// libraries to populate it with a tree of contents.
     /// Any existing `base::Value` in the slot will be replaced.
-    pub fn new(slot: &'elem mut ValueSlotRef<'container>, mut max_depth: usize) -> Self {
+    pub fn new(slot: ValueSlotRef<'container>, mut max_depth: usize) -> Self {
         max_depth += 1; // we will increment this counter when deserializing
         // the initial `base::Value`. To match C++ behavior, we should
         // only start counting for subsequent layers, hence decrement
@@ -86,7 +86,7 @@
 
     fn visit_i32<E: serde::de::Error>(self, value: i32) -> Result<Self::Value, E> {
         match self.container {
-            DeserializationTarget::NewValue { slot } => slot.construct_integer(value),
+            DeserializationTarget::NewValue { mut slot } => slot.construct_integer(value),
             DeserializationTarget::List { list } => list.append_integer(value),
             DeserializationTarget::Dict { dict, key } => dict.set_integer_key(&key, value),
         };
@@ -99,7 +99,7 @@
 
     fn visit_bool<E: serde::de::Error>(self, value: bool) -> Result<Self::Value, E> {
         match self.container {
-            DeserializationTarget::NewValue { slot } => slot.construct_bool(value),
+            DeserializationTarget::NewValue { mut slot } => slot.construct_bool(value),
             DeserializationTarget::List { list } => list.append_bool(value),
             DeserializationTarget::Dict { dict, key } => dict.set_bool_key(&key, value),
         };
@@ -128,7 +128,7 @@
 
     fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<Self::Value, E> {
         match self.container {
-            DeserializationTarget::NewValue { slot } => slot.construct_double(value),
+            DeserializationTarget::NewValue { mut slot } => slot.construct_double(value),
             DeserializationTarget::List { list } => list.append_double(value),
             DeserializationTarget::Dict { dict, key } => dict.set_double_key(&key, value),
         };
@@ -137,7 +137,7 @@
 
     fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
         match self.container {
-            DeserializationTarget::NewValue { slot } => slot.construct_string(value),
+            DeserializationTarget::NewValue { mut slot } => slot.construct_string(value),
             DeserializationTarget::List { list } => list.append_string(value),
             DeserializationTarget::Dict { dict, key } => dict.set_string_key(&key, value),
         };
@@ -146,7 +146,7 @@
 
     fn visit_borrowed_str<E: serde::de::Error>(self, value: &'de str) -> Result<Self::Value, E> {
         match self.container {
-            DeserializationTarget::NewValue { slot } => slot.construct_string(value),
+            DeserializationTarget::NewValue { mut slot } => slot.construct_string(value),
             DeserializationTarget::List { list } => list.append_string(value),
             DeserializationTarget::Dict { dict, key } => dict.set_string_key(&key, value),
         };
@@ -159,7 +159,7 @@
 
     fn visit_none<E: serde::de::Error>(self) -> Result<Self::Value, E> {
         match self.container {
-            DeserializationTarget::NewValue { slot } => slot.construct_none(),
+            DeserializationTarget::NewValue { mut slot } => slot.construct_none(),
             DeserializationTarget::List { list } => list.append_none(),
             DeserializationTarget::Dict { dict, key } => dict.set_none_key(&key),
         };
@@ -170,12 +170,12 @@
         self.visit_none()
     }
 
-    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
+    fn visit_map<M>(mut self, mut access: M) -> Result<Self::Value, M::Error>
     where
         M: MapAccess<'de>,
     {
         let mut value = match self.container {
-            DeserializationTarget::NewValue { slot } => slot.construct_dict(),
+            DeserializationTarget::NewValue { ref mut slot } => slot.construct_dict(),
             DeserializationTarget::List { list } => list.append_dict(),
             DeserializationTarget::Dict { dict, key } => dict.set_dict_key(&key),
         };
@@ -190,12 +190,12 @@
         Ok(())
     }
 
-    fn visit_seq<S>(self, mut access: S) -> Result<Self::Value, S::Error>
+    fn visit_seq<S>(mut self, mut access: S) -> Result<Self::Value, S::Error>
     where
         S: SeqAccess<'de>,
     {
         let mut value = match self.container {
-            DeserializationTarget::NewValue { slot } => slot.construct_list(),
+            DeserializationTarget::NewValue { ref mut slot } => slot.construct_list(),
             DeserializationTarget::List { list } => list.append_list(),
             DeserializationTarget::Dict { dict, key } => dict.set_list_key(&key),
         };
@@ -231,8 +231,8 @@
     #[test]
     fn test_create() {
         let mut value_slot = NewValueSlot();
-        let mut value_slot = ValueSlotRef::from(&mut value_slot);
-        let _ = ValueVisitor::new(&mut value_slot, 12);
+        let value_slot = ValueSlotRef::from(&mut value_slot);
+        let _ = ValueVisitor::new(value_slot, 12);
         // Without introducing extra dependencies such as serde_test,
         // we can't do much to test the actual deserialization.
         // In any case, when deserializing JSON, this code will be
diff --git a/build/android/pylib/local/emulator/avd.py b/build/android/pylib/local/emulator/avd.py
index 6a5a49ef..f430214 100644
--- a/build/android/pylib/local/emulator/avd.py
+++ b/build/android/pylib/local/emulator/avd.py
@@ -41,6 +41,10 @@
 # The snapshot name to load/save when writable_system=False.
 # This is the default name used by the emulator binary.
 _DEFAULT_SNAPSHOT_NAME = 'default_boot'
+
+# Set long press timeout for clicking to 1000ms.
+_LONG_PRESS_TIMEOUT = '1000'
+
 # The snapshot name to load/save when writable_system=True
 _SYSTEM_SNAPSHOT_NAME = 'boot_with_system'
 
@@ -300,7 +304,8 @@
       debug_tags = 'init,snapshot' if snapshot else None
       # Installing privileged apks requires modifying the system image.
       writable_system = bool(privileged_apk_tuples)
-      instance.Start(read_only=False,
+      instance.Start(ensure_system_settings=False,
+                     read_only=False,
                      writable_system=writable_system,
                      gpu_mode=_DEFAULT_GPU_MODE,
                      debug_tags=debug_tags)
@@ -574,6 +579,7 @@
     return '%s|%s' % (self._avd_name, (self._emulator_serial or id(self)))
 
   def Start(self,
+            ensure_system_settings=True,
             read_only=True,
             window=False,
             writable_system=False,
@@ -581,6 +587,7 @@
             wipe_data=False,
             debug_tags=None):
     """Starts the emulator running an instance of the given AVD."""
+    is_slow_start = False
     # Force to load system snapshot if detected.
     if self.HasSystemSnapshot():
       if not writable_system:
@@ -592,6 +599,7 @@
                      'to load it properly.')
         read_only = False
     elif writable_system:
+      is_slow_start = True
       logging.warning('Emulator will be slow to start, as '
                       '"writable_system=True" but system snapshot not found.')
 
@@ -676,6 +684,15 @@
         # pylint: disable=W0707
         raise AvdException('Emulator failed to start: %s' % str(e))
 
+    assert self.device is not None, '`instance.device` not initialized.'
+    self.device.WaitUntilFullyBooted(timeout=120 if is_slow_start else 30)
+
+    # Set the system settings in "Start" here instead of setting in "Create"
+    # because "Create" is used during AVD creation, and we want to avoid extra
+    # turn-around on rolling AVD.
+    if ensure_system_settings:
+      _EnsureSystemSettings(self.device)
+
   def Stop(self):
     """Stops the emulator process."""
     if self._emulator_proc:
@@ -727,3 +744,22 @@
     if not self._emulator_device and self._emulator_serial:
       self._emulator_device = device_utils.DeviceUtils(self._emulator_serial)
     return self._emulator_device
+
+
+# TODO(crbug.com/1275767): Refactor it to a dict-based approach.
+def _EnsureSystemSettings(device):
+  set_long_press_timeout_cmd = [
+      'settings', 'put', 'secure', 'long_press_timeout', _LONG_PRESS_TIMEOUT
+  ]
+  device.RunShellCommand(set_long_press_timeout_cmd, check_return=True)
+
+  # Verify if long_press_timeout is set correctly.
+  get_long_press_timeout_cmd = [
+      'settings', 'get', 'secure', 'long_press_timeout'
+  ]
+  adb_output = device.RunShellCommand(get_long_press_timeout_cmd,
+                                      check_return=True)
+  if _LONG_PRESS_TIMEOUT in adb_output:
+    logging.info('long_press_timeout set to %r', _LONG_PRESS_TIMEOUT)
+  else:
+    logging.warning('long_press_timeout is not set correctly')
diff --git a/build/android/pylib/local/emulator/local_emulator_environment.py b/build/android/pylib/local/emulator/local_emulator_environment.py
index 56102bbc..1bca8ec 100644
--- a/build/android/pylib/local/emulator/local_emulator_environment.py
+++ b/build/android/pylib/local/emulator/local_emulator_environment.py
@@ -8,7 +8,6 @@
 from six.moves import range  # pylint: disable=redefined-builtin
 from devil import base_error
 from devil.android import device_errors
-from devil.android import device_utils
 from devil.utils import parallelizer
 from devil.utils import reraiser_thread
 from devil.utils import timeout_retry
@@ -62,8 +61,6 @@
         except avd.AvdException:
           logging.exception('Failed to start emulator instance.')
           return None
-        try:
-          device_utils.DeviceUtils(e.serial).WaitUntilFullyBooted()
         except base_error.BaseError:
           e.Stop()
           raise
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 6719dca..b127da1 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-7.20220120.2.1
+7.20220120.3.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 6719dca..b127da1 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-7.20220120.2.1
+7.20220120.3.1
diff --git a/build/toolchain/apple/toolchain.gni b/build/toolchain/apple/toolchain.gni
index 1e2339c43..9bc0b69 100644
--- a/build/toolchain/apple/toolchain.gni
+++ b/build/toolchain/apple/toolchain.gni
@@ -231,7 +231,7 @@
       tool("rust_staticlib") {
         rust_outfile = "{{target_out_dir}}/{{crate_name}}.a"
         depfile = "{{output}}.d"
-        command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"$_cc\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV {{rustenv}}"
+        command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"$_cxx\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV {{rustenv}}"
         description = "RUST $rust_outfile"
         rust_sysroot = rust_sysroot_relative_to_out
         outputs = [ rust_outfile ]
@@ -240,7 +240,7 @@
       tool("rust_rlib") {
         rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.rlib"
         depfile = "{{output}}.d"
-        command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"$_cc\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV {{rustenv}}"
+        command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"$_cxx\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV {{rustenv}}"
         description = "RUST $rust_outfile"
         rust_sysroot = rust_sysroot_relative_to_out
         outputs = [ rust_outfile ]
@@ -250,7 +250,7 @@
         tool("rust_bin") {
           rust_outfile = "{{root_out_dir}}/{{crate_name}}"
           depfile = "{{output}}.d"
-          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${_cc}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} RUSTENV {{rustenv}}"
+          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${_cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} RUSTENV {{rustenv}}"
           description = "RUST $rust_outfile"
           rust_sysroot = rust_sysroot_relative_to_out
           outputs = [ rust_outfile ]
@@ -259,7 +259,7 @@
         tool("rust_cdylib") {
           rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.dylib"
           depfile = "{{output}}.d"
-          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${_cc}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} RUSTENV {{rustenv}}"
+          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${_cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} RUSTENV {{rustenv}}"
           description = "RUST $rust_outfile"
           rust_sysroot = rust_sysroot_relative_to_out
           outputs = [ rust_outfile ]
@@ -268,7 +268,7 @@
         tool("rust_macro") {
           rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.dylib"
           depfile = "{{output}}.d"
-          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${_cc}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} RUSTENV {{rustenv}}"
+          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${_cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} RUSTENV {{rustenv}}"
           description = "RUST $rust_outfile"
           rust_sysroot = rust_sysroot_relative_to_out
           outputs = [ rust_outfile ]
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index 0ad4537a..5c5f61e 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -689,7 +689,7 @@
       tool("rust_staticlib") {
         rust_outfile = "{{target_out_dir}}/{{crate_name}}.a"
         depfile = "{{output}}.d"
-        command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cc}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV {{rustenv}}"
+        command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV {{rustenv}}"
         description = "RUST $rust_outfile"
         rust_sysroot = rust_sysroot_relative_to_out
         outputs = [ rust_outfile ]
@@ -698,7 +698,7 @@
       tool("rust_rlib") {
         rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.rlib"
         depfile = "{{output}}.d"
-        command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cc}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV {{rustenv}}"
+        command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV {{rustenv}}"
         description = "RUST $rust_outfile"
         rust_sysroot = rust_sysroot_relative_to_out
         outputs = [ rust_outfile ]
@@ -708,7 +708,7 @@
         tool("rust_bin") {
           rust_outfile = "{{root_out_dir}}/{{crate_name}}"
           depfile = "{{output}}.d"
-          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cc}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV {{rustenv}}"
+          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV {{rustenv}}"
           description = "RUST $rust_outfile"
           rust_sysroot = rust_sysroot_relative_to_out
           outputs = [ rust_outfile ]
@@ -717,7 +717,7 @@
         tool("rust_cdylib") {
           rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.so"
           depfile = "{{output}}.d"
-          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cc}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV {{rustenv}}"
+          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV {{rustenv}}"
           description = "RUST $rust_outfile"
           rust_sysroot = rust_sysroot_relative_to_out
           outputs = [ rust_outfile ]
@@ -726,7 +726,7 @@
         tool("rust_macro") {
           rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.so"
           depfile = "{{output}}.d"
-          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cc}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV {{rustenv}}"
+          command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV {{rustenv}}"
           description = "RUST $rust_outfile"
           rust_sysroot = rust_sysroot_relative_to_out
           outputs = [ rust_outfile ]
diff --git a/chrome/MAJOR_BRANCH_DATE b/chrome/MAJOR_BRANCH_DATE
index 79d20e84..8ed2580 100644
--- a/chrome/MAJOR_BRANCH_DATE
+++ b/chrome/MAJOR_BRANCH_DATE
@@ -1 +1 @@
-MAJOR_BRANCH_DATE=2021-12-10
+MAJOR_BRANCH_DATE=2022-01-21
diff --git a/chrome/VERSION b/chrome/VERSION
index 76d2b93..f22a8172 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
-MAJOR=99
+MAJOR=100
 MINOR=0
-BUILD=4844
+BUILD=4845
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkItemsAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkItemsAdapter.java
index 4f131ff..ab529caf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkItemsAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkItemsAdapter.java
@@ -356,11 +356,8 @@
         mDelegate.getSelectionDelegate().addObserver(this);
 
         Runnable promoHeaderChangeAction = () -> {
-            // If top level folders are not showing, update the header and notify.
-            // Otherwise, update header without notifying; we are going to update the bookmarks
-            // list, in case other top-level folders appeared because of the sync, and then
-            // redraw.
-            updateHeader(!topLevelFoldersShowing());
+            // Notify the view of changes to the elements list as the promo might be showing.
+            updateHeader(true);
         };
 
         mPromoHeaderManager = new BookmarkPromoHeader(mContext, promoHeaderChangeAction);
diff --git a/chrome/app/app_management_strings.grdp b/chrome/app/app_management_strings.grdp
index df3f7c48..3e76864 100644
--- a/chrome/app/app_management_strings.grdp
+++ b/chrome/app/app_management_strings.grdp
@@ -85,4 +85,19 @@
   <message name="IDS_APP_MANAGEMENT_INTENT_OVERLAP_DIALOG_TEXT_5_OR_MORE_APPS" desc="Text to explain to the user that 5 or more preferred apps will be replaced, and the text states the first 3 apps that will be replaced.">
     Other apps are set to open the same links as <ph name="APP_NAME">$1<ex>Gmail</ex></ph>. This will disable <ph name="APP_NAME_2">$2<ex>EmailReader</ex></ph>, <ph name="APP_NAME_3">$3<ex>PDFViewer</ex></ph>, <ph name="APP_NAME_4">$4<ex>Photos</ex></ph> and <ph name="NUMBER_OF_OTHER_APPS">$5<ex>2</ex></ph> other apps from opening supported links.
   </message>
+  <message name="IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_1_APP" desc="Text to explain to the user that an overlapping app will still open some supported links, and the text states which app will open links.">
+    Some supported links will still open in <ph name="APP_NAME">$1<ex>Gmail</ex></ph>.
+  </message>
+  <message name="IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_2_APPS" desc="Text to explain to the user that 2 overlapping apps will still open some supported links, and the text states which apps will open links.">
+    Some supported links will still open in <ph name="APP_NAME">$1<ex>Gmail</ex></ph> or  <ph name="APP_NAME_2">$2<ex>EmailReader</ex></ph>.
+  </message>
+  <message name="IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_3_APPS" desc="Text to explain to the user that 3 overlapping apps will still open some supported links, and the text states which apps will open links.">
+    Some supported links will still open in <ph name="APP_NAME">$1<ex>Gmail</ex></ph>, <ph name="APP_NAME_2">$2<ex>EmailReader</ex></ph> or <ph name="APP_NAME_3">$3<ex>PDFViewer</ex></ph>.
+  </message>
+  <message name="IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_4_APPS" desc="Text to explain to the user that 4 overlapping apps will still open some supported links, and the text states the first 3 apps that will open links.">
+    Some supported links will still open in <ph name="APP_NAME">$1<ex>Gmail</ex></ph>, <ph name="APP_NAME_2">$2<ex>EmailReader</ex></ph>, <ph name="APP_NAME_3">$3<ex>PDFViewer</ex></ph> and 1 other app.
+  </message>
+  <message name="IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_5_OR_MORE_APPS" desc="Text to explain to the user that 5 or more overlapping apps will still open some supported links, and the text states the first 3 apps that will open links.">
+    Some supported links will still open in <ph name="APP_NAME">$1<ex>Gmail</ex></ph>, <ph name="APP_NAME_2">$2<ex>EmailReader</ex></ph>, <ph name="APP_NAME_3">$3<ex>PDFViewer</ex></ph> and <ph name="NUMBER_OF_OTHER_APPS">$4<ex>2</ex></ph> other apps.
+  </message>
 </grit-part>
diff --git a/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_1_APP.png.sha1 b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_1_APP.png.sha1
new file mode 100644
index 0000000..d9565f9
--- /dev/null
+++ b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_1_APP.png.sha1
@@ -0,0 +1 @@
+279970c8659bffa61c1bf9733133e9bdd1d88506
\ No newline at end of file
diff --git a/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_2_APPS.png.sha1 b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_2_APPS.png.sha1
new file mode 100644
index 0000000..75786ef
--- /dev/null
+++ b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_2_APPS.png.sha1
@@ -0,0 +1 @@
+e5c32b948ab36054f9f4c11119d93c7d59eea939
\ No newline at end of file
diff --git a/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_3_APPS.png.sha1 b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_3_APPS.png.sha1
new file mode 100644
index 0000000..99b8545c
--- /dev/null
+++ b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_3_APPS.png.sha1
@@ -0,0 +1 @@
+ca204444123a40a2a3607b808c14539011643bed
\ No newline at end of file
diff --git a/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_4_APPS.png.sha1 b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_4_APPS.png.sha1
new file mode 100644
index 0000000..c8b2d35d
--- /dev/null
+++ b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_4_APPS.png.sha1
@@ -0,0 +1 @@
+65757089d576e9cd28c776beb82ca269be156979
\ No newline at end of file
diff --git a/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_5_OR_MORE_APPS.png.sha1 b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_5_OR_MORE_APPS.png.sha1
new file mode 100644
index 0000000..98ae9651
--- /dev/null
+++ b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_5_OR_MORE_APPS.png.sha1
@@ -0,0 +1 @@
+d8e15e94211f8f5feedb724686d43f5c1fc6d117
\ No newline at end of file
diff --git a/chrome/browser/alternative_error_page_override_info_browsertest.cc b/chrome/browser/alternative_error_page_override_info_browsertest.cc
new file mode 100644
index 0000000..f9cce27
--- /dev/null
+++ b/chrome/browser/alternative_error_page_override_info_browsertest.cc
@@ -0,0 +1,196 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
+#include "chrome/browser/web_applications/test/web_app_test_utils.h"
+#include "chrome/browser/web_applications/web_app_id.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/url_formatter/url_formatter.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/test_utils.h"
+#include "skia/ext/skia_utils_base.h"
+#include "url/gurl.h"
+
+// Class to test browser error page display info.
+class AlternativeErrorPageOverrideInfoBrowserTest
+    : public InProcessBrowserTest {
+ public:
+  AlternativeErrorPageOverrideInfoBrowserTest() = default;
+
+  // Helper function to prepare PWA and retrieve information from the
+  // alternative error page function.
+  content::mojom::AlternativeErrorPageOverrideInfoPtr GetErrorPageInfo(
+      std::string html) {
+    ChromeContentBrowserClient browser_client;
+    content::ScopedContentBrowserClientSetting setting(&browser_client);
+
+    const GURL app_url = embedded_test_server()->GetURL(html);
+    web_app::NavigateToURLAndWait(browser(), app_url);
+    web_app::test::InstallPwaForCurrentUrl(browser());
+    content::BrowserContext* context = browser()->profile();
+
+    return browser_client.GetAlternativeErrorPageOverrideInfo(app_url, context);
+  }
+
+ private:
+  void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
+  }
+
+  void TearDownOnMainThread() override {
+    InProcessBrowserTest::TearDownOnMainThread();
+  }
+
+  base::test::ScopedFeatureList feature_list_{
+      features::kDesktopPWAsDefaultOfflinePage};
+};
+
+// Testing app manifest with no theme or background color.
+IN_PROC_BROWSER_TEST_F(AlternativeErrorPageOverrideInfoBrowserTest, Manifest) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  content::mojom::AlternativeErrorPageOverrideInfoPtr info =
+      GetErrorPageInfo("/banners/manifest_no_service_worker.html");
+
+  // Expect mojom struct with default background and theme colors.
+  EXPECT_TRUE(info);
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("background_color"),
+            base::Value(skia::SkColorToHexString(SK_ColorWHITE)));
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("theme_color"),
+            base::Value(skia::SkColorToHexString(SK_ColorBLACK)));
+}
+
+// Testing app manifest with theme color.
+IN_PROC_BROWSER_TEST_F(AlternativeErrorPageOverrideInfoBrowserTest,
+                       ManifestWithThemeColor) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  content::mojom::AlternativeErrorPageOverrideInfoPtr info =
+      GetErrorPageInfo("/banners/theme-color.html");
+
+  // Expect mojom struct with customized theme color and default background
+  // color.
+  EXPECT_TRUE(info);
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("background_color"),
+            base::Value(skia::SkColorToHexString(SK_ColorWHITE)));
+  EXPECT_EQ(
+      *info->alternative_error_page_params.FindKey("theme_color"),
+      base::Value(skia::SkColorToHexString(SkColorSetRGB(0xAA, 0xCC, 0xEE))));
+}
+
+// Testing app manifest with background color.
+IN_PROC_BROWSER_TEST_F(AlternativeErrorPageOverrideInfoBrowserTest,
+                       ManifestWithBackgroundColor) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  content::mojom::AlternativeErrorPageOverrideInfoPtr info =
+      GetErrorPageInfo("/banners/background-color.html");
+
+  // Expect mojom struct with default theme color and customized background
+  // color.
+  EXPECT_TRUE(info);
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("background_color"),
+            base::Value(skia::SkColorToHexString(SK_ColorBLUE)));
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("theme_color"),
+            base::Value(skia::SkColorToHexString(SK_ColorBLACK)));
+}
+
+// Testing url outside the scope of an installed app.
+IN_PROC_BROWSER_TEST_F(AlternativeErrorPageOverrideInfoBrowserTest,
+                       NoManifest) {
+  ChromeContentBrowserClient browser_client;
+  content::ScopedContentBrowserClientSetting setting(&browser_client);
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  const GURL app_url = embedded_test_server()->GetURL("/simple.html");
+  content::BrowserContext* context = browser()->profile();
+
+  content::mojom::AlternativeErrorPageOverrideInfoPtr info =
+      browser_client.GetAlternativeErrorPageOverrideInfo(app_url, context);
+
+  // Expect mojom struct to be null.
+  EXPECT_FALSE(info);
+}
+
+// Testing manifest with app short name.
+IN_PROC_BROWSER_TEST_F(AlternativeErrorPageOverrideInfoBrowserTest,
+                       ManifestWithAppShortName) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  content::mojom::AlternativeErrorPageOverrideInfoPtr info = GetErrorPageInfo(
+      "/banners/"
+      "manifest_test_page.html?manifest=manifest_short_name_only.json");
+
+  // Expect mojom struct with custom app short name.
+  EXPECT_TRUE(info);
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("app_short_name"),
+            base::Value("Manifest"));
+}
+
+// Testing app manifest with no app short name.
+IN_PROC_BROWSER_TEST_F(AlternativeErrorPageOverrideInfoBrowserTest,
+                       ManifestWithNoAppShortName) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  content::mojom::AlternativeErrorPageOverrideInfoPtr info = GetErrorPageInfo(
+      "/banners/"
+      "manifest_test_page.html?manifest=manifest.json");
+
+  // Expect mojom struct with customized with app name.
+  EXPECT_TRUE(info);
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("app_short_name"),
+            base::Value("Manifest test app"));
+}
+
+// Testing app manifest with no app short name or app name.
+IN_PROC_BROWSER_TEST_F(AlternativeErrorPageOverrideInfoBrowserTest,
+                       ManifestWithNoAppShortNameOrAppName) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  content::mojom::AlternativeErrorPageOverrideInfoPtr info = GetErrorPageInfo(
+      "/banners/"
+      "manifest_test_page.html?manifest=manifest_empty_name_short_name.json");
+
+  // Expect mojom struct customized with HTML page title.
+  EXPECT_TRUE(info);
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("app_short_name"),
+            base::Value("Web app banner test page"));
+}
+
+// Testing app manifest with no app short name or app name, and HTML page
+// has no title
+IN_PROC_BROWSER_TEST_F(AlternativeErrorPageOverrideInfoBrowserTest,
+                       ManifestWithNoAppShortNameOrAppNameOrTitle) {
+  ChromeContentBrowserClient browser_client;
+  content::ScopedContentBrowserClientSetting setting(&browser_client);
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  const GURL app_url = embedded_test_server()->GetURL("/title1.html");
+  web_app::NavigateToURLAndWait(browser(), app_url);
+  web_app::test::InstallPwaForCurrentUrl(browser());
+  content::BrowserContext* context = browser()->profile();
+
+  content::mojom::AlternativeErrorPageOverrideInfoPtr info =
+      browser_client.GetAlternativeErrorPageOverrideInfo(app_url, context);
+
+  // Expect mojom struct customized with HTML page title.
+  EXPECT_TRUE(info);
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("app_short_name"),
+            base::Value(url_formatter::FormatUrl(app_url)));
+}
+
+// Testing app with manifest and no service worker.
+IN_PROC_BROWSER_TEST_F(AlternativeErrorPageOverrideInfoBrowserTest,
+                       ManifestAndNoServiceWorker) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  content::mojom::AlternativeErrorPageOverrideInfoPtr info =
+      GetErrorPageInfo("/banners/no-sw-with-colors.html");
+
+  // Expect mojom struct with custom theme and background color.
+  EXPECT_TRUE(info);
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("background_color"),
+            base::Value(skia::SkColorToHexString(SK_ColorYELLOW)));
+  EXPECT_EQ(*info->alternative_error_page_params.FindKey("theme_color"),
+            base::Value(skia::SkColorToHexString(SK_ColorGREEN)));
+}
diff --git a/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.cc b/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.cc
index 1285b06..3cd35f8 100644
--- a/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.cc
+++ b/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.cc
@@ -16,6 +16,8 @@
 RecommendedArcAppFetcher::~RecommendedArcAppFetcher() = default;
 
 void RecommendedArcAppFetcher::GetApps(ResultCallback callback) {
+  // Only one request can ever be made at a time.
+  DCHECK(!callback_);
   callback_ = std::move(callback);
   recommend_apps_fetcher_ = ash::RecommendAppsFetcher::Create(this);
   recommend_apps_fetcher_->Start();
@@ -83,16 +85,21 @@
     }
   }
   std::move(callback_).Run(std::move(results));
+  recommend_apps_fetcher_.reset();
 }
 
 void RecommendedArcAppFetcher::OnLoadError() {
-  if (callback_)
+  if (callback_) {
     std::move(callback_).Run({});
+    recommend_apps_fetcher_.reset();
+  }
 }
 
 void RecommendedArcAppFetcher::OnParseResponseError() {
-  if (callback_)
+  if (callback_) {
     std::move(callback_).Run({});
+    recommend_apps_fetcher_.reset();
+  }
 }
 
 void RecommendedArcAppFetcher::SetCallbackForTesting(ResultCallback callback) {
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index a10d5352..09c5528 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -5074,6 +5074,7 @@
             entry->metrics.begin()->second);
 }
 
+#if BUILDFLAG(ENABLE_PLUGINS)
 class WebViewPPAPITest : public WebViewTest {
  protected:
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -5089,6 +5090,7 @@
 IN_PROC_BROWSER_TEST_F(WebViewPPAPITest, Shim_TestPluginLoadPermission) {
   TestHelper("testPluginLoadPermission", "web_view/shim", NO_TEST_SERVER);
 }
+#endif  // BUILDFLAG(ENABLE_PLUGINS)
 
 // Helper class to set up a fake Chrome Web Store URL which can be loaded in
 // tests.
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator.cc b/chrome/browser/ash/crosapi/browser_data_migrator.cc
index 38b46ee..7b616c8 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator.cc
+++ b/chrome/browser/ash/crosapi/browser_data_migrator.cc
@@ -46,23 +46,11 @@
 // important since crypotohome conducts an aggressive disk cleanup if free disk
 // space becomes less than 768MB. The buffer is rounded up to 1GB.
 const int64_t kBuffer = (int64_t)1024 * 1024 * 1024;
-// Category names used for logging information about corresponding
-// vector<TargetItem> in TargetInfo.
-constexpr char kLacrosCategory[] = "lacros";
-constexpr char kCommonCategory[] = "common";
 }  // namespace
 
 CancelFlag::CancelFlag() : cancelled_(false) {}
 CancelFlag::~CancelFlag() = default;
 
-BrowserDataMigratorImpl::TargetInfo::TargetInfo()
-    : remain_in_ash_data_size(0),
-      deletable_data_size(0),
-      lacros_data_size(0),
-      common_data_size(0) {}
-BrowserDataMigratorImpl::TargetInfo::TargetInfo(TargetInfo&&) = default;
-BrowserDataMigratorImpl::TargetInfo::~TargetInfo() = default;
-
 BrowserDataMigratorImpl::TargetItem::TargetItem(base::FilePath path,
                                                 int64_t size,
                                                 ItemType item_type)
@@ -74,14 +62,9 @@
          this->is_directory == rhs.is_directory;
 }
 
-int64_t BrowserDataMigratorImpl::TargetInfo::TotalCopySize() const {
-  return lacros_data_size + common_data_size;
-}
-
-int64_t BrowserDataMigratorImpl::TargetInfo::TotalDirSize() const {
-  return deletable_data_size + remain_in_ash_data_size + lacros_data_size +
-         common_data_size;
-}
+BrowserDataMigratorImpl::TargetItems::TargetItems() : total_size(0) {}
+BrowserDataMigratorImpl::TargetItems::TargetItems(TargetItems&&) = default;
+BrowserDataMigratorImpl::TargetItems::~TargetItems() = default;
 
 // static
 bool BrowserDataMigratorImpl::MaybeRestartToMigrate(
@@ -295,56 +278,24 @@
 }
 
 // static
-void BrowserDataMigratorImpl::RecordStatus(const FinalStatus& final_status,
-                                           const TargetInfo* target_info,
-                                           const base::ElapsedTimer* timer) {
-  // Record final status enum.
-  UMA_HISTOGRAM_ENUMERATION(kFinalStatus, final_status);
-
-  if (!target_info)
-    return;
-  // Record byte size. Range 0 ~ 10GB in MBs.
-  UMA_HISTOGRAM_CUSTOM_COUNTS(kCopiedDataSize,
-                              target_info->TotalCopySize() / 1024 / 1024, 1,
-                              10000, 100);
-  UMA_HISTOGRAM_CUSTOM_COUNTS(
-      kAshDataSize, target_info->remain_in_ash_data_size / 1024 / 1024, 1,
-      10000, 100);
-  UMA_HISTOGRAM_CUSTOM_COUNTS(kLacrosDataSize,
-                              target_info->lacros_data_size / 1024 / 1024, 1,
-                              10000, 100);
-  UMA_HISTOGRAM_CUSTOM_COUNTS(kCommonDataSize,
-                              target_info->common_data_size / 1024 / 1024, 1,
-                              10000, 100);
-  UMA_HISTOGRAM_CUSTOM_COUNTS(kNoCopyDataSize,
-                              target_info->deletable_data_size / 1024 / 1024, 1,
-                              10000, 100);
-
-  if (!timer || final_status != FinalStatus::kSuccess)
-    return;
-  // Record elapsed time only for successful cases.
-  UMA_HISTOGRAM_MEDIUM_TIMES(kTotalTime, timer->Elapsed());
-}
-
-// static
 // TODO(crbug.com/1178702): Once testing phase is over and lacros becomes the
 // only web browser, update the underlying logic of migration from copy to move.
 // Note that during testing phase we are copying files and leaving files in
 // original location intact. We will allow these two states to diverge.
 BrowserDataMigratorImpl::MigrationResult
 BrowserDataMigratorImpl::MigrateInternal(
-    const base::FilePath& original_user_dir,
+    const base::FilePath& original_profile_dir,
     std::unique_ptr<MigrationProgressTracker> progress_tracker,
     scoped_refptr<CancelFlag> cancel_flag) {
   ResultValue data_wipe_result = ResultValue::kSkipped;
 
-  const base::FilePath tmp_dir = original_user_dir.Append(kTmpDir);
-  const base::FilePath new_user_dir = original_user_dir.Append(kLacrosDir);
+  const base::FilePath tmp_dir = original_profile_dir.Append(kTmpDir);
+  const base::FilePath new_user_dir = original_profile_dir.Append(kLacrosDir);
 
   if (base::DirectoryExists(new_user_dir)) {
     if (!base::DeletePathRecursively(new_user_dir)) {
       PLOG(ERROR) << "Deleting " << new_user_dir.value() << " failed: ";
-      RecordStatus(FinalStatus::kDataWipeFailed);
+      UMA_HISTOGRAM_ENUMERATION(kFinalStatus, FinalStatus::kDataWipeFailed);
       return {ResultValue::kFailed, ResultValue::kFailed};
     }
     data_wipe_result = ResultValue::kSucceeded;
@@ -357,34 +308,39 @@
                     "previous attempt.";
     if (!base::DeletePathRecursively(tmp_dir)) {
       PLOG(ERROR) << "Failed to delete tmp dir";
-      RecordStatus(FinalStatus::kDeleteTmpDirFailed);
+      UMA_HISTOGRAM_ENUMERATION(kFinalStatus, FinalStatus::kDeleteTmpDirFailed);
       return {data_wipe_result, ResultValue::kFailed};
     }
   }
 
-  TargetInfo target_info = GetTargetInfo(original_user_dir);
-  progress_tracker->SetTotalSizeToCopy(target_info.TotalCopySize());
+  TargetItems need_copy_items =
+      GetTargetItems(original_profile_dir, ItemType::kNeedCopy);
+  TargetItems lacros_items =
+      GetTargetItems(original_profile_dir, ItemType::kLacros);
+  const int64_t total_copy_size =
+      need_copy_items.total_size + lacros_items.total_size;
+  progress_tracker->SetTotalSizeToCopy(total_copy_size);
 
   base::ElapsedTimer timer;
 
-  if (!HasEnoughDiskSpace(target_info, original_user_dir, Mode::kCopy)) {
-    RecordStatus(FinalStatus::kNotEnoughSpace, &target_info);
+  if (!HasEnoughDiskSpace(total_copy_size, original_profile_dir)) {
+    UMA_HISTOGRAM_ENUMERATION(kFinalStatus, FinalStatus::kNotEnoughSpace);
     return {data_wipe_result, ResultValue::kFailed};
   }
 
   // Copy files to `tmp_dir`.
-  if (!SetupTmpDir(target_info, original_user_dir, tmp_dir, cancel_flag.get(),
+  if (!SetupTmpDir(lacros_items, need_copy_items, tmp_dir, cancel_flag.get(),
                    progress_tracker.get())) {
     if (base::PathExists(tmp_dir)) {
       base::DeletePathRecursively(tmp_dir);
     }
     if (cancel_flag->IsSet()) {
       LOG(WARNING) << "Migration was cancelled.";
-      RecordStatus(FinalStatus::kCancelled, &target_info);
+      UMA_HISTOGRAM_ENUMERATION(kFinalStatus, FinalStatus::kCancelled);
       return {data_wipe_result, ResultValue::kCancelled};
     }
 
-    RecordStatus(FinalStatus::kCopyFailed, &target_info);
+    UMA_HISTOGRAM_ENUMERATION(kFinalStatus, FinalStatus::kCopyFailed);
     return {data_wipe_result, ResultValue::kFailed};
   }
 
@@ -394,14 +350,23 @@
     if (base::PathExists(tmp_dir)) {
       base::DeletePathRecursively(tmp_dir);
     }
-    RecordStatus(FinalStatus::kMoveFailed, &target_info);
+    UMA_HISTOGRAM_ENUMERATION(kFinalStatus, FinalStatus::kMoveFailed);
     return {data_wipe_result, ResultValue::kFailed};
   }
 
   LOG(WARNING) << "BrowserDataMigratorImpl::Migrate took "
                << timer.Elapsed().InMilliseconds() << " ms and migrated "
-               << target_info.TotalCopySize() / (1024 * 1024) << " MB.";
-  RecordStatus(FinalStatus::kSuccess, &target_info, &timer);
+               << total_copy_size / (1024 * 1024) << " MB.";
+  UMA_HISTOGRAM_ENUMERATION(kFinalStatus, FinalStatus::kSuccess);
+  // Record elapsed time for a successful migration.
+  // Record byte size. Range 0 ~ 10GB in MBs.
+  UMA_HISTOGRAM_CUSTOM_COUNTS(kCopiedDataSize, total_copy_size / 1024 / 1024, 1,
+                              10000, 100);
+  UMA_HISTOGRAM_CUSTOM_COUNTS(
+      kLacrosDataSize, lacros_items.total_size / 1024 / 1024, 1, 10000, 100);
+  UMA_HISTOGRAM_CUSTOM_COUNTS(
+      kCommonDataSize, need_copy_items.total_size / 1024 / 1024, 1, 10000, 100);
+  UMA_HISTOGRAM_MEDIUM_TIMES(kTotalTime, timer.Elapsed());
   return {data_wipe_result, ResultValue::kSucceeded};
 }
 
@@ -465,11 +430,29 @@
 }
 
 // static
-BrowserDataMigratorImpl::TargetInfo BrowserDataMigratorImpl::GetTargetInfo(
-    const base::FilePath& original_user_dir) {
-  TargetInfo target_info;
+BrowserDataMigratorImpl::TargetItems BrowserDataMigratorImpl::GetTargetItems(
+    const base::FilePath& original_profile_dir,
+    const ItemType type) {
+  base::span<const char* const> target_paths;
+  switch (type) {
+    case ItemType::kLacros:
+      target_paths = base::span<const char* const>(kLacrosDataPaths);
+      break;
+    case ItemType::kRemainInAsh:
+      target_paths = base::span<const char* const>(kRemainInAshDataPaths);
+      break;
+    case ItemType::kDeletable:
+      target_paths = base::span<const char* const>(kDeletablePaths);
+      break;
+    case ItemType::kNeedCopy:
+      target_paths = base::span<const char* const>(kNeedCopyDataPaths);
+      break;
+    default:
+      NOTREACHED();
+  }
 
-  base::FileEnumerator enumerator(original_user_dir, false /* recursive */,
+  TargetItems target_items;
+  base::FileEnumerator enumerator(original_profile_dir, false /* recursive */,
                                   base::FileEnumerator::FILES |
                                       base::FileEnumerator::DIRECTORIES |
                                       base::FileEnumerator::SHOW_SYM_LINKS);
@@ -490,61 +473,24 @@
       continue;
     }
 
-    if (base::Contains(kRemainInAshDataPaths, entry.BaseName().value())) {
-      target_info.remain_in_ash_items.emplace_back(
-          TargetItem{entry, size, item_type});
-      target_info.remain_in_ash_data_size += size;
-    } else if (base::Contains(kDeletablePaths, entry.BaseName().value())) {
-      target_info.deletable_items.emplace_back(
-          TargetItem{entry, size, item_type});
-      target_info.deletable_data_size += size;
-    } else if (base::Contains(kLacrosDataPaths, entry.BaseName().value())) {
-      // Items that should be moved to lacros.
-      target_info.lacros_data_items.emplace_back(
-          TargetItem{entry, size, item_type});
-      target_info.lacros_data_size += size;
-    } else if (base::Contains(kCommonDataPaths, entry.BaseName().value())) {
-      // Items that are not explicitly ash, deletable_data or lacros are put
-      // into common category.
-      target_info.common_data_items.emplace_back(
-          TargetItem{entry, size, item_type});
-      target_info.common_data_size += size;
+    if (base::Contains(target_paths, entry.BaseName().value())) {
+      target_items.total_size += size;
+      target_items.items.emplace_back(TargetItem{entry, size, item_type});
     }
   }
 
-  return target_info;
+  return target_items;
 }
 
 // static
 bool BrowserDataMigratorImpl::HasEnoughDiskSpace(
-    const TargetInfo& target_info,
-    const base::FilePath& original_user_dir,
-    Mode mode) {
+    const int64_t total_copy_size,
+    const base::FilePath& original_profile_dir) {
   const int64_t free_disk_space =
-      base::SysInfo::AmountOfFreeDiskSpace(original_user_dir);
+      base::SysInfo::AmountOfFreeDiskSpace(original_profile_dir);
 
-  int64_t required_space;
-  switch (mode) {
-    case Mode::kMove:
-      required_space = target_info.common_data_size;
-      break;
-    case Mode::kDeleteAndCopy:
-      required_space =
-          target_info.TotalCopySize() - target_info.deletable_data_size;
-      break;
-    case Mode::kDeleteAndMove:
-      required_space =
-          target_info.common_data_size - target_info.deletable_data_size;
-      break;
-    case Mode::kCopy:
-    default:
-      DCHECK_EQ(mode, Mode::kCopy);
-      required_space = target_info.TotalCopySize();
-      break;
-  }
-
-  if (free_disk_space < required_space + kBuffer) {
-    LOG(ERROR) << "Aborting migration. Need " << required_space
+  if (free_disk_space < total_copy_size + kBuffer) {
+    LOG(ERROR) << "Aborting migration. Need " << total_copy_size + kBuffer
                << " bytes but only have " << free_disk_space << " bytes left.";
     return false;
   }
@@ -598,13 +544,10 @@
 // static
 bool BrowserDataMigratorImpl::CopyTargetItems(
     const base::FilePath& to_dir,
-    const std::vector<TargetItem>& items,
+    const TargetItems& target_items,
     CancelFlag* cancel_flag,
-    int64_t items_size,
-    base::StringPiece category_name,
     MigrationProgressTracker* progress_tracker) {
-  base::ElapsedTimer timer;
-  for (const auto& item : items) {
+  for (const auto& item : target_items.items) {
     if (cancel_flag->IsSet())
       return false;
 
@@ -613,28 +556,14 @@
       return false;
     }
   }
-  base::TimeDelta elapsed_time = timer.Elapsed();
-  // TODO(crbug.com/1178702): Once BrowserDataMigrator stabilises, reduce the
-  // log level to VLOG(1).
-  LOG(WARNING) << "Copied " << items_size / (1024 * 1024) << " MB of "
-               << category_name << " data and it took "
-               << elapsed_time.InMilliseconds() << " ms.";
-
-  if (category_name == kLacrosCategory) {
-    UMA_HISTOGRAM_MEDIUM_TIMES(kLacrosDataTime, elapsed_time);
-  } else if (category_name == kCommonCategory) {
-    UMA_HISTOGRAM_MEDIUM_TIMES(kCommonDataTime, elapsed_time);
-  } else {
-    NOTREACHED();
-  }
 
   return true;
 }
 
 // static
 bool BrowserDataMigratorImpl::SetupTmpDir(
-    const TargetInfo& target_info,
-    const base::FilePath& from_dir,
+    const TargetItems& lacros_items,
+    const TargetItems& need_copy_items,
     const base::FilePath& tmp_dir,
     CancelFlag* cancel_flag,
     MigrationProgressTracker* progress_tracker) {
@@ -649,17 +578,21 @@
   }
 
   // Copy lacros items.
-  if (!CopyTargetItems(tmp_dir.Append(kLacrosProfilePath),
-                       target_info.lacros_data_items, cancel_flag,
-                       target_info.lacros_data_size, kLacrosCategory,
-                       progress_tracker))
+  base::ElapsedTimer timer_for_lacros_items;
+  if (!CopyTargetItems(tmp_dir.Append(kLacrosProfilePath), lacros_items,
+                       cancel_flag, progress_tracker)) {
     return false;
+  }
+  UMA_HISTOGRAM_MEDIUM_TIMES(kLacrosDataTime, timer_for_lacros_items.Elapsed());
+
   // Copy common items.
-  if (!CopyTargetItems(tmp_dir.Append(kLacrosProfilePath),
-                       target_info.common_data_items, cancel_flag,
-                       target_info.common_data_size, kCommonCategory,
-                       progress_tracker))
+  base::ElapsedTimer timer_for_need_copy_items;
+  if (!CopyTargetItems(tmp_dir.Append(kLacrosProfilePath), need_copy_items,
+                       cancel_flag, progress_tracker)) {
     return false;
+  }
+  UMA_HISTOGRAM_MEDIUM_TIMES(kCommonDataTime,
+                             timer_for_need_copy_items.Elapsed());
 
   // Create `First Run` sentinel file instead of copying. This avoids copying
   // from outside of cryptohome.
@@ -728,40 +661,53 @@
 // static
 void BrowserDataMigratorImpl::DryRunToCollectUMA(
     const base::FilePath& profile_data_dir) {
-  TargetInfo target_info = GetTargetInfo(profile_data_dir);
+  TargetItems lacros_items =
+      GetTargetItems(profile_data_dir, ItemType::kLacros);
+  TargetItems need_copy_items =
+      GetTargetItems(profile_data_dir, ItemType::kNeedCopy);
+  TargetItems remain_in_ash_items =
+      GetTargetItems(profile_data_dir, ItemType::kRemainInAsh);
+  TargetItems deletable_items =
+      GetTargetItems(profile_data_dir, ItemType::kDeletable);
 
   base::UmaHistogramCustomCounts(kDryRunNoCopyDataSize,
-                                 target_info.deletable_data_size / 1024 / 1024,
+                                 deletable_items.total_size / 1024 / 1024, 1,
+                                 10000, 100);
+  base::UmaHistogramCustomCounts(kDryRunAshDataSize,
+                                 remain_in_ash_items.total_size / 1024 / 1024,
                                  1, 10000, 100);
-  base::UmaHistogramCustomCounts(
-      kDryRunAshDataSize, target_info.remain_in_ash_data_size / 1024 / 1024, 1,
-      10000, 100);
   base::UmaHistogramCustomCounts(kDryRunLacrosDataSize,
-                                 target_info.lacros_data_size / 1024 / 1024, 1,
+                                 lacros_items.total_size / 1024 / 1024, 1,
                                  10000, 100);
   base::UmaHistogramCustomCounts(kDryRunCommonDataSize,
-                                 target_info.common_data_size / 1024 / 1024, 1,
+                                 need_copy_items.total_size / 1024 / 1024, 1,
                                  10000, 100);
 
-  browser_data_migrator_util::RecordTotalSize(target_info.TotalDirSize());
+  browser_data_migrator_util::RecordTotalSize(
+      need_copy_items.total_size + lacros_items.total_size +
+      remain_in_ash_items.total_size + deletable_items.total_size);
 
-  RecordTargetItemSizes(target_info.deletable_items);
-  RecordTargetItemSizes(target_info.remain_in_ash_items);
-  RecordTargetItemSizes(target_info.lacros_data_items);
-  RecordTargetItemSizes(target_info.common_data_items);
+  RecordTargetItemSizes(deletable_items.items);
+  RecordTargetItemSizes(remain_in_ash_items.items);
+  RecordTargetItemSizes(lacros_items.items);
+  RecordTargetItemSizes(need_copy_items.items);
 
   base::UmaHistogramBoolean(
       kDryRunCopyMigrationHasEnoughDiskSpace,
-      HasEnoughDiskSpace(target_info, profile_data_dir, Mode::kCopy));
+      HasEnoughDiskSpace(lacros_items.total_size + need_copy_items.total_size,
+                         profile_data_dir));
   base::UmaHistogramBoolean(
       kDryRunMoveMigrationHasEnoughDiskSpace,
-      HasEnoughDiskSpace(target_info, profile_data_dir, Mode::kMove));
+      HasEnoughDiskSpace(need_copy_items.total_size, profile_data_dir));
   base::UmaHistogramBoolean(
       kDryRunDeleteAndCopyMigrationHasEnoughDiskSpace,
-      HasEnoughDiskSpace(target_info, profile_data_dir, Mode::kDeleteAndCopy));
-  base::UmaHistogramBoolean(
-      kDryRunDeleteAndMoveMigrationHasEnoughDiskSpace,
-      HasEnoughDiskSpace(target_info, profile_data_dir, Mode::kDeleteAndMove));
+      HasEnoughDiskSpace(lacros_items.total_size + need_copy_items.total_size -
+                             deletable_items.total_size,
+                         profile_data_dir));
+  base::UmaHistogramBoolean(kDryRunDeleteAndMoveMigrationHasEnoughDiskSpace,
+                            HasEnoughDiskSpace(need_copy_items.total_size -
+                                                   deletable_items.total_size,
+                                               profile_data_dir));
 }
 
 // staic
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator.h b/chrome/browser/ash/crosapi/browser_data_migrator.h
index 12ec54c..68f69b6 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator.h
+++ b/chrome/browser/ash/crosapi/browser_data_migrator.h
@@ -159,12 +159,12 @@
 // The base names of files/dirs that are required by both ash and lacros and
 // thus should be copied to lacros while keeping the original files/dirs in ash
 // data dir.
-constexpr const char* const kCommonDataPaths[]{"Affiliation Database",
-                                               "Login Data",
-                                               "Platform Notifications",
-                                               "Policy",
-                                               "Preferences",
-                                               "shared_proto_db"};
+constexpr const char* const kNeedCopyDataPaths[]{"Affiliation Database",
+                                                 "Login Data",
+                                                 "Platform Notifications",
+                                                 "Policy",
+                                                 "Preferences",
+                                                 "shared_proto_db"};
 
 // Local state pref name, which is used to keep track of what step migration is
 // at. This ensures that ash does not get repeatedly for migration.
@@ -215,7 +215,8 @@
 // from ash-chrome to lacros-chrome.
 class BrowserDataMigratorImpl : public BrowserDataMigrator {
  public:
-  // Used to describe a file/dir that has to be migrated.
+  // This is used to describe top level entries inside ash-chrome's profile data
+  // directory.
   struct TargetItem {
     enum class ItemType { kFile, kDirectory };
     TargetItem(base::FilePath path, int64_t size, ItemType item_type);
@@ -229,37 +230,15 @@
     bool is_directory;
   };
 
-  // Used to describe what files/dirs have to be migrated to the new location
-  // and the total byte size of those files.
-  struct TargetInfo {
-    TargetInfo();
-    ~TargetInfo();
-    TargetInfo(TargetInfo&&);
+  // `TargetItems` should hold `TargetItem`s of the same `ItemType`.
+  struct TargetItems {
+    TargetItems();
+    ~TargetItems();
+    TargetItems(TargetItems&&);
 
-    // Items that should stay in ash data dir.
-    std::vector<TargetItem> remain_in_ash_items;
-    // Items that should be moved to lacros data dir.
-    std::vector<TargetItem> lacros_data_items;
-    // Items that will be duplicated in both ash and lacros data dir.
-    std::vector<TargetItem> common_data_items;
-    // Items that can be deleted from both ash and lacros data dir.
-    std::vector<TargetItem> deletable_items;
-    // The total size of `remain_in_ash_items`.
-    int64_t remain_in_ash_data_size;
-    // The total size of items that can be deleted during the migration.
-    int64_t deletable_data_size;
-    // The total size of `lacros_data_items`.
-    int64_t lacros_data_size;
-    // The total size of `common_data_items`.
-    int64_t common_data_size;
-    // The size of data that are duplicated. Equivalent to the extra space
-    // needed for the migration. Currently this is equal to `lacros_data_size +
-    // common_data_size` since we are copying lacros data rather than moving
-    // them.
-    int64_t TotalCopySize() const;
-    // The total size of the profile data directory. It is the sum of ash,
-    // no_copy, lacros and common sizes.
-    int64_t TotalDirSize() const;
+    std::vector<TargetItem> items;
+    // The sum of the sizes of `TargetItem`s in `items`.
+    int64_t total_size;
   };
 
   // These values are persisted to logs. Entries should not be renumbered and
@@ -310,6 +289,14 @@
                          // TargetInfo::no_copy_items to make extra space.
   };
 
+  // Specifies the type of `TargetItem`
+  enum class ItemType {
+    kLacros = 0,       // Item that should be moved to lacros profile directory.
+    kRemainInAsh = 1,  // Item that should remain in ash.
+    kNeedCopy = 2,     // Item that should be copied to lacros.
+    kDeletable = 3,    // Item that can be deleted to free up space i.e. cache.
+  };
+
   // `BrowserDataMigratorImpl` migrates browser data from `original_profile_dir`
   // to a new profile location for lacros chrome. `progress_callback` is called
   // to update the progress bar on the screen. `completion_callback` passed as
@@ -351,11 +338,10 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest,
                            ManipulateMigrationAttemptCount);
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, GetTargetInfo);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, GetTargetItems);
   FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, CopyDirectory);
   FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, SetupTmpDir);
   FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, CancelSetupTmpDir);
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, RecordStatus);
   FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, MigrateInternal);
   FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, Migrate);
   FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, MigrateCancelled);
@@ -388,7 +374,7 @@
   // wipe and migration. `progress_callback` gets posted on UI thread whenever
   // an update to the UI is required
   static MigrationResult MigrateInternal(
-      const base::FilePath& original_user_dir,
+      const base::FilePath& original_profile_dir,
       std::unique_ptr<MigrationProgressTracker> progress_tracker,
       scoped_refptr<CancelFlag> cancel_flag);
 
@@ -400,36 +386,28 @@
   // Called on UI thread once migration is finished.
   void MigrateInternalFinishedUIThread(MigrationResult result);
 
-  // Records to UMA histograms. Note that if `target_info` is nullptr, timer
-  // will be ignored.
-  static void RecordStatus(const FinalStatus& final_status,
-                           const TargetInfo* target_info = nullptr,
-                           const base::ElapsedTimer* timer = nullptr);
+  // It enumerates the file/dirs in the given directory and returns items of
+  // `type`. E.g. `GetTargetItems(path, ItemType::kLacros)` will get all items
+  // that should be moved to lacros.
+  static TargetItems GetTargetItems(const base::FilePath& original_profile_dir,
+                                    const ItemType type);
 
-  // Create `TargetInfo` from `original_user_dir`. `TargetInfo` will include
-  // paths of files/dirs that needs to be migrated.
-  static TargetInfo GetTargetInfo(const base::FilePath& original_user_dir);
-
-  // Compares space available for `to_dir` against total byte size that
-  // needs to be copied.
-  static bool HasEnoughDiskSpace(const TargetInfo& target_info,
-                                 const base::FilePath& original_user_dir,
-                                 Mode mode);
+  // Compares space available for `original_profile_dir` against total byte size
+  // that needs to be copied.
+  static bool HasEnoughDiskSpace(const int64_t total_copy_size,
+                                 const base::FilePath& original_profile_dir);
 
   // Set up the temporary directory `tmp_dir` by copying items into it.
-  static bool SetupTmpDir(const TargetInfo& target_info,
-                          const base::FilePath& from_dir,
+  static bool SetupTmpDir(const TargetItems& lacros_items,
+                          const TargetItems& common_items,
                           const base::FilePath& tmp_dir,
                           CancelFlag* cancel_flag,
                           MigrationProgressTracker* progress_tracker);
 
-  // Copies `items` to `to_dir`. `items_size` and `category_name` are used for
-  // logging.
+  // Copies `items` to `to_dir`.
   static bool CopyTargetItems(const base::FilePath& to_dir,
-                              const std::vector<TargetItem>& items,
+                              const TargetItems& items,
                               CancelFlag* cancel_flag,
-                              int64_t items_size,
-                              base::StringPiece category_name,
                               MigrationProgressTracker* progress_tracker);
 
   // Copies `item` to location pointed by `dest`. Returns true on success and
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc b/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
index ad096a2b..475cd8cf 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
@@ -56,7 +56,7 @@
   base::span<const char* const> deletable_paths =
       base::make_span(kDeletablePaths);
   base::span<const char* const> common_data_paths =
-      base::make_span(kCommonDataPaths);
+      base::make_span(kNeedCopyDataPaths);
 
   std::vector<base::span<const char* const>> paths_groups{
       remain_in_ash_paths, lacros_data_paths, deletable_paths,
@@ -177,15 +177,25 @@
             0);
 }
 
-TEST_F(BrowserDataMigratorImplTest, GetTargetInfo) {
-  BrowserDataMigratorImpl::TargetInfo target_info =
-      BrowserDataMigratorImpl::GetTargetInfo(from_dir_);
+TEST_F(BrowserDataMigratorImplTest, GetTargetItems) {
+  BrowserDataMigratorImpl::TargetItems lacros_items =
+      BrowserDataMigratorImpl::GetTargetItems(
+          from_dir_, BrowserDataMigratorImpl::ItemType::kLacros);
+  BrowserDataMigratorImpl::TargetItems need_copy_items =
+      BrowserDataMigratorImpl::GetTargetItems(
+          from_dir_, BrowserDataMigratorImpl::ItemType::kNeedCopy);
+  BrowserDataMigratorImpl::TargetItems remain_in_ash_items =
+      BrowserDataMigratorImpl::GetTargetItems(
+          from_dir_, BrowserDataMigratorImpl::ItemType::kRemainInAsh);
+  BrowserDataMigratorImpl::TargetItems deletable_items =
+      BrowserDataMigratorImpl::GetTargetItems(
+          from_dir_, BrowserDataMigratorImpl::ItemType::kDeletable);
 
-  EXPECT_EQ(target_info.remain_in_ash_data_size,
+  EXPECT_EQ(remain_in_ash_items.total_size,
             kFileSize * 2 /* expect two files */);
-  EXPECT_EQ(target_info.deletable_data_size, kFileSize /* expect one file */);
-  EXPECT_EQ(target_info.lacros_data_size, kFileSize * 2 /* expect two files */);
-  EXPECT_EQ(target_info.common_data_size, kFileSize * 2 /* expect two file */);
+  EXPECT_EQ(deletable_items.total_size, kFileSize /* expect one file */);
+  EXPECT_EQ(lacros_items.total_size, kFileSize * 2 /* expect two files */);
+  EXPECT_EQ(need_copy_items.total_size, kFileSize * 2 /* expect two file */);
 
   // Check for ash data.
   std::vector<BrowserDataMigratorImpl::TargetItem>
@@ -194,44 +204,50 @@
            BrowserDataMigratorImpl::TargetItem::ItemType::kDirectory},
           {from_dir_.Append(kFullRestoreData), kFileSize,
            BrowserDataMigratorImpl::TargetItem::ItemType::kFile}};
-  std::sort(target_info.remain_in_ash_items.begin(),
-            target_info.remain_in_ash_items.end(), TargetItemComparator());
-  ASSERT_EQ(target_info.remain_in_ash_items.size(),
+  std::sort(remain_in_ash_items.items.begin(), remain_in_ash_items.items.end(),
+            TargetItemComparator());
+  ASSERT_EQ(remain_in_ash_items.items.size(),
             expected_remain_in_ash_items.size());
-  for (int i = 0; i < target_info.remain_in_ash_items.size(); i++) {
-    SCOPED_TRACE(target_info.remain_in_ash_items[i].path.value());
-    EXPECT_EQ(target_info.remain_in_ash_items[i],
-              expected_remain_in_ash_items[i]);
+  for (int i = 0; i < remain_in_ash_items.items.size(); i++) {
+    SCOPED_TRACE(remain_in_ash_items.items[i].path.value());
+    EXPECT_EQ(remain_in_ash_items.items[i], expected_remain_in_ash_items[i]);
   }
 
   // Check for lacros data.
-  std::vector<BrowserDataMigratorImpl::TargetItem> expected_lacros_data_items =
-      {
-          {from_dir_.Append(kBookmarks), kFileSize,
-           BrowserDataMigratorImpl::TargetItem::ItemType::kFile},
-          {from_dir_.Append(kCookies), kFileSize,
-           BrowserDataMigratorImpl::TargetItem::ItemType::kFile},
-      };
-  ASSERT_EQ(target_info.lacros_data_items.size(),
-            expected_lacros_data_items.size());
-  std::sort(target_info.lacros_data_items.begin(),
-            target_info.lacros_data_items.end(), TargetItemComparator());
-  for (int i = 0; i < target_info.common_data_items.size(); i++) {
-    SCOPED_TRACE(target_info.lacros_data_items[i].path.value());
-    EXPECT_EQ(target_info.lacros_data_items[i], expected_lacros_data_items[i]);
+  std::vector<BrowserDataMigratorImpl::TargetItem> expected_lacros_items = {
+      {from_dir_.Append(kBookmarks), kFileSize,
+       BrowserDataMigratorImpl::TargetItem::ItemType::kFile},
+      {from_dir_.Append(kCookies), kFileSize,
+       BrowserDataMigratorImpl::TargetItem::ItemType::kFile},
+  };
+  ASSERT_EQ(lacros_items.items.size(), expected_lacros_items.size());
+  std::sort(lacros_items.items.begin(), lacros_items.items.end(),
+            TargetItemComparator());
+  for (int i = 0; i < lacros_items.items.size(); i++) {
+    SCOPED_TRACE(lacros_items.items[i].path.value());
+    EXPECT_EQ(lacros_items.items[i], expected_lacros_items[i]);
   }
 
   // Check for common data.
-  std::vector<BrowserDataMigratorImpl::TargetItem> expected_common_data_items =
-      {{from_dir_.Append(kAffiliationDatabase), kFileSize * 2,
-        BrowserDataMigratorImpl::TargetItem::ItemType::kDirectory}};
-  ASSERT_EQ(target_info.common_data_items.size(),
-            expected_common_data_items.size());
-  std::sort(target_info.common_data_items.begin(),
-            target_info.common_data_items.end(), TargetItemComparator());
-  for (int i = 0; i < target_info.common_data_items.size(); i++) {
-    SCOPED_TRACE(target_info.common_data_items[i].path.value());
-    EXPECT_EQ(target_info.common_data_items[i], expected_common_data_items[i]);
+  std::vector<BrowserDataMigratorImpl::TargetItem> expected_need_copy_items = {
+      {from_dir_.Append(kAffiliationDatabase), kFileSize * 2,
+       BrowserDataMigratorImpl::TargetItem::ItemType::kDirectory}};
+  ASSERT_EQ(need_copy_items.items.size(), expected_need_copy_items.size());
+  std::sort(need_copy_items.items.begin(), need_copy_items.items.end(),
+            TargetItemComparator());
+  for (int i = 0; i < need_copy_items.items.size(); i++) {
+    SCOPED_TRACE(need_copy_items.items[i].path.value());
+    EXPECT_EQ(need_copy_items.items[i], expected_need_copy_items[i]);
+  }
+
+  // Check for deletable items.
+  std::vector<BrowserDataMigratorImpl::TargetItem> expected_deletable_items = {
+      {from_dir_.Append(kCache), kFileSize,
+       BrowserDataMigratorImpl::TargetItem::ItemType::kFile}};
+  ASSERT_EQ(deletable_items.items.size(), expected_deletable_items.size());
+  for (int i = 0; i < deletable_items.items.size(); i++) {
+    SCOPED_TRACE(deletable_items.items[i].path.value());
+    EXPECT_EQ(deletable_items.items[i], expected_deletable_items[i]);
   }
 }
 
@@ -343,69 +359,19 @@
       kDryRunDeleteAndMoveMigrationHasEnoughDiskSpace, 1);
 }
 
-TEST_F(BrowserDataMigratorImplTest, RecordStatus) {
-  {
-    // If `FinalStatus::kSkipped`, only record the status and do not record
-    // copied data size or total time.
-    base::HistogramTester histogram_tester;
-
-    BrowserDataMigratorImpl::RecordStatus(
-        BrowserDataMigratorImpl::FinalStatus::kSkipped);
-
-    histogram_tester.ExpectTotalCount(kFinalStatus, 1);
-    histogram_tester.ExpectTotalCount(kCopiedDataSize, 0);
-    histogram_tester.ExpectTotalCount(kTotalTime, 0);
-
-    histogram_tester.ExpectBucketCount(
-        kFinalStatus, BrowserDataMigratorImpl::FinalStatus::kSkipped, 1);
-  }
-
-  {
-    // If `FInalStatus::kSuccess`, the three UMA `kFinalStatus`,
-    // `kCopiedDataSize`, `kTotalTime` should be recorded.
-    base::HistogramTester histogram_tester;
-
-    BrowserDataMigratorImpl::TargetInfo target_info;
-    target_info.remain_in_ash_data_size = /* 300 MBs */ 300 * 1024 * 1024;
-    target_info.lacros_data_size = /* 400 MBs */ 400 * 1024 * 1024;
-    target_info.common_data_size = /* 500 MBs */ 500 * 1024 * 1024;
-    target_info.deletable_data_size = /* 600 MBs */ 600 * 1024 * 1024;
-
-    base::ElapsedTimer timer;
-
-    BrowserDataMigratorImpl::RecordStatus(
-        BrowserDataMigratorImpl::FinalStatus::kSuccess, &target_info, &timer);
-
-    histogram_tester.ExpectTotalCount(kFinalStatus, 1);
-    histogram_tester.ExpectTotalCount(kCopiedDataSize, 1);
-    histogram_tester.ExpectTotalCount(kAshDataSize, 1);
-    histogram_tester.ExpectTotalCount(kLacrosDataSize, 1);
-    histogram_tester.ExpectTotalCount(kCommonDataSize, 1);
-    histogram_tester.ExpectTotalCount(kTotalTime, 1);
-
-    histogram_tester.ExpectBucketCount(
-        kFinalStatus, BrowserDataMigratorImpl::FinalStatus::kSuccess, 1);
-    histogram_tester.ExpectBucketCount(
-        kCopiedDataSize, target_info.TotalCopySize() / (1024 * 1024), 1);
-    histogram_tester.ExpectBucketCount(
-        kAshDataSize, target_info.remain_in_ash_data_size / (1024 * 1024), 1);
-    histogram_tester.ExpectBucketCount(
-        kLacrosDataSize, target_info.lacros_data_size / (1024 * 1024), 1);
-    histogram_tester.ExpectBucketCount(
-        kCommonDataSize, target_info.common_data_size / (1024 * 1024), 1);
-    histogram_tester.ExpectBucketCount(
-        kNoCopyDataSize, target_info.deletable_data_size / (1024 * 1024), 1);
-  }
-}
-
 TEST_F(BrowserDataMigratorImplTest, SetupTmpDir) {
   base::FilePath tmp_dir = from_dir_.Append(kTmpDir);
   scoped_refptr<CancelFlag> cancel_flag = base::MakeRefCounted<CancelFlag>();
-  BrowserDataMigratorImpl::TargetInfo target_info =
-      BrowserDataMigratorImpl::GetTargetInfo(from_dir_);
+  BrowserDataMigratorImpl::TargetItems lacros_items =
+      BrowserDataMigratorImpl::GetTargetItems(
+          from_dir_, BrowserDataMigratorImpl::ItemType::kLacros);
+  BrowserDataMigratorImpl::TargetItems need_copy_items =
+      BrowserDataMigratorImpl::GetTargetItems(
+          from_dir_, BrowserDataMigratorImpl::ItemType::kNeedCopy);
   FakeMigrationProgressTracker progress_tracker;
   EXPECT_TRUE(BrowserDataMigratorImpl::SetupTmpDir(
-      target_info, from_dir_, tmp_dir, cancel_flag.get(), &progress_tracker));
+      lacros_items, need_copy_items, tmp_dir, cancel_flag.get(),
+      &progress_tracker));
 
   EXPECT_TRUE(base::PathExists(tmp_dir));
   EXPECT_TRUE(base::PathExists(tmp_dir.Append(kFirstRun)));
@@ -429,13 +395,17 @@
   base::FilePath tmp_dir = from_dir_.Append(kTmpDir);
   scoped_refptr<CancelFlag> cancel_flag = base::MakeRefCounted<CancelFlag>();
   FakeMigrationProgressTracker progress_tracker;
-  BrowserDataMigratorImpl::TargetInfo target_info =
-      BrowserDataMigratorImpl::GetTargetInfo(from_dir_);
+  BrowserDataMigratorImpl::TargetItems lacros_items =
+      BrowserDataMigratorImpl::GetTargetItems(
+          from_dir_, BrowserDataMigratorImpl::ItemType::kLacros);
+  BrowserDataMigratorImpl::TargetItems need_copy_items =
+      BrowserDataMigratorImpl::GetTargetItems(
+          from_dir_, BrowserDataMigratorImpl::ItemType::kNeedCopy);
 
   // Set cancel_flag to cancel migrationl.
   cancel_flag->Set();
   EXPECT_FALSE(BrowserDataMigratorImpl::SetupTmpDir(
-      target_info, user_data_dir_.GetPath(), tmp_dir, cancel_flag.get(),
+      lacros_items, need_copy_items, tmp_dir, cancel_flag.get(),
       &progress_tracker));
 
   // These files should not exist.
@@ -494,10 +464,8 @@
 
   histogram_tester.ExpectTotalCount(kFinalStatus, 1);
   histogram_tester.ExpectTotalCount(kCopiedDataSize, 1);
-  histogram_tester.ExpectTotalCount(kAshDataSize, 1);
   histogram_tester.ExpectTotalCount(kLacrosDataSize, 1);
   histogram_tester.ExpectTotalCount(kCommonDataSize, 1);
-  histogram_tester.ExpectTotalCount(kNoCopyDataSize, 1);
   histogram_tester.ExpectTotalCount(kTotalTime, 1);
   histogram_tester.ExpectTotalCount(kLacrosDataTime, 1);
   histogram_tester.ExpectTotalCount(kCommonDataTime, 1);
diff --git a/chrome/browser/ash/crostini/crostini_manager.cc b/chrome/browser/ash/crostini/crostini_manager.cc
index d33babf..eb0c07a7 100644
--- a/chrome/browser/ash/crostini/crostini_manager.cc
+++ b/chrome/browser/ash/crostini/crostini_manager.cc
@@ -1105,8 +1105,7 @@
             std::move(callback).Run(res);
           },
           std::move(callback)),
-      // TODO(crbug/1228109): uncomment when Dlc issues are fixed.
-      /*is_initial_install=*/false);
+      is_initial_install);
 }
 
 void CrostiniManager::CancelInstallTermina() {
diff --git a/chrome/browser/ash/psi_memory_metrics.cc b/chrome/browser/ash/psi_memory_metrics.cc
index 46bcdcfe..deaeb6b 100644
--- a/chrome/browser/ash/psi_memory_metrics.cc
+++ b/chrome/browser/ash/psi_memory_metrics.cc
@@ -66,8 +66,6 @@
 }
 
 void PSIMemoryMetrics::Stop() {
-  stopped_.Set();
-
   // Note that you can't call last_timer.CancelTask() from here,
   // as we may not be running in the correct sequence.
   runner_->PostTask(FROM_HERE,
@@ -99,19 +97,11 @@
 }
 
 void PSIMemoryMetrics::CollectEventsAndReschedule() {
-  if (stopped_.IsSet()) {
-    return;
-  }
-
   CollectEvents();
   ScheduleCollector();
 }
 
 void PSIMemoryMetrics::ScheduleCollector() {
-  if (stopped_.IsSet()) {
-    return;
-  }
-
   last_timer_ = runner_->PostCancelableDelayedTask(
       base::subtle::PostDelayedTaskPassKey(), FROM_HERE,
       base::BindOnce(&PSIMemoryMetrics::CollectEventsAndReschedule, this),
diff --git a/chrome/browser/ash/psi_memory_metrics.h b/chrome/browser/ash/psi_memory_metrics.h
index 2c4509f1..32d2cad2 100644
--- a/chrome/browser/ash/psi_memory_metrics.h
+++ b/chrome/browser/ash/psi_memory_metrics.h
@@ -10,7 +10,6 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
-#include "base/synchronization/atomic_flag.h"
 #include "base/task/delayed_task_handle.h"
 #include "base/task/task_runner.h"
 #include "base/time/time.h"
@@ -80,7 +79,6 @@
 
   // Task controllers/monitors.
   scoped_refptr<base::SequencedTaskRunner> runner_;
-  base::AtomicFlag stopped_;
   base::DelayedTaskHandle last_timer_;
 };
 
diff --git a/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.cc b/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.cc
index aee3ac6..45ecc1a8 100644
--- a/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.cc
+++ b/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.cc
@@ -9,11 +9,8 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/notreached.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/image_fetcher/image_decoder_impl.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "components/image_fetcher/core/image_fetcher.h"
-#include "components/image_fetcher/core/image_fetcher_impl.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 #include "content/public/browser/service_process_host.h"
@@ -45,20 +42,6 @@
   return IdentityManagerFactory::GetForProfile(profile);
 }
 
-std::unique_ptr<image_fetcher::ImageFetcher>
-QuickPairBrowserDelegateImpl::GetImageFetcher() {
-  scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory =
-      GetURLLoaderFactory();
-  if (!shared_url_loader_factory) {
-    QP_LOG(WARNING)
-        << "No URL loader factory to provide an image fetcher instance.";
-    return nullptr;
-  }
-
-  return std::make_unique<image_fetcher::ImageFetcherImpl>(
-      std::make_unique<ImageDecoderImpl>(), shared_url_loader_factory);
-}
-
 PrefService* QuickPairBrowserDelegateImpl::GetActivePrefService() {
   Profile* profile = GetActiveProfile();
   if (!profile) {
diff --git a/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.h b/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.h
index 2c7ba85e..681f55b 100644
--- a/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.h
+++ b/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.h
@@ -31,7 +31,6 @@
   // QuickPairBrowserDelegate:
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
   signin::IdentityManager* GetIdentityManager() override;
-  std::unique_ptr<image_fetcher::ImageFetcher> GetImageFetcher() override;
   PrefService* GetActivePrefService() override;
   void RequestService(
       mojo::PendingReceiver<mojom::QuickPairService> receiver) override;
diff --git a/chrome/browser/ash/web_applications/OWNERS b/chrome/browser/ash/web_applications/OWNERS
index d47107a22..4afc2cc 100644
--- a/chrome/browser/ash/web_applications/OWNERS
+++ b/chrome/browser/ash/web_applications/OWNERS
@@ -1,4 +1,4 @@
-file://ash/webui/system_apps/PLATFORM_OWNERS
+file://ash/webui/PLATFORM_OWNERS
 
 tapted@chromium.org
 dstockwell@google.com
diff --git a/chrome/browser/autofill/captured_sites_test_utils.cc b/chrome/browser/autofill/captured_sites_test_utils.cc
index 6499c06..5942c0b 100644
--- a/chrome/browser/autofill/captured_sites_test_utils.cc
+++ b/chrome/browser/autofill/captured_sites_test_utils.cc
@@ -58,6 +58,7 @@
 using base::JSONReader;
 
 namespace {
+
 // The command-line flag to specify the command file flag.
 const char kCommandFileFlag[] = "command_file";
 
@@ -229,6 +230,57 @@
   return execution_state;
 }
 
+struct AllowNull {
+  inline constexpr AllowNull() = default;
+};
+
+absl::optional<std::string> FindPopulateString(
+    const base::Value::DictStorage& container,
+    base::StringPiece key_name,
+    absl::variant<base::StringPiece, AllowNull> key_descriptor) {
+  auto container_iter = container.find(key_name);
+  if (container_iter == container.end() ||
+      !container_iter->second.is_string()) {
+    if (absl::holds_alternative<base::StringPiece>(key_descriptor)) {
+      ADD_FAILURE() << "Failed to extract '"
+                    << absl::get<base::StringPiece>(key_descriptor)
+                    << "' string from container!";
+    }
+    return absl::nullopt;
+  }
+
+  return container_iter->second.GetString();
+}
+
+absl::optional<std::vector<std::string>> FindPopulateStringVector(
+    const base::Value::DictStorage& container,
+    base::StringPiece key_name,
+    absl::variant<base::StringPiece, AllowNull> key_descriptor) {
+  auto container_iter = container.find(key_name);
+  if (container_iter == container.end() || !container_iter->second.is_list()) {
+    if (absl::holds_alternative<base::StringPiece>(key_descriptor)) {
+      ADD_FAILURE() << "Failed to extract '"
+                    << absl::get<base::StringPiece>(key_descriptor)
+                    << "' strings from container!";
+    }
+    return absl::nullopt;
+  }
+
+  std::vector<std::string> strings;
+  for (const base::Value& item : container_iter->second.GetList()) {
+    if (!item.is_string()) {
+      if (absl::holds_alternative<base::StringPiece>(key_descriptor)) {
+        ADD_FAILURE() << "Failed to extract element of '"
+                      << absl::get<base::StringPiece>(key_descriptor)
+                      << "' vector from container!";
+      }
+      return absl::nullopt;
+    }
+    strings.push_back(item.GetString());
+  }
+  return strings;
+}
+
 }  // namespace
 
 namespace captured_sites_test_utils {
@@ -903,21 +955,6 @@
   completion_observer.BlockUntilCompletion();
 }
 
-absl::optional<std::string> FindPopulateString(
-    const base::Value::DictStorage& container,
-    const std::string key_name,
-    const std::string key_descriptor) {
-  auto container_iter = container.find(key_name);
-  if (container_iter == container.end() ||
-      !container_iter->second.is_string()) {
-    ADD_FAILURE() << "Failed to extract '" << key_descriptor
-                  << "' from container!";
-    return absl::nullopt;
-  }
-
-  return container_iter->second.GetString();
-}
-
 bool TestRecipeReplayer::ReplayRecordedActions(
     const base::FilePath& recipe_file_path,
     const absl::optional<base::FilePath>& command_file_path) {
@@ -1512,20 +1549,31 @@
         autofill_prediction_container_iter->second.GetString();
     VLOG(1) << "Checking the field `" << xpath << "` has the autofill type '"
             << expected_autofill_prediction_type << "'";
-    ExpectElementPropertyEquals(
-        frame, xpath,
-        "return target.getAttribute('autofill-prediction');",
-        expected_autofill_prediction_type, "autofill type mismatch", true);
+    ExpectElementPropertyEqualsAnyOf(
+        frame, xpath, "return target.getAttribute('autofill-prediction');",
+        {expected_autofill_prediction_type}, "autofill type mismatch",
+        IgnoreCase(true));
   }
 
+  absl::optional<std::vector<std::string>> expected_values =
+      FindPopulateStringVector(action, "expectedValues", AllowNull());
   absl::optional<std::string> expected_value =
-      FindPopulateString(action, "expectedValue", "validation expected value");
-  if (!expected_value)
+      FindPopulateString(action, "expectedValue", AllowNull());
+  if (!!expected_values == !!expected_value) {
+    ADD_FAILURE() << "Failed to extract 'expectedValue' xor 'expectedValues' "
+                     "vector from container !";
     return false;
+  }
 
   VLOG(1) << "Checking the field `" << xpath << "`.";
-  ExpectElementPropertyEquals(frame, xpath, "return target.value;",
-                              *expected_value, "text value mismatch");
+  if (expected_value) {
+    ExpectElementPropertyEqualsAnyOf(frame, xpath, "return target.value;",
+                                     {*expected_value}, "text value mismatch");
+  }
+  if (expected_values) {
+    ExpectElementPropertyEqualsAnyOf(frame, xpath, "return target.value;",
+                                     *expected_values, "text value mismatch");
+  }
   return true;
 }
 
@@ -1918,27 +1966,33 @@
       property);
 }
 
-bool TestRecipeReplayer::ExpectElementPropertyEquals(
+bool TestRecipeReplayer::ExpectElementPropertyEqualsAnyOf(
     const content::ToRenderFrameHost& frame,
     const std::string& element_xpath,
     const std::string& get_property_function_body,
-    const std::string& expected_value,
+    const std::vector<std::string>& expected_values,
     const std::string& validation_field,
-    bool ignore_case) {
-  std::string value;
+    IgnoreCase ignore_case) {
+  std::string actual_value;
   if (!GetElementProperty(frame, element_xpath, get_property_function_body,
-                          &value)) {
+                          &actual_value)) {
     ADD_FAILURE() << "Failed to extract element property! " << element_xpath
                   << ", " << get_property_function_body;
     return false;
   }
 
-  if ((ignore_case &&
-       !base::EqualsCaseInsensitiveASCII(expected_value, value)) ||
-      (!ignore_case && expected_value != value)) {
+  auto is_expected = [ignore_case,
+                      &actual_value](base::StringPiece expected_value) {
+    return ignore_case
+               ? base::EqualsCaseInsensitiveASCII(expected_value, actual_value)
+               : expected_value == actual_value;
+  };
+
+  if (base::ranges::none_of(expected_values, is_expected)) {
     std::string error_message = base::StrCat(
-      {"Field xpath: `", element_xpath, "` ", validation_field, ", ",
-       "Expected: '" , expected_value, "', actual: '", value, "'"});
+        {"Field xpath: `", element_xpath, "` ", validation_field, ", ",
+         "Expected: '", base::JoinString(expected_values, " or "),
+         "', actual: '", actual_value, "'"});
     VLOG(1) << error_message;
     validation_failures_.push_back(testing::AssertionFailure()
                                    << error_message);
diff --git a/chrome/browser/autofill/captured_sites_test_utils.h b/chrome/browser/autofill/captured_sites_test_utils.h
index aeedfe2..55b03e583 100644
--- a/chrome/browser/autofill/captured_sites_test_utils.h
+++ b/chrome/browser/autofill/captured_sites_test_utils.h
@@ -14,6 +14,7 @@
 #include "base/files/file_path.h"
 #include "base/memory/raw_ptr.h"
 #include "base/strings/strcat.h"
+#include "base/types/strong_alias.h"
 #include "base/values.h"
 #include "chrome/browser/ui/browser.h"
 #include "components/autofill/core/browser/test_autofill_clock.h"
@@ -283,6 +284,8 @@
                                    const gfx::Point& point);
 
  private:
+  using IgnoreCase = base::StrongAlias<struct IgnoreCaseTag, bool>;
+
   static bool GetIFrameOffsetFromIFramePath(
       const std::vector<std::string>& iframe_path,
       content::RenderFrameHost* frame,
@@ -369,13 +372,13 @@
                           const std::string& element_xpath,
                           const std::string& get_property_function_body,
                           std::string* property);
-  bool ExpectElementPropertyEquals(
+  bool ExpectElementPropertyEqualsAnyOf(
       const content::ToRenderFrameHost& frame,
       const std::string& element_xpath,
       const std::string& get_property_function_body,
-      const std::string& expected_value,
+      const std::vector<std::string>& expected_value,
       const std::string& validation_field,
-      const bool ignoreCase = false);
+      IgnoreCase ignore_case = IgnoreCase(false));
   void SimulateKeyPressWrapper(content::WebContents* web_contents,
                                ui::DomKey key);
   void NavigateAwayAndDismissBeforeUnloadDialog();
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 70df7be..d7b86d1 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -516,6 +516,7 @@
 #include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/web_applications/isolation_prefs_utils.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
 #include "extensions/browser/api/web_request/web_request_api.h"
 #include "extensions/browser/api/web_request/web_request_proxying_webtransport.h"
 #include "extensions/browser/extension_navigation_throttle.h"
@@ -6418,3 +6419,18 @@
 bool ChromeContentBrowserClient::IsFirstPartySetsEnabled() {
   return FirstPartySetsUtil::GetInstance()->IsFirstPartySetsEnabled();
 }
+
+content::mojom::AlternativeErrorPageOverrideInfoPtr
+ChromeContentBrowserClient::GetAlternativeErrorPageOverrideInfo(
+    const GURL& url,
+    content::BrowserContext* browser_context) {
+#if BUILDFLAG(IS_ANDROID)
+  return nullptr;
+#else
+  if (!base::FeatureList::IsEnabled(features::kDesktopPWAsDefaultOfflinePage)) {
+    return nullptr;
+  }
+
+  return web_app::GetAppManifestInfo(url, browser_context);
+#endif  //  BUILDFLAG(IS_ANDROID)
+}
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 71dfd39..ddd4c17 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -25,6 +25,7 @@
 #include "content/public/browser/child_process_security_policy.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/alternative_error_page_override_info.mojom-forward.h"
 #include "extensions/buildflags/buildflags.h"
 #include "media/media_buildflags.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -780,6 +781,11 @@
   bool ShouldDisableOriginAgentClusterDefault(
       content::BrowserContext* browser_context) override;
 
+  content::mojom::AlternativeErrorPageOverrideInfoPtr
+  GetAlternativeErrorPageOverrideInfo(
+      const GURL& url,
+      content::BrowserContext* browser_context) override;
+
  protected:
   static bool HandleWebUI(GURL* url, content::BrowserContext* browser_context);
   static bool HandleWebUIReverse(GURL* url,
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
index 5565ba1c..23299afc 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -16,6 +16,8 @@
 #include "ash/constants/ash_pref_names.h"
 #include "ash/public/cpp/multi_user_window_manager.h"
 #include "ash/public/cpp/new_window_delegate.h"
+#include "ash/public/cpp/style/color_provider.h"
+#include "ash/public/cpp/style/scoped_light_mode_as_default.h"
 #include "ash/public/cpp/tablet_mode.h"
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -81,6 +83,7 @@
 #include "storage/common/file_system/file_system_types.h"
 #include "storage/common/file_system/file_system_util.h"
 #include "ui/base/webui/web_ui_util.h"
+#include "ui/chromeos/styles/cros_styles.h"
 #include "ui/shell_dialogs/select_file_dialog.h"
 #include "url/gurl.h"
 
@@ -207,6 +210,11 @@
   return Redact(path.value());
 }
 
+std::string SkColorToHexString(const SkColor color) {
+  return base::StringPrintf("#%02x%02x%02x", SkColorGetR(color),
+                            SkColorGetG(color), SkColorGetB(color));
+}
+
 }  // namespace
 
 ExtensionFunction::ResponseAction
@@ -1265,6 +1273,20 @@
           *entry_definition_list))));
 }
 
+// TODO(crbug.com/1212768): Remove this once Files app SWA has fully launched.
+ExtensionFunction::ResponseAction
+FileManagerPrivateGetFrameColorFunction::Run() {
+  ash::ScopedLightModeAsDefault scoped_light_mode_as_default;
+  auto* color_provider = ash::ColorProvider::Get();
+  std::string frame_color = SkColorToHexString(SK_ColorWHITE);
+  if (color_provider) {
+    frame_color = SkColorToHexString(cros_styles::ResolveColor(
+        cros_styles::ColorName::kBgColor, color_provider->IsDarkModeEnabled(),
+        /*use_debug_color=*/false));
+  }
+  return RespondNow(OneArgument(base::Value(frame_color)));
+}
+
 ExtensionFunction::ResponseAction
 FileManagerPrivateIsTabletModeEnabledFunction::Run() {
   ash::TabletMode* tablet_mode = ash::TabletMode::Get();
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h
index a98f6196..d0d9f0f6 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h
@@ -546,6 +546,27 @@
           entry_definition_list);
 };
 
+// Implements the chrome.fileManagerPrivate.getFrameColor method.
+// Returns the Chrome app frame color to launch foreground windows.
+// TODO(crbug.com/1212768): Remove this once Files app SWA has fully launched.
+class FileManagerPrivateGetFrameColorFunction : public LoggedExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("fileManagerPrivate.getFrameColor",
+                             FILEMANAGERPRIVATE_GETFRAMECOLOR)
+  FileManagerPrivateGetFrameColorFunction() = default;
+
+  FileManagerPrivateGetFrameColorFunction(
+      const FileManagerPrivateGetFrameColorFunction&) = delete;
+  FileManagerPrivateGetFrameColorFunction operator=(
+      const FileManagerPrivateGetFrameColorFunction&) = delete;
+
+ protected:
+  ~FileManagerPrivateGetFrameColorFunction() override = default;
+
+ private:
+  ResponseAction Run() override;
+};
+
 // Implements the chrome.fileManagerPrivate.isTabletModeEnabled method.
 class FileManagerPrivateIsTabletModeEnabledFunction : public ExtensionFunction {
  public:
diff --git a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
index e052954..f90c84a 100644
--- a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
+++ b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
@@ -36,7 +36,6 @@
 #include "components/permissions/permission_result.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/notification_service.h"
-#include "content/public/browser/plugin_service.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/webplugininfo.h"
 #include "content/public/test/browser_test.h"
@@ -44,6 +43,10 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/test_extension_registry_observer.h"
 
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "content/public/browser/plugin_service.h"
+#endif
+
 namespace extensions {
 
 using ContextType = ExtensionApiTest::ContextType;
@@ -361,6 +364,7 @@
       "ContentSettings.ExtensionNonEmbeddedSettingSet", 2);
 }
 
+#if BUILDFLAG(ENABLE_PLUGINS)
 IN_PROC_BROWSER_TEST_F(ExtensionContentSettingsApiTest, ConsoleErrorTest) {
   constexpr char kExtensionPath[] = "content_settings/disablepluginsapi";
   const extensions::Extension* extension =
@@ -376,5 +380,6 @@
   console_observer.Wait();
   EXPECT_EQ(1u, console_observer.messages().size());
 }
+#endif  // BUILDFLAG(ENABLE_PLUGINS)
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest.cc b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
index c45bb9a..a3f1f42 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
@@ -357,7 +357,7 @@
   CheckStoredDirectoryMatches(base::FilePath());
 }
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX)
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 IN_PROC_BROWSER_TEST_F(FileSystemApiTest,
                        FileSystemApiOpenDirectoryOnGraylistAndAllowTest) {
   base::FilePath test_file = TempFilePath("open_existing.txt", true);
@@ -439,7 +439,7 @@
       << message_;
   CheckStoredDirectoryMatches(test_file);
 }
-#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX)
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 
 IN_PROC_BROWSER_TEST_F(FileSystemApiTest,
     FileSystemApiInvalidChooseEntryTypeTest) {
diff --git a/chrome/browser/extensions/api/messaging/messaging_apitest.cc b/chrome/browser/extensions/api/messaging/messaging_apitest.cc
index 5c1a865..4cd6915f 100644
--- a/chrome/browser/extensions/api/messaging/messaging_apitest.cc
+++ b/chrome/browser/extensions/api/messaging/messaging_apitest.cc
@@ -1207,6 +1207,46 @@
           "});", receiver->id().c_str())));
 }
 
+IN_PROC_BROWSER_TEST_F(MessagingApiTest, UserGestureFromContentScript) {
+  static constexpr char kBackground[] = R"(
+    chrome.runtime.onMessage.addListener(function() {
+      chrome.test.assertTrue(chrome.test.isProcessingUserGesture());
+      chrome.test.notifyPass();
+    });
+  )";
+
+  static constexpr char kContentScript[] = R"(
+    chrome.test.runWithUserGesture(function() {
+      chrome.runtime.sendMessage('');
+    });
+  )";
+
+  static constexpr char kManifest[] = R"(
+    {
+      "name": "Test user gesture from content script.",
+      "version": "1.0",
+      "manifest_version": 3,
+      "background": {
+        "service_worker": "background.js"
+      },
+      "content_scripts": [{
+        "matches": ["*://example.com/*"],
+        "js": ["content_script.js"]
+      }]
+    }
+  )";
+
+  TestExtensionDir test_dir;
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackground);
+  test_dir.WriteFile(FILE_PATH_LITERAL("content_script.js"), kContentScript);
+  test_dir.WriteManifest(kManifest);
+
+  GURL url = embedded_test_server()->GetURL("example.com", "/simple.html");
+  ASSERT_TRUE(RunExtensionTest(test_dir.UnpackedPath(),
+                               {.page_url = url.spec().c_str()}, {}))
+      << message_;
+}
+
 IN_PROC_BROWSER_TEST_F(MessagingApiTest,
                        RestrictedActivationTriggerBetweenExtensions) {
   base::CommandLine::ForCurrentProcess()->AppendSwitch(
diff --git a/chrome/browser/favicon/chrome_favicon_client.cc b/chrome/browser/favicon/chrome_favicon_client.cc
index 5e9a84c..e4e38dee 100644
--- a/chrome/browser/favicon/chrome_favicon_client.cc
+++ b/chrome/browser/favicon/chrome_favicon_client.cc
@@ -11,9 +11,13 @@
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/dom_distiller/core/url_constants.h"
 #include "components/dom_distiller/core/url_utils.h"
-#include "extensions/common/constants.h"
+#include "extensions/buildflags/buildflags.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/common/constants.h"
+#endif
+
 namespace {
 
 void RunFaviconCallbackIfNotCanceled(
@@ -33,8 +37,12 @@
 }
 
 bool ChromeFaviconClient::IsNativeApplicationURL(const GURL& url) {
-  return url.SchemeIs(content::kChromeUIScheme) ||
-         url.SchemeIs(extensions::kExtensionScheme);
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+  if (url.SchemeIs(extensions::kExtensionScheme))
+    return true;
+#endif
+
+  return url.SchemeIs(content::kChromeUIScheme);
 }
 
 bool ChromeFaviconClient::IsReaderModeURL(const GURL& url) {
diff --git a/chrome/browser/infobars/infobars_browsertest.cc b/chrome/browser/infobars/infobars_browsertest.cc
index 42e57d1a..55beec48 100644
--- a/chrome/browser/infobars/infobars_browsertest.cc
+++ b/chrome/browser/infobars/infobars_browsertest.cc
@@ -19,11 +19,6 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/theme_installed_infobar_delegate.h"
 #include "chrome/browser/infobars/infobar_observer.h"
-#include "chrome/browser/plugins/hung_plugin_infobar_delegate.h"
-#include "chrome/browser/plugins/plugin_infobar_delegates.h"
-#include "chrome/browser/plugins/plugin_metadata.h"
-#include "chrome/browser/plugins/plugin_observer.h"
-#include "chrome/browser/plugins/reload_plugin_infobar_delegate.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
@@ -57,6 +52,14 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/l10n/l10n_util.h"
 
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "chrome/browser/plugins/hung_plugin_infobar_delegate.h"
+#include "chrome/browser/plugins/plugin_infobar_delegates.h"
+#include "chrome/browser/plugins/plugin_metadata.h"
+#include "chrome/browser/plugins/plugin_observer.h"
+#include "chrome/browser/plugins/reload_plugin_infobar_delegate.h"
+#endif
+
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ui/startup/default_browser_infobar_delegate.h"
 #endif
@@ -179,16 +182,12 @@
   }
 
   const base::flat_map<std::string, IBD::InfoBarIdentifier> kIdentifiers = {
-      {"hung_plugin", IBD::HUNG_PLUGIN_INFOBAR_DELEGATE},
       {"dev_tools", IBD::DEV_TOOLS_INFOBAR_DELEGATE},
       {"extension_dev_tools", IBD::EXTENSION_DEV_TOOLS_INFOBAR_DELEGATE},
       {"incognito_connectability",
        IBD::INCOGNITO_CONNECTABILITY_INFOBAR_DELEGATE},
       {"theme_installed", IBD::THEME_INSTALLED_INFOBAR_DELEGATE},
       {"nacl", IBD::NACL_INFOBAR_DELEGATE},
-      {"outdated_plugin", IBD::OUTDATED_PLUGIN_INFOBAR_DELEGATE},
-      {"reload_plugin", IBD::RELOAD_PLUGIN_INFOBAR_DELEGATE},
-      {"plugin_observer", IBD::PLUGIN_OBSERVER_INFOBAR_DELEGATE},
       {"file_access_disabled", IBD::FILE_ACCESS_DISABLED_INFOBAR_DELEGATE},
       {"keystone_promotion", IBD::KEYSTONE_PROMOTION_INFOBAR_DELEGATE_MAC},
       {"collected_cookies", IBD::COLLECTED_COOKIES_INFOBAR_DELEGATE},
@@ -201,6 +200,13 @@
       {"translate", IBD::TRANSLATE_INFOBAR_DELEGATE_NON_AURA},
       {"automation", IBD::AUTOMATION_INFOBAR_DELEGATE},
       {"tab_sharing", IBD::TAB_SHARING_INFOBAR_DELEGATE},
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+      {"hung_plugin", IBD::HUNG_PLUGIN_INFOBAR_DELEGATE},
+      {"outdated_plugin", IBD::OUTDATED_PLUGIN_INFOBAR_DELEGATE},
+      {"reload_plugin", IBD::RELOAD_PLUGIN_INFOBAR_DELEGATE},
+      {"plugin_observer", IBD::PLUGIN_OBSERVER_INFOBAR_DELEGATE},
+#endif  // BUILDFLAG(ENABLE_PLUGINS)
   };
   auto id_entry = kIdentifiers.find(name);
   if (id_entry == kIdentifiers.end()) {
@@ -210,11 +216,6 @@
   auto infobar_identifier = id_entry->second;
   AddExpectedInfoBar(infobar_identifier);
   switch (infobar_identifier) {
-    case IBD::HUNG_PLUGIN_INFOBAR_DELEGATE:
-      HungPluginInfoBarDelegate::Create(GetInfoBarManager(), nullptr, 0,
-                                        u"Test Plugin");
-      break;
-
     case IBD::DEV_TOOLS_INFOBAR_DELEGATE:
       DevToolsInfoBarDelegate::Create(
           l10n_util::GetStringFUTF16(
@@ -254,6 +255,12 @@
 #endif
       break;
 
+#if BUILDFLAG(ENABLE_PLUGINS)
+    case IBD::HUNG_PLUGIN_INFOBAR_DELEGATE:
+      HungPluginInfoBarDelegate::Create(GetInfoBarManager(), nullptr, 0,
+                                        u"Test Plugin");
+      break;
+
     case IBD::OUTDATED_PLUGIN_INFOBAR_DELEGATE:
       OutdatedPluginInfoBarDelegate::Create(
           GetInfoBarManager(), nullptr,
@@ -273,6 +280,7 @@
       PluginObserver::CreatePluginObserverInfoBar(GetInfoBarManager(),
                                                   u"Test Plugin");
       break;
+#endif  // BUILDFLAG(ENABLE_PLUGINS)
 
     case IBD::FILE_ACCESS_DISABLED_INFOBAR_DELEGATE:
       ChromeSelectFilePolicy(GetWebContents()).SelectFileDenied();
@@ -362,14 +370,11 @@
       break;
 
     default:
+      ADD_FAILURE() << "Unhandled infobar " << name;
       break;
   }
 }
 
-IN_PROC_BROWSER_TEST_F(InfoBarUiTest, InvokeUi_hung_plugin) {
-  ShowAndVerifyUi();
-}
-
 IN_PROC_BROWSER_TEST_F(InfoBarUiTest, InvokeUi_dev_tools) {
   ShowAndVerifyUi();
 }
@@ -392,6 +397,11 @@
 }
 #endif
 
+#if BUILDFLAG(ENABLE_PLUGINS)
+IN_PROC_BROWSER_TEST_F(InfoBarUiTest, InvokeUi_hung_plugin) {
+  ShowAndVerifyUi();
+}
+
 IN_PROC_BROWSER_TEST_F(InfoBarUiTest, InvokeUi_outdated_plugin) {
   ShowAndVerifyUi();
 }
@@ -403,6 +413,7 @@
 IN_PROC_BROWSER_TEST_F(InfoBarUiTest, InvokeUi_plugin_observer) {
   ShowAndVerifyUi();
 }
+#endif  // BUILDFLAG(ENABLE_PLUGINS)
 
 IN_PROC_BROWSER_TEST_F(InfoBarUiTest, InvokeUi_file_access_disabled) {
   ShowAndVerifyUi();
diff --git a/chrome/browser/language/language_model_manager_factory.cc b/chrome/browser/language/language_model_manager_factory.cc
index 7f63dac73..8d9b736 100644
--- a/chrome/browser/language/language_model_manager_factory.cc
+++ b/chrome/browser/language/language_model_manager_factory.cc
@@ -51,17 +51,17 @@
   logger.RecordInitiationUILanguageInULP(
       logger.DetermineLanguageStatus(app_locale, ulp_languages));
 
-  const std::string translate_target_language =
+  const std::string target_language =
       translate::TranslatePrefs(pref_service).GetRecentTargetLanguage();
   logger.RecordInitiationTranslateTargetInULP(
-      logger.DetermineLanguageStatus(translate_target_language, ulp_languages));
+      logger.DetermineLanguageStatus(target_language, ulp_languages));
 
   std::vector<std::string> accept_languages;
   language::LanguagePrefs(pref_service)
       .GetAcceptLanguagesList(&accept_languages);
 
   language::ULPLanguageStatus accept_language_status =
-      language::ULPLanguageStatus::kLanguageNotInULP;
+      language::ULPLanguageStatus::kLanguageEmpty;
   if (accept_languages.size() > 0) {
     accept_language_status =
         logger.DetermineLanguageStatus(accept_languages[0], ulp_languages);
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.cc b/chrome/browser/media/webrtc/display_media_access_handler.cc
index 0f085e5..c11718b9 100644
--- a/chrome/browser/media/webrtc/display_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -459,9 +459,9 @@
       base::BindOnce(&DisplayMediaAccessHandler::OnDlpRestrictionChecked,
                      base::Unretained(this), web_contents->GetWeakPtr(),
                      media_id));
-#else   // BUILDFLAG(OS_CHROMEOS)
+#else   // BUILDFLAG(IS_CHROMEOS)
   AcceptRequest(web_contents, media_id);
-#endif  // !BUILDFLAG(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 }
 
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc
index dd767aa..1c8f8b1 100644
--- a/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc
@@ -172,7 +172,9 @@
   std::unique_ptr<content::PrerenderHandle> prerender_handle =
       prerender_helper_.AddEmbedderTriggeredPrerenderAsync(
           prerender_url, content::PrerenderTriggerType::kEmbedder,
-          prerender_utils::kDirectUrlInputMetricSuffix);
+          prerender_utils::kDirectUrlInputMetricSuffix,
+          ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+                                    ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
   EXPECT_TRUE(prerender_handle);
 
   // Wait until the completion of prerendering navigation.
diff --git a/chrome/browser/performance_hints/performance_hints_observer.cc b/chrome/browser/performance_hints/performance_hints_observer.cc
index 10a6508..0440aab 100644
--- a/chrome/browser/performance_hints/performance_hints_observer.cc
+++ b/chrome/browser/performance_hints/performance_hints_observer.cc
@@ -17,9 +17,7 @@
 #include "components/optimization_guide/core/optimization_guide_permissions_util.h"
 #include "components/optimization_guide/core/url_pattern_with_wildcards.h"
 #include "components/optimization_guide/proto/performance_hints_metadata.pb.h"
-#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
-#include "url/gurl.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/jni_string.h"
@@ -429,19 +427,8 @@
   }
 }
 
-void PerformanceHintsObserver::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
+void PerformanceHintsObserver::PrimaryPageChanged(content::Page& page) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(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() ||
-      navigation_handle->IsSameDocument() ||
-      !navigation_handle->HasCommitted()) {
-    // Use the same hints if the main frame hasn't changed.
-    return;
-  }
 
   // We've navigated to a new page, so clear out any existing cached hints.
   link_hints_.clear();
@@ -451,12 +438,13 @@
   if (!optimization_guide_decider_) {
     return;
   }
-  if (navigation_handle->IsErrorPage()) {
+
+  if (page.GetMainDocument().IsErrorDocument()) {
     // Don't provide hints on Chrome error pages.
     return;
   }
 
-  page_url_ = navigation_handle->GetURL();
+  page_url_ = page.GetMainDocument().GetLastCommittedURL();
 }
 
 WEB_CONTENTS_USER_DATA_KEY_IMPL(PerformanceHintsObserver);
diff --git a/chrome/browser/performance_hints/performance_hints_observer.h b/chrome/browser/performance_hints/performance_hints_observer.h
index 1757606..ca67eb7 100644
--- a/chrome/browser/performance_hints/performance_hints_observer.h
+++ b/chrome/browser/performance_hints/performance_hints_observer.h
@@ -22,7 +22,6 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace content {
-class NavigationHandle;
 class WebContents;
 }  // namespace content
 
@@ -70,8 +69,7 @@
   friend class PerformanceHintsObserverTest;
 
   // content::WebContentsObserver.
-  void DidFinishNavigation(
-      content::NavigationHandle* navigation_handle) override;
+  void PrimaryPageChanged(content::Page& page) override;
 
   // Returns true if the current page supports hints (not an error page,
   // must be HTTP/HTTPS, etc).
diff --git a/chrome/browser/performance_hints/performance_hints_observer_browsertest.cc b/chrome/browser/performance_hints/performance_hints_observer_browsertest.cc
new file mode 100644
index 0000000..a331143
--- /dev/null
+++ b/chrome/browser/performance_hints/performance_hints_observer_browsertest.cc
@@ -0,0 +1,216 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/performance_hints/performance_hints_observer.h"
+
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/optimization_guide/core/optimization_guide_switches.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/fenced_frame_test_util.h"
+#include "content/public/test/prerender_test_util.h"
+#include "net/dns/mock_host_resolver.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace performance_hints {
+
+class PerformanceHintsObserverBrowserTest : public InProcessBrowserTest {
+ public:
+  void SetUpOnMainThread() override {
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        optimization_guide::switches::
+            kDisableCheckingUserPermissionsForTesting);
+    // Add switch to avoid racing navigations in the test.
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        optimization_guide::switches::
+            kDisableFetchingHintsAtNavigationStartForTesting);
+    host_resolver()->AddRule("*", "127.0.0.1");
+    ASSERT_TRUE(embedded_test_server()->Start());
+    InProcessBrowserTest::SetUpOnMainThread();
+  }
+
+  content::WebContents* web_contents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+  void AddPerformanceHint(const GURL& url,
+                          const char* pattern,
+                          optimization_guide::proto::PerformanceClass value) {
+    optimization_guide::proto::PerformanceHintsMetadata hints_metadata;
+    auto* hint = hints_metadata.add_performance_hints();
+    hint->set_wildcard_pattern(pattern);
+    hint->set_performance_class(value);
+    optimization_guide::OptimizationMetadata metadata;
+    metadata.set_performance_hints_metadata(hints_metadata);
+
+    OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
+        ->AddHintForTesting(url, optimization_guide::proto::PERFORMANCE_HINTS,
+                            metadata);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(PerformanceHintsObserverBrowserTest,
+                       PerformanceHintsFound) {
+  PerformanceHintsObserver::CreateForWebContents(web_contents());
+
+  const GURL url = embedded_test_server()->GetURL("test.com", "/title1.html");
+
+  // Adds a performance hint for Google URL as kFast for the main frame URL.
+  AddPerformanceHint(url, "www.google.com",
+                     optimization_guide::proto::PERFORMANCE_FAST);
+
+  ASSERT_TRUE(NavigateToURL(web_contents(), url));
+
+  base::HistogramTester histogram_tester;
+
+  // PERFORMANCE_FAST should be fetched for the Google URL.
+  EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
+                  web_contents(), GURL("http://www.google.com"),
+                  /*record_metrics=*/true),
+              testing::Eq(optimization_guide::proto::PERFORMANCE_FAST));
+
+  histogram_tester.ExpectUniqueSample(
+      "PerformanceHints.Observer.HintForURLResult", /*kHintFound*/ 3, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PerformanceHints.Observer.PerformanceClassForURL", /*kFast*/ 2, 1);
+}
+
+class PerformanceHintsObserverFencedFrameTest
+    : public PerformanceHintsObserverBrowserTest {
+ public:
+  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
+    return fenced_frame_helper_;
+  }
+
+ protected:
+  content::test::FencedFrameTestHelper fenced_frame_helper_;
+};
+
+IN_PROC_BROWSER_TEST_F(PerformanceHintsObserverFencedFrameTest,
+                       FencedFrameDoesNotAffectPerformanceHints) {
+  PerformanceHintsObserver::CreateForWebContents(web_contents());
+
+  const GURL main_frame_url =
+      embedded_test_server()->GetURL("test.com", "/title1.html");
+  const GURL fenced_frame_url =
+      embedded_test_server()->GetURL("test.com", "/fenced_frames/title1.html");
+
+  ASSERT_TRUE(NavigateToURL(web_contents(), main_frame_url));
+
+  base::HistogramTester histogram_tester;
+
+  // PERFORMANCE_UNKNOWN should be fetched since any hints haven't been added.
+  EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
+                  web_contents(), GURL("http://www.google.com"),
+                  /*record_metrics=*/true),
+              testing::Eq(optimization_guide::proto::PERFORMANCE_UNKNOWN));
+
+  histogram_tester.ExpectUniqueSample(
+      "PerformanceHints.Observer.HintForURLResult", /*kHintNotFound*/ 0, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PerformanceHints.Observer.PerformanceClassForURL", /*kUnknown*/ 0, 1);
+
+  // Adds a performance hint for Google URL as kFast for the fenced frame URL.
+  AddPerformanceHint(fenced_frame_url, "www.google.com",
+                     optimization_guide::proto::PERFORMANCE_FAST);
+
+  ASSERT_TRUE(fenced_frame_test_helper().CreateFencedFrame(
+      web_contents()->GetMainFrame(), fenced_frame_url));
+
+  // The fenced frame URL has a hint for Google, but is not fetched.
+  EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
+                  web_contents(), GURL("http://www.google.com"),
+                  /*record_metrics=*/true),
+              testing::Eq(optimization_guide::proto::PERFORMANCE_UNKNOWN));
+
+  histogram_tester.ExpectUniqueSample(
+      "PerformanceHints.Observer.HintForURLResult", /*kHintNotFound*/ 0, 2);
+  histogram_tester.ExpectUniqueSample(
+      "PerformanceHints.Observer.PerformanceClassForURL", /*kUnknown*/ 0, 2);
+}
+
+class PerformanceHintsObserverPrerenderTest
+    : public PerformanceHintsObserverBrowserTest {
+ public:
+  PerformanceHintsObserverPrerenderTest()
+      : prerender_helper_(base::BindRepeating(
+            &PerformanceHintsObserverBrowserTest::web_contents,
+            base::Unretained(this))) {}
+
+  void SetUpOnMainThread() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    PerformanceHintsObserverBrowserTest::SetUpOnMainThread();
+  }
+
+  content::test::PrerenderTestHelper& prerender_test_helper() {
+    return prerender_helper_;
+  }
+
+ private:
+  content::test::PrerenderTestHelper prerender_helper_;
+};
+
+IN_PROC_BROWSER_TEST_F(PerformanceHintsObserverPrerenderTest,
+                       PrerenderDoesNotAffectPerformanceHints) {
+  PerformanceHintsObserver::CreateForWebContents(web_contents());
+
+  const GURL main_frame_url =
+      embedded_test_server()->GetURL("test.com", "/title1.html");
+  const GURL prerender_url =
+      embedded_test_server()->GetURL("test.com", "/title2.html");
+
+  ASSERT_TRUE(NavigateToURL(web_contents(), main_frame_url));
+
+  base::HistogramTester histogram_tester;
+
+  // PERFORMANCE_UNKNOWN should be fetched since any hints haven't been added.
+  EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
+                  web_contents(), GURL("http://www.google.com"),
+                  /*record_metrics=*/true),
+              testing::Eq(optimization_guide::proto::PERFORMANCE_UNKNOWN));
+
+  histogram_tester.ExpectUniqueSample(
+      "PerformanceHints.Observer.HintForURLResult", /*kHintNotFound*/ 0, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PerformanceHints.Observer.PerformanceClassForURL", /*kUnknown*/ 0, 1);
+
+  // Adds a performance hint for Google URL as kFast for the prerender URL.
+  AddPerformanceHint(prerender_url, "www.google.com",
+                     optimization_guide::proto::PERFORMANCE_FAST);
+
+  // Add a prerender.
+  int host_id = prerender_test_helper().AddPrerender(prerender_url);
+  content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
+  EXPECT_FALSE(host_observer.was_activated());
+
+  // The prerender URL has a hint for Google, but is not fetched.
+  EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
+                  web_contents(), GURL("http://www.google.com"),
+                  /*record_metrics=*/true),
+              testing::Eq(optimization_guide::proto::PERFORMANCE_UNKNOWN));
+
+  histogram_tester.ExpectUniqueSample(
+      "PerformanceHints.Observer.HintForURLResult", /*kHintNotFound*/ 0, 2);
+  histogram_tester.ExpectUniqueSample(
+      "PerformanceHints.Observer.PerformanceClassForURL", /*kUnknown*/ 0, 2);
+
+  // Activate the prerendered page.
+  prerender_test_helper().NavigatePrimaryPage(prerender_url);
+  EXPECT_TRUE(host_observer.was_activated());
+
+  // PERFORMANCE_FAST should be fetched for the Google URL.
+  EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
+                  web_contents(), GURL("http://www.google.com"),
+                  /*record_metrics=*/true),
+              testing::Eq(optimization_guide::proto::PERFORMANCE_FAST));
+
+  histogram_tester.ExpectBucketCount(
+      "PerformanceHints.Observer.HintForURLResult", /*kHintFound*/ 3, 1);
+  histogram_tester.ExpectBucketCount(
+      "PerformanceHints.Observer.PerformanceClassForURL", /*kFast*/ 2, 1);
+}
+
+}  // namespace performance_hints
diff --git a/chrome/browser/performance_hints/performance_hints_observer_unittest.cc b/chrome/browser/performance_hints/performance_hints_observer_unittest.cc
index deb581e..6a2e969 100644
--- a/chrome/browser/performance_hints/performance_hints_observer_unittest.cc
+++ b/chrome/browser/performance_hints/performance_hints_observer_unittest.cc
@@ -20,8 +20,7 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/optimization_guide/core/optimization_guide_switches.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/test/mock_navigation_handle.h"
+#include "content/public/test/navigation_simulator.h"
 #include "content/public/test/web_contents_tester.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
@@ -127,25 +126,14 @@
         CanApplyOptimization(_, optimization_guide::proto::FAST_HOST_HINTS, _))
         .WillByDefault(
             Return(optimization_guide::OptimizationGuideDecision::kFalse));
-
-    test_handle_ = std::make_unique<content::MockNavigationHandle>(
-        GURL(kPageUrl), main_rfh());
-    std::vector<GURL> redirect_chain;
-    redirect_chain.emplace_back(GURL(kPageUrl));
-    test_handle_->set_redirect_chain(redirect_chain);
-    test_handle_->set_has_committed(true);
-    test_handle_->set_is_same_document(false);
-    test_handle_->set_is_error_page(false);
   }
 
-  void CallDidFinishNavigation(content::WebContents* web_contents) {
-    PerformanceHintsObserver* observer =
-        PerformanceHintsObserver::FromWebContents(web_contents);
-    observer->DidFinishNavigation(test_handle_.get());
+  void NavigateAndCommit() {
+    content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(),
+                                                               GURL(kPageUrl));
   }
 
   base::test::ScopedFeatureList scoped_feature_list_;
-  std::unique_ptr<content::MockNavigationHandle> test_handle_;
   raw_ptr<NiceMock<MockOptimizationGuideKeyedService>>
       mock_optimization_guide_keyed_service_ = nullptr;
   raw_ptr<MockOptimizationGuideKeyedService>
@@ -190,7 +178,7 @@
                 Return(optimization_guide::OptimizationGuideDecision::kTrue)));
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -229,7 +217,7 @@
                 Return(optimization_guide::OptimizationGuideDecision::kTrue)));
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -271,7 +259,7 @@
                 Return(optimization_guide::OptimizationGuideDecision::kTrue)));
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -333,7 +321,7 @@
                 Return(optimization_guide::OptimizationGuideDecision::kTrue)));
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -353,7 +341,7 @@
 
 TEST_F(PerformanceHintsObserverTest, PageHintFound) {
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -389,7 +377,7 @@
 
 TEST_F(PerformanceHintsObserverTest, PageHintNotReady) {
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -417,7 +405,7 @@
 
 TEST_F(PerformanceHintsObserverTest, FastHostHintFound) {
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -446,7 +434,7 @@
 
 TEST_F(PerformanceHintsObserverTest, FastHostHintNotReady) {
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -488,7 +476,7 @@
                   optimization_guide::proto::PERFORMANCE_HINTS)));
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -526,7 +514,7 @@
 // returned.
 TEST_F(PerformanceHintsObserverTest, SomeSourcesNotReady) {
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   ON_CALL(
       *mock_optimization_guide_keyed_service_,
@@ -561,7 +549,7 @@
                 Return(optimization_guide::OptimizationGuideDecision::kTrue)));
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -648,7 +636,7 @@
                 Return(optimization_guide::OptimizationGuideDecision::kTrue)));
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -665,7 +653,7 @@
 
 TEST_F(PerformanceHintsObserverTest, InvalidURL) {
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -684,7 +672,7 @@
 
 TEST_F(PerformanceHintsObserverTest, NoHints) {
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -724,7 +712,7 @@
                 Return(optimization_guide::OptimizationGuideDecision::kTrue)));
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -743,7 +731,7 @@
           Return(optimization_guide::OptimizationGuideDecision::kUnknown));
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -761,7 +749,7 @@
 
 TEST_F(PerformanceHintsObserverTest, CacheLinkHints) {
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   optimization_guide::proto::PerformanceHintsMetadata hints_metadata;
   auto* hint = hints_metadata.add_performance_hints();
@@ -808,7 +796,7 @@
 
 TEST_F(PerformanceHintsObserverTest, ResetObserverForNextNavigation) {
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   {
     base::HistogramTester histogram_tester;
@@ -842,7 +830,7 @@
     base::HistogramTester histogram_tester;
 
     // Simulate navigation to another page.
-    CallDidFinishNavigation(web_contents());
+    NavigateAndCommit();
 
     EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
                     web_contents(), GURL("https://www.hint.com"),
@@ -861,7 +849,7 @@
       profile(), OptimizationGuideKeyedServiceFactory::TestingFactory());
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
                   web_contents(), GURL("http://www.test.com"),
@@ -870,14 +858,13 @@
 }
 
 TEST_F(PerformanceHintsObserverTest, NoErrorPageHints) {
-  test_handle_->set_is_error_page(true);
-
   EXPECT_CALL(*mock_optimization_guide_keyed_service_,
               CanApplyOptimization(_, _, _))
       .Times(0);
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  content::NavigationSimulator::NavigateAndFailFromBrowser(
+      web_contents(), GURL(kPageUrl), net::ERR_TIMED_OUT);
 
   EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
                   web_contents(), GURL("http://www.test.com"),
@@ -886,22 +873,14 @@
 }
 
 TEST_F(PerformanceHintsObserverTest, DontFetchForSubframe) {
-  test_handle_ = std::make_unique<content::MockNavigationHandle>(
-      GURL(kPageUrl),
-      content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe"));
-  std::vector<GURL> redirect_chain;
-  redirect_chain.emplace_back(GURL(kPageUrl));
-  test_handle_->set_redirect_chain(redirect_chain);
-  test_handle_->set_has_committed(true);
-  test_handle_->set_is_same_document(false);
-  test_handle_->set_is_error_page(false);
-
   EXPECT_CALL(*mock_optimization_guide_keyed_service_,
               CanApplyOptimization(_, _, _))
       .Times(0);
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  content::NavigationSimulator::NavigateAndCommitFromDocument(
+      GURL(kPageUrl),
+      content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe"));
 
   EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
                   web_contents(), GURL(kPageUrl),
@@ -950,7 +929,7 @@
                 Return(optimization_guide::OptimizationGuideDecision::kTrue)));
 
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   base::HistogramTester histogram_tester;
 
@@ -995,7 +974,7 @@
 TEST_F(OverrideUnknownPerformanceHintsObserverFetchingNotEnabledTest,
        HintFetchingNotEnabled) {
   PerformanceHintsObserver::CreateForWebContents(web_contents());
-  CallDidFinishNavigation(web_contents());
+  NavigateAndCommit();
 
   EXPECT_THAT(PerformanceHintsObserver::PerformanceClassForURL(
                   web_contents(), GURL("http://www.test.com"),
diff --git a/chrome/browser/policy/BUILD.gn b/chrome/browser/policy/BUILD.gn
index 9f77c4a..3647cd9 100644
--- a/chrome/browser/policy/BUILD.gn
+++ b/chrome/browser/policy/BUILD.gn
@@ -236,6 +236,7 @@
   sources = [
     "cloud/cloud_policy_browsertest.cc",
     "cloud/cloud_policy_manager_browsertest.cc",
+    "cloud/device_management_service_browsertest.cc",
   ]
 
   deps = [
diff --git a/chrome/browser/policy/cloud/device_management_service_browsertest.cc b/chrome/browser/policy/cloud/device_management_service_browsertest.cc
index b8e5bb04..40af152a 100644
--- a/chrome/browser/policy/cloud/device_management_service_browsertest.cc
+++ b/chrome/browser/policy/cloud/device_management_service_browsertest.cc
@@ -12,12 +12,14 @@
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/net/system_network_context_manager.h"
-#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/policy/core/common/cloud/device_management_service.h"
 #include "components/policy/core/common/cloud/dm_auth.h"
 #include "components/policy/core/common/cloud/mock_device_management_service.h"
-#include "components/policy/test_support/local_policy_test_server.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+#include "components/policy/test_support/embedded_policy_test_server.h"
+#include "components/policy/test_support/policy_storage.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/browser_test.h"
 #include "net/base/upload_bytes_element_reader.h"
@@ -83,9 +85,9 @@
 }  // namespace
 
 class DeviceManagementServiceIntegrationTest
-    : public InProcessBrowserTest,
-      public testing::WithParamInterface<
-          std::string (DeviceManagementServiceIntegrationTest::*)(void)> {
+    : public PlatformBrowserTest,
+      public testing::WithParamInterface<std::string (
+          DeviceManagementServiceIntegrationTest::*)(void)> {
  public:
   MOCK_METHOD4(OnJobDone,
                void(DeviceManagementService::Job*,
@@ -177,9 +179,16 @@
   }
 
   void StartTestServer() {
-    test_server_ = std::make_unique<LocalPolicyTestServer>(
-        "chrome/test/data/policy/"
-        "policy_device_management_service_browsertest.json");
+    test_server_ = std::make_unique<EmbeddedPolicyTestServer>();
+    em::CloudPolicySettings settings;
+    settings.mutable_homepagelocation()->mutable_policy_options()->set_mode(
+        em::PolicyOptions::MANDATORY);
+    settings.mutable_homepagelocation()->set_value("http://www.chromium.org");
+    PolicyStorage* policy_storage = test_server_->policy_storage();
+    policy_storage->SetPolicyPayload(dm_protocol::kChromeUserPolicyType,
+                                     settings.SerializeAsString());
+    policy_storage->add_managed_user("*");
+    policy_storage->set_robot_api_auth_code("fake_auth_code");
     ASSERT_TRUE(test_server_->Start());
   }
 
@@ -195,7 +204,7 @@
   std::string token_;
   std::string robot_auth_code_;
   std::unique_ptr<DeviceManagementService> service_;
-  std::unique_ptr<LocalPolicyTestServer> test_server_;
+  std::unique_ptr<EmbeddedPolicyTestServer> test_server_;
   std::unique_ptr<network::TestURLLoaderFactory> test_url_loader_factory_;
   scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
 };
diff --git a/chrome/browser/predictors/autocomplete_action_predictor.cc b/chrome/browser/predictors/autocomplete_action_predictor.cc
index f14e10b..0bda00a 100644
--- a/chrome/browser/predictors/autocomplete_action_predictor.cc
+++ b/chrome/browser/predictors/autocomplete_action_predictor.cc
@@ -221,7 +221,9 @@
 
     prerender_handle_ = web_contents.StartPrerendering(
         url, content::PrerenderTriggerType::kEmbedder,
-        prerender_utils::kDirectUrlInputMetricSuffix);
+        prerender_utils::kDirectUrlInputMetricSuffix,
+        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+                                  ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
   } else if (base::FeatureList::IsEnabled(
                  features::kOmniboxTriggerForNoStatePrefetch)) {
     content::SessionStorageNamespace* session_storage_namespace =
diff --git a/chrome/browser/prerender/prerender_browsertest.cc b/chrome/browser/prerender/prerender_browsertest.cc
index 3870d52..19303ad1 100644
--- a/chrome/browser/prerender/prerender_browsertest.cc
+++ b/chrome/browser/prerender/prerender_browsertest.cc
@@ -110,7 +110,9 @@
   std::unique_ptr<content::PrerenderHandle> prerender_handle =
       GetActiveWebContents()->StartPrerendering(
           prerender_url, content::PrerenderTriggerType::kEmbedder,
-          prerender_utils::kDirectUrlInputMetricSuffix);
+          prerender_utils::kDirectUrlInputMetricSuffix,
+          ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+                                    ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
   EXPECT_TRUE(prerender_handle);
   content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
       *GetActiveWebContents(), prerender_url);
diff --git a/chrome/browser/printing/print_backend_browsertest.cc b/chrome/browser/printing/print_backend_browsertest.cc
index 0fd59e81..36632bb0 100644
--- a/chrome/browser/printing/print_backend_browsertest.cc
+++ b/chrome/browser/printing/print_backend_browsertest.cc
@@ -42,6 +42,7 @@
 #include "printing/test_printing_context.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_WIN)
 #include "printing/emf_win.h"
@@ -162,6 +163,48 @@
         printer_name);
   }
 
+  // Common helpers to perform particular stages of printing a document.
+  mojom::ResultCode StartPrintingAndWait(const PrintSettings& print_settings) {
+    mojom::ResultCode result;
+
+    // Safe to use base::Unretained(this) since waiting locally on the callback
+    // forces a shorter lifetime than `this`.
+    GetPrintBackendService()->StartPrinting(
+        /*document_cookie=*/1, u"document name",
+        mojom::PrintTargetType::kDirectToDevice, print_settings,
+        base::BindOnce(&PrintBackendBrowserTest::CaptureResult,
+                       base::Unretained(this), std::ref(result)));
+    WaitUntilCallbackReceived();
+    return result;
+  }
+
+#if BUILDFLAG(IS_WIN)
+  absl::optional<mojom::ResultCode> RenderPageAndWait() {
+    // Load a sample EMF file for a single page for testing handling.
+    Emf metafile;
+    if (!LoadMetafileDataFromFile("test1.emf", metafile))
+      return absl::nullopt;
+
+    base::MappedReadOnlyRegion region_mapping =
+        metafile.GetDataAsSharedMemoryRegion();
+    if (!region_mapping.IsValid())
+      return absl::nullopt;
+
+    mojom::ResultCode result;
+    GetPrintBackendService()->RenderPrintedPage(
+        /*document_cookie=*/1,
+        /*page_index=*/0, metafile.GetDataType(),
+        std::move(region_mapping.region),
+        /*page_size=*/gfx::Size(200, 200),
+        /*page_content_rect=*/gfx::Rect(0, 0, 200, 200),
+        /*shrink_factor=*/1.0f,
+        base::BindOnce(&PrintBackendBrowserTest::CaptureResult,
+                       base::Unretained(this), std::ref(result)));
+    WaitUntilCallbackReceived();
+    return result;
+  }
+#endif  // BUILDFLAG(IS_WIN)
+
   // Public callbacks used by tests.
   void OnDidEnumeratePrinters(mojom::PrinterListResultPtr& capture_printer_list,
                               mojom::PrinterListResultPtr printer_list) {
@@ -484,21 +527,11 @@
   AddDefaultPrinter();
   SetPrinterNameForSubsequentContexts(kDefaultPrinterName);
 
-  mojom::ResultCode result;
-
   PrintSettings print_settings;
   print_settings.set_device_name(
       base::ASCIIToUTF16(base::StringPiece(kDefaultPrinterName)));
 
-  // Safe to use base::Unretained(this) since waiting locally on the callback
-  // forces a shorter lifetime than `this`.
-  GetPrintBackendService()->StartPrinting(
-      /*document_cookie=*/1, u"document name",
-      mojom::PrintTargetType::kDirectToDevice, print_settings,
-      base::BindOnce(&PrintBackendBrowserTest::CaptureResult,
-                     base::Unretained(this), std::ref(result)));
-  WaitUntilCallbackReceived();
-  EXPECT_EQ(result, mojom::ResultCode::kSuccess);
+  EXPECT_EQ(StartPrintingAndWait(print_settings), mojom::ResultCode::kSuccess);
 }
 
 IN_PROC_BROWSER_TEST_F(PrintBackendBrowserTest, StartPrintingInvalidPrinter) {
@@ -506,21 +539,11 @@
   AddDefaultPrinter();
   SetPrinterNameForSubsequentContexts(kDefaultPrinterName);
 
-  mojom::ResultCode result;
-
   PrintSettings print_settings;
   print_settings.set_device_name(
       base::ASCIIToUTF16(base::StringPiece(kInvalidPrinterName)));
 
-  // Safe to use base::Unretained(this) since waiting locally on the callback
-  // forces a shorter lifetime than `this`.
-  GetPrintBackendService()->StartPrinting(
-      /*document_cookie=*/1, u"document name",
-      mojom::PrintTargetType::kDirectToDevice, print_settings,
-      base::BindOnce(&PrintBackendBrowserTest::CaptureResult,
-                     base::Unretained(this), std::ref(result)));
-  WaitUntilCallbackReceived();
-  EXPECT_EQ(result, mojom::ResultCode::kFailed);
+  EXPECT_EQ(StartPrintingAndWait(print_settings), mojom::ResultCode::kFailed);
 }
 
 #if BUILDFLAG(IS_WIN)
@@ -529,40 +552,15 @@
   AddDefaultPrinter();
   SetPrinterNameForSubsequentContexts(kDefaultPrinterName);
 
-  mojom::ResultCode result;
-
   PrintSettings print_settings;
   print_settings.set_device_name(
       base::ASCIIToUTF16(base::StringPiece(kDefaultPrinterName)));
 
-  // Safe to use base::Unretained(this) since waiting locally on the callback
-  // forces a shorter lifetime than `this`.
-  GetPrintBackendService()->StartPrinting(
-      /*document_cookie=*/1, u"document name",
-      mojom::PrintTargetType::kDirectToDevice, print_settings,
-      base::BindOnce(&PrintBackendBrowserTest::CaptureResult,
-                     base::Unretained(this), std::ref(result)));
-  WaitUntilCallbackReceived();
-  EXPECT_EQ(result, mojom::ResultCode::kSuccess);
+  EXPECT_EQ(StartPrintingAndWait(print_settings), mojom::ResultCode::kSuccess);
 
-  auto metafile = std::make_unique<Emf>();
-  ASSERT_TRUE(LoadMetafileDataFromFile("embedded_images_ps_level3.emf",
-                                       *metafile.get()));
-  base::MappedReadOnlyRegion region_mapping =
-      metafile->GetDataAsSharedMemoryRegion();
-  ASSERT_TRUE(region_mapping.IsValid());
-
-  GetPrintBackendService()->RenderPrintedPage(
-      /*document_cookie=*/1,
-      /*page_index=*/0, metafile->GetDataType(),
-      std::move(region_mapping.region),
-      /*page_size=*/gfx::Size(200, 200),
-      /*page_content_rect=*/gfx::Rect(0, 0, 200, 200),
-      /*shrink_factor=*/1.0f,
-      base::BindOnce(&PrintBackendBrowserTest::CaptureResult,
-                     base::Unretained(this), std::ref(result)));
-  WaitUntilCallbackReceived();
-  EXPECT_EQ(result, mojom::ResultCode::kSuccess);
+  absl::optional<mojom::ResultCode> result = RenderPageAndWait();
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(result.value(), mojom::ResultCode::kSuccess);
 }
 #endif  // BUILDFLAG(IS_WIN)
 
diff --git a/chrome/browser/printing/print_browsertest.cc b/chrome/browser/printing/print_browsertest.cc
index 369f466..0b94234 100644
--- a/chrome/browser/printing/print_browsertest.cc
+++ b/chrome/browser/printing/print_browsertest.cc
@@ -1433,7 +1433,7 @@
   ASSERT_EQ(print_view_manager->GetPrintAllowance(),
             TestPrintViewManagerForDLP::PrintAllowance::kDisallowed);
 }
-#endif  // BUILDFLAG(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 // Printing preview a webpage with isolate-origins enabled.
 // Test that we will use oopif printing for this case.
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler.js
index 88c3679..53926b1 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler.js
@@ -124,6 +124,7 @@
         }
       });
       if (!scrollResult) {
+        this.isScrolling_ = false;
         ChromeVoxState.instance.navigateToRange(target, false, speechProps);
         return;
       }
@@ -214,7 +215,7 @@
 
       // Usual case. Retry navigation with a refreshed tree.
       retryCommandFunc();
-    })().catch(e => {
+    })().finally(() => {
       this.isScrolling_ = false;
     });
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js
index d61b055..c90ffc6 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js
@@ -26,6 +26,7 @@
         <p>2nd item</p>
         <p>3rd item</p>
       </div>
+      <p>hello</p><p>world</p>
       <script>
         let i = 4;
         const list = document.getElementById('div');
@@ -166,7 +167,7 @@
       this.runWithFakeArcSimpleScrollable(function(root) {
         const handler = new AutoScrollHandler();
 
-        const list = root.firstChild;
+        const list = root.find({role: RoleType.LIST});
         const firstItemCursor = cursors.Range.fromNode(list.firstChild);
         const lastItemCursor = cursors.Range.fromNode(list.lastChild);
 
@@ -183,7 +184,7 @@
       this.runWithFakeArcSimpleScrollable(function(root) {
         const handler = new AutoScrollHandler();
 
-        const list = root.firstChild;
+        const list = root.find({role: RoleType.LIST});
         const rootCursor = cursors.Range.fromNode(root);
         const firstItemCursor = cursors.Range.fromNode(list.firstChild);
         const lastItemCursor = cursors.Range.fromNode(list.lastChild);
@@ -220,6 +221,26 @@
   });
 });
 
+TEST_F(
+    'ChromeVoxAutoScrollHandlerTest', 'ScrollForwardReturnsFalse', function() {
+      const mockFeedback = this.createMockFeedback();
+      this.runWithFakeArcSimpleScrollable(function(root) {
+        const list = root.find({role: RoleType.LIST});
+        list.scrollForward = (callback) => callback(false);
+
+        mockFeedback.expectSpeech('1st item')
+            .call(doCmd('nextObject'))
+            .expectSpeech('2nd item')
+            .call(doCmd('nextObject'))
+            .expectSpeech('3rd item')
+            .call(doCmd('nextObject'))
+            .expectSpeech('hello')
+            .call(doCmd('nextObject'))
+            .expectSpeech('world')
+            .replay();
+      });
+    });
+
 TEST_F('ChromeVoxAutoScrollHandlerTest', 'RecyclerViewByObject', function() {
   const mockFeedback = this.createMockFeedback();
   this.runWithFakeArcRecyclerView(function(root) {
diff --git a/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn b/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
index 2724558..20361a0 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
+++ b/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
@@ -29,6 +29,7 @@
     ":build_mojo_grdp",
     ":build_preprocessed_grdp",
     ":emoji_data",
+    ":emoji_data_remaining",
     ":emoticon_data",
   ]
   grd_prefix = "emoji_picker"
@@ -109,12 +110,44 @@
     "//third_party/cldr/src/common/annotationsDerived/en.xml",
     "//third_party/cldr/src/common/annotationsDerived/en_001.xml",
   ]
-  merged_json = "$target_gen_dir/{{source_name_part}}.json"
+  merged_json = "$target_gen_dir/{{source_name_part}}_start.json"
 
   sources = metadata_json
   inputs = keyword_xmls
   outputs = [ merged_json ]
   args = [
+           "--firstgroup",
+           "True",
+           "--metadata",
+           "{{source}}",
+           "--output",
+           rebase_path(merged_json, root_build_dir),
+           "--keywords",
+         ] + rebase_path(keyword_xmls, root_build_dir)
+}
+
+action_foreach("emoji_data_remaining") {
+  script = "tools/emoji_data.py"
+
+  metadata_json = [
+    "//third_party/emoji-metadata/src/emoji_14_0_ordering.json",
+    "./emoji_test_ordering.json",
+  ]
+  keyword_xmls = [
+    # later keywords will override earlier keywords for a particular emoji.
+    "//third_party/cldr/src/common/annotations/en.xml",
+    "//third_party/cldr/src/common/annotations/en_001.xml",
+    "//third_party/cldr/src/common/annotationsDerived/en.xml",
+    "//third_party/cldr/src/common/annotationsDerived/en_001.xml",
+  ]
+  merged_json = "$target_gen_dir/{{source_name_part}}_remaining.json"
+
+  sources = metadata_json
+  inputs = keyword_xmls
+  outputs = [ merged_json ]
+  args = [
+           "--firstgroup",
+           "False",
            "--metadata",
            "{{source}}",
            "--output",
diff --git a/chrome/browser/resources/chromeos/emoji_picker/constants.js b/chrome/browser/resources/chromeos/emoji_picker/constants.js
index e4260a8..0bad62110 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/constants.js
+++ b/chrome/browser/resources/chromeos/emoji_picker/constants.js
@@ -38,7 +38,6 @@
 export const V2_EMOJI_PICKER_TOTAL_EMOJI_WIDTH =
     V2_EMOJI_ICON_SIZE + V2_EMOJI_GROUP_SPACING;
 export const V2_TAB_BUTTON_MARGIN = 5;
-export const V2_TEXT_GROUP_BUTTON_PADDING = 4;
 export const V2_EMOJI_SPACING =
     (V2_EMOJI_PICKER_WIDTH - 2 * V2_EMOJI_PICKER_SIDE_PADDING -
      V2_EMOJI_PER_ROW * V2_EMOJI_ICON_SIZE) /
@@ -53,6 +52,4 @@
 export const V2_EMOJI_PICKER_TOTAL_EMOJI_WIDTH_PX =
     `${V2_EMOJI_PICKER_TOTAL_EMOJI_WIDTH}px`;
 export const V2_TAB_BUTTON_MARGIN_PX = `${V2_TAB_BUTTON_MARGIN}px`;
-export const V2_TEXT_GROUP_BUTTON_PADDING_PX =
-    `${V2_TEXT_GROUP_BUTTON_PADDING}px`;
 export const V2_EMOJI_SPACING_PX = `${V2_EMOJI_SPACING}px`;
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_ordering.grdp b/chrome/browser/resources/chromeos/emoji_picker/emoji_ordering.grdp
index 70888ba3f..bf1efbd 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_ordering.grdp
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_ordering.grdp
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <grit-part>
-      <include name="IDR_EMOJI_PICKER_EMOJI_14_0_ORDERING_JSON" compress="gzip" file="${root_gen_dir}/chrome/browser/resources/chromeos/emoji_picker/emoji_14_0_ordering.json" resource_path="emoji_14_0_ordering.json" use_base_dir="false" type="chrome_html" />
-      <include name="IDR_EMOJI_PICKER_EMOJI_TEST_ORDERING_JSON" compress="gzip" file="${root_gen_dir}/chrome/browser/resources/chromeos/emoji_picker/emoji_test_ordering.json" resource_path="emoji_test_ordering.json" use_base_dir="false" type="chrome_html" />
+      <include name="IDR_EMOJI_PICKER_EMOJI_14_0_ORDERING_JSON_START" compress="gzip" file="${root_gen_dir}/chrome/browser/resources/chromeos/emoji_picker/emoji_14_0_ordering_start.json" resource_path="emoji_14_0_ordering_start.json" use_base_dir="false" type="chrome_html" />
+      <include name="IDR_EMOJI_PICKER_EMOJI_14_0_ORDERING_JSON_REMAINING" compress="gzip" file="${root_gen_dir}/chrome/browser/resources/chromeos/emoji_picker/emoji_14_0_ordering_remaining.json" resource_path="emoji_14_0_ordering_remaining.json" use_base_dir="false" type="chrome_html" />
+      <include name="IDR_EMOJI_PICKER_EMOJI_TEST_ORDERING_JSON_START" compress="gzip" file="${root_gen_dir}/chrome/browser/resources/chromeos/emoji_picker/emoji_test_ordering_start.json" resource_path="emoji_test_ordering_start.json" use_base_dir="false" type="chrome_html" />
+      <include name="IDR_EMOJI_PICKER_EMOJI_TEST_ORDERING_JSON_REMAINING" compress="gzip" file="${root_gen_dir}/chrome/browser/resources/chromeos/emoji_picker/emoji_test_ordering_remaining.json" resource_path="emoji_test_ordering_remaining.json" use_base_dir="false" type="chrome_html" />
 </grit-part>
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
index 53198f72..d742d29 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
@@ -12,16 +12,16 @@
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {afterNextRender, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {EMOJI_GROUP_SIZE_PX, EMOJI_ICON_SIZE, EMOJI_PER_ROW, EMOJI_PICKER_HEIGHT_PX, EMOJI_PICKER_SIDE_PADDING, EMOJI_PICKER_SIDE_PADDING_PX, EMOJI_PICKER_TOP_PADDING_PX, EMOJI_PICKER_TOTAL_EMOJI_WIDTH, EMOJI_PICKER_TOTAL_EMOJI_WIDTH_PX, EMOJI_PICKER_WIDTH, EMOJI_PICKER_WIDTH_PX, EMOJI_SIZE_PX, EMOJI_SPACING_PX, GROUP_ICON_SIZE, GROUP_PER_ROW, V2_EMOJI_GROUP_SPACING_PX, V2_EMOJI_ICON_SIZE, V2_EMOJI_ICON_SIZE_PX, V2_EMOJI_PICKER_HEIGHT_PX, V2_EMOJI_PICKER_SIDE_PADDING_PX, V2_EMOJI_PICKER_TOTAL_EMOJI_WIDTH, V2_EMOJI_PICKER_WIDTH_PX, V2_EMOJI_SPACING_PX, V2_TAB_BUTTON_MARGIN, V2_TAB_BUTTON_MARGIN_PX, V2_TEXT_GROUP_BUTTON_PADDING, V2_TEXT_GROUP_BUTTON_PADDING_PX} from './constants.js';
+import {EMOJI_GROUP_SIZE_PX, EMOJI_ICON_SIZE, EMOJI_PER_ROW, EMOJI_PICKER_HEIGHT_PX, EMOJI_PICKER_SIDE_PADDING, EMOJI_PICKER_SIDE_PADDING_PX, EMOJI_PICKER_TOP_PADDING_PX, EMOJI_PICKER_TOTAL_EMOJI_WIDTH, EMOJI_PICKER_TOTAL_EMOJI_WIDTH_PX, EMOJI_PICKER_WIDTH, EMOJI_PICKER_WIDTH_PX, EMOJI_SIZE_PX, EMOJI_SPACING_PX, GROUP_ICON_SIZE, GROUP_PER_ROW, V2_EMOJI_GROUP_SPACING_PX, V2_EMOJI_ICON_SIZE, V2_EMOJI_ICON_SIZE_PX, V2_EMOJI_PICKER_HEIGHT_PX, V2_EMOJI_PICKER_SIDE_PADDING_PX, V2_EMOJI_PICKER_TOTAL_EMOJI_WIDTH, V2_EMOJI_PICKER_WIDTH_PX, V2_EMOJI_SPACING_PX, V2_TAB_BUTTON_MARGIN, V2_TAB_BUTTON_MARGIN_PX} from './constants.js';
 import {EmojiButton} from './emoji_button.js';
 import {Feature} from './emoji_picker.mojom-webui.js';
 import {EmojiPickerApiProxy, EmojiPickerApiProxyImpl} from './emoji_picker_api_proxy.js';
-import {CATEGORY_BUTTON_CLICK, createCustomEvent, EMOJI_BUTTON_CLICK, EMOJI_CLEAR_RECENTS_CLICK, EMOJI_DATA_LOADED, EMOJI_VARIANTS_SHOWN, EmojiVariantsShownEvent, GROUP_BUTTON_CLICK, V2_CONTENT_LOADED} from './events.js';
+import {CATEGORY_BUTTON_CLICK, createCustomEvent, EMOJI_BUTTON_CLICK, EMOJI_CLEAR_RECENTS_CLICK, EMOJI_DATA_LOADED, EMOJI_VARIANTS_SHOWN, EmojiVariantsShownEvent, GROUP_BUTTON_CLICK} from './events.js';
 import {EMPTY_EMOTICON_DATA, V2_SUBCATEGORY_TABS} from './metadata_extension.js';
 import {RecentEmojiStore} from './store.js';
 import {Emoji, EmojiGroup, EmojiGroupData, EmojiVariants, StoredEmoji, SubcategoryData} from './types.js';
 
-const EMOJI_ORDERING_JSON = '/emoji_14_0_ordering.json';
+const EMOJI_ORDERING_JSON_TEMPLATE = '/emoji_14_0_ordering';
 
 // the name attributes below are used to label the group buttons.
 // the ordering group names are used for the group headings in the emoji picker.
@@ -124,10 +124,11 @@
 
   static get properties() {
     return {
+      /** {string} */
+      emojiDataUrl: {type: String, value: EMOJI_ORDERING_JSON_TEMPLATE},
       /** @private {string} */
       category: {type: String, value: 'emoji', observer: 'onCategoryChanged'},
       /** @type {string} */
-      emojiDataUrl: {type: String, value: EMOJI_ORDERING_JSON},
       /** @private {!Array<!SubcategoryData>} */
       emojiGroupTabs: {type: Array},
       /** @private {?EmojiGroupData} */
@@ -149,11 +150,7 @@
         reflectToAttribute: true
       },
       /** @private {boolean} */
-      v2Enabled: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true
-      }
+      v2Enabled: {type: Boolean, value: false, reflectToAttribute: true}
     };
   }
 
@@ -264,7 +261,6 @@
           // TODO(b/211520561): remove below line after the emoticon loading
           // logic is finished.
           this.emojiData = this.emojiData.concat(EMPTY_EMOTICON_DATA);
-          this.dispatchEvent(createCustomEvent(V2_CONTENT_LOADED));
         }
       });
     });
@@ -284,7 +280,6 @@
       '--v2-emoji-size': V2_EMOJI_ICON_SIZE_PX,
       '--v2-emoji-group-spacing': V2_EMOJI_GROUP_SPACING_PX,
       '--v2-tab-button-margin': V2_TAB_BUTTON_MARGIN_PX,
-      '--v2-text-group-button-padding': V2_TEXT_GROUP_BUTTON_PADDING_PX,
       '--v2-emoji-spacing': V2_EMOJI_SPACING_PX,
     });
   }
@@ -303,7 +298,7 @@
         this.onEmojiDataLoaded(xhr.responseText);
         resolve();
       };
-      xhr.open('GET', this.emojiDataUrl);
+      xhr.open('GET', this.emojiDataUrl + '_start.json');
       xhr.send();
     });
   }
@@ -394,9 +389,6 @@
       const offsetByLeftChevron = V2_EMOJI_ICON_SIZE + chevronMargin;
       const maxPagination = this.getPaginationArray(this.emojiGroupTabs).pop();
       this.pagination = Math.min(this.pagination + 1, maxPagination);
-      const nextTab =
-          this.emojiGroupTabs.find((tab) => tab.pagination === this.pagination);
-      this.scrollToGroup(nextTab.groupId);
       this.$.tabs.scrollLeft =
           (this.pagination - 1) * EMOJI_PICKER_WIDTH - offsetByLeftChevron;
       this.groupTabsMoving = true;
@@ -418,9 +410,6 @@
       const chevronMargin = V2_TAB_BUTTON_MARGIN;
       const offsetByLeftChevron = V2_EMOJI_ICON_SIZE + chevronMargin;
       this.pagination = Math.max(this.pagination - 1, 1);
-      const nextTab =
-          this.emojiGroupTabs.find((tab) => tab.pagination === this.pagination);
-      this.scrollToGroup(nextTab.groupId);
       this.$.tabs.scrollLeft = this.pagination === 1 ?
           0 :
           (this.pagination - 1) * EMOJI_PICKER_WIDTH - offsetByLeftChevron;
@@ -581,19 +570,13 @@
 
         // for text group button, the highlight bar only spans its inner width,
         // which excludes both padding and margin.
-        if (index < subcategoryTabs.length) {
-          const barInlineGap =
-              V2_TAB_BUTTON_MARGIN + V2_TEXT_GROUP_BUTTON_PADDING;
-          const currentTab = subcategoryTabs[index];
-          this.$.bar.style.left = `${
-              currentTab.offsetLeft - EMOJI_PICKER_SIDE_PADDING -
-              this.$.tabs.scrollLeft}px`;
-          this.$.bar.style.width =
-              `${subcategoryTabs[index].clientWidth - barInlineGap * 2}px`;
-        } else {
-          this.$.bar.style.left = `0px`;
-          this.$.bar.style.width = `0px`;
-        }
+        const barInlineGap = 9;
+        this.$.bar.style.left = `${
+            subcategoryTabs[index].offsetLeft - EMOJI_PICKER_SIDE_PADDING -
+            this.$.tabs.scrollLeft}px`;
+        this.$.bar.style.width =
+            `${subcategoryTabs[index].clientWidth - barInlineGap * 2}px`;
+
         // TODO(b/213230435): fix the bar width and left position when the
         // history tab is active
       }
@@ -674,11 +657,18 @@
     // other categories (which will be off screen).
     this.emojiData = [emojidata[0]];
     afterNextRender(this, () => {
-      this.emojiData = emojidata;
-      this.updateActiveGroup(/*updateTabsScroll=*/ true);
+      const xhr = new XMLHttpRequest();
+      xhr.onloadend = () => this.onEmojiDataLoadedRemaining(xhr.responseText);
+      xhr.open('GET', this.emojiDataUrl + '_remaining.json');
+      xhr.send();
     });
   }
 
+  onEmojiDataLoadedRemaining(data) {
+    const emojidata = /** @type {!EmojiGroupData} */ (JSON.parse(data));
+    this.push('emojiData', ...emojidata);
+  }
+
   /**
    * Fires DATA_LOADED_EVENT when emoji data is loaded and the emoji picker
    * is ready to use.
@@ -709,8 +699,11 @@
       this.set('emojiGroupTabs', [historyTab, ...categoryTabs]);
       this.set('pagination', 1);
       this.updateChevrons();
-      this.scrollToGroup(this.emojiGroupTabs[1].groupId);
-      this.$.tabs.scrollLeft = 0;
+      this.scrollToGroup(this.emojiGroupTabs[0].groupId);
+      afterNextRender(this, () => {
+        this.updateActiveGroup(true);
+        this.$.tabs.scrollLeft = 0;
+      });
     }
   }
 
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_test_ordering.json b/chrome/browser/resources/chromeos/emoji_picker/emoji_test_ordering.json
index a3d4060..b3d78d54 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_test_ordering.json
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_test_ordering.json
@@ -1035,29 +1035,5 @@
         "emoticons": []
       }
     ]
-  },
-  {
-    "group": "Group 4",
-    "emoji": []
-  },
-  {
-    "group": "Group 5",
-    "emoji": []
-  },
-  {
-    "group": "Group 6",
-    "emoji": []
-  },
-  {
-    "group": "Group 7",
-    "emoji": []
-  },
-  {
-    "group": "Group 8",
-    "emoji": []
-  },
-  {
-    "group": "Group 9",
-    "emoji": []
   }
 ]
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/emoji_picker/events.js b/chrome/browser/resources/chromeos/emoji_picker/events.js
index 0fa1da4..15004fa 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/events.js
+++ b/chrome/browser/resources/chromeos/emoji_picker/events.js
@@ -46,13 +46,6 @@
 export const EMOJI_CLEAR_RECENTS_CLICK = 'emoji-clear-recents-click';
 
 /**
- *
- * @typedef {!CustomEvent}
- */
-export let V2ContentLoadedEvent;
-
-export const V2_CONTENT_LOADED = 'v2-content-loaded';
-/**
  * Constructs a CustomEvent with the given event type and details.
  * The event will bubble up through elements and components.
  *
diff --git a/chrome/browser/resources/chromeos/emoji_picker/text_group_button.html b/chrome/browser/resources/chromeos/emoji_picker/text_group_button.html
index 5e50ba3..66c4dec3 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/text_group_button.html
+++ b/chrome/browser/resources/chromeos/emoji_picker/text_group_button.html
@@ -8,7 +8,7 @@
       margin: 0;
       margin-inline: var(--v2-tab-button-margin);
       min-width: unset;
-      padding-inline: var(--v2-text-group-button-padding);
+      padding-inline: 4px;
       width: max-content;
     }
 
diff --git a/chrome/browser/resources/chromeos/emoji_picker/tools/emoji_data.py b/chrome/browser/resources/chromeos/emoji_picker/tools/emoji_data.py
index 865ba62..3ec9f9b 100755
--- a/chrome/browser/resources/chromeos/emoji_picker/tools/emoji_data.py
+++ b/chrome/browser/resources/chromeos/emoji_picker/tools/emoji_data.py
@@ -48,7 +48,7 @@
         return json.load(file)
 
 
-def transform_emoji_data(metadata, names, keywords):
+def transform_emoji_data(metadata, names, keywords, firstOnly):
     def transform(codepoints, emoticons = None, shortcodes = None):
         if emoticons is None:
           emoticons = []
@@ -69,16 +69,26 @@
           keyword_list = emoticons
 
         return {'string': string, 'name': name, 'keywords': keyword_list}
-
-    for group in metadata:
+    if firstOnly:
+      metadata_out = [metadata[0]]
+    else:
+      metadata_out = metadata[1:]
+    for group in metadata_out:
         for emoji in group['emoji']:
             emoji['base'] = transform(emoji['base'],
                                       emoji['emoticons'],
                                       emoji.get('shortcodes',[]))
-            emoji['alternates'] = [
-                transform(e,) for e in emoji['alternates']
-            ]
+            del emoji['emoticons']
+            if emoji.get('shortcodes'):
+              del emoji['shortcodes']
+            if emoji['alternates']:
+              emoji['alternates'] = [
+                  transform(e,) for e in emoji['alternates']
+              ]
+            else:
+              del emoji['alternates']
 
+    return metadata_out
 
 def main(args):
     parser = argparse.ArgumentParser()
@@ -92,12 +102,17 @@
                         required=True,
                         nargs='+',
                         help='emoji keyword files as list of XML files')
+    parser.add_argument('--firstgroup',
+                        required = True,
+                        help='Only output the first group, otherwise output all groups'
+    )
 
     options = parser.parse_args(args)
 
     metadata_file = options.metadata
     keyword_files = options.keywords
     output_file = options.output
+    first_group = options.firstgroup == "True"
 
     # iterate through keyword files and combine them
     names = {}
@@ -109,7 +124,7 @@
 
     # parse emoji ordering data
     metadata = parse_emoji_metadata(metadata_file)
-    transform_emoji_data(metadata, names, keywords)
+    metadata = transform_emoji_data(metadata, names, keywords, first_group)
 
     # write output file atomically in utf-8 format.
     with build_utils.AtomicOutput(output_file) as tmp_file:
diff --git a/chrome/browser/resources/pdf/gesture_detector.js b/chrome/browser/resources/pdf/gesture_detector.js
index d9ba6cb..266fd130 100644
--- a/chrome/browser/resources/pdf/gesture_detector.js
+++ b/chrome/browser/resources/pdf/gesture_detector.js
@@ -55,6 +55,9 @@
     this.pinchStartEvent_ = null;
     this.lastTouchTouchesCount_ = 0;
 
+    /** @private {boolean} */
+    this.isPresentationMode_ = false;
+
     /** @private {TouchEvent} */
     this.lastEvent_ = null;
 
@@ -78,6 +81,11 @@
     this.eventTarget_ = new EventTarget();
   }
 
+  /** @param {boolean} enabled */
+  setPresentationMode(enabled) {
+    this.isPresentationMode_ = enabled;
+  }
+
   /** @return {!EventTarget} */
   getEventTarget() {
     return this.eventTarget_;
@@ -174,13 +182,19 @@
     // to anchor the zoom around the mouse position instead of the scroll
     // position.
     if (!event.ctrlKey) {
+      if (this.isPresentationMode_) {
+        this.notify_('wheel', {
+          center: {x: event.clientX, y: event.clientY},
+          direction: event.deltaY > 0 ? 'down' : 'up',
+        });
+      }
       return;
     }
 
     event.preventDefault();
 
     // Disable wheel gestures in Presentation mode.
-    if (document.fullscreenElement !== null) {
+    if (this.isPresentationMode_) {
       return;
     }
 
diff --git a/chrome/browser/resources/pdf/pdf_internal_plugin_wrapper.js b/chrome/browser/resources/pdf/pdf_internal_plugin_wrapper.js
index 469c559b..08392c1 100644
--- a/chrome/browser/resources/pdf/pdf_internal_plugin_wrapper.js
+++ b/chrome/browser/resources/pdf/pdf_internal_plugin_wrapper.js
@@ -60,6 +60,7 @@
 // Parent-to-plugin message handlers. Most messages are passed through, but some
 // messages (with handlers that `return` immediately) are meant only for this
 // frame, not the plugin.
+let isPresentationMode = false;
 channel.port1.onmessage = e => {
   switch (e.data.type) {
     case 'loadArray':
@@ -70,6 +71,22 @@
       plugin.setAttribute('has-edits', '');
       return;
 
+    case 'setReadOnly':
+      // TODO(crbug.com/702993): Rename the incoming message to reflect that
+      // this is only used by Presentation mode.
+      isPresentationMode = e.data.enableReadOnly;
+
+      gestureDetector.setPresentationMode(isPresentationMode);
+      if (isPresentationMode) {
+        document.documentElement.className = 'fullscreen';
+      } else {
+        document.documentElement.className = '';
+
+        // Ensure that directional keys still work after exiting.
+        plugin.focus();
+      }
+      break;
+
     case 'syncScrollToRemote':
       window.scrollTo(e.data.x, e.data.y);
       channel.port1.postMessage({
@@ -136,7 +153,7 @@
 }
 
 const gestureDetector = new GestureDetector(plugin);
-for (const type of ['pinchstart', 'pinchupdate', 'pinchend']) {
+for (const type of ['pinchstart', 'pinchupdate', 'pinchend', 'wheel']) {
   gestureDetector.getEventTarget().addEventListener(type, relayGesture);
 }
 
@@ -167,12 +184,26 @@
       // Print Preview is interested in Escape and Tab.
       break;
 
+    case '=':
+    case '-':
+    case '+':
+      // Ignore zoom shortcuts in Presentation mode.
+      if (isPresentationMode && hasCtrlModifier(e)) {
+        e.preventDefault();
+      }
+      return;
+
+    case 'a':
+      // Take over CTRL+A.
+      if (hasCtrlModifier(e)) {
+        e.preventDefault();
+        break;
+      }
+      return;
+
     default:
-      if (e.ctrlKey || e.metaKey) {
-        // Take over Ctrl+A, but not other shortcuts, such as zoom or print.
-        if (e.key === 'a') {
-          e.preventDefault();
-        }
+      // Relay (but don't prevent) other shortcuts.
+      if (hasCtrlModifier(e)) {
         break;
       }
       return;
@@ -206,6 +237,19 @@
   }
 });
 
+// TODO(crbug.com/1252096): Load from pdf_viewer_utils.js instead.
+/**
+ * @param {!KeyboardEvent} e
+ * @return {boolean}
+ */
+function hasCtrlModifier(e) {
+  let hasModifier = e.ctrlKey;
+  // <if expr="is_macosx">
+  hasModifier = e.metaKey;  // AKA Command.
+  // </if>
+  return hasModifier;
+}
+
 // TODO(crbug.com/1252096): Load from chrome://resources/js/util.m.js instead.
 /**
  * @param {!Event} e
diff --git a/chrome/browser/resources/pdf/pdf_viewer.js b/chrome/browser/resources/pdf/pdf_viewer.js
index c8990a29..4d45dfa 100644
--- a/chrome/browser/resources/pdf/pdf_viewer.js
+++ b/chrome/browser/resources/pdf/pdf_viewer.js
@@ -565,11 +565,6 @@
 
   /** @private */
   onPresentClick_() {
-    const onWheel = e => {
-      e.deltaY > 0 ? this.viewport.goToNextPage() :
-                     this.viewport.goToPreviousPage();
-    };
-
     const scroller = /** @type {!HTMLElement} */ (
         this.shadowRoot.querySelector('#scroller'));
 
@@ -581,8 +576,8 @@
         .then(() => {
           this.forceFit(FittingType.FIT_TO_HEIGHT);
 
-          // Add a 'wheel' listener, only while in Presentation mode.
-          scroller.addEventListener('wheel', onWheel);
+          // Switch viewport's wheel behavior.
+          this.viewport.setPresentationMode(true);
 
           // Restrict the content to read only (e.g. disable forms and links).
           this.pluginController_.setReadOnly(true);
@@ -590,7 +585,7 @@
           // Revert back to the normal state when exiting Presentation mode.
           eventToPromise('fullscreenchange', scroller).then(() => {
             assert(document.fullscreenElement === null);
-            scroller.removeEventListener('wheel', onWheel);
+            this.viewport.setPresentationMode(false);
             this.pluginController_.setReadOnly(false);
 
             // Ensure that directional keys still work after exiting.
diff --git a/chrome/browser/resources/pdf/viewport.js b/chrome/browser/resources/pdf/viewport.js
index 082b272..c3baa5f 100644
--- a/chrome/browser/resources/pdf/viewport.js
+++ b/chrome/browser/resources/pdf/viewport.js
@@ -168,6 +168,10 @@
         'pinchend',
         e => this.onPinchEnd_(
             /** @type {!CustomEvent<!PinchEventDetail>} */ (e)));
+    this.gestureDetector_.getEventTarget().addEventListener(
+        'wheel',
+        e => this.onWheel_(
+            /** @type {!CustomEvent<!PinchEventDetail>} */ (e)));
 
     // Set to a default zoom manager - used in tests.
     this.setZoomManager(new InactiveZoomManager(this.getZoom.bind(this), 1));
@@ -200,6 +204,15 @@
   }
 
   /**
+   * Sets whether the viewport is in Presentation mode.
+   * @param {boolean} enabled
+   */
+  setPresentationMode(enabled) {
+    assert((document.fullscreenElement !== null) === enabled);
+    this.gestureDetector_.setPresentationMode(enabled);
+  }
+
+  /**
    * Sets the contents of the viewport, scrolling within the viewport's window.
    * @param {?Node} content The new viewport contents, or null to clear the
    *     viewport.
@@ -1541,6 +1554,19 @@
     });
   }
 
+  /**
+   * A callback that's called when a Presentation mode wheel event is detected.
+   * @param {!CustomEvent<!PinchEventDetail>} e the pinch event.
+   * @private
+   */
+  onWheel_(e) {
+    if (e.detail.direction === 'down') {
+      this.goToNextPage();
+    } else {
+      this.goToPreviousPage();
+    }
+  }
+
   /** @return {!GestureDetector} */
   getGestureDetectorForTesting() {
     return this.gestureDetector_;
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.html
index f246084..6123cdac 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.html
@@ -14,6 +14,12 @@
     height: var(--help-icon-size);
     width: var(--help-icon-size);
   }
+
+  #overlap-warning {
+    color: var(--secondary-text-color);
+    margin-block-start: -12px;
+    margin-inline-start: 36px;
+  }
 </style>
 <div class="permission-section-header">
   <localized-link id="heading" class="header-text"
@@ -27,7 +33,7 @@
     <localized-link id="info-string"
       localized-string="[[getDisabledExplanation_(app)]]">
     </localized-link>
-</span>
+  </span>
 </template>
 <div class="list-frame">
   <cr-radio-group id="radio-group"
@@ -40,6 +46,11 @@
     <cr-radio-button id="browser" name="browser">
       $i18n{appManagementIntentSharingOpenBrowserLabel}
     </cr-radio-button>
+    <template is="dom-if" if="[[showOverlappingAppsWarning_]]">
+      <div id="overlap-warning">
+        [[overlappingAppsWarning_]]
+      </div>
+    </template>
   </cr-radio-group>
 </div>
 <template is="dom-if" if="[[showSupportedLinksDialog_]]" restamp>
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js
index bcd15c5..afb9b2e 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js
@@ -71,13 +71,46 @@
     },
 
     /**
+     * @private {string}
+     */
+    overlappingAppsWarning_: {
+      type: String,
+    },
+
+    /**
+     * @private {boolean}
+     */
+    showOverlappingAppsWarning_: {
+      type: Boolean,
+      value: false,
+    },
+
+    /**
+     * @type {AppMap}
+     * @private
+     */
+    apps_: {
+      type: Object,
+    },
+
+    /**
      * @private {Array<string>}
      */
     overlappingAppIds_: {
       type: Array,
     },
+
   },
 
+  attached() {
+    this.watch('apps_', state => state.apps);
+    this.updateFromStore();
+  },
+
+  observers: [
+    'getOverlappingAppsWarning_(apps_, app)',
+  ],
+
   /**
    * The supported links item is not available when an app has no supported
    * links.
@@ -136,6 +169,65 @@
         {substitutions: [String(app.title)]});
   },
 
+  /**
+   * @param {!AppMap} apps
+   * @param {!App} app
+   * @private
+   */
+  async getOverlappingAppsWarning_(apps, app) {
+    if (app === undefined || app.isPreferredApp || apps === undefined) {
+      this.showOverlappingAppsWarning_ = false;
+      return;
+    }
+
+    let overlappingAppIds = [];
+    try {
+      const {appIds: appIds} =
+          await BrowserProxy.getInstance().handler.getOverlappingPreferredApps(
+              app.id);
+      overlappingAppIds = appIds;
+    } catch (err) {
+      // If we fail to get the overlapping preferred apps, do not
+      // show the overlap warning.
+      console.log(err);
+      this.showOverlappingAppsWarning_ = false;
+      return;
+    }
+    this.overlappingAppIds_ = overlappingAppIds;
+
+    const appNames = overlappingAppIds.map(app_id => {
+      assert(apps[app_id]);
+      return apps[app_id].title;
+    });
+
+    if (appNames.length === 0) {
+      this.showOverlappingAppsWarning_ = false;
+      return;
+    }
+
+    switch (appNames.length) {
+      case 1:
+        this.overlappingAppsWarning_ =
+            this.i18n('appManagementIntentOverlapWarningText1App', appNames[0]);
+      case 2:
+        this.overlappingAppsWarning_ = this.i18n(
+            'appManagementIntentOverlapWarningText2Apps', ...appNames);
+      case 3:
+        this.overlappingAppsWarning_ = this.i18n(
+            'appManagementIntentOverlapWarningText3Apps', ...appNames);
+      case 4:
+        this.overlappingAppsWarning_ = this.i18n(
+            'appManagementIntentOverlapWarningText4Apps',
+            ...appNames.slice(0, 3));
+      default:
+        this.overlappingAppsWarning_ = this.i18n(
+            'appManagementIntentOverlapWarningText5OrMoreApps',
+            ...appNames.slice(0, 3), appNames.length - 3);
+    }
+
+    this.showOverlappingAppsWarning_ = true;
+  },
+
   /* Supported links list dialog functions ************************************/
   /**
    * Stamps and opens the Supported Links dialog.
diff --git a/chrome/browser/ui/app_list/search/chrome_search_result.cc b/chrome/browser/ui/app_list/search/chrome_search_result.cc
index dcde34108..5def656 100644
--- a/chrome/browser/ui/app_list/search/chrome_search_result.cc
+++ b/chrome/browser/ui/app_list/search/chrome_search_result.cc
@@ -134,9 +134,9 @@
     updater->SetSearchResultMetadata(id(), CloneMetadata());
 }
 
-void ChromeSearchResult::SetEquivalentResutlId(
-    const std::string& equivlanet_result_id) {
-  metadata_->equivalent_result_id = equivlanet_result_id;
+void ChromeSearchResult::SetEquivalentResultId(
+    const std::string& equivalent_result_id) {
+  metadata_->equivalent_result_id = equivalent_result_id;
   auto* updater = model_updater();
   if (updater)
     updater->SetSearchResultMetadata(id(), CloneMetadata());
diff --git a/chrome/browser/ui/app_list/search/chrome_search_result.h b/chrome/browser/ui/app_list/search/chrome_search_result.h
index 93b52088..4d73421 100644
--- a/chrome/browser/ui/app_list/search/chrome_search_result.h
+++ b/chrome/browser/ui/app_list/search/chrome_search_result.h
@@ -127,7 +127,7 @@
   void SetIsRecommendation(bool is_recommendation);
   void SetIsInstalling(bool is_installing);
   void SetQueryUrl(const GURL& url);
-  void SetEquivalentResutlId(const std::string& equivlanet_result_id);
+  void SetEquivalentResultId(const std::string& equivalent_result_id);
   void SetIcon(const IconInfo& icon);
   void SetChipIcon(const gfx::ImageSkia& icon);
   void SetBadgeIcon(const ui::ImageModel& badge_icon);
diff --git a/chrome/browser/ui/ash/shelf/shelf_controller_helper.cc b/chrome/browser/ui/ash/shelf/shelf_controller_helper.cc
index 8a44d9c8..b06450c 100644
--- a/chrome/browser/ui/ash/shelf/shelf_controller_helper.cc
+++ b/chrome/browser/ui/ash/shelf/shelf_controller_helper.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
+#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
 #include "components/services/app_service/public/cpp/types_util.h"
@@ -207,7 +208,7 @@
   }
   params.launch_id = id.launch_id;
 
-  proxy->BrowserAppLauncher()->LaunchAppWithParams(std::move(params));
+  ::OpenApplication(profile_, std::move(params));
 }
 
 ArcAppListPrefs* ShelfControllerHelper::GetArcAppListPrefs() const {
diff --git a/chrome/browser/ui/ash/system_tray_client_impl.cc b/chrome/browser/ui/ash/system_tray_client_impl.cc
index f39b4a0c..4383cdd9 100644
--- a/chrome/browser/ui/ash/system_tray_client_impl.cc
+++ b/chrome/browser/ui/ash/system_tray_client_impl.cc
@@ -391,11 +391,6 @@
       "?checkForUpdate=true");
 }
 
-void SystemTrayClientImpl::ShowHelp() {
-  chrome::ShowHelpForProfile(ProfileManager::GetActiveUserProfile(),
-                             chrome::HELP_SOURCE_MENU);
-}
-
 void SystemTrayClientImpl::ShowAccessibilityHelp() {
   chrome::ScopedTabbedBrowserDisplayer displayer(
       ProfileManager::GetActiveUserProfile());
@@ -433,12 +428,6 @@
       chromeos::settings::mojom::kStylusSubpagePath);
 }
 
-void SystemTrayClientImpl::ShowPublicAccountInfo() {
-  chrome::ScopedTabbedBrowserDisplayer displayer(
-      ProfileManager::GetActiveUserProfile());
-  chrome::ShowPolicy(displayer.browser());
-}
-
 void SystemTrayClientImpl::ShowEnterpriseInfo() {
   // At the login screen, lock screen, etc. show enterprise help in a window.
   if (SessionManager::Get()->IsUserSessionBlocked()) {
diff --git a/chrome/browser/ui/ash/system_tray_client_impl.h b/chrome/browser/ui/ash/system_tray_client_impl.h
index bc9c03a..7a3db48 100644
--- a/chrome/browser/ui/ash/system_tray_client_impl.h
+++ b/chrome/browser/ui/ash/system_tray_client_impl.h
@@ -77,13 +77,11 @@
   void ShowTetherNetworkSettings() override;
   void ShowWifiSyncSettings() override;
   void ShowAboutChromeOS() override;
-  void ShowHelp() override;
   void ShowAccessibilityHelp() override;
   void ShowAccessibilitySettings() override;
   void ShowGestureEducationHelp() override;
   void ShowPaletteHelp() override;
   void ShowPaletteSettings() override;
-  void ShowPublicAccountInfo() override;
   void ShowEnterpriseInfo() override;
   void ShowNetworkConfigure(const std::string& network_id) override;
   void ShowNetworkCreate(const std::string& type) override;
diff --git a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
index 5fa5f47..3baa48d 100644
--- a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
@@ -67,6 +67,7 @@
 #include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/fenced_frame_test_util.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/buildflags/buildflags.h"
@@ -882,4 +883,82 @@
                   ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
 
+class PopupBlockerFencedFrameTest : public PopupBlockerBrowserTest {
+ public:
+  PopupBlockerFencedFrameTest() = default;
+  ~PopupBlockerFencedFrameTest() override = default;
+
+  content::RenderFrameHost* primary_main_frame_host() {
+    return browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
+  }
+
+ protected:
+  content::test::FencedFrameTestHelper fenced_frame_helper_;
+};
+
+IN_PROC_BROWSER_TEST_F(PopupBlockerFencedFrameTest,
+                       AllowPopupThroughContentSettingFencedFrame) {
+  HostContentSettingsMap* content_settings =
+      HostContentSettingsMapFactory::GetForProfile(browser()->profile());
+
+  // The content setting of the main frame URL is set to allow popup.
+  const GURL main_frame_url(
+      embedded_test_server()->GetURL("a.com", "/title1.html"));
+  content_settings->SetContentSettingDefaultScope(main_frame_url, GURL(),
+                                                  ContentSettingsType::POPUPS,
+                                                  CONTENT_SETTING_ALLOW);
+
+  // The content setting of the fenced frame URL is set to block popup.
+  const GURL fenced_frame_url(embedded_test_server()->GetURL(
+      "b.com", "/popup_blocker/popup-window-open.html"));
+  content_settings->SetContentSettingDefaultScope(fenced_frame_url, GURL(),
+                                                  ContentSettingsType::POPUPS,
+                                                  CONTENT_SETTING_BLOCK);
+
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
+
+  // Create a fenced frame opening a popup.
+  content::RenderFrameHost* fenced_frame_rfh =
+      fenced_frame_helper_.CreateFencedFrame(primary_main_frame_host(),
+                                             fenced_frame_url);
+  ASSERT_NE(nullptr, fenced_frame_rfh);
+
+  // The popup should be shown even the iframe URL is blocked, since the
+  // top-level URL allows popups.
+  ASSERT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
+  ASSERT_EQ(0, GetBlockedContentsCount());
+}
+
+IN_PROC_BROWSER_TEST_F(PopupBlockerFencedFrameTest,
+                       BlockPopupThroughContentSettingFencedFrame) {
+  HostContentSettingsMap* content_settings =
+      HostContentSettingsMapFactory::GetForProfile(browser()->profile());
+
+  // The content setting of the main frame URL is set to block popup.
+  const GURL main_frame_url(
+      embedded_test_server()->GetURL("a.com", "/title1.html"));
+  content_settings->SetContentSettingDefaultScope(main_frame_url, GURL(),
+                                                  ContentSettingsType::POPUPS,
+                                                  CONTENT_SETTING_BLOCK);
+
+  // The content setting of the fenced frame URL is set to allow popup.
+  const GURL fenced_frame_url(embedded_test_server()->GetURL(
+      "b.com", "/popup_blocker/popup-window-open.html"));
+  content_settings->SetContentSettingDefaultScope(fenced_frame_url, GURL(),
+                                                  ContentSettingsType::POPUPS,
+                                                  CONTENT_SETTING_ALLOW);
+
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
+
+  // Create a fenced frame opening a popup.
+  content::RenderFrameHost* fenced_frame_rfh =
+      fenced_frame_helper_.CreateFencedFrame(primary_main_frame_host(),
+                                             fenced_frame_url);
+  ASSERT_NE(nullptr, fenced_frame_rfh);
+
+  // Popup should be blocked even the iframe URL is in AllowList, since the
+  // top-level URL blocks popups.
+  ASSERT_EQ(1, GetBlockedContentsCount());
+}
+
 }  // namespace
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper_browsertest.cc b/chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc
similarity index 86%
rename from chrome/browser/ui/thumbnails/thumbnail_tab_helper_browsertest.cc
rename to chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc
index c235733e..f49075a 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper_browsertest.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc
@@ -60,9 +60,9 @@
 
 // Test fixture for testing interaction of thumbnail tab helper and browser,
 // specifically testing interaction of tab load and thumbnail capture.
-class ThumbnailTabHelperBrowserTest : public InProcessBrowserTest {
+class ThumbnailTabHelperInteractiveTest : public InProcessBrowserTest {
  public:
-  ThumbnailTabHelperBrowserTest() {
+  ThumbnailTabHelperInteractiveTest() {
     url1_ = ui_test_utils::GetTestUrl(
         base::FilePath().AppendASCII("session_history"),
         base::FilePath().AppendASCII("bot1.html"));
@@ -71,9 +71,10 @@
         base::FilePath().AppendASCII("bot2.html"));
   }
 
-  ThumbnailTabHelperBrowserTest(const ThumbnailTabHelperBrowserTest&) = delete;
-  ThumbnailTabHelperBrowserTest& operator=(
-      const ThumbnailTabHelperBrowserTest&) = delete;
+  ThumbnailTabHelperInteractiveTest(const ThumbnailTabHelperInteractiveTest&) =
+      delete;
+  ThumbnailTabHelperInteractiveTest& operator=(
+      const ThumbnailTabHelperInteractiveTest&) = delete;
 
 #if BUILDFLAG(ENABLE_SESSION_SERVICE)
   void ConfigureTabLoader(TabLoader* tab_loader) {
@@ -156,14 +157,8 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-// Flaky on Mac: https://crbug.com/1288117
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_TabLoadTriggersScreenshot DISABLED_TabLoadTriggersScreenshot
-#else
-#define MAYBE_TabLoadTriggersScreenshot TabLoadTriggersScreenshot
-#endif
-IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperBrowserTest,
-                       MAYBE_TabLoadTriggersScreenshot) {
+IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperInteractiveTest,
+                       TabLoadTriggersScreenshot) {
   ui_test_utils::NavigateToURLWithDisposition(
       browser(), url2_, WindowOpenDisposition::NEW_BACKGROUND_TAB,
       ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB);
@@ -176,17 +171,10 @@
 // with ENABLE_SESSION_SERVICE.
 #if BUILDFLAG(ENABLE_SESSION_SERVICE)
 
-// Flaky on Win: https://crbug.com/1211377
-#if BUILDFLAG(IS_WIN)
-#define MAYBE_CapturesRestoredTabWhenRequested \
-  DISABLED_CapturesRestoredTabWhenRequested
-#else
-#define MAYBE_CapturesRestoredTabWhenRequested CapturesRestoredTabWhenRequested
-#endif
 // On browser restore, some tabs may not be loaded. Requesting a
 // thumbnail for one of these tabs should trigger load and capture.
-IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperBrowserTest,
-                       MAYBE_CapturesRestoredTabWhenRequested) {
+IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperInteractiveTest,
+                       CapturesRestoredTabWhenRequested) {
   ui_test_utils::NavigateToURLWithDisposition(
       browser(), url2_, WindowOpenDisposition::NEW_WINDOW,
       ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
@@ -199,9 +187,9 @@
   CloseBrowserSynchronously(browser2);
 
   // Set up the tab loader to ensure tabs are left unloaded.
-  base::RepeatingCallback<void(TabLoader*)> callback =
-      base::BindRepeating(&ThumbnailTabHelperBrowserTest::ConfigureTabLoader,
-                          base::Unretained(this));
+  base::RepeatingCallback<void(TabLoader*)> callback = base::BindRepeating(
+      &ThumbnailTabHelperInteractiveTest::ConfigureTabLoader,
+      base::Unretained(this));
   TabLoaderTester::SetConstructionCallbackForTesting(&callback);
 
   // Restore recently closed window.
diff --git a/chrome/browser/ui/toolbar/app_menu_model.h b/chrome/browser/ui/toolbar/app_menu_model.h
index 712c8fe..45eee88 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.h
+++ b/chrome/browser/ui/toolbar/app_menu_model.h
@@ -218,7 +218,7 @@
   // Disables/Enables the settings item based on kSystemFeaturesDisableList
   // pref.
   void UpdateSettingsItemState();
-#endif  // BUILDFLAG(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   // Time menu has been open. Used by LogMenuMetrics() to record the time
   // to action when the user selects a menu item.
diff --git a/chrome/browser/ui/views/passwords/password_items_view.cc b/chrome/browser/ui/views/passwords/password_items_view.cc
index 7a48ea1..66a2992 100644
--- a/chrome/browser/ui/views/passwords/password_items_view.cc
+++ b/chrome/browser/ui/views/passwords/password_items_view.cc
@@ -28,6 +28,7 @@
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/color/color_id.h"
 #include "ui/gfx/favicon_size.h"
+#include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/range/range.h"
@@ -41,9 +42,9 @@
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/link.h"
 #include "ui/views/controls/separator.h"
-#include "ui/views/layout/fill_layout.h"
-#include "ui/views/layout/grid_layout.h"
+#include "ui/views/layout/table_layout.h"
 #include "ui/views/style/typography.h"
+#include "ui/views/view_class_properties.h"
 
 namespace {
 
@@ -71,10 +72,8 @@
   return PASSWORD_COLUMN_SET;
 }
 
-void BuildColumnSet(views::GridLayout* layout,
+void BuildColumnSet(views::TableLayout* table_layout,
                     PasswordItemsViewColumnSetType type_id) {
-  DCHECK(!layout->GetColumnSet(type_id));
-  views::ColumnSet* column_set = layout->AddColumnSet(type_id);
   // Passwords are split 60/40 (6:4) as the username is more important
   // than obscured password digits. Otherwise two columns are 50/50 (1:1).
   constexpr float kFirstColumnWeight = 60.0f;
@@ -82,55 +81,44 @@
   const int between_column_padding =
       ChromeLayoutProvider::Get()->GetDistanceMetric(
           views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
-  // Add favicon column
-  if (type_id == PASSWORD_COLUMN_SET ||
-      type_id == MULTI_STORE_PASSWORD_COLUMN_SET) {
-    column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
-                          views::GridLayout::kFixedSize,
-                          views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-    column_set->AddPaddingColumn(views::GridLayout::kFixedSize,
-                                 between_column_padding);
-  }
+  table_layout
+      // favicon column
+      ->AddColumn(views::LayoutAlignment::kStretch,
+                  views::LayoutAlignment::kStretch,
+                  views::TableLayout::kFixedSize,
+                  views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      .AddPaddingColumn(views::TableLayout::kFixedSize, between_column_padding)
+      .AddColumn(views::LayoutAlignment::kStretch,
+                 views::LayoutAlignment::kStretch, kFirstColumnWeight,
+                 views::TableLayout::ColumnSize::kFixed, 0, 0)
+      .AddPaddingColumn(views::TableLayout::kFixedSize, between_column_padding)
+      .AddColumn(views::LayoutAlignment::kStretch,
+                 views::LayoutAlignment::kStretch, kSecondColumnWeight,
+                 views::TableLayout::ColumnSize::kFixed, 0, 0);
 
-  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
-                        kFirstColumnWeight,
-                        views::GridLayout::ColumnSize::kFixed, 0, 0);
-
-  if (type_id == PASSWORD_COLUMN_SET ||
-      type_id == MULTI_STORE_PASSWORD_COLUMN_SET) {
-    column_set->AddPaddingColumn(views::GridLayout::kFixedSize,
-                                 between_column_padding);
-    column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
-                          kSecondColumnWeight,
-                          views::GridLayout::ColumnSize::kFixed, 0, 0);
-  }
   if (type_id == MULTI_STORE_PASSWORD_COLUMN_SET) {
     // All rows show a store indicator or leave the space blank.
-    column_set->AddPaddingColumn(views::GridLayout::kFixedSize,
-                                 between_column_padding);
-    column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
-                          views::GridLayout::kFixedSize,
-                          views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-    // Add a column for the vertical bar.
-    column_set->AddPaddingColumn(views::GridLayout::kFixedSize,
-                                 between_column_padding);
-    column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
-                          views::GridLayout::kFixedSize,
-                          views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
+    table_layout
+        ->AddPaddingColumn(views::TableLayout::kFixedSize,
+                           between_column_padding)
+        .AddColumn(views::LayoutAlignment::kStretch,
+                   views::LayoutAlignment::kStretch,
+                   views::TableLayout::kFixedSize,
+                   views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+        // Add a column for the vertical bar.
+        .AddPaddingColumn(views::TableLayout::kFixedSize,
+                          between_column_padding)
+        .AddColumn(views::LayoutAlignment::kStretch,
+                   views::LayoutAlignment::kCenter,
+                   views::TableLayout::kFixedSize,
+                   views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
   }
   // All rows end with a trailing column for the undo/trash button.
-  column_set->AddPaddingColumn(views::GridLayout::kFixedSize,
-                               between_column_padding);
-  column_set->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
-                        views::GridLayout::kFixedSize,
-                        views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-}
-
-void StartRow(views::GridLayout* layout,
-              PasswordItemsViewColumnSetType type_id) {
-  if (!layout->GetColumnSet(type_id))
-    BuildColumnSet(layout, type_id);
-  layout->StartRow(views::GridLayout::kFixedSize, type_id);
+  table_layout
+      ->AddPaddingColumn(views::TableLayout::kFixedSize, between_column_padding)
+      .AddColumn(views::LayoutAlignment::kEnd, views::LayoutAlignment::kStretch,
+                 views::TableLayout::kFixedSize,
+                 views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
 }
 
 }  // namespace
@@ -145,12 +133,13 @@
   PasswordRow(const PasswordRow&) = delete;
   PasswordRow& operator=(const PasswordRow&) = delete;
 
-  void AddToLayout(views::GridLayout* layout,
+  void AddToLayout(views::TableLayout* table_layout,
                    PasswordItemsViewColumnSetType type_id);
 
  private:
-  void AddUndoRow(views::GridLayout* layout);
-  void AddPasswordRow(views::GridLayout* layout,
+  void AddUndoRow(views::TableLayout* table_layout,
+                  PasswordItemsViewColumnSetType type_id);
+  void AddPasswordRow(views::TableLayout* table_layout,
                       PasswordItemsViewColumnSetType type_id);
 
   void DeleteButtonPressed();
@@ -167,51 +156,56 @@
     : parent_(parent), password_form_(password_form) {}
 
 void PasswordItemsView::PasswordRow::AddToLayout(
-    views::GridLayout* layout,
+    views::TableLayout* table_layout,
     PasswordItemsViewColumnSetType type_id) {
   if (deleted_)
-    AddUndoRow(layout);
+    AddUndoRow(table_layout, type_id);
   else
-    AddPasswordRow(layout, type_id);
+    AddPasswordRow(table_layout, type_id);
 }
 
-void PasswordItemsView::PasswordRow::AddUndoRow(views::GridLayout* layout) {
-  StartRow(layout, UNDO_COLUMN_SET);
-  layout
-      ->AddView(std::make_unique<views::Label>(
-          l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED),
-          views::style::CONTEXT_DIALOG_BODY_TEXT))
-      ->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  auto* undo_button = layout->AddView(std::make_unique<views::MdTextButton>(
-      base::BindRepeating(&PasswordRow::UndoButtonPressed,
-                          base::Unretained(this)),
-      l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_UNDO)));
+void PasswordItemsView::PasswordRow::AddUndoRow(
+    views::TableLayout* table_layout,
+    PasswordItemsViewColumnSetType type_id) {
+  table_layout->AddRows(1, views::TableLayout::kFixedSize);
+  auto* undo_label = parent_->AddChildView(std::make_unique<views::Label>(
+      l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED),
+      views::style::CONTEXT_DIALOG_BODY_TEXT));
+  undo_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  undo_label->SetProperty(
+      views::kTableColAndRowSpanKey,
+      gfx::Size(type_id == MULTI_STORE_PASSWORD_COLUMN_SET ? 9 : 5, 1));
+  auto* undo_button =
+      parent_->AddChildView(std::make_unique<views::MdTextButton>(
+          base::BindRepeating(&PasswordRow::UndoButtonPressed,
+                              base::Unretained(this)),
+          l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_UNDO)));
   undo_button->SetTooltipText(l10n_util::GetStringFUTF16(
       IDS_MANAGE_PASSWORDS_UNDO_TOOLTIP, GetDisplayUsername(*password_form_)));
 }
 
 void PasswordItemsView::PasswordRow::AddPasswordRow(
-    views::GridLayout* layout,
+    views::TableLayout* table_layout,
     PasswordItemsViewColumnSetType type_id) {
-  StartRow(layout, type_id);
-
+  table_layout->AddRows(1, views::TableLayout::kFixedSize);
   if (parent_->favicon_.IsEmpty()) {
     // Use a globe fallback until the actual favicon is loaded.
-    layout->AddView(
+    parent_->AddChildView(
         std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
             kGlobeIcon, ui::kColorIcon, gfx::kFaviconSize)));
   } else {
-    layout->AddView(std::make_unique<views::ImageView>())
+    parent_->AddChildView(std::make_unique<views::ImageView>())
         ->SetImage(parent_->favicon_.AsImageSkia());
   }
 
-  layout->AddView(CreateUsernameLabel(*password_form_));
-  layout->AddView(CreatePasswordLabel(*password_form_));
+  parent_->AddChildView(CreateUsernameLabel(*password_form_));
+  parent_->AddChildView(CreatePasswordLabel(*password_form_));
 
   if (type_id == MULTI_STORE_PASSWORD_COLUMN_SET) {
     if (password_form_->in_store ==
         password_manager::PasswordForm::Store::kAccountStore) {
-      auto* image_view = layout->AddView(std::make_unique<views::ImageView>());
+      auto* image_view =
+          parent_->AddChildView(std::make_unique<views::ImageView>());
       image_view->SetImage(gfx::CreateVectorIcon(
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
           kGoogleGLogoIcon,
@@ -222,10 +216,11 @@
       image_view->SetAccessibleName(l10n_util::GetStringUTF16(
           IDS_MANAGE_PASSWORDS_ACCOUNT_STORE_ICON_DESCRIPTION));
     } else {
-      layout->SkipColumns(1);
+      parent_->AddChildView(std::make_unique<views::View>());
     }
 
-    auto* separator = layout->AddView(std::make_unique<views::Separator>());
+    auto* separator =
+        parent_->AddChildView(std::make_unique<views::Separator>());
     separator->SetFocusBehavior(
         LocationBarBubbleDelegateView::FocusBehavior::NEVER);
     separator->SetPreferredHeight(views::style::GetLineHeight(
@@ -233,13 +228,13 @@
     separator->SetCanProcessEventsWithinSubtree(false);
   }
 
-  auto* delete_button =
-      layout->AddView(views::CreateVectorImageButtonWithNativeTheme(
+  parent_
+      ->AddChildView(views::CreateVectorImageButtonWithNativeTheme(
           base::BindRepeating(&PasswordRow::DeleteButtonPressed,
                               base::Unretained(this)),
-          kTrashCanIcon));
-  delete_button->SetTooltipText(l10n_util::GetStringFUTF16(
-      IDS_MANAGE_PASSWORDS_DELETE, GetDisplayUsername(*password_form_)));
+          kTrashCanIcon))
+      ->SetTooltipText(l10n_util::GetStringFUTF16(
+          IDS_MANAGE_PASSWORDS_DELETE, GetDisplayUsername(*password_form_)));
 }
 
 void PasswordItemsView::PasswordRow::DeleteButtonPressed() {
@@ -274,21 +269,22 @@
           base::Unretained(this)),
       l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_MANAGE_PASSWORDS_BUTTON)));
 
-  if (controller_.local_credentials().empty()) {
+  auto& local_credentials = controller_.local_credentials();
+
+  if (local_credentials.empty()) {
     // A LayoutManager is required for GetHeightForWidth() even without
     // content.
-    SetLayoutManager(std::make_unique<views::FillLayout>());
+    SetUseDefaultFillLayout(true);
   } else {
     // The request is cancelled when the |controller_| is destructed.
     // |controller_| has the same life time as |this| and hence it's safe to use
     // base::Unretained(this).
     controller_.RequestFavicon(base::BindOnce(
         &PasswordItemsView::OnFaviconReady, base::Unretained(this)));
-    for (auto& password_form : controller_.local_credentials()) {
+    for (auto& password_form : local_credentials) {
       password_rows_.push_back(
           std::make_unique<PasswordRow>(this, &password_form));
     }
-
     RecreateLayout();
   }
 }
@@ -311,21 +307,21 @@
 
   RemoveAllChildViews();
 
-  views::GridLayout* grid_layout =
-      SetLayoutManager(std::make_unique<views::GridLayout>());
+  auto* table_layout = SetLayoutManager(std::make_unique<views::TableLayout>());
+  BuildColumnSet(table_layout, InferColumnSetTypeFromCredentials(
+                                   controller_.local_credentials()));
 
   const int vertical_padding = ChromeLayoutProvider::Get()->GetDistanceMetric(
       DISTANCE_CONTROL_LIST_VERTICAL);
-  bool first_row = true;
-  PasswordItemsViewColumnSetType row_column_set_type =
-      InferColumnSetTypeFromCredentials(controller_.local_credentials());
+  bool first = true;
   for (auto& row : password_rows_) {
-    if (!first_row)
-      grid_layout->AddPaddingRow(views::GridLayout::kFixedSize,
-                                 vertical_padding);
-
-    row->AddToLayout(grid_layout, row_column_set_type);
-    first_row = false;
+    if (!first) {
+      table_layout->AddPaddingRow(views::TableLayout::kFixedSize,
+                                  vertical_padding);
+    }
+    row->AddToLayout(table_layout, InferColumnSetTypeFromCredentials(
+                                       controller_.local_credentials()));
+    first = false;
   }
 
   PreferredSizeChanged();
diff --git a/chrome/browser/ui/web_applications/OWNERS b/chrome/browser/ui/web_applications/OWNERS
index bb4928a5..99c2cb76 100644
--- a/chrome/browser/ui/web_applications/OWNERS
+++ b/chrome/browser/ui/web_applications/OWNERS
@@ -1,3 +1,3 @@
 file://chrome/browser/web_applications/OWNERS
 
-per-file system_web_app_ui_utils*=file://ash/webui/system_apps/PLATFORM_OWNERS
+per-file system_web_app_ui_utils*=file://ash/webui/PLATFORM_OWNERS
diff --git a/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.cc b/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.cc
index 12ea66e2..4d114c29 100644
--- a/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.cc
+++ b/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.cc
@@ -6,13 +6,14 @@
 
 #include <memory>
 
-#include "ash/quick_pair/repository/fast_pair/fast_pair_image_decoder_impl.h"
+#include "ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h"
 #include "ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/i18n/time_formatting.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
+#include "components/image_fetcher/core/image_fetcher.h"
 
 namespace {
 // Keys in the JSON representation of a log message
@@ -3778,8 +3779,8 @@
 QuickPairHandler::QuickPairHandler()
     : fast_pair_notification_controller_(
           std::make_unique<ash::quick_pair::FastPairNotificationController>()),
-      image_decoder_(
-          std::make_unique<ash::quick_pair::FastPairImageDecoderImpl>()) {}
+      image_decoder_(std::make_unique<ash::quick_pair::FastPairImageDecoder>(
+          std::unique_ptr<image_fetcher::ImageFetcher>())) {}
 
 QuickPairHandler::~QuickPairHandler() = default;
 
@@ -3839,7 +3840,6 @@
   base::HexStringToBytes(kImageBytes, &bytes);
   image_decoder_->DecodeImage(
       std::move(bytes),
-      /*resize_to_notification_size=*/true,
       base::BindOnce(&QuickPairHandler::OnImageDecodedFastPairError,
                      weak_ptr_factory_.GetWeakPtr()));
 }
@@ -3854,7 +3854,6 @@
   base::HexStringToBytes(kImageBytes, &bytes);
   image_decoder_->DecodeImage(
       std::move(bytes),
-      /*resize_to_notification_size=*/true,
       base::BindOnce(&QuickPairHandler::OnImageDecodedFastPairDiscovery,
                      weak_ptr_factory_.GetWeakPtr()));
 }
@@ -3870,7 +3869,6 @@
   base::HexStringToBytes(kImageBytes, &bytes);
   image_decoder_->DecodeImage(
       std::move(bytes),
-      /*resize_to_notification_size=*/true,
       base::BindOnce(&QuickPairHandler::OnImageDecodedFastPairPairing,
                      weak_ptr_factory_.GetWeakPtr()));
 }
@@ -3886,7 +3884,6 @@
   base::HexStringToBytes(kImageBytes, &bytes);
   image_decoder_->DecodeImage(
       std::move(bytes),
-      /*resize_to_notification_size=*/true,
       base::BindOnce(
           &QuickPairHandler::OnImageDecodedFastPairAssociateAccountKey,
           weak_ptr_factory_.GetWeakPtr()));
diff --git a/chrome/browser/ui/webui/settings/chromeos/apps_section.cc b/chrome/browser/ui/webui/settings/chromeos/apps_section.cc
index fa09b48..21907f1 100644
--- a/chrome/browser/ui/webui/settings/chromeos/apps_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/apps_section.cc
@@ -188,6 +188,16 @@
        IDS_APP_MANAGEMENT_INTENT_OVERLAP_DIALOG_TEXT_5_OR_MORE_APPS},
       {"appManagementIntentOverlapDialogTitle",
        IDS_APP_MANAGEMENT_INTENT_OVERLAP_DIALOG_TITLE},
+      {"appManagementIntentOverlapWarningText1App",
+       IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_1_APP},
+      {"appManagementIntentOverlapWarningText2Apps",
+       IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_2_APPS},
+      {"appManagementIntentOverlapWarningText3Apps",
+       IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_3_APPS},
+      {"appManagementIntentOverlapWarningText4Apps",
+       IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_4_APPS},
+      {"appManagementIntentOverlapWarningText5OrMoreApps",
+       IDS_APP_MANAGEMENT_INTENT_OVERLAP_WARNING_TEXT_5_OR_MORE_APPS},
       {"appManagementIntentSettingsDialogTitle",
        IDS_APP_MANAGEMENT_INTENT_SETTINGS_DIALOG_TITLE},
       {"appManagementIntentSettingsTitle",
diff --git a/chrome/browser/web_applications/system_web_apps/OWNERS b/chrome/browser/web_applications/system_web_apps/OWNERS
index 47d56c85..44a1559 100644
--- a/chrome/browser/web_applications/system_web_apps/OWNERS
+++ b/chrome/browser/web_applications/system_web_apps/OWNERS
@@ -1 +1 @@
-file://ash/webui/system_apps/PLATFORM_OWNERS
+file://ash/webui/PLATFORM_OWNERS
diff --git a/chrome/browser/web_applications/system_web_apps/system_web_app_types.h b/chrome/browser/web_applications/system_web_apps/system_web_app_types.h
index 228640a..ea67e46 100644
--- a/chrome/browser/web_applications/system_web_apps/system_web_app_types.h
+++ b/chrome/browser/web_applications/system_web_apps/system_web_app_types.h
@@ -135,7 +135,7 @@
   // 6. Update kMaxValue.
   //
   // 7. Have one of System Web App Platform owners review the CL.
-  //    See: //ash/webui/system_apps/PLATFORM_OWNERS
+  //    See: //ash/webui/PLATFORM_OWNERS
   kMaxValue = OS_FLAGS,
 };
 
diff --git a/chrome/browser/web_applications/test/web_app_test_observers.cc b/chrome/browser/web_applications/test/web_app_test_observers.cc
index 15cd1c0..54c9001 100644
--- a/chrome/browser/web_applications/test/web_app_test_observers.cc
+++ b/chrome/browser/web_applications/test/web_app_test_observers.cc
@@ -147,7 +147,8 @@
   if (!optional_app_ids_.empty())
     return;
   last_app_id_ = app_id;
-  wait_loop_.Quit();
+  base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                   wait_loop_.QuitClosure());
   is_listening_ = false;
 }
 
diff --git a/chrome/browser/web_applications/web_app_utils.cc b/chrome/browser/web_applications/web_app_utils.cc
index 82584856..5df3685 100644
--- a/chrome/browser/web_applications/web_app_utils.cc
+++ b/chrome/browser/web_applications/web_app_utils.cc
@@ -20,6 +20,7 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/site_engagement/content/site_engagement_service.h"
+#include "skia/ext/skia_utils_base.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
@@ -115,6 +116,42 @@
   return is_web_app_metrics_enabled ? original_profile : nullptr;
 }
 
+content::mojom::AlternativeErrorPageOverrideInfoPtr GetAppManifestInfo(
+    const GURL& url,
+    content::BrowserContext* browser_context) {
+  Profile* profile = Profile::FromBrowserContext(browser_context);
+  web_app::WebAppProvider* web_app_provider =
+      web_app::WebAppProvider::GetForWebApps(profile);
+  if (web_app_provider == nullptr) {
+    return nullptr;
+  }
+
+  web_app::WebAppRegistrar& web_app_registrar = web_app_provider->registrar();
+  const absl::optional<web_app::AppId> app_id =
+      web_app_registrar.FindAppWithUrlInScope(url);
+  if (!app_id.has_value()) {
+    return nullptr;
+  }
+
+  auto alternative_error_page_info =
+      content::mojom::AlternativeErrorPageOverrideInfo::New();
+  // TODO(crbug.com/1285128): Ensure sufficient contrast.
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetKey(content::mojom::kThemeColor,
+              base::Value(skia::SkColorToHexString(
+                  web_app_registrar.GetAppThemeColor(*app_id).value_or(
+                      SK_ColorBLACK))));
+  dict.SetKey(content::mojom::kBackgroundColor,
+              base::Value(skia::SkColorToHexString(
+                  web_app_registrar.GetAppBackgroundColor(*app_id).value_or(
+                      SK_ColorWHITE))));
+  dict.SetKey(content::mojom::kAppShortName,
+              base::Value(web_app_registrar.GetAppShortName(*app_id)));
+  alternative_error_page_info->alternative_error_page_params = std::move(dict);
+
+  return alternative_error_page_info;
+}
+
 base::FilePath GetWebAppsRootDirectory(Profile* profile) {
   return profile->GetPath().Append(chrome::kWebAppDirname);
 }
diff --git a/chrome/browser/web_applications/web_app_utils.h b/chrome/browser/web_applications/web_app_utils.h
index 8e6cf5a5..14bed39 100644
--- a/chrome/browser/web_applications/web_app_utils.h
+++ b/chrome/browser/web_applications/web_app_utils.h
@@ -13,6 +13,7 @@
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "components/services/app_service/public/cpp/file_handler.h"
+#include "content/public/common/alternative_error_page_override_info.mojom.h"
 
 class GURL;
 class Profile;
@@ -47,6 +48,12 @@
 content::BrowserContext* GetBrowserContextForWebAppMetrics(
     content::BrowserContext* context);
 
+// Gets information from web app's manifest, including theme color, background
+// color and app short name, and returns this inside a struct.
+content::mojom::AlternativeErrorPageOverrideInfoPtr GetAppManifestInfo(
+    const GURL& url,
+    content::BrowserContext* browser_context);
+
 // Returns a root directory for all Web Apps themed data.
 //
 // All the related directory getters always require |web_apps_root_directory| as
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 1fd031a..a0d2d5b 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1642712753-379dc4d5f1ef1edcb0e1560253162ef22de45db0.profdata
+chrome-mac-main-1642723193-ae85aa458f8f1b10399785a3cd5342e3bbd948a2.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 7b9de82..34e9f8882 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1642673694-7347631494bb47853b7050913b3d09eaaa5cfff4.profdata
+chrome-win32-main-1642733783-685c87c742a2e62e6dbad0dbe644477a92ee3868.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 2fd69530..3e5b331 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1642701562-74736e1a1467504debb0f64c9745c095973f160e.profdata
+chrome-win64-main-1642733783-5e3222dbdf559b48189908a44ff2fe34cd9bf3d9.profdata
diff --git a/chrome/common/extensions/api/file_manager_private.idl b/chrome/common/extensions/api/file_manager_private.idl
index d62359e..c998c642 100644
--- a/chrome/common/extensions/api/file_manager_private.idl
+++ b/chrome/common/extensions/api/file_manager_private.idl
@@ -1001,6 +1001,9 @@
 // |result| Hash containing the string assets.
 callback GetStringsCallback = void(object result);
 
+// |color| String containing the color of the title bar.
+callback GetFrameColorCallback = void(DOMString color);
+
 // |success| True when file watch is successfully added.
 callback AddFileWatchCallback = void(optional boolean success);
 
@@ -1602,6 +1605,9 @@
   // |callback| The result callback.
   static void getHoldingSpaceState(HoldingSpaceStateCallback callback);
 
+  // Returns color via `callback` for Files app foreground window frame.
+  static void getFrameColor(GetFrameColorCallback callback);
+
   // Returns true via `callback` if tablet mode is enabled, false otherwise.
   static void isTabletModeEnabled(BooleanCallback callback);
 
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 9e2e8d18..a2491ea 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -1291,13 +1291,16 @@
     content::RenderFrame* render_frame,
     const blink::WebURLError& web_error,
     const std::string& http_method,
+    content::mojom::AlternativeErrorPageOverrideInfoPtr
+        alternative_error_page_info,
     std::string* error_html) {
   NetErrorHelper::Get(render_frame)
       ->PrepareErrorPage(
           error_page::Error::NetError(
               web_error.url(), web_error.reason(), web_error.extended_reason(),
               web_error.resolve_error_info(), web_error.has_copy_in_cache()),
-          http_method == "POST", error_html);
+          http_method == "POST", std::move(alternative_error_page_info),
+          error_html);
 
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
   SupervisedUserErrorPageControllerDelegateImpl::Get(render_frame)
@@ -1310,10 +1313,13 @@
     const blink::WebURLError& error,
     const std::string& http_method,
     int http_status,
+    content::mojom::AlternativeErrorPageOverrideInfoPtr
+        alternative_error_page_info,
     std::string* error_html) {
   NetErrorHelper::Get(render_frame)
       ->PrepareErrorPage(error_page::Error::HttpError(error.url(), http_status),
-                         http_method == "POST", error_html);
+                         http_method == "POST",
+                         std::move(alternative_error_page_info), error_html);
 }
 
 void ChromeContentRendererClient::PostIOThreadCreated(
diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h
index bdc27a6..460fd956 100644
--- a/chrome/renderer/chrome_content_renderer_client.h
+++ b/chrome/renderer/chrome_content_renderer_client.h
@@ -113,12 +113,17 @@
   void PrepareErrorPage(content::RenderFrame* render_frame,
                         const blink::WebURLError& error,
                         const std::string& http_method,
+                        content::mojom::AlternativeErrorPageOverrideInfoPtr
+                            alternative_error_page_info,
                         std::string* error_html) override;
-  void PrepareErrorPageForHttpStatusError(content::RenderFrame* render_frame,
-                                          const blink::WebURLError& error,
-                                          const std::string& http_method,
-                                          int http_status,
-                                          std::string* error_html) override;
+  void PrepareErrorPageForHttpStatusError(
+      content::RenderFrame* render_frame,
+      const blink::WebURLError& error,
+      const std::string& http_method,
+      int http_status,
+      content::mojom::AlternativeErrorPageOverrideInfoPtr
+          alternative_error_page_info,
+      std::string* error_html) override;
   bool DeferMediaLoad(content::RenderFrame* render_frame,
                       bool has_played_media_before,
                       base::OnceClosure closure) override;
diff --git a/chrome/renderer/extensions/extension_localization_peer.cc b/chrome/renderer/extensions/extension_localization_peer.cc
index d8ee112..35e146d 100644
--- a/chrome/renderer/extensions/extension_localization_peer.cc
+++ b/chrome/renderer/extensions/extension_localization_peer.cc
@@ -14,6 +14,7 @@
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/message_bundle.h"
+#include "extensions/renderer/renderer_i18n_util.h"
 #include "ipc/ipc_sender.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_response_headers.h"
@@ -240,21 +241,10 @@
     return;
 
   std::string extension_id = request_url_.host();
-  extensions::L10nMessagesMap* l10n_messages =
-      extensions::GetL10nMessagesMap(extension_id);
-  if (!l10n_messages) {
-    extensions::L10nMessagesMap messages;
-    message_sender_->Send(new ExtensionHostMsg_GetMessageBundle(
-        extension_id, &messages));
-
-    // Save messages we got, so we don't have to ask again.
-    // Messages map is never empty, it contains at least @@extension_id value.
-    extensions::ExtensionToL10nMessagesMap& l10n_messages_map =
-        *extensions::GetExtensionToL10nMessagesMap();
-    l10n_messages_map[extension_id] = messages;
-
-    l10n_messages = extensions::GetL10nMessagesMap(extension_id);
-  }
+  const extensions::L10nMessagesMap* l10n_messages =
+      extensions::i18n_util::GetRendererMessagesMap(extension_id,
+                                                    message_sender_);
+  DCHECK(l10n_messages);
 
   std::string error;
   if (extensions::MessageBundle::ReplaceMessagesWithExternalDictionary(
diff --git a/chrome/renderer/net/net_error_helper.cc b/chrome/renderer/net/net_error_helper.cc
index fd55969..3a8438d 100644
--- a/chrome/renderer/net/net_error_helper.cc
+++ b/chrome/renderer/net/net_error_helper.cc
@@ -177,11 +177,14 @@
   delete this;
 }
 
-void NetErrorHelper::PrepareErrorPage(const error_page::Error& error,
-                                      bool is_failed_post,
-                                      std::string* error_html) {
+void NetErrorHelper::PrepareErrorPage(
+    const error_page::Error& error,
+    bool is_failed_post,
+    content::mojom::AlternativeErrorPageOverrideInfoPtr
+        alternative_error_page_info,
+    std::string* error_html) {
   core_->PrepareErrorPage(GetFrameType(render_frame()), error, is_failed_post,
-                          error_html);
+                          std::move(alternative_error_page_info), error_html);
 }
 
 std::unique_ptr<network::ResourceRequest> NetErrorHelper::CreatePostRequest(
@@ -242,6 +245,8 @@
     const error_page::Error& error,
     bool is_failed_post,
     bool can_show_network_diagnostics_dialog,
+    content::mojom::AlternativeErrorPageOverrideInfoPtr
+        alternative_error_page_info,
     std::string* error_html) const {
   error_html->clear();
 
diff --git a/chrome/renderer/net/net_error_helper.h b/chrome/renderer/net/net_error_helper.h
index c5a45e24..7444765 100644
--- a/chrome/renderer/net/net_error_helper.h
+++ b/chrome/renderer/net/net_error_helper.h
@@ -20,6 +20,7 @@
 #include "components/error_page/common/net_error_info.h"
 #include "components/security_interstitials/content/renderer/security_interstitial_page_controller.h"
 #include "components/security_interstitials/core/controller_client.h"
+#include "content/public/common/alternative_error_page_override_info.mojom-forward.h"
 #include "content/public/renderer/render_frame_observer.h"
 #include "content/public/renderer/render_frame_observer_tracker.h"
 #include "mojo/public/cpp/bindings/associated_receiver_set.h"
@@ -72,6 +73,8 @@
   // loaded immediately.
   void PrepareErrorPage(const error_page::Error& error,
                         bool is_failed_post,
+                        content::mojom::AlternativeErrorPageOverrideInfoPtr
+                            alternative_error_page_info,
                         std::string* error_html);
 
  private:
@@ -88,6 +91,8 @@
       const error_page::Error& error,
       bool is_failed_post,
       bool can_use_local_diagnostics_service,
+      content::mojom::AlternativeErrorPageOverrideInfoPtr
+          alternative_error_page_info,
       std::string* html) const override;
 
   void EnablePageHelperFunctions() override;
diff --git a/chrome/renderer/net/net_error_helper_core.cc b/chrome/renderer/net/net_error_helper_core.cc
index 714430e..bf44ba92 100644
--- a/chrome/renderer/net/net_error_helper_core.cc
+++ b/chrome/renderer/net/net_error_helper_core.cc
@@ -154,18 +154,24 @@
   }
 }
 
-void NetErrorHelperCore::PrepareErrorPage(FrameType frame_type,
-                                          const error_page::Error& error,
-                                          bool is_failed_post,
-                                          std::string* error_html) {
+void NetErrorHelperCore::PrepareErrorPage(
+    FrameType frame_type,
+    const error_page::Error& error,
+    bool is_failed_post,
+    content::mojom::AlternativeErrorPageOverrideInfoPtr
+        alternative_error_page_info,
+    std::string* error_html) {
   if (frame_type == MAIN_FRAME) {
     pending_error_page_info_ =
         std::make_unique<ErrorPageInfo>(error, is_failed_post);
-    PrepareErrorPageForMainFrame(pending_error_page_info_.get(), error_html);
+    PrepareErrorPageForMainFrame(pending_error_page_info_.get(),
+                                 std::move(alternative_error_page_info),
+                                 error_html);
   } else if (error_html) {
     delegate_->GenerateLocalizedErrorPage(
         error, is_failed_post,
-        false /* No diagnostics dialogs allowed for subframes. */, error_html);
+        false /* No diagnostics dialogs allowed for subframes. */,
+        std::move(alternative_error_page_info), error_html);
   }
 }
 
@@ -199,6 +205,8 @@
 
 void NetErrorHelperCore::PrepareErrorPageForMainFrame(
     ErrorPageInfo* pending_error_page_info,
+    content::mojom::AlternativeErrorPageOverrideInfoPtr
+        alternative_error_page_info,
     std::string* error_html) {
   std::string error_param;
   error_page::Error error = pending_error_page_info->error;
@@ -216,7 +224,8 @@
   if (error_html) {
     pending_error_page_info->page_state = delegate_->GenerateLocalizedErrorPage(
         error, pending_error_page_info->was_failed_post,
-        can_show_network_diagnostics_dialog_, error_html);
+        can_show_network_diagnostics_dialog_,
+        std::move(alternative_error_page_info), error_html);
   }
 }
 
diff --git a/chrome/renderer/net/net_error_helper_core.h b/chrome/renderer/net/net_error_helper_core.h
index e7938ab5e..d2fd54f 100644
--- a/chrome/renderer/net/net_error_helper_core.h
+++ b/chrome/renderer/net/net_error_helper_core.h
@@ -14,6 +14,7 @@
 #include "components/error_page/common/error.h"
 #include "components/error_page/common/localized_error.h"
 #include "components/error_page/common/net_error_info.h"
+#include "content/public/common/alternative_error_page_override_info.mojom.h"
 #include "net/base/net_errors.h"
 #include "url/gurl.h"
 
@@ -60,6 +61,8 @@
         const error_page::Error& error,
         bool is_failed_post,
         bool can_show_network_diagnostics_dialog,
+        content::mojom::AlternativeErrorPageOverrideInfoPtr
+            alternative_error_page_info,
         std::string* html) const = 0;
 
     // Create extra Javascript bindings in the error page. Will only be invoked
@@ -126,6 +129,8 @@
   void PrepareErrorPage(FrameType frame_type,
                         const error_page::Error& error,
                         bool is_failed_post,
+                        content::mojom::AlternativeErrorPageOverrideInfoPtr
+                            alternative_error_page_info,
                         std::string* error_html);
 
   // These methods handle tracking the actual state of the page.
@@ -178,8 +183,11 @@
   // page HTML, and sets error_html to it. Depending on
   // |pending_error_page_info|, may show a DNS probe error page.  May modify
   // |pending_error_page_info|.
-  void PrepareErrorPageForMainFrame(ErrorPageInfo* pending_error_page_info,
-                                    std::string* error_html);
+  void PrepareErrorPageForMainFrame(
+      ErrorPageInfo* pending_error_page_info,
+      content::mojom::AlternativeErrorPageOverrideInfoPtr
+          alternative_error_page_info,
+      std::string* error_html);
 
   // Updates the currently displayed error page with a new error based on the
   // most recently received DNS probe result.  The page must have finished
diff --git a/chrome/renderer/net/net_error_helper_core_unittest.cc b/chrome/renderer/net/net_error_helper_core_unittest.cc
index a680492..4bfaf79 100644
--- a/chrome/renderer/net/net_error_helper_core_unittest.cc
+++ b/chrome/renderer/net/net_error_helper_core_unittest.cc
@@ -161,7 +161,8 @@
     std::string html;
     core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                              NetErrorForURL(error, url),
-                             false /* is_failed_post */, &html);
+                             /*is_failed_post=*/false,
+                             /*alternative_error_page_info=*/nullptr, &html);
     EXPECT_FALSE(html.empty());
     EXPECT_EQ(NetErrorStringForURL(error, url), html);
 
@@ -197,6 +198,8 @@
       const error_page::Error& error,
       bool is_failed_post,
       bool can_show_network_diagnostics_dialog,
+      content::mojom::AlternativeErrorPageOverrideInfoPtr
+          alternative_error_page_info,
       std::string* html) const override {
     last_can_show_network_diagnostics_dialog_ =
         can_show_network_diagnostics_dialog;
@@ -307,9 +310,9 @@
 TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsError) {
   // An error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_CONNECTION_RESET),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_CONNECTION_RESET),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page.
   EXPECT_FALSE(html.empty());
   EXPECT_EQ(NetErrorString(net::ERR_CONNECTION_RESET), html);
@@ -328,9 +331,9 @@
   // Loading fails, and an error page is requested.
   std::string html;
   core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_CONNECTION_RESET),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_CONNECTION_RESET),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
 
   // Should have returned a local error page.
@@ -355,7 +358,8 @@
   // indicating a custom error page. Calls below should not crash.
   core()->PrepareErrorPage(
       NetErrorHelperCore::SUB_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
-      false /* is_failed_post */, nullptr /* error_html */);
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr,
+      /*error_html=*/nullptr);
   core()->OnCommitLoad(NetErrorHelperCore::SUB_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::SUB_FRAME);
   EXPECT_EQ(0, update_count());
@@ -364,9 +368,9 @@
 TEST_F(NetErrorHelperCoreTest, SubFrameDnsError) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::SUB_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::SUB_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page.
   EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), html);
 
@@ -382,9 +386,9 @@
   // Loading fails, and an error page is requested.
   std::string html;
   core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
-  core()->PrepareErrorPage(NetErrorHelperCore::SUB_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::SUB_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
 
   // Should have returned a local error page.
@@ -412,9 +416,9 @@
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbe) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -442,9 +446,9 @@
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeNotRun) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -469,9 +473,9 @@
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeInconclusive) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -501,9 +505,9 @@
 
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -533,9 +537,9 @@
 
   // Perform a second error page load, and confirm that the previous load
   // doesn't affect the result.
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
@@ -554,9 +558,9 @@
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeBadConfig) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -586,9 +590,9 @@
 TEST_F(NetErrorHelperCoreTest, FinishedAfterStartProbe) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -622,9 +626,9 @@
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbePost) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           true /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/true, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ErrorToString(ProbeError(error_page::DNS_PROBE_POSSIBLE), true),
             html);
@@ -652,9 +656,9 @@
 TEST_F(NetErrorHelperCoreTest, ProbeFinishesEarly) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -685,9 +689,9 @@
 TEST_F(NetErrorHelperCoreTest, TwoErrorsWithProbes) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -705,9 +709,9 @@
   // The process starts again.
 
   // Loading fails, and an error page is requested.
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -733,9 +737,9 @@
 TEST_F(NetErrorHelperCoreTest, TwoErrorsWithProbesAfterSecondStarts) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -746,9 +750,9 @@
   // The process starts again.
 
   // Loading fails, and an error page is requested.
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -776,9 +780,9 @@
 TEST_F(NetErrorHelperCoreTest, ErrorPageLoadInterrupted) {
   // Loading fails, and an error page is requested.
   std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
@@ -788,9 +792,9 @@
   EXPECT_EQ(0, update_count());
 
   // A new navigation fails while the error page is loading.
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_NAME_NOT_RESOLVED),
-                           false /* is_failed_post */, &html);
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::MAIN_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      /*is_failed_post=*/false, /*alternative_error_page_info=*/nullptr, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 9983a0f7..45da9d98 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1479,6 +1479,7 @@
       "../browser/accessibility/interstitial_accessibility_browsertest.cc",
       "../browser/accessibility/live_caption_controller_browsertest.cc",
       "../browser/accessibility/live_caption_speech_recognition_host_browsertest.cc",
+      "../browser/alternative_error_page_override_info_browsertest.cc",
       "../browser/apps/guest_view/app_view_browsertest.cc",
       "../browser/apps/guest_view/web_view_browsertest.cc",
       "../browser/apps/platform_apps/app_browsertest.cc",
@@ -1759,6 +1760,7 @@
       "../browser/password_manager/password_manager_browsertest.cc",
       "../browser/pdf/pdf_extension_test.cc",
       "../browser/pdf/pdf_find_request_manager_browsertest.cc",
+      "../browser/performance_hints/performance_hints_observer_browsertest.cc",
       "../browser/performance_manager/background_tab_loading_policy_browsertest.cc",
       "../browser/performance_manager/frame_node_impl_browsertest.cc",
       "../browser/performance_manager/mechanisms/page_discarder_browsertest.cc",
@@ -1774,7 +1776,6 @@
       "../browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc",
       "../browser/plugins/pdf_iframe_navigation_throttle_browsertest.cc",
       "../browser/plugins/plugin_response_interceptor_url_loader_throttle_browsertest.cc",
-      "../browser/policy/cloud/device_management_service_browsertest.cc",
       "../browser/policy/policy_initialization_browsertest.cc",
       "../browser/policy/policy_network_browsertest.cc",
       "../browser/policy/policy_prefs_browsertest.cc",
@@ -2005,7 +2006,6 @@
       "../browser/ui/test/test_infobar.cc",
       "../browser/ui/test/test_infobar.h",
       "../browser/ui/thumbnails/thumbnail_readiness_tracker_browsertest.cc",
-      "../browser/ui/thumbnails/thumbnail_tab_helper_browsertest.cc",
       "../browser/ui/toolbar/toolbar_actions_model_browsertest.cc",
       "../browser/ui/update_chrome_dialog_browsertest.cc",
       "../browser/ui/views/apps/app_info_dialog/app_info_dialog_views_browsertest.cc",
@@ -8376,6 +8376,7 @@
       "../browser/ui/test/test_browser_dialog.h",
       "../browser/ui/test/test_browser_ui.cc",
       "../browser/ui/test/test_browser_ui.h",
+      "../browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc",
       "../browser/ui/translate/translate_bubble_test_utils.h",
       "../browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc",
       "../browser/ui/views/content_test_utils.cc",
diff --git a/chrome/test/data/autofill/captured_sites/testcases.json b/chrome/test/data/autofill/captured_sites/testcases.json
index 3ee5cf5..5813665 100644
--- a/chrome/test/data/autofill/captured_sites/testcases.json
+++ b/chrome/test/data/autofill/captured_sites/testcases.json
@@ -54,7 +54,7 @@
     { "site_name": "creditonebank" },
     { "site_name": "cricketwireless" },
     { "site_name": "crutch_field" },
-    { "site_name": "customink", "bug_number":1286945 },
+    { "site_name": "customink", "bug_number":1287986 },
     { "site_name": "cvs" },
     { "site_name": "davidsbridal" },
     { "site_name": "dell", "bug_number":1172945 },
@@ -120,7 +120,7 @@
     { "site_name": "mlbshop" },
     { "site_name": "modcloth" },
     { "site_name": "moma", "bug_number":1285733 },
-    { "site_name": "monoprice" },
+    { "site_name": "monoprice", "bug_number":1288032 },
     { "site_name": "movietickets" },
     { "site_name": "musicians_friend" },
     { "site_name": "nba" },
@@ -192,7 +192,7 @@
     { "site_name": "torrid" },
     { "site_name": "tractorsupply" },
     { "site_name": "tradepub" },
-    { "site_name": "tradesy" },
+    { "site_name": "tradesy", "bug_number":1288033 },
     { "site_name": "uhaul" },
     { "site_name": "vanillagift" },
     { "site_name": "vans" },
diff --git a/chrome/test/data/pdf/fullscreen_test.js b/chrome/test/data/pdf/fullscreen_test.js
index 9009122..7923269 100644
--- a/chrome/test/data/pdf/fullscreen_test.js
+++ b/chrome/test/data/pdf/fullscreen_test.js
@@ -59,13 +59,17 @@
     await ensureFullscreen();
     chrome.test.assertEq(0, viewer.viewport.getMostVisiblePage());
 
+    const content =
+        /** @type {!HTMLElement} */ (
+            viewer.shadowRoot.querySelector('#content'));
+
     // Simulate scrolling towards the bottom.
-    scroller.dispatchEvent(
+    content.dispatchEvent(
         createWheelEvent(40, {clientX: 0, clientY: 0}, false));
     chrome.test.assertEq(1, viewer.viewport.getMostVisiblePage());
 
     // Simulate scrolling towards the top.
-    scroller.dispatchEvent(
+    content.dispatchEvent(
         createWheelEvent(-40, {clientX: 0, clientY: 0}, false));
     chrome.test.assertEq(0, viewer.viewport.getMostVisiblePage());
 
diff --git a/chrome/test/data/policy/policy_device_management_service_browsertest.json b/chrome/test/data/policy/policy_device_management_service_browsertest.json
deleted file mode 100644
index 971d7c4e..0000000
--- a/chrome/test/data/policy/policy_device_management_service_browsertest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "google/chromeos/user": {
-    "mandatory": {
-      "HomepageLocation" : "http://www.chromium.org"
-    },
-    "recommended": {
-    }
-  },
-  "managed_users": [
-    "*"
-  ],
-  "robot_api_auth_code": "fake_auth_code"
-}
diff --git a/chrome/test/data/popup_blocker/popup-window-open.html.mock-http-headers b/chrome/test/data/popup_blocker/popup-window-open.html.mock-http-headers
new file mode 100644
index 0000000..263e89c4
--- /dev/null
+++ b/chrome/test/data/popup_blocker/popup-window-open.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/webui/chromeos/emoji_picker/emoji_picker_extension_test.js b/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_extension_test.js
index d545009..423ec9a8 100644
--- a/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_extension_test.js
+++ b/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_extension_test.js
@@ -3,11 +3,11 @@
 // found in the LICENSE file.
 
 import {EmojiPicker} from 'chrome://emoji-picker/emoji_picker.js';
-import {V2_CONTENT_LOADED} from 'chrome://emoji-picker/events.js';
+import {EMOJI_DATA_LOADED} from 'chrome://emoji-picker/events.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertFalse, assertTrue} from '../../chai_assert.js';
-import {deepQuerySelector, isGroupButtonActive, timeout, waitForCondition} from './emoji_picker_test_util.js';
+import {deepQuerySelector, waitForCondition} from './emoji_picker_test_util.js';
 
 const ACTIVE_CATEGORY_BUTTON = 'category-button-active';
 
@@ -25,17 +25,15 @@
   setup(() => {
     // Reset DOM state.
     document.body.innerHTML = '';
-    window.localStorage.clear();
 
     emojiPicker =
         /** @type {!EmojiPicker} */ (document.createElement('emoji-picker'));
-    emojiPicker.emojiDataUrl = '/emoji_test_ordering.json';
 
     findInEmojiPicker = (...path) => deepQuerySelector(emojiPicker, path);
 
     // Wait until emoji data is loaded before executing tests.
     return new Promise((resolve) => {
-      emojiPicker.addEventListener(V2_CONTENT_LOADED, resolve);
+      emojiPicker.addEventListener(EMOJI_DATA_LOADED, resolve);
       document.body.appendChild(emojiPicker);
       flush();
     });
@@ -54,43 +52,18 @@
   });
 
   test(
-      'emoticon category button should be active after clicking at it.', () => {
+      'emoticon category button should be active after clicking at it.',
+      async () => {
         const emojiCategoryButton = findInEmojiPicker(
             'emoji-search', 'emoji-category-button', 'cr-icon-button');
         const emoticonCategoryButton = findInEmojiPicker(
             'emoji-search', 'emoji-category-button:last-of-type',
             'cr-icon-button');
         emoticonCategoryButton.click();
-        assertTrue(isCategoryButtonActive(emoticonCategoryButton));
-        assertFalse(isCategoryButtonActive(emojiCategoryButton));
-      });
-
-  test(
-      `the first tab of the next pagination should be active when clicking at
-        either chevron.`,
-      async () => {
-        const leftChevron = findInEmojiPicker('#left-chevron');
-        const rightChevron = findInEmojiPicker('#right-chevron');
-        const emoticonCategoryButton = findInEmojiPicker(
-            'emoji-search', 'emoji-category-button:last-of-type',
-            'cr-icon-button');
-        emoticonCategoryButton.click();
-
-        const firstEmoticonTabInFirstPage = await waitForCondition(
-            () => findInEmojiPicker(
-                '.pagination text-group-button', 'cr-button'));
-        const firstEmoticonTabInSecondPage = await waitForCondition(
-            () => findInEmojiPicker(
-                '.pagination + .pagination', 'text-group-button', 'cr-button'));
         await waitForCondition(
-            () => isGroupButtonActive(firstEmoticonTabInFirstPage));
-        rightChevron.click();
-        await waitForCondition(
-            () => !isGroupButtonActive(firstEmoticonTabInFirstPage) &&
-                isGroupButtonActive(firstEmoticonTabInSecondPage));
-        leftChevron.click();
-        await waitForCondition(
-            () => !isGroupButtonActive(firstEmoticonTabInSecondPage) &&
-                isGroupButtonActive(firstEmoticonTabInFirstPage));
+            () => isCategoryButtonActive(emoticonCategoryButton) &&
+                !isCategoryButtonActive(emojiCategoryButton),
+            'Emoticon category button failed to become active or ' +
+                'emoji category button failed to become inactive.');
       });
 });
\ No newline at end of file
diff --git a/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test.js b/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test.js
index 0357d0f3..6e8d935d 100644
--- a/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test.js
+++ b/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test.js
@@ -13,7 +13,19 @@
 
 import {assertEquals, assertFalse, assertGT, assertLT, assertTrue} from '../../chai_assert.js';
 
-import {deepQuerySelector, dispatchMouseEvent, isGroupButtonActive, timeout, waitForCondition, waitForEvent, waitWithTimeout} from './emoji_picker_test_util.js';
+import {deepQuerySelector, dispatchMouseEvent, timeout, waitForCondition, waitForEvent, waitWithTimeout} from './emoji_picker_test_util.js';
+
+const ACTIVE_CLASS = 'emoji-group-active';
+
+/**
+ * Checks if the given emoji-group-button element is activated.
+ * @param {?Element} element element to check.
+ * @return {boolean} true if active, false otherwise.
+ */
+function isGroupButtonActive(element) {
+  assert(element, 'group button element should not be null');
+  return element.classList.contains(ACTIVE_CLASS);
+}
 
 function assertCloseTo(actual, expected) {
   assertTrue(
@@ -35,7 +47,7 @@
 
     emojiPicker =
         /** @type {!EmojiPicker} */ (document.createElement('emoji-picker'));
-    emojiPicker.emojiDataUrl = '/emoji_test_ordering.json';
+    emojiPicker.emojiDataUrl = '/emoji_test_ordering';
 
     findInEmojiPicker = (...path) => deepQuerySelector(emojiPicker, path);
 
diff --git a/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test_util.js b/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test_util.js
index cd6374d..30bcb927 100644
--- a/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test_util.js
+++ b/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test_util.js
@@ -115,17 +115,3 @@
     clientY: element.getBoundingClientRect().y
   }));
 }
-
-const ACTIVE_EMOJI_GROUP_CLASS = 'emoji-group-active';
-const ACTIVE_TEXT_GROUP_CLASS = 'text-group-active';
-/**
- * Checks if the given emoji-group-button or text-group-button element is
- * activated.
- * @param {?Element} element element to check.
- * @return {boolean} true if active, false otherwise.
- */
-export function isGroupButtonActive(element) {
-  assert(element, 'group button element should not be null');
-  return element.classList.contains(ACTIVE_EMOJI_GROUP_CLASS) ||
-      element.classList.contains(ACTIVE_TEXT_GROUP_CLASS);
-}
\ No newline at end of file
diff --git a/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.js b/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.js
index 830e640a..fbb7049 100644
--- a/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.js
+++ b/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.js
@@ -34,6 +34,9 @@
     provider = new FakeUpdateProvider();
     setUpdateProviderForTesting(provider);
     provider.setFakeFirmwareUpdates(fakeFirmwareUpdates);
+    page = /** @type {!FirmwareUpdateAppElement} */ (
+        document.createElement('firmware-update-app'));
+    document.body.appendChild(page);
   });
 
   teardown(() => {
@@ -45,12 +48,6 @@
     page = null;
   });
 
-  function initializePage() {
-    page = /** @type {!FirmwareUpdateAppElement} */ (
-        document.createElement('firmware-update-app'));
-    document.body.appendChild(page);
-  }
-
   /** @return {!CrDialogElement} */
   function getUpdateDialog() {
     return page.shadowRoot.querySelector('firmware-update-dialog')
@@ -89,7 +86,6 @@
   }
 
   test('LandingPageLoaded', () => {
-    initializePage();
     // TODO(michaelcheco): Remove this stub test once the page has more
     // capabilities to test.
     assertEquals(
@@ -98,7 +94,6 @@
   });
 
   test('SettingGettingTestProvider', () => {
-    initializePage();
     let fake_provider =
         /** @type {!UpdateProviderInterface} */ (new FakeUpdateProvider());
     setUpdateProviderForTesting(fake_provider);
@@ -106,7 +101,6 @@
   });
 
   test('OpenUpdateDialog', async () => {
-    initializePage();
     await flushTasks();
     // Open dialog for first firmware update card.
     getUpdateCards()[0].shadowRoot.querySelector(`#updateButton`).click();
@@ -117,7 +111,6 @@
   });
 
   test('SuccessfulUpdate', async () => {
-    initializePage();
     await flushTasks();
     // Open dialog for firmware update.
     getUpdateCards()[1].shadowRoot.querySelector(`#updateButton`).click();
@@ -141,7 +134,6 @@
   });
 
   test('UpdateFailed', async () => {
-    initializePage();
     await flushTasks();
     // Open dialog for firmware update. The third fake update in the list
     // will fail.
@@ -164,40 +156,4 @@
             mojoString16ToString(fakeFirmwareUpdate.deviceName)}`,
         getUpdateDialogTitle().innerText.trim());
   });
-
-  test('InflightUpdate', async () => {
-    // Simulate an inflight update is already in progress.
-    provider.setInflightUpdate(fakeFirmwareUpdates[0][0]);
-    initializePage();
-    await flushTasks();
-    await flushTasks();
-
-    // Simulate InstallProgressChangedObserver being called.
-    controller.beginUpdate();
-    await flushTasks();
-    // Check that the update dialog is now opened with an update.
-    assertEquals(UpdateState.kUpdating, getUpdateState());
-    const fakeUpdate = getFirmwareUpdateFromDialog();
-    assertEquals(
-        `Updating ${mojoString16ToString(fakeUpdate.deviceName)}`,
-        getUpdateDialogTitle().innerText.trim());
-    // Allow firmware update to complete.
-    await controller.getUpdateCompletedPromiseForTesting();
-    await flushTasks();
-    assertEquals(UpdateState.kSuccess, getUpdateState());
-    assertTrue(getUpdateDialog().open);
-  });
-
-  test('InflightUpdateNoProgressUpdate', async () => {
-    // Simulate an inflight update is already in progress.
-    provider.setInflightUpdate(fakeFirmwareUpdates[0][0]);
-    initializePage();
-    await flushTasks();
-    await flushTasks();
-
-    // Check that the update dialog is now opened with an update.
-    assertEquals(UpdateState.kIdle, getUpdateState());
-    const fakeUpdate = getFirmwareUpdateFromDialog();
-    assertTrue(getUpdateDialog().open);
-  });
 }
diff --git a/chrome/test/data/webui/chromeos/personalization_app/personalization_theme_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/personalization_theme_element_test.ts
index ccada54..bf27b44 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/personalization_theme_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/personalization_theme_element_test.ts
@@ -6,7 +6,7 @@
 
 import {emptyState} from 'chrome://personalization/trusted/personalization_state.js';
 import {PersonalizationThemeElement} from 'chrome://personalization/trusted/personalization_theme_element.js';
-import {ThemeActionName} from 'chrome://personalization/trusted/theme/theme_actions.js';
+import {SetDarkModeEnabledAction, ThemeActionName} from 'chrome://personalization/trusted/theme/theme_actions.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/test_util.js';
 
@@ -46,8 +46,9 @@
   test('sets color mode in store on first load', async () => {
     personalizationStore.expectAction(ThemeActionName.SET_DARK_MODE_ENABLED);
     personalizationThemeElement = initElement(PersonalizationThemeElement);
-    const action = await personalizationStore.waitForAction(
-        ThemeActionName.SET_DARK_MODE_ENABLED);
+    const action =
+        await personalizationStore.waitForAction(
+            ThemeActionName.SET_DARK_MODE_ENABLED) as SetDarkModeEnabledAction;
     assertTrue(action.enabled);
   });
 
@@ -63,8 +64,9 @@
     themeProvider.themeObserverRemote!.onColorModeChanged(
         /*darkModeEnabled=*/ false);
 
-    const {enabled} = await personalizationStore.waitForAction(
-        ThemeActionName.SET_DARK_MODE_ENABLED);
+    const {enabled} =
+        await personalizationStore.waitForAction(
+            ThemeActionName.SET_DARK_MODE_ENABLED) as SetDarkModeEnabledAction;
     assertFalse(enabled);
   });
 
diff --git a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_observer_test.ts b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_observer_test.ts
index e9c28f03..4da2ac6 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_observer_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_observer_test.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 import {emptyState} from 'chrome://personalization/trusted/personalization_state.js';
-import {WallpaperActionName} from 'chrome://personalization/trusted/wallpaper/wallpaper_actions.js';
+import {SetSelectedImageAction, WallpaperActionName} from 'chrome://personalization/trusted/wallpaper/wallpaper_actions.js';
 import {WallpaperObserver} from 'chrome://personalization/trusted/wallpaper/wallpaper_observer.js';
 import {assertDeepEquals, assertEquals} from 'chrome://webui-test/chai_assert.js';
 
@@ -27,8 +27,9 @@
 
   test('sets wallpaper image in store on first load', async () => {
     personalizationStore.expectAction(WallpaperActionName.SET_SELECTED_IMAGE);
-    const action = await personalizationStore.waitForAction(
-        WallpaperActionName.SET_SELECTED_IMAGE);
+    const action =
+        await personalizationStore.waitForAction(
+            WallpaperActionName.SET_SELECTED_IMAGE) as SetSelectedImageAction;
     assertDeepEquals(wallpaperProvider.currentWallpaper, action.image);
   });
 
@@ -40,8 +41,9 @@
     wallpaperProvider.wallpaperObserverRemote!.onWallpaperChanged(
         wallpaperProvider.currentWallpaper);
 
-    const {image} = await personalizationStore.waitForAction(
-        WallpaperActionName.SET_SELECTED_IMAGE);
+    const {image} =
+        await personalizationStore.waitForAction(
+            WallpaperActionName.SET_SELECTED_IMAGE) as SetSelectedImageAction;
 
     assertDeepEquals(wallpaperProvider.currentWallpaper, image);
   });
@@ -74,4 +76,4 @@
         },
         action);
   });
-}
\ No newline at end of file
+}
diff --git a/chrome/test/data/webui/settings/chromeos/app_management/supported_links_item_test.js b/chrome/test/data/webui/settings/chromeos/app_management/supported_links_item_test.js
index 5a3fdc04..4cc18e21 100644
--- a/chrome/test/data/webui/settings/chromeos/app_management/supported_links_item_test.js
+++ b/chrome/test/data/webui/settings/chromeos/app_management/supported_links_item_test.js
@@ -5,7 +5,7 @@
 // clang-format off
 // #import 'chrome://os-settings/chromeos/os_settings.js';
 
-// #import {AppManagementStore, FakePageHandler, updateSelectedAppId} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {AppManagementStore, FakePageHandler, updateSelectedAppId, addApp} from 'chrome://os-settings/chromeos/os_settings.js';
 // #import {setupFakeHandler, replaceStore, replaceBody, isHidden} from './test_util.m.js';
 // #import {flushTasks} from 'chrome://test/test_util.js';
 // clang-format on
@@ -22,6 +22,10 @@
 
     supportedLinksItem =
         document.createElement('app-management-supported-links-item');
+
+    replaceBody(supportedLinksItem);
+    test_util.flushTasks();
+
     // TODO(crbug.com/1204324): Remove this line when the feature is launched.
     loadTimeData.overrideValues({
       appManagementIntentSettingsEnabled: true,
@@ -257,7 +261,7 @@
     const promise = fakeHandler.whenCalled('getOverlappingPreferredApps');
     await supportedLinksItem.shadowRoot.querySelector('#preferred').click();
     await promise;
-    await test_util.flushTasks();
+
     assertTrue(
         !!supportedLinksItem.shadowRoot.querySelector('#overlap-dialog'));
 
@@ -331,4 +335,73 @@
         supportedLinksItem.shadowRoot.querySelector('cr-radio-group').selected,
         'preferred');
   });
+
+  test('overlap warning isnt shown when not selected', async function() {
+    const pwaOptions1 = {
+      type: apps.mojom.AppType.kWeb,
+      isPreferredApp: true,
+      supportedLinks: ['google.com', 'gmail.com'],
+    };
+
+    const pwaOptions2 = {
+      type: apps.mojom.AppType.kWeb,
+      isPreferredApp: false,
+      supportedLinks: ['google.com'],
+    };
+
+    // Add PWA app, and make it the currently selected app.
+    const app1 = await fakeHandler.addApp('app1', pwaOptions1);
+    await fakeHandler.addApp('app2', pwaOptions2);
+    fakeHandler.overlappingAppIds = ['app2'];
+
+    app_management.AppManagementStore.getInstance().dispatch(
+        app_management.actions.updateSelectedAppId(app1.id));
+    await fakeHandler.flushPipesForTesting();
+
+    expectTrue(
+        !!app_management.AppManagementStore.getInstance().data.apps[app1.id]);
+    supportedLinksItem.app = app1;
+    replaceBody(supportedLinksItem);
+    await fakeHandler.flushPipesForTesting();
+    await test_util.flushTasks();
+
+    assertFalse(
+        !!supportedLinksItem.shadowRoot.querySelector('#overlap-warning'));
+  });
+
+  test('overlap warning is shown', async function() {
+    const pwaOptions1 = {
+      type: apps.mojom.AppType.kWeb,
+      isPreferredApp: false,
+      supportedLinks: ['google.com', 'gmail.com'],
+    };
+
+    const pwaOptions2 = {
+      type: apps.mojom.AppType.kWeb,
+      isPreferredApp: true,
+      supportedLinks: ['google.com'],
+    };
+
+    // Add PWA app, and make it the currently selected app.
+    const app1 = await fakeHandler.addApp('app1', pwaOptions1);
+    const app2 = await fakeHandler.addApp('app2', pwaOptions2);
+    fakeHandler.overlappingAppIds = ['app2'];
+
+    app_management.AppManagementStore.getInstance().dispatch(
+        app_management.actions.updateSelectedAppId(app1.id));
+    await fakeHandler.flushPipesForTesting();
+    await test_util.flushTasks();
+
+    expectTrue(
+        !!app_management.AppManagementStore.getInstance().data.apps[app1.id]);
+    expectTrue(
+        !!app_management.AppManagementStore.getInstance().data.apps[app2.id]);
+    supportedLinksItem.app = app1;
+    replaceBody(supportedLinksItem);
+    await fakeHandler.flushPipesForTesting();
+    await test_util.flushTasks();
+
+    assertTrue(
+        !!supportedLinksItem.shadowRoot.querySelector('#overlap-warning'));
+  });
 });
diff --git a/chrome/test/data/webui/test_store.js b/chrome/test/data/webui/test_store.js
index 219ba827..2c0d0bbf 100644
--- a/chrome/test/data/webui/test_store.js
+++ b/chrome/test/data/webui/test_store.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {Store} from 'chrome://resources/js/cr/ui/store.js';
+import {Action, Store} from 'chrome://resources/js/cr/ui/store.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
 
 /**
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index 3f88dbd5..524ba00 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -504,26 +504,20 @@
 
       if (is_mac) {
         inputs = [
-          "$root_build_dir/.install",
-          "$root_build_dir/${updater_product_full_name}_test.app/Contents/Info.plist",
-          "$root_build_dir/${updater_product_full_name}_test.app/Contents/MacOS/${updater_product_full_name}_test",
-          "$root_build_dir/${updater_product_full_name}_test.app/Contents/Helpers/$keystone_app_name.bundle/Contents/MacOS/$keystone_app_name",
-          "$root_build_dir/${updater_product_full_name}_test.app/Contents/Helpers/$keystone_app_name.bundle/Contents/Helpers/ksinstall",
-          "$root_build_dir/${updater_product_full_name}_test.app/Contents/Helpers/$keystone_app_name.bundle/Contents/Helpers/ksadmin",
-          "$root_build_dir/${updater_product_full_name}_test.app/Contents/Helpers/$keystone_app_name.bundle/Contents/Resources/${keystone_app_name}Agent.app/Contents/MacOS/${keystone_app_name}Agent",
-          "$root_build_dir/${updater_product_full_name}_test.app/Contents/Helpers/$keystone_app_name.bundle/Contents/Resources/${keystone_app_name}Agent.app/Contents/Info.plist",
-          "$root_build_dir/${updater_product_full_name}_test.app/Contents/Helpers/$keystone_app_name.bundle/Contents/Info.plist",
-          "$root_build_dir/${updater_product_full_name}_test.app/Contents/PkgInfo",
+          "$root_build_dir/$updater_product_full_name.app/Contents/Info.plist",
+          "$root_build_dir/$updater_product_full_name.app/Contents/MacOS/$updater_product_full_name",
+          "$root_build_dir/$updater_product_full_name.app/Contents/Helpers/$keystone_app_name.bundle/Contents/MacOS/$keystone_app_name",
+          "$root_build_dir/$updater_product_full_name.app/Contents/Helpers/$keystone_app_name.bundle/Contents/Helpers/ksinstall",
+          "$root_build_dir/$updater_product_full_name.app/Contents/Helpers/$keystone_app_name.bundle/Contents/Helpers/ksadmin",
+          "$root_build_dir/$updater_product_full_name.app/Contents/Helpers/$keystone_app_name.bundle/Contents/Info.plist",
+          "$root_build_dir/$updater_product_full_name.app/Contents/PkgInfo",
         ]
-        deps = [
-          "//chrome/updater/mac:updater_bundle_test",
-          "//chrome/updater/mac:updater_test_install_script",
-        ]
+        deps = [ "//chrome/updater/mac:updater_bundle" ]
       }
 
       if (is_win) {
-        inputs = [ "$root_build_dir/UpdaterSetup_test.exe" ]
-        deps = [ "//chrome/updater/win/installer:installer_test" ]
+        inputs = [ "$root_build_dir/UpdaterSetup.exe" ]
+        deps = [ "//chrome/updater/win/installer" ]
       }
     }
   }
@@ -587,6 +581,11 @@
       "//url",
     ]
 
+    # TODO(crbug.com/1276126) - enable building the CRX target.
+    if (!is_linux) {
+      deps += [ ":updater_selfupdate_test_crx" ]
+    }
+
     if (is_win) {
       sources += [
         "activity_impl_win_unittest.cc",
@@ -610,7 +609,6 @@
       ]
 
       data_deps = [
-        ":updater_selfupdate_test_crx",
         "//chrome/updater/win:updater_test",
         "//chrome/updater/win/installer:installer_test",
         "//chrome/updater/win/installer:installer_unittest",
@@ -668,36 +666,9 @@
       ]
 
       data_deps = [
-        ":updater_selfupdate_test_crx",
         "//chrome/updater/mac:ksadmin",
         "//chrome/updater/mac:updater_bundle_test",
       ]
-
-      if (target_cpu == "arm64") {
-        data += [
-          "//third_party/updater/chromium_mac_arm64/updater/ChromiumUpdater_test.app/Contents/MacOS/ChromiumUpdater_test",
-          "//third_party/updater/chromium_mac_arm64/updater/ChromiumUpdater_test.app/Contents/Info.plist",
-          "//third_party/updater/chromium_mac_arm64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/MacOS/ChromiumSoftwareUpdate",
-          "//third_party/updater/chromium_mac_arm64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/Resources/ChromiumSoftwareUpdateAgent.app/Contents/MacOS/ChromiumSoftwareUpdateAgent",
-          "//third_party/updater/chromium_mac_arm64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/Resources/ChromiumSoftwareUpdateAgent.app/Contents/MacOS/Info.plist",
-          "//third_party/updater/chromium_mac_arm64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/Helpers/ksinstall",
-          "//third_party/updater/chromium_mac_arm64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/Helpers/ksadmin",
-          "//third_party/updater/chromium_mac_arm64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/Info.plist",
-        ]
-      }
-
-      if (target_cpu == "x64") {
-        data += [
-          "//third_party/updater/chromium_mac_amd64/updater/ChromiumUpdater_test.app/Contents/MacOS/ChromiumUpdater_test",
-          "//third_party/updater/chromium_mac_amd64/updater/ChromiumUpdater_test.app/Contents/Info.plist",
-          "//third_party/updater/chromium_mac_amd64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/MacOS/ChromiumSoftwareUpdate",
-          "//third_party/updater/chromium_mac_amd64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/Resources/ChromiumSoftwareUpdateAgent.app/Contents/MacOS/ChromiumSoftwareUpdateAgent",
-          "//third_party/updater/chromium_mac_amd64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/Resources/ChromiumSoftwareUpdateAgent.app/Contents/MacOS/Info.plist",
-          "//third_party/updater/chromium_mac_amd64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/Helpers/ksinstall",
-          "//third_party/updater/chromium_mac_amd64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/Helpers/ksadmin",
-          "//third_party/updater/chromium_mac_amd64/updater/ChromiumUpdater_test.app/Contents/Helpers/ChromiumSoftwareUpdate.bundle/Contents/Info.plist",
-        ]
-      }
     }
 
     if (is_linux) {
diff --git a/chrome/updater/DEPS b/chrome/updater/DEPS
index 5d61d217..26d806a2 100644
--- a/chrome/updater/DEPS
+++ b/chrome/updater/DEPS
@@ -15,6 +15,5 @@
   "+third_party/boringssl",
   "+third_party/crashpad",
   "+third_party/re2",
-  "+third_party/updater",
   "+third_party/zlib/google",
 ]
diff --git a/chrome/updater/mac/BUILD.gn b/chrome/updater/mac/BUILD.gn
index 8d2b584..be429c8 100644
--- a/chrome/updater/mac/BUILD.gn
+++ b/chrome/updater/mac/BUILD.gn
@@ -116,26 +116,6 @@
   ]
 }
 
-action("updater_test_install_script") {
-  script = "embed_variables.py"
-
-  inputs = [
-    script,
-    "setup/.install.sh",
-  ]
-
-  outputs = [ "$root_out_dir/.install" ]
-
-  args = [
-    "-i",
-    rebase_path("setup/.install.sh"),
-    "-o",
-    rebase_path(root_out_dir + "/.install"),
-    "-p",
-    updater_product_full_name + "_test",
-  ]
-}
-
 action("browser_install_script") {
   script = "embed_variables.py"
 
diff --git a/chrome/updater/test/integration_test_commands.h b/chrome/updater/test/integration_test_commands.h
index cb49f91..7d15af8b 100644
--- a/chrome/updater/test/integration_test_commands.h
+++ b/chrome/updater/test/integration_test_commands.h
@@ -37,7 +37,6 @@
   virtual void ExpectActiveUpdater() const = 0;
   virtual void ExpectActive(const std::string& app_id) const = 0;
   virtual void ExpectNotActive(const std::string& app_id) const = 0;
-  virtual void ExpectSelfUpdateSequence(ScopedServer* test_server) const = 0;
   virtual void ExpectUpdateSequence(ScopedServer* test_server,
                                     const std::string& app_id,
                                     const base::Version& from_version,
@@ -49,7 +48,6 @@
   virtual void CopyLog() const = 0;
   virtual void SetupFakeUpdaterHigherVersion() const = 0;
   virtual void SetupFakeUpdaterLowerVersion() const = 0;
-  virtual void SetupRealUpdaterLowerVersion() const = 0;
   virtual void SetExistenceCheckerPath(const std::string& app_id,
                                        const base::FilePath& path) const = 0;
   virtual void SetServerStarts(int value) const = 0;
@@ -58,7 +56,6 @@
   virtual void ExpectAppVersion(const std::string& app_id,
                                 const base::Version& version) const = 0;
   virtual void RunWake(int exit_code) const = 0;
-  virtual void RunWakeActive(int exit_code) const = 0;
   virtual void Update(const std::string& app_id) const = 0;
   virtual void UpdateAll() const = 0;
   virtual void PrintLog() const = 0;
diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc
index 8779edb..b5b8241 100644
--- a/chrome/updater/test/integration_test_commands_system.cc
+++ b/chrome/updater/test/integration_test_commands_system.cc
@@ -68,10 +68,6 @@
     RunCommand("enter_test_mode", {Param("url", url.spec())});
   }
 
-  void ExpectSelfUpdateSequence(ScopedServer* test_server) const override {
-    updater::test::ExpectSelfUpdateSequence(updater_scope_, test_server);
-  }
-
   void ExpectUpdateSequence(ScopedServer* test_server,
                             const std::string& app_id,
                             const base::Version& from_version,
@@ -108,10 +104,6 @@
     RunCommand("setup_fake_updater_lower_version");
   }
 
-  void SetupRealUpdaterLowerVersion() const override {
-    RunCommand("setup_real_updater_lower_version");
-  }
-
   void SetExistenceCheckerPath(const std::string& app_id,
                                const base::FilePath& path) const override {
     RunCommand("set_existence_checker_path",
@@ -144,11 +136,6 @@
                {Param("exit_code", base::NumberToString(expected_exit_code))});
   }
 
-  void RunWakeActive(int expected_exit_code) const override {
-    RunCommand("run_wake_active",
-               {Param("exit_code", base::NumberToString(expected_exit_code))});
-  }
-
   void Update(const std::string& app_id) const override {
     RunCommand("update", {Param("app_id", app_id)});
   }
diff --git a/chrome/updater/test/integration_test_commands_user.cc b/chrome/updater/test/integration_test_commands_user.cc
index 17e92cda..e2f5298 100644
--- a/chrome/updater/test/integration_test_commands_user.cc
+++ b/chrome/updater/test/integration_test_commands_user.cc
@@ -61,10 +61,6 @@
     updater::test::EnterTestMode(url);
   }
 
-  void ExpectSelfUpdateSequence(ScopedServer* test_server) const override {
-    updater::test::ExpectSelfUpdateSequence(updater_scope_, test_server);
-  }
-
   void ExpectUpdateSequence(ScopedServer* test_server,
                             const std::string& app_id,
                             const base::Version& from_version,
@@ -93,10 +89,6 @@
     updater::test::SetupFakeUpdaterLowerVersion(updater_scope_);
   }
 
-  void SetupRealUpdaterLowerVersion() const override {
-    updater::test::SetupRealUpdaterLowerVersion(updater_scope_);
-  }
-
   void SetExistenceCheckerPath(const std::string& app_id,
                                const base::FilePath& path) const override {
     updater::test::SetExistenceCheckerPath(updater_scope_, app_id, path);
@@ -133,10 +125,6 @@
     updater::test::RunWake(updater_scope_, exit_code);
   }
 
-  void RunWakeActive(int exit_code) const override {
-    updater::test::RunWakeActive(updater_scope_, exit_code);
-  }
-
   void Update(const std::string& app_id) const override {
     updater::test::Update(updater_scope_, app_id);
   }
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index c9b5f4c..a259dcc9 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -23,7 +23,6 @@
 #include "base/version.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
-#include "build/buildflag.h"
 #include "chrome/updater/constants.h"
 #include "chrome/updater/persisted_data.h"
 #include "chrome/updater/prefs.h"
@@ -171,10 +170,6 @@
     test_commands_->SetupFakeUpdaterLowerVersion();
   }
 
-  void SetupRealUpdaterLowerVersion() {
-    test_commands_->SetupRealUpdaterLowerVersion();
-  }
-
   void SetActive(const std::string& app_id) {
     test_commands_->SetActive(app_id);
   }
@@ -209,10 +204,6 @@
 
   void RunWake(int exit_code) { test_commands_->RunWake(exit_code); }
 
-  void RunWakeActive(int exit_code) {
-    test_commands_->RunWakeActive(exit_code);
-  }
-
   void Update(const std::string& app_id) { test_commands_->Update(app_id); }
 
   void UpdateAll() { test_commands_->UpdateAll(); }
@@ -243,10 +234,6 @@
                                          to_version);
   }
 
-  void ExpectSelfUpdateSequence(ScopedServer* test_server) {
-    test_commands_->ExpectSelfUpdateSequence(test_server);
-  }
-
   void ExpectRegistrationEvent(ScopedServer* test_server,
                                const std::string& app_id) {
     test_server->ExpectOnce(
@@ -592,35 +579,6 @@
 }
 #endif  // BUILDFLAG(IS_MAC)
 
-// Windows and Google-branded builds will eventually support this test, but for
-// now only Chromium-branded macOS updaters are available in third_party.
-#if BUILDFLAG(IS_MAC) && BUILDFLAG(CHROMIUM_BRANDING)
-TEST_F(IntegrationTest, SelfUpdateFromOldReal) {
-  ScopedServer test_server(test_commands_);
-  ExpectRegistrationEvent(&test_server, kUpdaterAppId);
-  SetupRealUpdaterLowerVersion();
-  ExpectVersionNotActive(kUpdaterVersion);
-
-  // Trigger an old instance update check.
-  ExpectSelfUpdateSequence(&test_server);
-  RunWakeActive(0);
-
-  // Qualify the new instance.
-  ExpectRegistrationEvent(&test_server, kQualificationAppId);
-  ExpectUpdateSequence(&test_server, kQualificationAppId, base::Version("0.1"),
-                       base::Version("0.2"));
-  RunWake(0);
-  WaitForUpdaterExit();
-
-  // Activate the new instance. (It should not check itself for updates.)
-  RunWake(0);
-  WaitForUpdaterExit();
-
-  ExpectVersionActive(kUpdaterVersion);
-  Uninstall();
-}
-#endif
-
 TEST_F(IntegrationTest, UpdateServiceStress) {
   Install();
   ExpectInstalled();
diff --git a/chrome/updater/test/integration_tests_helper.cc b/chrome/updater/test/integration_tests_helper.cc
index e62aca6..eaaf8f9 100644
--- a/chrome/updater/test/integration_tests_helper.cc
+++ b/chrome/updater/test/integration_tests_helper.cc
@@ -227,8 +227,6 @@
     {"install", WithSystemScope(Wrap(&Install))},
     {"print_log", WithSystemScope(Wrap(&PrintLog))},
     {"run_wake", WithSwitch("exit_code", WithSystemScope(Wrap(&RunWake)))},
-    {"run_wake_active",
-     WithSwitch("exit_code", WithSystemScope(Wrap(&RunWakeActive)))},
     {"update", WithSwitch("app_id", WithSystemScope(Wrap(&Update)))},
     {"update_all", WithSystemScope(Wrap(&UpdateAll))},
     {"register_app", WithSwitch("app_id", WithSystemScope(Wrap(&RegisterApp)))},
@@ -240,8 +238,6 @@
      WithSystemScope(Wrap(&SetupFakeUpdaterHigherVersion))},
     {"setup_fake_updater_lower_version",
      WithSystemScope(Wrap(&SetupFakeUpdaterLowerVersion))},
-    {"setup_real_updater_lower_version",
-     WithSystemScope(Wrap(&SetupRealUpdaterLowerVersion))},
     {"set_first_registration_counter",
      WithSwitch("value", WithSystemScope(Wrap(&SetServerStarts)))},
     {"stress_update_service", WithSystemScope(Wrap(&StressUpdateService))},
diff --git a/chrome/updater/test/integration_tests_impl.cc b/chrome/updater/test/integration_tests_impl.cc
index 999b20f0..382a081 100644
--- a/chrome/updater/test/integration_tests_impl.cc
+++ b/chrome/updater/test/integration_tests_impl.cc
@@ -14,7 +14,6 @@
 #include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
-#include "base/files/memory_mapped_file.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/json/json_reader.h"
 #include "base/logging.h"
@@ -25,7 +24,6 @@
 #include "base/process/process.h"
 #include "base/run_loop.h"
 #include "base/strings/strcat.h"
-#include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/post_task.h"
@@ -47,12 +45,9 @@
 #include "chrome/updater/service_proxy_factory.h"
 #include "chrome/updater/test/server.h"
 #include "chrome/updater/update_service.h"
-#include "chrome/updater/updater_branding.h"
 #include "chrome/updater/updater_scope.h"
 #include "chrome/updater/updater_version.h"
 #include "chrome/updater/util.h"
-#include "crypto/secure_hash.h"
-#include "crypto/sha2.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/re2/src/re2/re2.h"
@@ -61,38 +56,25 @@
 namespace test {
 namespace {
 
-constexpr char kSelfUpdateCRXName[] = "updater_selfupdate.crx3";
 #if BUILDFLAG(IS_MAC)
-constexpr char kSelfUpdateCRXRun[] = PRODUCT_FULLNAME_STRING "_test.app";
 constexpr char kDoNothingCRXName[] = "updater_qualification_app_dmg.crx";
 constexpr char kDoNothingCRXRun[] = "updater_qualification_app_dmg.dmg";
+constexpr char kDoNothingCRXHash[] =
+    "c9eeadf63732f3259e2ad1cead6298f90a3ef4b601b1ba1cbb0f37b6112a632c";
 #elif BUILDFLAG(IS_WIN)
-constexpr char kSelfUpdateCRXRun[] = "UpdaterSetup_test.exe";
 constexpr char kDoNothingCRXName[] = "updater_qualification_app_exe.crx";
 constexpr char kDoNothingCRXRun[] = "qualification_app.exe";
+constexpr char kDoNothingCRXHash[] =
+    "0705f7eedb0427810db76dfc072c8cbc302fbeb9b2c56fa0de3752ed8d6f9164";
 #elif BUILDFLAG(IS_LINUX)
-constexpr char kSelfUpdateCRXRun[] = "UpdaterSetup_test";
 constexpr char kDoNothingCRXName[] = "updater_qualification_app.crx";
 constexpr char kDoNothingCRXRun[] = "qualification_app";
+constexpr char kDoNothingCRXHash[] = "";
 #endif
 
-std::string GetHashHex(const base::FilePath& file) {
-  std::unique_ptr<crypto::SecureHash> hasher(
-      crypto::SecureHash::Create(crypto::SecureHash::SHA256));
-  base::MemoryMappedFile mmfile;
-  EXPECT_TRUE(mmfile.Initialize(file));  // Note: This fails with an empty file.
-  hasher->Update(mmfile.data(), mmfile.length());
-  uint8_t actual_hash[crypto::kSHA256Length] = {0};
-  hasher->Finish(actual_hash, sizeof(actual_hash));
-  return base::HexEncode(actual_hash, sizeof(actual_hash));
-}
-
 std::string GetUpdateResponse(const std::string& app_id,
                               const std::string& codebase,
-                              const base::Version& version,
-                              const base::FilePath& update_file,
-                              const std::string& run_action,
-                              const std::string& arguments) {
+                              const base::Version& version) {
   return base::StringPrintf(
       ")]}'\n"
       R"({"response":{)"
@@ -107,7 +89,6 @@
       R"(        "manifest":{)"
       R"(          "version":"%s",)"
       R"(          "run":"%s",)"
-      R"(          "arguments":"%s",)"
       R"(          "packages":{)"
       R"(            "package":[)"
       R"(              {"name":"%s","hash_sha256":"%s"})"
@@ -119,38 +100,7 @@
       R"(  ])"
       R"(}})",
       app_id.c_str(), codebase.c_str(), version.GetString().c_str(),
-      run_action.c_str(), arguments.c_str(),
-      update_file.BaseName().AsUTF8Unsafe().c_str(),
-      GetHashHex(update_file).c_str());
-}
-
-base::RepeatingCallback<bool(const std::string&)> GetScopePredicate(
-    UpdaterScope scope) {
-  return base::BindLambdaForTesting([scope](const std::string& request_body) {
-    const bool is_match = [&scope, &request_body]() {
-      const absl::optional<base::Value> doc =
-          base::JSONReader::Read(request_body);
-      if (!doc || !doc->is_dict())
-        return false;
-      const base::Value* object_request = doc->FindKey("request");
-      if (!object_request || !object_request->is_dict())
-        return false;
-      const base::Value* value_ismachine = object_request->FindKey("ismachine");
-      if (!value_ismachine || !value_ismachine->is_bool())
-        return false;
-      switch (scope) {
-        case UpdaterScope::kSystem:
-          return value_ismachine->GetBool();
-        case UpdaterScope::kUser:
-          return !value_ismachine->GetBool();
-      }
-    }();
-    if (!is_match) {
-      ADD_FAILURE() << R"(Request does not match "ismachine": )"
-                    << request_body;
-    }
-    return is_match;
-  });
+      kDoNothingCRXRun, kDoNothingCRXName, kDoNothingCRXHash);
 }
 
 }  // namespace
@@ -190,16 +140,6 @@
   EXPECT_NE(prefs->GetActiveVersion(), version);
 }
 
-void Install(UpdaterScope scope) {
-  const base::FilePath path = GetSetupExecutablePath();
-  ASSERT_FALSE(path.empty());
-  base::CommandLine command_line(path);
-  command_line.AppendSwitch(kInstallSwitch);
-  int exit_code = -1;
-  ASSERT_TRUE(Run(scope, command_line, &exit_code));
-  EXPECT_EQ(exit_code, 0);
-}
-
 void PrintLog(UpdaterScope scope) {
   std::string contents;
   absl::optional<base::FilePath> path = GetDataDirPath(scope);
@@ -249,31 +189,9 @@
   EXPECT_TRUE(base::PathExists(*installed_executable_path));
   base::CommandLine command_line(*installed_executable_path);
   command_line.AppendSwitch(kWakeSwitch);
-  int exit_code = -1;
-  ASSERT_TRUE(Run(scope, command_line, &exit_code));
-  EXPECT_EQ(exit_code, expected_exit_code);
-}
-
-void RunWakeActive(UpdaterScope scope, int expected_exit_code) {
-  // Find the active version.
-  base::Version active_version;
-  {
-    scoped_refptr<UpdateService> service = CreateUpdateServiceProxy(scope);
-    base::RunLoop loop;
-    service->GetVersion(base::BindOnce(base::BindLambdaForTesting(
-        [&loop, &active_version](const base::Version& version) {
-          active_version = version;
-          loop.Quit();
-        })));
-    loop.Run();
-  }
-  ASSERT_TRUE(active_version.IsValid());
-
-  // Invoke the wake client of that version.
-  base::CommandLine command_line(
-      GetVersionedUpdaterFolderPathForVersion(scope, active_version)
-          ->Append(GetExecutableRelativePath()));
-  command_line.AppendSwitch(kWakeSwitch);
+  command_line.AppendSwitch(kEnableLoggingSwitch);
+  command_line.AppendSwitchASCII(kLoggingModuleSwitch,
+                                 kLoggingModuleSwitchValue);
   int exit_code = -1;
   ASSERT_TRUE(Run(scope, command_line, &exit_code));
   EXPECT_EQ(exit_code, expected_exit_code);
@@ -416,62 +334,53 @@
   return true;
 }
 
-void ExpectSelfUpdateSequence(UpdaterScope scope, ScopedServer* test_server) {
-  base::FilePath test_data_path;
-  ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &test_data_path));
-  base::FilePath crx_path = test_data_path.AppendASCII(kSelfUpdateCRXName);
-  ASSERT_TRUE(base::PathExists(crx_path));
-
-  // First request: update check.
-  test_server->ExpectOnce(
-      {base::BindRepeating(
-           RequestMatcherRegex,
-           base::StringPrintf(R"(.*"appid":"%s".*)", kUpdaterAppId)),
-       GetScopePredicate(scope)},
-      GetUpdateResponse(
-          kUpdaterAppId, test_server->base_url().spec(),
-          base::Version(kUpdaterVersion), crx_path, kSelfUpdateCRXRun,
-          base::StrCat({"--update",
-                        scope == UpdaterScope::kSystem ? " --system" : ""})));
-
-  // Second request: update download.
-  std::string crx_bytes;
-  base::ReadFileToString(crx_path, &crx_bytes);
-  test_server->ExpectOnce({base::BindRepeating(RequestMatcherRegex, "")},
-                          crx_bytes);
-
-  // Third request: event ping.
-  test_server->ExpectOnce(
-      {base::BindRepeating(
-           RequestMatcherRegex,
-           base::StringPrintf(R"(.*"eventresult":1,"eventtype":3,)"
-                              R"("nextversion":"%s",.*)",
-                              kUpdaterVersion)),
-       GetScopePredicate(scope)},
-      ")]}'\n");
-}
-
 void ExpectUpdateSequence(UpdaterScope scope,
                           ScopedServer* test_server,
                           const std::string& app_id,
                           const base::Version& from_version,
                           const base::Version& to_version) {
-  base::FilePath test_data_path;
-  ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
-  base::FilePath crx_path = test_data_path.Append(FILE_PATH_LITERAL("updater"))
-                                .AppendASCII(kDoNothingCRXName);
-  ASSERT_TRUE(base::PathExists(crx_path));
+  auto request_matcher_scope =
+      base::BindLambdaForTesting([scope](const std::string& request_body) {
+        const bool is_match = [&scope, &request_body]() {
+          const absl::optional<base::Value> doc =
+              base::JSONReader::Read(request_body);
+          if (!doc || !doc->is_dict())
+            return false;
+          const base::Value* object_request = doc->FindKey("request");
+          if (!object_request || !object_request->is_dict())
+            return false;
+          const base::Value* value_ismachine =
+              object_request->FindKey("ismachine");
+          if (!value_ismachine || !value_ismachine->is_bool())
+            return false;
+          switch (scope) {
+            case UpdaterScope::kSystem:
+              return value_ismachine->GetBool();
+            case UpdaterScope::kUser:
+              return !value_ismachine->GetBool();
+          }
+        }();
+        if (!is_match) {
+          ADD_FAILURE() << R"(Request does not match "ismachine": )"
+                        << request_body;
+        }
+        return is_match;
+      });
 
   // First request: update check.
   test_server->ExpectOnce(
       {base::BindRepeating(
            RequestMatcherRegex,
            base::StringPrintf(R"(.*"appid":"%s".*)", app_id.c_str())),
-       GetScopePredicate(scope)},
-      GetUpdateResponse(app_id, test_server->base_url().spec(), to_version,
-                        crx_path, kDoNothingCRXRun, {}));
+       request_matcher_scope},
+      GetUpdateResponse(app_id, test_server->base_url().spec(), to_version));
 
   // Second request: update download.
+  base::FilePath test_data_path;
+  ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
+  base::FilePath crx_path = test_data_path.Append(FILE_PATH_LITERAL("updater"))
+                                .AppendASCII(kDoNothingCRXName);
+  ASSERT_TRUE(base::PathExists(crx_path));
   std::string crx_bytes;
   base::ReadFileToString(crx_path, &crx_bytes);
   test_server->ExpectOnce({base::BindRepeating(RequestMatcherRegex, "")},
@@ -485,7 +394,7 @@
                               R"("nextversion":"%s","previousversion":"%s".*)",
                               to_version.GetString().c_str(),
                               from_version.GetString().c_str())),
-       GetScopePredicate(scope)},
+       request_matcher_scope},
       ")]}'\n");
 }
 
diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h
index 9d6bd91d..ee71e62 100644
--- a/chrome/updater/test/integration_tests_impl.h
+++ b/chrome/updater/test/integration_tests_impl.h
@@ -31,9 +31,6 @@
 
 class ScopedServer;
 
-// Returns the path to the updater executable (in the build output directory).
-base::FilePath GetSetupExecutablePath();
-
 // Prints the updater.log file to stdout.
 void PrintLog(UpdaterScope scope);
 
@@ -83,10 +80,6 @@
 // `exit_code`. The server should exit a few seconds after.
 void RunWake(UpdaterScope scope, int exit_code);
 
-// As RunWake, but runs the wake client for whatever version of the server is
-// active, rather than kUpdaterVersion.
-void RunWakeActive(UpdaterScope scope, int exit_code);
-
 // Invokes the active instance's UpdateService::Update (via RPC) for an app.
 void Update(UpdaterScope scope, const std::string& app_id);
 
@@ -115,10 +108,6 @@
 // Sets up a fake updater on the system at a version lower than the test.
 void SetupFakeUpdaterLowerVersion(UpdaterScope scope);
 
-// Sets up a real updater on the system at a version lower than the test. The
-// exact version of the updater is not defined.
-void SetupRealUpdaterLowerVersion(UpdaterScope scope);
-
 // Sets up a fake updater on the system at a version higher than the test.
 void SetupFakeUpdaterHigherVersion(UpdaterScope scope);
 
@@ -178,8 +167,6 @@
 bool RequestMatcherRegex(const std::string& request_body_regex,
                          const std::string& request_body);
 
-void ExpectSelfUpdateSequence(UpdaterScope scope, ScopedServer* test_server);
-
 void ExpectUpdateSequence(UpdaterScope scope,
                           ScopedServer* test_server,
                           const std::string& app_id,
diff --git a/chrome/updater/test/integration_tests_linux.cc b/chrome/updater/test/integration_tests_linux.cc
index e8084e3..6850c64 100644
--- a/chrome/updater/test/integration_tests_linux.cc
+++ b/chrome/updater/test/integration_tests_linux.cc
@@ -22,11 +22,6 @@
   return absl::nullopt;
 }
 
-base::FilePath GetSetupExecutablePath() {
-  NOTREACHED();
-  return base::FilePath();
-}
-
 absl::optional<base::FilePath> GetInstalledExecutablePath(UpdaterScope scope) {
   NOTREACHED();
   return absl::nullopt;
@@ -49,6 +44,10 @@
   NOTREACHED();
 }
 
+void Install(UpdaterScope scope) {
+  NOTREACHED();
+}
+
 void ExpectCandidateUninstalled(UpdaterScope scope) {
   NOTREACHED();
 }
@@ -81,9 +80,5 @@
   NOTREACHED();
 }
 
-void SetupRealUpdaterLowerVersion(UpdaterScope scope) {
-  NOTREACHED();
-}
-
 }  // namespace test
 }  // namespace updater
diff --git a/chrome/updater/test/integration_tests_mac.mm b/chrome/updater/test/integration_tests_mac.mm
index 1f39d3a..4ff2314 100644
--- a/chrome/updater/test/integration_tests_mac.mm
+++ b/chrome/updater/test/integration_tests_mac.mm
@@ -15,14 +15,11 @@
 #include "base/process/launch.h"
 #include "base/run_loop.h"
 #include "base/strings/strcat.h"
-#include "base/strings/string_split.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/test_timeouts.h"
 #include "base/time/time.h"
 #include "base/version.h"
-#include "build/build_config.h"
-#include "chrome/common/chrome_paths.h"
 #include "chrome/common/mac/launchd.h"
 #include "chrome/updater/constants.h"
 #include "chrome/updater/external_constants_builder.h"
@@ -34,7 +31,6 @@
 #include "chrome/updater/updater_branding.h"
 #include "chrome/updater/updater_scope.h"
 #include "chrome/updater/util.h"
-#include "components/crx_file/crx_verifier.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
@@ -107,18 +103,12 @@
 
 }  // namespace
 
-base::FilePath GetSetupExecutablePath() {
-  // There is no metainstaller on mac, use the main executable for setup.
-  return GetExecutablePath();
-}
-
 void EnterTestMode(const GURL& url) {
   ASSERT_TRUE(ExternalConstantsBuilder()
                   .SetUpdateURL(std::vector<std::string>{url.spec()})
                   .SetUseCUP(false)
                   .SetInitialDelay(0.1)
                   .SetServerKeepAliveSeconds(1)
-                  .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
                   .Overwrite());
 }
 
@@ -169,23 +159,6 @@
     RemoveJobFromLaunchd(scope, launchd_domain, launchd_type,
                          CopyUpdateServiceInternalLaunchdName(scope));
   }
-
-  // Also clean up any other versions of the updater that are around.
-  base::CommandLine launchctl(base::FilePath("/bin/launchctl"));
-  launchctl.AppendArg("list");
-  std::string out;
-  ASSERT_TRUE(base::GetAppOutput(launchctl, &out));
-  for (const auto& token : base::SplitStringPiece(out, base::kWhitespaceASCII,
-                                                  base::TRIM_WHITESPACE,
-                                                  base::SPLIT_WANT_NONEMPTY)) {
-    if (base::StartsWith(token, MAC_BUNDLE_IDENTIFIER_STRING)) {
-      std::string out_rm;
-      base::CommandLine launchctl_rm(base::FilePath("/bin/launchctl"));
-      launchctl_rm.AppendArg("remove");
-      launchctl_rm.AppendArg(token);
-      ASSERT_TRUE(base::GetAppOutput(launchctl_rm, &out_rm));
-    }
-  }
 }
 
 void ExpectClean(UpdaterScope scope) {
@@ -242,6 +215,16 @@
       CopyUpdateServiceInternalLaunchdName(scope)));
 }
 
+void Install(UpdaterScope scope) {
+  const base::FilePath path = GetExecutablePath();
+  ASSERT_FALSE(path.empty());
+  base::CommandLine command_line(path);
+  command_line.AppendSwitch(kInstallSwitch);
+  int exit_code = -1;
+  ASSERT_TRUE(Run(scope, command_line, &exit_code));
+  EXPECT_EQ(exit_code, 0);
+}
+
 void ExpectActiveUpdater(UpdaterScope scope) {
   Launchd::Domain launchd_domain = LaunchdDomain(scope);
   Launchd::Type launchd_type = LaunchdType(scope);
@@ -329,29 +312,5 @@
   })));
 }
 
-void SetupRealUpdaterLowerVersion(UpdaterScope scope) {
-  base::FilePath source_path;
-  ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_path));
-  base::FilePath old_updater_path =
-      source_path.Append("third_party").Append("updater");
-#if BUILDFLAG(CHROMIUM_BRANDING)
-#if defined(ARCH_CPU_ARM64)
-  old_updater_path = old_updater_path.Append("chromium_mac_arm64");
-#elif defined(ARCH_CPU_X86_64)
-  old_updater_path = old_updater_path.Append("chromium_mac_amd64");
-#endif
-#endif
-  base::CommandLine command_line(
-      old_updater_path.Append("updater")
-          .Append(PRODUCT_FULLNAME_STRING "_test.app")
-          .Append("Contents")
-          .Append("MacOS")
-          .Append(PRODUCT_FULLNAME_STRING "_test"));
-  command_line.AppendSwitch(kInstallSwitch);
-  int exit_code = -1;
-  ASSERT_TRUE(Run(scope, command_line, &exit_code));
-  ASSERT_EQ(exit_code, 0);
-}
-
 }  // namespace test
 }  // namespace updater
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index b36ce0b..9a93e9ba 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -47,7 +47,6 @@
 #include "chrome/updater/win/task_scheduler.h"
 #include "chrome/updater/win/win_constants.h"
 #include "chrome/updater/win/win_util.h"
-#include "components/crx_file/crx_verifier.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
@@ -68,6 +67,13 @@
   kCheckActiveAndSxS = 1,
 };
 
+base::FilePath GetInstallerPath() {
+  base::FilePath test_executable;
+  if (!base::PathService::Get(base::FILE_EXE, &test_executable))
+    return base::FilePath();
+  return test_executable.DirName().AppendASCII("UpdaterSetup_test.exe");
+}
+
 // Returns the root directory where the updater product is installed. This
 // is the parent directory where the versioned directories of the
 // updater instances are.
@@ -298,13 +304,6 @@
 
 }  // namespace
 
-base::FilePath GetSetupExecutablePath() {
-  base::FilePath test_executable;
-  if (!base::PathService::Get(base::FILE_EXE, &test_executable))
-    return base::FilePath();
-  return test_executable.DirName().AppendASCII("UpdaterSetup_test.exe");
-}
-
 absl::optional<base::FilePath> GetInstalledExecutablePath(UpdaterScope scope) {
   absl::optional<base::FilePath> path = GetProductVersionPath(scope);
   if (!path)
@@ -379,7 +378,6 @@
                   .SetUpdateURL(std::vector<std::string>{url.spec()})
                   .SetUseCUP(false)
                   .SetInitialDelay(0.1)
-                  .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
                   .Overwrite());
 }
 
@@ -403,13 +401,23 @@
                     CheckInstallationVersions::kCheckActiveAndSxS);
 }
 
+void Install(UpdaterScope scope) {
+  const base::FilePath path = GetInstallerPath();
+  ASSERT_FALSE(path.empty());
+  base::CommandLine command_line(path);
+  command_line.AppendSwitch(kInstallSwitch);
+  int exit_code = -1;
+  ASSERT_TRUE(Run(scope, command_line, &exit_code));
+  EXPECT_EQ(0, exit_code);
+}
+
 void Uninstall(UpdaterScope scope) {
   // Note: updater_test.exe --uninstall is run from the build dir, not the
   // install dir, because it is useful for tests to be able to run it to clean
   // the system even if installation has failed or the installed binaries have
   // already been removed.
   base::FilePath path =
-      GetSetupExecutablePath().DirName().AppendASCII("updater_test.exe");
+      GetInstallerPath().DirName().AppendASCII("updater_test.exe");
   ASSERT_FALSE(path.empty());
   base::CommandLine command_line(path);
   command_line.AppendSwitch("uninstall");
@@ -794,10 +802,6 @@
   EXPECT_EQ(RunVPythonCommand(command), 0);
 }
 
-void SetupRealUpdaterLowerVersion(UpdaterScope scope) {
-  // TODO(crbug.com/1268555): Implement.
-}
-
 void RunUninstallCmdLine(UpdaterScope scope) {
   HKEY root =
       (scope == UpdaterScope::kSystem) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
diff --git a/chromecast/cast_core/grpc/BUILD.gn b/chromecast/cast_core/grpc/BUILD.gn
index 1f22491..f06ba2b 100644
--- a/chromecast/cast_core/grpc/BUILD.gn
+++ b/chromecast/cast_core/grpc/BUILD.gn
@@ -3,27 +3,15 @@
 # found in the LICENSE file.
 
 import("//chromecast/chromecast.gni")
-import("//third_party/grpc/grpc_library.gni")
+import("//third_party/cast_core/public/src/proto/proto.gni")
 
-cast_source_set("calls") {
+cast_source_set("grpc") {
   sources = [
     "grpc_call.h",
     "grpc_client_reactor.h",
     "grpc_server_streaming_call.h",
     "grpc_stub.h",
     "grpc_unary_call.h",
-  ]
-
-  public_deps = [
-    ":grpc_call_options",
-    ":grpc_status_or",
-    "//base",
-    "//third_party/grpc:grpc++",
-  ]
-}
-
-cast_source_set("handlers") {
-  sources = [
     "cancellable_reactor.h",
     "grpc_handler.cc",
     "grpc_handler.h",
@@ -35,39 +23,16 @@
     "server_reactor_tracker.cc",
     "server_reactor_tracker.h",
     "trackable_reactor.h",
-  ]
-
-  public_deps = [
-    ":grpc_call_options",
-    ":grpc_status_or",
-    "//base",
-    "//third_party/abseil-cpp:absl",
-    "//third_party/grpc:grpc++",
-  ]
-}
-
-cast_source_set("grpc_call_options") {
-  sources = [
     "grpc_call_options.cc",
     "grpc_call_options.h",
-  ]
-
-  public_deps = [
-    "//base",
-    "//third_party/grpc:grpc++",
-  ]
-}
-
-cast_source_set("grpc_status_or") {
-  sources = [
     "grpc_status_or.cc",
     "grpc_status_or.h",
   ]
 
   public_deps = [
     "//base",
-    "//third_party/abseil-cpp:absl",
     "//third_party/grpc:grpc++",
+    "//third_party/abseil-cpp:absl",
   ]
 }
 
@@ -82,31 +47,19 @@
   deps = [ "//testing/gmock" ]
 }
 
-grpc_library("test_service_proto") {
-  sources = [ "test_service.proto" ]
+cast_core_proto_library("test_service_messages_proto") {
+  sources = [ "test_service_messages.proto" ]
 }
 
-cast_source_set("test_service_handlers") {
-  testonly = true
-
+cast_core_grpc_library("test_service_proto") {
   sources = [
-    "test_service_handlers.cc",
-    "test_service_handlers.h",
+    "test_service.proto",
+    "test_service_extra.proto",
   ]
 
-  deps = [
-    ":handlers",
-    ":test_service_proto",
-  ]
-}
+  deps = [ ":test_service_messages_proto" ]
 
-cast_source_set("test_service_stubs") {
-  sources = [ "test_service_stubs.h" ]
-
-  deps = [
-    ":calls",
-    ":test_service_proto",
-  ]
+  generate_castcore_stubs = true
 }
 
 cast_source_set("unit_tests") {
@@ -119,12 +72,9 @@
   ]
 
   deps = [
-    ":calls",
-    ":handlers",
+    ":grpc",
     ":status_matchers",
-    ":test_service_handlers",
-    ":test_service_proto",
-    ":test_service_stubs",
+    ":test_service_proto_castcore",
     "//base",
     "//base/test:test_support",
     "//testing/gmock",
diff --git a/chromecast/cast_core/grpc/grpc_server_streaming_test.cc b/chromecast/cast_core/grpc/grpc_server_streaming_test.cc
index 286e22f..e025579c 100644
--- a/chromecast/cast_core/grpc/grpc_server_streaming_test.cc
+++ b/chromecast/cast_core/grpc/grpc_server_streaming_test.cc
@@ -14,9 +14,7 @@
 #include "base/time/time.h"
 #include "chromecast/cast_core/grpc/grpc_server.h"
 #include "chromecast/cast_core/grpc/status_matchers.h"
-#include "chromecast/cast_core/grpc/test_service.grpc.pb.h"
-#include "chromecast/cast_core/grpc/test_service_handlers.h"
-#include "chromecast/cast_core/grpc/test_service_stubs.h"
+#include "chromecast/cast_core/grpc/test_service.castcore.pb.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chromecast/cast_core/grpc/grpc_unary_test.cc b/chromecast/cast_core/grpc/grpc_unary_test.cc
index f4b7d51..652e180 100644
--- a/chromecast/cast_core/grpc/grpc_unary_test.cc
+++ b/chromecast/cast_core/grpc/grpc_unary_test.cc
@@ -17,9 +17,14 @@
 #include "base/time/time.h"
 #include "chromecast/cast_core/grpc/grpc_server.h"
 #include "chromecast/cast_core/grpc/status_matchers.h"
+// Include all various protobuf headers generated by Cast Core rules.
+#include "chromecast/cast_core/grpc/test_service.castcore.pb.h"
 #include "chromecast/cast_core/grpc/test_service.grpc.pb.h"
-#include "chromecast/cast_core/grpc/test_service_handlers.h"
-#include "chromecast/cast_core/grpc/test_service_stubs.h"
+#include "chromecast/cast_core/grpc/test_service.pb.h"
+// Include all various protobuf headers generated by Cast Core rules.
+#include "chromecast/cast_core/grpc/test_service_extra.castcore.pb.h"
+#include "chromecast/cast_core/grpc/test_service_extra.grpc.pb.h"
+#include "chromecast/cast_core/grpc/test_service_extra.pb.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -117,10 +122,7 @@
   ASSERT_THAT(response, StatusIs(grpc::StatusCode::UNAVAILABLE));
 
   // Need to wait for server to fully stop.
-  {
-    base::ScopedAllowBaseSyncPrimitivesForTesting allow_base_sync_primitives;
-    ASSERT_TRUE(server_stopped_event.TimedWait(kEventTimeout));
-  }
+  ASSERT_TRUE(server_stopped_event.TimedWait(kEventTimeout));
 }
 
 TEST_F(GrpcUnaryTest, AsyncUnaryCallSucceeds) {
@@ -221,6 +223,29 @@
   }
 }
 
+TEST_F(GrpcUnaryTest, SyncUnaryCallSucceedsExtra) {
+  GrpcServer server;
+  server.SetHandler<SimpleServiceExtraHandler::SimpleCall>(
+      base::BindLambdaForTesting(
+          [](TestExtraRequest request,
+             SimpleServiceExtraHandler::SimpleCall::Reactor* reactor) {
+            EXPECT_EQ(request.extra(), "test_extra");
+            TestResponse response;
+            response.set_bar("test_bar");
+            reactor->Write(std::move(response));
+          }));
+  server.Start(endpoint_);
+
+  SimpleServiceExtraStub stub(endpoint_);
+  auto call = stub.CreateCall<SimpleServiceExtraStub::SimpleCall>();
+  call.request().set_extra("test_extra");
+  auto response = std::move(call).Invoke();
+  CU_ASSERT_OK(response);
+  EXPECT_EQ(response->bar(), "test_bar");
+
+  server.StopForTesting(kServerStopTimeout);
+}
+
 }  // namespace
 }  // namespace utils
 }  // namespace cast
diff --git a/chromecast/cast_core/grpc/test_service.proto b/chromecast/cast_core/grpc/test_service.proto
index 7b049143b..ab33bf3 100644
--- a/chromecast/cast_core/grpc/test_service.proto
+++ b/chromecast/cast_core/grpc/test_service.proto
@@ -8,6 +8,8 @@
 
 option optimize_for = LITE_RUNTIME;
 
+import "chromecast/cast_core/grpc/test_service_messages.proto";
+
 // An unary test service.
 service SimpleService {
   // Makes an unary test call.
@@ -20,12 +22,7 @@
   rpc StreamingCall(TestRequest) returns (stream TestResponse);
 }
 
-// Test request.
-message TestRequest {
-  string foo = 1;
-}
-
-// Test response.
-message TestResponse {
-  string bar = 1;
-}
+// The request is added here to verify the build rules.
+message TestExtraRequest {
+  string extra = 1;
+};
diff --git a/chromecast/cast_core/grpc/test_service_extra.proto b/chromecast/cast_core/grpc/test_service_extra.proto
new file mode 100644
index 0000000..3d0a694
--- /dev/null
+++ b/chromecast/cast_core/grpc/test_service_extra.proto
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto3";
+
+package cast.utils;
+
+import "chromecast/cast_core/grpc/test_service.proto";
+import "chromecast/cast_core/grpc/test_service_messages.proto";
+
+option optimize_for = LITE_RUNTIME;
+
+// An unary test service.
+service SimpleServiceExtra {
+  // Makes an unary test call.
+  rpc SimpleCall(TestExtraRequest) returns (TestResponse);
+}
diff --git a/chromecast/cast_core/grpc/test_service_handlers.cc b/chromecast/cast_core/grpc/test_service_handlers.cc
deleted file mode 100644
index a7c2bca..0000000
--- a/chromecast/cast_core/grpc/test_service_handlers.cc
+++ /dev/null
@@ -1,15 +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 "chromecast/cast_core/grpc/test_service_handlers.h"
-
-namespace cast {
-namespace utils {
-
-const char SimpleServiceHandler::kSimpleCall[] = "SimpleCall";
-
-const char ServerStreamingServiceHandler::kStreamingCall[] = "StreamingCall";
-
-}  // namespace utils
-}  // namespace cast
diff --git a/chromecast/cast_core/grpc/test_service_handlers.h b/chromecast/cast_core/grpc/test_service_handlers.h
deleted file mode 100644
index d4280ee..0000000
--- a/chromecast/cast_core/grpc/test_service_handlers.h
+++ /dev/null
@@ -1,40 +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 CHROMECAST_CAST_CORE_GRPC_TEST_SERVICE_HANDLERS_H_
-#define CHROMECAST_CAST_CORE_GRPC_TEST_SERVICE_HANDLERS_H_
-
-#include "chromecast/cast_core/grpc/grpc_server_streaming_handler.h"
-#include "chromecast/cast_core/grpc/grpc_unary_handler.h"
-#include "chromecast/cast_core/grpc/test_service.grpc.pb.h"
-#include "chromecast/cast_core/grpc/test_service.pb.h"
-
-namespace cast {
-namespace utils {
-
-class SimpleServiceHandler {
- private:
-  static const char kSimpleCall[];
-
- public:
-  using SimpleCall = utils::
-      GrpcUnaryHandler<SimpleService, TestRequest, TestResponse, kSimpleCall>;
-};
-
-class ServerStreamingServiceHandler {
- private:
-  static const char kStreamingCall[];
-
- public:
-  using StreamingCall =
-      utils::GrpcServerStreamingHandler<ServerStreamingService,
-                                        TestRequest,
-                                        TestResponse,
-                                        kStreamingCall>;
-};
-
-}  // namespace utils
-}  // namespace cast
-
-#endif  // CHROMECAST_CAST_CORE_GRPC_TEST_SERVICE_HANDLERS_H_
diff --git a/chromecast/cast_core/grpc/test_service_messages.proto b/chromecast/cast_core/grpc/test_service_messages.proto
new file mode 100644
index 0000000..b0ecc8f
--- /dev/null
+++ b/chromecast/cast_core/grpc/test_service_messages.proto
@@ -0,0 +1,19 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto3";
+
+package cast.utils;
+
+option optimize_for = LITE_RUNTIME;
+
+// Test request.
+message TestRequest {
+  string foo = 1;
+}
+
+// Test response.
+message TestResponse {
+  string bar = 1;
+}
diff --git a/chromecast/cast_core/grpc/test_service_stubs.h b/chromecast/cast_core/grpc/test_service_stubs.h
deleted file mode 100644
index 217da5a..0000000
--- a/chromecast/cast_core/grpc/test_service_stubs.h
+++ /dev/null
@@ -1,51 +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 CHROMECAST_CAST_CORE_GRPC_TEST_SERVICE_STUBS_H_
-#define CHROMECAST_CAST_CORE_GRPC_TEST_SERVICE_STUBS_H_
-
-#include "chromecast/cast_core/grpc/grpc_server_streaming_call.h"
-#include "chromecast/cast_core/grpc/grpc_stub.h"
-#include "chromecast/cast_core/grpc/grpc_unary_call.h"
-#include "chromecast/cast_core/grpc/test_service.grpc.pb.h"
-#include "chromecast/cast_core/grpc/test_service.pb.h"
-
-namespace cast {
-namespace utils {
-
-class SimpleServiceStub final : public utils::GrpcStub<SimpleService> {
- public:
-  using GrpcStub::GrpcStub;
-  using GrpcStub::operator=;
-  using GrpcStub::AsyncInterface;
-  using GrpcStub::CreateCall;
-  using GrpcStub::SyncInterface;
-
-  using SimpleCall = utils::GrpcUnaryCall<SimpleServiceStub,
-                                          TestRequest,
-                                          TestResponse,
-                                          &AsyncInterface::SimpleCall,
-                                          &SyncInterface::SimpleCall>;
-};
-
-class ServerStreamingServiceStub
-    : public utils::GrpcStub<ServerStreamingService> {
- public:
-  using GrpcStub::GrpcStub;
-  using GrpcStub::operator=;
-  using GrpcStub::AsyncInterface;
-  using GrpcStub::CreateCall;
-  using GrpcStub::SyncInterface;
-
-  using StreamingCall =
-      utils::GrpcServerStreamingCall<ServerStreamingServiceStub,
-                                     TestRequest,
-                                     TestResponse,
-                                     &AsyncInterface::StreamingCall>;
-};
-
-}  // namespace utils
-}  // namespace cast
-
-#endif  // CHROMECAST_CAST_CORE_GRPC_TEST_SERVICE_STUBS_H_
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index 4ac492b..6812d3e 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-99-4815.0-1642417637-benchmark-99.0.4835.0-r1.orderfile.xz
+chromeos-chrome-orderfile-field-99-4815.0-1642417637-benchmark-99.0.4837.0-r1.orderfile.xz
diff --git a/chromeos/services/bluetooth_config/discovery_session_manager.cc b/chromeos/services/bluetooth_config/discovery_session_manager.cc
index f630da8f..92ad635 100644
--- a/chromeos/services/bluetooth_config/discovery_session_manager.cc
+++ b/chromeos/services/bluetooth_config/discovery_session_manager.cc
@@ -39,8 +39,10 @@
   mojo::RemoteSetElementId id = delegates_.Add(std::move(delegate));
 
   // The number of clients has increased from 0 to 1.
-  if (!had_client_before_call)
+  if (!had_client_before_call) {
     OnHasAtLeastOneDiscoveryClientChanged();
+    return;
+  }
 
   // If discovery is already active, notify the delegate that discovery has
   // started and of the current discovered devices list.
diff --git a/chromeos/services/bluetooth_config/discovery_session_manager_impl_unittest.cc b/chromeos/services/bluetooth_config/discovery_session_manager_impl_unittest.cc
index 8ce64d1a..fe23acf0 100644
--- a/chromeos/services/bluetooth_config/discovery_session_manager_impl_unittest.cc
+++ b/chromeos/services/bluetooth_config/discovery_session_manager_impl_unittest.cc
@@ -67,6 +67,10 @@
                    StartScanCallback& callback) {
               EXPECT_FALSE(start_scan_callback_);
               start_scan_callback_ = std::move(callback);
+
+              if (should_synchronously_invoke_start_scan_callback_) {
+                InvokePendingStartScanCallback(/*success=*/true);
+              }
             }));
     ON_CALL(*mock_adapter_, StopScan(testing::_))
         .WillByDefault(testing::Invoke([this](StopScanCallback callback) {
@@ -163,6 +167,10 @@
     discovery_session_manager_->FlushForTesting();
   }
 
+  void SetShouldSynchronouslyInvokeStartScanCallback(bool should) {
+    should_synchronously_invoke_start_scan_callback_ = should;
+  }
+
  private:
   class FakeDevicePairingHandlerFactory
       : public DevicePairingHandlerImpl::Factory {
@@ -226,6 +234,7 @@
   std::vector<NiceMockDevice> mock_devices_;
   size_t num_devices_created_ = 0u;
 
+  bool should_synchronously_invoke_start_scan_callback_ = false;
   StartScanCallback start_scan_callback_;
   StopScanCallback stop_scan_callback_;
 
@@ -463,5 +472,25 @@
   InvokePendingStopScanCallback(/*success=*/true);
 }
 
+TEST_F(DiscoverySessionManagerImplTest, StartDiscoverySynchronous) {
+  // Simulate adapter finishing starting scanning immediately. This should cause
+  // |delegate.OnBluetoothDiscoveryStarted()| still to only be called once.
+  SetShouldSynchronouslyInvokeStartScanCallback(true);
+  std::unique_ptr<FakeBluetoothDiscoveryDelegate> delegate = StartDiscovery();
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
+  EXPECT_EQ(1u, delegate->num_start_callbacks());
+  EXPECT_TRUE(delegate->discovered_devices_list().empty());
+
+  // Disconnect the Mojo pipe; this should trigger a StopScan() call.
+  delegate->DisconnectMojoPipe();
+  EXPECT_FALSE(delegate->IsMojoPipeConnected());
+  EXPECT_EQ(1u, delegate->num_start_callbacks());
+
+  // Invoke the StopScan() callback. Since the delegate was already
+  // disconnected, it should not have received a callback.
+  InvokePendingStopScanCallback(/*success=*/true);
+  EXPECT_EQ(0u, delegate->num_stop_callbacks());
+}
+
 }  // namespace bluetooth_config
 }  // namespace chromeos
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 0c44ad51..129b50b 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -355,6 +355,7 @@
       "//components/webapps/browser:unit_tests",
       "//components/webapps/services/web_app_origin_association:unit_tests",
       "//components/webcrypto:unit_tests",
+      "//components/webrtc:unit_tests",
       "//components/webrtc_logging:unit_tests",
       "//components/webrtc_logging/browser:unit_tests",
       "//components/webrtc_logging/common:unit_tests",
diff --git a/components/exo/pointer.cc b/components/exo/pointer.cc
index ef2c16f0..4aab04d 100644
--- a/components/exo/pointer.cc
+++ b/components/exo/pointer.cc
@@ -144,6 +144,10 @@
     VLOG(1) << "Pointer constraint broken by pointer destruction";
     pointer_constraint_delegate_->OnConstraintBroken();
   }
+  for (auto it : constraints_) {
+    it.first->RemoveSurfaceObserver(this);
+    it.second->OnDefunct();
+  }
   if (stylus_delegate_)
     stylus_delegate_->OnPointerDestroying(this);
   // TODO(sky): CursorClient does not exist in mash
@@ -243,18 +247,37 @@
   // lock support unless we are on chromeos.
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   Surface* constrained_surface = delegate->GetConstrainedSurface();
-  if (!constrained_surface)
+  if (!constrained_surface) {
+    delegate->OnDefunct();
     return false;
+  }
   // Pointer lock should be enabled for ARC by default. The kExoPointerLock
   // should only apply to Crostini windows.
   bool is_arc_window =
       ash::IsArcWindow(constrained_surface->window()->GetToplevelWindow());
   if (!is_arc_window &&
-      !base::FeatureList::IsEnabled(chromeos::features::kExoPointerLock))
+      !base::FeatureList::IsEnabled(chromeos::features::kExoPointerLock)) {
+    delegate->OnDefunct();
     return false;
+  }
+
+  // Can only have one active constraint request per surface
+  auto result = constraints_.try_emplace(constrained_surface, delegate);
+  if (result.first->second != delegate) {
+    VLOG(1) << "Pointer constraint not granted; one already exists.";
+    delegate->OnAlreadyConstrained();
+    delegate->OnDefunct();
+    return false;
+  }
+
+  if (!constrained_surface->HasSurfaceObserver(this))
+    constrained_surface->AddSurfaceObserver(this);
+
   bool success = EnablePointerCapture(constrained_surface);
-  if (success)
+  if (success) {
     pointer_constraint_delegate_ = delegate;
+    delegate->OnConstraintActivated();
+  }
   return success;
 #else
   NOTIMPLEMENTED();
@@ -262,14 +285,68 @@
 #endif
 }
 
-void Pointer::UnconstrainPointer() {
-  if (pointer_constraint_delegate_) {
-    DisablePointerCapture();
-    pointer_constraint_delegate_ = nullptr;
+bool Pointer::UnconstrainPointerByUserAction() {
+  // Prevent pointer capture until the next user action that permits it,
+  // even if a constraint is currently not active (to prevent an app from
+  // rapidly toggling pointer capture to evade such prevention).
+  capture_permitted_ = false;
+  UpdateCursor();  // forces the cursor to be visible in case the app hid it
+
+  if (pointer_constraint_delegate_ && capture_window_) {
+    VLOG(1) << "Pointer constraint broken by user action";
+    UnconstrainPointer();
+    return true;
+  } else {
+    VLOG(1) << "Pointer constraint forbidden by user (though none active now)";
+    return false;
   }
 }
 
+void Pointer::RemoveConstraintDelegate(PointerConstraintDelegate* delegate) {
+  delegate->OnDefunct();
+
+  Surface* surface = delegate->GetConstrainedSurface();
+  auto it = constraints_.find(surface);
+  if (it != constraints_.end() && it->second == delegate) {
+    constraints_.erase(it);
+    MaybeRemoveSurfaceObserver(surface);
+  }
+}
+
+void Pointer::UnconstrainPointer() {
+  if (pointer_constraint_delegate_) {
+    pointer_constraint_delegate_->OnConstraintBroken();
+    if (!pointer_constraint_delegate_->IsPersistent()) {
+      RemoveConstraintDelegate(pointer_constraint_delegate_);
+    }
+    pointer_constraint_delegate_ = nullptr;
+    DisablePointerCapture();
+  }
+}
+
+void Pointer::MaybeReactivatePointerConstraint(Surface* surface) {
+  if (!pointer_constraint_delegate_ && surface) {
+    auto it = constraints_.find(surface);
+    if (it != constraints_.end())
+      ConstrainPointer(it->second);
+  }
+}
+
+void Pointer::OnPointerConstraintDelegateDestroying(
+    PointerConstraintDelegate* delegate) {
+  if (pointer_constraint_delegate_ == delegate) {
+    DisablePointerCapture();
+    pointer_constraint_delegate_ = nullptr;
+  }
+  RemoveConstraintDelegate(delegate);
+}
+
 bool Pointer::EnablePointerCapture(Surface* capture_surface) {
+  if (!capture_permitted_) {
+    VLOG(1) << "Unable to re-capture the pointer due to previous user action.";
+    return false;
+  }
+
   if (!base::FeatureList::IsEnabled(kPointerCapture)) {
     LOG(WARNING) << "Unable to capture the pointer, feature is disabled.";
     return false;
@@ -278,13 +355,10 @@
   aura::Window* window = capture_surface->window();
   aura::Window* active_window = WMHelper::GetInstance()->GetActiveWindow();
   if (!active_window || !active_window->Contains(window)) {
-    LOG(ERROR) << "Cannot enable pointer capture on an inactive window.";
+    VLOG(1) << "Cannot enable pointer capture on an inactive window.";
     return false;
   }
 
-  if (!capture_surface->HasSurfaceObserver(this))
-    capture_surface->AddSurfaceObserver(this);
-
   capture_window_ = window;
 
   // Add a pre-target handler that can consume all mouse events before it gets
@@ -297,6 +371,8 @@
   if (ShouldMoveToCenter())
     MoveCursorToCenterOfActiveDisplay();
 
+  seat_->NotifyPointerCaptureEnabled(this, window);
+
   return true;
 }
 
@@ -314,9 +390,12 @@
                      : root->bounds().CenterPoint();
   root->MoveCursorTo(p);
 
+  aura::Window* window = capture_window_;
   capture_window_ = nullptr;
   location_when_pointer_capture_enabled_.reset();
   UpdateCursor();
+
+  seat_->NotifyPointerCaptureDisabled(this, window);
 }
 
 void Pointer::SetStylusDelegate(PointerStylusDelegate* delegate) {
@@ -347,24 +426,36 @@
 // SurfaceObserver overrides:
 
 void Pointer::OnSurfaceDestroying(Surface* surface) {
+  bool was_correctly_subscribed = false;
   if (surface && pointer_constraint_delegate_ &&
       surface == pointer_constraint_delegate_->GetConstrainedSurface()) {
     surface->RemoveSurfaceObserver(this);
     VLOG(1) << "Pointer constraint broken by surface destruction";
-    pointer_constraint_delegate_->OnConstraintBroken();
     UnconstrainPointer();
+    was_correctly_subscribed = true;
   }
-  if (surface && surface->window() == capture_window_)
+  if (surface && surface->window() == capture_window_) {
     DisablePointerCapture();
+    was_correctly_subscribed = true;
+  }
+
+  auto it = constraints_.find(surface);
+  if (it != constraints_.end()) {
+    it->second->OnDefunct();
+    constraints_.erase(it);
+    surface->RemoveSurfaceObserver(this);
+    was_correctly_subscribed = true;
+  }
+
   if (surface == focus_surface_) {
     SetFocus(nullptr, gfx::PointF(), 0);
-    return;
-  }
-  if (surface == root_surface()) {
+    was_correctly_subscribed = true;
+  } else if (surface == root_surface()) {
     UpdatePointerSurface(nullptr);
-    return;
+    was_correctly_subscribed = true;
   }
-  NOTREACHED();
+  DCHECK(was_correctly_subscribed);
+  DCHECK(!surface->HasSurfaceObserver(this));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -453,6 +544,15 @@
       seat_->AbortPendingDragOperation();
       [[fallthrough]];
     case ui::ET_MOUSE_PRESSED: {
+      if (!capture_permitted_) {
+        // Clicking any surface with a constraint delegate permits capture
+        auto it = constraints_.find(focus_surface_);
+        if (it != constraints_.end()) {
+          capture_permitted_ = true;
+          UpdateCursor();
+          ConstrainPointer(it->second);
+        }
+      }
       delegate_->OnPointerButton(event->time_stamp(),
                                  event->changed_button_flags(),
                                  event->type() == ui::ET_MOUSE_PRESSED);
@@ -627,12 +727,13 @@
   if (capture_window_ && capture_window_ != gained_focus) {
     if (pointer_constraint_delegate_) {
       VLOG(1) << "Pointer constraint broken by focus change";
-      pointer_constraint_delegate_->OnConstraintBroken();
       UnconstrainPointer();
     } else {
       DisablePointerCapture();
     }
   }
+  if (gained_focus)
+    MaybeReactivatePointerConstraint(Surface::AsSurface(gained_focus));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -657,10 +758,11 @@
   // First generate a leave event if we currently have a target in focus.
   if (focus_surface_) {
     delegate_->OnPointerLeave(focus_surface_);
-    focus_surface_->RemoveSurfaceObserver(this);
     // Require SetCursor() to be called and cursor to be re-defined in
     // response to each OnPointerEnter() call.
+    Surface* old_surface = focus_surface_;
     focus_surface_ = nullptr;
+    MaybeRemoveSurfaceObserver(old_surface);
     cursor_capture_weak_ptr_factory_.InvalidateWeakPtrs();
   }
   // Second generate an enter event if focus moved to a new surface.
@@ -680,12 +782,14 @@
     host_window()->SetTransform(gfx::Transform());
     if (host_window()->parent())
       host_window()->parent()->RemoveChild(host_window());
-    root_surface()->RemoveSurfaceObserver(this);
+    Surface* old_surface = root_surface();
     SetRootSurface(nullptr);
+    MaybeRemoveSurfaceObserver(old_surface);
   }
 
   if (surface) {
-    surface->AddSurfaceObserver(this);
+    if (!surface->HasSurfaceObserver(this))
+      surface->AddSurfaceObserver(this);
     // Note: Surface window needs to be added to the tree so we can take a
     // snapshot. Where in the tree is not important but we might as well use
     // the cursor container.
@@ -783,6 +887,12 @@
     cursor_.set_custom_hotspot(hotspot);
   }
 
+  // When pointer capture is broken, use the standard system cursor instead of
+  // the application-requested one. But we keep the app-requested cursor around
+  // for when capture becomes permitted again.
+  const ui::Cursor& cursor =
+      capture_permitted_ ? cursor_ : ui::mojom::CursorType::kPointer;
+
   // If there is a focused surface, update its widget as the views framework
   // expect that Widget knows the current cursor. Otherwise update the
   // cursor directly on CursorClient.
@@ -791,13 +901,13 @@
     do {
       views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
       if (widget) {
-        widget->SetCursor(cursor_);
+        widget->SetCursor(cursor);
         return;
       }
       window = window->parent();
     } while (window);
   } else {
-    cursor_client->SetCursor(cursor_);
+    cursor_client->SetCursor(cursor);
   }
 }
 
@@ -856,4 +966,21 @@
   return true;
 }
 
+bool Pointer::ShouldObserveSurface(Surface* surface) {
+  if (!surface)
+    return false;
+
+  if (surface == root_surface() || surface == focus_surface_ ||
+      constraints_.find(surface) != constraints_.end()) {
+    return true;
+  }
+  return false;
+}
+
+void Pointer::MaybeRemoveSurfaceObserver(Surface* surface) {
+  if (!ShouldObserveSurface(surface)) {
+    surface->RemoveSurfaceObserver(this);
+  }
+}
+
 }  // namespace exo
diff --git a/components/exo/pointer.h b/components/exo/pointer.h
index 3523f26..fc391019 100644
--- a/components/exo/pointer.h
+++ b/components/exo/pointer.h
@@ -100,21 +100,43 @@
   // Enable the pointer constraint on the given surface. Returns true if the
   // lock was granted, false otherwise.
   //
+  // The delegate must call OnPointerConstraintDelegateDestroying() upon/before
+  // being destroyed, regardless of the return value of ConstrainPointer(),
+  // unless PointerConstraintDelegate::OnDefunct() is called first.
+  //
   // TODO(crbug.com/957455): For legacy reasons, locking the pointer will also
   // hide the cursor.
   bool ConstrainPointer(PointerConstraintDelegate* delegate);
 
-  // Disable the pointer constraint. This is designed to be called by the
-  // delegate, so it does not call OnConstraintBroken(), which should be done
-  // separately if the constraint was broken by something other than the
-  // delegate.
-  void UnconstrainPointer();
+  // Notifies that |delegate| is being destroyed.
+  void OnPointerConstraintDelegateDestroying(
+      PointerConstraintDelegate* delegate);
+
+  // Disable the pointer constraint, notify the delegate, and do not permit
+  // the constraint to be re-established until the user acts on the surface
+  // (by clicking on it).
+  //
+  // Designed to be called by client code, on behalf of a user action to break
+  // the constraint.
+  //
+  // Returns true if an active pointer constraint was disabled.
+  bool UnconstrainPointerByUserAction();
 
   // Set the stylus delegate for handling stylus events.
   void SetStylusDelegate(PointerStylusDelegate* delegate);
   bool HasStylusDelegate() const;
 
  private:
+  // Remove |delegate| from |constraints_|.
+  void RemoveConstraintDelegate(PointerConstraintDelegate* delegate);
+
+  // Disable the pointer constraint and notify the delegate.
+  void UnconstrainPointer();
+
+  // Try to reactivate a pointer constraint previously requested for the given
+  // surface, if any.
+  void MaybeReactivatePointerConstraint(Surface* surface);
+
   // Capture the pointer for the given surface. Returns true iff the capture
   // succeeded.
   bool EnablePointerCapture(Surface* capture_surface);
@@ -165,6 +187,12 @@
       gfx::PointF location_in_target,
       const absl::optional<gfx::Vector2dF>& ordinal_motion);
 
+  // Whether this Pointer should observe the given |surface|.
+  bool ShouldObserveSurface(Surface* surface);
+
+  // Stop observing |surface| if it's no longer relevant.
+  void MaybeRemoveSurfaceObserver(Surface* surface);
+
   // The delegate instance that all events are dispatched to.
   PointerDelegate* const delegate_;
 
@@ -176,9 +204,13 @@
   // The delegate instance that relative movement events are dispatched to.
   RelativePointerDelegate* relative_pointer_delegate_ = nullptr;
 
-  // The delegate instance that controls when to lock/unlock this pointer.
+  // Delegate that owns the currently granted pointer lock, if any.
   PointerConstraintDelegate* pointer_constraint_delegate_ = nullptr;
 
+  // All delegates currently requesting a pointer locks, whether granted or
+  // not. Only one such request may exist per surface; others will be denied.
+  base::flat_map<Surface*, PointerConstraintDelegate*> constraints_;
+
   // The delegate instance that stylus/pen events are dispatched to.
   PointerStylusDelegate* stylus_delegate_ = nullptr;
 
@@ -199,6 +231,12 @@
   // this is not null.
   aura::Window* capture_window_ = nullptr;
 
+  // True if this pointer is permitted to be captured.
+  //
+  // Set false when a user action (except focus loss) breaks pointer capture.
+  // Set true when the user clicks in any Exo window.
+  bool capture_permitted_ = true;
+
   // The position of the pointer surface relative to the pointer location.
   gfx::Point hotspot_;
 
diff --git a/components/exo/pointer_constraint_delegate.h b/components/exo/pointer_constraint_delegate.h
index 9af8180..93dfa024 100644
--- a/components/exo/pointer_constraint_delegate.h
+++ b/components/exo/pointer_constraint_delegate.h
@@ -7,24 +7,42 @@
 
 namespace exo {
 
+class Pointer;
 class Surface;
 
 class PointerConstraintDelegate {
  public:
   virtual ~PointerConstraintDelegate() = default;
 
+  // Called when the lock is activated by the compositor.
+  //
+  // For non-persistent ("one-shot") locks, this may be called 0 or 1 times.
+  // For persistent locks, this may be called again after OnConstraintBroken().
+  virtual void OnConstraintActivated() = 0;
+
+  // Called when the lock is not activated by the compositor because a
+  // pointer constraint was already requested on this surface.
+  virtual void OnAlreadyConstrained() = 0;
+
   // Called when this lock is broken for any reason. Possibly:
   //  - A user action broke the lock.
   //  - The lock was granted to a different client.
   //  - The pointer was destroyed while the lock was active.
-  //
-  // No matter the case, this delegate no longer holds the lock and therefore
-  // should not call UnconstrainPointer().
   virtual void OnConstraintBroken() = 0;
 
-  // Callback to access the surface which this delegate wants to lock the
-  // curstor for.
+  // Whether the lock is "persistent", meaning it can be reactivated by the
+  // compositor after being broken.
+  virtual bool IsPersistent() = 0;
+
+  // Returns the surface which this delegate wants to lock the cursor for.
+  // The delegate does not guarantee that this pointer is valid, except
+  // when calling Pointer::ConstrainPointer(); the caller is responsible for
+  // tracking the Surface's lifetime.
   virtual Surface* GetConstrainedSurface() = 0;
+
+  // Notifies the delegate that it's defunct and must not call
+  // Pointer::OnPointerConstraintDelegateDestroying().
+  virtual void OnDefunct() = 0;
 };
 
 }  // namespace exo
diff --git a/components/exo/pointer_unittest.cc b/components/exo/pointer_unittest.cc
index 89e62df..427135e 100644
--- a/components/exo/pointer_unittest.cc
+++ b/components/exo/pointer_unittest.cc
@@ -82,12 +82,26 @@
 
 class MockPointerConstraintDelegate : public PointerConstraintDelegate {
  public:
-  MockPointerConstraintDelegate() = default;
+  MockPointerConstraintDelegate() {
+    ON_CALL(*this, OnConstraintActivated).WillByDefault([this]() {
+      activated_count++;
+    });
+    ON_CALL(*this, OnConstraintBroken).WillByDefault([this]() {
+      broken_count++;
+    });
+  }
   ~MockPointerConstraintDelegate() = default;
 
   // Overridden from PointerConstraintDelegate:
+  MOCK_METHOD0(OnConstraintActivated, void());
+  MOCK_METHOD0(OnAlreadyConstrained, void());
   MOCK_METHOD0(OnConstraintBroken, void());
+  MOCK_METHOD0(IsPersistent, bool());
   MOCK_METHOD0(GetConstrainedSurface, Surface*());
+  MOCK_METHOD0(OnDefunct, void());
+
+  int activated_count = 0;
+  int broken_count = 0;
 };
 
 class MockPointerStylusDelegate : public PointerStylusDelegate {
@@ -185,8 +199,8 @@
   std::unique_ptr<ui::test::EventGenerator> generator_;
   std::unique_ptr<Pointer> pointer_;
   std::unique_ptr<Seat> seat_;
-  MockPointerConstraintDelegate constraint_delegate_;
-  MockPointerDelegate delegate_;
+  testing::NiceMock<MockPointerConstraintDelegate> constraint_delegate_;
+  testing::NiceMock<MockPointerDelegate> delegate_;
   std::unique_ptr<ShellSurface> shell_surface_;
   Surface* surface_;
   base::test::ScopedFeatureList feature_list_;
@@ -1220,17 +1234,44 @@
   EXPECT_CALL(delegate_, OnPointerFrame());
   // Moving the cursor to a different surface should change the focus when
   // the pointer is unconstrained.
-  pointer_->UnconstrainPointer();
+  pointer_->UnconstrainPointerByUserAction();
   generator_->MoveMouseTo(
       child_surface->window()->GetBoundsInScreen().origin());
 
+  pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
   EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
   pointer_.reset();
 }
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-TEST_F(PointerConstraintTest, ConstrainPointerFailsWhenSurfaceIsNotActive) {
+TEST_F(PointerConstraintTest, OneConstraintPerSurface) {
+  ON_CALL(constraint_delegate_, IsPersistent())
+      .WillByDefault(testing::Return(false));
+  EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
+
+  EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
+  EXPECT_CALL(delegate_, OnPointerFrame()).Times(testing::AtLeast(1));
+  generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
+
+  // Add a second constraint for the same surface, it should fail.
+  MockPointerConstraintDelegate second_constraint;
+  EXPECT_CALL(second_constraint, GetConstrainedSurface())
+      .WillRepeatedly(testing::Return(surface_));
+  ON_CALL(second_constraint, IsPersistent())
+      .WillByDefault(testing::Return(false));
+  EXPECT_CALL(second_constraint, OnAlreadyConstrained());
+  EXPECT_CALL(second_constraint, OnDefunct());
+  EXPECT_FALSE(pointer_->ConstrainPointer(&second_constraint));
+
+  pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
+  EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
+  pointer_.reset();
+}
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(PointerConstraintTest, OneShotConstraintActivatedOnFirstFocus) {
   auto second_shell_surface =
       test::ShellSurfaceBuilder({10, 10}).BuildShellSurface();
   Surface* second_surface = second_shell_surface->surface_for_testing();
@@ -1238,19 +1279,22 @@
   EXPECT_CALL(delegate_, CanAcceptPointerEventsForSurface(second_surface))
       .WillRepeatedly(testing::Return(true));
 
-  // Setting the focused window also makes it activated.
   focus_client_->FocusWindow(second_surface->window());
-  EXPECT_FALSE(pointer_->ConstrainPointer(&constraint_delegate_));
 
+  // Assert: Can no longer activate the constraint on the first surface.
+  EXPECT_FALSE(pointer_->ConstrainPointer(&constraint_delegate_));
+  EXPECT_EQ(constraint_delegate_.activated_count, 0);
+
+  // Assert: Constraint is activated when first surface gains focus.
   focus_client_->FocusWindow(surface_->window());
-  EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
+  EXPECT_EQ(constraint_delegate_.activated_count, 1);
 
   EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
   EXPECT_CALL(delegate_, OnPointerFrame());
   generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
 
-  pointer_->UnconstrainPointer();
-
+  // Teardown
+  pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
   EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
   pointer_.reset();
 }
@@ -1277,6 +1321,29 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 TEST_F(PointerConstraintTest, UnconstrainPointerWhenWindowLosesFocus) {
+  ON_CALL(constraint_delegate_, IsPersistent())
+      .WillByDefault(testing::Return(false));
+  EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
+
+  EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
+  EXPECT_CALL(delegate_, OnPointerFrame());
+  generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
+
+  EXPECT_CALL(constraint_delegate_, OnConstraintBroken());
+  EXPECT_CALL(constraint_delegate_, OnConstraintActivated()).Times(0);
+  focus_client_->FocusWindow(nullptr);
+  focus_client_->FocusWindow(surface_->window());
+
+  pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
+  EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
+  pointer_.reset();
+}
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(PointerConstraintTest, PersistentConstraintActivatedOnRefocus) {
+  ON_CALL(constraint_delegate_, IsPersistent())
+      .WillByDefault(testing::Return(true));
   EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
 
   EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
@@ -1285,7 +1352,129 @@
 
   EXPECT_CALL(constraint_delegate_, OnConstraintBroken());
   focus_client_->FocusWindow(nullptr);
+  EXPECT_CALL(constraint_delegate_, OnConstraintActivated());
+  focus_client_->FocusWindow(surface_->window());
 
+  pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
+  EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
+  pointer_.reset();
+}
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(PointerConstraintTest, MultipleSurfacesCanBeConstrained) {
+  // Arrange: First surface + persistent constraint
+  ON_CALL(constraint_delegate_, IsPersistent())
+      .WillByDefault(testing::Return(true));
+  EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
+
+  EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
+  EXPECT_CALL(delegate_, OnPointerFrame());
+  generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
+
+  EXPECT_EQ(constraint_delegate_.activated_count, 1);
+
+  // Arrange: Second surface + persistent constraint
+  auto second_shell_surface =
+      test::ShellSurfaceBuilder({10, 10}).BuildShellSurface();
+  Surface* second_surface = second_shell_surface->surface_for_testing();
+  focus_client_->FocusWindow(second_surface->window());
+  EXPECT_CALL(delegate_, CanAcceptPointerEventsForSurface(second_surface))
+      .WillRepeatedly(testing::Return(true));
+  testing::NiceMock<MockPointerConstraintDelegate> second_constraint;
+  EXPECT_CALL(second_constraint, GetConstrainedSurface())
+      .WillRepeatedly(testing::Return(second_surface));
+  ON_CALL(second_constraint, IsPersistent())
+      .WillByDefault(testing::Return(true));
+  EXPECT_TRUE(pointer_->ConstrainPointer(&second_constraint));
+
+  EXPECT_EQ(constraint_delegate_.activated_count, 1);
+  EXPECT_EQ(second_constraint.activated_count, 1);
+
+  // Act: Toggle focus, first surface's constraint should activate.
+  focus_client_->FocusWindow(surface_->window());
+
+  EXPECT_EQ(constraint_delegate_.activated_count, 2);
+  EXPECT_EQ(second_constraint.activated_count, 1);
+
+  // Act: Toggle focus, second surface's constraint should activate.
+  focus_client_->FocusWindow(second_surface->window());
+
+  EXPECT_EQ(constraint_delegate_.activated_count, 2);
+  EXPECT_EQ(second_constraint.activated_count, 2);
+
+  pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
+  pointer_->OnPointerConstraintDelegateDestroying(&second_constraint);
+  EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
+  pointer_.reset();
+}
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(PointerConstraintTest, UserActionPreventsConstraint) {
+  ON_CALL(constraint_delegate_, IsPersistent())
+      .WillByDefault(testing::Return(false));
+  EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
+
+  EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
+  EXPECT_CALL(delegate_, OnPointerFrame()).Times(testing::AtLeast(1));
+  generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
+
+  EXPECT_CALL(constraint_delegate_, OnConstraintBroken());
+  pointer_->UnconstrainPointerByUserAction();
+
+  // New constraints are no longer permitted.
+  MockPointerConstraintDelegate second_constraint;
+  EXPECT_CALL(second_constraint, GetConstrainedSurface())
+      .WillRepeatedly(testing::Return(surface_));
+  ON_CALL(second_constraint, IsPersistent())
+      .WillByDefault(testing::Return(false));
+  EXPECT_FALSE(pointer_->ConstrainPointer(&second_constraint));
+  EXPECT_EQ(second_constraint.activated_count, 0);
+
+  // A click event will activate the pending constraint.
+  generator_->ClickLeftButton();
+  EXPECT_EQ(second_constraint.activated_count, 1);
+
+  pointer_->OnPointerConstraintDelegateDestroying(&second_constraint);
+
+  // New constraints are now permitted too.
+  MockPointerConstraintDelegate third_constraint;
+  EXPECT_CALL(third_constraint, GetConstrainedSurface())
+      .WillRepeatedly(testing::Return(surface_));
+  ON_CALL(third_constraint, IsPersistent())
+      .WillByDefault(testing::Return(false));
+  EXPECT_TRUE(pointer_->ConstrainPointer(&third_constraint));
+  pointer_->OnPointerConstraintDelegateDestroying(&third_constraint);
+
+  pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
+  EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
+  pointer_.reset();
+}
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(PointerConstraintTest, UserCanBreakAndActivatePersistentConstraint) {
+  ON_CALL(constraint_delegate_, IsPersistent())
+      .WillByDefault(testing::Return(true));
+  EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
+  EXPECT_EQ(constraint_delegate_.activated_count, 1);
+  EXPECT_EQ(constraint_delegate_.broken_count, 0);
+
+  EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
+  EXPECT_CALL(delegate_, OnPointerFrame()).Times(testing::AtLeast(1));
+  generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
+
+  EXPECT_CALL(constraint_delegate_, OnConstraintBroken());
+  pointer_->UnconstrainPointerByUserAction();
+  EXPECT_EQ(constraint_delegate_.activated_count, 1);
+  EXPECT_EQ(constraint_delegate_.broken_count, 1);
+
+  // Click events re-enable the constraint.
+  generator_->ClickLeftButton();
+  EXPECT_EQ(constraint_delegate_.activated_count, 2);
+
+  pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
   EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
   pointer_.reset();
 }
diff --git a/components/exo/seat.cc b/components/exo/seat.cc
index 8fe755a..9143fb1 100644
--- a/components/exo/seat.cc
+++ b/components/exo/seat.cc
@@ -105,6 +105,22 @@
     observer_list.RemoveObserver(observer);
 }
 
+void Seat::NotifyPointerCaptureEnabled(Pointer* pointer,
+                                       aura::Window* capture_window) {
+  for (auto& observer_list : priority_observer_list_) {
+    for (auto& observer : observer_list)
+      observer.OnPointerCaptureEnabled(pointer, capture_window);
+  }
+}
+
+void Seat::NotifyPointerCaptureDisabled(Pointer* pointer,
+                                        aura::Window* capture_window) {
+  for (auto& observer_list : priority_observer_list_) {
+    for (auto& observer : observer_list)
+      observer.OnPointerCaptureDisabled(pointer, capture_window);
+  }
+}
+
 Surface* Seat::GetFocusedSurface() {
   return GetTargetSurfaceForKeyboardFocus(
       WMHelper::GetInstance()->GetFocusedWindow());
diff --git a/components/exo/seat.h b/components/exo/seat.h
index e91b1f1..619eda3 100644
--- a/components/exo/seat.h
+++ b/components/exo/seat.h
@@ -37,6 +37,7 @@
 namespace exo {
 class DragDropOperation;
 class DataExchangeDelegate;
+class Pointer;
 class ScopedDataSource;
 class SeatObserver;
 class Surface;
@@ -81,7 +82,13 @@
     return 0 <= priority && priority <= kMaxObserverPriority;
   }
 
-  // Returns currently focused surface. This is vertual so that we can override
+  // Notify observers about pointer capture state changes.
+  void NotifyPointerCaptureEnabled(Pointer* pointer,
+                                   aura::Window* capture_window);
+  void NotifyPointerCaptureDisabled(Pointer* pointer,
+                                    aura::Window* capture_window);
+
+  // Returns currently focused surface. This is virtual so that we can override
   // the behavior for testing.
   virtual Surface* GetFocusedSurface();
 
diff --git a/components/exo/seat_observer.h b/components/exo/seat_observer.h
index 0ca00ed..3d9fcc84 100644
--- a/components/exo/seat_observer.h
+++ b/components/exo/seat_observer.h
@@ -5,8 +5,13 @@
 #ifndef COMPONENTS_EXO_SEAT_OBSERVER_H_
 #define COMPONENTS_EXO_SEAT_OBSERVER_H_
 
+namespace aura {
+class Window;
+}
+
 namespace exo {
 
+class Pointer;
 class Surface;
 
 // Observers can listen to various events on the Seats.
@@ -17,6 +22,14 @@
                                 Surface* lost_focus,
                                 bool has_focused_client) = 0;
 
+  // Called when a pointer is captured by the given window.
+  virtual void OnPointerCaptureEnabled(Pointer* pointer,
+                                       aura::Window* capture_window) {}
+
+  // Called when the given pointer is no longer captured by the given window.
+  virtual void OnPointerCaptureDisabled(Pointer* pointer,
+                                        aura::Window* capture_window) {}
+
  protected:
   virtual ~SeatObserver() = default;
 };
diff --git a/components/exo/wayland/zwp_pointer_constraints.cc b/components/exo/wayland/zwp_pointer_constraints.cc
index 452b5d6..8bcda6b 100644
--- a/components/exo/wayland/zwp_pointer_constraints.cc
+++ b/components/exo/wayland/zwp_pointer_constraints.cc
@@ -21,6 +21,14 @@
 
 namespace {
 
+// Implements a PointerConstraintDelegate in terms of the zwp_locked_pointer
+// Wayland protocol.
+//
+// Lifetime note: The underlying Wayland protocol gives control over this
+// object's lifetime to the client. However, it's possible that its
+// dependencies could be destroyed prior to the client destroying it.
+// At this point we consider the object "defunct" and its |surface_| member
+// to be potentially dangling. |pointer_| is correctly nulled when appropriate.
 class WaylandPointerConstraintDelegate : public PointerConstraintDelegate {
  public:
   WaylandPointerConstraintDelegate(wl_resource* constraint_resource,
@@ -30,43 +38,56 @@
                                    uint32_t lifetime)
       : constraint_resource_(constraint_resource),
         pointer_(pointer),
-        surface_(surface) {
-    if (pointer->ConstrainPointer(this))
-      EnableConstraint();
-    else
-      pointer_ = nullptr;
+        surface_(surface),
+        is_persistent_(lifetime ==
+                       ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT) {
+    pointer->ConstrainPointer(this);
   }
 
   ~WaylandPointerConstraintDelegate() override {
-    if (pointer_)
-      pointer_->UnconstrainPointer();
+    if (pointer_) {
+      pointer_->OnPointerConstraintDelegateDestroying(this);
+      pointer_ = nullptr;
+    }
   }
 
-  void OnConstraintBroken() override {
-    DisableConstraint();
-    pointer_ = nullptr;
+  // PointerConstraintDelegate::
+  void OnConstraintActivated() override { SendLocked(); }
+  void OnAlreadyConstrained() override {
+    wl_resource_post_error(
+        constraint_resource_,
+        ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED,
+        "A pointer constraint was already requested for this wl_pointer "
+        "on this wl_surface.");
   }
-
+  void OnConstraintBroken() override { SendUnlocked(); }
+  bool IsPersistent() override { return is_persistent_; }
   Surface* GetConstrainedSurface() override { return surface_; }
+  void OnDefunct() override { pointer_ = nullptr; }
 
  private:
-  void EnableConstraint() {
+  // Inform the client of the state of the lock.
+  void SendLocked() {
+    VLOG(1) << "send_locked(" << constraint_resource_ << ")";
     zwp_locked_pointer_v1_send_locked(constraint_resource_);
   }
 
-  void DisableConstraint() {
+  void SendUnlocked() {
+    VLOG(1) << "send_unlocked(" << constraint_resource_ << ")";
     zwp_locked_pointer_v1_send_unlocked(constraint_resource_);
   }
 
-  wl_resource* constraint_resource_;
+  wl_resource* const constraint_resource_;
   Pointer* pointer_;
-  Surface* surface_;
+  Surface* const surface_;
+  bool is_persistent_;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 // zwp_locked_pointer
 
 void locked_pointer_destroy(wl_client* client, wl_resource* resource) {
+  VLOG(1) << "locked_pointer_destroy(" << client << ", " << resource << ")";
   wl_resource_destroy(resource);
 }
 
@@ -110,6 +131,8 @@
 // zwp_pointer_constraints
 
 void pointer_constraints_destroy(wl_client* client, wl_resource* resource) {
+  VLOG(1) << "pointer_constraints_destroy(" << client << ", " << resource
+          << ")";
   wl_resource_destroy(resource);
 }
 
@@ -125,6 +148,13 @@
   SkRegion* region =
       region_resource ? GetUserDataAs<SkRegion>(region_resource) : nullptr;
 
+  VLOG(1) << "lock_pointer(" << client << ", " << resource << "; Surface "
+          << surface << " @ window '"
+          << (surface && surface->window() ? surface->window()->GetTitle()
+                                           : base::EmptyString16())
+          << "', "
+          << "Pointer " << pointer << ")";
+
   wl_resource* locked_pointer_resource =
       wl_resource_create(client, &zwp_locked_pointer_v1_interface, 1, id);
   SetImplementation(
diff --git a/components/feedback/redaction_tool.cc b/components/feedback/redaction_tool.cc
index 1eb10d3..d9c4eff 100644
--- a/components/feedback/redaction_tool.cc
+++ b/components/feedback/redaction_tool.cc
@@ -412,8 +412,9 @@
   re2::RE2::Arg a0(argc > 0 ? args[0] : nullptr);
   re2::RE2::Arg a1(argc > 1 ? args[1] : nullptr);
   re2::RE2::Arg a2(argc > 2 ? args[2] : nullptr);
-  const re2::RE2::Arg* const wrapped_args[] = {&a0, &a1, &a2};
-  CHECK_LE(argc, 3);
+  re2::RE2::Arg a3(argc > 3 ? args[3] : nullptr);
+  const re2::RE2::Arg* const wrapped_args[] = {&a0, &a1, &a2, &a3};
+  CHECK_LE(argc, 4);
 
   bool result = re2::RE2::FindAndConsumeN(input, pattern, wrapped_args, argc);
 
@@ -643,20 +644,28 @@
   std::string result;
   result.reserve(input.size());
 
-  // This is for redacting 'android_app_storage' output. When the path starts
-  // either /home/root/<hash>/data/data/<package_name>/ or
-  // /home/root/<hash>/data/user_de/<number>/<package_name>/, this function will
-  // redact path components following <package_name>/.
+  // This is for redacting Android data paths included in 'android_app_storage'
+  // and 'audit_log' output. <app_specific_path> in the following data paths
+  // will be redacted.
+  // - /data/data/<package_name>/<app_specific_path>
+  // - /data/app/<package_name>/<app_specific_path>
+  // - /data/user_de/<number>/<package_name>/<app_specific_path>
+  // These data paths are preceded by "/home/root/<user_hash>/android-data" in
+  // 'android_app_storage' output, and preceded by "path=" or "exe=" in
+  // 'audit_log' output.
   RE2* path_re = GetRegExp(
-      "(?m)(\\t/home/root/[\\da-f]+/android-data/data/"
-      "(data|user_de/\\d+)/[^/\\n]+)("
-      "/[^\\n]+)");
+      R"((?m)((path=|exe=|/home/root/[\da-f]+/android-data))"
+      R"(/data/(data|app|user_de/\d+)/[^/\n]+)(/[^\n\s]+))");
 
   // Keep consuming, building up a result string as we go.
   re2::StringPiece text(input);
-  re2::StringPiece skipped, path_prefix, ignored, app_specific;
+  re2::StringPiece skipped;
+  re2::StringPiece path_prefix;  // path before app_specific;
+  re2::StringPiece pre_data;  // (path=|exe=|/home/root/<hash>/android-data)
+  re2::StringPiece post_data;  // (data|app|user_de/\d+)
+  re2::StringPiece app_specific;  // (/[^\n\s]+)
   while (FindAndConsumeAndGetSkipped(&text, *path_re, &skipped, &path_prefix,
-                                     &ignored, &app_specific)) {
+                                     &pre_data, &post_data, &app_specific)) {
     // We can record these parts as-is.
     skipped.AppendToString(&result);
     path_prefix.AppendToString(&result);
diff --git a/components/feedback/redaction_tool_unittest.cc b/components/feedback/redaction_tool_unittest.cc
index ddf81a0f..0c8a747 100644
--- a/components/feedback/redaction_tool_unittest.cc
+++ b/components/feedback/redaction_tool_unittest.cc
@@ -677,15 +677,16 @@
       "\xe3\x81\x82\xe3\x81\x83\n"
       "8.0K\t/home/root/deadbeef1234/android-data/data/data/pa.ckage2/ef\n"
       "24K\t/home/root/deadbeef1234/android-data/data/data/pa.ckage2\n"
-      // /data/app won't.
       "8.0K\t/home/root/deadbeef1234/android-data/data/app/pack.age1/a\n"
       "8.0K\t/home/root/deadbeef1234/android-data/data/app/pack.age1/bc\n"
       "24K\t/home/root/deadbeef1234/android-data/data/app/pack.age1\n"
-      // /data/user_de will.
       "8.0K\t/home/root/deadbeef1234/android-data/data/user_de/0/pack.age1/a\n"
       "8.0K\t/home/root/deadbeef1234/android-data/data/user_de/0/pack.age1/bc\n"
       "24K\t/home/root/deadbeef1234/android-data/data/user_de/0/pack.age1\n"
-      "78M\t/home/root/deadbeef1234/android-data/data/data\n";
+      "78M\t/home/root/deadbeef1234/android-data/data/data\n"
+      "key=value path=/data/data/pack.age1/bc key=value\n"
+      "key=value path=/data/user_de/0/pack.age1/bc key=value\n"
+      "key=value exe=/data/app/pack.age1/bc key=value\n";
   constexpr char kDuOutputRedacted[] =
       "112K\t/home/root/deadbeef1234/android-data/data/system_de\n"
       "8.0K\t/home/root/deadbeef1234/android-data/data/data/pack.age1/a\n"
@@ -698,12 +699,15 @@
       "8.0K\t/home/root/deadbeef1234/android-data/data/data/pa.ckage2/e_\n"
       "24K\t/home/root/deadbeef1234/android-data/data/data/pa.ckage2\n"
       "8.0K\t/home/root/deadbeef1234/android-data/data/app/pack.age1/a\n"
-      "8.0K\t/home/root/deadbeef1234/android-data/data/app/pack.age1/bc\n"
+      "8.0K\t/home/root/deadbeef1234/android-data/data/app/pack.age1/b_\n"
       "24K\t/home/root/deadbeef1234/android-data/data/app/pack.age1\n"
       "8.0K\t/home/root/deadbeef1234/android-data/data/user_de/0/pack.age1/a\n"
       "8.0K\t/home/root/deadbeef1234/android-data/data/user_de/0/pack.age1/b_\n"
       "24K\t/home/root/deadbeef1234/android-data/data/user_de/0/pack.age1\n"
-      "78M\t/home/root/deadbeef1234/android-data/data/data\n";
+      "78M\t/home/root/deadbeef1234/android-data/data/data\n"
+      "key=value path=/data/data/pack.age1/b_ key=value\n"
+      "key=value path=/data/user_de/0/pack.age1/b_ key=value\n"
+      "key=value exe=/data/app/pack.age1/b_ key=value\n";
   EXPECT_EQ(kDuOutputRedacted, RedactAndroidAppStoragePaths(kDuOutput));
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/components/language/core/browser/ulp_metrics_logger.cc b/components/language/core/browser/ulp_metrics_logger.cc
index 374be3a..5035585 100644
--- a/components/language/core/browser/ulp_metrics_logger.cc
+++ b/components/language/core/browser/ulp_metrics_logger.cc
@@ -5,6 +5,7 @@
 #include "components/language/core/browser/ulp_metrics_logger.h"
 
 #include "base/metrics/histogram_macros.h"
+#include "ui/base/l10n/l10n_util.h"
 
 namespace language {
 
@@ -36,15 +37,32 @@
 ULPLanguageStatus ULPMetricsLogger::DetermineLanguageStatus(
     const std::string& language,
     const std::vector<std::string>& ulp_languages) {
-  std::vector<std::string>::const_iterator i =
-      std::find(ulp_languages.begin(), ulp_languages.end(), language);
-  if (i == ulp_languages.end()) {
-    return ULPLanguageStatus::kLanguageNotInULP;
-  } else if (i == ulp_languages.begin()) {
-    return ULPLanguageStatus::kTopULPLanguage;
-  } else {
-    return ULPLanguageStatus::kNonTopULPLanguage;
+  if (language.empty() || language.compare("und") == 0) {
+    return ULPLanguageStatus::kLanguageEmpty;
   }
+
+  // Search for exact match of language in ulp_languages (e.g. pt-BR != pt-MZ).
+  std::vector<std::string>::const_iterator exact_match =
+      std::find(ulp_languages.begin(), ulp_languages.end(), language);
+  if (exact_match == ulp_languages.begin()) {
+    return ULPLanguageStatus::kTopULPLanguageExactMatch;
+  } else if (exact_match != ulp_languages.end()) {
+    return ULPLanguageStatus::kNonTopULPLanguageExactMatch;
+  }
+
+  // Now search for a base language match (e.g pt-BR == pt-MZ).
+  const std::string base_language = l10n_util::GetLanguage(language);
+  std::vector<std::string>::const_iterator base_match = std::find_if(
+      ulp_languages.begin(), ulp_languages.end(),
+      [&base_language](const std::string& ulp_language) {
+        return base_language.compare(l10n_util::GetLanguage(ulp_language)) == 0;
+      });
+  if (base_match == ulp_languages.begin()) {
+    return ULPLanguageStatus::kTopULPLanguageBaseMatch;
+  } else if (base_match != ulp_languages.end()) {
+    return ULPLanguageStatus::kNonTopULPLanguageBaseMatch;
+  }
+  return ULPLanguageStatus::kLanguageNotInULP;
 }
 
 int ULPMetricsLogger::ULPLanguagesInAcceptLanguagesRatio(
diff --git a/components/language/core/browser/ulp_metrics_logger.h b/components/language/core/browser/ulp_metrics_logger.h
index 3a72c37..1ace7898 100644
--- a/components/language/core/browser/ulp_metrics_logger.h
+++ b/components/language/core/browser/ulp_metrics_logger.h
@@ -24,10 +24,13 @@
 // Keep up to date with ULPLanguageStatus in
 // //tools/metrics/histograms/enums.xml.
 enum class ULPLanguageStatus {
-  kTopULPLanguage = 0,
-  kNonTopULPLanguage = 1,
+  kTopULPLanguageExactMatch = 0,
+  kNonTopULPLanguageExactMatch = 1,
   kLanguageNotInULP = 2,
-  kMaxValue = kLanguageNotInULP,
+  kTopULPLanguageBaseMatch = 3,
+  kNonTopULPLanguageBaseMatch = 4,
+  kLanguageEmpty = 5,
+  kMaxValue = kLanguageEmpty,
 };
 
 // ULPMetricsLogger abstracts the UMA histograms populated by the User Language
diff --git a/components/language/core/browser/ulp_metrics_logger_unittest.cc b/components/language/core/browser/ulp_metrics_logger_unittest.cc
index b4870f3a..1c81e4cc 100644
--- a/components/language/core/browser/ulp_metrics_logger_unittest.cc
+++ b/components/language/core/browser/ulp_metrics_logger_unittest.cc
@@ -23,10 +23,11 @@
   ULPMetricsLogger logger;
   base::HistogramTester histogram;
 
-  logger.RecordInitiationUILanguageInULP(ULPLanguageStatus::kTopULPLanguage);
+  logger.RecordInitiationUILanguageInULP(
+      ULPLanguageStatus::kTopULPLanguageExactMatch);
 
   histogram.ExpectUniqueSample(kInitiationUILanguageInULPHistogram,
-                               ULPLanguageStatus::kTopULPLanguage, 1);
+                               ULPLanguageStatus::kTopULPLanguageExactMatch, 1);
 }
 
 TEST(ULPMetricsLoggerTest, TestTranslateTargetStatus) {
@@ -34,10 +35,11 @@
   base::HistogramTester histogram;
 
   logger.RecordInitiationTranslateTargetInULP(
-      ULPLanguageStatus::kNonTopULPLanguage);
+      ULPLanguageStatus::kNonTopULPLanguageExactMatch);
 
   histogram.ExpectUniqueSample(kInitiationTranslateTargetInULPHistogram,
-                               ULPLanguageStatus::kNonTopULPLanguage, 1);
+                               ULPLanguageStatus::kNonTopULPLanguageExactMatch,
+                               1);
 }
 
 TEST(ULPMetricsLoggerTest, TestTopAcceptLanguageStatus) {
@@ -61,4 +63,56 @@
                                21, 1);
 }
 
+TEST(ULPMetricsLoggerTest, TestDetermineLanguageStatus) {
+  ULPMetricsLogger logger;
+  std::vector<std::string> ulp_languages = {"en-US", "es-419", "pt-BR", "de",
+                                            "fr-CA"};
+
+  EXPECT_EQ(ULPLanguageStatus::kTopULPLanguageExactMatch,
+            logger.DetermineLanguageStatus("en-US", ulp_languages));
+
+  EXPECT_EQ(ULPLanguageStatus::kNonTopULPLanguageExactMatch,
+            logger.DetermineLanguageStatus("de", ulp_languages));
+
+  EXPECT_EQ(ULPLanguageStatus::kTopULPLanguageBaseMatch,
+            logger.DetermineLanguageStatus("en-GB", ulp_languages));
+
+  EXPECT_EQ(ULPLanguageStatus::kNonTopULPLanguageBaseMatch,
+            logger.DetermineLanguageStatus("pt", ulp_languages));
+
+  EXPECT_EQ(ULPLanguageStatus::kLanguageNotInULP,
+            logger.DetermineLanguageStatus("zu", ulp_languages));
+
+  EXPECT_EQ(ULPLanguageStatus::kLanguageEmpty,
+            logger.DetermineLanguageStatus("", ulp_languages));
+
+  EXPECT_EQ(ULPLanguageStatus::kLanguageEmpty,
+            logger.DetermineLanguageStatus("und", ulp_languages));
+}
+
+TEST(ULPMetricsLoggerTest, TestULPLanguagesInAcceptLanguagesRatio) {
+  ULPMetricsLogger logger;
+  std::vector<std::string> ulp_languages = {"en-US", "es", "pt-BR", "de",
+                                            "fr-CA"};
+
+  EXPECT_EQ(0, logger.ULPLanguagesInAcceptLanguagesRatio({"en-GB", "af", "zu"},
+                                                         ulp_languages));
+
+  EXPECT_EQ(20, logger.ULPLanguagesInAcceptLanguagesRatio({"en-US", "af", "zu"},
+                                                          ulp_languages));
+
+  EXPECT_EQ(40, logger.ULPLanguagesInAcceptLanguagesRatio(
+                    {"en-US", "af", "zu", "es"}, ulp_languages));
+
+  EXPECT_EQ(60, logger.ULPLanguagesInAcceptLanguagesRatio(
+                    {"en-US", "af", "pt-BR", "es"}, ulp_languages));
+
+  EXPECT_EQ(80, logger.ULPLanguagesInAcceptLanguagesRatio(
+                    {"en-US", "af", "pt-BR", "es", "de"}, ulp_languages));
+
+  EXPECT_EQ(100,
+            logger.ULPLanguagesInAcceptLanguagesRatio(
+                {"en-US", "af", "pt-BR", "es", "de", "fr-CA"}, ulp_languages));
+}
+
 }  // namespace language
diff --git a/components/pdf/browser/plugin_response_writer.cc b/components/pdf/browser/plugin_response_writer.cc
index 7e4b78a..aef963cc 100644
--- a/components/pdf/browser/plugin_response_writer.cc
+++ b/components/pdf/browser/plugin_response_writer.cc
@@ -57,6 +57,11 @@
   position: fixed;
   top: 0;
 }
+
+/* Hide scrollbars when in Presentation mode. */
+.fullscreen {
+  overflow: hidden;
+}
 </style>
 <div id="sizer"></div>
 <embed type="application/x-google-chrome-pdf" src="$1" original-url="$2"
diff --git a/components/policy/test_support/BUILD.gn b/components/policy/test_support/BUILD.gn
index e56e307..15dcfbd 100644
--- a/components/policy/test_support/BUILD.gn
+++ b/components/policy/test_support/BUILD.gn
@@ -46,6 +46,8 @@
     "request_handler_for_remote_commands.h",
     "request_handler_for_status_upload.cc",
     "request_handler_for_status_upload.h",
+    "request_handler_for_unregister.cc",
+    "request_handler_for_unregister.h",
     "signature_provider.cc",
     "signature_provider.h",
     "test_server_helpers.cc",
@@ -98,6 +100,7 @@
     "request_handler_for_register_device_and_user_unittest.cc",
     "request_handler_for_remote_commands_unittest.cc",
     "request_handler_for_status_upload_unittest.cc",
+    "request_handler_for_unregister_unittest.cc",
     "signature_provider_unittest.cc",
   ]
 
diff --git a/components/policy/test_support/client_storage.cc b/components/policy/test_support/client_storage.cc
index 238fd062..bd6480e 100644
--- a/components/policy/test_support/client_storage.cc
+++ b/components/policy/test_support/client_storage.cc
@@ -39,6 +39,7 @@
   CHECK(!client_info.device_id.empty());
 
   clients_[client_info.device_id] = client_info;
+  registered_tokens_[client_info.device_token] = client_info.device_id;
 }
 
 bool ClientStorage::HasClient(const std::string& device_id) const {
@@ -68,6 +69,21 @@
   return nullptr;
 }
 
+bool ClientStorage::DeleteClient(const std::string& device_token) {
+  auto it = registered_tokens_.find(device_token);
+  if (it == registered_tokens_.end())
+    return false;
+
+  const std::string& device_id = it->second;
+  DCHECK(!device_id.empty());
+  auto it_clients = clients_.find(device_id);
+  DCHECK(it_clients != clients_.end());
+
+  clients_.erase(it_clients, clients_.end());
+  registered_tokens_.erase(it, registered_tokens_.end());
+  return true;
+}
+
 size_t ClientStorage::GetNumberOfRegisteredClients() const {
   return clients_.size();
 }
diff --git a/components/policy/test_support/client_storage.h b/components/policy/test_support/client_storage.h
index 2b2058a..999909eeb 100644
--- a/components/policy/test_support/client_storage.h
+++ b/components/policy/test_support/client_storage.h
@@ -58,6 +58,9 @@
   // no such a client.
   const ClientInfo* LookupByStateKey(const std::string& state_key) const;
 
+  // Returns true if deletion of client with token |device_token| succeeded.
+  bool DeleteClient(const std::string& device_token);
+
   // Returns the number of clients registered.
   size_t GetNumberOfRegisteredClients() const;
 
@@ -69,6 +72,8 @@
  private:
   // Key: device ids.
   std::map<std::string, ClientInfo> clients_;
+  // Maps device tokens to device IDs.
+  std::map<std::string, std::string> registered_tokens_;
 };
 
 }  // namespace policy
diff --git a/components/policy/test_support/client_storage_unittest.cc b/components/policy/test_support/client_storage_unittest.cc
index ff7b1b4..5b2a2760 100644
--- a/components/policy/test_support/client_storage_unittest.cc
+++ b/components/policy/test_support/client_storage_unittest.cc
@@ -18,6 +18,8 @@
 constexpr const char kStateKey2[] = "ggg";
 constexpr const char kStateKey3[] = "fff";
 constexpr const char kStateKey4[] = "ccc";
+constexpr const char kDeviceToken[] = "device-token";
+constexpr const char kNonExistingDeviceToken[] = "non-existing-device-token";
 constexpr const uint64_t kModulus = 3;
 constexpr const uint64_t kRemainder = 2;
 // Following SHA256 hashes produce |kRemainder| when divided by |kModulus|.
@@ -30,8 +32,36 @@
     "\x97\xd8\x32\x41\x58\x1b\x37\xdb\xd7\x0a\x7a\x49\x00\xfe",
     32);
 
+void RegisterClient(const std::string& device_token,
+                    ClientStorage* client_storage) {
+  ClientStorage::ClientInfo client_info;
+  client_info.device_id = kDeviceId1;
+  client_info.device_token = device_token;
+
+  client_storage->RegisterClient(client_info);
+  ASSERT_EQ(client_storage->GetNumberOfRegisteredClients(), 1u);
+  ASSERT_EQ(client_storage->GetClient(kDeviceId1).device_token, device_token);
+}
+
 }  // namespace
 
+TEST(ClientStorageTest, Unregister_Success) {
+  ClientStorage client_storage;
+  RegisterClient(kDeviceToken, &client_storage);
+
+  ASSERT_TRUE(client_storage.DeleteClient(kDeviceToken));
+  EXPECT_EQ(client_storage.GetNumberOfRegisteredClients(), 0u);
+}
+
+TEST(ClientStorageTest, Unregister_NonExistingClient) {
+  ClientStorage client_storage;
+  RegisterClient(kDeviceToken, &client_storage);
+
+  ASSERT_FALSE(client_storage.DeleteClient(kNonExistingDeviceToken));
+  ASSERT_EQ(client_storage.GetNumberOfRegisteredClients(), 1u);
+  EXPECT_EQ(client_storage.GetClient(kDeviceId1).device_token, kDeviceToken);
+}
+
 TEST(ClientStorageTest, GetMatchingStateKeyHashes) {
   ClientStorage client_storage;
   ClientStorage::ClientInfo client_info1;
diff --git a/components/policy/test_support/embedded_policy_test_server.cc b/components/policy/test_support/embedded_policy_test_server.cc
index 52fe8c1..106a1d6c 100644
--- a/components/policy/test_support/embedded_policy_test_server.cc
+++ b/components/policy/test_support/embedded_policy_test_server.cc
@@ -31,6 +31,7 @@
 #include "components/policy/test_support/request_handler_for_register_device_and_user.h"
 #include "components/policy/test_support/request_handler_for_remote_commands.h"
 #include "components/policy/test_support/request_handler_for_status_upload.h"
+#include "components/policy/test_support/request_handler_for_unregister.h"
 #include "components/policy/test_support/test_server_helpers.h"
 #include "crypto/sha2.h"
 #include "net/base/url_util.h"
@@ -115,6 +116,8 @@
       client_storage_.get(), policy_storage_.get()));
   RegisterHandler(std::make_unique<RequestHandlerForStatusUpload>(
       client_storage_.get(), policy_storage_.get()));
+  RegisterHandler(std::make_unique<RequestHandlerForUnregister>(
+      client_storage_.get(), policy_storage_.get()));
 
   http_server_.RegisterDefaultHandler(base::BindRepeating(
       &EmbeddedPolicyTestServer::HandleRequest, base::Unretained(this)));
diff --git a/components/policy/test_support/request_handler_for_register_device_and_user.cc b/components/policy/test_support/request_handler_for_register_device_and_user.cc
index d26022d0..f07b0c1 100644
--- a/components/policy/test_support/request_handler_for_register_device_and_user.cc
+++ b/components/policy/test_support/request_handler_for_register_device_and_user.cc
@@ -31,6 +31,11 @@
 void AddAllowedPolicyTypes(em::DeviceRegisterRequest::Type type,
                            std::set<std::string>* allowed_policy_types) {
   switch (type) {
+    // TODO(crbug.com/1289442): Remove this case once the type is correctly set
+    // for request type `register`.
+    case em::DeviceRegisterRequest::TT:
+      allowed_policy_types->insert({dm_protocol::kChromeUserPolicyType});
+      break;
     case em::DeviceRegisterRequest::USER:
       allowed_policy_types->insert({dm_protocol::kChromeUserPolicyType,
                                     dm_protocol::kChromeExtensionPolicyType});
diff --git a/components/policy/test_support/request_handler_for_unregister.cc b/components/policy/test_support/request_handler_for_unregister.cc
new file mode 100644
index 0000000..ff8360d
--- /dev/null
+++ b/components/policy/test_support/request_handler_for_unregister.cc
@@ -0,0 +1,48 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_unregister.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForUnregister::RequestHandlerForUnregister(
+    ClientStorage* client_storage,
+    PolicyStorage* policy_storage)
+    : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForUnregister::~RequestHandlerForUnregister() = default;
+
+std::string RequestHandlerForUnregister::RequestType() {
+  return dm_protocol::kValueRequestUnregister;
+}
+
+std::unique_ptr<HttpResponse> RequestHandlerForUnregister::HandleRequest(
+    const HttpRequest& request) {
+  std::string request_device_token;
+  if (!GetDeviceTokenFromRequest(request, &request_device_token) ||
+      !client_storage()->DeleteClient(request_device_token)) {
+    return CreateHttpResponse(net::HTTP_UNAUTHORIZED, "Invalid device token.");
+  }
+
+  em::DeviceManagementResponse device_management_response;
+  device_management_response.mutable_unregister_response();
+  return CreateHttpResponse(net::HTTP_OK,
+                            device_management_response.SerializeAsString());
+}
+
+}  // namespace policy
diff --git a/components/policy/test_support/request_handler_for_unregister.h b/components/policy/test_support/request_handler_for_unregister.h
new file mode 100644
index 0000000..19aedfb2
--- /dev/null
+++ b/components/policy/test_support/request_handler_for_unregister.h
@@ -0,0 +1,31 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_UNREGISTER_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_UNREGISTER_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `unregister`.
+class RequestHandlerForUnregister
+    : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+  RequestHandlerForUnregister(ClientStorage* client_storage,
+                              PolicyStorage* policy_storage);
+  RequestHandlerForUnregister(RequestHandlerForUnregister&& handler) = delete;
+  RequestHandlerForUnregister& operator=(
+      RequestHandlerForUnregister&& handler) = delete;
+  ~RequestHandlerForUnregister() override;
+
+  // EmbeddedPolicyTestServer::RequestHandler:
+  std::string RequestType() override;
+  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+      const net::test_server::HttpRequest& request) override;
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_UNREGISTER_H_
diff --git a/components/policy/test_support/request_handler_for_unregister_unittest.cc b/components/policy/test_support/request_handler_for_unregister_unittest.cc
new file mode 100644
index 0000000..a8d6b7e
--- /dev/null
+++ b/components/policy/test_support/request_handler_for_unregister_unittest.cc
@@ -0,0 +1,73 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_unregister.h"
+
+#include <utility>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kDeviceToken[] = "fake_device_token";
+constexpr char kNonExistingDeviceToken[] = "non_existing_device_token";
+
+}  // namespace
+
+class RequestHandlerForUnregisterTest
+    : public EmbeddedPolicyTestServerTestBase {
+ protected:
+  RequestHandlerForUnregisterTest() = default;
+  ~RequestHandlerForUnregisterTest() override = default;
+
+  void SetUp() override {
+    EmbeddedPolicyTestServerTestBase::SetUp();
+
+    SetRequestTypeParam(dm_protocol::kValueRequestUnregister);
+    SetAppType(dm_protocol::kValueAppType);
+    SetDeviceIdParam(kDeviceId);
+    SetDeviceType(dm_protocol::kValueDeviceType);
+
+    ClientStorage::ClientInfo client_info;
+    client_info.device_id = kDeviceId;
+    client_info.device_token = kDeviceToken;
+    client_storage()->RegisterClient(client_info);
+  }
+};
+
+TEST_F(RequestHandlerForUnregisterTest, HandleRequest_NoDeviceToken) {
+  StartRequestAndWait();
+
+  EXPECT_EQ(GetResponseCode(), net::HTTP_UNAUTHORIZED);
+  EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 1u);
+}
+
+TEST_F(RequestHandlerForUnregisterTest, HandleRequest_ClientNotFound) {
+  SetDeviceTokenHeader(kNonExistingDeviceToken);
+
+  StartRequestAndWait();
+
+  EXPECT_EQ(GetResponseCode(), net::HTTP_UNAUTHORIZED);
+  EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 1u);
+}
+
+TEST_F(RequestHandlerForUnregisterTest, HandleRequest_DeleteClient) {
+  SetDeviceTokenHeader(kDeviceToken);
+
+  StartRequestAndWait();
+
+  EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+  EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 0u);
+}
+
+}  // namespace policy
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
index 86c9bf3..e7316d26 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
@@ -978,16 +978,13 @@
   }
 
   if (frame) {
-    gfx::Rect sub_region = properties.capture_rect;
-    // In some cases, the content_rect is smaller than the capture_rect.
-    sub_region.ClampToCenteredSize(content_rect.size());
-
     if (pixel_format_ != media::PIXEL_FORMAT_NV12) {
       // TODO(bialpio): implement overlays for NV12!
       auto overlay_renderer = VideoCaptureOverlay::MakeCombinedRenderer(
           GetOverlaysInOrder(),
           VideoCaptureOverlay::CapturedFrameProperties{
-              properties.active_frame_rect, sub_region, frame->format()});
+              properties.active_frame_rect, properties.capture_rect,
+              content_rect, frame->format()});
       if (overlay_renderer) {
         std::move(overlay_renderer).Run(frame.get());
       }
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
index 96613792..fa6277e3 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
@@ -246,6 +246,8 @@
 
     // The actual content size of the copied frame, as a post-scaled size
     // with an origin at (0, 0).
+    // TODO(https://crbug.com/1287686): replace zero-origin gfx::Rect with
+    // gfx::Size.
     gfx::Rect content_rect;
 
     // The requested capture region, may be larger or at a different
diff --git a/components/viz/service/frame_sinks/video_capture/video_capture_overlay.cc b/components/viz/service/frame_sinks/video_capture/video_capture_overlay.cc
index 449987c..4f813b6 100644
--- a/components/viz/service/frame_sinks/video_capture/video_capture_overlay.cc
+++ b/components/viz/service/frame_sinks/video_capture/video_capture_overlay.cc
@@ -114,17 +114,47 @@
                    std::max(0, bottom - top));
 }
 
+// Uses the mapping of a region R that exists in coordinate system A
+// as |from_region| and in coordinate system B as |to_region|. The |source|
+// rectangle is in coordinate system A and mapped to coordinate system B
+// in three steps:
+//   1. translate to remove the origin of the old coordinate space.
+//   2. scale values to the new space.
+//   3. translate to add the origin of the new coordinate space.
+gfx::Rect Transform(const gfx::Rect& source,
+                    const gfx::Rect& from_region,
+                    const gfx::Rect& to_region) {
+  // Transforming from or to a zero space is undefined behavior.
+  if (from_region.IsEmpty() || to_region.IsEmpty())
+    return {};
+
+  const gfx::Vector2dF scale{static_cast<float>(to_region.width()) /
+                                 static_cast<float>(from_region.width()),
+                             static_cast<float>(to_region.height()) /
+                                 static_cast<float>(from_region.height())};
+
+  const gfx::Rect old_translated =
+      gfx::Rect(source.x() - from_region.x(), source.y() - from_region.y(),
+                source.width(), source.height());
+  const gfx::Rect scaled =
+      gfx::ScaleToEnclosingRect(old_translated, scale.x(), scale.y());
+  const gfx::Rect new_translated =
+      gfx::Rect(scaled.x() + to_region.x(), scaled.y() + to_region.y(),
+                scaled.width(), scaled.height());
+
+  return MinimallyShrinkRectForI420(new_translated);
+}
+
 }  // namespace
 
 VideoCaptureOverlay::OnceRenderer VideoCaptureOverlay::MakeRenderer(
     const CapturedFrameProperties& properties) {
-  // If there's no image set yet, punt.
-  if (image_.drawsNothing()) {
-    return VideoCaptureOverlay::OnceRenderer();
-  }
-
   // The sub region should always be a subset of the frame region.
-  DCHECK(properties.frame_region.Contains(properties.sub_region));
+  DCHECK(properties.compositor_region.Contains(properties.sub_region));
+
+  // If there's no image set yet, punt.
+  if (image_.drawsNothing())
+    return {};
 
   // Determine the bounds of the sprite to be blitted onto the video frame. The
   // calculations here align to the 2x2 pixel-quads, since dealing with
@@ -132,52 +162,38 @@
   // complexify the blitting algorithm later on. This introduces a little
   // inaccuracy in the size and position of the overlay in the final result, but
   // should be an acceptable trade-off for all use cases.
-
+  //
   // Rescale the relative bounds (scoped between [0, 1]) to absolute bounds
   // based on the entire region of the frame sink being captured. This allows
   // for calculations such as mouse cursor position (which is retrieved in
-  // relationship to the entire tab or window) to be scaled properly, then
-  // have its location manipulated below as |bounds_in_frame|.
+  // relationship to the entire tab or window) to be scaled properly.
   const gfx::Rect absolute_bounds =
-      ToAbsoluteBoundsForI420(bounds_, properties.frame_region);
+      ToAbsoluteBoundsForI420(bounds_, properties.compositor_region);
+  if (!absolute_bounds.Intersects(properties.sub_region))
+    return {};
 
-  // Translate the location of the cursor from the entire surface to just
-  // the |sub_region| we are capturing. This will be a noop if we are
-  // capturing the entire |frame_region|.
-  gfx::Rect bounds_in_frame = absolute_bounds;
-  bounds_in_frame.Offset(properties.frame_region.origin() -
-                         properties.sub_region.origin());
-
-  // After offset, the bounds may not be properly set for I420.
-  bounds_in_frame = MinimallyShrinkRectForI420(bounds_in_frame);
+  // The bounds are currently in the coordinate space of the captured compositor
+  // frame, however blitting is actually done in the coordinate space of the
+  // outputted video frame and must be scaled and translated.
+  const gfx::Rect bounds_in_target = Transform(
+      absolute_bounds, properties.sub_region, properties.content_region);
 
   // If the sprite's size will be unreasonably large, punt.
-  if (bounds_in_frame.width() > media::limits::kMaxDimension ||
-      bounds_in_frame.height() > media::limits::kMaxDimension) {
-    return VideoCaptureOverlay::OnceRenderer();
-  }
+  if (bounds_in_target.width() > media::limits::kMaxDimension ||
+      bounds_in_target.height() > media::limits::kMaxDimension)
+    return {};
 
-  // Compute the region of the frame to be modified by future Sprite::Blit()
-  // calls.
-  gfx::Rect blit_rect = MinimallyShrinkRectForI420(gfx::Rect(
-      properties.frame_region.origin(), properties.sub_region.size()));
-  blit_rect.Intersect(bounds_in_frame);
-
-  // If the two rects didn't intersect at all (i.e., everything has been
-  // clipped), punt.
-  if (blit_rect.IsEmpty()) {
-    return VideoCaptureOverlay::OnceRenderer();
-  }
-
-  // If the cached sprite does not match the computed scaled size and/or pixel
-  // format, create a new instance for this (and future) renderers.
-  if (!sprite_ || sprite_->size() != absolute_bounds.size() ||
+  // If the cached sprite does not match the computed scaled size and/or
+  // pixel format, create a new instance for this (and future) renderers.
+  if (!sprite_ || sprite_->size() != bounds_in_target.size() ||
       sprite_->format() != properties.format) {
-    sprite_ = base::MakeRefCounted<Sprite>(image_, absolute_bounds.size(),
+    sprite_ = base::MakeRefCounted<Sprite>(image_, bounds_in_target.size(),
                                            properties.format);
   }
 
-  return base::BindOnce(&Sprite::Blit, sprite_, bounds_in_frame.origin(),
+  gfx::Rect blit_rect = bounds_in_target;
+  blit_rect.Intersect(properties.content_region);
+  return base::BindOnce(&Sprite::Blit, sprite_, bounds_in_target.origin(),
                         blit_rect);
 }
 
diff --git a/components/viz/service/frame_sinks/video_capture/video_capture_overlay.h b/components/viz/service/frame_sinks/video_capture/video_capture_overlay.h
index e38257e..f558a73 100644
--- a/components/viz/service/frame_sinks/video_capture/video_capture_overlay.h
+++ b/components/viz/service/frame_sinks/video_capture/video_capture_overlay.h
@@ -93,16 +93,20 @@
   void SetBounds(const gfx::RectF& bounds) final;
 
   struct CapturedFrameProperties {
-    // The entire size of the frame on the surface. This should be the
-    // maximum possible capturable surface size.
-    gfx::Rect frame_region;
+    // The entire size of the compositor frame on the surface. This should be
+    // the maximum possible capturable surface size.
+    gfx::Rect compositor_region;
 
-    // The sub region of the frame selected for capture. Should be in the
-    // same coordinate system as |frame_region| as a subset of pixels. If
-    // sub_region == frame_region, then the entire frame surface is being
-    // captured.
+    // The sub region of the compositor frame selected for capture. Should be in
+    // the same coordinate system as |compositor_region| as a subset of pixels.
+    // If sub_region == compositor_region, then the entire frame surface is
+    // being captured.
     gfx::Rect sub_region;
 
+    // Ultimately the overlay gets outputted onto a video frame with a region
+    // of |content_region|.
+    gfx::Rect content_region;
+
     // The frame's pixel format.
     media::VideoPixelFormat format;
   };
diff --git a/components/viz/service/frame_sinks/video_capture/video_capture_overlay_unittest.cc b/components/viz/service/frame_sinks/video_capture/video_capture_overlay_unittest.cc
index 4fb1141..514ea800 100644
--- a/components/viz/service/frame_sinks/video_capture/video_capture_overlay_unittest.cc
+++ b/components/viz/service/frame_sinks/video_capture/video_capture_overlay_unittest.cc
@@ -161,16 +161,18 @@
   constexpr gfx::Rect kRegionInFrame = gfx::Rect(kSize);
   EXPECT_FALSE(
       overlay->MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-          .frame_region = kRegionInFrame,
+          .compositor_region = kRegionInFrame,
           .sub_region = kRegionInFrame,
+          .content_region = kRegionInFrame,
           .format = kI420Format}));
 
   // Once an image is set, the renderer should not be null.
   overlay->SetImageAndBounds(MakeTestBitmap(1), gfx::RectF(0, 0, 1, 1));
   EXPECT_TRUE(
       overlay->MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-          .frame_region = kRegionInFrame,
+          .compositor_region = kRegionInFrame,
           .sub_region = kRegionInFrame,
+          .content_region = kRegionInFrame,
           .format = kI420Format}));
 }
 
@@ -185,8 +187,9 @@
   constexpr gfx::Rect kRegionInFrame = gfx::Rect(kSize);
   EXPECT_FALSE(
       overlay->MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-          .frame_region = kRegionInFrame,
+          .compositor_region = kRegionInFrame,
           .sub_region = kRegionInFrame,
+          .content_region = kRegionInFrame,
           .format = kI420Format}));
 
   // Setting an image, but out-of-bounds, should always result in a null
@@ -194,26 +197,30 @@
   overlay->SetImageAndBounds(MakeTestBitmap(0), gfx::RectF(-1, -1, 1, 1));
   EXPECT_FALSE(
       overlay->MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-          .frame_region = kRegionInFrame,
+          .compositor_region = kRegionInFrame,
           .sub_region = kRegionInFrame,
+          .content_region = kRegionInFrame,
           .format = kI420Format}));
   overlay->SetBounds(gfx::RectF(1, 1, 1, 1));
   EXPECT_FALSE(
       overlay->MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-          .frame_region = kRegionInFrame,
+          .compositor_region = kRegionInFrame,
           .sub_region = kRegionInFrame,
+          .content_region = kRegionInFrame,
           .format = kI420Format}));
   overlay->SetBounds(gfx::RectF(-1, 1, 1, 1));
   EXPECT_FALSE(
       overlay->MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-          .frame_region = kRegionInFrame,
+          .compositor_region = kRegionInFrame,
           .sub_region = kRegionInFrame,
+          .content_region = kRegionInFrame,
           .format = kI420Format}));
   overlay->SetBounds(gfx::RectF(1, -1, 1, 1));
   EXPECT_FALSE(
       overlay->MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-          .frame_region = kRegionInFrame,
+          .compositor_region = kRegionInFrame,
           .sub_region = kRegionInFrame,
+          .content_region = kRegionInFrame,
           .format = kI420Format}));
 }
 
@@ -232,8 +239,9 @@
   constexpr gfx::Rect kRegionInFrame = gfx::Rect(kSize);
   EXPECT_FALSE(VideoCaptureOverlay::MakeCombinedRenderer(
       overlays, VideoCaptureOverlay::CapturedFrameProperties{
-                    .frame_region = kRegionInFrame,
+                    .compositor_region = kRegionInFrame,
                     .sub_region = kRegionInFrame,
+                    .content_region = kRegionInFrame,
                     .format = kI420Format}));
 
   // If just the first overlay renders, the combined renderer should not be
@@ -241,16 +249,18 @@
   overlays[0]->SetImageAndBounds(MakeTestBitmap(0), gfx::RectF(0, 0, 1, 1));
   EXPECT_TRUE(VideoCaptureOverlay::MakeCombinedRenderer(
       overlays, VideoCaptureOverlay::CapturedFrameProperties{
-                    .frame_region = kRegionInFrame,
+                    .compositor_region = kRegionInFrame,
                     .sub_region = kRegionInFrame,
+                    .content_region = kRegionInFrame,
                     .format = kI420Format}));
 
   // If both overlays render, the combined renderer should not be null.
   overlays[1]->SetImageAndBounds(MakeTestBitmap(1), gfx::RectF(0, 0, 1, 1));
   EXPECT_TRUE(VideoCaptureOverlay::MakeCombinedRenderer(
       overlays, VideoCaptureOverlay::CapturedFrameProperties{
-                    .frame_region = kRegionInFrame,
+                    .compositor_region = kRegionInFrame,
                     .sub_region = kRegionInFrame,
+                    .content_region = kRegionInFrame,
                     .format = kI420Format}));
 
   // If only the second overlay renders, because the first is hidden, the
@@ -258,16 +268,18 @@
   overlays[0]->SetBounds(gfx::RectF());
   EXPECT_TRUE(VideoCaptureOverlay::MakeCombinedRenderer(
       overlays, VideoCaptureOverlay::CapturedFrameProperties{
-                    .frame_region = kRegionInFrame,
+                    .compositor_region = kRegionInFrame,
                     .sub_region = kRegionInFrame,
+                    .content_region = kRegionInFrame,
                     .format = kI420Format}));
 
   // Both overlays are hidden, so the combined renderer should be null.
   overlays[1]->SetBounds(gfx::RectF());
   EXPECT_FALSE(VideoCaptureOverlay::MakeCombinedRenderer(
       overlays, VideoCaptureOverlay::CapturedFrameProperties{
-                    .frame_region = kRegionInFrame,
+                    .compositor_region = kRegionInFrame,
                     .sub_region = kRegionInFrame,
+                    .content_region = kRegionInFrame,
                     .format = kI420Format}));
 }
 
@@ -440,6 +452,18 @@
     return matches_golden_file;
   }
 
+  void ExpectRendersAs(VideoCaptureOverlay::OnceRenderer* renderers,
+                       const char* const* expected_files,
+                       const std::size_t count,
+                       const gfx::Size& frame_size) {
+    for (std::size_t i = 0; i < count; ++i) {
+      auto frame = CreateVideoFrame(frame_size);
+      DCHECK(renderers[i]);
+      std::move(renderers[i]).Run(frame.get());
+      EXPECT_TRUE(FrameMatchesPNG(*frame, expected_files[i]));
+    }
+  }
+
   // The size of the compositor frame sink's Surface.
   static constexpr gfx::Size kSourceSize = gfx::Size(96, 40);
 
@@ -470,8 +494,9 @@
   const gfx::Size output_size(test_bitmap.width(), test_bitmap.height());
   VideoCaptureOverlay::OnceRenderer renderer =
       overlay.MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-          .frame_region = gfx::Rect(output_size),
+          .compositor_region = gfx::Rect(output_size),
           .sub_region = gfx::Rect(output_size),
+          .content_region = gfx::Rect(output_size),
           .format = pixel_format()});
   ASSERT_TRUE(renderer);
   auto frame = CreateVideoFrame(output_size);
@@ -500,8 +525,9 @@
                               test_bitmap.height() * 4);
   VideoCaptureOverlay::OnceRenderer renderer =
       overlay.MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-          .frame_region = gfx::Rect(output_size),
+          .compositor_region = gfx::Rect(output_size),
           .sub_region = gfx::Rect(output_size),
+          .content_region = gfx::Rect(output_size),
           .format = pixel_format()});
   ASSERT_TRUE(renderer);
   auto frame = CreateVideoFrame(output_size);
@@ -542,8 +568,9 @@
     }
     renderers[i] =
         overlay.MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-            .frame_region = gfx::Rect(frame_size),
+            .compositor_region = gfx::Rect(frame_size),
             .sub_region = gfx::Rect(frame_size),
+            .content_region = gfx::Rect(frame_size),
             .format = pixel_format()});
   }
 
@@ -552,15 +579,8 @@
       "overlay_moves_2_1.png", "overlay_moves_2_2.png", "overlay_moves_lr.png",
   };
 
-  for (int i = 0; i < 6; ++i) {
-    SCOPED_TRACE(testing::Message() << "relative_image_bounds="
-                                    << relative_image_bounds[i].ToString()
-                                    << ", frame_size=" << frame_size.ToString()
-                                    << ", golden_file=" << kGoldenFiles[i]);
-    auto frame = CreateVideoFrame(frame_size);
-    std::move(renderers[i]).Run(frame.get());
-    EXPECT_TRUE(FrameMatchesPNG(*frame, kGoldenFiles[i]));
-  }
+  ExpectRendersAs(renderers, kGoldenFiles.data(), kGoldenFiles.size(),
+                  frame_size);
 }
 
 // Tests that the overlay will be partially rendered (clipped) when any part of
@@ -611,8 +631,9 @@
     }
     renderers[i] =
         overlay.MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-            .frame_region = region_in_frame,
+            .compositor_region = region_in_frame,
             .sub_region = region_in_frame,
+            .content_region = region_in_frame,
             .format = pixel_format()});
   }
 
@@ -623,11 +644,8 @@
       "overlay_clips_ll.png",
   };
 
-  for (int i = 0; i < 4; ++i) {
-    auto frame = CreateVideoFrame(frame_size);
-    std::move(renderers[i]).Run(frame.get());
-    EXPECT_TRUE(FrameMatchesPNG(*frame, kGoldenFiles[i]));
-  }
+  ExpectRendersAs(renderers, kGoldenFiles.data(), kGoldenFiles.size(),
+                  frame_size);
 }
 
 TEST_P(VideoCaptureOverlayRenderTest, HandlesEmptySubRegion) {
@@ -640,15 +658,16 @@
 
   const SkBitmap test_bitmap = MakeTestBitmap(0);
   const gfx::Size frame_size(test_bitmap.width() * 4, test_bitmap.height() * 4);
-  const gfx::Rect frame_region(frame_size);
+  const gfx::Rect compositor_region(frame_size);
   const gfx::Rect sub_region_in_frame;
   const gfx::RectF relative_image_bounds(0.125f, .125f, 0.25f, 0.25f);
 
   overlay.SetImageAndBounds(test_bitmap, relative_image_bounds);
   auto renderer =
       overlay.MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-          .frame_region = frame_region,
+          .compositor_region = compositor_region,
           .sub_region = sub_region_in_frame,
+          .content_region = compositor_region,
           .format = pixel_format()});
 
   // We shouldn't even create a renderer if we aren't capturing any pixels.
@@ -665,7 +684,7 @@
 
   const SkBitmap test_bitmap = MakeTestBitmap(0);
   const gfx::Size frame_size(test_bitmap.width() * 4, test_bitmap.height() * 4);
-  const gfx::Rect frame_region(frame_size);
+  const gfx::Rect compositor_region(frame_size);
   const gfx::Rect sub_region_in_frame(test_bitmap.width(), test_bitmap.height(),
                                       test_bitmap.width() * 2,
                                       test_bitmap.height() * 2);
@@ -685,8 +704,9 @@
     }
     renderers[i] =
         overlay.MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
-            .frame_region = frame_region,
+            .compositor_region = compositor_region,
             .sub_region = sub_region_in_frame,
+            .content_region = gfx::Rect(sub_region_in_frame.size()),
             .format = pixel_format()});
   }
 
@@ -697,11 +717,59 @@
       "overlay_clips_ll_subregion.png",
   };
 
+  ExpectRendersAs(renderers, kGoldenFiles.data(), kGoldenFiles.size(),
+                  sub_region_in_frame.size());
+}
+
+TEST_P(VideoCaptureOverlayRenderTest, ScalesToContentRegion) {
+  NiceMock<MockFrameSource> frame_source;
+  EXPECT_CALL(frame_source, GetSourceSize())
+      .WillRepeatedly(Return(kSourceSize));
+  mojo::Remote<mojom::FrameSinkVideoCaptureOverlay> overlay_remote;
+  VideoCaptureOverlay overlay(&frame_source,
+                              overlay_remote.BindNewPipeAndPassReceiver());
+
+  const SkBitmap test_bitmap = MakeTestBitmap(0);
+  const gfx::Size frame_size(test_bitmap.width() * 4, test_bitmap.height() * 4);
+  const gfx::Rect compositor_region(frame_size);
+  const gfx::Rect sub_region_in_frame(test_bitmap.width(), test_bitmap.height(),
+                                      test_bitmap.width() * 2,
+                                      test_bitmap.height() * 2);
+  const gfx::Rect content_region(
+      test_bitmap.width() * 2, test_bitmap.height() * 2,
+      test_bitmap.width() * 6, test_bitmap.height() * 6);
+
+  const gfx::RectF relative_image_bounds[4] = {
+      gfx::RectF(0.125f, .125f, 0.25f, 0.25f),
+      gfx::RectF(0.625f, .125f, 0.25f, 0.25f),
+      gfx::RectF(0.625f, 0.625f, 0.25f, 0.25f),
+      gfx::RectF(.125f, 0.625f, 0.25f, 0.25f),
+  };
+
+  VideoCaptureOverlay::OnceRenderer renderers[4];
   for (int i = 0; i < 4; ++i) {
-    auto frame = CreateVideoFrame(sub_region_in_frame.size());
-    std::move(renderers[i]).Run(frame.get());
-    EXPECT_TRUE(FrameMatchesPNG(*frame, kGoldenFiles[i]));
+    if (i == 0) {
+      overlay.SetImageAndBounds(test_bitmap, relative_image_bounds[i]);
+    } else {
+      overlay.SetBounds(relative_image_bounds[i]);
+    }
+    renderers[i] =
+        overlay.MakeRenderer(VideoCaptureOverlay::CapturedFrameProperties{
+            .compositor_region = compositor_region,
+            .sub_region = sub_region_in_frame,
+            .content_region = content_region,
+            .format = pixel_format()});
   }
+
+  constexpr std::array<const char*, 4> kGoldenFiles = {
+      "overlay_clips_ul_contentscaled.png",
+      "overlay_clips_ur_contentscaled.png",
+      "overlay_clips_lr_contentscaled.png",
+      "overlay_clips_ll_contentscaled.png",
+  };
+
+  ExpectRendersAs(renderers, kGoldenFiles.data(), kGoldenFiles.size(),
+                  gfx::Size(content_region.right(), content_region.bottom()));
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/components/viz/test/data/video_capture/overlay_clips_ll_contentscaled.png b/components/viz/test/data/video_capture/overlay_clips_ll_contentscaled.png
new file mode 100644
index 0000000..e23e7e3
--- /dev/null
+++ b/components/viz/test/data/video_capture/overlay_clips_ll_contentscaled.png
Binary files differ
diff --git a/components/viz/test/data/video_capture/overlay_clips_lr_contentscaled.png b/components/viz/test/data/video_capture/overlay_clips_lr_contentscaled.png
new file mode 100644
index 0000000..4accb8c
--- /dev/null
+++ b/components/viz/test/data/video_capture/overlay_clips_lr_contentscaled.png
Binary files differ
diff --git a/components/viz/test/data/video_capture/overlay_clips_ul_contentscaled.png b/components/viz/test/data/video_capture/overlay_clips_ul_contentscaled.png
new file mode 100644
index 0000000..7e6cb0e
--- /dev/null
+++ b/components/viz/test/data/video_capture/overlay_clips_ul_contentscaled.png
Binary files differ
diff --git a/components/viz/test/data/video_capture/overlay_clips_ur_contentscaled.png b/components/viz/test/data/video_capture/overlay_clips_ur_contentscaled.png
new file mode 100644
index 0000000..0bdf93d
--- /dev/null
+++ b/components/viz/test/data/video_capture/overlay_clips_ur_contentscaled.png
Binary files differ
diff --git a/components/webrtc/BUILD.gn b/components/webrtc/BUILD.gn
index 49eac6cc..a507d06 100644
--- a/components/webrtc/BUILD.gn
+++ b/components/webrtc/BUILD.gn
@@ -2,36 +2,68 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-source_set("media_stream_device_enumerator") {
-  sources = [
-    "media_stream_device_enumerator.h",
-    "media_stream_device_enumerator_impl.cc",
-    "media_stream_device_enumerator_impl.h",
-  ]
+if (!is_ios) {
+  source_set("media_stream_device_enumerator") {
+    sources = [
+      "media_stream_device_enumerator.h",
+      "media_stream_device_enumerator_impl.cc",
+      "media_stream_device_enumerator_impl.h",
+    ]
 
-  deps = [
+    deps = [
+      "//base",
+      "//content/public/browser",
+      "//third_party/blink/public/common",
+    ]
+  }
+
+  source_set("webrtc") {
+    sources = [
+      "media_stream_devices_controller.cc",
+      "media_stream_devices_controller.h",
+    ]
+
+    public_deps = [ ":media_stream_device_enumerator" ]
+
+    deps = [
+      "//base",
+      "//components/content_settings/core/common",
+      "//components/permissions",
+      "//content/public/browser",
+      "//third_party/blink/public/common",
+    ]
+    if (is_android) {
+      deps += [ "//ui/android" ]
+    }
+  }
+}
+
+source_set("fake_ssl_socket") {
+  visibility = [
+    ":*",
+    "//services/network:*",
+  ]
+  sources = [
+    "fake_ssl_client_socket.cc",
+    "fake_ssl_client_socket.h",
+  ]
+  public_deps = [
     "//base",
-    "//content/public/browser",
-    "//third_party/blink/public/common",
+    "//net",
+    "//net/traffic_annotation",
   ]
 }
 
-source_set("webrtc") {
-  sources = [
-    "media_stream_devices_controller.cc",
-    "media_stream_devices_controller.h",
-  ]
+source_set("unit_tests") {
+  testonly = true
 
-  public_deps = [ ":media_stream_device_enumerator" ]
+  sources = [ "fake_ssl_client_socket_unittest.cc" ]
 
   deps = [
-    "//base",
-    "//components/content_settings/core/common",
-    "//components/permissions",
-    "//content/public/browser",
-    "//third_party/blink/public/common",
+    ":fake_ssl_socket",
+    "//base/test:test_support",
+    "//net:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
   ]
-  if (is_android) {
-    deps += [ "//ui/android" ]
-  }
 }
diff --git a/components/webrtc/DEPS b/components/webrtc/DEPS
index a69a783..8d55cb5 100644
--- a/components/webrtc/DEPS
+++ b/components/webrtc/DEPS
@@ -3,6 +3,7 @@
   "+components/permissions",
   "+content/public/browser",
   "+content/public/common",
+  "+net",
   "+services/network/public/cpp/is_potentially_trustworthy.h",
   "+third_party/blink/public/common",
   "+third_party/blink/public/mojom",
diff --git a/jingle/glue/fake_ssl_client_socket.cc b/components/webrtc/fake_ssl_client_socket.cc
similarity index 98%
rename from jingle/glue/fake_ssl_client_socket.cc
rename to components/webrtc/fake_ssl_client_socket.cc
index 6eb7c66..ee11f24 100644
--- a/jingle/glue/fake_ssl_client_socket.cc
+++ b/components/webrtc/fake_ssl_client_socket.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "jingle/glue/fake_ssl_client_socket.h"
+#include "components/webrtc/fake_ssl_client_socket.h"
 
 #include <stddef.h>
 #include <stdint.h>
@@ -17,7 +17,7 @@
 #include "net/base/io_buffer.h"
 #include "net/base/net_errors.h"
 
-namespace jingle_glue {
+namespace webrtc {
 
 namespace {
 
@@ -375,4 +375,4 @@
   NOTIMPLEMENTED();
 }
 
-}  // namespace jingle_glue
+}  // namespace webrtc
diff --git a/jingle/glue/fake_ssl_client_socket.h b/components/webrtc/fake_ssl_client_socket.h
similarity index 94%
rename from jingle/glue/fake_ssl_client_socket.h
rename to components/webrtc/fake_ssl_client_socket.h
index 67313220..cb39935 100644
--- a/jingle/glue/fake_ssl_client_socket.h
+++ b/components/webrtc/fake_ssl_client_socket.h
@@ -13,8 +13,8 @@
 // NOTE: This StreamSocket implementation does *not* do a real SSL
 // handshake nor does it do any encryption!
 
-#ifndef JINGLE_GLUE_FAKE_SSL_CLIENT_SOCKET_H_
-#define JINGLE_GLUE_FAKE_SSL_CLIENT_SOCKET_H_
+#ifndef COMPONENTS_WEBRTC_FAKE_SSL_CLIENT_SOCKET_H_
+#define COMPONENTS_WEBRTC_FAKE_SSL_CLIENT_SOCKET_H_
 
 #include <stdint.h>
 
@@ -34,7 +34,7 @@
 class SSLInfo;
 }  // namespace net
 
-namespace jingle_glue {
+namespace webrtc {
 
 class FakeSSLClientSocket : public net::StreamSocket {
  public:
@@ -120,6 +120,6 @@
   scoped_refptr<net::DrainableIOBuffer> read_buf_;
 };
 
-}  // namespace jingle_glue
+}  // namespace webrtc
 
-#endif  // JINGLE_GLUE_FAKE_SSL_CLIENT_SOCKET_H_
+#endif  // COMPONENTS_WEBRTC_FAKE_SSL_CLIENT_SOCKET_H_
diff --git a/jingle/glue/fake_ssl_client_socket_unittest.cc b/components/webrtc/fake_ssl_client_socket_unittest.cc
similarity index 87%
rename from jingle/glue/fake_ssl_client_socket_unittest.cc
rename to components/webrtc/fake_ssl_client_socket_unittest.cc
index 0760b5c..ca9f06e 100644
--- a/jingle/glue/fake_ssl_client_socket_unittest.cc
+++ b/components/webrtc/fake_ssl_client_socket_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "jingle/glue/fake_ssl_client_socket.h"
+#include "components/webrtc/fake_ssl_client_socket.h"
 
 #include <stddef.h>
 #include <stdint.h>
@@ -29,7 +29,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace jingle_glue {
+namespace webrtc {
 
 namespace {
 
@@ -88,8 +88,10 @@
 // Break up |data| into a bunch of chunked MockReads/Writes and push
 // them onto |ops|.
 template <net::MockReadWriteType type>
-void AddChunkedOps(base::StringPiece data, size_t chunk_size, net::IoMode mode,
-                   std::vector<net::MockReadWrite<type> >* ops) {
+void AddChunkedOps(base::StringPiece data,
+                   size_t chunk_size,
+                   net::IoMode mode,
+                   std::vector<net::MockReadWrite<type>>* ops) {
   DCHECK_GT(chunk_size, 0U);
   size_t offset = 0;
   while (offset < data.size()) {
@@ -121,9 +123,10 @@
         static_socket_data_provider_.get());
   }
 
-  void ExpectStatus(
-      net::IoMode mode, int expected_status, int immediate_status,
-      net::TestCompletionCallback* test_completion_callback) {
+  void ExpectStatus(net::IoMode mode,
+                    int expected_status,
+                    int immediate_status,
+                    net::TestCompletionCallback* test_completion_callback) {
     if (mode == net::ASYNC) {
       EXPECT_EQ(net::ERR_IO_PENDING, immediate_status);
       int status = test_completion_callback->WaitForResult();
@@ -136,9 +139,10 @@
   // Sets up the mock socket to generate a successful handshake
   // (sliced up according to the parameters) and makes sure the
   // FakeSSLClientSocket behaves as expected.
-  void RunSuccessfulHandshakeTest(
-      net::IoMode mode, size_t read_chunk_size, size_t write_chunk_size,
-      int num_resets) {
+  void RunSuccessfulHandshakeTest(net::IoMode mode,
+                                  size_t read_chunk_size,
+                                  size_t write_chunk_size,
+                                  int num_resets) {
     base::StringPiece ssl_client_hello =
         FakeSSLClientSocket::GetSslClientHello();
     base::StringPiece ssl_server_hello =
@@ -198,8 +202,9 @@
 
   // Sets up the mock socket to generate an unsuccessful handshake
   // FakeSSLClientSocket fails as expected.
-  void RunUnsuccessfulHandshakeTestHelper(
-      net::IoMode mode, int error, HandshakeErrorLocation location) {
+  void RunUnsuccessfulHandshakeTestHelper(net::IoMode mode,
+                                          int error,
+                                          HandshakeErrorLocation location) {
     DCHECK_NE(error, net::OK);
     base::StringPiece ssl_client_hello =
         FakeSSLClientSocket::GetSslClientHello();
@@ -241,8 +246,7 @@
           reads[index].data_len = 0;
         }
         reads.resize(index + 1);
-        if (error ==
-            net::ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ) {
+        if (error == net::ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ) {
           static const char kDummyData[] = "DUMMY";
           reads.push_back(net::MockRead(mode, kDummyData));
         }
@@ -257,19 +261,20 @@
     // an unexpected event.
     int expected_status =
         ((error == net::ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ) ||
-         (error == ERR_MALFORMED_SERVER_HELLO)) ?
-        net::ERR_UNEXPECTED : error;
+         (error == ERR_MALFORMED_SERVER_HELLO))
+            ? net::ERR_UNEXPECTED
+            : error;
 
     net::TestCompletionCallback test_completion_callback;
-    int status = fake_ssl_client_socket.Connect(
-        test_completion_callback.callback());
+    int status =
+        fake_ssl_client_socket.Connect(test_completion_callback.callback());
     EXPECT_FALSE(fake_ssl_client_socket.IsConnected());
     ExpectStatus(mode, expected_status, status, &test_completion_callback);
     EXPECT_FALSE(fake_ssl_client_socket.IsConnected());
   }
 
-  void RunUnsuccessfulHandshakeTest(
-      int error, HandshakeErrorLocation location) {
+  void RunUnsuccessfulHandshakeTest(int error,
+                                    HandshakeErrorLocation location) {
     RunUnsuccessfulHandshakeTestHelper(net::SYNCHRONOUS, error, location);
     RunUnsuccessfulHandshakeTestHelper(net::ASYNC, error, location);
   }
@@ -290,16 +295,15 @@
   net::NetLogWithSource net_log;
   EXPECT_CALL(*mock_client_socket, SetReceiveBufferSize(kReceiveBufferSize));
   EXPECT_CALL(*mock_client_socket, SetSendBufferSize(kSendBufferSize));
-  EXPECT_CALL(*mock_client_socket, GetPeerAddress(&ip_endpoint)).
-      WillOnce(Return(kPeerAddress));
+  EXPECT_CALL(*mock_client_socket, GetPeerAddress(&ip_endpoint))
+      .WillOnce(Return(kPeerAddress));
   EXPECT_CALL(*mock_client_socket, NetLog()).WillOnce(ReturnRef(net_log));
 
   // Takes ownership of |mock_client_socket|.
   FakeSSLClientSocket fake_ssl_client_socket(std::move(mock_client_socket));
   fake_ssl_client_socket.SetReceiveBufferSize(kReceiveBufferSize);
   fake_ssl_client_socket.SetSendBufferSize(kSendBufferSize);
-  EXPECT_EQ(kPeerAddress,
-            fake_ssl_client_socket.GetPeerAddress(&ip_endpoint));
+  EXPECT_EQ(kPeerAddress, fake_ssl_client_socket.GetPeerAddress(&ip_endpoint));
   EXPECT_EQ(&net_log, &fake_ssl_client_socket.NetLog());
 }
 
@@ -332,8 +336,7 @@
 }
 
 TEST_F(FakeSSLClientSocketTest, UnsuccessfulHandshakeWriteError) {
-  RunUnsuccessfulHandshakeTest(net::ERR_OUT_OF_MEMORY,
-                               SEND_CLIENT_HELLO_ERROR);
+  RunUnsuccessfulHandshakeTest(net::ERR_OUT_OF_MEMORY, SEND_CLIENT_HELLO_ERROR);
 }
 
 TEST_F(FakeSSLClientSocketTest, UnsuccessfulHandshakeReadError) {
@@ -342,9 +345,8 @@
 }
 
 TEST_F(FakeSSLClientSocketTest, PeerClosedDuringHandshake) {
-  RunUnsuccessfulHandshakeTest(
-      net::ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ,
-      VERIFY_SERVER_HELLO_ERROR);
+  RunUnsuccessfulHandshakeTest(net::ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ,
+                               VERIFY_SERVER_HELLO_ERROR);
 }
 
 TEST_F(FakeSSLClientSocketTest, MalformedServerHello) {
@@ -354,4 +356,4 @@
 
 }  // namespace
 
-}  // namespace jingle_glue
+}  // namespace webrtc
diff --git a/content/browser/back_forward_cache_features_browsertest.cc b/content/browser/back_forward_cache_features_browsertest.cc
index daa973f..375bbea 100644
--- a/content/browser/back_forward_cache_features_browsertest.cc
+++ b/content/browser/back_forward_cache_features_browsertest.cc
@@ -2018,7 +2018,23 @@
   mojo::Remote<blink::mojom::AppBannerController> controller_;
 };
 
-IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfAppBanner) {
+// The parameter to this test class is whether or not App Banner is supported
+// for BFCache.
+class AppBannerBackForwardCacheBrowserTest
+    : public BackForwardCacheBrowserTest,
+      public ::testing::WithParamInterface<bool> {
+ public:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    if (GetParam()) {
+      EnableFeatureAndSetParams(blink::features::kBackForwardCacheAppBanner, "",
+                                "");
+    }
+    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
+  }
+};
+
+IN_PROC_BROWSER_TEST_P(AppBannerBackForwardCacheBrowserTest,
+                       TestAppBannerCaching) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
   // 1) Navigate to A and request a PWA app banner.
@@ -2037,16 +2053,26 @@
   // 2) Navigate away. Page A requested a PWA app banner, and thus not cached.
   EXPECT_TRUE(NavigateToURL(
       shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
-  delete_observer_rfh.WaitUntilDeleted();
+  if (!GetParam()) {
+    delete_observer_rfh.WaitUntilDeleted();
+  }
 
   // 3) Go back to A.
   ASSERT_TRUE(HistoryGoBack(web_contents()));
-  ExpectNotRestored(
-      {BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
-      {blink::scheduler::WebSchedulerTrackedFeature::kAppBanner}, {}, {}, {},
-      FROM_HERE);
+  if (GetParam()) {
+    ExpectRestored(FROM_HERE);
+  } else {
+    ExpectNotRestored(
+        {BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
+        {blink::scheduler::WebSchedulerTrackedFeature::kAppBanner}, {}, {}, {},
+        FROM_HERE);
+  }
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         AppBannerBackForwardCacheBrowserTest,
+                         testing::Bool());
+
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfWebDatabase) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
diff --git a/content/browser/interest_group/ad_auction_service_impl.cc b/content/browser/interest_group/ad_auction_service_impl.cc
index 16fbc18ad..339a662 100644
--- a/content/browser/interest_group/ad_auction_service_impl.cc
+++ b/content/browser/interest_group/ad_auction_service_impl.cc
@@ -219,10 +219,9 @@
     return;
   }
   // If the interest group API is not allowed for this origin do nothing.
-  if (!GetContentClient()->browser()->IsInterestGroupAPIAllowed(
-          render_frame_host(),
+  if (!IsInterestGroupAPIAllowed(
           ContentBrowserClient::InterestGroupApiOperation::kJoin,
-          main_frame_origin_, group.owner)) {
+          group.owner)) {
     return;
   }
 
@@ -250,10 +249,8 @@
     return;
   }
   // If the interest group API is not allowed for this origin do nothing.
-  if (!GetContentClient()->browser()->IsInterestGroupAPIAllowed(
-          render_frame_host(),
-          ContentBrowserClient::InterestGroupApiOperation::kLeave,
-          main_frame_origin_, origin())) {
+  if (!IsInterestGroupAPIAllowed(
+          ContentBrowserClient::InterestGroupApiOperation::kLeave, origin())) {
     return;
   }
 
@@ -275,10 +272,8 @@
     return;
   }
   // If the interest group API is not allowed for this origin do nothing.
-  if (!GetContentClient()->browser()->IsInterestGroupAPIAllowed(
-          render_frame_host(),
-          ContentBrowserClient::InterestGroupApiOperation::kUpdate,
-          main_frame_origin_, origin())) {
+  if (!IsInterestGroupAPIAllowed(
+          ContentBrowserClient::InterestGroupApiOperation::kUpdate, origin())) {
     return;
   }
   GetInterestGroupManager().UpdateInterestGroupsOfOwner(
@@ -299,12 +294,10 @@
     return;
   }
 
-  const url::Origin& frame_origin = origin();
-  auto* rfh = render_frame_host();
   // If the interest group API is not allowed for this seller do nothing.
-  if (!GetContentClient()->browser()->IsInterestGroupAPIAllowed(
-          rfh, ContentBrowserClient::InterestGroupApiOperation::kSell,
-          frame_origin, config->seller)) {
+  if (!IsInterestGroupAPIAllowed(
+          ContentBrowserClient::InterestGroupApiOperation::kSell,
+          config->seller)) {
     std::move(callback).Run(absl::nullopt);
     return;
   }
@@ -315,10 +308,9 @@
                            ->interest_group_buyers->get_buyers();
   std::copy_if(
       buyers.begin(), buyers.end(), std::back_inserter(filtered_buyers),
-      [rfh, &frame_origin](const url::Origin& buyer) {
-        return GetContentClient()->browser()->IsInterestGroupAPIAllowed(
-            rfh, ContentBrowserClient::InterestGroupApiOperation::kBuy,
-            frame_origin, buyer);
+      [this](const url::Origin& buyer) {
+        return IsInterestGroupAPIAllowed(
+            ContentBrowserClient::InterestGroupApiOperation::kBuy, buyer);
       });
 
   // If there are no buyers (either due to filtering, or in the original auction
@@ -337,7 +329,7 @@
 
   std::unique_ptr<AuctionRunner> auction = AuctionRunner::CreateAndStart(
       &auction_worklet_manager_, this, &GetInterestGroupManager(),
-      std::move(config), std::move(filtered_buyers), frame_origin,
+      std::move(config), std::move(filtered_buyers), /*frame_origin=*/origin(),
       base::BindOnce(&AdAuctionServiceImpl::OnAuctionComplete,
                      base::Unretained(this), std::move(callback)));
   auctions_.insert(std::move(auction));
@@ -464,6 +456,16 @@
   return GetFrame()->BuildClientSecurityState();
 }
 
+bool AdAuctionServiceImpl::IsInterestGroupAPIAllowed(
+    ContentBrowserClient::InterestGroupApiOperation
+        interest_group_api_operation,
+    const url::Origin& origin) const {
+  return GetContentClient()->browser()->IsInterestGroupAPIAllowed(
+      render_frame_host(),
+      ContentBrowserClient::InterestGroupApiOperation::kJoin,
+      main_frame_origin_, origin);
+}
+
 void AdAuctionServiceImpl::OnAuctionComplete(
     RunAdAuctionCallback callback,
     AuctionRunner* auction,
diff --git a/content/browser/interest_group/ad_auction_service_impl.h b/content/browser/interest_group/ad_auction_service_impl.h
index e95e43d..698f437 100644
--- a/content/browser/interest_group/ad_auction_service_impl.h
+++ b/content/browser/interest_group/ad_auction_service_impl.h
@@ -11,6 +11,7 @@
 #include "base/containers/unique_ptr_adapters.h"
 #include "content/browser/interest_group/auction_worklet_manager.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/document_service.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -75,6 +76,13 @@
   // `this` can only be destroyed by DocumentService.
   ~AdAuctionServiceImpl() override;
 
+  // Returns true if `origin` is allowed to perform the specified
+  // `interest_group_api_operation` in this frame. Must be called on worklet /
+  // interest group origins before using them in any interest group API.
+  bool IsInterestGroupAPIAllowed(ContentBrowserClient::InterestGroupApiOperation
+                                     interest_group_api_operation,
+                                 const url::Origin& origin) const;
+
   // Deletes `auction`.
   void OnAuctionComplete(RunAdAuctionCallback callback,
                          AuctionRunner* auction,
diff --git a/content/browser/prerender/prerender_browsertest.cc b/content/browser/prerender/prerender_browsertest.cc
index 5d74c98..db8f574c 100644
--- a/content/browser/prerender/prerender_browsertest.cc
+++ b/content/browser/prerender/prerender_browsertest.cc
@@ -4637,9 +4637,11 @@
 
   // Start prerendering by embedder triggered prerendering.
   std::unique_ptr<PrerenderHandle> prerender_handle =
-      web_contents_impl()->StartPrerendering(kPrerenderingUrl,
-                                             PrerenderTriggerType::kEmbedder,
-                                             "EmbedderSuffixForTest");
+      web_contents_impl()->StartPrerendering(
+          kPrerenderingUrl, PrerenderTriggerType::kEmbedder,
+          "EmbedderSuffixForTest",
+          ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+                                    ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
   EXPECT_TRUE(prerender_handle);
   test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
       *shell()->web_contents(), kPrerenderingUrl);
@@ -4659,9 +4661,11 @@
 
   // Start prerendering by embedder triggered prerendering.
   std::unique_ptr<PrerenderHandle> prerender_handle =
-      web_contents.StartPrerendering(prerendering_url,
-                                     PrerenderTriggerType::kEmbedder,
-                                     "EmbedderSuffixForTest");
+      web_contents.StartPrerendering(
+          prerendering_url, PrerenderTriggerType::kEmbedder,
+          "EmbedderSuffixForTest",
+          ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+                                    ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
   EXPECT_TRUE(prerender_handle);
   test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(web_contents,
                                                             prerendering_url);
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 7a1f6eb..7c0487a 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -174,6 +174,7 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/weak_document_ptr.h"
 #include "content/public/browser/web_ui_url_loader_factory.h"
+#include "content/public/common/alternative_error_page_override_info.mojom-forward.h"
 #include "content/public/common/bindings_policy.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
@@ -6632,7 +6633,8 @@
   bool can_create_window =
       IsActive() && is_render_frame_created() &&
       GetContentClient()->browser()->CanCreateWindow(
-          this, GetLastCommittedURL(), GetMainFrame()->GetLastCommittedURL(),
+          this, GetLastCommittedURL(),
+          GetOutermostMainFrame()->GetLastCommittedURL(),
           last_committed_origin_, params->window_container_type,
           params->target_url, params->referrer.To<Referrer>(),
           params->frame_name, params->disposition, *params->features,
@@ -11033,6 +11035,8 @@
       has_stale_copy_in_cache, error_code, extended_error_code,
       navigation_request->GetResolveErrorInfo(), error_page_content,
       std::move(subresource_loader_factories), std::move(policy_container),
+      GetContentClient()->browser()->GetAlternativeErrorPageOverrideInfo(
+          navigation_request->GetURL(), GetBrowserContext()),
       BuildCommitFailedNavigationCallback(navigation_request));
 }
 
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 26a96fb..ba16f15 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -70,6 +70,7 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host_observer.h"
 #include "content/public/browser/web_ui.h"
+#include "content/public/common/content_client.h"
 #include "content/public/common/javascript_dialog_type.h"
 #include "media/mojo/mojom/interface_factory.mojom-forward.h"
 #include "media/mojo/mojom/media_metrics_provider.mojom-forward.h"
diff --git a/content/browser/service_worker/service_worker_registry.cc b/content/browser/service_worker/service_worker_registry.cc
index d7b81a6f..a208a90 100644
--- a/content/browser/service_worker/service_worker_registry.cc
+++ b/content/browser/service_worker/service_worker_registry.cc
@@ -1523,6 +1523,10 @@
 
 mojo::Remote<storage::mojom::ServiceWorkerStorageControl>&
 ServiceWorkerRegistry::GetRemoteStorageControl() {
+  // TODO(https://crbug.com/1282869): Replace CHECK with DCHECK_CURRENTLY_ON
+  // once the cause is identified.
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
   DCHECK(!(remote_storage_control_.is_bound() &&
            !remote_storage_control_.is_connected()))
       << "Rebinding is not supported yet.";
@@ -1542,7 +1546,9 @@
 void ServiceWorkerRegistry::OnRemoteStorageDisconnected() {
   const size_t kMaxRetryCounts = 100;
 
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  // TODO(https://crbug.com/1282869): Replace CHECK with DCHECK_CURRENTLY_ON
+  // once the cause is identified.
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 
   remote_storage_control_.reset();
 
diff --git a/content/browser/service_worker/service_worker_test_utils.cc b/content/browser/service_worker/service_worker_test_utils.cc
index 12c9c02..b966246 100644
--- a/content/browser/service_worker/service_worker_test_utils.cc
+++ b/content/browser/service_worker/service_worker_test_utils.cc
@@ -23,6 +23,7 @@
 #include "content/browser/service_worker/service_worker_registration.h"
 #include "content/common/frame.mojom.h"
 #include "content/common/frame_messages.mojom.h"
+#include "content/public/common/alternative_error_page_override_info.mojom-forward.h"
 #include "content/public/common/child_process_host.h"
 #include "content/public/test/policy_container_utils.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
@@ -107,6 +108,7 @@
       const absl::optional<std::string>& error_page_content,
       std::unique_ptr<blink::PendingURLLoaderFactoryBundle> subresource_loaders,
       blink::mojom::PolicyContainerPtr policy_container,
+      mojom::AlternativeErrorPageOverrideInfoPtr alternative_error_page_info,
       CommitFailedNavigationCallback callback) override {
     std::move(callback).Run(MinimalDidCommitNavigationLoadParams(), nullptr);
   }
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index bf235e7..5147532 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -9210,16 +9210,14 @@
 std::unique_ptr<PrerenderHandle> WebContentsImpl::StartPrerendering(
     const GURL& prerendering_url,
     PrerenderTriggerType trigger_type,
-    const std::string& embedder_histogram_suffix) {
-  // TODO(https://crbug.com/1166085): Use the ui::PageTransition value passed
-  // from Embedders for flexibility.
+    const std::string& embedder_histogram_suffix,
+    ui::PageTransition page_transition) {
   PrerenderAttributes attributes(
       prerendering_url, trigger_type, embedder_histogram_suffix,
       content::Referrer(), /*initiator_origin=*/absl::nullopt, prerendering_url,
       content::ChildProcessHost::kInvalidUniqueID,
       /*initiator_frame_token=*/absl::nullopt, ukm::kInvalidSourceId,
-      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
-                                ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
+      page_transition);
   int frame_tree_node_id =
       GetPrerenderHostRegistry()->CreateAndStartHost(attributes, *this);
 
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index ad32420..d2d34b2 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -856,7 +856,8 @@
   std::unique_ptr<PrerenderHandle> StartPrerendering(
       const GURL& prerendering_url,
       PrerenderTriggerType trigger_type,
-      const std::string& embedder_histogram_suffix) override;
+      const std::string& embedder_histogram_suffix,
+      ui::PageTransition page_transition) override;
 
   // NavigatorDelegate ---------------------------------------------------------
 
diff --git a/content/common/navigation_client.mojom b/content/common/navigation_client.mojom
index 0459bfe..5962b568 100644
--- a/content/common/navigation_client.mojom
+++ b/content/common/navigation_client.mojom
@@ -5,6 +5,7 @@
 module content.mojom;
 
 import "content/common/frame_messages.mojom";
+import "content/public/common/alternative_error_page_override_info.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "mojo/public/mojom/base/unguessable_token.mojom";
 import "services/network/public/mojom/network_param.mojom";
@@ -265,6 +266,9 @@
   // When the Network Service is enabled, |subresource_loader_factories| may
   // also be provided by the browser as a means for the renderer to load
   // subresources where applicable.
+  //
+  // |info| contains values used to customise the error page. It may be null if
+  // the page which displays an error should not be customized.
   CommitFailedNavigation(
       blink.mojom.CommonNavigationParams common_params,
       blink.mojom.CommitNavigationParams request_params,
@@ -274,7 +278,8 @@
       network.mojom.ResolveErrorInfo resolve_error_info,
       string? error_page_content,
       blink.mojom.URLLoaderFactoryBundle? subresource_loader_factories,
-      blink.mojom.PolicyContainer policy_container)
+      blink.mojom.PolicyContainer policy_container,
+      AlternativeErrorPageOverrideInfo? alternative_error_page_info)
       => (DidCommitProvisionalLoadParams params,
           DidCommitProvisionalLoadInterfaceParams? interface_params);
 };
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 2579cf2..7c700a4 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -1297,4 +1297,11 @@
   return base::FeatureList::IsEnabled(features::kFirstPartySets);
 }
 
+mojom::AlternativeErrorPageOverrideInfoPtr
+ContentBrowserClient::GetAlternativeErrorPageOverrideInfo(
+    const GURL& url,
+    BrowserContext* browser_context) {
+  return nullptr;
+}
+
 }  // namespace content
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 7d3fafd5..3aaacce 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -34,6 +34,7 @@
 #include "content/public/browser/mojo_binder_policy_map.h"
 #include "content/public/browser/storage_partition_config.h"
 #include "content/public/browser/web_ui_browser_interface_broker_registry.h"
+#include "content/public/common/alternative_error_page_override_info.mojom.h"
 #include "content/public/common/main_function_params.h"
 #include "content/public/common/page_visibility_state.h"
 #include "content/public/common/window_container_type.mojom-forward.h"
@@ -2176,6 +2177,14 @@
 
   // Returns true if First-Party Sets is enabled.
   virtual bool IsFirstPartySetsEnabled();
+
+  // Gets information required for an alternative error page from web app's
+  // manifest for |url|, including theme color, background color and app short
+  // name. Information is returned in a struct. Default implementation returns
+  // nullptr.
+  virtual mojom::AlternativeErrorPageOverrideInfoPtr
+  GetAlternativeErrorPageOverrideInfo(const GURL& url,
+                                      BrowserContext* browser_context);
 };
 
 }  // namespace content
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index b980a289..7e433cf 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -1340,7 +1340,8 @@
   virtual std::unique_ptr<PrerenderHandle> StartPrerendering(
       const GURL& prerendering_url,
       PrerenderTriggerType trigger_type,
-      const std::string& embedder_histogram_suffix) = 0;
+      const std::string& embedder_histogram_suffix,
+      ui::PageTransition page_transition) = 0;
 
  private:
   // This interface should only be implemented inside content.
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn
index 0179bbb..1a92f220 100644
--- a/content/public/common/BUILD.gn
+++ b/content/public/common/BUILD.gn
@@ -325,6 +325,7 @@
   ]
 
   sources = [
+    "alternative_error_page_override_info.mojom",
     "drop_data.mojom",
     "resource_usage_reporter.mojom",
     "webplugininfo.mojom",
@@ -338,6 +339,7 @@
   deps = [
     "//mojo/public/mojom/base:base",
     "//services/network/public/mojom",
+    "//skia/public/mojom",
     "//url/mojom:url_mojom_gurl",
 
     # This dependency is really a dependency for the typemaps, but we need
diff --git a/content/public/common/alternative_error_page_override_info.mojom b/content/public/common/alternative_error_page_override_info.mojom
new file mode 100644
index 0000000..7e608f1
--- /dev/null
+++ b/content/public/common/alternative_error_page_override_info.mojom
@@ -0,0 +1,22 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module content.mojom;
+
+import "mojo/public/mojom/base/values.mojom";
+
+// |alternative_error_page_params| dictionary key values.
+const string kAppShortName = "app_short_name";
+const string kThemeColor = "theme_color";
+const string kBackgroundColor = "background_color";
+
+// Stores information about an alternative error page to use in place of the
+// embedder's default error page.
+struct AlternativeErrorPageOverrideInfo {
+  // Id of HTML resource of alternative error page to be used.
+  uint32 resource_id;
+
+  // A dictionary containing information needed by alternative error page.
+  mojo_base.mojom.DictionaryValue alternative_error_page_params;
+};
\ No newline at end of file
diff --git a/content/public/renderer/content_renderer_client.cc b/content/public/renderer/content_renderer_client.cc
index 1316c34..46efb19 100644
--- a/content/public/renderer/content_renderer_client.cc
+++ b/content/public/renderer/content_renderer_client.cc
@@ -57,8 +57,10 @@
     const blink::WebURLError& error,
     const std::string& http_method,
     int http_status,
+    mojom::AlternativeErrorPageOverrideInfoPtr alternative_error_page_info,
     std::string* error_html) {
-  PrepareErrorPage(render_frame, error, http_method, error_html);
+  PrepareErrorPage(render_frame, error, http_method,
+                   std::move(alternative_error_page_info), error_html);
 }
 
 bool ContentRendererClient::DeferMediaLoad(RenderFrame* render_frame,
diff --git a/content/public/renderer/content_renderer_client.h b/content/public/renderer/content_renderer_client.h
index b914cb9..3c5b962a 100644
--- a/content/public/renderer/content_renderer_client.h
+++ b/content/public/renderer/content_renderer_client.h
@@ -18,6 +18,7 @@
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "build/build_config.h"
 #include "content/common/content_export.h"
+#include "content/public/common/alternative_error_page_override_info.mojom.h"
 #include "content/public/common/content_client.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/supported_types.h"
@@ -136,17 +137,23 @@
   // be set to a HTML page containing the details of the error and maybe links
   // to more info. Note that |error_html| may be not written to in certain cases
   // (lack of information on the error code) so the caller should take care to
-  // initialize it with a safe default before the call.
-  virtual void PrepareErrorPage(content::RenderFrame* render_frame,
-                                const blink::WebURLError& error,
-                                const std::string& http_method,
-                                std::string* error_html) {}
+  // initialize it with a safe default before the call. |info| contains PWA
+  // information used to customise error page, and is set to null if
+  // the webpage that goes offline is not within the scope of a PWA.
+
+  virtual void PrepareErrorPage(
+      content::RenderFrame* render_frame,
+      const blink::WebURLError& error,
+      const std::string& http_method,
+      mojom::AlternativeErrorPageOverrideInfoPtr alternative_error_page_info,
+      std::string* error_html) {}
 
   virtual void PrepareErrorPageForHttpStatusError(
       content::RenderFrame* render_frame,
       const blink::WebURLError& error,
       const std::string& http_method,
       int http_status,
+      mojom::AlternativeErrorPageOverrideInfoPtr alternative_error_page_info,
       std::string* error_html);
 
   // Allows the embedder to control when media resources are loaded. Embedders
diff --git a/content/public/test/prerender_test_util.cc b/content/public/test/prerender_test_util.cc
index 262f11b..6c5dd617 100644
--- a/content/public/test/prerender_test_util.cc
+++ b/content/public/test/prerender_test_util.cc
@@ -315,17 +315,19 @@
 PrerenderTestHelper::AddEmbedderTriggeredPrerenderAsync(
     const GURL& prerendering_url,
     PrerenderTriggerType trigger_type,
-    const std::string& embedder_histogram_suffix) {
+    const std::string& embedder_histogram_suffix,
+    ui::PageTransition page_transition) {
   TRACE_EVENT("test", "PrerenderTestHelper::AddEmbedderTriggeredPrerenderAsync",
               "prerendering_url", prerendering_url, "trigger_type",
               trigger_type, "embedder_histogram_suffix",
-              embedder_histogram_suffix);
+              embedder_histogram_suffix, "page_transition", page_transition);
   if (!content::BrowserThread::CurrentlyOn(BrowserThread::UI))
     return nullptr;
 
   WebContents* web_contents = GetWebContents();
   return web_contents->StartPrerendering(prerendering_url, trigger_type,
-                                         embedder_histogram_suffix);
+                                         embedder_histogram_suffix,
+                                         page_transition);
 }
 
 void PrerenderTestHelper::NavigatePrerenderedPage(int host_id,
diff --git a/content/public/test/prerender_test_util.h b/content/public/test/prerender_test_util.h
index 35c9aae..4ab0a9a 100644
--- a/content/public/test/prerender_test_util.h
+++ b/content/public/test/prerender_test_util.h
@@ -122,7 +122,8 @@
   std::unique_ptr<PrerenderHandle> AddEmbedderTriggeredPrerenderAsync(
       const GURL& prerendering_url,
       PrerenderTriggerType trigger_type,
-      const std::string& embedder_histogram_suffix);
+      const std::string& embedder_histogram_suffix,
+      ui::PageTransition page_transition);
 
   // This navigates, but does not activate, the prerendered page.
   void NavigatePrerenderedPage(int host_id, const GURL& gurl);
diff --git a/content/renderer/browser_render_view_browsertest.cc b/content/renderer/browser_render_view_browsertest.cc
index a5d64e95..301815c 100644
--- a/content/renderer/browser_render_view_browsertest.cc
+++ b/content/renderer/browser_render_view_browsertest.cc
@@ -53,10 +53,12 @@
         latest_error_reason_(0),
         latest_error_stale_copy_in_cache_(false) {}
 
-  void PrepareErrorPage(content::RenderFrame* render_frame,
-                        const blink::WebURLError& error,
-                        const std::string& http_method,
-                        std::string* error_html) override {
+  void PrepareErrorPage(
+      content::RenderFrame* render_frame,
+      const blink::WebURLError& error,
+      const std::string& http_method,
+      mojom::AlternativeErrorPageOverrideInfoPtr alternative_error_page_info,
+      std::string* error_html) override {
     if (error_html)
       *error_html = "A suffusion of yellow.";
     latest_error_valid_ = true;
diff --git a/content/renderer/navigation_client.cc b/content/renderer/navigation_client.cc
index 927fd6ab..b74deeb 100644
--- a/content/renderer/navigation_client.cc
+++ b/content/renderer/navigation_client.cc
@@ -70,13 +70,15 @@
     const absl::optional<std::string>& error_page_content,
     std::unique_ptr<blink::PendingURLLoaderFactoryBundle> subresource_loaders,
     blink::mojom::PolicyContainerPtr policy_container,
+    mojom::AlternativeErrorPageOverrideInfoPtr alternative_error_page_info,
     CommitFailedNavigationCallback callback) {
   ResetDisconnectionHandler();
   render_frame_->CommitFailedNavigation(
       std::move(common_params), std::move(commit_params),
       has_stale_copy_in_cache, error_code, extended_error_code,
       resolve_error_info, error_page_content, std::move(subresource_loaders),
-      std::move(policy_container), std::move(callback));
+      std::move(policy_container), std::move(alternative_error_page_info),
+      std::move(callback));
 }
 
 void NavigationClient::Bind(
diff --git a/content/renderer/navigation_client.h b/content/renderer/navigation_client.h
index ce59bf9..3f90025 100644
--- a/content/renderer/navigation_client.h
+++ b/content/renderer/navigation_client.h
@@ -6,6 +6,7 @@
 #define CONTENT_RENDERER_NAVIGATION_CLIENT_H_
 
 #include "content/common/navigation_client.mojom.h"
+#include "content/public/common/alternative_error_page_override_info.mojom.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 
@@ -49,6 +50,7 @@
       const absl::optional<std::string>& error_page_content,
       std::unique_ptr<blink::PendingURLLoaderFactoryBundle> subresource_loaders,
       blink::mojom::PolicyContainerPtr policy_container,
+      mojom::AlternativeErrorPageOverrideInfoPtr alternative_error_page_info,
       CommitFailedNavigationCallback callback) override;
 
   void Bind(mojo::PendingAssociatedReceiver<mojom::NavigationClient> receiver);
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 5b0d4184..bc55abf 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -2957,6 +2957,7 @@
     std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
         subresource_loader_factories,
     blink::mojom::PolicyContainerPtr policy_container,
+    mojom::AlternativeErrorPageOverrideInfoPtr alternative_error_page_info,
     mojom::NavigationClient::CommitFailedNavigationCallback callback) {
   TRACE_EVENT1("navigation,benchmark,rail",
                "RenderFrameImpl::CommitFailedNavigation", "id", routing_id_);
@@ -3029,7 +3030,7 @@
     DCHECK_NE(commit_params->http_response_code, -1);
     GetContentClient()->renderer()->PrepareErrorPageForHttpStatusError(
         this, error, navigation_params->http_method.Ascii(),
-        commit_params->http_response_code, error_html_ptr);
+        commit_params->http_response_code, nullptr, error_html_ptr);
   } else {
     if (error_page_content) {
       error_html = error_page_content.value();
@@ -3039,7 +3040,8 @@
     // null above, PrepareErrorPage might have other side effects e.g. setting
     // some error-related states, so we should still call it.
     GetContentClient()->renderer()->PrepareErrorPage(
-        this, error, navigation_params->http_method.Ascii(), error_html_ptr);
+        this, error, navigation_params->http_method.Ascii(),
+        std::move(alternative_error_page_info), error_html_ptr);
   }
 
   // Make sure we never show errors in view source mode.
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index ed5cb44..4e5d10d 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -38,6 +38,7 @@
 #include "content/common/render_accessibility.mojom.h"
 #include "content/common/renderer.mojom.h"
 #include "content/common/web_ui.mojom.h"
+#include "content/public/common/alternative_error_page_override_info.mojom.h"
 #include "content/public/common/referrer.h"
 #include "content/public/common/stop_find_action.h"
 #include "content/public/common/widget_type.h"
@@ -456,6 +457,7 @@
       std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
           subresource_loader_factories,
       blink::mojom::PolicyContainerPtr policy_container,
+      mojom::AlternativeErrorPageOverrideInfoPtr alternative_error_page_info,
       mojom::NavigationClient::CommitFailedNavigationCallback
           per_navigation_mojo_interface_callback);
 
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 557b553..7e4d769 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -2561,6 +2561,8 @@
     void PrepareErrorPage(content::RenderFrame* render_frame,
                           const blink::WebURLError& error,
                           const std::string& http_method,
+                          content::mojom::AlternativeErrorPageOverrideInfoPtr
+                              alternative_error_page_info,
                           std::string* error_html) override {
       if (error_html)
         *error_html = "A suffusion of yellow.";
diff --git a/content/shell/renderer/shell_content_renderer_client.cc b/content/shell/renderer/shell_content_renderer_client.cc
index c928704..8ae4719 100644
--- a/content/shell/renderer/shell_content_renderer_client.cc
+++ b/content/shell/renderer/shell_content_renderer_client.cc
@@ -154,6 +154,8 @@
     RenderFrame* render_frame,
     const blink::WebURLError& error,
     const std::string& http_method,
+    content::mojom::AlternativeErrorPageOverrideInfoPtr
+        alternative_error_page_info,
     std::string* error_html) {
   if (error_html && error_html->empty()) {
     *error_html =
@@ -171,6 +173,8 @@
     const blink::WebURLError& error,
     const std::string& http_method,
     int http_status,
+    content::mojom::AlternativeErrorPageOverrideInfoPtr
+        alternative_error_page_info,
     std::string* error_html) {
   if (error_html) {
     *error_html =
diff --git a/content/shell/renderer/shell_content_renderer_client.h b/content/shell/renderer/shell_content_renderer_client.h
index 51801ff..b6d196d 100644
--- a/content/shell/renderer/shell_content_renderer_client.h
+++ b/content/shell/renderer/shell_content_renderer_client.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "build/build_config.h"
+#include "content/public/common/alternative_error_page_override_info.mojom-forward.h"
 #include "content/public/renderer/content_renderer_client.h"
 #include "media/mojo/buildflags.h"
 
@@ -30,12 +31,17 @@
   void PrepareErrorPage(RenderFrame* render_frame,
                         const blink::WebURLError& error,
                         const std::string& http_method,
+                        content::mojom::AlternativeErrorPageOverrideInfoPtr
+                            alternative_error_page_info,
                         std::string* error_html) override;
-  void PrepareErrorPageForHttpStatusError(content::RenderFrame* render_frame,
-                                          const blink::WebURLError& error,
-                                          const std::string& http_method,
-                                          int http_status,
-                                          std::string* error_html) override;
+  void PrepareErrorPageForHttpStatusError(
+      content::RenderFrame* render_frame,
+      const blink::WebURLError& error,
+      const std::string& http_method,
+      int http_status,
+      content::mojom::AlternativeErrorPageOverrideInfoPtr
+          alternative_error_page_info,
+      std::string* error_html) override;
 
   void DidInitializeWorkerContextOnWorkerThread(
       v8::Local<v8::Context> context) override;
diff --git a/content/test/data/accessibility/aom/aom-modal-dialog-expected-uia-win.txt b/content/test/data/accessibility/aom/aom-modal-dialog-expected-uia-win.txt
index cb7d5e84..3e4ff43 100644
--- a/content/test/data/accessibility/aom/aom-modal-dialog-expected-uia-win.txt
+++ b/content/test/data/accessibility/aom/aom-modal-dialog-expected-uia-win.txt
@@ -2,5 +2,5 @@
 ++Group LocalizedControlType='group' IsControlElement=false
 ++++Text LocalizedControlType='text' Name='Content outside modal dialog. '
 ++++Button LocalizedControlType='button' Name='Button outside modal dialog.'
-++Pane LocalizedControlType='dialog' Name='Modal dialog.' Window.IsModal=true
+++Window LocalizedControlType='dialog' Name='Modal dialog.' Window.IsModal=true
 ++++Button LocalizedControlType='button' Name='Button inside modal dialog.'
diff --git a/content/test/data/accessibility/aom/aom-modal-dialog-expected-uia-win7.txt b/content/test/data/accessibility/aom/aom-modal-dialog-expected-uia-win7.txt
index d0369093..1e8ec11 100644
--- a/content/test/data/accessibility/aom/aom-modal-dialog-expected-uia-win7.txt
+++ b/content/test/data/accessibility/aom/aom-modal-dialog-expected-uia-win7.txt
@@ -2,5 +2,5 @@
 ++Group LocalizedControlType='group' IsControlElement=false
 ++++Text LocalizedControlType='text' Name='Content outside modal dialog. '
 ++++Button LocalizedControlType='button' Name='Button outside modal dialog.'
-++Pane LocalizedControlType='pane' Name='Modal dialog.' Window.IsModal=true
+++Window LocalizedControlType='pane' Name='Modal dialog.' Window.IsModal=true
 ++++Button LocalizedControlType='button' Name='Button inside modal dialog.'
diff --git a/content/test/data/accessibility/html/dialog-expected-uia-win.txt b/content/test/data/accessibility/html/dialog-expected-uia-win.txt
index 68f7175..002cbb2 100644
--- a/content/test/data/accessibility/html/dialog-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/dialog-expected-uia-win.txt
@@ -1,3 +1,3 @@
 Document
-++Pane IsControlElement=false Window.IsModal=false
+++Window IsControlElement=false Window.IsModal=false
 ++++Text Name='Text in dialog'
diff --git a/content/test/data/accessibility/html/modal-dialog-opened-expected-uia-win.txt b/content/test/data/accessibility/html/modal-dialog-opened-expected-uia-win.txt
index f4955efd..341f2a5 100644
--- a/content/test/data/accessibility/html/modal-dialog-opened-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/modal-dialog-opened-expected-uia-win.txt
@@ -1,5 +1,5 @@
 Document
-++Pane IsControlElement=false Window.IsModal=true
+++Window IsControlElement=false Window.IsModal=true
 ++++Text Name='The dialog subtree should be the only text content in the accessibility tree. '
 ++++Hyperlink Name='Link inside the dialog.'
 ++++++Text Name='Link inside the dialog.' IsControlElement=false
diff --git a/content/test/data/accessibility/html/modal-dialog-stack-expected-uia-win.txt b/content/test/data/accessibility/html/modal-dialog-stack-expected-uia-win.txt
index 8ac0f8d..b37f7026 100644
--- a/content/test/data/accessibility/html/modal-dialog-stack-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/modal-dialog-stack-expected-uia-win.txt
@@ -1,4 +1,4 @@
 Document
-++Pane IsControlElement=false Window.IsModal=true
+++Window IsControlElement=false Window.IsModal=true
 ++++Text Name='This is the now active dialog. Of course it should be in the tree. '
 ++++Button Name='This is in the active dialog and should be in the tree.'
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 08369096..9ff5853 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -398,9 +398,13 @@
 crbug.com/angleproject/6430 [ mac passthrough angle-metal ] WebglExtension_EXT_disjoint_timer_query_webgl2 [ Failure ]
 crbug.com/angleproject/6430 [ mac passthrough angle-metal ] WebglExtension_WEBGL_draw_instanced_base_vertex_base_instance [ Failure ]
 crbug.com/angleproject/6430 [ mac passthrough angle-metal ] WebglExtension_WEBGL_multi_draw_instanced_base_vertex_base_instance [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal ] conformance/extensions/s3tc-and-rgtc.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal ] conformance/extensions/webgl-compressed-texture-s3tc-srgb.html [ Failure ]
 crbug.com/angleproject/6430 [ mac passthrough angle-metal ] conformance/ogles/GL/build/build_009_to_016.html [ Failure ]
 crbug.com/angleproject/6430 [ mac passthrough angle-metal ] conformance/ogles/GL/build/build_017_to_024.html [ Failure ]
 crbug.com/angleproject/6430 [ mac passthrough angle-metal ] conformance2/rendering/fs-color-type-mismatch-color-buffer-type.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal ] deqp/functional/gles3/fboinvalidate/sub.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal ] deqp/functional/gles3/fboinvalidate/whole.html [ Failure ]
 crbug.com/angleproject/6430 [ mac passthrough angle-metal ] deqp/functional/gles3/occlusionquery_conservative.html [ Failure ]
 crbug.com/angleproject/6430 [ mac passthrough angle-metal ] deqp/functional/gles3/occlusionquery_strict.html [ Failure ]
 crbug.com/angleproject/6430 [ mac passthrough angle-metal ] deqp/functional/gles3/fbomultisample.2_samples.html [ Failure ]
diff --git a/content/test/test_render_frame.cc b/content/test/test_render_frame.cc
index b4ea4aea..68d4358 100644
--- a/content/test/test_render_frame.cc
+++ b/content/test/test_render_frame.cc
@@ -298,9 +298,10 @@
           network::NotImplementedURLLoaderFactory::Create());
   mock_navigation_client_->CommitFailedNavigation(
       std::move(common_params), std::move(commit_params),
-      false /* has_stale_copy_in_cache */, error_code,
-      0 /* extended_error_code */, resolve_error_info, error_page_content,
+      /*has_stale_copy_in_cache=*/false, error_code,
+      /*extended_error_code=*/0, resolve_error_info, error_page_content,
       std::move(pending_factory_bundle), CreateStubPolicyContainer(),
+      /*alternative_error_page_info=*/nullptr,
       base::BindOnce(&MockFrameHost::DidCommitProvisionalLoad,
                      base::Unretained(mock_frame_host_.get())));
 }
diff --git a/docs/security/mojo.md b/docs/security/mojo.md
index bb8f0b0f7..969689afd 100644
--- a/docs/security/mojo.md
+++ b/docs/security/mojo.md
@@ -249,7 +249,7 @@
  public:
   // ...
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   void UpdateBrowserControlsState(bool enable_hiding, bool enable_showing,
                                   bool animate);
 #endif
@@ -271,7 +271,7 @@
  public:
   // ...
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   void UpdateBrowserControlsState(bool enable_hiding, bool enable_showing,
                                   bool animate) override;
 #else
diff --git a/docs/ui/views/platform_style.md b/docs/ui/views/platform_style.md
index 7ebf3bf9..2aa6f2f 100644
--- a/docs/ui/views/platform_style.md
+++ b/docs/ui/views/platform_style.md
@@ -48,7 +48,7 @@
 PlatformStyle, instead of ifdefs inside the control's implementation. For
 example, instead of:
 
-    #if defined(OS_BAR)
+    #if BUILDFLAG(IS_BAR)
     void Foo::DoThing() { ... }
     #else
     void Foo::DoThing() { ... }
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 4d0ea7f..2d35243 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1676,6 +1676,7 @@
   ACTION_OPENPOPUP = 1613,
   OS_TELEMETRY_GETCPUINFO = 1614,
   FILEMANAGERPRIVATE_OPENURL = 1615,
+  FILEMANAGERPRIVATE_GETFRAMECOLOR = 1616,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/renderer/BUILD.gn b/extensions/renderer/BUILD.gn
index 2180db1..ee7aab0 100644
--- a/extensions/renderer/BUILD.gn
+++ b/extensions/renderer/BUILD.gn
@@ -172,6 +172,8 @@
     "render_frame_observer_natives.h",
     "renderer_extension_registry.cc",
     "renderer_extension_registry.h",
+    "renderer_i18n_util.cc",
+    "renderer_i18n_util.h",
     "resource_bundle_source_map.cc",
     "resource_bundle_source_map.h",
     "runtime_custom_bindings.cc",
diff --git a/extensions/renderer/i18n_hooks_delegate.cc b/extensions/renderer/i18n_hooks_delegate.cc
index c63f7e7..42893ae 100644
--- a/extensions/renderer/i18n_hooks_delegate.cc
+++ b/extensions/renderer/i18n_hooks_delegate.cc
@@ -14,11 +14,11 @@
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_thread.h"
 #include "extensions/common/extension.h"
-#include "extensions/common/extension_messages.h"
 #include "extensions/common/message_bundle.h"
 #include "extensions/renderer/bindings/api_binding_types.h"
 #include "extensions/renderer/bindings/js_runner.h"
 #include "extensions/renderer/get_script_context.h"
+#include "extensions/renderer/renderer_i18n_util.h"
 #include "extensions/renderer/script_context.h"
 #include "gin/converter.h"
 #include "gin/data_object_builder.h"
@@ -152,27 +152,10 @@
                                     content::RenderFrame* render_frame,
                                     v8::Local<v8::Context> context) {
   v8::Isolate* isolate = context->GetIsolate();
-  L10nMessagesMap* l10n_messages = nullptr;
-  {
-    ExtensionToL10nMessagesMap& messages_map = *GetExtensionToL10nMessagesMap();
-    auto iter = messages_map.find(extension_id);
-    if (iter != messages_map.end()) {
-      l10n_messages = &iter->second;
-    } else {
-      if (!render_frame)
-        return v8::Undefined(isolate);
-
-      l10n_messages = &messages_map[extension_id];
-      // A sync call to load message catalogs for current extension.
-      // TODO(devlin): Wait, what?! A synchronous call to the browser to perform
-      // potentially blocking work reading files from disk? That's Bad.
-      {
-        SCOPED_UMA_HISTOGRAM_TIMER("Extensions.SyncGetMessageBundle");
-        render_frame->Send(
-            new ExtensionHostMsg_GetMessageBundle(extension_id, l10n_messages));
-      }
-    }
-  }
+  const L10nMessagesMap* l10n_messages =
+      i18n_util::GetRendererMessagesMap(extension_id, render_frame);
+  if (!l10n_messages)
+    return v8::Undefined(isolate);
 
   std::string message =
       MessageBundle::GetL10nMessage(message_name, *l10n_messages);
diff --git a/extensions/renderer/native_renderer_messaging_service.cc b/extensions/renderer/native_renderer_messaging_service.cc
index 6ecf3673..f2dd29eca 100644
--- a/extensions/renderer/native_renderer_messaging_service.cc
+++ b/extensions/renderer/native_renderer_messaging_service.cc
@@ -26,6 +26,7 @@
 #include "extensions/renderer/api_activity_logger.h"
 #include "extensions/renderer/bindings/api_binding_util.h"
 #include "extensions/renderer/bindings/get_per_context_data.h"
+#include "extensions/renderer/extension_interaction_provider.h"
 #include "extensions/renderer/get_script_context.h"
 #include "extensions/renderer/ipc_message_sender.h"
 #include "extensions/renderer/message_target.h"
@@ -42,6 +43,7 @@
 #include "third_party/blink/public/web/web_local_frame.h"
 #include "third_party/blink/public/web/web_scoped_window_focus_allowed_indicator.h"
 #include "v8/include/v8-context.h"
+#include "v8/include/v8-local-handle.h"
 #include "v8/include/v8-persistent-handle.h"
 
 using blink::mojom::UserActivationNotificationType;
@@ -334,6 +336,37 @@
   if (!ContextHasMessagePort(script_context, target_port_id))
     return;
 
+  if (script_context->IsForServiceWorker()) {
+    DeliverMessageToWorker(message, target_port_id, script_context);
+  } else {
+    DeliverMessageToBackgroundPage(message, target_port_id, script_context);
+  }
+}
+
+void NativeRendererMessagingService::DeliverMessageToWorker(
+    const Message& message,
+    const PortId& target_port_id,
+    ScriptContext* script_context) {
+  // Note |scoped_extension_interaction| requires a HandleScope.
+  v8::Isolate* isolate = script_context->isolate();
+  v8::HandleScope handle_scope(isolate);
+  std::unique_ptr<InteractionProvider::Scope> scoped_extension_interaction;
+  if (message.user_gesture) {
+    // TODO(https://crbug.com/977629): Add logging for privilege level for
+    // sender and receiver and decide if want to allow unprivileged to
+    // privileged support.
+    scoped_extension_interaction =
+        ExtensionInteractionProvider::Scope::ForWorker(
+            script_context->v8_context());
+  }
+
+  DispatchOnMessageToListeners(script_context, message, target_port_id);
+}
+
+void NativeRendererMessagingService::DeliverMessageToBackgroundPage(
+    const Message& message,
+    const PortId& target_port_id,
+    ScriptContext* script_context) {
   std::unique_ptr<blink::WebScopedWindowFocusAllowedIndicator>
       allow_window_focus;
   if (message.user_gesture && script_context->web_frame()) {
diff --git a/extensions/renderer/native_renderer_messaging_service.h b/extensions/renderer/native_renderer_messaging_service.h
index 6969c5f..890a54c 100644
--- a/extensions/renderer/native_renderer_messaging_service.h
+++ b/extensions/renderer/native_renderer_messaging_service.h
@@ -156,6 +156,12 @@
   void DeliverMessageToScriptContext(const Message& message,
                                      const PortId& target_port_id,
                                      ScriptContext* script_context);
+  void DeliverMessageToWorker(const Message& message,
+                              const PortId& target_port_id,
+                              ScriptContext* script_context);
+  void DeliverMessageToBackgroundPage(const Message& message,
+                                      const PortId& target_port_id,
+                                      ScriptContext* script_context);
   void DispatchOnDisconnectToScriptContext(const PortId& port_id,
                                            const std::string& error_message,
                                            ScriptContext* script_context);
diff --git a/extensions/renderer/renderer_i18n_util.cc b/extensions/renderer/renderer_i18n_util.cc
new file mode 100644
index 0000000..61952147
--- /dev/null
+++ b/extensions/renderer/renderer_i18n_util.cc
@@ -0,0 +1,40 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/renderer_i18n_util.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "extensions/common/extension_messages.h"
+#include "ipc/ipc_sender.h"
+
+namespace extensions::i18n_util {
+
+const L10nMessagesMap* GetRendererMessagesMap(const ExtensionId& extension_id,
+                                              IPC::Sender* message_sender) {
+  ExtensionToL10nMessagesMap& messages_map = *GetExtensionToL10nMessagesMap();
+  auto iter = messages_map.find(extension_id);
+  if (iter != messages_map.end())
+    return &iter->second;
+
+  if (!message_sender)
+    return nullptr;
+
+  L10nMessagesMap& l10n_messages = messages_map[extension_id];
+
+  // A sync call to load message catalogs for current extension.
+  // TODO(devlin): Wait, what?! A synchronous call to the browser to perform
+  // potentially blocking work reading files from disk? That's Bad.
+  {
+    SCOPED_UMA_HISTOGRAM_TIMER("Extensions.SyncGetMessageBundle");
+    message_sender->Send(
+        new ExtensionHostMsg_GetMessageBundle(extension_id, &l10n_messages));
+  }
+  // In practice, the messages map is never empty, because it contains at least
+  // the @@extension_id value. But this doesn't hold in renderer unit tests.
+  // DCHECK(!l10n_messages->empty());
+
+  return &l10n_messages;
+}
+
+}  // namespace extensions::i18n_util
diff --git a/extensions/renderer/renderer_i18n_util.h b/extensions/renderer/renderer_i18n_util.h
new file mode 100644
index 0000000..446fb3c
--- /dev/null
+++ b/extensions/renderer/renderer_i18n_util.h
@@ -0,0 +1,25 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_RENDERER_I18N_UTIL_H_
+#define EXTENSIONS_RENDERER_RENDERER_I18N_UTIL_H_
+
+#include "extensions/common/extension_id.h"
+#include "extensions/common/message_bundle.h"
+
+namespace IPC {
+class Sender;
+}
+
+namespace extensions::i18n_util {
+
+// Retrieves the L10nMessagesMap associated with the given `extension_id`. This
+// may trigger a (sync!) IPC to the browser in order to fetch the map if it
+// hasn't been initialized.
+const L10nMessagesMap* GetRendererMessagesMap(const ExtensionId& extension_id,
+                                              IPC::Sender* message_sender);
+
+}  // namespace extensions::i18n_util
+
+#endif  // EXTENSIONS_RENDERER_RENDERER_I18N_UTIL_H_
diff --git a/gpu/command_buffer/service/wrapped_sk_image.cc b/gpu/command_buffer/service/wrapped_sk_image.cc
index 40d142e..5944ca5c 100644
--- a/gpu/command_buffer/service/wrapped_sk_image.cc
+++ b/gpu/command_buffer/service/wrapped_sk_image.cc
@@ -115,9 +115,13 @@
     auto destroy_resources = [](scoped_refptr<SharedContextState> context_state,
                                 sk_sp<SkPromiseImageTexture> promise_texture,
                                 GrBackendTexture backend_texture) {
-      DCHECK(promise_texture);
       context_state->MakeCurrent(nullptr);
-      context_state->EraseCachedSkSurface(promise_texture.get());
+
+      // Note that if we fail to initialize this backing, |promise_texture| will
+      // not be created and hence could be null while backing is destroyed after
+      // a failed init.
+      if (promise_texture)
+        context_state->EraseCachedSkSurface(promise_texture.get());
       promise_texture.reset();
 
       if (backend_texture.isValid())
diff --git a/ios/chrome/browser/net/ios_chrome_network_delegate.cc b/ios/chrome/browser/net/ios_chrome_network_delegate.cc
index 088cd03a..c0341dc3 100644
--- a/ios/chrome/browser/net/ios_chrome_network_delegate.cc
+++ b/ios/chrome/browser/net/ios_chrome_network_delegate.cc
@@ -108,17 +108,20 @@
              request.url(), request.site_for_cookies().RepresentativeUrl());
 }
 
-bool IOSChromeNetworkDelegate::OnForcePrivacyMode(
+net::NetworkDelegate::PrivacySetting
+IOSChromeNetworkDelegate::OnForcePrivacyMode(
     const GURL& url,
     const net::SiteForCookies& site_for_cookies,
     const absl::optional<url::Origin>& top_frame_origin,
     net::SamePartyContext::Type same_party_context_type) const {
   // Null during tests, or when we're running in the system context.
   if (!cookie_settings_.get())
-    return false;
+    return net::NetworkDelegate::PrivacySetting::kStateAllowed;
 
-  return !cookie_settings_->IsFullCookieAccessAllowed(url, site_for_cookies,
-                                                      top_frame_origin);
+  return cookie_settings_->IsFullCookieAccessAllowed(url, site_for_cookies,
+                                                     top_frame_origin)
+             ? net::NetworkDelegate::PrivacySetting::kStateAllowed
+             : net::NetworkDelegate::PrivacySetting::kStateDisallowed;
 }
 
 bool IOSChromeNetworkDelegate::
diff --git a/ios/chrome/browser/net/ios_chrome_network_delegate.h b/ios/chrome/browser/net/ios_chrome_network_delegate.h
index 830e12e..a84345c 100644
--- a/ios/chrome/browser/net/ios_chrome_network_delegate.h
+++ b/ios/chrome/browser/net/ios_chrome_network_delegate.h
@@ -61,7 +61,7 @@
                       const net::CanonicalCookie& cookie,
                       net::CookieOptions* options,
                       bool allowed_from_caller) override;
-  bool OnForcePrivacyMode(
+  net::NetworkDelegate::PrivacySetting OnForcePrivacyMode(
       const GURL& url,
       const net::SiteForCookies& site_for_cookies,
       const absl::optional<url::Origin>& top_frame_origin,
diff --git a/jingle/BUILD.gn b/jingle/BUILD.gn
index 619847fb..df7d896 100644
--- a/jingle/BUILD.gn
+++ b/jingle/BUILD.gn
@@ -30,7 +30,6 @@
 
 test("jingle_unittests") {
   sources = [
-    "glue/fake_ssl_client_socket_unittest.cc",
     "glue/thread_wrapper_unittest.cc",
     "run_all_unittests.cc",
   ]
@@ -39,7 +38,6 @@
     ":webrtc_glue",
     "//base",
     "//base/test:test_support",
-    "//jingle/glue:fake_ssl_socket",
     "//mojo/core/embedder",
     "//mojo/public/cpp/bindings",
     "//mojo/public/cpp/system",
diff --git a/jingle/glue/BUILD.gn b/jingle/glue/BUILD.gn
deleted file mode 100644
index c34b2b5..0000000
--- a/jingle/glue/BUILD.gn
+++ /dev/null
@@ -1,21 +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.
-
-# These files are separated into their own target to avoid a circular dependency
-# since services/network depends on this.
-source_set("fake_ssl_socket") {
-  visibility = [
-    "//jingle:*",
-    "//services/network:*",
-  ]
-  sources = [
-    "fake_ssl_client_socket.cc",
-    "fake_ssl_client_socket.h",
-  ]
-  public_deps = [
-    "//base",
-    "//net",
-    "//net/traffic_annotation",
-  ]
-}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl
index 305dbd21..324fce3 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl
@@ -23,7 +23,7 @@
       {{kind|cpp_data_view_type}}* output);
 
   template <typename UserType>
-  WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) {
+  [[nodiscard]] bool Read{{name|under_to_camel}}(UserType* output) {
     {{struct_macros.assert_nullable_output_type_if_necessary(kind, name)}}
 {%-     if pf.min_version != 0 %}
     auto* pointer = data_->header_.version >= {{pf.min_version}} && !data_->{{name}}.is_null()
@@ -40,7 +40,7 @@
       {{kind|cpp_data_view_type}}* output);
 
   template <typename UserType>
-  WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) {
+  [[nodiscard]] bool Read{{name|under_to_camel}}(UserType* output) {
     {{struct_macros.assert_nullable_output_type_if_necessary(kind, name)}}
 {%-     if pf.min_version != 0 %}
     auto* pointer = data_->header_.version >= {{pf.min_version}}
@@ -54,7 +54,7 @@
 
 {%-   elif kind|is_enum_kind %}
   template <typename UserType>
-  WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) const {
+  [[nodiscard]] bool Read{{name|under_to_camel}}(UserType* output) const {
 {%-     if pf.min_version != 0 %}
     auto data_value = data_->header_.version >= {{pf.min_version}}
                       ? data_->{{name}} : 0;
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl
index a3a9ee5..cd08831 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl
@@ -33,7 +33,7 @@
       {{kind|cpp_data_view_type}}* output) const;
 
   template <typename UserType>
-  WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) const {
+  [[nodiscard]] bool Read{{name|under_to_camel}}(UserType* output) const {
     {{struct_macros.assert_nullable_output_type_if_necessary(kind, name)}}
     CHECK(is_{{name}}());
     return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
@@ -42,7 +42,7 @@
 
 {%-   elif kind|is_enum_kind %}
   template <typename UserType>
-  WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) const {
+  [[nodiscard]] bool Read{{name|under_to_camel}}(UserType* output) const {
     CHECK(is_{{name}}());
     return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
         data_->data.f_{{name}}, output);
diff --git a/net/base/network_delegate.cc b/net/base/network_delegate.cc
index 68e05fc7..cac13fadf 100644
--- a/net/base/network_delegate.cc
+++ b/net/base/network_delegate.cc
@@ -123,7 +123,7 @@
   return OnCanSetCookie(request, cookie, options, allowed_from_caller);
 }
 
-bool NetworkDelegate::ForcePrivacyMode(
+NetworkDelegate::PrivacySetting NetworkDelegate::ForcePrivacyMode(
     const GURL& url,
     const SiteForCookies& site_for_cookies,
     const absl::optional<url::Origin>& top_frame_origin,
diff --git a/net/base/network_delegate.h b/net/base/network_delegate.h
index 8bd0c3b..b9919cd8 100644
--- a/net/base/network_delegate.h
+++ b/net/base/network_delegate.h
@@ -85,10 +85,27 @@
                     const net::CanonicalCookie& cookie,
                     CookieOptions* options,
                     bool allowed_from_caller);
-  bool ForcePrivacyMode(const GURL& url,
-                        const SiteForCookies& site_for_cookies,
-                        const absl::optional<url::Origin>& top_frame_origin,
-                        SamePartyContext::Type same_party_context_type) const;
+
+  // PrivacySetting is kStateDisallowed iff the given |url| has to be
+  // requested over connection that is not tracked by the server.
+  //
+  // Usually PrivacySetting is kStateAllowed, unless user privacy settings
+  // block cookies from being get or set.
+  //
+  // It may be set to kPartitionedStateAllowedOnly if the request allows
+  // partitioned state to be sent over the connection, but unpartitioned
+  // state should be blocked.
+  enum class PrivacySetting {
+    kStateAllowed,
+    kStateDisallowed,
+    // First-party requests will never have this setting.
+    kPartitionedStateAllowedOnly,
+  };
+  PrivacySetting ForcePrivacyMode(
+      const GURL& url,
+      const SiteForCookies& site_for_cookies,
+      const absl::optional<url::Origin>& top_frame_origin,
+      SamePartyContext::Type same_party_context_type) const;
 
   bool CancelURLRequestWithPolicyViolatingReferrerHeader(
       const URLRequest& request,
@@ -246,10 +263,7 @@
                               CookieOptions* options,
                               bool allowed_from_caller) = 0;
 
-  // Returns true if the given |url| has to be requested over connection that
-  // is not tracked by the server. Usually is false, unless user privacy
-  // settings block cookies from being get or set.
-  virtual bool OnForcePrivacyMode(
+  virtual PrivacySetting OnForcePrivacyMode(
       const GURL& url,
       const SiteForCookies& site_for_cookies,
       const absl::optional<url::Origin>& top_frame_origin,
diff --git a/net/base/network_delegate_impl.cc b/net/base/network_delegate_impl.cc
index 6227858..a47dbb0 100644
--- a/net/base/network_delegate_impl.cc
+++ b/net/base/network_delegate_impl.cc
@@ -66,12 +66,12 @@
   return allowed_from_caller;
 }
 
-bool NetworkDelegateImpl::OnForcePrivacyMode(
+NetworkDelegate::PrivacySetting NetworkDelegateImpl::OnForcePrivacyMode(
     const GURL& url,
     const SiteForCookies& site_for_cookies,
     const absl::optional<url::Origin>& top_frame_origin,
     SamePartyContext::Type same_party_context_type) const {
-  return false;
+  return NetworkDelegate::PrivacySetting::kStateAllowed;
 }
 
 bool NetworkDelegateImpl::OnCancelURLRequestWithPolicyViolatingReferrerHeader(
diff --git a/net/base/network_delegate_impl.h b/net/base/network_delegate_impl.h
index 08b13d3..b4dacd82 100644
--- a/net/base/network_delegate_impl.h
+++ b/net/base/network_delegate_impl.h
@@ -77,7 +77,7 @@
                       CookieOptions* options,
                       bool allowed_from_caller) override;
 
-  bool OnForcePrivacyMode(
+  NetworkDelegate::PrivacySetting OnForcePrivacyMode(
       const GURL& url,
       const SiteForCookies& site_for_cookies,
       const absl::optional<url::Origin>& top_frame_origin,
diff --git a/net/base/privacy_mode.cc b/net/base/privacy_mode.cc
index f490a1ca..9578848e 100644
--- a/net/base/privacy_mode.cc
+++ b/net/base/privacy_mode.cc
@@ -16,6 +16,8 @@
       return "enabled";
     case PRIVACY_MODE_ENABLED_WITHOUT_CLIENT_CERTS:
       return "enabled without client certs";
+    case PRIVACY_MODE_ENABLED_PARTITIONED_STATE_ALLOWED:
+      return "enabled partitioned state allowed";
   }
   NOTREACHED();
   return "";
diff --git a/net/base/privacy_mode.h b/net/base/privacy_mode.h
index c9cb6558..0b7c5d37 100644
--- a/net/base/privacy_mode.h
+++ b/net/base/privacy_mode.h
@@ -16,6 +16,9 @@
   // Due to http://crbug.com/775438, PRIVACY_MODE_ENABLED still sends client
   // certs. This mode ensures that the request is sent without client certs.
   PRIVACY_MODE_ENABLED_WITHOUT_CLIENT_CERTS = 2,
+
+  // Privacy mode is enabled but partitioned HTTP cookies are still allowed.
+  PRIVACY_MODE_ENABLED_PARTITIONED_STATE_ALLOWED = 3,
 };
 
 const char* PrivacyModeToDebugString(PrivacyMode privacy_mode);
diff --git a/net/dns/address_sorter.h b/net/dns/address_sorter.h
index 63965bb..0e3409fa 100644
--- a/net/dns/address_sorter.h
+++ b/net/dns/address_sorter.h
@@ -6,8 +6,10 @@
 #define NET_DNS_ADDRESS_SORTER_H_
 
 #include <memory>
+#include <vector>
 
 #include "base/callback.h"
+#include "net/base/ip_endpoint.h"
 #include "net/base/net_export.h"
 
 namespace net {
@@ -21,17 +23,18 @@
 class NET_EXPORT AddressSorter {
  public:
   using CallbackType =
-      base::OnceCallback<void(bool success, const AddressList& list)>;
+      base::OnceCallback<void(bool success, std::vector<IPEndPoint> sorted)>;
 
   AddressSorter(const AddressSorter&) = delete;
   AddressSorter& operator=(const AddressSorter&) = delete;
 
   virtual ~AddressSorter() {}
 
-  // Sorts |list|, which must include at least one IPv6 address.
-  // Calls |callback| upon completion. Could complete synchronously. Could
+  // Sorts `endpoints`, which must include at least one IPv6 address.
+  // Calls `callback` upon completion. Could complete synchronously. Could
   // complete after this AddressSorter is destroyed.
-  virtual void Sort(const AddressList& list, CallbackType callback) const = 0;
+  virtual void Sort(const std::vector<IPEndPoint>& endpoints,
+                    CallbackType callback) const = 0;
 
   // Creates platform-dependent AddressSorter.
   static std::unique_ptr<AddressSorter> CreateAddressSorter();
diff --git a/net/dns/address_sorter_posix.cc b/net/dns/address_sorter_posix.cc
index 3a3e42d..ba748bc 100644
--- a/net/dns/address_sorter_posix.cc
+++ b/net/dns/address_sorter_posix.cc
@@ -8,6 +8,7 @@
 
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/memory/raw_ptr.h"
 #include "build/build_config.h"
@@ -26,6 +27,7 @@
 
 #include "base/cxx17_backports.h"
 #include "base/logging.h"
+#include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
 #include "net/log/net_log_source.h"
 #include "net/socket/client_socket_factory.h"
@@ -190,7 +192,7 @@
 };
 
 struct DestinationInfo {
-  IPAddress address;
+  IPEndPoint endpoint;
   AddressSorterPosix::AddressScope scope;
   unsigned precedence;
   unsigned label;
@@ -240,7 +242,7 @@
     return dst_a->scope < dst_b->scope;
 
   // Rule 9: Use longest matching prefix. Only for matching address families.
-  if (dst_a->address.size() == dst_b->address.size()) {
+  if (dst_a->endpoint.address().size() == dst_b->endpoint.address().size()) {
     if (dst_a->common_prefix_length != dst_b->common_prefix_length)
       return dst_a->common_prefix_length > dst_b->common_prefix_length;
   }
@@ -269,17 +271,18 @@
   NetworkChangeNotifier::RemoveIPAddressObserver(this);
 }
 
-void AddressSorterPosix::Sort(const AddressList& list,
+void AddressSorterPosix::Sort(const std::vector<IPEndPoint>& endpoints,
                               CallbackType callback) const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   std::vector<std::unique_ptr<DestinationInfo>> sort_list;
 
-  for (size_t i = 0; i < list.size(); ++i) {
+  for (const IPEndPoint& endpoint : endpoints) {
     std::unique_ptr<DestinationInfo> info(new DestinationInfo());
-    info->address = list[i].address();
-    info->scope = GetScope(ipv4_scope_table_, info->address);
-    info->precedence = GetPolicyValue(precedence_table_, info->address);
-    info->label = GetPolicyValue(label_table_, info->address);
+    info->endpoint = endpoint;
+    info->scope = GetScope(ipv4_scope_table_, info->endpoint.address());
+    info->precedence =
+        GetPolicyValue(precedence_table_, info->endpoint.address());
+    info->label = GetPolicyValue(label_table_, info->endpoint.address());
 
     // Each socket can only be bound once.
     std::unique_ptr<DatagramClientSocket> socket(
@@ -287,8 +290,10 @@
             DatagramSocket::DEFAULT_BIND, nullptr /* NetLog */,
             NetLogSource()));
 
+    IPEndPoint dest = info->endpoint;
     // Even though no packets are sent, cannot use port 0 in Connect.
-    IPEndPoint dest(info->address, 80 /* port */);
+    if (dest.port() == 0)
+      dest = IPEndPoint(dest.address(), /*port=*/80);
     int rv = socket->Connect(dest);
     if (rv != OK) {
       VLOG(1) << "Could not connect to " << dest.ToStringWithoutPort()
@@ -312,9 +317,9 @@
     }
     info->src = &src_info;
 
-    if (info->address.size() == src.address().size()) {
+    if (info->endpoint.address().size() == src.address().size()) {
       info->common_prefix_length =
-          std::min(CommonPrefixLength(info->address, src.address()),
+          std::min(CommonPrefixLength(info->endpoint.address(), src.address()),
                    info->src->prefix_length);
     }
     sort_list.push_back(std::move(info));
@@ -322,11 +327,11 @@
 
   std::stable_sort(sort_list.begin(), sort_list.end(), CompareDestinations);
 
-  AddressList result;
-  for (size_t i = 0; i < sort_list.size(); ++i)
-    result.push_back(IPEndPoint(sort_list[i]->address, 0 /* port */));
+  std::vector<IPEndPoint> sorted_result;
+  for (const auto& info : sort_list)
+    sorted_result.push_back(info->endpoint);
 
-  std::move(callback).Run(true, result);
+  std::move(callback).Run(true, std::move(sorted_result));
 }
 
 void AddressSorterPosix::OnIPAddressChanged() {
diff --git a/net/dns/address_sorter_posix.h b/net/dns/address_sorter_posix.h
index f3e1fdcc..7f6919c 100644
--- a/net/dns/address_sorter_posix.h
+++ b/net/dns/address_sorter_posix.h
@@ -10,7 +10,6 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/threading/thread_checker.h"
-#include "net/base/address_list.h"
 #include "net/base/ip_address.h"
 #include "net/base/net_export.h"
 #include "net/base/network_change_notifier.h"
@@ -67,7 +66,8 @@
   ~AddressSorterPosix() override;
 
   // AddressSorter:
-  void Sort(const AddressList& list, CallbackType callback) const override;
+  void Sort(const std::vector<IPEndPoint>& endpoints,
+            CallbackType callback) const override;
 
  private:
   friend class AddressSorterPosixTest;
diff --git a/net/dns/address_sorter_posix_unittest.cc b/net/dns/address_sorter_posix_unittest.cc
index d4a2cb5..9a4a319 100644
--- a/net/dns/address_sorter_posix_unittest.cc
+++ b/net/dns/address_sorter_posix_unittest.cc
@@ -6,12 +6,14 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/check_op.h"
 #include "base/memory/raw_ptr.h"
 #include "base/notreached.h"
 #include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
 #include "net/base/test_completion_callback.h"
 #include "net/log/net_log_with_source.h"
@@ -22,6 +24,7 @@
 #include "net/socket/stream_socket.h"
 #include "net/test/test_with_task_environment.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace net {
@@ -182,13 +185,13 @@
   AddressMapping mapping_;
 };
 
-void OnSortComplete(AddressList* result_buf,
+void OnSortComplete(std::vector<IPEndPoint>* sorted_buf,
                     CompletionOnceCallback callback,
                     bool success,
-                    const AddressList& result) {
+                    std::vector<IPEndPoint> sorted) {
   EXPECT_TRUE(success);
   if (success)
-    *result_buf = result;
+    *sorted_buf = std::move(sorted);
   std::move(callback).Run(OK);
 }
 
@@ -216,25 +219,25 @@
   // Verify that NULL-terminated |addresses| matches (-1)-terminated |order|
   // after sorting.
   void Verify(const char* const addresses[], const int order[]) {
-    AddressList list;
+    std::vector<IPEndPoint> endpoints;
     for (const char* const* addr = addresses; *addr != NULL; ++addr)
-      list.push_back(IPEndPoint(ParseIP(*addr), 80));
+      endpoints.emplace_back(ParseIP(*addr), 80);
     for (size_t i = 0; order[i] >= 0; ++i)
-      CHECK_LT(order[i], static_cast<int>(list.size()));
+      CHECK_LT(order[i], static_cast<int>(endpoints.size()));
 
-    AddressList result;
+    std::vector<IPEndPoint> sorted;
     TestCompletionCallback callback;
-    sorter_.Sort(list,
-                 base::BindOnce(&OnSortComplete, &result, callback.callback()));
+    sorter_.Sort(endpoints,
+                 base::BindOnce(&OnSortComplete, &sorted, callback.callback()));
     callback.WaitForResult();
 
-    for (size_t i = 0; (i < result.size()) || (order[i] >= 0); ++i) {
-      IPEndPoint expected = order[i] >= 0 ? list[order[i]] : IPEndPoint();
-      IPEndPoint actual = i < result.size() ? result[i] : IPEndPoint();
-      EXPECT_TRUE(expected.address() == actual.address()) <<
-          "Address out of order at position " << i << "\n" <<
-          "  Actual: " << actual.ToStringWithoutPort() << "\n" <<
-          "Expected: " << expected.ToStringWithoutPort();
+    for (size_t i = 0; (i < sorted.size()) || (order[i] >= 0); ++i) {
+      IPEndPoint expected = order[i] >= 0 ? endpoints[order[i]] : IPEndPoint();
+      IPEndPoint actual = i < sorted.size() ? sorted[i] : IPEndPoint();
+      EXPECT_TRUE(expected == actual)
+          << "Endpoint out of order at position " << i << "\n"
+          << "  Actual: " << actual.ToString() << "\n"
+          << "Expected: " << expected.ToString();
     }
   }
 
@@ -380,4 +383,23 @@
   Verify(addresses, order);
 }
 
+TEST_F(AddressSorterPosixTest, InputPortsAreMaintained) {
+  AddMapping("::1", "::1");
+  AddMapping("::2", "::2");
+  AddMapping("::3", "::3");
+
+  IPEndPoint endpoint1(ParseIP("::1"), /*port=*/111);
+  IPEndPoint endpoint2(ParseIP("::2"), /*port=*/222);
+  IPEndPoint endpoint3(ParseIP("::3"), /*port=*/333);
+
+  std::vector<IPEndPoint> input = {endpoint1, endpoint2, endpoint3};
+  std::vector<IPEndPoint> sorted;
+  TestCompletionCallback callback;
+  sorter_.Sort(input,
+               base::BindOnce(&OnSortComplete, &sorted, callback.callback()));
+  callback.WaitForResult();
+
+  EXPECT_THAT(sorted, testing::ElementsAre(endpoint1, endpoint2, endpoint3));
+}
+
 }  // namespace net
diff --git a/net/dns/address_sorter_unittest.cc b/net/dns/address_sorter_unittest.cc
index 79cb7ff..534a23d8 100644
--- a/net/dns/address_sorter_unittest.cc
+++ b/net/dns/address_sorter_unittest.cc
@@ -11,13 +11,14 @@
 #endif
 
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/test/task_environment.h"
-#include "net/base/address_list.h"
 #include "net/base/completion_once_callback.h"
 #include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
 #include "net/base/test_completion_callback.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -34,12 +35,12 @@
   return IPEndPoint(addr, 0);
 }
 
-void OnSortComplete(AddressList* result_buf,
+void OnSortComplete(std::vector<IPEndPoint>* sorted_buf,
                     CompletionOnceCallback callback,
                     bool success,
-                    const AddressList& result) {
+                    std::vector<IPEndPoint> sorted) {
   if (success)
-    *result_buf = result;
+    *sorted_buf = std::move(sorted);
   std::move(callback).Run(success ? OK : ERR_FAILED);
 }
 
@@ -56,15 +57,15 @@
   }
 #endif
   std::unique_ptr<AddressSorter> sorter(AddressSorter::CreateAddressSorter());
-  AddressList list;
-  list.push_back(MakeEndPoint("10.0.0.1"));
-  list.push_back(MakeEndPoint("8.8.8.8"));
-  list.push_back(MakeEndPoint("::1"));
-  list.push_back(MakeEndPoint("2001:4860:4860::8888"));
+  std::vector<IPEndPoint> endpoints;
+  endpoints.push_back(MakeEndPoint("10.0.0.1"));
+  endpoints.push_back(MakeEndPoint("8.8.8.8"));
+  endpoints.push_back(MakeEndPoint("::1"));
+  endpoints.push_back(MakeEndPoint("2001:4860:4860::8888"));
 
-  AddressList result;
+  std::vector<IPEndPoint> result;
   TestCompletionCallback callback;
-  sorter->Sort(list,
+  sorter->Sort(endpoints,
                base::BindOnce(&OnSortComplete, &result, callback.callback()));
   EXPECT_EQ(expected_result, callback.WaitForResult());
 }
diff --git a/net/dns/address_sorter_win.cc b/net/dns/address_sorter_win.cc
index ae48904..56a3b6c 100644
--- a/net/dns/address_sorter_win.cc
+++ b/net/dns/address_sorter_win.cc
@@ -7,6 +7,8 @@
 #include <winsock2.h>
 
 #include <algorithm>
+#include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/location.h"
@@ -14,7 +16,6 @@
 #include "base/memory/free_deleter.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
-#include "net/base/address_list.h"
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/winsock_init.h"
@@ -35,18 +36,20 @@
   ~AddressSorterWin() override {}
 
   // AddressSorter:
-  void Sort(const AddressList& list, CallbackType callback) const override {
-    DCHECK(!list.empty());
-    Job::Start(list, std::move(callback));
+  void Sort(const std::vector<IPEndPoint>& endpoints,
+            CallbackType callback) const override {
+    DCHECK(!endpoints.empty());
+    Job::Start(endpoints, std::move(callback));
   }
 
  private:
   // Executes the SIO_ADDRESS_LIST_SORT ioctl asynchronously, and
-  // performs the necessary conversions to/from AddressList.
+  // performs the necessary conversions to/from `std::vector<IPEndPoint>`.
   class Job : public base::RefCountedThreadSafe<Job> {
    public:
-    static void Start(const AddressList& list, CallbackType callback) {
-      auto job = base::WrapRefCounted(new Job(list, std::move(callback)));
+    static void Start(const std::vector<IPEndPoint>& endpoints,
+                      CallbackType callback) {
+      auto job = base::WrapRefCounted(new Job(endpoints, std::move(callback)));
       base::ThreadPool::PostTaskAndReply(
           FROM_HERE,
           {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
@@ -60,10 +63,10 @@
    private:
     friend class base::RefCountedThreadSafe<Job>;
 
-    Job(const AddressList& list, CallbackType callback)
+    Job(const std::vector<IPEndPoint>& endpoints, CallbackType callback)
         : callback_(std::move(callback)),
           buffer_size_((sizeof(SOCKET_ADDRESS_LIST) +
-                        base::CheckedNumeric<DWORD>(list.size()) *
+                        base::CheckedNumeric<DWORD>(endpoints.size()) *
                             (sizeof(SOCKET_ADDRESS) + sizeof(SOCKADDR_STORAGE)))
                            .ValueOrDie<DWORD>()),
           input_buffer_(
@@ -71,12 +74,12 @@
           output_buffer_(
               reinterpret_cast<SOCKET_ADDRESS_LIST*>(malloc(buffer_size_))),
           success_(false) {
-      input_buffer_->iAddressCount = base::checked_cast<INT>(list.size());
+      input_buffer_->iAddressCount = base::checked_cast<INT>(endpoints.size());
       SOCKADDR_STORAGE* storage = reinterpret_cast<SOCKADDR_STORAGE*>(
           input_buffer_->Address + input_buffer_->iAddressCount);
 
-      for (size_t i = 0; i < list.size(); ++i) {
-        IPEndPoint ipe = list[i];
+      for (size_t i = 0; i < endpoints.size(); ++i) {
+        IPEndPoint ipe = endpoints[i];
         // Addresses must be sockaddr_in6.
         if (ipe.address().IsIPv4()) {
           ipe = IPEndPoint(ConvertIPv4ToIPv4MappedIPv6(ipe.address()),
@@ -113,9 +116,9 @@
 
     // Executed on the calling thread.
     void OnComplete() {
-      AddressList list;
+      std::vector<IPEndPoint> sorted;
       if (success_) {
-        list.reserve(output_buffer_->iAddressCount);
+        sorted.reserve(output_buffer_->iAddressCount);
         for (int i = 0; i < output_buffer_->iAddressCount; ++i) {
           IPEndPoint ipe;
           bool result =
@@ -128,10 +131,10 @@
             ipe = IPEndPoint(ConvertIPv4MappedIPv6ToIPv4(ipe.address()),
                              ipe.port());
           }
-          list.push_back(ipe);
+          sorted.push_back(ipe);
         }
       }
-      std::move(callback_).Run(success_, list);
+      std::move(callback_).Run(success_, std::move(sorted));
     }
 
     CallbackType callback_;
diff --git a/net/dns/dns_test_util.cc b/net/dns/dns_test_util.cc
index 7e37f15..b26496a 100644
--- a/net/dns/dns_test_util.cc
+++ b/net/dns/dns_test_util.cc
@@ -22,6 +22,7 @@
 #include "net/base/address_list.h"
 #include "net/base/io_buffer.h"
 #include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
 #include "net/dns/address_sorter.h"
 #include "net/dns/dns_hosts.h"
@@ -73,9 +74,10 @@
 class MockAddressSorter : public AddressSorter {
  public:
   ~MockAddressSorter() override = default;
-  void Sort(const AddressList& list, CallbackType callback) const override {
+  void Sort(const std::vector<IPEndPoint>& endpoints,
+            CallbackType callback) const override {
     // Do nothing.
-    std::move(callback).Run(true, list);
+    std::move(callback).Run(true, endpoints);
   }
 };
 
diff --git a/net/dns/host_cache.h b/net/dns/host_cache.h
index 1c86a14..2f8aa9b 100644
--- a/net/dns/host_cache.h
+++ b/net/dns/host_cache.h
@@ -192,8 +192,8 @@
     const absl::optional<AddressList>& legacy_addresses() const {
       return legacy_addresses_;
     }
-    void set_legacy_addresses(const absl::optional<AddressList>& addresses) {
-      legacy_addresses_ = addresses;
+    void set_legacy_addresses(absl::optional<AddressList> addresses) {
+      legacy_addresses_ = std::move(addresses);
     }
     const absl::optional<std::vector<std::string>>& text_records() const {
       return text_records_;
diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc
index 3fbf7ca..6a79b34 100644
--- a/net/dns/host_resolver_manager.cc
+++ b/net/dns/host_resolver_manager.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 #include <cmath>
+#include <iterator>
 #include <limits>
 #include <memory>
 #include <set>
@@ -1800,9 +1801,10 @@
 
     if (at_least_one_ipv6_address) {
       // Sort addresses if needed.  Sort could complete synchronously.
-      AddressList addresses = results.legacy_addresses().value();
+      std::vector<IPEndPoint> endpoints =
+          results.legacy_addresses().value().endpoints();
       client_->GetAddressSorter()->Sort(
-          addresses,
+          endpoints,
           base::BindOnce(&DnsTask::OnSortComplete, AsWeakPtr(),
                          tick_clock_->NowTicks(), std::move(results), secure_));
       return;
@@ -1815,8 +1817,15 @@
                       HostCache::Entry results,
                       bool secure,
                       bool success,
-                      const AddressList& addr_list) {
-    results.set_legacy_addresses(addr_list);
+                      std::vector<IPEndPoint> sorted) {
+    DCHECK(results.legacy_addresses().has_value());
+    AddressList sorted_list;
+    for (IPEndPoint& endpoint : sorted) {
+      sorted_list.push_back(std::move(endpoint));
+    }
+    sorted_list.SetDnsAliases(results.legacy_addresses()->dns_aliases());
+
+    results.set_legacy_addresses(std::move(sorted_list));
 
     if (!success) {
       OnFailure(ERR_DNS_SORT_ERROR, results.GetOptionalTtl());
@@ -1824,7 +1833,7 @@
     }
 
     // AddressSorter prunes unusable destinations.
-    if (addr_list.empty() &&
+    if (results.legacy_addresses().value().empty() &&
         results.text_records().value_or(std::vector<std::string>()).empty() &&
         results.hostnames().value_or(std::vector<HostPortPair>()).empty()) {
       LOG(WARNING) << "Address list empty after RFC3484 sort";
diff --git a/net/quic/quic_session_key.cc b/net/quic/quic_session_key.cc
index 8b95951..8a1cc0a 100644
--- a/net/quic/quic_session_key.cc
+++ b/net/quic/quic_session_key.cc
@@ -32,11 +32,7 @@
                                SecureDnsPolicy secure_dns_policy)
     : QuicSessionKey(
           // TODO(crbug.com/1103350): Handle non-boolean privacy modes.
-          quic::QuicServerId(
-              host,
-              port,
-              privacy_mode == PRIVACY_MODE_ENABLED_WITHOUT_CLIENT_CERTS ||
-                  privacy_mode == PRIVACY_MODE_ENABLED),
+          quic::QuicServerId(host, port, privacy_mode != PRIVACY_MODE_DISABLED),
           socket_tag,
           network_isolation_key,
           secure_dns_policy) {}
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index affbbf4..ff3abc3b 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -354,14 +354,26 @@
   // |URLRequest::DefaultCanUseCookies()| if not.
   // TODO(mmenke): Looks like |URLRequest::DefaultCanUseCookies()| is not too
   // useful, with the network service - remove it.
-  bool enable_privacy_mode = !URLRequest::DefaultCanUseCookies();
-  if (request()->network_delegate()) {
-    enable_privacy_mode = request()->network_delegate()->ForcePrivacyMode(
-        request()->url(), request()->site_for_cookies(),
-        request()->isolation_info().top_frame_origin(),
-        request()->first_party_set_metadata().context().context_type());
+  NetworkDelegate::PrivacySetting privacy_setting =
+      URLRequest::DefaultCanUseCookies()
+          ? NetworkDelegate::PrivacySetting::kStateAllowed
+          : NetworkDelegate::PrivacySetting::kStateDisallowed;
+  if (request_->network_delegate()) {
+    privacy_setting = request()->network_delegate()->ForcePrivacyMode(
+        request_->url(), request_->site_for_cookies(),
+        request_->isolation_info().top_frame_origin(),
+        request_->first_party_set_metadata().context().context_type());
   }
-  return enable_privacy_mode ? PRIVACY_MODE_ENABLED : PRIVACY_MODE_DISABLED;
+  switch (privacy_setting) {
+    case NetworkDelegate::PrivacySetting::kStateAllowed:
+      return PRIVACY_MODE_DISABLED;
+    case NetworkDelegate::PrivacySetting::kPartitionedStateAllowedOnly:
+      return PRIVACY_MODE_ENABLED_PARTITIONED_STATE_ALLOWED;
+    case NetworkDelegate::PrivacySetting::kStateDisallowed:
+      return PRIVACY_MODE_ENABLED;
+  }
+  NOTREACHED();
+  return PRIVACY_MODE_ENABLED;
 }
 
 void URLRequestHttpJob::NotifyHeadersComplete() {
@@ -630,6 +642,19 @@
   }
 }
 
+namespace {
+
+bool ShouldBlockAllCookies(const PrivacyMode& privacy_mode) {
+  return privacy_mode == PRIVACY_MODE_ENABLED ||
+         privacy_mode == PRIVACY_MODE_ENABLED_WITHOUT_CLIENT_CERTS;
+}
+
+bool ShouldBlockUnpartitionedCookiesOnly(const PrivacyMode& privacy_mode) {
+  return privacy_mode == PRIVACY_MODE_ENABLED_PARTITIONED_STATE_ALLOWED;
+}
+
+}  // namespace
+
 void URLRequestHttpJob::SetCookieHeaderAndStart(
     const CookieOptions& options,
     const CookieAccessResultList& cookies_with_access_result_list,
@@ -640,10 +665,10 @@
       cookies_with_access_result_list;
   CookieAccessResultList excluded_cookies = excluded_list;
 
-  if (request_info_.privacy_mode != PRIVACY_MODE_DISABLED) {
-    // If cookies are blocked (without our needing to consult the delegate), we
-    // move them to `excluded_cookies` and ensure that they have the correct
-    // exclusion reason.
+  if (ShouldBlockAllCookies(request_info_.privacy_mode)) {
+    // If cookies are blocked (without our needing to consult the delegate),
+    // we move them to `excluded_cookies` and ensure that they have the
+    // correct exclusion reason.
     excluded_cookies.insert(
         excluded_cookies.end(),
         std::make_move_iterator(maybe_included_cookies.begin()),
@@ -653,7 +678,23 @@
       cookie.access_result.status.AddExclusionReason(
           CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
     }
-  } else {
+  }
+  if (ShouldBlockUnpartitionedCookiesOnly(request_info_.privacy_mode)) {
+    auto partition_it = base::ranges::stable_partition(
+        maybe_included_cookies, [](const CookieWithAccessResult& el) {
+          return el.cookie.IsPartitioned();
+        });
+    for (auto it = partition_it; it < maybe_included_cookies.end(); ++it) {
+      it->access_result.status.AddExclusionReason(
+          CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
+    }
+    excluded_cookies.insert(
+        excluded_cookies.end(), std::make_move_iterator(partition_it),
+        std::make_move_iterator(maybe_included_cookies.end()));
+    maybe_included_cookies.erase(partition_it, maybe_included_cookies.end());
+  }
+  if (request_info_.privacy_mode == PRIVACY_MODE_DISABLED ||
+      !maybe_included_cookies.empty()) {
     AnnotateAndMoveUserBlockedCookies(maybe_included_cookies, excluded_cookies);
     if (!maybe_included_cookies.empty()) {
       std::string cookie_line =
@@ -733,7 +774,15 @@
 void URLRequestHttpJob::AnnotateAndMoveUserBlockedCookies(
     CookieAccessResultList& maybe_included_cookies,
     CookieAccessResultList& excluded_cookies) const {
-  DCHECK_EQ(request_info_.privacy_mode, PrivacyMode::PRIVACY_MODE_DISABLED);
+  DCHECK(request_info_.privacy_mode == PrivacyMode::PRIVACY_MODE_DISABLED ||
+         (request_info_.privacy_mode ==
+              PrivacyMode::PRIVACY_MODE_ENABLED_PARTITIONED_STATE_ALLOWED &&
+          base::ranges::all_of(maybe_included_cookies,
+                               [](const CookieWithAccessResult& el) {
+                                 return el.cookie.IsPartitioned();
+                               })))
+      << request_info_.privacy_mode;
+
   bool can_get_cookies = URLRequest::DefaultCanUseCookies();
   if (request()->network_delegate()) {
     can_get_cookies =
diff --git a/net/url_request/url_request_http_job_unittest.cc b/net/url_request/url_request_http_job_unittest.cc
index bd4dd66..4d8cb1a7 100644
--- a/net/url_request/url_request_http_job_unittest.cc
+++ b/net/url_request/url_request_http_job_unittest.cc
@@ -1982,4 +1982,112 @@
   }
 }
 
+TEST_P(PartitionedCookiesURLRequestHttpJobTest, PrivacyMode) {
+  EmbeddedTestServer https_test(EmbeddedTestServer::TYPE_HTTPS);
+  https_test.AddDefaultHandlers(base::FilePath());
+  ASSERT_TRUE(https_test.Start());
+
+  FilteringTestNetworkDelegate network_delegate;
+  CookieMonster cm(/*store=*/nullptr, /*net_log=*/nullptr,
+                   /*first_party_sets_enabled=*/false);
+  TestURLRequestContext context(true);
+  context.set_cookie_store(&cm);
+  context.set_network_delegate(&network_delegate);
+  context.Init();
+
+  const url::Origin kTopFrameOrigin =
+      url::Origin::Create(GURL("https://www.toplevelsite.com"));
+  const IsolationInfo kTestIsolationInfo =
+      IsolationInfo::CreateForInternalRequest(kTopFrameOrigin);
+
+  // Set an unpartitioned and partitioned cookie.
+  TestDelegate delegate;
+  std::unique_ptr<URLRequest> req(context.CreateRequest(
+      https_test.GetURL(
+          "/set-cookie?__Host-partitioned=0;SameSite=None;Secure;Path=/"
+          ";Partitioned;&__Host-unpartitioned=1;SameSite=None;Secure;Path=/"),
+      DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
+  req->set_isolation_info(kTestIsolationInfo);
+  req->Start();
+  ASSERT_TRUE(req->is_pending());
+  delegate.RunUntilComplete();
+
+  {  // Get both cookies when privacy mode is disabled.
+    TestDelegate delegate;
+    std::unique_ptr<URLRequest> req(context.CreateRequest(
+        https_test.GetURL("/echoheader?Cookie"), DEFAULT_PRIORITY, &delegate,
+        TRAFFIC_ANNOTATION_FOR_TESTS));
+    req->set_isolation_info(kTestIsolationInfo);
+    req->Start();
+    delegate.RunUntilComplete();
+    EXPECT_EQ("__Host-partitioned=0; __Host-unpartitioned=1",
+              delegate.data_received());
+  }
+
+  {  // Get cookies with privacy mode enabled and partitioned state allowed.
+    network_delegate.set_force_privacy_mode(true);
+    network_delegate.set_partitioned_state_allowed(true);
+    TestDelegate delegate;
+    std::unique_ptr<URLRequest> req(context.CreateRequest(
+        https_test.GetURL("/echoheader?Cookie"), DEFAULT_PRIORITY, &delegate,
+        TRAFFIC_ANNOTATION_FOR_TESTS));
+    req->set_isolation_info(kTestIsolationInfo);
+    req->Start();
+    delegate.RunUntilComplete();
+    EXPECT_EQ(PartitionedCookiesEnabled() ? "__Host-partitioned=0" : "None",
+              delegate.data_received());
+    auto want_exclusion_reasons =
+        PartitionedCookiesEnabled()
+            ? std::vector<CookieInclusionStatus::ExclusionReason>{}
+            : std::vector<CookieInclusionStatus::ExclusionReason>{
+                  CookieInclusionStatus::EXCLUDE_USER_PREFERENCES};
+    EXPECT_THAT(
+        req->maybe_sent_cookies(),
+        UnorderedElementsAre(
+            MatchesCookieWithAccessResult(
+                MatchesCookieWithName("__Host-partitioned"),
+                MatchesCookieAccessResult(HasExactlyExclusionReasonsForTesting(
+                                              want_exclusion_reasons),
+                                          _, _, _)),
+            MatchesCookieWithAccessResult(
+                MatchesCookieWithName("__Host-unpartitioned"),
+                MatchesCookieAccessResult(
+                    HasExactlyExclusionReasonsForTesting(
+                        std::vector<CookieInclusionStatus::ExclusionReason>{
+                            CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}),
+                    _, _, _))));
+  }
+
+  {  // Get cookies with privacy mode enabled and partitioned state is not
+     // allowed.
+    network_delegate.set_force_privacy_mode(true);
+    network_delegate.set_partitioned_state_allowed(false);
+    TestDelegate delegate;
+    std::unique_ptr<URLRequest> req(context.CreateRequest(
+        https_test.GetURL("/echoheader?Cookie"), DEFAULT_PRIORITY, &delegate,
+        TRAFFIC_ANNOTATION_FOR_TESTS));
+    req->set_isolation_info(kTestIsolationInfo);
+    req->Start();
+    delegate.RunUntilComplete();
+    EXPECT_EQ("None", delegate.data_received());
+    EXPECT_THAT(
+        req->maybe_sent_cookies(),
+        UnorderedElementsAre(
+            MatchesCookieWithAccessResult(
+                MatchesCookieWithName("__Host-partitioned"),
+                MatchesCookieAccessResult(
+                    HasExactlyExclusionReasonsForTesting(
+                        std::vector<CookieInclusionStatus::ExclusionReason>{
+                            CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}),
+                    _, _, _)),
+            MatchesCookieWithAccessResult(
+                MatchesCookieWithName("__Host-unpartitioned"),
+                MatchesCookieAccessResult(
+                    HasExactlyExclusionReasonsForTesting(
+                        std::vector<CookieInclusionStatus::ExclusionReason>{
+                            CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}),
+                    _, _, _))));
+  }
+}
+
 }  // namespace net
diff --git a/net/url_request/url_request_test_util.cc b/net/url_request/url_request_test_util.cc
index acf5e13..f894d6c7 100644
--- a/net/url_request/url_request_test_util.cc
+++ b/net/url_request/url_request_test_util.cc
@@ -646,12 +646,12 @@
   return allow;
 }
 
-bool TestNetworkDelegate::OnForcePrivacyMode(
+NetworkDelegate::PrivacySetting TestNetworkDelegate::OnForcePrivacyMode(
     const GURL& url,
     const SiteForCookies& site_for_cookies,
     const absl::optional<url::Origin>& top_frame_origin,
     SamePartyContext::Type same_party_context_type) const {
-  return false;
+  return NetworkDelegate::PrivacySetting::kStateAllowed;
 }
 
 bool TestNetworkDelegate::OnCanSetCookie(const URLRequest& request,
@@ -709,13 +709,17 @@
   return TestNetworkDelegate::OnCanSetCookie(request, cookie, options, allowed);
 }
 
-bool FilteringTestNetworkDelegate::OnForcePrivacyMode(
+NetworkDelegate::PrivacySetting
+FilteringTestNetworkDelegate::OnForcePrivacyMode(
     const GURL& url,
     const SiteForCookies& site_for_cookies,
     const absl::optional<url::Origin>& top_frame_origin,
     SamePartyContext::Type same_party_context_type) const {
-  if (force_privacy_mode_)
-    return true;
+  if (force_privacy_mode_) {
+    return partitioned_state_allowed_
+               ? NetworkDelegate::PrivacySetting::kPartitionedStateAllowedOnly
+               : NetworkDelegate::PrivacySetting::kStateDisallowed;
+  }
 
   return TestNetworkDelegate::OnForcePrivacyMode(
       url, site_for_cookies, top_frame_origin, same_party_context_type);
diff --git a/net/url_request/url_request_test_util.h b/net/url_request/url_request_test_util.h
index 8976375..cfad5fa6 100644
--- a/net/url_request/url_request_test_util.h
+++ b/net/url_request/url_request_test_util.h
@@ -364,7 +364,7 @@
       net::CookieAccessResultList& maybe_included_cookies,
       net::CookieAccessResultList& excluded_cookies,
       bool allowed_from_caller) override;
-  bool OnForcePrivacyMode(
+  NetworkDelegate::PrivacySetting OnForcePrivacyMode(
       const GURL& url,
       const SiteForCookies& site_for_cookies,
       const absl::optional<url::Origin>& top_frame_origin,
@@ -451,7 +451,7 @@
       net::CookieAccessResultList& excluded_cookies,
       bool allowed_from_caller) override;
 
-  bool OnForcePrivacyMode(
+  NetworkDelegate::PrivacySetting OnForcePrivacyMode(
       const GURL& url,
       const SiteForCookies& site_for_cookies,
       const absl::optional<url::Origin>& top_frame_origin,
@@ -481,6 +481,10 @@
 
   void set_force_privacy_mode(bool enabled) { force_privacy_mode_ = enabled; }
 
+  void set_partitioned_state_allowed(bool allowed) {
+    partitioned_state_allowed_ = allowed;
+  }
+
  private:
   std::string cookie_name_filter_ = "";
   int set_cookie_called_count_ = 0;
@@ -492,6 +496,7 @@
   bool block_get_cookies_by_name_ = false;
 
   bool force_privacy_mode_ = false;
+  bool partitioned_state_allowed_ = false;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index 43206d22..2405a0f 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -250,7 +250,7 @@
     "//components/os_crypt",
     "//components/prefs",
     "//components/web_package",
-    "//jingle/glue:fake_ssl_socket",
+    "//components/webrtc:fake_ssl_socket",
     "//mojo/public/cpp/bindings",
     "//mojo/public/cpp/system",
     "//net",
@@ -442,8 +442,8 @@
     "//components/prefs:test_support",
     "//components/variations:test_support",
     "//components/web_package",
+    "//components/webrtc:fake_ssl_socket",
     "//crypto",
-    "//jingle/glue:fake_ssl_socket",
     "//mojo/public/cpp/bindings",
     "//mojo/public/cpp/system",
     "//mojo/public/cpp/test_support:test_utils",
diff --git a/services/network/DEPS b/services/network/DEPS
index 700959b9..d77488d7 100644
--- a/services/network/DEPS
+++ b/services/network/DEPS
@@ -11,10 +11,10 @@
   "+components/prefs",
   "+components/version_info",
   "+components/web_package",
+  # FakeSSLClientSocket
+  "+components/webrtc",
   "+crypto",
   "+ipc",
-  # FakeSSLClientSocket
-  "+jingle/glue",
   "+net",
   "+sandbox",
   "+services/proxy_resolver/public/mojom",
diff --git a/services/network/cookie_settings.cc b/services/network/cookie_settings.cc
index a860a19..b0d1311 100644
--- a/services/network/cookie_settings.cc
+++ b/services/network/cookie_settings.cc
@@ -107,7 +107,7 @@
           GetFirstPartyURL(site_for_cookies,
                            base::OptionalOrNullptr(top_frame_origin)),
           IsThirdPartyRequest(url, site_for_cookies)),
-      cookie.IsSameParty(), /*record_metrics=*/true);
+      cookie.IsSameParty(), cookie.IsPartitioned(), /*record_metrics=*/true);
 }
 
 bool CookieSettings::ShouldAlwaysAllowCookies(
@@ -121,27 +121,44 @@
           url.SchemeIs(first_party_url.scheme_piece()));
 }
 
-bool CookieSettings::IsPrivacyModeEnabled(
+net::NetworkDelegate::PrivacySetting CookieSettings::IsPrivacyModeEnabled(
     const GURL& url,
     const net::SiteForCookies& site_for_cookies,
     const absl::optional<url::Origin>& top_frame_origin,
     SamePartyCookieContextType same_party_cookie_context_type) const {
-  // Privacy mode should be enabled iff no cookies should ever be sent on this
-  // request. E.g.:
+  // PrivacySetting should be kStateDisallowed iff no cookies should ever
+  // be sent on this request. E.g.:
   //
   // * if cookie settings block cookies on this site or for this URL; or
   //
-  // * if cookie settings block 3P cookies, and the context is cross-party; or
+  // * if cookie settings block 3P cookies, the context is cross-party, and
+  // content settings blocks the 1P from using cookies; or
   //
   // * if cookie settings block 3P cookies, and the context is same-party, but
   // SameParty cookies aren't considered 1P.
   //
+  // PrivacySetting should be kPartitionedStateAllowedOnly iff the request is
+  // cross-party, cookie settings block 3P cookies, and content settings allows
+  // the 1P to use cookies.
+  //
+  // Otherwise, the PrivacySetting should be kStateAllowed.
+  //
   // We don't record metrics here, since this isn't actually accessing a cookie.
-  return !IsHypotheticalCookieAllowed(
-      GetCookieSettingWithMetadata(url, site_for_cookies,
-                                   base::OptionalOrNullptr(top_frame_origin)),
-      same_party_cookie_context_type == SamePartyCookieContextType::kSameParty,
-      /*record_metrics=*/false);
+  CookieSettingWithMetadata metadata = GetCookieSettingWithMetadata(
+      url, site_for_cookies, base::OptionalOrNullptr(top_frame_origin));
+  if (IsHypotheticalCookieAllowed(metadata,
+                                  same_party_cookie_context_type ==
+                                      SamePartyCookieContextType::kSameParty,
+                                  /*is_partitioned*/ false,
+                                  /*record_metrics=*/false)) {
+    return net::NetworkDelegate::PrivacySetting::kStateAllowed;
+  }
+  return metadata.blocked_by_third_party_setting ==
+                 CookieSettings::ThirdPartyCookieBlockingSetting::
+                     kPartitionedThirdPartyStateAllowedOnly
+             ? net::NetworkDelegate::PrivacySetting::
+                   kPartitionedStateAllowedOnly
+             : net::NetworkDelegate::PrivacySetting::kStateDisallowed;
 }
 
 CookieSettings::CookieSettingWithMetadata
@@ -152,16 +169,23 @@
   if (ShouldAlwaysAllowCookies(url, first_party_url)) {
     return {
         /*cookie_setting=*/CONTENT_SETTING_ALLOW,
-        /*blocked_by_third_party_setting=*/false,
+        /*blocked_by_third_party_setting=*/
+        CookieSettings::ThirdPartyCookieBlockingSetting::
+            kThirdPartyStateAllowed,
     };
   }
 
   // Default to allowing cookies.
   ContentSetting cookie_setting = CONTENT_SETTING_ALLOW;
-  bool blocked_by_third_party_setting =
-      block_third_party_cookies_ && is_third_party_request &&
+  CookieSettings::ThirdPartyCookieBlockingSetting
+      blocked_by_third_party_setting = CookieSettings::
+          ThirdPartyCookieBlockingSetting::kThirdPartyStateAllowed;
+  if (block_third_party_cookies_ && is_third_party_request &&
       !base::Contains(third_party_cookies_allowed_schemes_,
-                      first_party_url.scheme());
+                      first_party_url.scheme())) {
+    blocked_by_third_party_setting = CookieSettings::
+        ThirdPartyCookieBlockingSetting::kThirdPartyStateDisallowed;
+  }
   {
     // `content_settings_` is sorted in order of precedence, so we use the first
     // matching rule we find.
@@ -179,11 +203,15 @@
       // third-party cookies" setting.
       // Note: global settings are implemented as a catch-all (*, *) pattern.
       if (IsExplicitSetting(*entry) || cookie_setting == CONTENT_SETTING_BLOCK)
-        blocked_by_third_party_setting = false;
+        // TODO(dylancutler): Consider adding an enum variant for this case.
+        blocked_by_third_party_setting = CookieSettings::
+            ThirdPartyCookieBlockingSetting::kThirdPartyStateAllowed;
     }
   }
 
-  if (blocked_by_third_party_setting) {
+  if (blocked_by_third_party_setting ==
+      CookieSettings::ThirdPartyCookieBlockingSetting::
+          kThirdPartyStateDisallowed) {
     // If a valid entry exists that matches both our first party and request url
     // this indicates a Storage Access API grant that may unblock storage access
     // despite third party cookies being blocked.
@@ -203,10 +231,27 @@
       // CONTENT_SETTING_ALLOW as other values would indicate the user
       // rejected a prompt to allow access.
       if (entry->GetContentSetting() == CONTENT_SETTING_ALLOW) {
-        blocked_by_third_party_setting = false;
+        blocked_by_third_party_setting = CookieSettings::
+            ThirdPartyCookieBlockingSetting::kThirdPartyStateAllowed;
         FireStorageAccessHistogram(net::cookie_util::StorageAccessResult::
                                        ACCESS_ALLOWED_STORAGE_ACCESS_GRANT);
       }
+    } else {
+      // If the third-party cookie blocking setting is enabled, we check if the
+      // user has any content settings for the first-party URL as the primary
+      // pattern. If cookies are allowed for the first-party URL then we allow
+      // partitioned cross-site cookies.
+      const auto& first_party_entry = base::ranges::find_if(
+          content_settings_, [&](const ContentSettingPatternSource& entry) {
+            return entry.primary_pattern.Matches(first_party_url) &&
+                   entry.secondary_pattern.Matches(first_party_url);
+          });
+      if (first_party_entry == content_settings_.end() ||
+          first_party_entry->GetContentSetting() == CONTENT_SETTING_ALLOW) {
+        blocked_by_third_party_setting =
+            CookieSettings::ThirdPartyCookieBlockingSetting::
+                kPartitionedThirdPartyStateAllowedOnly;
+      }
     }
   } else {
     // Cookies aren't blocked solely due to the third-party-cookie blocking
@@ -218,7 +263,9 @@
             : net::cookie_util::StorageAccessResult::ACCESS_ALLOWED);
   }
 
-  if (blocked_by_third_party_setting) {
+  if (blocked_by_third_party_setting !=
+      CookieSettings::ThirdPartyCookieBlockingSetting::
+          kThirdPartyStateAllowed) {
     cookie_setting = CONTENT_SETTING_BLOCK;
     FireStorageAccessHistogram(
         net::cookie_util::StorageAccessResult::ACCESS_BLOCKED);
@@ -300,26 +347,39 @@
           !cookie.access_result.status.HasExclusionReason(
               net::CookieInclusionStatus::
                   EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT),
+      cookie.cookie.IsPartitioned(),
       /*record_metrics=*/true);
 }
 
 bool CookieSettings::IsHypotheticalCookieAllowed(
     const CookieSettings::CookieSettingWithMetadata& setting_with_metadata,
     bool is_same_party,
+    bool is_partitioned,
     bool record_metrics) const {
   if (IsAllowed(setting_with_metadata.cookie_setting))
     return true;
 
   bool blocked_by_3p_but_same_party =
-      setting_with_metadata.blocked_by_third_party_setting && is_same_party;
+      setting_with_metadata.blocked_by_third_party_setting !=
+          CookieSettings::ThirdPartyCookieBlockingSetting::
+              kThirdPartyStateAllowed &&
+      is_same_party;
   if (record_metrics && blocked_by_3p_but_same_party) {
     UMA_HISTOGRAM_BOOLEAN(
         "Cookie.SameParty.BlockedByThirdPartyCookieBlockingSetting",
         !sameparty_cookies_considered_first_party_);
   }
+  bool blocked = !(blocked_by_3p_but_same_party &&
+                   sameparty_cookies_considered_first_party_);
+  DCHECK(!is_partitioned || !is_same_party);
+  if (blocked && is_partitioned &&
+      setting_with_metadata.blocked_by_third_party_setting ==
+          CookieSettings::ThirdPartyCookieBlockingSetting::
+              kPartitionedThirdPartyStateAllowedOnly) {
+    return true;
+  }
 
-  return blocked_by_3p_but_same_party &&
-         sameparty_cookies_considered_first_party_;
+  return !blocked;
 }
 
 bool CookieSettings::HasSessionOnlyOrigins() const {
diff --git a/services/network/cookie_settings.h b/services/network/cookie_settings.h
index 64d0e50..d7318e53 100644
--- a/services/network/cookie_settings.h
+++ b/services/network/cookie_settings.h
@@ -14,6 +14,7 @@
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/cookie_settings_base.h"
 #include "net/base/features.h"
+#include "net/base/network_delegate.h"
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/same_party_context.h"
 #include "services/network/public/cpp/session_cookie_delete_predicate.h"
@@ -99,9 +100,13 @@
       const GURL& url,
       const net::SiteForCookies& site_for_cookies) const override;
 
-  // Returns true iff "privacy mode" should be enabled for the URL request in
-  // question, according to the user's settings.
-  bool IsPrivacyModeEnabled(
+  // Returns kStateDisallowed iff the given |url| has to be requested over
+  // connection that is not tracked by the server. Usually is kStateAllowed,
+  // unless user privacy settings block cookies from being get or set.
+  // It may be set to kPartitionedStateAllowedOnly if the request allows
+  // partitioned state to be sent over the connection, but unpartitioned
+  // state should be blocked.
+  net::NetworkDelegate::PrivacySetting IsPrivacyModeEnabled(
       const GURL& url,
       const net::SiteForCookies& site_for_cookies,
       const absl::optional<url::Origin>& top_frame_origin,
@@ -149,9 +154,15 @@
       bool is_third_party_request,
       content_settings::SettingSource* source) const override;
 
+  enum class ThirdPartyCookieBlockingSetting {
+    kThirdPartyStateAllowed = 1,
+    kThirdPartyStateDisallowed,
+    kPartitionedThirdPartyStateAllowedOnly,
+  };
+
   struct CookieSettingWithMetadata {
     ContentSetting cookie_setting;
-    bool blocked_by_third_party_setting;
+    ThirdPartyCookieBlockingSetting blocked_by_third_party_setting;
   };
 
   // Returns the cookie setting for the given request, along with metadata
@@ -190,6 +201,7 @@
   bool IsHypotheticalCookieAllowed(
       const CookieSettings::CookieSettingWithMetadata& setting_with_metadata,
       bool is_same_party,
+      bool is_partitioned,
       bool record_metrics) const;
 
   // Returns true if at least one content settings is session only.
diff --git a/services/network/cookie_settings_unittest.cc b/services/network/cookie_settings_unittest.cc
index 4a02932..bede44eee 100644
--- a/services/network/cookie_settings_unittest.cc
+++ b/services/network/cookie_settings_unittest.cc
@@ -40,16 +40,20 @@
 constexpr char kDomainWildcardPattern[] = "[*.]example.com";
 constexpr char kFPSOwnerURL[] = "https://fps-owner.test";
 constexpr char kFPSMemberURL[] = "https://fps-member.test";
+constexpr char kUnrelatedURL[] = "http://unrelated.com";
 
 std::unique_ptr<net::CanonicalCookie> MakeCanonicalCookie(
     const std::string& name,
     const std::string& domain,
-    bool sameparty) {
+    bool sameparty,
+    absl::optional<net::CookiePartitionKey> cookie_partition_key =
+        absl::nullopt) {
   return net::CanonicalCookie::CreateUnsafeCookieForTesting(
       name, "1", domain, "/" /* path */, base::Time() /* creation */,
       base::Time() /* expiration */, base::Time() /* last_access */,
       true /* secure */, false /* httponly */, net::CookieSameSite::UNSPECIFIED,
-      net::CookiePriority::COOKIE_PRIORITY_DEFAULT, sameparty);
+      net::CookiePriority::COOKIE_PRIORITY_DEFAULT, sameparty,
+      cookie_partition_key);
 }
 
 class CookieSettingsTest : public testing::Test {
@@ -458,40 +462,72 @@
   CookieSettings settings;
   settings.set_block_third_party_cookies(true);
 
-  // Enabled for third-party requests.
-  EXPECT_TRUE(settings.IsPrivacyModeEnabled(
-      GURL(kURL), net::SiteForCookies(), url::Origin::Create(GURL(kOtherURL)),
-      net::SamePartyContext::Type::kCrossParty));
+  // Third-party requests should only have accessed to partitioned state.
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kPartitionedStateAllowedOnly,
+      settings.IsPrivacyModeEnabled(GURL(kURL), net::SiteForCookies(),
+                                    url::Origin::Create(GURL(kOtherURL)),
+                                    net::SamePartyContext::Type::kCrossParty));
 
-  // Enabled for requests with a null site_for_cookies, even if the
+  // Same for requests with a null site_for_cookies, even if the
   // top_frame_origin matches.
-  EXPECT_TRUE(settings.IsPrivacyModeEnabled(
-      GURL(kURL), net::SiteForCookies(), url::Origin::Create(GURL(kURL)),
-      net::SamePartyContext::Type::kCrossParty));
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kPartitionedStateAllowedOnly,
+      settings.IsPrivacyModeEnabled(GURL(kURL), net::SiteForCookies(),
+                                    url::Origin::Create(GURL(kURL)),
+                                    net::SamePartyContext::Type::kCrossParty));
 
-  // Disabled for first-party requests, if no other rule applies.
-  EXPECT_FALSE(settings.IsPrivacyModeEnabled(
-      GURL(kURL), net::SiteForCookies::FromUrl(GURL(kURL)),
-      url::Origin::Create(GURL(kURL)),
-      net::SamePartyContext::Type::kSameParty));
+  // The first party is able to send any type of state.
+  EXPECT_EQ(net::NetworkDelegate::PrivacySetting::kStateAllowed,
+            settings.IsPrivacyModeEnabled(
+                GURL(kURL), net::SiteForCookies::FromUrl(GURL(kURL)),
+                url::Origin::Create(GURL(kURL)),
+                net::SamePartyContext::Type::kSameParty));
 
-  // Enabled if there's a site-specific rule that blocks access, regardless of
-  // the kind of request.
+  // Setting a site-specific rule for the top-level frame origin that blocks
+  // access should cause partitioned state to be disallowed.
+  settings.set_content_settings(
+      {CreateSetting(kOtherURL, "*", CONTENT_SETTING_BLOCK)});
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kStateDisallowed,
+      settings.IsPrivacyModeEnabled(GURL(kURL), net::SiteForCookies(),
+                                    url::Origin::Create(GURL(kOtherURL)),
+                                    net::SamePartyContext::Type::kCrossParty));
+
+  // Setting a site-specific rule for the top-level frame origin when it is
+  // embedded on an unrelated site should not affect if partitioned state is
+  // allowed.
+  settings.set_content_settings(
+      {CreateSetting(kOtherURL, kUnrelatedURL, CONTENT_SETTING_BLOCK)});
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kPartitionedStateAllowedOnly,
+      settings.IsPrivacyModeEnabled(GURL(kURL), net::SiteForCookies(),
+                                    url::Origin::Create(GURL(kOtherURL)),
+                                    net::SamePartyContext::Type::kCrossParty));
+
+  // No state is allowed if there's a site-specific rule that blocks access,
+  // regardless of the kind of request.
   settings.set_content_settings(
       {CreateSetting(kURL, "*", CONTENT_SETTING_BLOCK)});
   // Third-party requests:
-  EXPECT_TRUE(settings.IsPrivacyModeEnabled(
-      GURL(kURL), net::SiteForCookies(), url::Origin::Create(GURL(kOtherURL)),
-      net::SamePartyContext::Type::kCrossParty));
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kStateDisallowed,
+      settings.IsPrivacyModeEnabled(GURL(kURL), net::SiteForCookies(),
+                                    url::Origin::Create(GURL(kOtherURL)),
+                                    net::SamePartyContext::Type::kCrossParty));
+
   // Requests with a null site_for_cookies, but matching top_frame_origin.
-  EXPECT_TRUE(settings.IsPrivacyModeEnabled(
-      GURL(kURL), net::SiteForCookies(), url::Origin::Create(GURL(kURL)),
-      net::SamePartyContext::Type::kCrossParty));
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kStateDisallowed,
+      settings.IsPrivacyModeEnabled(GURL(kURL), net::SiteForCookies(),
+                                    url::Origin::Create(GURL(kURL)),
+                                    net::SamePartyContext::Type::kCrossParty));
   // First-party requests.
-  EXPECT_TRUE(settings.IsPrivacyModeEnabled(
-      GURL(kURL), net::SiteForCookies::FromUrl(GURL(kURL)),
-      url::Origin::Create(GURL(kURL)),
-      net::SamePartyContext::Type::kSameParty));
+  EXPECT_EQ(net::NetworkDelegate::PrivacySetting::kStateDisallowed,
+            settings.IsPrivacyModeEnabled(
+                GURL(kURL), net::SiteForCookies::FromUrl(GURL(kURL)),
+                url::Origin::Create(GURL(kURL)),
+                net::SamePartyContext::Type::kSameParty));
 
   // No histogram samples should have been recorded.
   EXPECT_THAT(histogram_tester.GetAllSamples(
@@ -508,13 +544,15 @@
   settings.set_block_third_party_cookies(true);
 
   // Enabled for cross-party requests.
-  EXPECT_TRUE(
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kPartitionedStateAllowedOnly,
       settings.IsPrivacyModeEnabled(GURL(kFPSMemberURL), net::SiteForCookies(),
                                     url::Origin::Create(GURL(kFPSOwnerURL)),
                                     net::SamePartyContext::Type::kCrossParty));
 
   // Disabled for cross-site, same-party requests.
-  EXPECT_FALSE(
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kStateAllowed,
       settings.IsPrivacyModeEnabled(GURL(kFPSMemberURL), net::SiteForCookies(),
                                     url::Origin::Create(GURL(kFPSOwnerURL)),
                                     net::SamePartyContext::Type::kSameParty));
@@ -522,7 +560,8 @@
   // Enabled for same-party requests if blocked by a site-specific rule.
   settings.set_content_settings(
       {CreateSetting(kFPSMemberURL, "*", CONTENT_SETTING_BLOCK)});
-  EXPECT_TRUE(
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kStateDisallowed,
       settings.IsPrivacyModeEnabled(GURL(kFPSMemberURL), net::SiteForCookies(),
                                     url::Origin::Create(GURL(kFPSOwnerURL)),
                                     net::SamePartyContext::Type::kSameParty));
@@ -633,6 +672,73 @@
               ElementsAre(base::Bucket(/*min=*/0, /*count=*/1)));
 }
 
+TEST_F(CookieSettingsTest, IsCookieAccessible_PartitionedCookies) {
+  base::HistogramTester histogram_tester;
+  base::test::ScopedFeatureList feature_list;
+  CookieSettings settings;
+  settings.set_block_third_party_cookies(true);
+
+  std::unique_ptr<net::CanonicalCookie> unpartitioned_cookie =
+      MakeCanonicalCookie("unpartitioned", kURL, false /* sameparty */,
+                          absl::nullopt /* cookie_partition_key */);
+
+  EXPECT_FALSE(settings.IsCookieAccessible(
+      *unpartitioned_cookie, GURL(kURL), net::SiteForCookies(),
+      url::Origin::Create(GURL(kOtherURL))));
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "Cookie.SameParty.BlockedByThirdPartyCookieBlockingSetting"),
+              IsEmpty());
+
+  std::unique_ptr<net::CanonicalCookie> partitioned_cookie =
+      MakeCanonicalCookie(
+          "__Host-partitioned", kURL, false /* sameparty */,
+          net::CookiePartitionKey::FromURLForTesting(GURL(kOtherURL)));
+
+  EXPECT_TRUE(settings.IsCookieAccessible(
+      *partitioned_cookie, GURL(kURL), net::SiteForCookies(),
+      url::Origin::Create(GURL(kOtherURL))));
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "Cookie.SameParty.BlockedByThirdPartyCookieBlockingSetting"),
+              IsEmpty());
+
+  // If there is a site-specific content setting blocking cookies, then
+  // partitioned cookies should not be available.
+  settings.set_block_third_party_cookies(false);
+  settings.set_content_settings(
+      {CreateSetting(kURL, "*", CONTENT_SETTING_BLOCK)});
+  EXPECT_FALSE(settings.IsCookieAccessible(
+      *partitioned_cookie, GURL(kURL), net::SiteForCookies(),
+      url::Origin::Create(GURL(kOtherURL))));
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "Cookie.SameParty.BlockedByThirdPartyCookieBlockingSetting"),
+              IsEmpty());
+
+  // If third-party cookie blocking is enabled and there is a site-specific
+  // content setting blocking the top-frame origin's own cookies, then
+  // the partitioned cookie should not be allowed.
+  settings.set_block_third_party_cookies(true);
+  settings.set_content_settings(
+      {CreateSetting(kOtherURL, "*", CONTENT_SETTING_BLOCK)});
+  EXPECT_FALSE(settings.IsCookieAccessible(
+      *partitioned_cookie, GURL(kURL), net::SiteForCookies(),
+      url::Origin::Create(GURL(kOtherURL))));
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "Cookie.SameParty.BlockedByThirdPartyCookieBlockingSetting"),
+              IsEmpty());
+
+  // If third-party cookie blocking is enabled and there is a site-specific
+  // setting for the top-frame origin that only applies on an unrelated site,
+  // then the partitioned cookie should still be allowed.
+  settings.set_content_settings(
+      {CreateSetting(kOtherURL, kUnrelatedURL, CONTENT_SETTING_BLOCK)});
+  EXPECT_TRUE(settings.IsCookieAccessible(
+      *partitioned_cookie, GURL(kURL), net::SiteForCookies(),
+      url::Origin::Create(GURL(kOtherURL))));
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "Cookie.SameParty.BlockedByThirdPartyCookieBlockingSetting"),
+              IsEmpty());
+}
+
 TEST_F(CookieSettingsTest, AnnotateAndMoveUserBlockedCookies) {
   base::HistogramTester histogram_tester;
   CookieSettings settings;
@@ -783,5 +889,127 @@
               ElementsAre(base::Bucket(/*min=*/0, /*count=*/2)));
 }
 
+namespace {
+
+net::CookieAccessResultList MakeUnpartitionedAndPartitionedCookies() {
+  return {
+      {*MakeCanonicalCookie("unpartitioned", kURL, false /* sameparty */), {}},
+      {*MakeCanonicalCookie(
+           "__Host-partitioned", kURL, false /* sameparty */,
+           net::CookiePartitionKey::FromURLForTesting(GURL(kOtherURL))),
+       {}},
+  };
+}
+
+}  // namespace
+
+TEST_F(CookieSettingsTest,
+       AnnotateAndMoveUserBlockedCookies_PartitionedCookies) {
+  base::HistogramTester histogram_tester;
+  base::test::ScopedFeatureList feature_list;
+  CookieSettings settings;
+
+  net::CookieAccessResultList maybe_included_cookies =
+      MakeUnpartitionedAndPartitionedCookies();
+  net::CookieAccessResultList excluded_cookies = {};
+
+  url::Origin top_level_origin = url::Origin::Create(GURL(kOtherURL));
+
+  // If 3PC blocking is enabled and there are no site-specific content settings
+  // then partitioned cookies should be allowed.
+  settings.set_block_third_party_cookies(true);
+  EXPECT_TRUE(settings.AnnotateAndMoveUserBlockedCookies(
+      GURL(kURL), net::SiteForCookies(), &top_level_origin,
+      maybe_included_cookies, excluded_cookies));
+  EXPECT_THAT(maybe_included_cookies,
+              ElementsAre(MatchesCookieWithAccessResult(
+                  net::MatchesCookieWithName("__Host-partitioned"),
+                  MatchesCookieAccessResult(net::IsInclude(), _, _, _))));
+  EXPECT_THAT(excluded_cookies,
+              ElementsAre(MatchesCookieWithAccessResult(
+                  net::MatchesCookieWithName("unpartitioned"),
+                  MatchesCookieAccessResult(
+                      net::HasExclusionReason(
+                          net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES),
+                      _, _, _))));
+
+  // If there is a site-specific content setting blocking cookies, then
+  // partitioned cookies should not be allowed.
+  maybe_included_cookies = MakeUnpartitionedAndPartitionedCookies();
+  excluded_cookies = {};
+  settings.set_block_third_party_cookies(false);
+  settings.set_content_settings(
+      {CreateSetting(kURL, "*", CONTENT_SETTING_BLOCK)});
+  EXPECT_FALSE(settings.AnnotateAndMoveUserBlockedCookies(
+      GURL(kURL), net::SiteForCookies(), &top_level_origin,
+      maybe_included_cookies, excluded_cookies));
+  EXPECT_THAT(maybe_included_cookies, IsEmpty());
+  EXPECT_THAT(
+      excluded_cookies,
+      UnorderedElementsAre(
+          MatchesCookieWithAccessResult(
+              net::MatchesCookieWithName("__Host-partitioned"),
+              MatchesCookieAccessResult(
+                  net::HasExclusionReason(
+                      net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES),
+                  _, _, _)),
+          MatchesCookieWithAccessResult(
+              net::MatchesCookieWithName("unpartitioned"),
+              MatchesCookieAccessResult(
+                  net::HasExclusionReason(
+                      net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES),
+                  _, _, _))));
+
+  // If there is a site-specific content setting blocking cookies on the
+  // current top-level origin, then partitioned cookies should not be allowed.
+  maybe_included_cookies = MakeUnpartitionedAndPartitionedCookies();
+  excluded_cookies = {};
+  settings.set_block_third_party_cookies(true);
+  settings.set_content_settings(
+      {CreateSetting(kOtherURL, "*", CONTENT_SETTING_BLOCK)});
+  EXPECT_FALSE(settings.AnnotateAndMoveUserBlockedCookies(
+      GURL(kURL), net::SiteForCookies(), &top_level_origin,
+      maybe_included_cookies, excluded_cookies));
+  EXPECT_THAT(maybe_included_cookies, IsEmpty());
+  EXPECT_THAT(
+      excluded_cookies,
+      UnorderedElementsAre(
+          MatchesCookieWithAccessResult(
+              net::MatchesCookieWithName("__Host-partitioned"),
+              MatchesCookieAccessResult(
+                  net::HasExclusionReason(
+                      net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES),
+                  _, _, _)),
+          MatchesCookieWithAccessResult(
+              net::MatchesCookieWithName("unpartitioned"),
+              MatchesCookieAccessResult(
+                  net::HasExclusionReason(
+                      net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES),
+                  _, _, _))));
+
+  // If there is a site-specific content setting blocking cookies on the
+  // current top-level origin but only when it is embedded on an unrelated site,
+  // then partitioned cookies should still be allowed.
+  maybe_included_cookies = MakeUnpartitionedAndPartitionedCookies();
+  excluded_cookies = {};
+  settings.set_block_third_party_cookies(true);
+  settings.set_content_settings(
+      {CreateSetting(kOtherURL, kUnrelatedURL, CONTENT_SETTING_BLOCK)});
+  EXPECT_TRUE(settings.AnnotateAndMoveUserBlockedCookies(
+      GURL(kURL), net::SiteForCookies(), &top_level_origin,
+      maybe_included_cookies, excluded_cookies));
+  EXPECT_THAT(maybe_included_cookies,
+              ElementsAre(MatchesCookieWithAccessResult(
+                  net::MatchesCookieWithName("__Host-partitioned"),
+                  MatchesCookieAccessResult(net::IsInclude(), _, _, _))));
+  EXPECT_THAT(excluded_cookies,
+              ElementsAre(MatchesCookieWithAccessResult(
+                  net::MatchesCookieWithName("unpartitioned"),
+                  MatchesCookieAccessResult(
+                      net::HasExclusionReason(
+                          net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES),
+                      _, _, _))));
+}
+
 }  // namespace
 }  // namespace network
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index d4c27c7..781b46e9 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -3990,13 +3990,13 @@
   std::unique_ptr<NetworkContext> network_context =
       CreateContextWithParams(CreateNetworkContextParamsForTesting());
 
-  EXPECT_FALSE(
-      network_context->url_request_context()
-          ->network_delegate()
-          ->ForcePrivacyMode(GURL("http://foo.com"),
-                             net::SiteForCookies::FromUrl(kOtherURL),
-                             url::Origin::Create(kOtherURL),
-                             net::SamePartyContext::Type::kCrossParty));
+  EXPECT_EQ(net::NetworkDelegate::PrivacySetting::kStateAllowed,
+            network_context->url_request_context()
+                ->network_delegate()
+                ->ForcePrivacyMode(GURL("http://foo.com"),
+                                   net::SiteForCookies::FromUrl(kOtherURL),
+                                   url::Origin::Create(kOtherURL),
+                                   net::SamePartyContext::Type::kCrossParty));
 }
 
 TEST_F(NetworkContextTest, PrivacyModeEnabledIfCookiesBlocked) {
@@ -4008,13 +4008,15 @@
 
   SetContentSetting(kURL, kOtherURL, CONTENT_SETTING_BLOCK,
                     network_context.get());
-  EXPECT_TRUE(network_context->url_request_context()
-                  ->network_delegate()
-                  ->ForcePrivacyMode(kURL,
-                                     net::SiteForCookies::FromUrl(kOtherURL),
-                                     url::Origin::Create(kOtherURL),
-                                     net::SamePartyContext::Type::kCrossParty));
-  EXPECT_FALSE(
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kStateDisallowed,
+      network_context->url_request_context()
+          ->network_delegate()
+          ->ForcePrivacyMode(kURL, net::SiteForCookies::FromUrl(kOtherURL),
+                             url::Origin::Create(kOtherURL),
+                             net::SamePartyContext::Type::kCrossParty));
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kStateAllowed,
       network_context->url_request_context()
           ->network_delegate()
           ->ForcePrivacyMode(kOtherURL, net::SiteForCookies::FromUrl(kURL),
@@ -4031,7 +4033,8 @@
 
   SetContentSetting(kURL, kOtherURL, CONTENT_SETTING_ALLOW,
                     network_context.get());
-  EXPECT_FALSE(
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kStateAllowed,
       network_context->url_request_context()
           ->network_delegate()
           ->ForcePrivacyMode(kURL, net::SiteForCookies::FromUrl(kOtherURL),
@@ -4049,7 +4052,8 @@
   // URLs are switched so setting should not apply.
   SetContentSetting(kOtherURL, kURL, CONTENT_SETTING_BLOCK,
                     network_context.get());
-  EXPECT_FALSE(
+  EXPECT_EQ(
+      net::NetworkDelegate::PrivacySetting::kStateAllowed,
       network_context->url_request_context()
           ->network_delegate()
           ->ForcePrivacyMode(kURL, net::SiteForCookies::FromUrl(kOtherURL),
@@ -4069,20 +4073,24 @@
       network_context->url_request_context()->network_delegate();
 
   network_context->cookie_manager()->BlockThirdPartyCookies(true);
-  EXPECT_TRUE(delegate->ForcePrivacyMode(
-      kURL, net::SiteForCookies::FromUrl(kOtherURL), kOtherOrigin,
-      net::SamePartyContext::Type::kCrossParty));
-  EXPECT_FALSE(delegate->ForcePrivacyMode(
-      kURL, net::SiteForCookies::FromUrl(kURL), kOrigin,
-      net::SamePartyContext::Type::kSameParty));
+  EXPECT_EQ(net::NetworkDelegate::PrivacySetting::kPartitionedStateAllowedOnly,
+            delegate->ForcePrivacyMode(
+                kURL, net::SiteForCookies::FromUrl(kOtherURL), kOtherOrigin,
+                net::SamePartyContext::Type::kCrossParty));
+  EXPECT_EQ(net::NetworkDelegate::PrivacySetting::kStateAllowed,
+            delegate->ForcePrivacyMode(
+                kURL, net::SiteForCookies::FromUrl(kURL), kOrigin,
+                net::SamePartyContext::Type::kSameParty));
 
   network_context->cookie_manager()->BlockThirdPartyCookies(false);
-  EXPECT_FALSE(delegate->ForcePrivacyMode(
-      kURL, net::SiteForCookies::FromUrl(kOtherURL), kOtherOrigin,
-      net::SamePartyContext::Type::kCrossParty));
-  EXPECT_FALSE(delegate->ForcePrivacyMode(
-      kURL, net::SiteForCookies::FromUrl(kURL), kOrigin,
-      net::SamePartyContext::Type::kSameParty));
+  EXPECT_EQ(net::NetworkDelegate::PrivacySetting::kStateAllowed,
+            delegate->ForcePrivacyMode(
+                kURL, net::SiteForCookies::FromUrl(kOtherURL), kOtherOrigin,
+                net::SamePartyContext::Type::kCrossParty));
+  EXPECT_EQ(net::NetworkDelegate::PrivacySetting::kStateAllowed,
+            delegate->ForcePrivacyMode(
+                kURL, net::SiteForCookies::FromUrl(kURL), kOrigin,
+                net::SamePartyContext::Type::kSameParty));
 }
 
 TEST_F(NetworkContextTest, CanSetCookieFalseIfCookiesBlocked) {
diff --git a/services/network/network_service_network_delegate.cc b/services/network/network_service_network_delegate.cc
index c765b468..b6ac5b8 100644
--- a/services/network/network_service_network_delegate.cc
+++ b/services/network/network_service_network_delegate.cc
@@ -251,7 +251,8 @@
   return true;
 }
 
-bool NetworkServiceNetworkDelegate::OnForcePrivacyMode(
+net::NetworkDelegate::PrivacySetting
+NetworkServiceNetworkDelegate::OnForcePrivacyMode(
     const GURL& url,
     const net::SiteForCookies& site_for_cookies,
     const absl::optional<url::Origin>& top_frame_origin,
diff --git a/services/network/network_service_network_delegate.h b/services/network/network_service_network_delegate.h
index e43920f..1198b476 100644
--- a/services/network/network_service_network_delegate.h
+++ b/services/network/network_service_network_delegate.h
@@ -12,6 +12,7 @@
 #include "net/base/completion_once_callback.h"
 #include "net/base/network_delegate_impl.h"
 #include "net/cookies/same_party_context.h"
+#include "services/network/cookie_settings.h"
 #include "services/network/network_context.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -75,7 +76,7 @@
                       const net::CanonicalCookie& cookie,
                       net::CookieOptions* options,
                       bool allowed_from_caller) override;
-  bool OnForcePrivacyMode(
+  net::NetworkDelegate::PrivacySetting OnForcePrivacyMode(
       const GURL& url,
       const net::SiteForCookies& site_for_cookies,
       const absl::optional<url::Origin>& top_frame_origin,
diff --git a/services/network/p2p/socket_tcp.cc b/services/network/p2p/socket_tcp.cc
index be573dd..02413ed 100644
--- a/services/network/p2p/socket_tcp.cc
+++ b/services/network/p2p/socket_tcp.cc
@@ -11,7 +11,7 @@
 #include "base/sys_byteorder.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
-#include "jingle/glue/fake_ssl_client_socket.h"
+#include "components/webrtc/fake_ssl_client_socket.h"
 #include "net/base/io_buffer.h"
 #include "net/base/net_errors.h"
 #include "net/base/network_isolation_key.h"
@@ -112,8 +112,7 @@
       IsTlsClientSocket(type_));
 
   if (IsPseudoTlsClientSocket(type_)) {
-    socket_ =
-        std::make_unique<jingle_glue::FakeSSLClientSocket>(std::move(socket_));
+    socket_ = std::make_unique<webrtc::FakeSSLClientSocket>(std::move(socket_));
   }
 
   int status = socket_->Connect(
diff --git a/services/network/p2p/socket_tcp_unittest.cc b/services/network/p2p/socket_tcp_unittest.cc
index 6da2d34..fc17346fb9 100644
--- a/services/network/p2p/socket_tcp_unittest.cc
+++ b/services/network/p2p/socket_tcp_unittest.cc
@@ -17,7 +17,7 @@
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
-#include "jingle/glue/fake_ssl_client_socket.h"
+#include "components/webrtc/fake_ssl_client_socket.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "net/base/features.h"
@@ -511,9 +511,9 @@
   ProxyResolvingClientSocketFactory factory(context.get());
 
   base::StringPiece ssl_client_hello =
-      jingle_glue::FakeSSLClientSocket::GetSslClientHello();
+      webrtc::FakeSSLClientSocket::GetSslClientHello();
   base::StringPiece ssl_server_hello =
-      jingle_glue::FakeSSLClientSocket::GetSslServerHello();
+      webrtc::FakeSSLClientSocket::GetSslServerHello();
   net::MockRead reads[] = {
       net::MockRead(net::ASYNC, ssl_server_hello.data(),
                     ssl_server_hello.size()),
@@ -569,9 +569,9 @@
   ProxyResolvingClientSocketFactory factory(context.get());
 
   base::StringPiece ssl_client_hello =
-      jingle_glue::FakeSSLClientSocket::GetSslClientHello();
+      webrtc::FakeSSLClientSocket::GetSslClientHello();
   base::StringPiece ssl_server_hello =
-      jingle_glue::FakeSSLClientSocket::GetSslServerHello();
+      webrtc::FakeSSLClientSocket::GetSslServerHello();
   net::MockRead reads[] = {
       net::MockRead(net::ASYNC, ssl_server_hello.data(),
                     ssl_server_hello.size()),
diff --git a/services/network/proxy_resolving_socket_factory_mojo.cc b/services/network/proxy_resolving_socket_factory_mojo.cc
index 750391c..95685c22 100644
--- a/services/network/proxy_resolving_socket_factory_mojo.cc
+++ b/services/network/proxy_resolving_socket_factory_mojo.cc
@@ -6,7 +6,7 @@
 
 #include <utility>
 
-#include "jingle/glue/fake_ssl_client_socket.h"
+#include "components/webrtc/fake_ssl_client_socket.h"
 #include "net/url_request/url_request_context.h"
 #include "services/network/proxy_resolving_client_socket.h"
 #include "services/network/proxy_resolving_client_socket_factory.h"
@@ -33,8 +33,8 @@
       url, network_isolation_key, options && options->use_tls);
   if (options && options->fake_tls_handshake) {
     DCHECK(!options->use_tls);
-    net_socket = std::make_unique<jingle_glue::FakeSSLClientSocket>(
-        std::move(net_socket));
+    net_socket =
+        std::make_unique<webrtc::FakeSSLClientSocket>(std::move(net_socket));
   }
 
   auto socket = std::make_unique<ProxyResolvingSocketMojo>(
diff --git a/services/network/proxy_resolving_socket_mojo_unittest.cc b/services/network/proxy_resolving_socket_mojo_unittest.cc
index 111ccb80..3e80792 100644
--- a/services/network/proxy_resolving_socket_mojo_unittest.cc
+++ b/services/network/proxy_resolving_socket_mojo_unittest.cc
@@ -11,7 +11,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
-#include "jingle/glue/fake_ssl_client_socket.h"
+#include "components/webrtc/fake_ssl_client_socket.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -341,9 +341,9 @@
   set_fake_tls_handshake(true);
 
   base::StringPiece client_hello =
-      jingle_glue::FakeSSLClientSocket::GetSslClientHello();
+      webrtc::FakeSSLClientSocket::GetSslClientHello();
   base::StringPiece server_hello =
-      jingle_glue::FakeSSLClientSocket::GetSslServerHello();
+      webrtc::FakeSSLClientSocket::GetSslServerHello();
   std::vector<net::MockRead> reads = {
       net::MockRead(net::ASYNC, server_hello.data(), server_hello.length(), 1),
       net::MockRead(net::ASYNC, 2, kTestMsg),
diff --git a/services/network/public/mojom/proxy_resolving_socket.mojom b/services/network/public/mojom/proxy_resolving_socket.mojom
index 3c5d790..1b9f543 100644
--- a/services/network/public/mojom/proxy_resolving_socket.mojom
+++ b/services/network/public/mojom/proxy_resolving_socket.mojom
@@ -43,7 +43,7 @@
 
   // Tries to do a fake TLS handshake on the connection.
   // This is sometimes used with XMPP to pass through proxies.
-  // See jingle_glue::FakeSSLClientSocket for more details.
+  // See webrtc::FakeSSLClientSocket for more details.
   // Should not be used with |use_tls| set to true.
   bool fake_tls_handshake = false;
 };
diff --git a/services/network/websocket.cc b/services/network/websocket.cc
index ef70ed56..3da24fc 100644
--- a/services/network/websocket.cc
+++ b/services/network/websocket.cc
@@ -743,7 +743,8 @@
 }
 
 void WebSocket::ReadAndSendFromDataPipe(InterruptionReason resume_reason) {
-  if (outgoing_frames_interrupted_ != resume_reason)
+  if (outgoing_frames_interrupted_ != resume_reason &&
+      outgoing_frames_interrupted_ != InterruptionReason::kNone)
     return;
 
   if (outgoing_frames_interrupted_ != InterruptionReason::kNone)
diff --git a/styleguide/c++/c++.md b/styleguide/c++/c++.md
index 5d604c39..de3ff1d 100644
--- a/styleguide/c++/c++.md
+++ b/styleguide/c++/c++.md
@@ -139,12 +139,13 @@
   #include <algorithm>
 
   #include "base/strings/utf_string_conversions.h"
+  #include "build/build_config.h"
   #include "chrome/common/render_messages.h"
 
-  #if defined(OS_WIN)
+  #if BUILDFLAG(IS_WIN)
   #include <windows.h>
   #include "base/win/com_init_util.h"
-  #elif defined(OS_POSIX)
+  #elif BUILDFLAG(IS_POSIX)
   #include "base/posix/global_descriptors.h"
   #endif
 ```
diff --git a/testing/scripts/wpt_android_lib.py b/testing/scripts/wpt_android_lib.py
index 3640dbe..50a4ee8 100644
--- a/testing/scripts/wpt_android_lib.py
+++ b/testing/scripts/wpt_android_lib.py
@@ -433,7 +433,6 @@
       for _ in range(max(args.processes, 1)):
         instance = avd_config.CreateInstance()
         instance.Start(writable_system=True, window=args.emulator_window)
-        device_utils.DeviceUtils(instance.serial).WaitUntilFullyBooted()
         instances.append(instance)
 
     #TODO(weizhong): when choose device, make sure abi matches with target
diff --git a/third_party/blink/common/chrome_debug_urls.cc b/third_party/blink/common/chrome_debug_urls.cc
index f857e7d..2540261f 100644
--- a/third_party/blink/common/chrome_debug_urls.cc
+++ b/third_party/blink/common/chrome_debug_urls.cc
@@ -13,10 +13,10 @@
 #include "third_party/blink/common/crash_helpers.h"
 #include "url/gurl.h"
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 #include "base/debug/invalid_access_win.h"
 #include "base/process/kill.h"
-#elif defined(OS_POSIX)
+#elif BUILDFLAG(IS_POSIX)
 #include <signal.h>
 #endif
 
@@ -44,10 +44,10 @@
 const char kChromeUINetworkErrorURL[] = "chrome://network-error/";
 const char kChromeUINetworkErrorsListingURL[] = "chrome://network-errors/";
 const char kChromeUIProcessInternalsURL[] = "chrome://process-internals";
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
 const char kChromeUIGpuJavaCrashURL[] = "chrome://gpu-java-crash/";
 #endif
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 const char kChromeUIBrowserHeapCorruptionURL[] =
     "chrome://inducebrowserheapcorruption/";
 const char kChromeUIHeapCorruptionCrashURL[] = "chrome://heapcorruptioncrash/";
@@ -58,11 +58,11 @@
 const char kChromeUICrashHeapUnderflowURL[] = "chrome://crash/heap-underflow";
 const char kChromeUICrashUseAfterFreeURL[] = "chrome://crash/use-after-free";
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 const char kChromeUICrashCorruptHeapBlockURL[] =
     "chrome://crash/corrupt-heap-block";
 const char kChromeUICrashCorruptHeapURL[] = "chrome://crash/corrupt-heap";
-#endif  // OS_WIN
+#endif  // BUILDFLAG(IS_WIN)
 #endif  // ADDRESS_SANITIZER
 
 #if DCHECK_IS_ON()
@@ -97,7 +97,7 @@
   }
 #endif
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   if (url == kChromeUIHeapCorruptionCrashURL)
     return true;
 #endif
@@ -107,7 +107,7 @@
     return true;
 #endif
 
-#if defined(OS_WIN) && defined(ADDRESS_SANITIZER)
+#if BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER)
   if (url == kChromeUICrashCorruptHeapBlockURL ||
       url == kChromeUICrashCorruptHeapURL) {
     return true;
@@ -145,7 +145,7 @@
     LOG(ERROR) << "Intentionally causing ASAN heap use-after-free"
                << " because user navigated to " << url.spec();
     base::debug::AsanHeapUseAfterFree();
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   } else if (url == kChromeUICrashCorruptHeapBlockURL) {
     LOG(ERROR) << "Intentionally causing ASAN corrupt heap block"
                << " because user navigated to " << url.spec();
@@ -154,7 +154,7 @@
     LOG(ERROR) << "Intentionally causing ASAN corrupt heap"
                << " because user navigated to " << url.spec();
     base::debug::AsanCorruptHeap();
-#endif  // OS_WIN
+#endif  // BUILDFLAG(IS_WIN)
   }
 }
 #endif  // ADDRESS_SANITIZER
@@ -177,20 +177,20 @@
     // base::debug::SetDumpWithoutCrashingFunction.  Refer to the documentation
     // of base::debug::DumpWithoutCrashing for more details.
     base::debug::DumpWithoutCrashing();
-#if defined(OS_WIN) || defined(OS_POSIX)
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX)
   } else if (url == kChromeUIKillURL) {
     LOG(ERROR) << "Intentionally terminating current process because user"
                   " navigated to "
                << url.spec();
     // Simulate termination such that the base::GetTerminationStatus() API will
     // return TERMINATION_STATUS_PROCESS_WAS_KILLED.
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
     base::Process::TerminateCurrentProcessImmediately(
         base::win::kProcessKilledExitCode);
-#elif defined(OS_POSIX)
+#elif BUILDFLAG(IS_POSIX)
     PCHECK(kill(base::Process::Current().Pid(), SIGTERM) == 0);
 #endif
-#endif  // defined(OS_WIN) || defined(OS_POSIX)
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX)
   } else if (url == kChromeUIHangURL) {
     LOG(ERROR) << "Intentionally hanging ourselves with sleep infinite loop"
                << " because user navigated to " << url.spec();
@@ -212,7 +212,7 @@
     CHECK(false);
   }
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   if (url == kChromeUIHeapCorruptionCrashURL) {
     LOG(ERROR)
         << "Intentionally causing heap corruption because user navigated to "
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index c9f555b..1f080bf 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -63,7 +63,7 @@
 // Desktop platforms.
 const base::Feature kPaintHoldingCrossOrigin {
   "PaintHoldingCrossOrigin",
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       base::FEATURE_DISABLED_BY_DEFAULT
 #else
       base::FEATURE_ENABLED_BY_DEFAULT
@@ -160,7 +160,7 @@
 // future navigations faster.
 const base::Feature kNavigationPredictor {
   "NavigationPredictor",
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       base::FEATURE_ENABLED_BY_DEFAULT
 #else
       base::FEATURE_DISABLED_BY_DEFAULT
@@ -218,7 +218,7 @@
 // command line or a valid Origin Trial token in the page.
 const base::Feature kPrerender2 {
   "Prerender2",
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       base::FEATURE_ENABLED_BY_DEFAULT
 #else
       base::FEATURE_DISABLED_BY_DEFAULT
@@ -254,7 +254,7 @@
 // reasons. Consider enabling by default if experiment results are positive.
 const base::Feature kPurgeRendererMemoryWhenBackgrounded {
   "PurgeRendererMemoryWhenBackgrounded",
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       base::FEATURE_DISABLED_BY_DEFAULT
 #else
       base::FEATURE_ENABLED_BY_DEFAULT
@@ -367,7 +367,7 @@
 // https://crbug.com/1190167
 const base::Feature kSpeculationRulesPrefetchProxy {
   "SpeculationRulesPrefetchProxy",
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       base::FEATURE_ENABLED_BY_DEFAULT
 #else
       base::FEATURE_DISABLED_BY_DEFAULT
@@ -378,7 +378,7 @@
 // "stop" is a legacy name.
 const base::Feature kStopInBackground {
   "stop-in-background",
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       base::FEATURE_ENABLED_BY_DEFAULT
 #else
       base::FEATURE_DISABLED_BY_DEFAULT
@@ -443,7 +443,7 @@
 // https://crbug.com/942440.
 const base::Feature kWebFontsCacheAwareTimeoutAdaption {
   "WebFontsCacheAwareTimeoutAdaption",
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       base::FEATURE_DISABLED_BY_DEFAULT
 #else
       base::FEATURE_ENABLED_BY_DEFAULT
@@ -464,7 +464,7 @@
 // A feature to reduce the set of resources fetched by No-State Prefetch.
 const base::Feature kLightweightNoStatePrefetch {
   "LightweightNoStatePrefetch",
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       base::FEATURE_ENABLED_BY_DEFAULT
 #else
       base::FEATURE_DISABLED_BY_DEFAULT
@@ -567,7 +567,7 @@
 // compositor & IO threads.
 const base::Feature kBlinkCompositorUseDisplayThreadPriority {
   "BlinkCompositorUseDisplayThreadPriority",
-#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_WIN)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_WIN)
       base::FEATURE_ENABLED_BY_DEFAULT
 #else
       base::FEATURE_DISABLED_BY_DEFAULT
@@ -610,7 +610,7 @@
       base::FEATURE_ENABLED_BY_DEFAULT
 #else
       base::FEATURE_DISABLED_BY_DEFAULT
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS)
 };
 
 // Enables the use of shared image swap chains for low latency 2d canvas.
@@ -706,7 +706,7 @@
 // receiving client hints, regardless of Permissions Policy.
 const base::Feature kAllowClientHintsToThirdParty {
   "AllowClientHintsToThirdParty",
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       base::FEATURE_ENABLED_BY_DEFAULT
 #else
       base::FEATURE_DISABLED_BY_DEFAULT
@@ -1174,7 +1174,7 @@
 const base::Feature kClientHintThirdPartyDelegation{
     "ClientHintThirdPartyDelegation", base::FEATURE_DISABLED_BY_DEFAULT};
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
 // Enables prefetching Android fonts on renderer startup.
 const base::Feature kPrefetchAndroidFonts{"PrefetchAndroidFonts",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
@@ -1187,5 +1187,8 @@
 const base::Feature kCompositedCaret{"CompositedCaret",
                                      base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kBackForwardCacheAppBanner{
+    "BackForwardCacheAppBanner", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace blink
diff --git a/third_party/blink/common/mime_util/mime_util.cc b/third_party/blink/common/mime_util/mime_util.cc
index 842e7392f..ca59d69 100644
--- a/third_party/blink/common/mime_util/mime_util.cc
+++ b/third_party/blink/common/mime_util/mime_util.cc
@@ -15,7 +15,7 @@
 #include "third_party/blink/public/common/buildflags.h"
 #include "third_party/blink/public/common/features.h"
 
-#if !defined(OS_IOS)
+#if !BUILDFLAG(IS_IOS)
 // iOS doesn't use and must not depend on //media
 #include "media/base/mime_util.h"
 #endif
@@ -164,7 +164,7 @@
 bool MimeUtil::IsSupportedNonImageMimeType(const std::string& mime_type) const {
   return non_image_types_.find(base::ToLowerASCII(mime_type)) !=
              non_image_types_.end() ||
-#if !defined(OS_IOS)
+#if !BUILDFLAG(IS_IOS)
          media::IsSupportedMediaMimeType(mime_type) ||
 #endif
          (base::StartsWith(mime_type, "text/",
diff --git a/third_party/blink/common/mime_util/mime_util_unittest.cc b/third_party/blink/common/mime_util/mime_util_unittest.cc
index 704d629e..94a23e4 100644
--- a/third_party/blink/common/mime_util/mime_util_unittest.cc
+++ b/third_party/blink/common/mime_util/mime_util_unittest.cc
@@ -41,7 +41,7 @@
   EXPECT_TRUE(IsSupportedNonImageMimeType("application/+json"));
   EXPECT_TRUE(IsSupportedNonImageMimeType("application/x-suggestions+json"));
   EXPECT_TRUE(IsSupportedNonImageMimeType("application/x-s+json;x=2"));
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
 #if 0  // Disabled until http://crbug.com/318217 is resolved.
   EXPECT_TRUE(IsSupportedMediaMimeType("application/vnd.apple.mpegurl"));
   EXPECT_TRUE(IsSupportedMediaMimeType("application/x-mpegurl"));
diff --git a/third_party/blink/common/page_state/page_state_serialization.cc b/third_party/blink/common/page_state/page_state_serialization.cc
index 9f19afc..038e3e0 100644
--- a/third_party/blink/common/page_state/page_state_serialization.cc
+++ b/third_party/blink/common/page_state/page_state_serialization.cc
@@ -29,7 +29,7 @@
 
 namespace {
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
 float g_device_scale_factor_for_testing = 0.0;
 #endif
 
@@ -565,7 +565,7 @@
   if (obj->version < 14)
     ReadString(obj);  // Skip unused referrer string.
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   if (obj->version == 11) {
     // Now-unused values that shipped in this version of Chrome for Android when
     // it was on a private branch.
@@ -1016,7 +1016,7 @@
   *encoded = obj.GetAsString();
 }
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
 bool DecodePageStateWithDeviceScaleFactorForTesting(
     const std::string& encoded,
     float device_scale_factor,
diff --git a/third_party/blink/common/page_state/page_state_serialization_unittest.cc b/third_party/blink/common/page_state/page_state_serialization_unittest.cc
index 676cec1..2a0117d 100644
--- a/third_party/blink/common/page_state/page_state_serialization_unittest.cc
+++ b/third_party/blink/common/page_state/page_state_serialization_unittest.cc
@@ -269,7 +269,7 @@
     EXPECT_TRUE(
         base::Base64Decode(trimmed_file_contents, &saved_encoded_state));
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
     // Because version 11 of the file format unfortunately bakes in the device
     // scale factor on Android, perform this test by assuming a preset device
     // scale factor, ignoring the device scale factor of the current device.
@@ -285,7 +285,7 @@
   void TestBackwardsCompat(int version) {
     std::string suffix = base::StringPrintf("v%d", version);
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
     // Unfortunately, the format of version 11 is different on Android, so we
     // need to use a special reference file.
     if (version == 11) {
@@ -522,7 +522,7 @@
 }
 #endif
 
-#if !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_ANDROID)
 // TODO(darin): Re-enable for Android once this test accounts for systems with
 //              a device scale factor not equal to 2.
 TEST_F(PageStateSerializationTest, BackwardsCompat_v11) {
diff --git a/third_party/blink/common/renderer_preferences/renderer_preferences_mojom_traits.cc b/third_party/blink/common/renderer_preferences/renderer_preferences_mojom_traits.cc
index e71369ee..4197cc6 100644
--- a/third_party/blink/common/renderer_preferences/renderer_preferences_mojom_traits.cc
+++ b/third_party/blink/common/renderer_preferences/renderer_preferences_mojom_traits.cc
@@ -64,11 +64,11 @@
   if (!data.ReadAcceptLanguages(&out->accept_languages))
     return false;
 
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   if (!data.ReadSystemFontFamilyName(&out->system_font_family_name))
     return false;
 #endif
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   if (!data.ReadCaptionFontFamilyName(&out->caption_font_family_name))
     return false;
   out->caption_font_height = data.caption_font_height();
diff --git a/third_party/blink/common/web_preferences/web_preferences.cc b/third_party/blink/common/web_preferences/web_preferences.cc
index 9250d08..30e237f8 100644
--- a/third_party/blink/common/web_preferences/web_preferences.cc
+++ b/third_party/blink/common/web_preferences/web_preferences.cc
@@ -39,7 +39,7 @@
       minimum_font_size(0),
       minimum_logical_font_size(6),
       default_encoding("ISO-8859-1"),
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
       context_menu_on_mouse_up(true),
 #else
       context_menu_on_mouse_up(false),
@@ -104,22 +104,22 @@
       dont_send_key_events_to_javascript(false),
       sync_xhr_in_documents_enabled(true),
       number_of_cpu_cores(1),
-#if defined(OS_MAC)
+#if BUILDFLAG(IS_MAC)
       editing_behavior(mojom::EditingBehavior::kEditingMacBehavior),
-#elif defined(OS_WIN)
+#elif BUILDFLAG(IS_WIN)
       editing_behavior(mojom::EditingBehavior::kEditingWindowsBehavior),
-#elif defined(OS_ANDROID)
+#elif BUILDFLAG(IS_ANDROID)
       editing_behavior(mojom::EditingBehavior::kEditingAndroidBehavior),
-#elif defined(OS_CHROMEOS)
+#elif BUILDFLAG(IS_CHROMEOS)
       editing_behavior(mojom::EditingBehavior::kEditingChromeOSBehavior),
-#elif defined(OS_POSIX)
+#elif BUILDFLAG(IS_POSIX)
       editing_behavior(mojom::EditingBehavior::kEditingUnixBehavior),
 #else
       editing_behavior(mojom::EditingBehavior::kEditingMacBehavior),
 #endif
       supports_multiple_windows(true),
       viewport_enabled(false),
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       viewport_meta_enabled(true),
       shrinks_viewport_contents_to_fit(true),
       viewport_style(mojom::ViewportStyle::kMobile),
@@ -135,7 +135,7 @@
       main_frame_resizes_are_orientation_changes(false),
 #endif
       initialize_at_minimum_page_scale(true),
-#if defined(OS_MAC)
+#if BUILDFLAG(IS_MAC)
       smart_insert_delete_enabled(true),
 #else
       smart_insert_delete_enabled(false),
@@ -153,13 +153,13 @@
       text_tracks_enabled(false),
       text_track_margin_percentage(0.0f),
       immersive_mode_enabled(false),
-#if defined(OS_ANDROID) || defined(OS_MAC)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC)
       double_tap_to_zoom_enabled(true),
 #else
       double_tap_to_zoom_enabled(false),
 #endif
       fullscreen_supported(true),
-#if !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_ANDROID)
       text_autosizing_enabled(false),
 #else
       text_autosizing_enabled(true),
@@ -185,11 +185,11 @@
       css_hex_alpha_color_enabled(true),
       scroll_top_left_interop_enabled(true),
       disable_accelerated_small_canvases(false),
-#endif  // defined(OS_ANDROID)
-#if defined(OS_ANDROID)
+#endif  // BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       default_minimum_page_scale_factor(0.25f),
       default_maximum_page_scale_factor(5.f),
-#elif defined(OS_MAC)
+#elif BUILDFLAG(IS_MAC)
       default_minimum_page_scale_factor(1.f),
       default_maximum_page_scale_factor(3.f),
 #else
diff --git a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
index b1f4625..b9a65b0a 100644
--- a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
+++ b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
@@ -47,7 +47,7 @@
       !data.ReadNetworkQualityEstimatorWebHoldback(
           &out->network_quality_estimator_web_holdback) ||
       !data.ReadWebAppScope(&out->web_app_scope)
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
       || !data.ReadDefaultVideoPosterUrl(&out->default_video_poster_url)
 #endif
   )
@@ -165,7 +165,7 @@
   out->double_tap_to_zoom_enabled = data.double_tap_to_zoom_enabled();
   out->fullscreen_supported = data.fullscreen_supported();
   out->text_autosizing_enabled = data.text_autosizing_enabled();
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   out->font_scale_factor = data.font_scale_factor();
   out->device_scale_adjustment = data.device_scale_adjustment();
   out->force_enable_zoom = data.force_enable_zoom();
diff --git a/third_party/blink/public/common/chrome_debug_urls.h b/third_party/blink/public/common/chrome_debug_urls.h
index 4cd3e9a..71c0cee 100644
--- a/third_party/blink/public/common/chrome_debug_urls.h
+++ b/third_party/blink/public/common/chrome_debug_urls.h
@@ -39,10 +39,10 @@
 BLINK_COMMON_EXPORT extern const char kChromeUINetworkErrorsListingURL[];
 BLINK_COMMON_EXPORT extern const char kChromeUINetworkErrorURL[];
 BLINK_COMMON_EXPORT extern const char kChromeUIProcessInternalsURL[];
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
 BLINK_COMMON_EXPORT extern const char kChromeUIGpuJavaCrashURL[];
 #endif
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 BLINK_COMMON_EXPORT extern const char kChromeUIBrowserHeapCorruptionURL[];
 BLINK_COMMON_EXPORT extern const char kChromeUIHeapCorruptionCrashURL[];
 #endif
@@ -51,10 +51,10 @@
 BLINK_COMMON_EXPORT extern const char kChromeUICrashHeapOverflowURL[];
 BLINK_COMMON_EXPORT extern const char kChromeUICrashHeapUnderflowURL[];
 BLINK_COMMON_EXPORT extern const char kChromeUICrashUseAfterFreeURL[];
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 BLINK_COMMON_EXPORT extern const char kChromeUICrashCorruptHeapBlockURL[];
 BLINK_COMMON_EXPORT extern const char kChromeUICrashCorruptHeapURL[];
-#endif  // OS_WIN
+#endif  // BUILDFLAG(IS_WIN)
 #endif  // ADDRESS_SANITIZER
 
 #if DCHECK_IS_ON()
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 17e7ffe..0772551 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -574,7 +574,7 @@
 
 BLINK_COMMON_EXPORT extern const base::Feature kClientHintThirdPartyDelegation;
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
 BLINK_COMMON_EXPORT extern const base::Feature kPrefetchAndroidFonts;
 #endif
 
@@ -583,6 +583,10 @@
 
 BLINK_COMMON_EXPORT extern const base::Feature kCompositedCaret;
 
+// Allows pages that support App Install Banners to stay eligible for the
+// back/forward cache.
+BLINK_COMMON_EXPORT extern const base::Feature kBackForwardCacheAppBanner;
+
 }  // namespace features
 }  // namespace blink
 
diff --git a/third_party/blink/public/common/page/launching_process_state.h b/third_party/blink/public/common/page/launching_process_state.h
index 0b07d18..a39486d 100644
--- a/third_party/blink/public/common/page/launching_process_state.h
+++ b/third_party/blink/public/common/page/launching_process_state.h
@@ -12,7 +12,7 @@
 
 // This file is used to maintain a consistent initial set of state between the
 // RendererProcessHostImpl and the RendererSchedulerImpl.
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
 // This matches Android's ChildProcessConnection state before OnProcessLaunched.
 constexpr bool kLaunchingProcessIsBackgrounded = true;
 #else
diff --git a/third_party/blink/public/common/page_state/page_state_serialization.h b/third_party/blink/public/common/page_state/page_state_serialization.h
index 167fd92..02cf96a 100644
--- a/third_party/blink/public/common/page_state/page_state_serialization.h
+++ b/third_party/blink/public/common/page_state/page_state_serialization.h
@@ -93,7 +93,7 @@
     int version,
     std::string* encoded);
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
 BLINK_COMMON_EXPORT bool DecodePageStateWithDeviceScaleFactorForTesting(
     const std::string& encoded,
     float device_scale_factor,
diff --git a/third_party/blink/public/common/renderer_preferences/renderer_preferences.h b/third_party/blink/public/common/renderer_preferences/renderer_preferences.h
index 3f4df0f1..9ed0ad6c 100644
--- a/third_party/blink/public/common/renderer_preferences/renderer_preferences.h
+++ b/third_party/blink/public/common/renderer_preferences/renderer_preferences.h
@@ -50,10 +50,10 @@
   bool webrtc_allow_legacy_tls_protocols{false};
   UserAgentOverride user_agent_override;
   std::string accept_languages;
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   std::string system_font_family_name;
 #endif
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   std::u16string caption_font_family_name;
   int32_t caption_font_height{0};
   std::u16string small_caption_font_family_name;
diff --git a/third_party/blink/public/common/renderer_preferences/renderer_preferences_mojom_traits.h b/third_party/blink/public/common/renderer_preferences/renderer_preferences_mojom_traits.h
index 1a083230..5841162 100644
--- a/third_party/blink/public/common/renderer_preferences/renderer_preferences_mojom_traits.h
+++ b/third_party/blink/public/common/renderer_preferences/renderer_preferences_mojom_traits.h
@@ -19,7 +19,7 @@
 #include "third_party/blink/public/mojom/renderer_preferences.mojom-shared.h"
 #include "ui/gfx/mojom/font_render_params_mojom_traits.h"
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 #include "mojo/public/cpp/base/string16_mojom_traits.h"
 #endif
 
@@ -157,7 +157,7 @@
     return data.accept_languages;
   }
 
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   static const std::string& system_font_family_name(
       const ::blink::RendererPreferences& data) {
     return data.system_font_family_name;
@@ -171,7 +171,7 @@
   }
 #endif
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   static const std::u16string& caption_font_family_name(
       const ::blink::RendererPreferences& data) {
     return data.caption_font_family_name;
diff --git a/third_party/blink/public/common/web_preferences/web_preferences.h b/third_party/blink/public/common/web_preferences/web_preferences.h
index 0924c76..a8d4f19 100644
--- a/third_party/blink/public/common/web_preferences/web_preferences.h
+++ b/third_party/blink/public/common/web_preferences/web_preferences.h
@@ -219,7 +219,7 @@
   // Representation of the Web App Manifest scope if any.
   GURL web_app_scope;
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   float font_scale_factor;
   float device_scale_adjustment;
   bool force_enable_zoom;
@@ -257,7 +257,7 @@
 
   // Don't accelerate small canvases to avoid crashes TODO(crbug.com/1004304)
   bool disable_accelerated_small_canvases;
-#endif  // defined(OS_ANDROID)
+#endif  // BUILDFLAG(IS_ANDROID)
 
   // Enable forcibly modifying content rendering to result in a light on dark
   // color scheme.
diff --git a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
index 871c4f6..dc84e6f5 100644
--- a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
+++ b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
@@ -546,7 +546,7 @@
     return r.web_app_scope;
   }
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   static float font_scale_factor(const blink::web_pref::WebPreferences& r) {
     return r.font_scale_factor;
   }
diff --git a/third_party/blink/public/platform/media/web_media_player_impl.h b/third_party/blink/public/platform/media/web_media_player_impl.h
index 0307614..7a24e5c 100644
--- a/third_party/blink/public/platform/media/web_media_player_impl.h
+++ b/third_party/blink/public/platform/media/web_media_player_impl.h
@@ -229,7 +229,7 @@
   void OnIdleTimeout() override;
   void RequestRemotePlaybackDisabled(bool disabled) override;
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   // TODO(https://crbug.com/839651): Rename Flinging[Started/Stopped] to
   // RemotePlayback[Started/Stopped] once the other RemotePlayback methods have
   // been removed
@@ -856,7 +856,7 @@
   // MediaResource::Type::URL for now.
   bool using_media_player_renderer_ = false;
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   // Set during the initial DoLoad() call. Used to determine whether to allow
   // credentials or not for MediaPlayerRenderer.
   bool allow_media_player_renderer_credentials_ = false;
diff --git a/third_party/blink/public/platform/platform.h b/third_party/blink/public/platform/platform.h
index f545c18..6acdee7 100644
--- a/third_party/blink/public/platform/platform.h
+++ b/third_party/blink/public/platform/platform.h
@@ -419,7 +419,7 @@
     return nullptr;
   }
 
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   // This is called after the compositor thread is created, so the embedder
   // can initiate an IPC to change its thread priority (on Linux we can't
   // increase the nice value, so we need to ask the browser process). This
@@ -578,7 +578,7 @@
   // called by platform/graphics/ is fine.
   virtual bool IsGpuCompositingDisabled() const { return true; }
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   // Returns if synchronous compositing is enabled. Only used for Android
   // webview.
   virtual bool IsSynchronousCompositingEnabledForAndroidWebView() {
diff --git a/third_party/blink/public/platform/scheduler/test/web_fake_thread_scheduler.h b/third_party/blink/public/platform/scheduler/test/web_fake_thread_scheduler.h
index 881204c..9fe37c7 100644
--- a/third_party/blink/public/platform/scheduler/test/web_fake_thread_scheduler.h
+++ b/third_party/blink/public/platform/scheduler/test/web_fake_thread_scheduler.h
@@ -49,7 +49,7 @@
   void SetRendererHidden(bool hidden) override;
   void SetRendererBackgrounded(bool backgrounded) override;
   std::unique_ptr<RendererPauseHandle> PauseRenderer() override;
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   void PauseTimersForAndroidWebView() override;
   void ResumeTimersForAndroidWebView() override;
 #endif
diff --git a/third_party/blink/public/platform/scheduler/test/web_mock_thread_scheduler.h b/third_party/blink/public/platform/scheduler/test/web_mock_thread_scheduler.h
index e45264b6..181aa79 100644
--- a/third_party/blink/public/platform/scheduler/test/web_mock_thread_scheduler.h
+++ b/third_party/blink/public/platform/scheduler/test/web_mock_thread_scheduler.h
@@ -59,7 +59,7 @@
   MOCK_METHOD1(SetRendererHidden, void(bool));
   MOCK_METHOD1(SetRendererBackgrounded, void(bool));
   MOCK_METHOD0(PauseRenderer, std::unique_ptr<RendererPauseHandle>());
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   MOCK_METHOD0(PauseTimersForAndroidWebView, void());
   MOCK_METHOD0(ResumeTimersForAndroidWebView, void());
 #endif
diff --git a/third_party/blink/public/platform/scheduler/web_thread_scheduler.h b/third_party/blink/public/platform/scheduler/web_thread_scheduler.h
index 90a41184..90d203d 100644
--- a/third_party/blink/public/platform/scheduler/web_thread_scheduler.h
+++ b/third_party/blink/public/platform/scheduler/web_thread_scheduler.h
@@ -198,7 +198,7 @@
   // handling.
   virtual void OnMainFrameRequestedForInput();
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   // Android WebView has very strange WebView.pauseTimers/resumeTimers API.
   // It's very old and very inconsistent. The API promises that this
   // "pauses all layout, parsing, and JavaScript timers for all WebViews".
@@ -209,7 +209,7 @@
   // DO NOT USE FOR ANYTHING EXCEPT ANDROID WEBVIEW API IMPLEMENTATION.
   virtual void PauseTimersForAndroidWebView();
   virtual void ResumeTimersForAndroidWebView();
-#endif  // defined(OS_ANDROID)
+#endif  // BUILDFLAG(IS_ANDROID)
 
   // RAII handle for pausing the renderer. Renderer is paused while
   // at least one pause handle exists.
diff --git a/third_party/blink/public/platform/web_theme_engine.h b/third_party/blink/public/platform/web_theme_engine.h
index 4fa509a..9ee4759 100644
--- a/third_party/blink/public/platform/web_theme_engine.h
+++ b/third_party/blink/public/platform/web_theme_engine.h
@@ -188,7 +188,7 @@
     std::map<SystemThemeColor, uint32_t> colors;
   };
 
-#if defined(OS_MAC)
+#if BUILDFLAG(IS_MAC)
   enum ScrollbarOrientation {
     // Vertical scrollbar on the right side of content.
     kVerticalOnRight,
@@ -217,7 +217,7 @@
     ProgressBarExtraParams progress_bar;
     ScrollbarThumbExtraParams scrollbar_thumb;
     ScrollbarButtonExtraParams scrollbar_button;
-#if defined(OS_MAC)
+#if BUILDFLAG(IS_MAC)
     ScrollbarExtraParams scrollbar_extra;
 #endif
   };
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 0f87646..90991ff6 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
@@ -177,7 +177,7 @@
   // Returns true if encoded output can be enabled in the source.
   virtual bool SupportsEncodedOutput() const;
 
-#if !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_ANDROID)
   // Start/stop cropping a video track.
   // Non-empty |crop_id| sets (or changes) the crop-target.
   // Empty |crop_id| reverts the capture to its original, uncropped state.
diff --git a/third_party/blink/public/web/modules/mediastream/webmediaplayer_ms.h b/third_party/blink/public/web/modules/mediastream/webmediaplayer_ms.h
index b49562e4..ccd7a757 100644
--- a/third_party/blink/public/web/modules/mediastream/webmediaplayer_ms.h
+++ b/third_party/blink/public/web/modules/mediastream/webmediaplayer_ms.h
@@ -203,9 +203,9 @@
  private:
   friend class WebMediaPlayerMSTest;
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   static const gfx::Size kUseGpuMemoryBufferVideoFramesMinResolution;
-#endif  // defined(OS_WIN)
+#endif  // BUILDFLAG(IS_WIN)
 
   bool IsInPictureInPicture() const;
 
diff --git a/third_party/blink/public/web/web_local_frame.h b/third_party/blink/public/web/web_local_frame.h
index 2ce14df..fed1e95 100644
--- a/third_party/blink/public/web/web_local_frame.h
+++ b/third_party/blink/public/web/web_local_frame.h
@@ -98,7 +98,7 @@
 struct WebPrintPresetOptions;
 struct WebScriptSource;
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 struct WebFontFamilyNames;
 #endif
 
@@ -833,7 +833,7 @@
 
   // Fonts --------------------------------------------------------------------
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // Returns the font family names currently used.
   virtual WebFontFamilyNames GetWebFontFamilyNames() const = 0;
 #endif
diff --git a/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.cc b/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.cc
index 1c39a7c..7720c75 100644
--- a/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.cc
+++ b/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.cc
@@ -11,7 +11,6 @@
 #include "base/process/memory.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.h b/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.h
index c300514..fd813c41 100644
--- a/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.h
+++ b/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.h
@@ -8,8 +8,7 @@
 #include "third_party/blink/renderer/core/accessibility/blink_ax_event_intent.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/document.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/heap/persistent.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/animation/css/css_timing_data.h b/third_party/blink/renderer/core/animation/css/css_timing_data.h
index 1d0ee6a3..93f420ba 100644
--- a/third_party/blink/renderer/core/animation/css/css_timing_data.h
+++ b/third_party/blink/renderer/core/animation/css/css_timing_data.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_CSS_CSS_TIMING_DATA_H_
 
 #include "third_party/blink/renderer/platform/animation/timing_function.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/animation/css_interpolation_types_map.h b/third_party/blink/renderer/core/animation/css_interpolation_types_map.h
index 5df4519..de9b847 100644
--- a/third_party/blink/renderer/core/animation/css_interpolation_types_map.h
+++ b/third_party/blink/renderer/core/animation/css_interpolation_types_map.h
@@ -9,7 +9,6 @@
 #include "third_party/blink/renderer/core/animation/interpolation_types_map.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/document.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/animation/image_list_property_functions.h b/third_party/blink/renderer/core/animation/image_list_property_functions.h
index 2717ce8..e3f1544 100644
--- a/third_party/blink/renderer/core/animation/image_list_property_functions.h
+++ b/third_party/blink/renderer/core/animation/image_list_property_functions.h
@@ -7,7 +7,8 @@
 
 #include "third_party/blink/renderer/core/css/css_property_names.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/animation/interpolation_type.h b/third_party/blink/renderer/core/animation/interpolation_type.h
index b2567c48..dfa7e6d 100644
--- a/third_party/blink/renderer/core/animation/interpolation_type.h
+++ b/third_party/blink/renderer/core/animation/interpolation_type.h
@@ -14,7 +14,6 @@
 #include "third_party/blink/renderer/core/animation/property_handle.h"
 #include "third_party/blink/renderer/core/animation/underlying_value_owner.h"
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/animation/interpolation_value.h b/third_party/blink/renderer/core/animation/interpolation_value.h
index 2de684a0..891b293 100644
--- a/third_party/blink/renderer/core/animation/interpolation_value.h
+++ b/third_party/blink/renderer/core/animation/interpolation_value.h
@@ -8,7 +8,7 @@
 #include <memory>
 #include "third_party/blink/renderer/core/animation/interpolable_value.h"
 #include "third_party/blink/renderer/core/animation/non_interpolable_value.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/animation/pairwise_interpolation_value.h b/third_party/blink/renderer/core/animation/pairwise_interpolation_value.h
index 87bb431..1d2879d 100644
--- a/third_party/blink/renderer/core/animation/pairwise_interpolation_value.h
+++ b/third_party/blink/renderer/core/animation/pairwise_interpolation_value.h
@@ -8,7 +8,7 @@
 #include <memory>
 #include "third_party/blink/renderer/core/animation/interpolable_value.h"
 #include "third_party/blink/renderer/core/animation/non_interpolable_value.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/animation/primitive_interpolation.h b/third_party/blink/renderer/core/animation/primitive_interpolation.h
index e2627ea..e2e8c0c 100644
--- a/third_party/blink/renderer/core/animation/primitive_interpolation.h
+++ b/third_party/blink/renderer/core/animation/primitive_interpolation.h
@@ -13,7 +13,7 @@
 #include "base/memory/ptr_util.h"
 #include "third_party/blink/renderer/core/animation/typed_interpolation_value.h"
 #include "third_party/blink/renderer/platform/geometry/blend.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/clipboard/dragged_isolated_file_system.cc b/third_party/blink/renderer/core/clipboard/dragged_isolated_file_system.cc
index fd44d03..48b0bf573 100644
--- a/third_party/blink/renderer/core/clipboard/dragged_isolated_file_system.cc
+++ b/third_party/blink/renderer/core/clipboard/dragged_isolated_file_system.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/core/clipboard/dragged_isolated_file_system.h"
 
+#include "base/check.h"
+
 namespace blink {
 
 DraggedIsolatedFileSystem::FileSystemIdPreparationCallback
diff --git a/third_party/blink/renderer/core/clipboard/dragged_isolated_file_system.h b/third_party/blink/renderer/core/clipboard/dragged_isolated_file_system.h
index 75092ce1..0d33f01 100644
--- a/third_party/blink/renderer/core/clipboard/dragged_isolated_file_system.h
+++ b/third_party/blink/renderer/core/clipboard/dragged_isolated_file_system.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CLIPBOARD_DRAGGED_ISOLATED_FILE_SYSTEM_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/css/active_style_sheets.h b/third_party/blink/renderer/core/css/active_style_sheets.h
index 3123684..17d2312 100644
--- a/third_party/blink/renderer/core/css/active_style_sheets.h
+++ b/third_party/blink/renderer/core/css/active_style_sheets.h
@@ -9,7 +9,7 @@
 #include "third_party/blink/renderer/core/css/media_value_change.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/css/basic_shape_functions.h b/third_party/blink/renderer/core/css/basic_shape_functions.h
index 36e87566..01b0672e 100644
--- a/third_party/blink/renderer/core/css/basic_shape_functions.h
+++ b/third_party/blink/renderer/core/css/basic_shape_functions.h
@@ -32,7 +32,6 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/core/style/basic_shapes.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace gfx {
 class PointF;
diff --git a/third_party/blink/renderer/core/css/css_layout_function_value.h b/third_party/blink/renderer/core/css/css_layout_function_value.h
index ea14616f..d59475b6 100644
--- a/third_party/blink/renderer/core/css/css_layout_function_value.h
+++ b/third_party/blink/renderer/core/css/css_layout_function_value.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_LAYOUT_FUNCTION_VALUE_H_
 
 #include "third_party/blink/renderer/core/css/css_value.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node.cc b/third_party/blink/renderer/core/css/css_math_expression_node.cc
index 20a8fb3..503a8ae 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node.cc
+++ b/third_party/blink/renderer/core/css/css_math_expression_node.cc
@@ -381,7 +381,9 @@
         return kCalcOther;
       return left_category == kCalcNumber ? right_category : left_category;
     case CSSMathOperator::kDivide:
-      if (right_category != kCalcNumber || right_side.IsZero())
+      if (right_category != kCalcNumber ||
+          (!RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled() &&
+           right_side.IsZero()))
         return kCalcOther;
       return left_category;
     default:
@@ -565,14 +567,14 @@
     const CSSToLengthConversionData& conversion_data) const {
   switch (operator_) {
     case CSSMathOperator::kAdd:
-      return CalculationExpressionOperatorNode::CreateSimplified(
-          CalculationExpressionOperatorNode::Children(
+      return CalculationExpressionOperationNode::CreateSimplified(
+          CalculationExpressionOperationNode::Children(
               {left_side_->ToCalculationExpression(conversion_data),
                right_side_->ToCalculationExpression(conversion_data)}),
           CalculationOperator::kAdd);
     case CSSMathOperator::kSubtract:
-      return CalculationExpressionOperatorNode::CreateSimplified(
-          CalculationExpressionOperatorNode::Children(
+      return CalculationExpressionOperationNode::CreateSimplified(
+          CalculationExpressionOperationNode::Children(
               {left_side_->ToCalculationExpression(conversion_data),
                right_side_->ToCalculationExpression(conversion_data)}),
           CalculationOperator::kSubtract);
@@ -580,23 +582,23 @@
       DCHECK_NE((left_side_->Category() == kCalcNumber),
                 (right_side_->Category() == kCalcNumber));
       if (left_side_->Category() == kCalcNumber) {
-        return CalculationExpressionOperatorNode::CreateSimplified(
-            CalculationExpressionOperatorNode::Children(
+        return CalculationExpressionOperationNode::CreateSimplified(
+            CalculationExpressionOperationNode::Children(
                 {right_side_->ToCalculationExpression(conversion_data),
                  base::MakeRefCounted<CalculationExpressionNumberNode>(
                      left_side_->DoubleValue())}),
             CalculationOperator::kMultiply);
       }
-      return CalculationExpressionOperatorNode::CreateSimplified(
-          CalculationExpressionOperatorNode::Children(
+      return CalculationExpressionOperationNode::CreateSimplified(
+          CalculationExpressionOperationNode::Children(
               {left_side_->ToCalculationExpression(conversion_data),
                base::MakeRefCounted<CalculationExpressionNumberNode>(
                    right_side_->DoubleValue())}),
           CalculationOperator::kMultiply);
     case CSSMathOperator::kDivide:
       DCHECK_EQ(right_side_->Category(), kCalcNumber);
-      return CalculationExpressionOperatorNode::CreateSimplified(
-          CalculationExpressionOperatorNode::Children(
+      return CalculationExpressionOperationNode::CreateSimplified(
+          CalculationExpressionOperationNode::Children(
               {left_side_->ToCalculationExpression(conversion_data),
                base::MakeRefCounted<CalculationExpressionNumberNode>(
                    1.0 / right_side_->DoubleValue())}),
@@ -977,7 +979,7 @@
   auto expression_operator = operator_ == CSSMathOperator::kMin
                                  ? CalculationOperator::kMin
                                  : CalculationOperator::kMax;
-  return CalculationExpressionOperatorNode::CreateSimplified(
+  return CalculationExpressionOperationNode::CreateSimplified(
       std::move(operands), expression_operator);
 }
 
@@ -1349,9 +1351,9 @@
     return Create(pixels_and_percent.GetPixelsAndPercent());
   }
 
-  DCHECK(node.IsOperator());
+  DCHECK(node.IsOperation());
 
-  const auto& operation = To<CalculationExpressionOperatorNode>(node);
+  const auto& operation = To<CalculationExpressionOperationNode>(node);
   const auto& children = operation.GetChildren();
   const auto calc_op = operation.GetOperator();
   switch (calc_op) {
diff --git a/third_party/blink/renderer/core/css/document_style_sheet_collector.h b/third_party/blink/renderer/core/css/document_style_sheet_collector.h
index 55a3d8c..97af610 100644
--- a/third_party/blink/renderer/core/css/document_style_sheet_collector.h
+++ b/third_party/blink/renderer/core/css/document_style_sheet_collector.h
@@ -29,7 +29,8 @@
 
 #include "third_party/blink/renderer/core/css/active_style_sheets.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/css/dom_window_css.h b/third_party/blink/renderer/core/css/dom_window_css.h
index 3889ea3..570a706 100644
--- a/third_party/blink/renderer/core/css/dom_window_css.h
+++ b/third_party/blink/renderer/core/css/dom_window_css.h
@@ -32,7 +32,6 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/css/font_face_source.h b/third_party/blink/renderer/core/css/font_face_source.h
index ac53642..73566cb 100644
--- a/third_party/blink/renderer/core/css/font_face_source.h
+++ b/third_party/blink/renderer/core/css/font_face_source.h
@@ -7,7 +7,6 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/css/invalidation/style_invalidator.h b/third_party/blink/renderer/core/css/invalidation/style_invalidator.h
index c8365e6..76edb15b 100644
--- a/third_party/blink/renderer/core/css/invalidation/style_invalidator.h
+++ b/third_party/blink/renderer/core/css/invalidation/style_invalidator.h
@@ -7,7 +7,7 @@
 
 #include "third_party/blink/renderer/core/css/invalidation/invalidation_flags.h"
 #include "third_party/blink/renderer/core/css/invalidation/pending_invalidations.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/css/media_query.h b/third_party/blink/renderer/core/css/media_query.h
index 8a0a24e..6b3d4a6 100644
--- a/third_party/blink/renderer/core/css/media_query.h
+++ b/third_party/blink/renderer/core/css/media_query.h
@@ -34,7 +34,6 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/geometry/axis.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.h b/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.h
index faf577c..b5e6c6a8 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.h
+++ b/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.h
@@ -8,7 +8,6 @@
 #include "third_party/blink/renderer/core/css/css_property_names.h"
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_impl.h b/third_party/blink/renderer/core/css/parser/css_parser_impl.h
index e566e56..0a92680 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_impl.h
+++ b/third_party/blink/renderer/core/css/parser/css_parser_impl.h
@@ -14,7 +14,7 @@
 #include "third_party/blink/renderer/core/css/css_property_value_set.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h"
 #include "third_party/blink/renderer/core/css/parser/css_tokenized_value.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/css/parser/css_supports_parser.h b/third_party/blink/renderer/core/css/parser/css_supports_parser.h
index 05c5f2a..7ed6589 100644
--- a/third_party/blink/renderer/core/css/parser/css_supports_parser.h
+++ b/third_party/blink/renderer/core/css/parser/css_supports_parser.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PARSER_CSS_SUPPORTS_PARSER_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/css/parser/css_variable_parser.h b/third_party/blink/renderer/core/css/parser/css_variable_parser.h
index 31c1c08..26106b4 100644
--- a/third_party/blink/renderer/core/css/parser/css_variable_parser.h
+++ b/third_party/blink/renderer/core/css/parser/css_variable_parser.h
@@ -8,7 +8,6 @@
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/css/parser/sizes_attribute_parser.h b/third_party/blink/renderer/core/css/parser/sizes_attribute_parser.h
index cf8881f..3d4ef951 100644
--- a/third_party/blink/renderer/core/css/parser/sizes_attribute_parser.h
+++ b/third_party/blink/renderer/core/css/parser/sizes_attribute_parser.h
@@ -8,7 +8,7 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/media_values.h"
 #include "third_party/blink/renderer/core/css/parser/media_query_parser.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
index 34946d32..7efccb6 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
@@ -18,7 +18,8 @@
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/core/frame/web_feature_forward.h"
 #include "third_party/blink/renderer/core/style/grid_area.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/css/resolver/element_style_resources.h b/third_party/blink/renderer/core/css/resolver/element_style_resources.h
index e18b1f8..5d9b580c 100644
--- a/third_party/blink/renderer/core/css/resolver/element_style_resources.h
+++ b/third_party/blink/renderer/core/css/resolver/element_style_resources.h
@@ -25,7 +25,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RESOLVER_ELEMENT_STYLE_RESOURCES_H_
 
 #include "third_party/blink/renderer/core/css/css_property_names.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/css/resolver/filter_operation_resolver.h b/third_party/blink/renderer/core/css/resolver/filter_operation_resolver.h
index b0961e0..e13f9a71 100644
--- a/third_party/blink/renderer/core/css/resolver/filter_operation_resolver.h
+++ b/third_party/blink/renderer/core/css/resolver/filter_operation_resolver.h
@@ -26,7 +26,7 @@
 #include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h"
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/core/style/filter_operations.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/css/resolver/font_builder.h b/third_party/blink/renderer/core/css/resolver/font_builder.h
index 89c596cf..4ec5226 100644
--- a/third_party/blink/renderer/core/css/resolver/font_builder.h
+++ b/third_party/blink/renderer/core/css/resolver/font_builder.h
@@ -31,7 +31,7 @@
 #include "third_party/blink/renderer/platform/fonts/font_description.h"
 #include "third_party/blink/renderer/platform/fonts/font_palette.h"
 #include "third_party/blink/renderer/platform/fonts/font_variant_numeric.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/css/resolver/media_query_result.h b/third_party/blink/renderer/core/css/resolver/media_query_result.h
index e9309df1..6793e401 100644
--- a/third_party/blink/renderer/core/css/resolver/media_query_result.h
+++ b/third_party/blink/renderer/core/css/resolver/media_query_result.h
@@ -25,6 +25,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RESOLVER_MEDIA_QUERY_RESULT_H_
 
 #include "third_party/blink/renderer/core/css/media_query_exp.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder.h b/third_party/blink/renderer/core/css/resolver/style_builder.h
index 9db84ac..fc8d5074 100644
--- a/third_party/blink/renderer/core/css/resolver/style_builder.h
+++ b/third_party/blink/renderer/core/css/resolver/style_builder.h
@@ -34,7 +34,7 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_property_names.h"
 #include "third_party/blink/renderer/core/css/properties/css_property.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.h b/third_party/blink/renderer/core/css/resolver/style_cascade.h
index a3313380..18cd4df5 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.h
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.h
@@ -19,7 +19,9 @@
 #include "third_party/blink/renderer/core/css/resolver/cascade_priority.h"
 #include "third_party/blink/renderer/core/css/resolver/match_result.h"
 #include "third_party/blink/renderer/core/frame/web_feature_forward.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/css/resolver/transform_builder.cc b/third_party/blink/renderer/core/css/resolver/transform_builder.cc
index fe1b90d..665ac59 100644
--- a/third_party/blink/renderer/core/css/resolver/transform_builder.cc
+++ b/third_party/blink/renderer/core/css/resolver/transform_builder.cc
@@ -35,7 +35,6 @@
 #include "third_party/blink/renderer/core/css/css_math_function_value.h"
 #include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value_mappings.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/transforms/matrix_3d_transform_operation.h"
 #include "third_party/blink/renderer/platform/transforms/matrix_transform_operation.h"
 #include "third_party/blink/renderer/platform/transforms/perspective_transform_operation.h"
diff --git a/third_party/blink/renderer/core/css/resolver/transform_builder.h b/third_party/blink/renderer/core/css/resolver/transform_builder.h
index d8084352..e7a4d3e 100644
--- a/third_party/blink/renderer/core/css/resolver/transform_builder.h
+++ b/third_party/blink/renderer/core/css/resolver/transform_builder.h
@@ -31,8 +31,8 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RESOLVER_TRANSFORM_BUILDER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RESOLVER_TRANSFORM_BUILDER_H_
 
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/transforms/transform_operations.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/css/rule_feature_set.h b/third_party/blink/renderer/core/css/rule_feature_set.h
index 8835bdbd2..35df8f4 100644
--- a/third_party/blink/renderer/core/css/rule_feature_set.h
+++ b/third_party/blink/renderer/core/css/rule_feature_set.h
@@ -28,7 +28,7 @@
 #include "third_party/blink/renderer/core/css/invalidation/invalidation_flags.h"
 #include "third_party/blink/renderer/core/css/invalidation/invalidation_set.h"
 #include "third_party/blink/renderer/core/css/media_query_evaluator.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
diff --git a/third_party/blink/renderer/core/css/selector_query.h b/third_party/blink/renderer/core/css/selector_query.h
index dd570b6..46f3265b 100644
--- a/third_party/blink/renderer/core/css/selector_query.h
+++ b/third_party/blink/renderer/core/css/selector_query.h
@@ -30,7 +30,7 @@
 #include <memory>
 
 #include "third_party/blink/renderer/core/css/css_selector_list.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
diff --git a/third_party/blink/renderer/core/css/style_sheet.h b/third_party/blink/renderer/core/css/style_sheet.h
index aafeade..a8374bc 100644
--- a/third_party/blink/renderer/core/css/style_sheet.h
+++ b/third_party/blink/renderer/core/css/style_sheet.h
@@ -23,7 +23,6 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/css/style_sheet_candidate.h b/third_party/blink/renderer/core/css/style_sheet_candidate.h
index 5fdeb1ba..59b5291 100644
--- a/third_party/blink/renderer/core/css/style_sheet_candidate.h
+++ b/third_party/blink/renderer/core/css/style_sheet_candidate.h
@@ -27,7 +27,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_SHEET_CANDIDATE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_SHEET_CANDIDATE_H_
 
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
diff --git a/third_party/blink/renderer/core/dom/child_frame_disconnector.h b/third_party/blink/renderer/core/dom/child_frame_disconnector.h
index 42c8831..7d64ba0 100644
--- a/third_party/blink/renderer/core/dom/child_frame_disconnector.h
+++ b/third_party/blink/renderer/core/dom/child_frame_disconnector.h
@@ -6,7 +6,8 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_CHILD_FRAME_DISCONNECTOR_H_
 
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 3af2462a..ab3e273 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -2496,8 +2496,8 @@
   if (reason != DocumentUpdateReason::kBeginMainFrame && frame_view)
     frame_view->DidFinishForcedLayout(reason);
 
-  if (update_focus_appearance_after_layout_)
-    UpdateFocusAppearance();
+  if (should_update_selection_after_layout_)
+    UpdateSelectionAfterLayout();
 }
 
 void Document::LayoutUpdated() {
@@ -4785,11 +4785,11 @@
         frame->Selection().DidChangeFocus();
       return false;
     }
-    CancelFocusAppearanceUpdate();
+    SetShouldUpdateSelectionAfterLayout(false);
     EnsurePaintLocationDataValidForNode(focused_element_,
                                         DocumentUpdateReason::kFocus);
-    focused_element_->UpdateFocusAppearanceWithOptions(
-        params.selection_behavior, params.options);
+    focused_element_->UpdateSelectionOnFocus(params.selection_behavior,
+                                             params.options);
 
     // Dispatch the focus event and let the node do any other focus related
     // activities (important for text fields)
@@ -6969,25 +6969,13 @@
   return true;
 }
 
-void Document::UpdateFocusAppearanceAfterLayout() {
-  update_focus_appearance_after_layout_ = true;
-}
-
-void Document::CancelFocusAppearanceUpdate() {
-  update_focus_appearance_after_layout_ = false;
-}
-
-bool Document::WillUpdateFocusAppearance() const {
-  return update_focus_appearance_after_layout_;
-}
-
-void Document::UpdateFocusAppearance() {
-  update_focus_appearance_after_layout_ = false;
+void Document::UpdateSelectionAfterLayout() {
+  should_update_selection_after_layout_ = false;
   Element* element = FocusedElement();
   if (!element)
     return;
   if (element->IsFocusable())
-    element->UpdateFocusAppearance(SelectionBehaviorOnFocus::kRestore);
+    element->UpdateSelectionOnFocus(SelectionBehaviorOnFocus::kRestore);
 }
 
 void Document::AttachRange(Range* range) {
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 16fdd5c..c832bf7 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -1248,11 +1248,12 @@
   // there is no such element.
   HTMLLinkElement* LinkCanonical() const;
 
-  void UpdateFocusAppearanceAfterLayout();
-  void CancelFocusAppearanceUpdate();
-  // Return true after UpdateFocusAppearanceAfterLayout() call and before
-  // updating focus appearance.
-  bool WillUpdateFocusAppearance() const;
+  void SetShouldUpdateSelectionAfterLayout(bool flag) {
+    should_update_selection_after_layout_ = flag;
+  }
+  bool ShouldUpdateSelectionAfterLayout() const {
+    return should_update_selection_after_layout_;
+  }
 
   void SendFocusNotification(Element*, mojom::blink::FocusType);
 
@@ -1895,7 +1896,7 @@
 
   void UpdateTitle(const String&);
   void DispatchDidReceiveTitle();
-  void UpdateFocusAppearance();
+  void UpdateSelectionAfterLayout();
   void UpdateBaseURL();
 
   void ExecuteScriptsWaitingForResources();
@@ -2120,7 +2121,7 @@
   Member<AXObjectCache> ax_object_cache_;
   Member<DocumentMarkerController> markers_;
 
-  bool update_focus_appearance_after_layout_ = false;
+  bool should_update_selection_after_layout_ = false;
 
   Member<Element> css_target_;
 
diff --git a/third_party/blink/renderer/core/dom/dom_string_list.h b/third_party/blink/renderer/core/dom/dom_string_list.h
index bd1ea46..96a689c6 100644
--- a/third_party/blink/renderer/core/dom/dom_string_list.h
+++ b/third_party/blink/renderer/core/dom/dom_string_list.h
@@ -28,7 +28,6 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/dom/dom_string_map.h b/third_party/blink/renderer/core/dom/dom_string_map.h
index 1a95d1e..d49e9d9f 100644
--- a/third_party/blink/renderer/core/dom/dom_string_map.h
+++ b/third_party/blink/renderer/core/dom/dom_string_map.h
@@ -29,7 +29,6 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 7903efd..2b110480 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -4499,12 +4499,12 @@
   }
 }
 
-void Element::UpdateFocusAppearance(
+void Element::UpdateSelectionOnFocus(
     SelectionBehaviorOnFocus selection_behavior) {
-  UpdateFocusAppearanceWithOptions(selection_behavior, FocusOptions::Create());
+  UpdateSelectionOnFocus(selection_behavior, FocusOptions::Create());
 }
 
-void Element::UpdateFocusAppearanceWithOptions(
+void Element::UpdateSelectionOnFocus(
     SelectionBehaviorOnFocus selection_behavior,
     const FocusOptions* options) {
   if (selection_behavior == SelectionBehaviorOnFocus::kNone)
@@ -4559,7 +4559,7 @@
 }
 
 void Element::blur() {
-  CancelFocusAppearanceUpdate();
+  CancelSelectionAfterLayout();
   if (AdjustedFocusedElementInTreeScope() == this) {
     Document& doc = GetDocument();
     if (doc.GetPage()) {
@@ -5580,9 +5580,9 @@
   return GetDocument().GetCachedLocale(ComputeInheritedLanguage());
 }
 
-void Element::CancelFocusAppearanceUpdate() {
+void Element::CancelSelectionAfterLayout() {
   if (GetDocument().FocusedElement() == this)
-    GetDocument().CancelFocusAppearanceUpdate();
+    GetDocument().SetShouldUpdateSelectionAfterLayout(false);
 }
 
 void Element::UpdateFirstLetterPseudoElement(StyleUpdatePhase phase) {
@@ -7078,7 +7078,13 @@
     container_pseudo->UpdatePseudoElement(
         kPseudoIdTransitionNewContent, style_recalc_change,
         style_recalc_context, document_transition_tag);
+    container_pseudo->ClearChildNeedsStyleRecalc();
   }
+
+  // Regular pseudo update doesn't clear child style, since there are
+  // (typically) no children / dirty child style. However, here we do need to
+  // clear the child dirty bit.
+  transition_pseudo->ClearChildNeedsStyleRecalc();
 }
 
 void Element::RebuildTransitionPseudoLayoutTree(
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 7ac352e..4647059 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -694,9 +694,11 @@
   void focus();
   void focus(const FocusOptions*);
 
-  void UpdateFocusAppearance(SelectionBehaviorOnFocus);
-  virtual void UpdateFocusAppearanceWithOptions(SelectionBehaviorOnFocus,
-                                                const FocusOptions*);
+  void UpdateSelectionOnFocus(SelectionBehaviorOnFocus);
+  // This function is called after SetFocused(true) before dispatching 'focus'
+  // event, or is called just after a layout after changing <input> type.
+  virtual void UpdateSelectionOnFocus(SelectionBehaviorOnFocus,
+                                      const FocusOptions*);
   virtual void blur();
 
   // Whether this element can receive focus at all. Most elements are not
@@ -1338,7 +1340,7 @@
       AtomicString name,
       WTF::AtomicStringTable::WeakResult hint) const;
 
-  void CancelFocusAppearanceUpdate();
+  void CancelSelectionAfterLayout();
   virtual int DefaultTabIndex() const;
 
   const ComputedStyle* VirtualEnsureComputedStyle(
diff --git a/third_party/blink/renderer/core/dom/events/scoped_event_queue.h b/third_party/blink/renderer/core/dom/events/scoped_event_queue.h
index 504b51b..c818b0f 100644
--- a/third_party/blink/renderer/core/dom/events/scoped_event_queue.h
+++ b/third_party/blink/renderer/core/dom/events/scoped_event_queue.h
@@ -34,7 +34,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/dom/idle_deadline.h b/third_party/blink/renderer/core/dom/idle_deadline.h
index fc98ee6..851b4f4 100644
--- a/third_party/blink/renderer/core/dom/idle_deadline.h
+++ b/third_party/blink/renderer/core/dom/idle_deadline.h
@@ -8,7 +8,6 @@
 #include "base/time/time.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace base {
 class TickClock;
diff --git a/third_party/blink/renderer/core/dom/iterator.h b/third_party/blink/renderer/core/dom/iterator.h
index 49bc8540..0b05887 100644
--- a/third_party/blink/renderer/core/dom/iterator.h
+++ b/third_party/blink/renderer/core/dom/iterator.h
@@ -8,7 +8,6 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/dom/mutation_record.h b/third_party/blink/renderer/core/dom/mutation_record.h
index 1e9c54066..74e133c 100644
--- a/third_party/blink/renderer/core/dom/mutation_record.h
+++ b/third_party/blink/renderer/core/dom/mutation_record.h
@@ -35,7 +35,6 @@
 #include "third_party/blink/renderer/core/dom/static_node_list.h"
 #include "third_party/blink/renderer/core/probe/async_task_context.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/dom/node_child_removal_tracker.h b/third_party/blink/renderer/core/dom/node_child_removal_tracker.h
index 533b52ca..10c60ec 100644
--- a/third_party/blink/renderer/core/dom/node_child_removal_tracker.h
+++ b/third_party/blink/renderer/core/dom/node_child_removal_tracker.h
@@ -28,7 +28,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_NODE_CHILD_REMOVAL_TRACKER_H_
 
 #include "third_party/blink/renderer/core/dom/node.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/dom/node_with_index.h b/third_party/blink/renderer/core/dom/node_with_index.h
index 952cf7d..9be240e 100644
--- a/third_party/blink/renderer/core/dom/node_with_index.h
+++ b/third_party/blink/renderer/core/dom/node_with_index.h
@@ -27,7 +27,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_NODE_WITH_INDEX_H_
 
 #include "third_party/blink/renderer/core/dom/node.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/dom/parent_node.h b/third_party/blink/renderer/core/dom/parent_node.h
index 30c8866..3c24faa 100644
--- a/third_party/blink/renderer/core/dom/parent_node.h
+++ b/third_party/blink/renderer/core/dom/parent_node.h
@@ -34,7 +34,8 @@
 #include "third_party/blink/renderer/core/dom/container_node.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/html/html_collection.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/dom/traversal_range.h b/third_party/blink/renderer/core/dom/traversal_range.h
index cd8d349e..0de1c7d 100644
--- a/third_party/blink/renderer/core/dom/traversal_range.h
+++ b/third_party/blink/renderer/core/dom/traversal_range.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TRAVERSAL_RANGE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TRAVERSAL_RANGE_H_
 
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/editing/commands/editor_command.h b/third_party/blink/renderer/core/editing/commands/editor_command.h
index ca5b9d9..e476e06 100644
--- a/third_party/blink/renderer/core/editing/commands/editor_command.h
+++ b/third_party/blink/renderer/core/editing/commands/editor_command.h
@@ -33,7 +33,7 @@
 #include "base/gtest_prod_util.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/static_range.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/editing/iterators/text_iterator.h b/third_party/blink/renderer/core/editing/iterators/text_iterator.h
index b589f2d2..4d65bc55 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_iterator.h
+++ b/third_party/blink/renderer/core/editing/iterators/text_iterator.h
@@ -34,7 +34,7 @@
 #include "third_party/blink/renderer/core/editing/iterators/text_iterator_behavior.h"
 #include "third_party/blink/renderer/core/editing/iterators/text_iterator_text_node_handler.h"
 #include "third_party/blink/renderer/core/editing/iterators/text_iterator_text_state.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/editing/iterators/text_iterator_text_node_handler.h b/third_party/blink/renderer/core/editing/iterators/text_iterator_text_node_handler.h
index 17de213..e525e9a 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_iterator_text_node_handler.h
+++ b/third_party/blink/renderer/core/editing/iterators/text_iterator_text_node_handler.h
@@ -8,7 +8,8 @@
 #include "third_party/blink/renderer/core/dom/text.h"
 #include "third_party/blink/renderer/core/editing/iterators/text_iterator_behavior.h"
 #include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/editing/iterators/text_iterator_text_state.h b/third_party/blink/renderer/core/editing/iterators/text_iterator_text_state.h
index 366a8af..0314d6c 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_iterator_text_state.h
+++ b/third_party/blink/renderer/core/editing/iterators/text_iterator_text_state.h
@@ -29,7 +29,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/editing/iterators/text_iterator_behavior.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/editing/markers/marker_test_utilities.h b/third_party/blink/renderer/core/editing/markers/marker_test_utilities.h
index 6fe2aff8..a95c33dce 100644
--- a/third_party/blink/renderer/core/editing/markers/marker_test_utilities.h
+++ b/third_party/blink/renderer/core/editing/markers/marker_test_utilities.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_MARKERS_MARKER_TEST_UTILITIES_H_
 
 #include "third_party/blink/renderer/core/editing/markers/suggestion_marker.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
 
 namespace blink {
 inline bool compare_markers(const Member<DocumentMarker>& marker1,
diff --git a/third_party/blink/renderer/core/editing/markers/sorted_document_marker_list_editor.h b/third_party/blink/renderer/core/editing/markers/sorted_document_marker_list_editor.h
index ba88c1fc..d354a84 100644
--- a/third_party/blink/renderer/core/editing/markers/sorted_document_marker_list_editor.h
+++ b/third_party/blink/renderer/core/editing/markers/sorted_document_marker_list_editor.h
@@ -7,7 +7,6 @@
 
 #include "third_party/blink/renderer/core/editing/markers/document_marker_list.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/editing/markers/unsorted_document_marker_list_editor.h b/third_party/blink/renderer/core/editing/markers/unsorted_document_marker_list_editor.h
index 86297b2..7b1a504 100644
--- a/third_party/blink/renderer/core/editing/markers/unsorted_document_marker_list_editor.h
+++ b/third_party/blink/renderer/core/editing/markers/unsorted_document_marker_list_editor.h
@@ -7,7 +7,6 @@
 
 #include "third_party/blink/renderer/core/editing/markers/document_marker_list.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/editing/plain_text_range.h b/third_party/blink/renderer/core/editing/plain_text_range.h
index dc874c9..9724c0d8 100644
--- a/third_party/blink/renderer/core/editing/plain_text_range.h
+++ b/third_party/blink/renderer/core/editing/plain_text_range.h
@@ -28,7 +28,6 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/editing/forward.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
 
diff --git a/third_party/blink/renderer/core/editing/serializers/create_markup_options.h b/third_party/blink/renderer/core/editing/serializers/create_markup_options.h
index 1f1a14a..551a4542 100644
--- a/third_party/blink/renderer/core/editing/serializers/create_markup_options.h
+++ b/third_party/blink/renderer/core/editing/serializers/create_markup_options.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_SERIALIZERS_CREATE_MARKUP_OPTIONS_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/editing/serializers/serialization.h b/third_party/blink/renderer/core/editing/serializers/serialization.h
index 41e05f7..9570dec 100644
--- a/third_party/blink/renderer/core/editing/serializers/serialization.h
+++ b/third_party/blink/renderer/core/editing/serializers/serialization.h
@@ -33,7 +33,6 @@
 #include "third_party/blink/renderer/core/editing/forward.h"
 #include "third_party/blink/renderer/core/editing/serializers/create_markup_options.h"
 #include "third_party/blink/renderer/core/editing/serializers/html_interchange.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/editing/spellcheck/hot_mode_spell_check_requester.h b/third_party/blink/renderer/core/editing/spellcheck/hot_mode_spell_check_requester.h
index 9e17c50f..1d8b188 100644
--- a/third_party/blink/renderer/core/editing/spellcheck/hot_mode_spell_check_requester.h
+++ b/third_party/blink/renderer/core/editing/spellcheck/hot_mode_spell_check_requester.h
@@ -7,7 +7,7 @@
 
 #include "third_party/blink/renderer/core/editing/forward.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/editing/visible_units.cc b/third_party/blink/renderer/core/editing/visible_units.cc
index f0a3c88..543e056 100644
--- a/third_party/blink/renderer/core/editing/visible_units.cc
+++ b/third_party/blink/renderer/core/editing/visible_units.cc
@@ -61,7 +61,6 @@
 #include "third_party/blink/renderer/core/layout/line/inline_text_box.h"
 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h"
 #include "third_party/blink/renderer/core/svg_element_type_helpers.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/text/text_boundaries.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 
diff --git a/third_party/blink/renderer/core/events/event_factory.h b/third_party/blink/renderer/core/events/event_factory.h
index 5494663..3aa6d8d 100644
--- a/third_party/blink/renderer/core/events/event_factory.h
+++ b/third_party/blink/renderer/core/events/event_factory.h
@@ -28,7 +28,6 @@
 
 #include <memory>
 #include "base/memory/scoped_refptr.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
diff --git a/third_party/blink/renderer/core/execution_context/remote_security_context.h b/third_party/blink/renderer/core/execution_context/remote_security_context.h
index 35f3dcb..89d699c 100644
--- a/third_party/blink/renderer/core/execution_context/remote_security_context.h
+++ b/third_party/blink/renderer/core/execution_context/remote_security_context.h
@@ -9,7 +9,6 @@
 #include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/execution_context/security_context.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/execution_context/security_context_init.h b/third_party/blink/renderer/core/execution_context/security_context_init.h
index 4ef43557..4d781ee 100644
--- a/third_party/blink/renderer/core/execution_context/security_context_init.h
+++ b/third_party/blink/renderer/core/execution_context/security_context_init.h
@@ -16,7 +16,7 @@
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/frame/web_feature.h"
 #include "third_party/blink/renderer/core/permissions_policy/policy_helper.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/exported/web_memory_statistics.cc b/third_party/blink/renderer/core/exported/web_memory_statistics.cc
index 190fcf7..78680fc9 100644
--- a/third_party/blink/renderer/core/exported/web_memory_statistics.cc
+++ b/third_party/blink/renderer/core/exported/web_memory_statistics.cc
@@ -4,7 +4,6 @@
 
 #include "third_party/blink/public/web/web_memory_statistics.h"
 
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/heap/process_heap.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
 
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 87e3d73..297ed65 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1857,7 +1857,7 @@
         // no caret and does respond to keyboard inputs.
         focused_frame->GetDocument()->UpdateStyleAndLayoutTree();
         if (element->IsTextControl()) {
-          element->UpdateFocusAppearance(SelectionBehaviorOnFocus::kRestore);
+          element->UpdateSelectionOnFocus(SelectionBehaviorOnFocus::kRestore);
         } else if (HasEditableStyle(*element)) {
           // updateFocusAppearance() selects all the text of
           // contentseditable DIVs. So we set the selection explicitly
diff --git a/third_party/blink/renderer/core/fetch/body.h b/third_party/blink/renderer/core/fetch/body.h
index 93d4442..957bf16 100644
--- a/third_party/blink/renderer/core/fetch/body.h
+++ b/third_party/blink/renderer/core/fetch/body.h
@@ -11,7 +11,6 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/frame/external.h b/third_party/blink/renderer/core/frame/external.h
index bcc2791..2e587fd 100644
--- a/third_party/blink/renderer/core/frame/external.h
+++ b/third_party/blink/renderer/core/frame/external.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_EXTERNAL_H_
 
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/frame/frame_serializer.cc b/third_party/blink/renderer/core/frame/frame_serializer.cc
index 962c04a..83d4dc7 100644
--- a/third_party/blink/renderer/core/frame/frame_serializer.cc
+++ b/third_party/blink/renderer/core/frame/frame_serializer.cc
@@ -65,7 +65,7 @@
 #include "third_party/blink/renderer/core/style/style_fetched_image.h"
 #include "third_party/blink/renderer/core/style/style_image.h"
 #include "third_party/blink/renderer/platform/graphics/image.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/instrumentation/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/mhtml/serialized_resource.h"
diff --git a/third_party/blink/renderer/core/frame/frame_serializer.h b/third_party/blink/renderer/core/frame/frame_serializer.h
index d8a9f46..2abe6f3 100644
--- a/third_party/blink/renderer/core/frame/frame_serializer.h
+++ b/third_party/blink/renderer/core/frame/frame_serializer.h
@@ -33,9 +33,9 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/attribute.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl_hash.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/deque.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
diff --git a/third_party/blink/renderer/core/frame/local_frame_client.h b/third_party/blink/renderer/core/frame/local_frame_client.h
index aa2c98b5..cf6f792 100644
--- a/third_party/blink/renderer/core/frame/local_frame_client.h
+++ b/third_party/blink/renderer/core/frame/local_frame_client.h
@@ -70,7 +70,6 @@
 #include "third_party/blink/renderer/core/loader/frame_load_request.h"
 #include "third_party/blink/renderer/core/loader/frame_loader_types.h"
 #include "third_party/blink/renderer/core/loader/navigation_policy.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
 #include "third_party/blink/renderer/platform/network/content_security_policy_parsers.h"
diff --git a/third_party/blink/renderer/core/frame/rotation_viewport_anchor.h b/third_party/blink/renderer/core/frame/rotation_viewport_anchor.h
index 00cd506..2d25641 100644
--- a/third_party/blink/renderer/core/frame/rotation_viewport_anchor.h
+++ b/third_party/blink/renderer/core/frame/rotation_viewport_anchor.h
@@ -7,7 +7,7 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/size_f.h"
diff --git a/third_party/blink/renderer/core/frame/smart_clip.h b/third_party/blink/renderer/core/frame/smart_clip.h
index 826e38a..4a9b93b 100644
--- a/third_party/blink/renderer/core/frame/smart_clip.h
+++ b/third_party/blink/renderer/core/frame/smart_clip.h
@@ -33,7 +33,7 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/node.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "ui/gfx/geometry/rect.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/frame/web_frame.cc b/third_party/blink/renderer/core/frame/web_frame.cc
index beabcd0..611f8ed 100644
--- a/third_party/blink/renderer/core/frame/web_frame.cc
+++ b/third_party/blink/renderer/core/frame/web_frame.cc
@@ -23,7 +23,6 @@
 #include "third_party/blink/renderer/core/html_names.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/geometry/dom_point_read_only.h b/third_party/blink/renderer/core/geometry/dom_point_read_only.h
index 4ffd237..d3ac5a2 100644
--- a/third_party/blink/renderer/core/geometry/dom_point_read_only.h
+++ b/third_party/blink/renderer/core/geometry/dom_point_read_only.h
@@ -7,7 +7,6 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_context_creation_attributes_core.h b/third_party/blink/renderer/core/html/canvas/canvas_context_creation_attributes_core.h
index 88c6d46c..9ba74aeb 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_context_creation_attributes_core.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_context_creation_attributes_core.h
@@ -8,7 +8,7 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_types.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/html/custom/ce_reactions_scope.h b/third_party/blink/renderer/core/html/custom/ce_reactions_scope.h
index 9f24787f..20afbd4 100644
--- a/third_party/blink/renderer/core/html/custom/ce_reactions_scope.h
+++ b/third_party/blink/renderer/core/html/custom/ce_reactions_scope.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_CUSTOM_CE_REACTIONS_SCOPE_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_test_helpers.h b/third_party/blink/renderer/core/html/custom/custom_element_test_helpers.h
index b6e7910..57295aa4 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_test_helpers.h
+++ b/third_party/blink/renderer/core/html/custom/custom_element_test_helpers.h
@@ -15,8 +15,6 @@
 #include "third_party/blink/renderer/core/html/custom/custom_element_definition_builder.h"
 #include "third_party/blink/renderer/core/html/html_document.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
-#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_upgrade_sorter.h b/third_party/blink/renderer/core/html/custom/custom_element_upgrade_sorter.h
index 658685d3..3bd895eb 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_upgrade_sorter.h
+++ b/third_party/blink/renderer/core/html/custom/custom_element_upgrade_sorter.h
@@ -9,7 +9,7 @@
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_upgrade_sorter_test.cc b/third_party/blink/renderer/core/html/custom/custom_element_upgrade_sorter_test.cc
index 38b1af2..de2bd2f7 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_upgrade_sorter_test.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element_upgrade_sorter_test.cc
@@ -14,8 +14,7 @@
 #include "third_party/blink/renderer/core/html_names.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
-#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/html/forms/file_chooser.h b/third_party/blink/renderer/core/html/forms/file_chooser.h
index b79759e..a79223a 100644
--- a/third_party/blink/renderer/core/html/forms/file_chooser.h
+++ b/third_party/blink/renderer/core/html/forms/file_chooser.h
@@ -33,7 +33,6 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/choosers/file_chooser.mojom-blink.h"
 #include "third_party/blink/renderer/core/page/popup_opening_observer.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
diff --git a/third_party/blink/renderer/core/html/forms/html_input_element.cc b/third_party/blink/renderer/core/html/forms/html_input_element.cc
index 9e95d794..05d666b 100644
--- a/third_party/blink/renderer/core/html/forms/html_input_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_input_element.cc
@@ -318,7 +318,7 @@
   return TextControlElement::ShouldHaveFocusAppearance();
 }
 
-void HTMLInputElement::UpdateFocusAppearanceWithOptions(
+void HTMLInputElement::UpdateSelectionOnFocus(
     SelectionBehaviorOnFocus selection_behavior,
     const FocusOptions* options) {
   if (IsTextField()) {
@@ -347,8 +347,7 @@
         GetDocument().GetFrame()->Selection().RevealSelection();
     }
   } else {
-    TextControlElement::UpdateFocusAppearanceWithOptions(selection_behavior,
-                                                         options);
+    TextControlElement::UpdateSelectionOnFocus(selection_behavior, options);
   }
 }
 
@@ -579,7 +578,7 @@
   // UA Shadow tree was recreated. We need to set selection again. We do it
   // later in order to avoid force layout.
   if (GetDocument().FocusedElement() == this)
-    GetDocument().UpdateFocusAppearanceAfterLayout();
+    GetDocument().SetShouldUpdateSelectionAfterLayout(true);
 
   // TODO(tkent): Should we dispatch a change event?
   ClearValueBeforeFirstUserEdit();
diff --git a/third_party/blink/renderer/core/html/forms/html_input_element.h b/third_party/blink/renderer/core/html/forms/html_input_element.h
index e64c4e3..31e1569 100644
--- a/third_party/blink/renderer/core/html/forms/html_input_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_input_element.h
@@ -211,8 +211,8 @@
   bool LayoutObjectIsNeeded(const ComputedStyle&) const final;
   LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override;
   void DetachLayoutTree(bool performing_reattach) final;
-  void UpdateFocusAppearanceWithOptions(SelectionBehaviorOnFocus,
-                                        const FocusOptions*) final;
+  void UpdateSelectionOnFocus(SelectionBehaviorOnFocus,
+                              const FocusOptions*) final;
 
   // FIXME: For isActivatedSubmit and setActivatedSubmit, we should use the
   // NVI-idiom here by making it private virtual in all classes and expose a
diff --git a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
index a00069db..d30b2fc 100644
--- a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
@@ -315,7 +315,7 @@
   return true;
 }
 
-void HTMLTextAreaElement::UpdateFocusAppearanceWithOptions(
+void HTMLTextAreaElement::UpdateSelectionOnFocus(
     SelectionBehaviorOnFocus selection_behavior,
     const FocusOptions* options) {
   switch (selection_behavior) {
diff --git a/third_party/blink/renderer/core/html/forms/html_text_area_element.h b/third_party/blink/renderer/core/html/forms/html_text_area_element.h
index 44daf89d..8048015c 100644
--- a/third_party/blink/renderer/core/html/forms/html_text_area_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_text_area_element.h
@@ -124,8 +124,8 @@
   bool HasCustomFocusLogic() const override;
   bool MayTriggerVirtualKeyboard() const override;
   bool IsKeyboardFocusable() const override;
-  void UpdateFocusAppearanceWithOptions(SelectionBehaviorOnFocus,
-                                        const FocusOptions*) override;
+  void UpdateSelectionOnFocus(SelectionBehaviorOnFocus,
+                              const FocusOptions*) override;
 
   void AccessKeyAction(SimulatedClickCreationScope creation_scope) override;
 
diff --git a/third_party/blink/renderer/core/html/forms/option_list.h b/third_party/blink/renderer/core/html/forms/option_list.h
index 5847136..97edf62 100644
--- a/third_party/blink/renderer/core/html/forms/option_list.h
+++ b/third_party/blink/renderer/core/html/forms/option_list.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_OPTION_LIST_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/html/forms/text_control_element.cc b/third_party/blink/renderer/core/html/forms/text_control_element.cc
index 43024a2..abe5eabf 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_element.cc
+++ b/third_party/blink/renderer/core/html/forms/text_control_element.cc
@@ -448,7 +448,7 @@
 
 bool TextControlElement::ShouldApplySelectionCache() const {
   const auto& doc = GetDocument();
-  return doc.FocusedElement() != this || doc.WillUpdateFocusAppearance();
+  return doc.FocusedElement() != this || doc.ShouldUpdateSelectionAfterLayout();
 }
 
 bool TextControlElement::SetSelectionRange(
diff --git a/third_party/blink/renderer/core/html/forms/text_field_input_type.cc b/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
index 0420730..1e4b851 100644
--- a/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
@@ -399,8 +399,9 @@
           MakeGarbageCollected<DataListIndicatorElement>(document);
       rp_container->AppendChild(data_list);
       data_list->InitializeInShadowTree();
-      if (GetElement().GetDocument().FocusedElement() == GetElement())
-        GetElement().UpdateFocusAppearance(SelectionBehaviorOnFocus::kRestore);
+      Element& input = GetElement();
+      if (input.GetDocument().FocusedElement() == input)
+        input.UpdateSelectionOnFocus(SelectionBehaviorOnFocus::kRestore);
     }
   } else {
     picker->remove(ASSERT_NO_EXCEPTION);
diff --git a/third_party/blink/renderer/core/html/html_area_element.cc b/third_party/blink/renderer/core/html/html_area_element.cc
index 7702be4..89b53ec 100644
--- a/third_party/blink/renderer/core/html/html_area_element.cc
+++ b/third_party/blink/renderer/core/html/html_area_element.cc
@@ -219,7 +219,7 @@
     layout_image->AreaElementFocusChanged(this);
 }
 
-void HTMLAreaElement::UpdateFocusAppearanceWithOptions(
+void HTMLAreaElement::UpdateSelectionOnFocus(
     SelectionBehaviorOnFocus selection_behavior,
     const FocusOptions* options) {
   GetDocument().UpdateStyleAndLayoutTreeForNode(this);
@@ -227,8 +227,7 @@
     return;
 
   if (HTMLImageElement* image_element = ImageElement()) {
-    image_element->UpdateFocusAppearanceWithOptions(selection_behavior,
-                                                    options);
+    image_element->UpdateSelectionOnFocus(selection_behavior, options);
   }
 }
 
diff --git a/third_party/blink/renderer/core/html/html_area_element.h b/third_party/blink/renderer/core/html/html_area_element.h
index 863d489..b86d867 100644
--- a/third_party/blink/renderer/core/html/html_area_element.h
+++ b/third_party/blink/renderer/core/html/html_area_element.h
@@ -63,8 +63,8 @@
   bool IsKeyboardFocusable() const override;
   bool IsMouseFocusable() const override;
   bool IsFocusableStyle() const override;
-  void UpdateFocusAppearanceWithOptions(SelectionBehaviorOnFocus,
-                                        const FocusOptions*) override;
+  void UpdateSelectionOnFocus(SelectionBehaviorOnFocus,
+                              const FocusOptions*) override;
   void SetFocused(bool, mojom::blink::FocusType) override;
 
   enum Shape { kDefault, kPoly, kRect, kCircle };
diff --git a/third_party/blink/renderer/core/html/html_iframe_element_sandbox.h b/third_party/blink/renderer/core/html/html_iframe_element_sandbox.h
index 6d6115a..cd78d1d9 100644
--- a/third_party/blink/renderer/core/html/html_iframe_element_sandbox.h
+++ b/third_party/blink/renderer/core/html/html_iframe_element_sandbox.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_HTML_IFRAME_ELEMENT_SANDBOX_H_
 
 #include "third_party/blink/renderer/core/dom/dom_token_list.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/html/media/html_media_element_controls_list.h b/third_party/blink/renderer/core/html/media/html_media_element_controls_list.h
index 1d1b56f..4e96ae1 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element_controls_list.h
+++ b/third_party/blink/renderer/core/html/media/html_media_element_controls_list.h
@@ -7,7 +7,6 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/dom_token_list.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/html/media/media_source_attachment.h b/third_party/blink/renderer/core/html/media/media_source_attachment.h
index ff08809..88e356b3 100644
--- a/third_party/blink/renderer/core/html/media/media_source_attachment.h
+++ b/third_party/blink/renderer/core/html/media/media_source_attachment.h
@@ -10,8 +10,6 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/fileapi/url_registry.h"
 #include "third_party/blink/renderer/core/html/media/media_source_tracer.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
 
diff --git a/third_party/blink/renderer/core/html/parser/nesting_level_incrementer.h b/third_party/blink/renderer/core/html/parser/nesting_level_incrementer.h
index 082d088..f37cbb25 100644
--- a/third_party/blink/renderer/core/html/parser/nesting_level_incrementer.h
+++ b/third_party/blink/renderer/core/html/parser/nesting_level_incrementer.h
@@ -26,7 +26,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_PARSER_NESTING_LEVEL_INCREMENTER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_PARSER_NESTING_LEVEL_INCREMENTER_H_
 
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/html/time_ranges.h b/third_party/blink/renderer/core/html/time_ranges.h
index 9668344..f12da9a 100644
--- a/third_party/blink/renderer/core/html/time_ranges.h
+++ b/third_party/blink/renderer/core/html/time_ranges.h
@@ -29,7 +29,6 @@
 #include "third_party/blink/public/platform/web_time_range.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/html/track/inband_text_track.h b/third_party/blink/renderer/core/html/track/inband_text_track.h
index 1a284df..9ade4e97 100644
--- a/third_party/blink/renderer/core/html/track/inband_text_track.h
+++ b/third_party/blink/renderer/core/html/track/inband_text_track.h
@@ -28,7 +28,6 @@
 
 #include "third_party/blink/public/platform/web_inband_text_track_client.h"
 #include "third_party/blink/renderer/core/html/track/text_track.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/imagebitmap/image_bitmap.h b/third_party/blink/renderer/core/imagebitmap/image_bitmap.h
index 7e27f152..c4f13920 100644
--- a/third_party/blink/renderer/core/imagebitmap/image_bitmap.h
+++ b/third_party/blink/renderer/core/imagebitmap/image_bitmap.h
@@ -17,7 +17,6 @@
 #include "third_party/blink/renderer/platform/graphics/image_orientation.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 #include "ui/gfx/geometry/rect.h"
diff --git a/third_party/blink/renderer/core/inspector/inspector_highlight.h b/third_party/blink/renderer/core/inspector/inspector_highlight.h
index 0fed0dd6..ba4e3f94 100644
--- a/third_party/blink/renderer/core/inspector/inspector_highlight.h
+++ b/third_party/blink/renderer/core/inspector/inspector_highlight.h
@@ -11,7 +11,7 @@
 #include "third_party/blink/renderer/core/inspector/protocol/dom.h"
 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "ui/gfx/geometry/quad_f.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/inspector/inspector_issue_storage.h b/third_party/blink/renderer/core/inspector/inspector_issue_storage.h
index d950b6e..fb68c80 100644
--- a/third_party/blink/renderer/core/inspector/inspector_issue_storage.h
+++ b/third_party/blink/renderer/core/inspector/inspector_issue_storage.h
@@ -7,7 +7,6 @@
 
 #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/deque.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
diff --git a/third_party/blink/renderer/core/inspector/main_thread_debugger.h b/third_party/blink/renderer/core/inspector/main_thread_debugger.h
index dd48c12f..84662e3 100644
--- a/third_party/blink/renderer/core/inspector/main_thread_debugger.h
+++ b/third_party/blink/renderer/core/inspector/main_thread_debugger.h
@@ -36,7 +36,6 @@
 #include "third_party/blink/renderer/core/dom/document_lifecycle.h"
 #include "third_party/blink/renderer/core/inspector/thread_debugger.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "v8/include/v8-inspector.h"
 #include "v8/include/v8.h"
 
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_geometry.h b/third_party/blink/renderer/core/intersection_observer/intersection_geometry.h
index b69b0db..a9e4b0b8 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_geometry.h
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_geometry.h
@@ -9,7 +9,6 @@
 #include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
 #include "third_party/blink/renderer/platform/geometry/length.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/layout/counter_node.cc b/third_party/blink/renderer/core/layout/counter_node.cc
index 6991df81..cdeff2f 100644
--- a/third_party/blink/renderer/core/layout/counter_node.cc
+++ b/third_party/blink/renderer/core/layout/counter_node.cc
@@ -23,7 +23,6 @@
 
 #include "base/numerics/checked_math.h"
 #include "third_party/blink/renderer/core/layout/layout_counter.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 #if DCHECK_IS_ON()
 #include <stdio.h>
diff --git a/third_party/blink/renderer/core/layout/layout_text_fragment.h b/third_party/blink/renderer/core/layout/layout_text_fragment.h
index de0567f8..7a91958 100644
--- a/third_party/blink/renderer/core/layout/layout_text_fragment.h
+++ b/third_party/blink/renderer/core/layout/layout_text_fragment.h
@@ -25,7 +25,7 @@
 
 #include "third_party/blink/renderer/core/editing/editing_utilities.h"
 #include "third_party/blink/renderer/core/layout/layout_text.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/ng/custom/css_layout_worklet.h b/third_party/blink/renderer/core/layout/ng/custom/css_layout_worklet.h
index 39ed21a..096f9ae1 100644
--- a/third_party/blink/renderer/core/layout/ng/custom/css_layout_worklet.h
+++ b/third_party/blink/renderer/core/layout/ng/custom/css_layout_worklet.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_CUSTOM_CSS_LAYOUT_WORKLET_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_attributes_builder.h b/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_attributes_builder.h
index 3dfa16ba..0238f406 100644
--- a/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_attributes_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_attributes_builder.h
@@ -7,7 +7,8 @@
 
 #include "third_party/blink/renderer/core/layout/ng/svg/ng_svg_character_data.h"
 #include "third_party/blink/renderer/core/layout/ng/svg/svg_inline_node_data.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/svg/svg_text_chunk_builder.h b/third_party/blink/renderer/core/layout/svg/svg_text_chunk_builder.h
index 4c8c959..97e040e 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_text_chunk_builder.h
+++ b/third_party/blink/renderer/core/layout/svg/svg_text_chunk_builder.h
@@ -21,7 +21,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_SVG_SVG_TEXT_CHUNK_BUILDER_H_
 
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/loader/empty_clients.h b/third_party/blink/renderer/core/loader/empty_clients.h
index 42f30c87..094de359 100644
--- a/third_party/blink/renderer/core/loader/empty_clients.h
+++ b/third_party/blink/renderer/core/loader/empty_clients.h
@@ -51,8 +51,8 @@
 #include "third_party/blink/renderer/platform/cursors.h"
 #include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h"
 #include "third_party/blink/renderer/platform/graphics/touch_action.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "ui/base/cursor/cursor.h"
 #include "ui/display/screen_info.h"
diff --git a/third_party/blink/renderer/core/loader/mixed_content_checker.h b/third_party/blink/renderer/core/loader/mixed_content_checker.h
index fd667ccce..fa2e958 100644
--- a/third_party/blink/renderer/core/loader/mixed_content_checker.h
+++ b/third_party/blink/renderer/core/loader/mixed_content_checker.h
@@ -38,12 +38,12 @@
 #include "third_party/blink/public/mojom/loader/mixed_content.mojom-blink-forward.h"
 #include "third_party/blink/public/platform/web_url_request.h"
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/loader/fetch/https_state.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
 #include "third_party/blink/renderer/platform/loader/mixed_content.h"
 #include "third_party/blink/renderer/platform/weborigin/reporting_disposition.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/loader/ping_loader.h b/third_party/blink/renderer/core/loader/ping_loader.h
index 802a970..d987cfd 100644
--- a/third_party/blink/renderer/core/loader/ping_loader.h
+++ b/third_party/blink/renderer/core/loader/ping_loader.h
@@ -34,9 +34,8 @@
 
 #include "third_party/blink/public/platform/web_url_loader_client.h"
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/heap/self_keep_alive.h"
 #include "third_party/blink/renderer/platform/timer.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/page/drag_data.h b/third_party/blink/renderer/core/page/drag_data.h
index 5afff8c..6fcdcac7 100644
--- a/third_party/blink/renderer/core/page/drag_data.h
+++ b/third_party/blink/renderer/core/page/drag_data.h
@@ -29,7 +29,7 @@
 #include "third_party/blink/public/common/page/drag_operation.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/page/drag_actions.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
diff --git a/third_party/blink/renderer/core/page/scoped_page_pauser.cc b/third_party/blink/renderer/core/page/scoped_page_pauser.cc
index e8328166..501019a 100644
--- a/third_party/blink/renderer/core/page/scoped_page_pauser.cc
+++ b/third_party/blink/renderer/core/page/scoped_page_pauser.cc
@@ -24,7 +24,6 @@
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/loader/frame_loader.h"
 #include "third_party/blink/renderer/core/page/page.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
diff --git a/third_party/blink/renderer/core/page/touch_adjustment.h b/third_party/blink/renderer/core/page/touch_adjustment.h
index 92fd1766..2c4c91e 100644
--- a/third_party/blink/renderer/core/page/touch_adjustment.h
+++ b/third_party/blink/renderer/core/page/touch_adjustment.h
@@ -24,8 +24,6 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/geometry/layout_size.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/wtf/vector.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/rect.h"
diff --git a/third_party/blink/renderer/core/paint/background_image_geometry.h b/third_party/blink/renderer/core/paint/background_image_geometry.h
index f59ab75..5bdd517d 100644
--- a/third_party/blink/renderer/core/paint/background_image_geometry.h
+++ b/third_party/blink/renderer/core/paint/background_image_geometry.h
@@ -8,7 +8,6 @@
 #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
 #include "third_party/blink/renderer/core/paint/paint_phase.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_types.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/paint/filter_effect_builder.h b/third_party/blink/renderer/core/paint/filter_effect_builder.h
index 701613c6..f0cc157f 100644
--- a/third_party/blink/renderer/core/paint/filter_effect_builder.h
+++ b/third_party/blink/renderer/core/paint/filter_effect_builder.h
@@ -28,7 +28,6 @@
 
 #include "cc/paint/paint_flags.h"
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/skia/include/core/SkTileMode.h"
 #include "ui/gfx/geometry/rect_f.h"
diff --git a/third_party/blink/renderer/core/paint/frame_paint_timing.h b/third_party/blink/renderer/core/paint/frame_paint_timing.h
index 949af7d..436ccb7a 100644
--- a/third_party/blink/renderer/core/paint/frame_paint_timing.h
+++ b/third_party/blink/renderer/core/paint/frame_paint_timing.h
@@ -9,7 +9,7 @@
 #include "third_party/blink/renderer/core/paint/paint_timing.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/paint/scrollable_area_painter.h b/third_party/blink/renderer/core/paint/scrollable_area_painter.h
index ab6b809..4114e61 100644
--- a/third_party/blink/renderer/core/paint/scrollable_area_painter.h
+++ b/third_party/blink/renderer/core/paint/scrollable_area_painter.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SCROLLABLE_AREA_PAINTER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SCROLLABLE_AREA_PAINTER_H_
 
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace gfx {
 class Rect;
diff --git a/third_party/blink/renderer/core/permissions_policy/document_policy_fuzzer.cc b/third_party/blink/renderer/core/permissions_policy/document_policy_fuzzer.cc
index 8d051a7f..d7feb03 100644
--- a/third_party/blink/renderer/core/permissions_policy/document_policy_fuzzer.cc
+++ b/third_party/blink/renderer/core/permissions_policy/document_policy_fuzzer.cc
@@ -6,7 +6,6 @@
 
 #include <stddef.h>
 #include <stdint.h>
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
diff --git a/third_party/blink/renderer/core/permissions_policy/feature_policy_fuzzer.cc b/third_party/blink/renderer/core/permissions_policy/feature_policy_fuzzer.cc
index d7398db..ae027e16 100644
--- a/third_party/blink/renderer/core/permissions_policy/feature_policy_fuzzer.cc
+++ b/third_party/blink/renderer/core/permissions_policy/feature_policy_fuzzer.cc
@@ -7,7 +7,6 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <memory>
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
diff --git a/third_party/blink/renderer/core/permissions_policy/permissions_policy_attr_fuzzer.cc b/third_party/blink/renderer/core/permissions_policy/permissions_policy_attr_fuzzer.cc
index 74347b9..457d199 100644
--- a/third_party/blink/renderer/core/permissions_policy/permissions_policy_attr_fuzzer.cc
+++ b/third_party/blink/renderer/core/permissions_policy/permissions_policy_attr_fuzzer.cc
@@ -7,7 +7,6 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <memory>
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
diff --git a/third_party/blink/renderer/core/permissions_policy/permissions_policy_fuzzer.cc b/third_party/blink/renderer/core/permissions_policy/permissions_policy_fuzzer.cc
index 8bb1c9cc..243fec7 100644
--- a/third_party/blink/renderer/core/permissions_policy/permissions_policy_fuzzer.cc
+++ b/third_party/blink/renderer/core/permissions_policy/permissions_policy_fuzzer.cc
@@ -8,7 +8,6 @@
 #include <stdint.h>
 #include <memory>
 
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
diff --git a/third_party/blink/renderer/core/script/document_modulator_impl.h b/third_party/blink/renderer/core/script/document_modulator_impl.h
index a4846d2..23d3de2 100644
--- a/third_party/blink/renderer/core/script/document_modulator_impl.h
+++ b/third_party/blink/renderer/core/script/document_modulator_impl.h
@@ -8,7 +8,6 @@
 #include "third_party/blink/renderer/core/script/modulator_impl_base.h"
 
 #include "third_party/blink/public/mojom/v8_cache_options.mojom-blink.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.cc b/third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.cc
index 5dafe4d6..ce5db27 100644
--- a/third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.cc
@@ -10,7 +10,7 @@
 #include "third_party/blink/renderer/core/streams/readable_stream_default_controller.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/scoped_persistent.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/visitor.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/testing/callback_function_test.h b/third_party/blink/renderer/core/testing/callback_function_test.h
index 37cf10e1..9d26119 100644
--- a/third_party/blink/renderer/core/testing/callback_function_test.h
+++ b/third_party/blink/renderer/core/testing/callback_function_test.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_TESTING_CALLBACK_FUNCTION_TEST_H_
 
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/core/typed_arrays/flexible_array_buffer_view.h b/third_party/blink/renderer/core/typed_arrays/flexible_array_buffer_view.h
index 4927e44..b50e164 100644
--- a/third_party/blink/renderer/core/typed_arrays/flexible_array_buffer_view.h
+++ b/third_party/blink/renderer/core/typed_arrays/flexible_array_buffer_view.h
@@ -8,7 +8,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/modules/app_banner/app_banner_controller.cc b/third_party/blink/renderer/modules/app_banner/app_banner_controller.cc
index e55c5fb..32e35ca 100644
--- a/third_party/blink/renderer/modules/app_banner/app_banner_controller.cc
+++ b/third_party/blink/renderer/modules/app_banner/app_banner_controller.cc
@@ -6,6 +6,8 @@
 
 #include <memory>
 #include <utility>
+#include "base/feature_list.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/event_type_names.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
@@ -64,13 +66,17 @@
     const Vector<String>& platforms,
     BannerPromptRequestCallback callback) {
   // TODO(hajimehoshi): Add tests for the case the frame is detached.
+  // TODO(http://crbug/1289079): Test that prompt() behaves correctly when
+  // called in pagehide().
 
-  // With the current implementation, bfcache could cause prompt() event to be
-  // lost if called after being put into the cache, and the banner will not be
-  // hidden properly. We disable bfcache to avoid these issues.
-  GetSupplementable()->GetFrame()->GetFrameScheduler()->RegisterStickyFeature(
-      blink::SchedulingPolicy::Feature::kAppBanner,
-      {blink::SchedulingPolicy::DisableBackForwardCache()});
+  if (!base::FeatureList::IsEnabled(features::kBackForwardCacheAppBanner)) {
+    // With the current implementation, bfcache could cause prompt() event to be
+    // lost if called after being put into the cache, and the banner will not be
+    // hidden properly. We disable bfcache to avoid these issues.
+    GetSupplementable()->GetFrame()->GetFrameScheduler()->RegisterStickyFeature(
+        blink::SchedulingPolicy::Feature::kAppBanner,
+        {blink::SchedulingPolicy::DisableBackForwardCache()});
+  }
 
   mojom::AppBannerPromptReply reply =
       GetSupplementable()->DispatchEvent(*BeforeInstallPromptEvent::Create(
diff --git a/third_party/blink/renderer/modules/bluetooth/bluetooth_error.h b/third_party/blink/renderer/modules/bluetooth/bluetooth_error.h
index 6ce7c87..5fd714e 100644
--- a/third_party/blink/renderer/modules/bluetooth/bluetooth_error.h
+++ b/third_party/blink/renderer/modules/bluetooth/bluetooth_error.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_BLUETOOTH_BLUETOOTH_ERROR_H_
 
 #include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom-blink-forward.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
index bc6240d5..e567d7b 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
@@ -107,7 +107,7 @@
 
   ExecutionContext* execution_context = canvas->GetTopExecutionContext();
   if (auto* window = DynamicTo<LocalDOMWindow>(execution_context)) {
-    DCHECK(window->GetFrame());
+    DCHECK(window->GetFrame() && window->GetFrame()->GetSettings());
     if (window->GetFrame()->GetSettings()->GetDisableReadingFromCanvas())
       canvas->SetDisableReadingFromCanvasTrue();
     return;
diff --git a/third_party/blink/renderer/modules/event_modules_factory.h b/third_party/blink/renderer/modules/event_modules_factory.h
index 3e8af4492..36a7083 100644
--- a/third_party/blink/renderer/modules/event_modules_factory.h
+++ b/third_party/blink/renderer/modules/event_modules_factory.h
@@ -8,7 +8,6 @@
 #include <memory>
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/core/events/event_factory.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc b/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
index 6cca7a7..e982c38 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
+++ b/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
@@ -65,11 +65,11 @@
 }
 #endif
 
-// ------ CalculationExpressionOperatorNode ------
+// ------ CalculationExpressionOperationNode ------
 
 // static
 scoped_refptr<const CalculationExpressionNode>
-CalculationExpressionOperatorNode::CreateSimplified(Children&& children,
+CalculationExpressionOperationNode::CreateSimplified(Children&& children,
                                                     CalculationOperator op) {
   switch (op) {
     case CalculationOperator::kAdd:
@@ -77,7 +77,7 @@
       DCHECK_EQ(children.size(), 2u);
       if (!children[0]->IsPixelsAndPercent() ||
           !children[1]->IsPixelsAndPercent()) {
-        return base::MakeRefCounted<CalculationExpressionOperatorNode>(
+        return base::MakeRefCounted<CalculationExpressionOperationNode>(
             Children({std::move(children[0]), std::move(children[1])}), op);
       }
       const auto& left_pixels_and_percent =
@@ -100,7 +100,7 @@
       auto& maybe_pixels_and_percent_node =
           children[0]->IsNumber() ? children[1] : children[0];
       if (!maybe_pixels_and_percent_node->IsPixelsAndPercent()) {
-        return base::MakeRefCounted<CalculationExpressionOperatorNode>(
+        return base::MakeRefCounted<CalculationExpressionOperationNode>(
             Children({std::move(children[0]), std::move(children[1])}), op);
       }
       auto& number_node = children[0]->IsNumber() ? children[0] : children[1];
@@ -141,7 +141,7 @@
         return base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
             PixelsAndPercent(simplified_px, 0));
       }
-      return base::MakeRefCounted<CalculationExpressionOperatorNode>(
+      return base::MakeRefCounted<CalculationExpressionOperationNode>(
           std::move(children), op);
     }
     default:
@@ -150,7 +150,7 @@
   }
 }
 
-float CalculationExpressionOperatorNode::Evaluate(float max_value) const {
+float CalculationExpressionOperationNode::Evaluate(float max_value) const {
   switch (operator_) {
     case CalculationOperator::kAdd: {
       DCHECK_EQ(children_.size(), 2u);
@@ -192,17 +192,17 @@
   return std::numeric_limits<float>::quiet_NaN();
 }
 
-bool CalculationExpressionOperatorNode::operator==(
+bool CalculationExpressionOperationNode::operator==(
     const CalculationExpressionNode& other) const {
-  if (!other.IsOperator())
+  if (!other.IsOperation())
     return false;
-  const auto& other_operation = To<CalculationExpressionOperatorNode>(other);
+  const auto& other_operation = To<CalculationExpressionOperationNode>(other);
   return operator_ == other_operation.GetOperator() &&
          children_ == other_operation.GetChildren();
 }
 
 scoped_refptr<const CalculationExpressionNode>
-CalculationExpressionOperatorNode::Zoom(double factor) const {
+CalculationExpressionOperationNode::Zoom(double factor) const {
   switch (operator_) {
     case CalculationOperator::kAdd:
     case CalculationOperator::kSubtract:
@@ -235,7 +235,7 @@
 
 #if DCHECK_IS_ON()
 CalculationExpressionNode::ResultType
-CalculationExpressionOperatorNode::ResolvedResultType() const {
+CalculationExpressionOperationNode::ResolvedResultType() const {
   switch (operator_) {
     case CalculationOperator::kAdd:
     case CalculationOperator::kSubtract: {
diff --git a/third_party/blink/renderer/platform/geometry/calculation_expression_node.h b/third_party/blink/renderer/platform/geometry/calculation_expression_node.h
index a772a21..79b78be 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_expression_node.h
+++ b/third_party/blink/renderer/platform/geometry/calculation_expression_node.h
@@ -35,7 +35,7 @@
 
   virtual bool IsNumber() const { return false; }
   virtual bool IsPixelsAndPercent() const { return false; }
-  virtual bool IsOperator() const { return false; }
+  virtual bool IsOperation() const { return false; }
 
   virtual scoped_refptr<const CalculationExpressionNode> Zoom(
       double factor) const = 0;
@@ -123,7 +123,7 @@
   }
 };
 
-class PLATFORM_EXPORT CalculationExpressionOperatorNode final
+class PLATFORM_EXPORT CalculationExpressionOperationNode final
     : public CalculationExpressionNode {
  public:
   using Children = Vector<scoped_refptr<const CalculationExpressionNode>>;
@@ -132,7 +132,7 @@
       Children&& children,
       CalculationOperator op);
 
-  CalculationExpressionOperatorNode(Children&& children, CalculationOperator op)
+  CalculationExpressionOperationNode(Children&& children, CalculationOperator op)
       : children_(std::move(children)), operator_(op) {
 #if DCHECK_IS_ON()
     result_type_ = ResolvedResultType();
@@ -148,8 +148,8 @@
   bool operator==(const CalculationExpressionNode& other) const final;
   scoped_refptr<const CalculationExpressionNode> Zoom(
       double factor) const final;
-  bool IsOperator() const final { return true; }
-  ~CalculationExpressionOperatorNode() final = default;
+  bool IsOperation() const final { return true; }
+  ~CalculationExpressionOperationNode() final = default;
 
 #if DCHECK_IS_ON()
   ResultType ResolvedResultType() const final;
@@ -161,9 +161,9 @@
 };
 
 template <>
-struct DowncastTraits<CalculationExpressionOperatorNode> {
+struct DowncastTraits<CalculationExpressionOperationNode> {
   static bool AllowFrom(const CalculationExpressionNode& node) {
-    return node.IsOperator();
+    return node.IsOperation();
   }
 };
 
diff --git a/third_party/blink/renderer/platform/geometry/calculation_value.cc b/third_party/blink/renderer/platform/geometry/calculation_value.cc
index d2ef8080..465d033b 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_value.cc
+++ b/third_party/blink/renderer/platform/geometry/calculation_value.cc
@@ -80,18 +80,18 @@
     return Create(PixelsAndPercent(pixels, percent), range);
   }
 
-  auto blended_from = CalculationExpressionOperatorNode::CreateSimplified(
-      CalculationExpressionOperatorNode::Children(
+  auto blended_from = CalculationExpressionOperationNode::CreateSimplified(
+      CalculationExpressionOperationNode::Children(
           {from.GetOrCreateExpression(),
            base::MakeRefCounted<CalculationExpressionNumberNode>(1.0 -
                                                                  progress)}),
       CalculationOperator::kMultiply);
-  auto blended_to = CalculationExpressionOperatorNode::CreateSimplified(
-      CalculationExpressionOperatorNode::Children(
+  auto blended_to = CalculationExpressionOperationNode::CreateSimplified(
+      CalculationExpressionOperationNode::Children(
           {GetOrCreateExpression(),
            base::MakeRefCounted<CalculationExpressionNumberNode>(progress)}),
       CalculationOperator::kMultiply);
-  auto result_expression = CalculationExpressionOperatorNode::CreateSimplified(
+  auto result_expression = CalculationExpressionOperationNode::CreateSimplified(
       {std::move(blended_from), std::move(blended_to)},
       CalculationOperator::kAdd);
   return CreateSimplified(result_expression, range);
@@ -106,8 +106,8 @@
   auto hundred_percent =
       base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
           PixelsAndPercent(0, 100));
-  auto result_expression = CalculationExpressionOperatorNode::CreateSimplified(
-      CalculationExpressionOperatorNode::Children(
+  auto result_expression = CalculationExpressionOperationNode::CreateSimplified(
+      CalculationExpressionOperationNode::Children(
           {std::move(hundred_percent), GetOrCreateExpression()}),
       CalculationOperator::kSubtract);
   return CreateSimplified(std::move(result_expression),
diff --git a/third_party/blink/renderer/platform/geometry/length_test.cc b/third_party/blink/renderer/platform/geometry/length_test.cc
index 3b6c14b..75ce870 100644
--- a/third_party/blink/renderer/platform/geometry/length_test.cc
+++ b/third_party/blink/renderer/platform/geometry/length_test.cc
@@ -30,34 +30,34 @@
   }
 
   Pointer Add(Pointer lhs, Pointer rhs) {
-    return base::MakeRefCounted<CalculationExpressionOperatorNode>(
-        CalculationExpressionOperatorNode::Children(
+    return base::MakeRefCounted<CalculationExpressionOperationNode>(
+        CalculationExpressionOperationNode::Children(
             {std::move(lhs), std::move(rhs)}),
         CalculationOperator::kAdd);
   }
 
   Pointer Subtract(Pointer lhs, Pointer rhs) {
-    return base::MakeRefCounted<CalculationExpressionOperatorNode>(
-        CalculationExpressionOperatorNode::Children(
+    return base::MakeRefCounted<CalculationExpressionOperationNode>(
+        CalculationExpressionOperationNode::Children(
             {std::move(lhs), std::move(rhs)}),
         CalculationOperator::kSubtract);
   }
 
   Pointer Multiply(Pointer node, float factor) {
-    return base::MakeRefCounted<CalculationExpressionOperatorNode>(
-        CalculationExpressionOperatorNode::Children(
+    return base::MakeRefCounted<CalculationExpressionOperationNode>(
+        CalculationExpressionOperationNode::Children(
             {std::move(node),
              base::MakeRefCounted<CalculationExpressionNumberNode>(factor)}),
         CalculationOperator::kMultiply);
   }
 
   Pointer Min(Vector<Pointer>&& operands) {
-    return base::MakeRefCounted<CalculationExpressionOperatorNode>(
+    return base::MakeRefCounted<CalculationExpressionOperationNode>(
         std::move(operands), CalculationOperator::kMin);
   }
 
   Pointer Max(Vector<Pointer>&& operands) {
-    return base::MakeRefCounted<CalculationExpressionOperatorNode>(
+    return base::MakeRefCounted<CalculationExpressionOperationNode>(
         std::move(operands), CalculationOperator::kMax);
   }
 
diff --git a/third_party/blink/renderer/platform/heap/collection_support/heap_vector.h b/third_party/blink/renderer/platform/heap/collection_support/heap_vector.h
index b1cbb980..8cc4504 100644
--- a/third_party/blink/renderer/platform/heap/collection_support/heap_vector.h
+++ b/third_party/blink/renderer/platform/heap/collection_support/heap_vector.h
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/platform/heap/forward.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/heap_allocator_impl.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/wtf/type_traits.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 7537979..94f2b1fb 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -5298,7 +5298,7 @@
 crbug.com/938884 http/tests/devtools/elements/styles-3/styles-add-blank-property.js [ Pass Skip Timeout ]
 
 # Sheriff 2019-04-30
-crbug.com/948785 [ Debug ] fast/events/pointerevents/pointer-event-consumed-touchstart-in-slop-region.html [ Failure Pass ]
+crbug.com/948785 fast/events/pointerevents/pointer-event-consumed-touchstart-in-slop-region.html [ Skip ]
 
 # Sheriff 2019-05-01
 crbug.com/958347 [ Linux ] external/wpt/editing/run/removeformat.html [ Crash Pass ]
diff --git a/third_party/blink/web_tests/document-transition/multiple-shared-elements-animate-crash.html b/third_party/blink/web_tests/document-transition/multiple-shared-elements-animate-crash.html
new file mode 100644
index 0000000..b9ff763
--- /dev/null
+++ b/third_party/blink/web_tests/document-transition/multiple-shared-elements-animate-crash.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Shared transitions of different elements and shapes</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+
+<style>
+#container {
+  width: max-content;
+  position: relative;
+}
+
+.left {
+  left: 50px;
+}
+.right {
+  left: 550px;
+}
+
+div {
+  margin: 10px;
+  contain: paint;
+}
+
+.square {
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+.rounded {
+  width: 100px;
+  height: 100px;
+  background: green;
+  border-radius: 20%;
+}
+</style>
+
+<div id=container class=left>
+  <div id=e1 class=square></div>
+  <div id=e2 class=rounded></div>
+</div>
+
+<script>
+async_test((t) => {
+  t.step(() => {
+    requestAnimationFrame(() => requestAnimationFrame(async () => {
+      await document.documentTransition.prepare({
+        rootTransition: "none",
+        sharedElements: [e1, e2]
+      });
+
+      container.classList.remove("left");
+      container.classList.add("right");
+
+      await document.documentTransition.start({
+        sharedElements: [e1, e2]
+      });
+
+      requestAnimationFrame(() => {
+        setTimeout(() => t.done(), 100);
+      });
+    }));
+  });
+}, "The test passes if it does not crash");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html
index 8d9e2a41..e313647 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html
@@ -111,6 +111,28 @@
     desc: 'CSSTransformValue containing CSSMathValues'
   },
   {
+    value: new CSSTransformValue([
+      new CSSRotate(
+        new CSSMathInvert(
+          new CSSUnitValue(0, 'number')),
+        0, 0, CSS.deg(0))
+      ]),
+    cssText:'rotate3d(calc(1 / 0), 0, 0, 0deg)',
+    desc: 'CSSMathInvert with 0 parameter'
+  },
+  {
+    value: new CSSTransformValue([
+      new CSSRotate(
+        0, 0, 0,
+          new CSSMathProduct(CSS.deg(1),
+            new CSSMathInvert(
+              new CSSUnitValue(0, 'number')))
+        )
+      ]),
+    cssText:'rotate3d(0, 0, 0, calc(1deg * (1 / 0)))',
+    desc: 'CSSMathInvert with 0 parameter and nested'
+  },
+  {
     value: new CSSMatrixComponent(new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6])),
     cssText: 'matrix(1, 2, 3, 4, 5, 6)',
     desc: 'CSSMatrixComponent with 6 elements'
diff --git a/third_party/blink/web_tests/external/wpt/lint.ignore b/third_party/blink/web_tests/external/wpt/lint.ignore
index b997374..1a5fdba 100644
--- a/third_party/blink/web_tests/external/wpt/lint.ignore
+++ b/third_party/blink/web_tests/external/wpt/lint.ignore
@@ -720,6 +720,7 @@
 MISSING DEPENDENCY: resources/chromium/web-bluetooth-test.js
 MISSING DEPENDENCY: resources/chromium/webusb-test.js
 MISSING DEPENDENCY: resources/chromium/fake-serial.js
+MISSING DEPENDENCY: resources/chromium/fake-hid.js
 MISSING DEPENDENCY: resources/chromium/generic_sensor_mocks.js
 MISSING DEPENDENCY: resources/chromium/mock-barcodedetection.js
 MISSING DEPENDENCY: resources/chromium/mock-direct-sockets.js
diff --git a/third_party/blink/web_tests/wpt_internal/hid/resources/hid-test-utils.js b/third_party/blink/web_tests/external/wpt/resources/chromium/fake-hid.js
similarity index 82%
rename from third_party/blink/web_tests/wpt_internal/hid/resources/hid-test-utils.js
rename to third_party/blink/web_tests/external/wpt/resources/chromium/fake-hid.js
index 140be8b..89318b5 100644
--- a/third_party/blink/web_tests/wpt_internal/hid/resources/hid-test-utils.js
+++ b/third_party/blink/web_tests/external/wpt/resources/chromium/fake-hid.js
@@ -1,28 +1,6 @@
-import '/resources/testdriver.js';
-import '/resources/testdriver-vendor.js';
-import '/resources/testharness.js';
-import '/resources/testharnessreport.js';
-
-import {HidConnection, HidConnectionReceiver, HidDeviceInfo, HidManagerClientRemote} from '/gen/services/device/public/mojom/hid.mojom.m.js';
+import {HidConnectionReceiver, HidDeviceInfo} from '/gen/services/device/public/mojom/hid.mojom.m.js';
 import {HidService, HidServiceReceiver} from '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js';
 
-// Compare two DataViews byte-by-byte.
-export function compareDataViews(actual, expected) {
-  assert_true(actual instanceof DataView, 'actual is DataView');
-  assert_true(expected instanceof DataView, 'expected is DataView');
-  assert_equals(actual.byteLength, expected.byteLength, 'lengths equal');
-  for (let i = 0; i < expected.byteLength; ++i) {
-    assert_equals(actual.getUint8(i), expected.getUint8(i),
-                  `Mismatch at byte ${i}.`);
-  }
-}
-
-// Returns a Promise that resolves once |device| receives an input report.
-export function oninputreport(device) {
-  assert_true(device instanceof HIDDevice);
-  return new Promise(resolve => { device.oninputreport = resolve; });
-}
-
 // Fake implementation of device.mojom.HidConnection. HidConnection represents
 // an open connection to a HID device and can be used to send and receive
 // reports.
@@ -98,8 +76,9 @@
     let actual = new Uint8Array(buffer);
     compareDataViews(
         new DataView(actual.buffer, actual.byteOffset),
-        new DataView(expectedWrite.params.data.buffer,
-                     expectedWrite.params.data.byteOffset));
+        new DataView(
+            expectedWrite.params.data.buffer,
+            expectedWrite.params.data.byteOffset));
     return expectedWrite.result;
   }
 
@@ -123,8 +102,9 @@
     let actual = new Uint8Array(buffer);
     compareDataViews(
         new DataView(actual.buffer, actual.byteOffset),
-        new DataView(expectedSendFeatureReport.params.data.buffer,
-                     expectedSendFeatureReport.params.data.byteOffset));
+        new DataView(
+            expectedSendFeatureReport.params.data.buffer,
+            expectedSendFeatureReport.params.data.byteOffset));
     return expectedSendFeatureReport.result;
   }
 }
@@ -314,32 +294,4 @@
   }
 }
 
-let fakeHidService = new FakeHidService();
-
-export function hid_test(func, name, properties) {
-  promise_test(async (test) => {
-    fakeHidService.start();
-    try {
-      await func(test, fakeHidService);
-    } finally {
-      fakeHidService.stop();
-      fakeHidService.reset();
-    }
-  }, name, properties);
-}
-
-export function trustedClick() {
-  return new Promise(resolve => {
-    let button = document.createElement('button');
-    button.textContent = 'click to continue test';
-    button.style.display = 'block';
-    button.style.fontSize = '20px';
-    button.style.padding = '10px';
-    button.onclick = () => {
-      document.body.removeChild(button);
-      resolve();
-    };
-    document.body.appendChild(button);
-    test_driver.click(button);
-  });
-}
+export const fakeHidService = new FakeHidService();
diff --git a/third_party/blink/web_tests/external/wpt/webhid/resources/automation.js b/third_party/blink/web_tests/external/wpt/webhid/resources/automation.js
new file mode 100644
index 0000000..32d7174
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webhid/resources/automation.js
@@ -0,0 +1,41 @@
+'use strict';
+
+let fakeHidService = undefined;
+
+function hid_test(func, name, properties) {
+  promise_test(async (test) => {
+    assert_implements(navigator.hid, 'missing navigator.hid');
+    if (fakeHidService === undefined) {
+      // Try loading a polyfill for the fake hid service.
+      if (isChromiumBased) {
+        const fakes = await import('/resources/chromium/fake-hid.js');
+        fakeHidService = fakes.fakeHidService;
+      }
+    }
+    assert_implements(fakeHidService, 'missing fakeHidService after initialization');
+
+    fakeHidService.start();
+    try {
+      await func(test, fakeHidService);
+    } finally {
+      fakeHidService.stop();
+      fakeHidService.reset();
+    }
+  }, name, properties);
+}
+
+function trustedClick() {
+  return new Promise(resolve => {
+    let button = document.createElement('button');
+    button.textContent = 'click to continue test';
+    button.style.display = 'block';
+    button.style.fontSize = '20px';
+    button.style.padding = '10px';
+    button.onclick = () => {
+      document.body.removeChild(button);
+      resolve();
+    };
+    document.body.appendChild(button);
+    test_driver.click(button);
+  });
+}
diff --git a/third_party/blink/web_tests/external/wpt/webhid/resources/common.js b/third_party/blink/web_tests/external/wpt/webhid/resources/common.js
new file mode 100644
index 0000000..1d0904a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webhid/resources/common.js
@@ -0,0 +1,18 @@
+// Compare two DataViews byte-by-byte.
+function compareDataViews(actual, expected) {
+  assert_true(actual instanceof DataView, 'actual is DataView');
+  assert_true(expected instanceof DataView, 'expected is DataView');
+  assert_equals(actual.byteLength, expected.byteLength, 'lengths equal');
+  for (let i = 0; i < expected.byteLength; ++i) {
+    assert_equals(
+        actual.getUint8(i), expected.getUint8(i), `Mismatch at byte ${i}.`);
+  }
+}
+
+// Returns a Promise that resolves once |device| receives an input report.
+function oninputreport(device) {
+  assert_true(device instanceof HIDDevice);
+  return new Promise(resolve => {
+    device.oninputreport = resolve;
+  });
+}
diff --git a/third_party/blink/web_tests/wpt_internal/hid/README.md b/third_party/blink/web_tests/wpt_internal/hid/README.md
index 147d8736..807a91e 100644
--- a/third_party/blink/web_tests/wpt_internal/hid/README.md
+++ b/third_party/blink/web_tests/wpt_internal/hid/README.md
@@ -2,7 +2,7 @@
 
 Automated testing for the [WebHID API] uses [MojoJS] to override the
 implementation of the [HidService] Mojo interface with a testing version in
-`resources/hid-test-utils.js`.
+`/resources/chromium/fake-hid.js`.
 
 Most of these tests can be upstreamed to the Web Platform Tests repository by
 creating an abstraction between the test cases and this Chromium-specific test
diff --git a/third_party/blink/web_tests/wpt_internal/hid/hidDevice_deviceInfo.https.html b/third_party/blink/web_tests/wpt_internal/hid/hidDevice_deviceInfo.https.window.js
similarity index 89%
rename from third_party/blink/web_tests/wpt_internal/hid/hidDevice_deviceInfo.https.html
rename to third_party/blink/web_tests/wpt_internal/hid/hidDevice_deviceInfo.https.window.js
index 75daa13..d3ee5957 100644
--- a/third_party/blink/web_tests/wpt_internal/hid/hidDevice_deviceInfo.https.html
+++ b/third_party/blink/web_tests/wpt_internal/hid/hidDevice_deviceInfo.https.window.js
@@ -1,11 +1,9 @@
-<!DOCTYPE html>
-<body>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script type="module">
-
-import {hid_test, trustedClick} from './resources/hid-test-utils.js';
-import {GENERIC_DESKTOP_GAME_PAD, HID_COLLECTION_TYPE_APPLICATION, HidBusType, HidCollectionInfo, HidReportDescription, HidReportItem, HidUsageAndPage, PAGE_BUTTON, PAGE_GENERIC_DESKTOP} from '/gen/services/device/public/mojom/hid.mojom.m.js';
+// META: script=/resources/test-only-api.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/webhid/resources/common.js
+// META: script=/webhid/resources/automation.js
+'use strict';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
@@ -19,7 +17,18 @@
 // Constructs and returns a device.mojom.HidDeviceInfo representing a
 // gamepad-like device with one input report. The input report has a single
 // 8-bit field with a button usage.
-function createDeviceWithInputReport(fake) {
+async function createDeviceWithInputReport(fake) {
+  const {
+    GENERIC_DESKTOP_GAME_PAD,
+    HID_COLLECTION_TYPE_APPLICATION,
+    HidBusType,
+    HidCollectionInfo,
+    HidReportDescription,
+    HidReportItem,
+    HidUsageAndPage,
+    PAGE_BUTTON,
+    PAGE_GENERIC_DESKTOP
+  } = await import('/gen/services/device/public/mojom/hid.mojom.m.js');
   const nullUsage = new HidUsageAndPage();
   const buttonUsage = new HidUsageAndPage();
   buttonUsage.usagePage = PAGE_BUTTON;
@@ -85,7 +94,18 @@
 }
 
 hid_test(async (t, fake) => {
-  const deviceInfo = createDeviceWithInputReport(fake);
+  const {
+    GENERIC_DESKTOP_GAME_PAD,
+    HID_COLLECTION_TYPE_APPLICATION,
+    HidBusType,
+    HidCollectionInfo,
+    HidReportDescription,
+    HidReportItem,
+    HidUsageAndPage,
+    PAGE_BUTTON,
+    PAGE_GENERIC_DESKTOP
+  } = await import('/gen/services/device/public/mojom/hid.mojom.m.js');
+  const deviceInfo = await createDeviceWithInputReport(fake);
   const guid = fake.addDevice(deviceInfo);
   fake.setSelectedDevice(guid);
 
@@ -154,7 +174,7 @@
 }, 'HIDDevice preserves device info');
 
 hid_test(async (t, fake) => {
-  const deviceInfo = createDeviceWithInputReport(fake);
+  const deviceInfo = await createDeviceWithInputReport(fake);
 
   // Set the units to nano-Newtons. 10^-9 kg m/s^2 = 10^-4 g cm/s^2
   // |unit_exponent| is set to 0x0C which encodes the factor 10^-4.
@@ -201,7 +221,10 @@
 
 
 hid_test(async (t, fake) => {
-  const deviceInfo = createDeviceWithInputReport(fake);
+  const {
+    PAGE_BUTTON,
+  } = await import('/gen/services/device/public/mojom/hid.mojom.m.js');
+  const deviceInfo = await createDeviceWithInputReport(fake);
 
   deviceInfo.collections[0].inputReports[0].items[0].isRange = true;
   deviceInfo.collections[0].inputReports[0].items[0].usages = [];
@@ -235,6 +258,3 @@
   assert_equals(i.usageMaximum, 0x00090008, 'reportItem.usageMaximum');
 
 }, 'HIDDevice usage range item presents usageMinimum and usageMaximum');
-
-</script>
-</body>
diff --git a/third_party/blink/web_tests/wpt_internal/hid/hidDevice_forget.https.html b/third_party/blink/web_tests/wpt_internal/hid/hidDevice_forget.https.window.js
similarity index 95%
rename from third_party/blink/web_tests/wpt_internal/hid/hidDevice_forget.https.html
rename to third_party/blink/web_tests/wpt_internal/hid/hidDevice_forget.https.window.js
index 4c8c74a..befc030 100644
--- a/third_party/blink/web_tests/wpt_internal/hid/hidDevice_forget.https.html
+++ b/third_party/blink/web_tests/wpt_internal/hid/hidDevice_forget.https.window.js
@@ -1,10 +1,9 @@
-<!DOCTYPE html>
-<body>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script type="module">
-
-import {hid_test, trustedClick} from './resources/hid-test-utils.js';
+// META: script=/resources/test-only-api.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/webhid/resources/common.js
+// META: script=/webhid/resources/automation.js
+'use strict';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
@@ -190,6 +189,3 @@
   devices = await navigator.hid.getDevices();
   assert_equals(devices.length, 0);
 }, 'Permission is not remembered even after reconnection');
-
-</script>
-</body>
diff --git a/third_party/blink/web_tests/wpt_internal/hid/hidDevice_openAndClose.https.html b/third_party/blink/web_tests/wpt_internal/hid/hidDevice_openAndClose.https.window.js
similarity index 92%
rename from third_party/blink/web_tests/wpt_internal/hid/hidDevice_openAndClose.https.html
rename to third_party/blink/web_tests/wpt_internal/hid/hidDevice_openAndClose.https.window.js
index a24d9cf..fd5147ea 100644
--- a/third_party/blink/web_tests/wpt_internal/hid/hidDevice_openAndClose.https.html
+++ b/third_party/blink/web_tests/wpt_internal/hid/hidDevice_openAndClose.https.window.js
@@ -1,10 +1,9 @@
-<!DOCTYPE html>
-<body>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script type="module">
-
-import {hid_test, trustedClick} from './resources/hid-test-utils.js';
+// META: script=/resources/test-only-api.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/webhid/resources/common.js
+// META: script=/webhid/resources/automation.js
+'use strict';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
@@ -104,6 +103,3 @@
   await promise_rejects_dom(t, 'InvalidStateError', device.open());
   await firstRequest;
 }, 'Opening a device twice simultaneously is an error');
-
-</script>
-</body>
diff --git a/third_party/blink/web_tests/wpt_internal/hid/hidDevice_reports.https.html b/third_party/blink/web_tests/wpt_internal/hid/hidDevice_reports.https.window.js
similarity index 95%
rename from third_party/blink/web_tests/wpt_internal/hid/hidDevice_reports.https.html
rename to third_party/blink/web_tests/wpt_internal/hid/hidDevice_reports.https.window.js
index 21614775..d0ddef6 100644
--- a/third_party/blink/web_tests/wpt_internal/hid/hidDevice_reports.https.html
+++ b/third_party/blink/web_tests/wpt_internal/hid/hidDevice_reports.https.window.js
@@ -1,10 +1,9 @@
-<!DOCTYPE html>
-<body>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script type="module">
-
-import {compareDataViews, hid_test, oninputreport, trustedClick} from './resources/hid-test-utils.js';
+// META: script=/resources/test-only-api.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/webhid/resources/common.js
+// META: script=/webhid/resources/automation.js
+'use strict';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
@@ -164,6 +163,3 @@
   await device.sendFeatureReport(kReportId, detachedReport);
   fakeConnection.assertExpectationsMet();
 }, 'sendFeatureReport treates a detached buffer as an empty report');
-
-</script>
-</body>
diff --git a/third_party/blink/web_tests/wpt_internal/hid/hid_connectionEvents.https.html b/third_party/blink/web_tests/wpt_internal/hid/hid_connectionEvents.https.window.js
similarity index 92%
rename from third_party/blink/web_tests/wpt_internal/hid/hid_connectionEvents.https.html
rename to third_party/blink/web_tests/wpt_internal/hid/hid_connectionEvents.https.window.js
index 3861b8c..d161b3b 100644
--- a/third_party/blink/web_tests/wpt_internal/hid/hid_connectionEvents.https.html
+++ b/third_party/blink/web_tests/wpt_internal/hid/hid_connectionEvents.https.window.js
@@ -1,10 +1,7 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script type="module">
-
-import {hid_test} from './resources/hid-test-utils.js';
-import {HidCollectionInfo, HidUsageAndPage} from '/gen/services/device/public/mojom/hid.mojom.m.js';
+// META: script=/resources/test-only-api.js
+// META: script=/webhid/resources/common.js
+// META: script=/webhid/resources/automation.js
+'use strict';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
@@ -37,6 +34,8 @@
 }, 'HID dispatches connection and disconnection events');
 
 hid_test(async (t, fake) => {
+  const {HidCollectionInfo, HidUsageAndPage} = await import(
+    '/gen/services/device/public/mojom/hid.mojom.m.js');
   const watcher = new EventWatcher(t, navigator.hid, ['connect', 'disconnect']);
 
   // Wait for getDevices() to resolve in order to ensure that the Mojo client
@@ -107,5 +106,3 @@
   devices = await navigator.hid.getDevices();
   assert_equals(devices.length, 1);
 }, 'HID dispatches connect for DeviceChanged without DeviceAdded');
-
-</script>
diff --git a/third_party/blink/web_tests/wpt_internal/hid/hid_getDevices.https.html b/third_party/blink/web_tests/wpt_internal/hid/hid_getDevices.https.window.js
similarity index 82%
rename from third_party/blink/web_tests/wpt_internal/hid/hid_getDevices.https.html
rename to third_party/blink/web_tests/wpt_internal/hid/hid_getDevices.https.window.js
index 5d74cdd..9220f46b 100644
--- a/third_party/blink/web_tests/wpt_internal/hid/hid_getDevices.https.html
+++ b/third_party/blink/web_tests/wpt_internal/hid/hid_getDevices.https.window.js
@@ -1,16 +1,15 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script type="module">
-
-import {hid_test} from './resources/hid-test-utils.js';
-
-import {HidService} from '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js';
+// META: script=/resources/test-only-api.js
+// META: script=/webhid/resources/common.js
+// META: script=/webhid/resources/automation.js
+'use strict';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
 
 promise_test(async () => {
+  const {HidService} = await import(
+    '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js');
+
   let interceptor = new MojoInterfaceInterceptor(HidService.$interfaceName);
   interceptor.oninterfacerequest = e => e.handle.close();
   interceptor.start();
@@ -42,5 +41,3 @@
   assert_true(devicesSecond[0] instanceof HIDDevice);
   assert_true(devicesFirst[0] === devicesSecond[0]);
 }, 'getDevices() returns the same device objects every time');
-
-</script>
diff --git a/third_party/blink/web_tests/wpt_internal/hid/hid_requestDevice.https.html b/third_party/blink/web_tests/wpt_internal/hid/hid_requestDevice.https.window.js
similarity index 91%
rename from third_party/blink/web_tests/wpt_internal/hid/hid_requestDevice.https.html
rename to third_party/blink/web_tests/wpt_internal/hid/hid_requestDevice.https.window.js
index e923b7bc..ed043442 100644
--- a/third_party/blink/web_tests/wpt_internal/hid/hid_requestDevice.https.html
+++ b/third_party/blink/web_tests/wpt_internal/hid/hid_requestDevice.https.window.js
@@ -1,12 +1,9 @@
-<!DOCTYPE html>
-<body>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script type="module">
-
-import {hid_test, trustedClick} from './resources/hid-test-utils.js';
-
-import {HidService} from '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js';
+// META: script=/resources/test-only-api.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/webhid/resources/common.js
+// META: script=/webhid/resources/automation.js
+'use strict';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
@@ -23,6 +20,8 @@
 }, 'requestDevice() rejects with an empty filter');
 
 promise_test(async (t) => {
+  const {HidService} = await import(
+    '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js');
   let interceptor = new MojoInterfaceInterceptor(HidService.$interfaceName);
   interceptor.oninterfacerequest = e => e.handle.close();
   interceptor.start();
@@ -111,6 +110,3 @@
   assert_equals(1, devices.length);
   assert_true(devices[0] instanceof HIDDevice);
 }, 'requestDevice() does not merge devices with empty physical device IDs');
-
-</script>
-</body>
diff --git a/third_party/closure_compiler/externs/file_manager_private.js b/third_party/closure_compiler/externs/file_manager_private.js
index 6fb1c799..0cd6bb9d 100644
--- a/third_party/closure_compiler/externs/file_manager_private.js
+++ b/third_party/closure_compiler/externs/file_manager_private.js
@@ -1375,6 +1375,13 @@
  */
 chrome.fileManagerPrivate.cancelIOTask = function (taskId) { };
 
+/**
+ * Returns color via `callback` for Files app foreground window frame.
+ * @param {function(string): void} callback |color| String containing the color
+ *     of the title bar.
+ */
+chrome.fileManagerPrivate.getFrameColor = function(callback) {};
+
 /** @type {!ChromeEvent} */
 chrome.fileManagerPrivate.onMountCompleted;
 
diff --git a/third_party/protobuf/BUILD.gn b/third_party/protobuf/BUILD.gn
index 987723e..c7727fc 100644
--- a/third_party/protobuf/BUILD.gn
+++ b/third_party/protobuf/BUILD.gn
@@ -265,6 +265,9 @@
     "//third_party/dawn/third_party/tint/fuzzers/tint_ast_fuzzer:tint_ast_fuzzer",
     "//third_party/dawn/third_party/tint/fuzzers/tint_ast_fuzzer:tint_ast_fuzzer_proto",
     "//third_party/dawn/third_party/tint/fuzzers/tint_spirv_tools_fuzzer:tint_spirv_tools_fuzzer",
+
+    # The Cast Core gRPC generator tool.
+    "//third_party/cast_core/public/src/build/chromium:cast_core_grpc_generator",
   ]
 
   sources = protobuf_lite_sources + [
diff --git a/tools/disable_tests/resultdb.py b/tools/disable_tests/resultdb.py
index aeb7cff..87e9734 100644
--- a/tools/disable_tests/resultdb.py
+++ b/tools/disable_tests/resultdb.py
@@ -159,6 +159,13 @@
 
   stdout, stderr = p.communicate(json.dumps(request))
   if p.returncode != 0:
+    # rdb doesn't return unique status codes for different errors, so we have to
+    # just match on the output.
+    if 'interactive login is required' in stderr:
+      raise errors.UserError(
+          "Authentication is required to fetch test metadata.\n" +
+          "Please run:\n\trdb auth-login\nand try again")
+
     raise Exception(f'rdb rpc {method} failed with: {stderr}')
 
   if CANNED_RESPONSE_FILE:
diff --git a/tools/json_schema_compiler/util.h b/tools/json_schema_compiler/util.h
index d3875b1..963d7ec 100644
--- a/tools/json_schema_compiler/util.h
+++ b/tools/json_schema_compiler/util.h
@@ -45,11 +45,10 @@
 // This template is used for types generated by tools/json_schema_compiler.
 template <class T>
 bool PopulateItem(const base::Value& from, T* out) {
-  const base::DictionaryValue* dict = nullptr;
-  if (!from.GetAsDictionary(&dict))
+  if (!from.is_dict())
     return false;
   T obj;
-  if (!T::Populate(*dict, &obj))
+  if (!T::Populate(from, &obj))
     return false;
   *out = std::move(obj);
   return true;
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index 0ed6164..25e06c9 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -1778,8 +1778,7 @@
           # Enable lsan when asan is enabled except on Windows where LSAN isn't
           # supported.
           # TODO(https://crbug.com/948939): Enable on Mac once things pass.
-          # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
-          '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
+          '--lsan=%d' % (asan and not is_mac and not is_win),
           '--msan=%d' % msan,
           '--tsan=%d' % tsan,
           '--cfi-diag=%d' % cfi_diag,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index c2f65cf..de4d971 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -31310,6 +31310,7 @@
   <int value="1613" label="ACTION_OPENPOPUP"/>
   <int value="1614" label="OS_TELEMETRY_GETCPUINFO"/>
   <int value="1615" label="FILEMANAGERPRIVATE_OPENURL"/>
+  <int value="1616" label="FILEMANAGERPRIVATE_GETFRAMECOLOR"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -88793,9 +88794,12 @@
 </enum>
 
 <enum name="ULPLanguageStatus">
-  <int value="0" label="Top ULP language"/>
-  <int value="1" label="Non-top ULP language"/>
+  <int value="0" label="Top ULP language (Exact Match)"/>
+  <int value="1" label="Non-top ULP language (Exact Match)"/>
   <int value="2" label="Language not in ULP"/>
+  <int value="3" label="Top ULP language (Base Match)"/>
+  <int value="4" label="Non-top ULP language (Base Match)"/>
+  <int value="5" label="Language empty"/>
 </enum>
 
 <enum name="UmaCleanExitConsistency">
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index b9f6a52..c7aa74c 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -241,11 +241,6 @@
     smooth 60 frames per second. {AshAssistantAnimationSmoothness}
   </summary>
   <token key="AshAssistantAnimationSmoothness">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
     <variant name=".CardElement"
         summary="Animation for showing and hiding card responses"/>
     <variant name=".ResizeAssistantPageView"
@@ -791,55 +786,6 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.ClipboardHistory.ContextualNudge.NudgeToFeatureOpenTime"
-    units="seconds" expires_after="2021-09-01">
-  <obsolete>
-    Removed 04/2021 as of http://crrev.com/2761780. This histogram entry was
-    recreated as to some metrics may not be logging correctly. Replaced by
-    Ash.ClipboardHistory.Nudges.OnboardingNudge.ToFeatureOpenTime.
-  </obsolete>
-  <owner>ckincaid@chromium.org</owner>
-  <owner>multipaste@google.com</owner>
-  <summary>
-    The number of seconds between the user being shown the clipboard history
-    contextual nudge and the opening the clipboard history menu. The sum over
-    this histogram will also be used to measure the conversion rate between
-    showing the nudge and the clipboard history feature being opened.
-  </summary>
-</histogram>
-
-<histogram name="Ash.ClipboardHistory.ContextualNudge.NudgeToFeatureUseTime"
-    units="seconds" expires_after="2021-09-01">
-  <obsolete>
-    Removed 04/2021 as of http://crrev.com/2761780. This histogram entry was
-    recreated as to some metrics may not be logging correctly. Replaced by
-    Ash.ClipboardHistory.Nudges.OnboardingNudge.ToFeaturePasteTime.
-  </obsolete>
-  <owner>ckincaid@chromium.org</owner>
-  <owner>multipaste@google.com</owner>
-  <summary>
-    The number of seconds between the user being shown the clipboard history
-    contextual nudge and the user pasting with the clipboard history feature.
-    The sum over this histogram will also be used to measure the conversion rate
-    between showing the nudge and the clipboard history feature being used.
-  </summary>
-</histogram>
-
-<histogram name="Ash.ClipboardHistory.ContextualNudge.ShownCount"
-    enum="BooleanHit" expires_after="2021-09-01">
-  <obsolete>
-    Replaced 04/2021 by Ash.ClipboardHistory.Nudges.OnboardingNudge.ShownCount
-    as of http://crrev.com/2761780.
-  </obsolete>
-  <owner>ckincaid@chromium.org</owner>
-  <owner>multipaste@google.com</owner>
-  <summary>
-    The number of times the clipboard history contextual nudge has been shown.
-    This number will be used as the baseline against the sum of the
-    NudgeToFeatureUseTime and NudgeToFeatureOpenTime.
-  </summary>
-</histogram>
-
 <histogram name="Ash.ClipboardHistory.ControlToVDelay" units="ms"
     expires_after="2022-04-24">
   <owner>ckincaid@chromium.org</owner>
@@ -939,25 +885,8 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.ClipboardHistory.ZeroStateContextualNudge.ShownCount"
-    enum="BooleanHit" expires_after="2021-09-01">
-  <obsolete>
-    Replaced 04/2021 by Ash.ClipboardHistory.Nudges.ZeroStateNudge.ShownCount as
-    of http://crrev.com/2761780.
-  </obsolete>
-  <owner>ckincaid@chromium.org</owner>
-  <owner>multipaste@google.com</owner>
-  <summary>
-    The number of times the clipboard history zero state contextual nudge has
-    been shown. This number will be used as the baseline against the sum of the
-    ToFeatureUseTime and ToFeatureOpenTime.
-  </summary>
-</histogram>
-
 <histogram name="Ash.ContextualNudgeDismissContext{ContextualNudgesCategories}"
     enum="ContextualNudgeDismissContext" expires_after="2022-10-01">
-<!-- Name completed by histogram_suffixes name="ContextualNudgesNames" -->
-
   <owner>yulunwu@chromium.org</owner>
   <owner>tbarzic@chromium.org</owner>
   <summary>
@@ -967,19 +896,12 @@
     user performs the gesture suggested by the nudge as well as when the user
     exits the relevant state some other way. {ContextualNudgesCategories}
   </summary>
-  <token key="ContextualNudgesCategories" variants="ContextualNudgesCategories">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
-  </token>
+  <token key="ContextualNudgesCategories"
+      variants="ContextualNudgesCategories"/>
 </histogram>
 
 <histogram name="Ash.ContextualNudgeDismissTime{ContextualNudgesCategories}"
     units="ms" expires_after="2022-10-01">
-<!-- Name completed by histogram_suffixes name="ContextualNudgesNames" -->
-
   <owner>yulunwu@chromium.org</owner>
   <owner>tbarzic@chromium.org</owner>
   <summary>
@@ -988,13 +910,8 @@
     contextual nudge. No metrics are recorded if the user exits the relevant
     state some other way. {ContextualNudgesCategories}
   </summary>
-  <token key="ContextualNudgesCategories" variants="ContextualNudgesCategories">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
-  </token>
+  <token key="ContextualNudgesCategories"
+      variants="ContextualNudgesCategories"/>
 </histogram>
 
 <histogram name="Ash.Desks.AnimationSmoothness.DeskActivation" units="%"
@@ -1108,22 +1025,6 @@
   </token>
 </histogram>
 
-<histogram name="Ash.Desks.DesksCount2" units="units"
-    expires_after="2021-11-29">
-  <obsolete>
-    Removed 04/2021. #enhanced-desks flag is removed from chrome://flags, which
-    means users will not able to switch to classic desks. Make this histogram
-    only for classic desks obsolete.
-  </obsolete>
-  <owner>afakhry@chromium.org</owner>
-  <owner>tclaiborne@chromium.org</owner>
-  <summary>
-    Emitted when there's a change in the virtual desks count whether due to desk
-    creation or removal in classic desks. Specifies the number of available
-    desks. It is not emitted for the first-ever created default desk.
-  </summary>
-</histogram>
-
 <histogram name="Ash.Desks.DesksCount3" units="units"
     expires_after="2022-04-03">
   <owner>afakhry@chromium.org</owner>
@@ -1690,13 +1591,7 @@
     states. Check Ash.HotseatWidgetAnimation.AnimationSmoothness for smoothness
     of the HotseatWidget. {HotseatTransitionType}
   </summary>
-  <token key="HotseatTransitionType" variants="HotseatTransitionType">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
-  </token>
+  <token key="HotseatTransitionType" variants="HotseatTransitionType"/>
 </histogram>
 
 <histogram name="Ash.HotseatTransition.Drag.PresentationTime" units="ms"
@@ -1747,31 +1642,12 @@
     smoothness of the shelf's animating background. {HotseatWidgetElement}
   </summary>
   <token key="HotseatWidgetElement">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
     <variant name="TranslucentBackground."
         summary="Hotseat widget's translucent background"/>
     <variant name="Widget." summary="Hotseat widget"/>
   </token>
 </histogram>
 
-<histogram name="Ash.ImmersiveFullscreen.WindowType" enum="WindowType"
-    expires_after="M77">
-  <obsolete>
-    Removed 10/2020 in Issue 1138662 - its recording code was deleted. This
-    histogram entry was added long time ago, likely when immersive fullscreen
-    was first added (circa 2014), and there is no records of what was learned.
-  </obsolete>
-  <owner>kuscher@google.com</owner>
-  <summary>
-    The type of the window which is put into immersive fullscreen. Immersive
-    fullscreen is entered via the F4 key.
-  </summary>
-</histogram>
-
 <histogram name="Ash.InteractiveWindowResize.TimeToPresent" units="ms"
     expires_after="2022-04-17">
   <owner>oshima@chromium.org</owner>
@@ -2084,11 +1960,6 @@
     {NavigationWidgetElement}
   </summary>
   <token key="NavigationWidgetElement">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
     <variant name="BackButton." summary="Navigation widget's back button"/>
     <variant name="HomeButton." summary="Navigation widget's home button"/>
     <variant name="Widget." summary="Navigation widget"/>
@@ -2237,13 +2108,7 @@
     the animation completes. 100% represents ideally smooth 60 frames per
     second. {OverviewAnimationMode}
   </summary>
-  <token key="OverviewAnimationMode" variants="OverviewAnimationMode">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
-  </token>
+  <token key="OverviewAnimationMode" variants="OverviewAnimationMode"/>
 </histogram>
 
 <histogram name="Ash.Overview.AnimationSmoothness.Exit{OverviewAnimationMode}"
@@ -2255,13 +2120,7 @@
     the animation completes. 100% represents ideally smooth 60 frames per
     second. {OverviewAnimationMode}
   </summary>
-  <token key="OverviewAnimationMode" variants="OverviewAnimationMode">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
-  </token>
+  <token key="OverviewAnimationMode" variants="OverviewAnimationMode"/>
 </histogram>
 
 <histogram name="Ash.Overview.ArrowKeyPresses" units="units"
@@ -2430,23 +2289,6 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.PciePeripheral.ConnectivityResults"
-    enum="PciePeripheralConnectivityResult" expires_after="2022-03-15">
-  <obsolete>
-    Expired on M98. Marked as obsolete on 12/2021. Replaced by
-    Ash.Peripheral.ConnectivityResults which includes Thunderbolt and USB4 data
-    in addition to invalid cable data for PCIe and USB.
-  </obsolete>
-  <owner>jimmyxgong@chromium.org</owner>
-  <owner>cros-peripherals@google.com</owner>
-  <summary>
-    The connectivity results of when the user plugs in a Thunderbolt/USB4
-    peripheral to their Chrome OS device. This is recorded for only devices that
-    support Thunderbolt/USB4 connectivity. Gets recorded every time a
-    Thunderbolt/USB4 peripheral is plugged in.
-  </summary>
-</histogram>
-
 <histogram name="Ash.Peripheral.ConnectivityResults"
     enum="PeripheralConnectivityResult" expires_after="2022-12-31">
   <owner>jimmyxgong@chromium.org</owner>
@@ -2462,21 +2304,6 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.PersistentWindow.NumOfWindowsRestored" units="units"
-    expires_after="M82">
-  <obsolete>
-    Expired on M82. Marked as obsolete on 10/2021. Replaced by
-    Ash.PersistentWindow.NumOfWindowsRestoredOnDisplayAdded.
-  </obsolete>
-  <owner>zentaro@chromium.org</owner>
-  <owner>cros-peripherals@google.com</owner>
-  <summary>
-    The number of windows restored in multi-display scenario, such as due to
-    disconnecting and reconnecting display, enabling and disabling mirror mode,
-    entering and leaving dock mode. Zero is not recorded.
-  </summary>
-</histogram>
-
 <histogram name="Ash.PersistentWindow.NumOfWindowsRestoredOnDisplayAdded"
     units="units" expires_after="2022-10-31">
   <owner>minch@chromium.org</owner>
@@ -2498,127 +2325,6 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.PhoneHub.InterstitialScreenEvent.{Screen}"
-    enum="PhoneHubInterstitialScreenEvent" expires_after="2021-10-31">
-  <obsolete>
-    Moved to tools/metrics/histograms/metadata/phonehub/histograms.xml. Removed
-    11/2020 in M89.
-  </obsolete>
-  <owner>amehfooz@chromium.org</owner>
-  <owner>khorimoto@chromium.org</owner>
-  <summary>
-    Events logged when the given PhoneHub interstitial screen is shown.
-  </summary>
-<!-- The entries below should be a subset of the PhoneHubScreen enum -->
-
-  <token key="Screen">
-    <variant name="BluetoothOrWifiDisabled"/>
-    <variant name="ConnectionError">
-      <obsolete>
-        Renamed to PhoneDisconnected on Nov 2020.
-      </obsolete>
-    </variant>
-    <variant name="InitialConnecting">
-      <obsolete>
-        Combined into PhoneConnecting on Nov 2020.
-      </obsolete>
-    </variant>
-    <variant name="Onboarding.ExistingMultideviceUser"/>
-    <variant name="Onboarding.NewMultideviceUser"/>
-    <variant name="OnboardingDismissPrompt"/>
-    <variant name="PhoneConnecting"/>
-    <variant name="PhoneDisconnected"/>
-    <variant name="Reconnecting">
-      <obsolete>
-        Combined into PhoneConnecting on Nov 2020.
-      </obsolete>
-    </variant>
-  </token>
-</histogram>
-
-<histogram name="Ash.PhoneHub.NotificationCount" units="notifications"
-    expires_after="2021-10-31">
-  <obsolete>
-    Moved to tools/metrics/histograms/metadata/phonehub/histograms.xml. Removed
-    11/2020 in M89.
-  </obsolete>
-  <owner>amehfooz@chromium.org</owner>
-  <owner>khorimoto@chromium.org</owner>
-  <summary>
-    The number of PhoneHub notifications, logged each time a notification is
-    added or removed.
-  </summary>
-</histogram>
-
-<histogram name="Ash.PhoneHub.NotificationOptInEvents"
-    enum="PhoneHubInterstitialScreenEvent" expires_after="2021-10-31">
-  <obsolete>
-    Moved to tools/metrics/histograms/metadata/phonehub/histograms.xml. Removed
-    11/2020 in M89.
-  </obsolete>
-  <owner>amehfooz@chromium.org</owner>
-  <owner>khorimoto@chromium.org</owner>
-  <summary>Events for the given notification opt-in prompt.</summary>
-</histogram>
-
-<histogram name="Ash.PhoneHub.QuickActionClicked" enum="PhoneHubQuickAction"
-    expires_after="2021-10-31">
-  <obsolete>
-    Moved to tools/metrics/histograms/metadata/phonehub/histograms.xml. Removed
-    11/2020 in M89.
-  </obsolete>
-  <owner>amehfooz@chromium.org</owner>
-  <owner>khorimoto@chromium.org</owner>
-  <summary>Event logged after the user clicks on a quick action.</summary>
-</histogram>
-
-<histogram name="Ash.PhoneHub.ScreenOnSettingsButtonClicked"
-    enum="PhoneHubScreen" expires_after="2021-10-31">
-  <obsolete>
-    Moved to tools/metrics/histograms/metadata/phonehub/histograms.xml. Removed
-    11/2020 in M89.
-  </obsolete>
-  <owner>amehfooz@chromium.org</owner>
-  <owner>khorimoto@chromium.org</owner>
-  <summary>
-    Logs the current screen when the settings button is clicked in the tray
-    bubble.
-  </summary>
-</histogram>
-
-<histogram name="Ash.PhoneHub.ScreenOn{BubbleEvent}" enum="PhoneHubScreen"
-    expires_after="2021-10-31">
-  <obsolete>
-    Moved to tools/metrics/histograms/metadata/phonehub/histograms.xml. Removed
-    11/2020 in M89.
-  </obsolete>
-  <owner>amehfooz@chromium.org</owner>
-  <owner>khorimoto@chromium.org</owner>
-  <summary>
-    Logs the current screen for the PhoneHub tray given bubble event (i.e. on
-    bubble open or close).
-  </summary>
-  <token key="BubbleEvent">
-    <variant name="BubbleClose"/>
-    <variant name="BubbleOpen"/>
-  </token>
-</histogram>
-
-<histogram name="Ash.PhoneHub.TabContinuationChipClicked" units="tab index"
-    expires_after="2021-10-31">
-  <obsolete>
-    Moved to tools/metrics/histograms/metadata/phonehub/histograms.xml. Removed
-    11/2020 in M89.
-  </obsolete>
-  <owner>amehfooz@chromium.org</owner>
-  <owner>khorimoto@chromium.org</owner>
-  <summary>
-    After a tab continuation chip is clicked, the index of the tab is logged.
-    Tab indices are ordered left-to-right, top-to-bottom in a standard LTR
-    locale.
-  </summary>
-</histogram>
-
 <histogram name="Ash.Pip.AndroidPipUseTime" units="ms"
     expires_after="2022-04-24">
   <owner>takise@chromium.org</owner>
@@ -2736,37 +2442,6 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.ScreenshotController.ScreenshotsPerDay" units="int"
-    expires_after="2021-09-29">
-  <obsolete>
-    Removed 07/2021. Use Ash.CaptureModeController.ScreenshotsPerDay instead.
-  </obsolete>
-  <owner>xiyuan@chromium.org</owner>
-  <owner>gzadina@google.com</owner>
-  <summary>
-    Records the number of screenshots that have been taken via the
-    ScreenshotController every 24 hours. Note that counts are not persisted
-    across crashes, restarts, or sessions so this is only intended to give a
-    rough approximation.
-  </summary>
-</histogram>
-
-<histogram name="Ash.ScreenshotController.ScreenshotsPerWeek" units="int"
-    expires_after="2021-11-28">
-  <obsolete>
-    Removed 07/2021. Use Ash.CaptureModeController.ScreenshotsPerWeek instead.
-  </obsolete>
-  <owner>xiyuan@chromium.org</owner>
-  <owner>gzadina@google.com</owner>
-  <summary>
-    Records the number of screenshots that have been taken via the
-    ScreenshotController every 7 days. Note that counts are not persisted across
-    crashes, restarts, or sessions so this is only intended to give a rough
-    approximation. This means that this metric will only be recorded in sessions
-    spanning at least 7 days.
-  </summary>
-</histogram>
-
 <histogram name="Ash.SearchModelUpdateInterval" units="ms"
     expires_after="2022-12-01">
   <owner>yulunwu@google.com</owner>
@@ -3085,43 +2760,6 @@
   </token>
 </histogram>
 
-<histogram name="Ash.Smoothness.MaxPercentDroppedFrames_1sWindow" units="%"
-    expires_after="2022-09-30">
-  <obsolete>
-    Deprecated 12/2021 as of http://crrev.com/c/3345730 becaue we are interested
-    in dropped frame of current sliding window instead of max and would only
-    track the uniform version.
-  </obsolete>
-  <owner>xiyuan@chromium.org</owner>
-  <owner>chromeos-perfmetrics-eng@google.com</owner>
-  <summary>
-    Similar to Graphics.Smoothness.MaxPercentDroppedFrames_1sWindow but for
-    ui::Compositor. Tracks the max percent of dropped frames for in a 1 second
-    sliding window when the percentage changes.
-
-    PercentDroppedFrames is measured by tracking the number of frames which were
-    not displayed on screen out of the total number of frames expected to be
-    produced and displayed. In other words, the lower this number is, the
-    smoother experience.
-  </summary>
-</histogram>
-
-<histogram name="Ash.Smoothness.MaxPercentDroppedFrames_1sWindow.Uniform"
-    units="%" expires_after="2022-09-30">
-  <obsolete>
-    Replaced 12/2021 by Ash.Smoothness.PercentDroppedFrames_1sWindow as of
-    http://crrev.com/c/3345730 becaue we are interested in dropped frame of
-    current sliding window instead of max.
-  </obsolete>
-  <owner>xiyuan@chromium.org</owner>
-  <owner>chromeos-perfmetrics-eng@google.com</owner>
-  <summary>
-    Same as Ash.Smoothness.MaxPercentDroppedFrames_1sWindow but instead of
-    recording samples when dropped frame percentage changes, it records the
-    percentage uniformly over time.
-  </summary>
-</histogram>
-
 <histogram name="Ash.Smoothness.PercentDroppedFrames_1sWindow" units="%"
     expires_after="2022-12-15">
   <owner>xiyuan@chromium.org</owner>
@@ -3286,13 +2924,7 @@
     Maximum latency of the presentation time while resizing one or two split
     view windows. {SplitViewResizeModes}
   </summary>
-  <token key="SplitViewResizeModes" variants="SplitViewResizeModes">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
-  </token>
+  <token key="SplitViewResizeModes" variants="SplitViewResizeModes"/>
 </histogram>
 
 <histogram name="Ash.SplitViewResize.PresentationTime{SplitViewResizeModes}"
@@ -3305,13 +2937,7 @@
     Presentation time while resizing one or two split view windows.
     {SplitViewResizeModes}
   </summary>
-  <token key="SplitViewResizeModes" variants="SplitViewResizeModes">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
-  </token>
+  <token key="SplitViewResizeModes" variants="SplitViewResizeModes"/>
 </histogram>
 
 <histogram name="Ash.StateKeysPresent" enum="BooleanPresent"
@@ -3803,23 +3429,6 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.Wallpaper.Source" enum="WallpaperType"
-    expires_after="2022-04-24">
-  <obsolete>
-    Replaced 12/2021 by Ash.Wallpaper.Source2
-  </obsolete>
-  <owner>xdai@chromium.org</owner>
-  <summary>
-    Recorded when a new wallpaper is set, either by the built-in Wallpaper
-    Picker App, or by a third party App. Note the wallpaper change triggered by
-    Sync file system event doesn't count. b/205163106: Ash.Wallpaper.Source
-    metric was captured incorrectly. The metric was only triggered by the
-    deprecated Wallpaper Picker. Fixed by creating a new Ash.Wallpaper.Source2
-    metric that is recorded by the new Wallpaper App. Affected milestones: 94,
-    96, 97 and 98.
-  </summary>
-</histogram>
-
 <histogram name="Ash.Wallpaper.Source2" enum="WallpaperType"
     expires_after="2022-12-03">
   <owner>thuongphan@chromium.org</owner>
@@ -4038,198 +3647,6 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.WindowSelector.ArrowKeyPresses" units="units"
-    expires_after="M81">
-  <obsolete>
-    Replaced 04/2021 by Ash.Overview.ArrowKeyPresses.
-  </obsolete>
-  <owner>flackr@chromium.org</owner>
-  <summary>
-    The number of times the arrow keys are pressed in overview mode per session,
-    i.e. between bringing up overview mode and ending it. This is only measured
-    for the sessions that end by selecting a window with the enter key.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.Items" units="units"
-    expires_after="2020-10-04">
-  <obsolete>
-    Replaced 04/2021 by Ash.Overview.Items.
-  </obsolete>
-  <owner>flackr@chromium.org</owner>
-  <owner>kuscher@google.com</owner>
-  <summary>
-    The number of items (single windows or groups of windows such as panels) in
-    the overview mode, present at the start of each session.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.ItemsWhenTextFilteringUsed" units="items"
-    expires_after="M77">
-  <obsolete>
-    Removed 04/2021 as of https://crrev.com/c/2816339. The text filtering
-    feature has been removed.
-  </obsolete>
-  <owner>flackr@chromium.org</owner>
-  <summary>
-    The number of items showing in overview mode at the moment when an item is
-    selected or when selection is canceled. Only recorded if the text filtering
-    textfield contains a non-empty string.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.KeyPressesOverItemsRatio" units="%"
-    expires_after="2020-03-01">
-  <obsolete>
-    Replaced 04/2021 by Ash.Overview.KeyPressesOverItemsRatio.
-  </obsolete>
-  <owner>flackr@chromium.org</owner>
-  <summary>
-    The ratio between the arrow key presses and the number of overview items,
-    expressed as a percentage for a single session.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.OverviewClosedItems" units="units"
-    expires_after="2020-01-26">
-  <obsolete>
-    Replaced 04/2021 by Ash.Overview.OverviewClosedItems.
-  </obsolete>
-  <owner>flackr@chromium.org</owner>
-  <summary>
-    The number of items closed from the window overview for a single session.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.SelectionDepth" units="items"
-    expires_after="2021-09-29">
-  <obsolete>
-    Replaced 04/2021 by Ash.Overview.SelectionDepth.
-  </obsolete>
-  <owner>sammiequon@chromium.org</owner>
-  <owner>tclaiborne@chromium.org</owner>
-  <summary>
-    When a window is selected in overview mode, records that window's position
-    in the global MRU ordering. 1 represents the most-recently used window, 2
-    represents the next most-recently used window, and so on.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.TextFilteringStringLength"
-    units="characters" expires_after="M77">
-  <obsolete>
-    Removed 04/2021 as of https://crrev.com/c/2816339. The text filtering
-    feature has been removed.
-  </obsolete>
-  <owner>flackr@chromium.org</owner>
-  <summary>
-    The length of the string entered into the text filtering textfield at the
-    moment when an item is selected or when selection is canceled.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.TextFilteringTextfieldCleared"
-    units="units" expires_after="M77">
-  <obsolete>
-    Removed 04/2021 as of https://crrev.com/c/2816339. The text filtering
-    feature has been removed.
-  </obsolete>
-  <owner>flackr@chromium.org</owner>
-  <summary>
-    The number of times the text filtering textfield has had all of its text
-    removed within a single overview mode session. Measured from the time
-    overview mode is invoked to when an item is selected or when selection is
-    canceled.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.TimeBetweenActiveWindowChanges"
-    units="seconds" expires_after="M81">
-  <obsolete>
-    Replaced 04/2021 by Ash.Overview.TimeBetweenActiveWindowChanges.
-  </obsolete>
-  <owner>tbuckley@chromium.org</owner>
-  <summary>
-    The amount of time between endings of overview mode sessions which were
-    caused by the user selecting a window which was not previously active. Only
-    recorded on the second and later times after startup that the user selected
-    a window which was not previously active.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.TimeBetweenUse" units="ms"
-    expires_after="M82">
-  <obsolete>
-    Replaced 04/2021 by Ash.Overview.TimeBetweenUse.
-  </obsolete>
-  <owner>flackr@chromium.org</owner>
-  <owner>kuscher@google.com</owner>
-  <summary>
-    The amount of time between uses of overview mode, recorded when overview
-    mode is entered. Only recorded on the second and later times after startup
-    that the user entered overview mode.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.TimeInOverview" units="ms"
-    expires_after="2020-10-18">
-  <obsolete>
-    Replaced 04/2021 by Ash.Overview.TimeInOverview.
-  </obsolete>
-  <owner>flackr@chromium.org</owner>
-  <owner>kuscher@google.com</owner>
-  <summary>
-    The amount of time spent in overview mode. Overview mode is engaged by
-    pressing the overview button. The time is measured from the moment the
-    windows begin animating to a thumbnail size preview to when a window is
-    selected or selection is canceled.
-  </summary>
-</histogram>
-
-<histogram name="Ash.WindowSelector.TimeInOverviewWithTextFiltering" units="ms"
-    expires_after="M85">
-  <obsolete>
-    Removed 04/2021 as of https://crrev.com/c/2816339. The text filtering
-    feature has been removed.
-  </obsolete>
-  <owner>flackr@chromium.org</owner>
-  <summary>
-    The amount of time spent in overview mode when text filtering is used. The
-    time is measured from the moment the windows begin animating to a thumbnail
-    size preview to when a window is selected or selection is canceled. Only
-    recorded if the text filtering textfield contains a non-empty string.
-  </summary>
-</histogram>
-
-<histogram
-    name="Ash.WorkspaceWindowResizer.TabDragging.PresentationTime.ClamshellMode"
-    units="ms" expires_after="2021-10-04">
-  <obsolete>
-    Replaced in 03/2021 by Ash.TabDrag.PresentationTime.ClamshellMode.
-  </obsolete>
-  <owner>yichenz@chromium.org</owner>
-  <owner>chromeos-wmp@google.com</owner>
-  <summary>
-    Presentation time in ms when a tab is dragged in clamshell mode. Each time
-    the tab is dragged within a display, the time it takes to present the new
-    frame on screen is recorded.
-  </summary>
-</histogram>
-
-<histogram
-    name="Ash.WorkspaceWindowResizer.TabDragging.PresentationTime.MaxLatency.ClamshellMode"
-    units="ms" expires_after="2021-08-09">
-  <obsolete>
-    Replaced in 03/2021 by
-    Ash.TabDrag.PresentationTime.MaxLatency.ClamshellMode.
-  </obsolete>
-  <owner>yichenz@chromium.org</owner>
-  <owner>chromeos-wmp@google.com</owner>
-  <summary>
-    Maximum presentation time recorded during the tab dragging session.
-  </summary>
-</histogram>
-
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index a3fd60b..0f66f3d 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -1450,29 +1450,6 @@
   <affected-histogram name="BlueZ.PerProfile.ProbingResult"/>
 </histogram_suffixes>
 
-<histogram_suffixes name="BrowserRunningMode" separator=".">
-  <obsolete>
-    Removed in M91.
-  </obsolete>
-  <suffix name="FullBrowser" label=""/>
-  <suffix name="ReducedMode" label=""/>
-  <affected-histogram
-      name="Memory.BackgroundTask.Browser.PrivateMemoryFootprint"/>
-  <affected-histogram
-      name="Memory.BackgroundTask.Browser.PrivateSwapFootprint"/>
-  <affected-histogram name="Memory.BackgroundTask.Browser.ResidentSet"/>
-  <affected-histogram
-      name="Memory.BackgroundTask.Browser.SharedMemoryFootprint"/>
-  <affected-histogram
-      name="Memory.BackgroundTask.OfflinePrefetch.Browser.PrivateMemoryFootprint"/>
-  <affected-histogram
-      name="Memory.BackgroundTask.OfflinePrefetch.Browser.PrivateSwapFootprint"/>
-  <affected-histogram
-      name="Memory.BackgroundTask.OfflinePrefetch.Browser.ResidentSet"/>
-  <affected-histogram
-      name="Memory.BackgroundTask.OfflinePrefetch.Browser.SharedMemoryFootprint"/>
-</histogram_suffixes>
-
 <histogram_suffixes name="CacheDeletedEntries" separator="_">
   <suffix name="11" label="Out of the experiment"/>
   <suffix name="12" label="Control"/>
@@ -2737,32 +2714,6 @@
   <affected-histogram name="GPU.ContextLost"/>
 </histogram_suffixes>
 
-<histogram_suffixes name="ContextualNudgesNames" separator=".">
-  <obsolete>
-    Removed from code as of 04/2020.
-  </obsolete>
-  <suffix name="BackGesture"
-      label="Metrics related to back gesture's nudes and usage."/>
-  <suffix name="HomeToOverview"
-      label="Metrics related to home to overview gesture's nudes and usage."/>
-  <suffix name="InAppToHome"
-      label="Metrics related to in app to home gesture's nudes and usage."/>
-  <affected-histogram name="Ash.ContextualNudge"/>
-</histogram_suffixes>
-
-<histogram_suffixes name="ContextualNudgesTrackedMetrics" separator=".">
-  <obsolete>
-    Removed from code as of 04/2020.
-  </obsolete>
-  <suffix name="ExitNudgeContext"
-      label="Metrics related to back gesture's nudes and usage."/>
-  <suffix name="TimeDelta"
-      label="Metrics related to home to overview gesture's nudes and usage."/>
-  <affected-histogram name="Ash.ContextualNudge.BackGesture"/>
-  <affected-histogram name="Ash.ContextualNudge.HomeToOverview"/>
-  <affected-histogram name="Ash.ContextualNudge.InAppToHome"/>
-</histogram_suffixes>
-
 <histogram_suffixes name="ContextualSearchQuickActionCategory" separator=".">
   <suffix name="Address" label=""/>
   <suffix name="Email" label=""/>
@@ -5315,9 +5266,6 @@
   <suffix name="SharedImageStub" label="Shared Image Stub."/>
   <suffix name="Skia" label="Skia."/>
   <suffix name="Unknown" label="No source specified."/>
-  <affected-histogram name="Memory.GPU.PeakMemoryAllocationSource.ChangeTab"/>
-  <affected-histogram name="Memory.GPU.PeakMemoryAllocationSource.PageLoad"/>
-  <affected-histogram name="Memory.GPU.PeakMemoryAllocationSource.Scroll"/>
   <affected-histogram name="Memory.GPU.PeakMemoryAllocationSource2.ChangeTab"/>
   <affected-histogram name="Memory.GPU.PeakMemoryAllocationSource2.PageLoad"/>
   <affected-histogram name="Memory.GPU.PeakMemoryAllocationSource2.Scroll"/>
@@ -5327,7 +5275,6 @@
   <suffix base="true" name="ChangeTab" label="Changing Tabs."/>
   <suffix base="true" name="PageLoad" label="Page Load."/>
   <suffix base="true" name="Scroll" label="Scroll."/>
-  <affected-histogram name="Memory.GPU.PeakMemoryAllocationSource"/>
   <affected-histogram name="Memory.GPU.PeakMemoryAllocationSource2"/>
 </histogram_suffixes>
 
@@ -5335,7 +5282,6 @@
   <suffix name="ChangeTab" label="Changing Tabs."/>
   <suffix name="PageLoad" label="Page Load."/>
   <suffix name="Scroll" label="Scroll."/>
-  <affected-histogram name="Memory.GPU.PeakMemoryUsage"/>
   <affected-histogram name="Memory.GPU.PeakMemoryUsage2"/>
 </histogram_suffixes>
 
@@ -5373,31 +5319,6 @@
   <affected-histogram name="GPU.BlocklistFeatureTestResults"/>
 </histogram_suffixes>
 
-<histogram_suffixes name="GpuChannelManagerPressureHandlerDurationDetails"
-    separator=".">
-  <obsolete>
-    Removed 03/2021.
-  </obsolete>
-  <suffix name="DiscardableManagerHandleMemoryPressureDuration"
-      label="The time taken by the call to
-             |ServiceDiscardableManager::HandleMemoryPressure|."/>
-  <suffix name="GrShaderCachePurgeMemoryDuration"
-      label="The time taken by the call to |GrShaderCache::PurgeMemory|."/>
-  <suffix name="PasshtroughDiscardableManagerHandleMemoryPressureDuration"
-      label="The time taken by the call to
-             |PassthroughDiscardableManager::HandleMemoryPressure|."/>
-  <suffix name="ProgramCacheHandleMemoryPressureDuration"
-      label="The time taken by the call to
-             |ProgramCache::HandleMemoryPressure|."/>
-  <suffix name="SharedContextStatePurgeMemoryDuration"
-      label="The time taken by the call to |SharedContextState::PurgeMemory|."/>
-  <suffix name="TotalDuration" label="The total duration of this handler."/>
-  <suffix name="TrimD3DResourcesDuration"
-      label="The time taken by the call to |TrimD3DResources|."/>
-  <affected-histogram
-      name="Memory.Experimental.GpuChannelManagerPressureHandlerDuration"/>
-</histogram_suffixes>
-
 <histogram_suffixes name="GpuChannelResponse" separator=".">
   <obsolete>
     Expired in M75.
@@ -16479,10 +16400,6 @@
   <suffix name="Min" label="The minimum over the time interval"/>
   <affected-histogram name="WebRTC.Audio.EchoCanceller.ComfortNoiseBand0"/>
   <affected-histogram name="WebRTC.Audio.EchoCanceller.ComfortNoiseBand1"/>
-  <affected-histogram name="WebRTC.Audio.EchoCanceller.ErlBand0"/>
-  <affected-histogram name="WebRTC.Audio.EchoCanceller.ErlBand1"/>
-  <affected-histogram name="WebRTC.Audio.EchoCanceller.ErleBand0"/>
-  <affected-histogram name="WebRTC.Audio.EchoCanceller.ErleBand1"/>
   <affected-histogram name="WebRTC.Audio.EchoCanceller.SuppressorGainBand0"/>
   <affected-histogram name="WebRTC.Audio.EchoCanceller.SuppressorGainBand1"/>
 </histogram_suffixes>
diff --git a/tools/metrics/histograms/metadata/memory/histograms.xml b/tools/metrics/histograms/metadata/memory/histograms.xml
index fbc3609..df16a7b 100644
--- a/tools/metrics/histograms/metadata/memory/histograms.xml
+++ b/tools/metrics/histograms/metadata/memory/histograms.xml
@@ -93,153 +93,6 @@
   </summary>
 </histogram>
 
-<histogram base="true"
-    name="Memory.BackgroundTask.Browser.PrivateMemoryFootprint" units="MiB"
-    expires_after="2021-08-01">
-  <obsolete>
-    Removed in M91.
-  </obsolete>
-<!-- Name completed by histogram_suffixes name="BrowserRunningMode" -->
-
-  <owner>hanxi@chromium.org</owner>
-  <owner>hnakashima@chromium.org</owner>
-  <summary>
-    A rough estimate of the private memory footprint of the browser process
-    while an Android background task is running. Recorded once per background
-    task, with a random delay of 0s to 60s after it starts. Available only on
-    Android.
-  </summary>
-</histogram>
-
-<histogram base="true"
-    name="Memory.BackgroundTask.Browser.PrivateSwapFootprint" units="MiB"
-    expires_after="2021-09-15">
-  <obsolete>
-    Removed in M91.
-  </obsolete>
-<!-- Name completed by histogram_suffixes name="BrowserRunningMode" -->
-
-  <owner>hanxi@chromium.org</owner>
-  <owner>hnakashima@chromium.org</owner>
-  <summary>
-    An amount of private memory the browser process placed in swap (VmSwap) by
-    the browser process while an Android background task is running in Full
-    Browser Mode. Recorded once per background task, with a random delay of 0s
-    to 60s after it starts. Available only on Android.
-  </summary>
-</histogram>
-
-<histogram base="true" name="Memory.BackgroundTask.Browser.ResidentSet"
-    units="MiB" expires_after="2021-08-01">
-  <obsolete>
-    Removed in M91.
-  </obsolete>
-<!-- Name completed by histogram_suffixes name="BrowserRunningMode" -->
-
-  <owner>hanxi@chromium.org</owner>
-  <owner>hnakashima@chromium.org</owner>
-  <summary>
-    The size of the resident memory in a browser process while an Android
-    background task is running. Recorded once per background task, with a random
-    delay of 0s to 60s after it starts. Available only on Android.
-  </summary>
-</histogram>
-
-<histogram base="true"
-    name="Memory.BackgroundTask.Browser.SharedMemoryFootprint" units="MiB"
-    expires_after="2021-09-15">
-  <obsolete>
-    Removed in M91.
-  </obsolete>
-<!-- Name completed by histogram_suffixes name="BrowserRunningMode" -->
-
-  <owner>hanxi@chromium.org</owner>
-  <owner>hnakashima@chromium.org</owner>
-  <summary>
-    A rough estimate of the shared memory footprint of the browser process while
-    an Android background task is running. Recorded once per background task,
-    with a random delay of 0s to 60s after it starts. Available only on Android.
-  </summary>
-</histogram>
-
-<histogram base="true"
-    name="Memory.BackgroundTask.OfflinePrefetch.Browser.PrivateMemoryFootprint"
-    units="MiB" expires_after="2021-03-28">
-  <obsolete>
-    Removed in M91.
-  </obsolete>
-<!-- Name completed by histogram_suffixes name="BrowserRunningMode" -->
-
-  <owner>hanxi@chromium.org</owner>
-  <owner>hnakashima@chromium.org</owner>
-  <summary>
-    A rough estimate of the private memory footprint of the browser process
-    while the Android Offline Prefetch Android background task is running.
-    Recorded once per background task, with a random delay of 0s to 60s after it
-    starts. The same value is recorded into
-    Memory.BackgroundTask.Browser.PrivateMemoryFootprint, which aggregates all
-    task types. Available only on Android.
-  </summary>
-</histogram>
-
-<histogram base="true"
-    name="Memory.BackgroundTask.OfflinePrefetch.Browser.PrivateSwapFootprint"
-    units="MiB" expires_after="2021-09-15">
-  <obsolete>
-    Removed in M91.
-  </obsolete>
-<!-- Name completed by histogram_suffixes name="BrowserRunningMode" -->
-
-  <owner>hanxi@chromium.org</owner>
-  <owner>hnakashima@chromium.org</owner>
-  <summary>
-    An amount of private memory the browser process placed in swap (VmSwap) by
-    the browser process while the Android Offline Prefetch Android background
-    task is running in Full Browser Mode. Recorded once per background task,
-    with a random delay of 0s to 60s after it starts. The same value is recorded
-    into Memory.BackgroundTask.Browser.PrivateSwapFootprint, which aggregates
-    all task types. Available only on Android.
-  </summary>
-</histogram>
-
-<histogram base="true"
-    name="Memory.BackgroundTask.OfflinePrefetch.Browser.ResidentSet"
-    units="MiB" expires_after="2021-09-15">
-  <obsolete>
-    Removed in M91.
-  </obsolete>
-<!-- Name completed by histogram_suffixes name="BrowserRunningMode" -->
-
-  <owner>hanxi@chromium.org</owner>
-  <owner>hnakashima@chromium.org</owner>
-  <summary>
-    The size of the resident memory in a browser process while the Android
-    Offline Prefetch Android background task is running. Recorded once per
-    background task, with a random delay of 0s to 60s after it starts. The same
-    value is recorded into Memory.BackgroundTask.Browser.ResidentSet, which
-    aggregates all task types. Available only on Android.
-  </summary>
-</histogram>
-
-<histogram base="true"
-    name="Memory.BackgroundTask.OfflinePrefetch.Browser.SharedMemoryFootprint"
-    units="MiB" expires_after="2021-09-15">
-  <obsolete>
-    Removed in M91.
-  </obsolete>
-<!-- Name completed by histogram_suffixes name="BrowserRunningMode" -->
-
-  <owner>hanxi@chromium.org</owner>
-  <owner>hnakashima@chromium.org</owner>
-  <summary>
-    A rough estimate of the shared memory footprint of the browser process while
-    the Android Offline Prefetch background task is running. Recorded once per
-    background task, with a random delay of 0s to 60s after it starts. The same
-    value is recorded into Memory.BackgroundTask.Browser.SharedMemoryFootprint,
-    which aggregates all task types. Available only on Android.
-  </summary>
-</histogram>
-
 <histogram name="Memory.BackingStore" units="units" expires_after="M85">
   <owner>hajimehoshi@chromium.org</owner>
   <owner>kenjibaheux@google.com</owner>
@@ -811,22 +664,6 @@
   </summary>
 </histogram>
 
-<histogram base="true"
-    name="Memory.Experimental.GpuChannelManagerPressureHandlerDuration"
-    units="ms" expires_after="2021-08-22">
-  <obsolete>
-    Removed 03/2021.
-  </obsolete>
-<!-- Name completed by histogram_suffixes name="GpuChannelManagerPressureHandlerDetails" -->
-
-  <owner>sebmarchand@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    The time taken by the call to the various memory pressure handlers in
-    GpuChannelManager::HandleMemoryPressure.
-  </summary>
-</histogram>
-
 <histogram base="true" name="Memory.Experimental.NetworkService2" units="MiB"
     expires_after="2023-01-10">
 <!-- Name completed by histogram_suffixes name="ProcessMemoryAllocator2" -->
@@ -1135,18 +972,6 @@
   </summary>
 </histogram>
 
-<histogram name="Memory.Experimental.Renderer.LoadsInMainFrameDuringUptime"
-    units="loads" expires_after="M77">
-  <obsolete>
-    Removed in May 2021.
-  </obsolete>
-  <owner>keishi@chromium.org</owner>
-  <summary>
-    The number of loads in a main frame during the lifetime of a render process
-    (excludes extensions). Emitted when the processes quits.
-  </summary>
-</histogram>
-
 <histogram
     name="Memory.Experimental.Renderer.PeakResidentSet.AtHighestPrivateMemoryFootprint"
     units="MB" expires_after="2021-08-09">
@@ -1165,18 +990,6 @@
   </summary>
 </histogram>
 
-<histogram name="Memory.Experimental.Renderer.Uptime" units="ms"
-    expires_after="2021-10-25">
-  <obsolete>
-    Removed in May 2021.
-  </obsolete>
-  <owner>keishi@chromium.org</owner>
-  <summary>
-    The uptime of a render process in time ticks (excludes extensions). Emitted
-    when the processes quits.
-  </summary>
-</histogram>
-
 <histogram
     name="Memory.Experimental.Renderer.WebpageCount.AtHighestPrivateMemoryFootprint"
     units="counts" expires_after="2020-12-31">
@@ -1439,29 +1252,6 @@
   </summary>
 </histogram>
 
-<histogram base="true" name="Memory.GPU.PeakMemoryAllocationSource" units="KB"
-    expires_after="2021-08-22">
-  <obsolete>
-    Removed in M92. Replaced by Memory.GPU.PeakMemoryAllocationSource2.
-  </obsolete>
-<!-- Name completed by a combination of the following two histogram_suffixes: -->
-
-<!-- histogram_suffixes name="GPU.PeakMemoryAllocationSourceBase" -->
-
-<!-- and histogram_suffixes name="GPU.PeakMemoryAllocationSource" -->
-
-  <owner>jonross@chromium.org</owner>
-  <owner>graphics-dev@chromium.org</owner>
-  <owner>sadrul@chromium.org</owner>
-  <summary>
-    Replaced by Memory.GPU.PeakMemoryAllocationSource2.
-
-    The maximum amount of memory of the GPU process allocated by a particular
-    source during a user interaction (e.g. tab-switch, page-load, scroll etc.).
-    See Memory.GPU.PeakMemoryUsage.
-  </summary>
-</histogram>
-
 <histogram base="true" name="Memory.GPU.PeakMemoryAllocationSource2" units="MB"
     expires_after="2022-08-22">
 <!-- Name completed by a combination of the following two histogram_suffixes: -->
@@ -1480,24 +1270,6 @@
   </summary>
 </histogram>
 
-<histogram base="true" name="Memory.GPU.PeakMemoryUsage" units="KB"
-    expires_after="2021-08-22">
-  <obsolete>
-    Removed in M92. Replaced by Memory.GPU.PeakMemoryUsage2.
-  </obsolete>
-<!-- Name completed by histogram_suffixes name="GPU_PeakMemoryUsage" -->
-
-  <owner>jonross@chromium.org</owner>
-  <owner>sadrul@chromium.org</owner>
-  <owner>graphics-dev@chromium.org</owner>
-  <summary>
-    Replaced by Memory.GPU.PeakMemoryUsage2.
-
-    The maximum amount of memory of the GPU process during a particular
-    interaction (e.g. tab-switch, page-load, scroll etc.).
-  </summary>
-</histogram>
-
 <histogram base="true" name="Memory.GPU.PeakMemoryUsage2" units="MB"
     expires_after="2022-08-22">
 <!-- Name completed by histogram_suffixes name="GPU_PeakMemoryUsage" -->
@@ -1593,18 +1365,6 @@
   </summary>
 </histogram>
 
-<histogram name="Memory.HeapProfiler.Browser.Malloc" units="MB"
-    expires_after="M81">
-  <obsolete>
-    Removed from Chrome code in Sept 2021.
-  </obsolete>
-  <owner>alph@chromium.org</owner>
-  <owner>erikchen@chromium.org</owner>
-  <summary>
-    Browser process memory allocated with malloc when UMA heap profile is taken.
-  </summary>
-</histogram>
-
 <histogram name="Memory.LowMemoryKiller.Count" units="low-memory kills"
     expires_after="2022-04-03">
   <owner>vovoy@google.com</owner>
@@ -2427,19 +2187,6 @@
   </token>
 </histogram>
 
-<histogram name="Memory.PressureLevel" enum="MemoryPressureLevel"
-    expires_after="2021-10-24">
-  <obsolete>
-    Replaced by Memory.PressureLevel2 in M93.
-  </obsolete>
-  <owner>chrisha@chromium.org</owner>
-  <summary>
-    The memory pressure level, which is recorded periodically. This shows the
-    cumulative number of seconds that systems spend in each of the memory
-    pressure states.
-  </summary>
-</histogram>
-
 <histogram name="Memory.PressureLevel2" enum="MemoryPressureLevel"
     expires_after="never">
 <!-- expires-never: Generic system health metric used to diagnose various performance issues. -->
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index 6c55b4e5..6ca6530 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -42,73 +42,6 @@
   <variant name="WiFi.SecurityPasswordProtected"/>
 </variants>
 
-<histogram name="Network.3G.Gobi.Activation" units="ms" expires_after="M85">
-  <obsolete>
-    Removed 12/2020
-  </obsolete>
-  <owner>stevenjb@chromium.org</owner>
-  <summary>The time the Gobi modem takes to complete activation.</summary>
-</histogram>
-
-<histogram name="Network.3G.Gobi.Connect" units="ms" expires_after="2019-12-31">
-  <obsolete>
-    Removed 12/2020
-  </obsolete>
-  <owner>stevenjb@chromium.org</owner>
-  <summary>
-    The time the Gobi modem takes to connect to the cellular network.
-  </summary>
-</histogram>
-
-<histogram name="Network.3G.Gobi.Disconnect" units="ms"
-    expires_after="2019-12-31">
-  <obsolete>
-    Removed 12/2020
-  </obsolete>
-  <owner>stevenjb@chromium.org</owner>
-  <summary>
-    The time the Gobi modem takes to disconnect from the cellular network.
-  </summary>
-</histogram>
-
-<histogram name="Network.3G.Gobi.FirmwareDownload.Attempts" units="units"
-    expires_after="M85">
-  <obsolete>
-    Removed 12/2020
-  </obsolete>
-  <owner>stevenjb@chromium.org</owner>
-  <summary>Number of attempts taken to install Gobi firmware.</summary>
-</histogram>
-
-<histogram name="Network.3G.Gobi.FirmwareDownload.Time" units="ms"
-    expires_after="M85">
-  <obsolete>
-    Removed 12/2020
-  </obsolete>
-  <owner>stevenjb@chromium.org</owner>
-  <summary>The time it takes to install Gobi firmware.</summary>
-</histogram>
-
-<histogram name="Network.3G.Gobi.Registration" units="ms"
-    expires_after="2019-12-31">
-  <obsolete>
-    Removed 12/2020
-  </obsolete>
-  <owner>stevenjb@chromium.org</owner>
-  <summary>
-    The time the Gobi modem takes to register on the cellular network.
-  </summary>
-</histogram>
-
-<histogram name="Network.3G.Gobi.SetPower" enum="Network3GGobiError"
-    expires_after="2019-12-31">
-  <obsolete>
-    Removed 12/2020
-  </obsolete>
-  <owner>stevenjb@chromium.org</owner>
-  <summary>Errors experienced during Gobi device powerup.</summary>
-</histogram>
-
 <histogram name="Network.Ash.VPN.{VPNProviderType}.ConfigurationSource"
     enum="VPNConfigurationSource" expires_after="2022-12-31">
   <owner>chadduffin@chromium.org</owner>
@@ -182,20 +115,6 @@
   </token>
 </histogram>
 
-<histogram name="Network.Cellular.Activation.StatusAtLogin"
-    enum="NetworkCellularActivationState" expires_after="2021-08-29">
-  <obsolete>
-    Split into Network.Cellular.PSim.StatusAtLogin and
-    Network.Cellular.ESim.StatusAtLogin on 3/2021 in M91.
-  </obsolete>
-  <owner>azeemarshad@chromium.org</owner>
-  <owner>cros-system-services-networking@google.com</owner>
-  <summary>
-    Tracks the Cellular network activation state when the primary user logs-in
-    to the device.
-  </summary>
-</histogram>
-
 <histogram name="Network.Cellular.Apn.UseAttachApnOnSave" enum="Boolean"
     expires_after="2022-05-18">
   <owner>hsuregan@chromium.org</owner>
@@ -207,34 +126,6 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Cellular.Connection.Disconnections"
-    enum="NetworkCellularConnectionState" expires_after="2021-08-29">
-  <obsolete>
-    Split into Network.Cellular.PSim.Disconnections and
-    Network.Cellular.ESim.Disconnections on 03/2021.
-  </obsolete>
-  <owner>azeemarshad@chromium.org</owner>
-  <owner>cros-system-services-networking@google.com</owner>
-  <summary>
-    Tracks when cellular network is connected and when cellular network is
-    disconnected without explicit user action.
-  </summary>
-</histogram>
-
-<histogram name="Network.Cellular.Connection.TimeToConnected" units="ms"
-    expires_after="2021-08-29">
-  <obsolete>
-    Split into Network.Cellular.ESim.TimeToConnected and
-    Network.Cellular.ESim.TimeToConnected on 03/2021.
-  </obsolete>
-  <owner>azeemarshad@chromium.org</owner>
-  <owner>cros-connectivity@google.com</owner>
-  <summary>
-    Tracks the amount fo time taken between when cellular device starts and
-    finishes connecting.
-  </summary>
-</histogram>
-
 <histogram name="Network.Cellular.ESim.DisableProfile.Result"
     enum="HermesResponseStatus" expires_after="2022-09-01">
   <owner>azeemarshad@chromium.org</owner>
@@ -696,20 +587,6 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Cellular.Usage.Count" enum="NetworkCellularUsage"
-    expires_after="2021-06-30">
-  <obsolete>
-    Split into Network.Cellular.PSim.Usage.Count and
-    Network.Cellular.ESim.Usage.Count on 2/2021 in M91.
-  </obsolete>
-  <owner>azeemarshad@chromium.org</owner>
-  <owner>cros-system-services-networking@google.com</owner>
-  <summary>
-    Tracks the number of times a cellular network is connected as the only
-    network, or with other network or not connected at all.
-  </summary>
-</histogram>
-
 <histogram name="Network.Cellular.{SimType}.CellularSetup.{Result}.Duration"
     units="ms" expires_after="2022-08-29">
   <owner>azeemarshad@chromium.org</owner>
@@ -762,29 +639,6 @@
   </token>
 </histogram>
 
-<histogram name="Network.Cellular.{SimType}.ConnectionSuccess"
-    enum="CellularConnectResult" expires_after="2022-03-01">
-  <obsolete>
-    Removed 04/2021. Split to
-    Network.Cellular.{SimType}.ConnectionResult.UserInitiated and
-    Network.Cellular.{SimType}.ConnectionResult.All.
-  </obsolete>
-  <owner>azeemarshad@chromium.org</owner>
-  <owner>cros-connectivity@google.com</owner>
-  <owner>hsuregan@chromium.org</owner>
-  <summary>
-    Tracks the result of connecting to a cellular network. Logged when a
-    {SimType} connection succeeds or fails. In the case of failure, which can
-    occur in the Chrome or Shill layers, the error reason is emitted. Note that
-    Shill errors are mapped to Unknown. Refer to
-    go/cros-cellular-success-metrics for details.
-  </summary>
-  <token key="SimType">
-    <variant name="ESim"/>
-    <variant name="PSim"/>
-  </token>
-</histogram>
-
 <histogram name="Network.Cellular.{SimType}.DisconnectByPolicy.Result"
     enum="BooleanSuccess" expires_after="2022-09-01">
   <owner>azeemarshad@chromium.org</owner>
@@ -1102,20 +956,6 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Radio.PossibleWakeupTrigger.ResolveHostPurpose"
-    enum="ResolveHostPurpose" expires_after="2021-12-01">
-  <obsolete>
-    Removed 12/2021. Use ResolveHostPurpose2 instead.
-  </obsolete>
-  <owner>bashi@chromium.org</owner>
-  <owner>blink-network-stack@google.com</owner>
-  <summary>
-    Records the purpose of a host resolve request. Recorded when
-    NetworkContext::ResolveHost() is called while the radio state is dormant.
-    Only recorded on Android.
-  </summary>
-</histogram>
-
 <histogram name="Network.Radio.PossibleWakeupTrigger.ResolveHostPurpose2"
     enum="ResolveHostPurpose" expires_after="2022-06-01">
   <owner>bashi@chromium.org</owner>
@@ -1127,19 +967,6 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Radio.PossibleWakeupTrigger.URLLoaderAnnotationId"
-    enum="TrafficAnnotationUniqueIdHash" expires_after="2021-12-01">
-  <obsolete>
-    Removed 12/2021. Use URLLoaderAnnotationId2 instead.
-  </obsolete>
-  <owner>bashi@chromium.org</owner>
-  <owner>blink-network-stack@google.com</owner>
-  <summary>
-    Records a traffic annotation ID hash when a URLLoader is created and the
-    radio state is dormant. Only recorded on Android.
-  </summary>
-</histogram>
-
 <histogram name="Network.Radio.PossibleWakeupTrigger.URLLoaderAnnotationId2"
     enum="TrafficAnnotationUniqueIdHash" expires_after="2022-06-01">
   <owner>bashi@chromium.org</owner>
@@ -1491,96 +1318,6 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Shill.DailyChosenFractionOnline.Cellular" units="%"
-    expires_after="M98">
-  <obsolete>
-    Removed 12/2021. b/172213047: shill's implementation of this metrics never
-    worked.
-  </obsolete>
-  <owner>matthewmwang@chromium.org</owner>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS fraction of daily cumulative time on a cellular connection when
-    both cellular and WiFi adaptors are available. Each sample is the ratio of
-    the corresponding DailyChosenTimeOnLine.{Cellular,Any} samples. Reported
-    only when both a cellular and a WiFi adaptor are present (independently of
-    whether they would both be able to connect).
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.DailyChosenFractionOnline.Wifi" units="%"
-    expires_after="M98">
-  <obsolete>
-    Removed 12/2021. b/172213047: shill's implementation of this metrics never
-    worked.
-  </obsolete>
-  <owner>matthewmwang@chromium.org</owner>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS fraction of daily cumulative time on a WiFi connection when both
-    cellular and WiFi adaptors are available. Each sample is the ratio of the
-    corresponding DailyChosenTimeOnLine.{Cellular,Any} samples. Reported only
-    when both a cellular and a WiFi adaptor are present (independently of
-    whether they would both be able to connect).
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.DailyChosenTimeOnline.Any" units="seconds"
-    expires_after="M98">
-  <obsolete>
-    Removed 12/2021. b/172213047: shill's implementation of this metrics never
-    worked.
-  </obsolete>
-  <owner>matthewmwang@chromium.org</owner>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS daily cumulative time on line when both cellular and WiFi adaptors
-    are available. Reported only when both kinds of adaptor are present
-    (independently of whether they would both be able to connect). Each sample
-    contains the total on-line time in the 24-hour slot following the previous
-    sample. The time granularity is 5 minutes.
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.DailyChosenTimeOnline.Cellular" units="seconds"
-    expires_after="M98">
-  <obsolete>
-    Removed 12/2021. b/172213047: shill's implementation of this metrics never
-    worked.
-  </obsolete>
-  <owner>matthewmwang@chromium.org</owner>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS daily cumulative time on a cellular connection when both cellular
-    and WiFi adaptors are available. Reported only when both kinds of adaptor
-    are present (independently of whether they would both be able to connect).
-    Each sample contains the total on-line time in the 24-hour slot following
-    the previous sample. The time granularity is 5 minutes.
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.DailyChosenTimeOnline.Wifi" units="seconds"
-    expires_after="M98">
-  <obsolete>
-    Removed 12/2021. b/172213047: shill's implementation of this metrics never
-    worked.
-  </obsolete>
-  <owner>matthewmwang@chromium.org</owner>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS daily cumulative time on a WiFi connection when both cellular and
-    WiFi adaptors are available. Reported only when both kinds of adaptor are
-    present (independently of whether they would both be able to connect). Each
-    sample contains the total on-line time in the 24-hour slot following the
-    previous sample. The time granularity is 5 minutes.
-  </summary>
-</histogram>
-
 <histogram name="Network.Shill.DarkResumeActionResult"
     enum="ShillSuspendTerminationDarkResumeActionResult"
     expires_after="2022-04-03">
@@ -1865,96 +1602,6 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Shill.MonthlyChosenFractionOnline.Cellular" units="%"
-    expires_after="M98">
-  <obsolete>
-    Removed 12/2021. b/172213047: shill's implementation of this metrics never
-    worked.
-  </obsolete>
-  <owner>matthewmwang@chromium.org</owner>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS fraction of monthly cumulative time on a cellular connection when
-    both cellular and WiFi adaptors are available. Each sample is the ratio of
-    the corresponding MonthlyChosenTimeOnLine.{Cellular,Any} samples. Reported
-    only when both a cellular and a WiFi adaptor are present (independently of
-    whether they would both be able to connect).
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.MonthlyChosenFractionOnline.Wifi" units="%"
-    expires_after="M98">
-  <obsolete>
-    Removed 12/2021. b/172213047: shill's implementation of this metrics never
-    worked.
-  </obsolete>
-  <owner>matthewmwang@chromium.org</owner>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS fraction of monthly cumulative time on a WiFi connection when both
-    cellular and WiFi adaptors are available. Each sample is the ratio of the
-    corresponding MonthlyChosenTimeOnLine.{Cellular,Any} samples. Reported only
-    when both a cellular and a WiFi adaptor are present (independently of
-    whether they would both be able to connect).
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.MonthlyChosenTimeOnline.Any" units="seconds"
-    expires_after="M98">
-  <obsolete>
-    Removed 12/2021. b/172213047: shill's implementation of this metrics never
-    worked.
-  </obsolete>
-  <owner>matthewmwang@chromium.org</owner>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS monthly cumulative time on line when both cellular and WiFi
-    adaptors are available. Reported only when both kinds of adaptor are present
-    (independently of whether they would both be able to connect). Each sample
-    contains the total on-line time in the 30-day slot following the previous
-    sample. The time granularity is 5 minutes.
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.MonthlyChosenTimeOnline.Cellular"
-    units="seconds" expires_after="M98">
-  <obsolete>
-    Removed 12/2021. b/172213047: shill's implementation of this metrics never
-    worked.
-  </obsolete>
-  <owner>matthewmwang@chromium.org</owner>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS monthly cumulative time on a cellular connection when both
-    cellular and WiFi adaptors are available. Reported only when both kinds of
-    adaptor are present (independently of whether they would both be able to
-    connect). Each sample contains the total on-line time in the 30-day slot
-    following the previous sample. The time granularity is 5 minutes.
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.MonthlyChosenTimeOnline.Wifi" units="seconds"
-    expires_after="M98">
-  <obsolete>
-    Removed 12/2021. b/172213047: shill's implementation of this metrics never
-    worked.
-  </obsolete>
-  <owner>matthewmwang@chromium.org</owner>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS monthly cumulative time on a WiFi connection when both cellular
-    and WiFi adaptors are available. Reported only when both kinds of adaptor
-    are present (independently of whether they would both be able to connect).
-    Each sample contains the total on-line time in the 30-day slot following the
-    previous sample. The time granularity is 5 minutes.
-  </summary>
-</histogram>
-
 <histogram name="Network.Shill.PortalDetectionMultiProbeResult"
     enum="PortalDetectionMultiProbeResult" expires_after="2022-05-01">
   <owner>matthewmwang@chromium.org</owner>
@@ -1986,19 +1633,6 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Shill.ServicesOnSameNetwork" units="units"
-    expires_after="M92">
-  <obsolete>
-    Removed 05/2021
-  </obsolete>
-  <owner>stevenjb@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS network metric sampling the number of services that are connected
-    to the currently connected network.
-  </summary>
-</histogram>
-
 <histogram name="Network.Shill.SuspendActionResult"
     enum="ShillSuspendTerminationDarkResumeActionResult"
     expires_after="2022-12-01">
@@ -2614,111 +2248,6 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Shill.Wifi.LinkMonitorBroadcastErrorsAtFailure"
-    units="units" expires_after="M92">
-  <obsolete>
-    Removed 05/2021
-  </obsolete>
-  <owner>briannorris@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS network performance metric that tracks the number of LinkMonitor
-    broadcast errors that were accrued on an 802.11 wireless network at the time
-    that the link was declared to be failed.
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.Wifi.LinkMonitorFailure"
-    enum="LinkMonitorFailureType" expires_after="M92">
-  <obsolete>
-    Removed 05/2021
-  </obsolete>
-  <owner>briannorris@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS metric that signals the type of failure the LinkMonitor
-    encountered which caused it to stop monitoring an 802.11 wireless network.
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.Wifi.LinkMonitorResponseTimeSample" units="ms"
-    expires_after="M92">
-  <obsolete>
-    Removed 05/2021
-  </obsolete>
-  <owner>briannorris@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS network performance metric that tracks the number of milliseconds
-    between an ARP request and a received reply on an 802.11 wireless network.
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.Wifi.LinkMonitorsDetectionTimeDiff.ArpBetter"
-    units="ms" expires_after="M91">
-  <obsolete>
-    Removed 04/2021
-  </obsolete>
-  <owner>jiejiang@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS metric that tracks the number of milliseconds between the
-    detection times for the same failure of NeighborLinkMonitor and
-    shill::LinkMonitor. Recorded after either of the link monitor detects an
-    error. The larger this value is, the better shill::LinkMonitor performs than
-    NeighborLinkMonitor. 0 means shill::LinkMonitor is no better than
-    NeighborLinkMonitor. If shill::LinkMonitor detects a failure but
-    NeighborLinkMonitor does not, the maximum value of a timeout will be
-    emitted.
-  </summary>
-</histogram>
-
-<histogram
-    name="Network.Shill.Wifi.LinkMonitorsDetectionTimeDiff.NeighborBetter"
-    units="ms" expires_after="M91">
-  <obsolete>
-    Removed 04/2021
-  </obsolete>
-  <owner>jiejiang@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS metric that tracks the number of milliseconds between the
-    detection times for the same failure of patchpanel::NeighborLinkMonitor and
-    shill::LinkMonitor. Recorded after either of the link monitor detects an
-    error. The larger this value is, the better NeighborLinkMonitor performs
-    than shill::LinkMonitor. 0 means NeighborLinkMonitor is no better than
-    shill::LinkMonitor. If NeighborLinkMonitor detects a failure but
-    shill::LinkMonitor does not, the maxmium value of a timeout will be emitted.
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.Wifi.LinkMonitorSecondsToFailure"
-    units="seconds" expires_after="M92">
-  <obsolete>
-    Removed 05/2021
-  </obsolete>
-  <owner>briannorris@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS network performance metric that tracks the number of seconds from
-    the start of the LinkMonitor until failure on an 802.11 wireless network.
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.Wifi.LinkMonitorUnicastErrorsAtFailure"
-    units="units" expires_after="M92">
-  <obsolete>
-    Removed 05/2021
-  </obsolete>
-  <owner>briannorris@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS network performance metric that tracks the number of LinkMonitor
-    unicast errors that were accrued on an 802.11 wireless network at the time
-    that the link was declaired to be failed.
-  </summary>
-</histogram>
-
 <histogram name="Network.Shill.WiFi.MBOSupport" enum="MBOSupport"
     expires_after="2022-12-01">
   <owner>matthewmwang@google.com</owner>
@@ -2955,47 +2484,6 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Shill.WiFi.StoppedTxQueueLength" units="frames"
-    expires_after="2021-12-01">
-  <obsolete>
-    Removed 12/2020
-  </obsolete>
-  <owner>briannorris@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS network metric indicating the maximal length of any stopped
-    mac80211 transmit queue. The metric is reported when a queue-status check
-    determines that at least one transmit queue is stopped, and has more than a
-    threshold number of frames queued.
-  </summary>
-</histogram>
-
-<histogram name="Network.Shill.WiFi.StoppedTxQueueReason"
-    enum="NetworkQueueStopReason" expires_after="2021-12-01">
-  <obsolete>
-    Removed 12/2020
-  </obsolete>
-  <owner>briannorris@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    Chrome OS network metric indicating the reason that mac80211 transmit queues
-    were stopped. The metric is reported when a queue-status check determines
-    that at least one queue is stopped, and has more than a threshold number of
-    frames queued.
-
-    One measurement is reported per stop reason, per queue-status check. Reasons
-    that apply to multiple queues are reported only once per queue-status check.
-    Reasons that only apply to queues that have a below-threshold number of
-    frames are skipped.
-
-    Note that, because we may report multiple stop reasons for a single
-    queue-status check, this histogram is not suitable for determining the
-    number of times a queue-status check found that the queues were stopped. To
-    determine that number, use the count of
-    Network.Shill.WiFi.StoppedTxQueueLength reports.
-  </summary>
-</histogram>
-
 <histogram name="Network.Shill.WiFi.SupplicantAttempts" units="attempts"
     expires_after="2022-04-17">
   <owner>norvez@chromium.org</owner>
@@ -3487,24 +2975,6 @@
   </summary>
 </histogram>
 
-<histogram name="NetworkService.CorsForcedOffForIsolatedWorldOrigin"
-    enum="BooleanForceDisabled" expires_after="2021-09-30">
-  <obsolete>
-    Removed in September 2021.
-  </obsolete>
-  <owner>lukasza@chromium.org</owner>
-  <owner>rdevlin.cronin@chromium.org</owner>
-  <summary>
-    Whether CORS was turned off based on the origin of an isolated world (e.g.
-    for a content script of a platform app).
-
-    Logged in CorsURLLoader::HandleComplete for all successful requests.
-
-    The logged value depends on whether CorsURLLoader::SetCorsFlagIfNeeded had
-    been affected by network::ResourceRequest::isolated_world_origin.
-  </summary>
-</histogram>
-
 <histogram name="NetworkService.CorsPreflightMethodAllowed"
     enum="NetworkServiceCorsPreflightMethodAllowed" expires_after="M100">
   <owner>hiroshige@chromium.org</owner>
@@ -3592,20 +3062,6 @@
   </summary>
 </histogram>
 
-<histogram name="NetworkService.StreamingUploadDataPipeGetterValidity"
-    units="BooleanValid" expires_after="2021-03-31">
-  <obsolete>
-    Removed 03/2021
-  </obsolete>
-  <owner>yhirano@chromium.org</owner>
-  <owner>yoichio@chromium.org</owner>
-  <summary>
-    It seems in some cases the ChunkedDataPipeGetter for a request body is
-    missing. This histogram counts whether the mojo::Remote is valid, for each
-    request with body of type KChunkedDataPipe or kReadOnceStream.
-  </summary>
-</histogram>
-
 <histogram name="NetworkService.TimeToFirstResponse" units="ms"
     expires_after="M82">
   <owner>cduvall@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/notifications/histograms.xml b/tools/metrics/histograms/metadata/notifications/histograms.xml
index 5ef7572..a8d3d48 100644
--- a/tools/metrics/histograms/metadata/notifications/histograms.xml
+++ b/tools/metrics/histograms/metadata/notifications/histograms.xml
@@ -24,17 +24,7 @@
 
 <variants name="MacOSNotificationStyle">
   <variant name="Alert" summary="Alert: Stays on screen"/>
-  <variant name="Alerts">
-    <obsolete>
-      Replaced by Alert in 2021/09.
-    </obsolete>
-  </variant>
   <variant name="Banner" summary="Banner: Auto dismisses"/>
-  <variant name="Banners">
-    <obsolete>
-      Replaced by Banner in 2021/09.
-    </obsolete>
-  </variant>
 </variants>
 
 <histogram name="Notifications.Actions" enum="NotificationActionType"
@@ -81,19 +71,6 @@
   </summary>
 </histogram>
 
-<histogram name="Notifications.Announcement.Events"
-    enum="AnnouncementNotificationEvent" expires_after="2020-10-30">
-  <obsolete>
-    Deprecated as of 10/2020
-  </obsolete>
-  <owner>dtrainor@chromium.org</owner>
-  <owner>xingliu@chromium.org</owner>
-  <summary>
-    Records the events when the announcement notification is shown, clicked, or
-    closed.
-  </summary>
-</histogram>
-
 <histogram name="Notifications.AppNotificationStatus"
     enum="NotificationAppStatus" expires_after="never">
 <!-- expires-never: Indicates whether Chrome has notification permission. -->
@@ -916,22 +893,6 @@
   </summary>
 </histogram>
 
-<histogram name="Notifications.UsingNativeNotificationCenter"
-    enum="BooleanNativeNotifications" expires_after="never">
-  <obsolete>
-    Replaced by Notifications.UsingSystemNotificationCenter in October 2020.
-  </obsolete>
-<!-- expires-never: core to the notification user experience. -->
-
-  <owner>peter@chromium.org</owner>
-  <owner>knollr@chromium.org</owner>
-  <owner>thomasanderson@chromium.org</owner>
-  <summary>
-    Indicates if Chrome is using system notifications or the Chrome notification
-    center. Logged on each start up.
-  </summary>
-</histogram>
-
 <histogram name="Notifications.UsingSystemNotificationCenter"
     enum="BooleanSystemNotifications" expires_after="never">
 <!-- expires-never: core to the notification user experience. -->
@@ -945,42 +906,6 @@
   </summary>
 </histogram>
 
-<histogram name="Notifications.WebPlatform.{Action}.TimeToActivity" units="ms"
-    expires_after="M96">
-  <obsolete>
-    From 2021-03 use Notifications.WebPlatform2 instead.
-  </obsolete>
-  <owner>peconn@chromium.org</owner>
-  <owner>peter@chromium.org</owner>
-  <summary>
-    Records the delay between the user clicking on the {Action} of a web
-    platform notification, and an Activity being launched. Only records if the
-    duration is less than 5 seconds.
-  </summary>
-  <token key="Action">
-    <variant name="ActionButton" summary="action button"/>
-    <variant name="Body" summary="body"/>
-  </token>
-</histogram>
-
-<histogram name="Notifications.WebPlatform.{Action}.TimeToClose" units="ms"
-    expires_after="M96">
-  <obsolete>
-    From 2021-03 use Notifications.WebPlatform2 instead.
-  </obsolete>
-  <owner>peconn@chromium.org</owner>
-  <owner>peter@chromium.org</owner>
-  <summary>
-    Records the delay between the user clicking on the {Action} of a web
-    platform notification, and the notification being closed. Only records if
-    the duration is less than 5 seconds.
-  </summary>
-  <token key="Action">
-    <variant name="ActionButton" summary="action button"/>
-    <variant name="Body" summary="body"/>
-  </token>
-</histogram>
-
 <histogram name="Notifications.WebPlatformV2.{Action}.TimeToActivity"
     units="ms" expires_after="M102">
   <owner>peconn@chromium.org</owner>
@@ -1216,20 +1141,6 @@
   </summary>
 </histogram>
 
-<histogram name="Notifications.XPCConnectionEvent" enum="XPCConnectionEvent"
-    expires_after="M94">
-  <obsolete>
-    We no longer use an XPC service for macOS notifications from 06/2021.
-  </obsolete>
-  <owner>peter@chromium.org</owner>
-  <owner>rsesek@chromium.org</owner>
-  <summary>
-    Mac only. Records the different events of a Notification XPC connection.
-    These are recorded by monitoring the different error callbacks provided by
-    the XPC connection object.
-  </summary>
-</histogram>
-
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index 39c6a7d1b..fa1bfbcf 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -322,62 +322,6 @@
   <token key="RequestContext" variants="RequestContext"/>
 </histogram>
 
-<histogram
-    name="OptimizationGuide.HintsFetcher.TopHostProvider.BlacklistSize.OnInitialize"
-    units="total host count" expires_after="2021-03-31">
-  <obsolete>
-    Deprecated as of 2021/02.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>sophiechang@chromium.org</owner>
-  <summary>
-    Records the number of hosts placed on the HintsFetcherTopHostBlacklist when
-    it is initialized.
-  </summary>
-</histogram>
-
-<histogram
-    name="OptimizationGuide.HintsFetcher.TopHostProvider.BlacklistSize.OnRequest"
-    units="total host count" expires_after="2021-03-31">
-  <obsolete>
-    Deprecated as of 2021/02.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>sophiechang@chromium.org</owner>
-  <summary>
-    Records the number of hosts on the HintsFetcherTopHostBlacklist when top
-    hosts are requested.
-  </summary>
-</histogram>
-
-<histogram
-    name="OptimizationGuide.HintsFetcher.TopHostProvider.BlocklistSize.OnInitialize"
-    units="total host count" expires_after="M94">
-  <obsolete>
-    Deprecated as of 2021/06.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>sophiechang@chromium.org</owner>
-  <summary>
-    Records the number of hosts placed on the HintsFetcherTopHostBlocklist when
-    it is initialized.
-  </summary>
-</histogram>
-
-<histogram
-    name="OptimizationGuide.HintsFetcher.TopHostProvider.BlocklistSize.OnRequest"
-    units="total host count" expires_after="M94">
-  <obsolete>
-    Deprecated as of 2021/06.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>sophiechang@chromium.org</owner>
-  <summary>
-    Records the number of hosts on the HintsFetcherTopHostBlocklist when top
-    hosts are requested.
-  </summary>
-</histogram>
-
 <histogram name="OptimizationGuide.HintsManager.ActiveTabUrlsToFetchFor"
     units="counts" expires_after="M106">
   <owner>sophiechang@chromium.org</owner>
@@ -536,37 +480,6 @@
 </histogram>
 
 <histogram
-    name="OptimizationGuide.ModelExecutor.ModelLoadingResult.{OptimizationTarget}"
-    enum="ModelExecutorLoadingState" expires_after="M106">
-  <obsolete>
-    Removed in favor of OptimizationGuide.ModelExecutor.ExecutionStatus in M98.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>chrome-intelligence-core@google.com</owner>
-  <summary>
-    Records the result of a loading a model with a provided file for
-    {OptimizationTarget}. Recorded once per model load attempt for
-    {OptimizationTarget}.
-  </summary>
-  <token key="OptimizationTarget" variants="OptimizationTarget"/>
-</histogram>
-
-<histogram name="OptimizationGuide.ModelExecutor.RunCount.{OptimizationTarget}"
-    units="count" expires_after="M94">
-  <obsolete>
-    Removed 2021-May.
-  </obsolete>
-  <owner>robertogden@chromium.org</owner>
-  <owner>chrome-intelligence-core@google.com</owner>
-  <summary>
-    Records the number of times the model for the {OptimizationTarget}
-    optimization target was run during the lifetime of the model being in
-    memory. Recorded every time the model is unloaded from memory.
-  </summary>
-  <token key="OptimizationTarget" variants="OptimizationTarget"/>
-</histogram>
-
-<histogram
     name="OptimizationGuide.ModelExecutor.TaskExecutionLatency.{OptimizationTarget}"
     units="ms" expires_after="M106">
   <owner>mcrouse@chromium.org</owner>
@@ -724,13 +637,7 @@
     {PageTextDumpEvent} event. Note that these values are capped at runtime to
     an experimentally controlled value.
   </summary>
-  <token key="PageTextDumpEvent" variants="PageTextDumpEvent">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use variants of this histogram instead.
-      </obsolete>
-    </variant>
-  </token>
+  <token key="PageTextDumpEvent" variants="PageTextDumpEvent"/>
 </histogram>
 
 <histogram
@@ -753,13 +660,7 @@
     event, from being sent until being canceled. Recorded every time a request
     is canceled. This normally occurs because the mojo pipe disconnected.
   </summary>
-  <token key="PageTextDumpEvent" variants="PageTextDumpEvent">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use variants of this histogram instead.
-      </obsolete>
-    </variant>
-  </token>
+  <token key="PageTextDumpEvent" variants="PageTextDumpEvent"/>
 </histogram>
 
 <histogram
@@ -772,13 +673,7 @@
     event, to complete successfully. Recorded every time a request completes
     successfully.
   </summary>
-  <token key="PageTextDumpEvent" variants="PageTextDumpEvent">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use variants of this histogram instead.
-      </obsolete>
-    </variant>
-  </token>
+  <token key="PageTextDumpEvent" variants="PageTextDumpEvent"/>
 </histogram>
 
 <histogram
@@ -793,20 +688,6 @@
   </summary>
 </histogram>
 
-<histogram
-    name="OptimizationGuide.PredictionManager.HasHostModelFeaturesForHost"
-    enum="Boolean" expires_after="2021-10-13">
-  <obsolete>
-    Deprecated as of 2021/10.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>sophiechang@chromium.org</owner>
-  <summary>
-    Whether the PredictionManager had HostModelFeatures for the host of the
-    navigation. Recorded when ShouldTargetNavigation is called.
-  </summary>
-</histogram>
-
 <histogram name="OptimizationGuide.PredictionManager.HostModelFeaturesMapSize"
     units="total host count" expires_after="M106">
   <owner>mcrouse@chromium.org</owner>
@@ -818,21 +699,6 @@
   </summary>
 </histogram>
 
-<histogram name="OptimizationGuide.PredictionManager.IsDownloadUrlValid"
-    units="BooleanValid" expires_after="M106">
-  <obsolete>
-    Replaced by
-    OptimizationGuide.PredictionManager.IsDownloadUrlValid.{OptimizationTarget}
-    in M96.
-  </obsolete>
-  <owner>sophiechang@chromium.org</owner>
-  <owner>mcrouse@chromium.org</owner>
-  <summary>
-    Records whether the download URL received from the remote Optimization Guide
-    server is valid.
-  </summary>
-</histogram>
-
 <histogram
     name="OptimizationGuide.PredictionManager.IsDownloadUrlValid.{OptimizationTarget}"
     units="BooleanValid" expires_after="M106">
@@ -881,24 +747,6 @@
 </histogram>
 
 <histogram
-    name="OptimizationGuide.PredictionModelDownloadManager.ReplaceFileError"
-    enum="PlatformFileError" expires_after="M106">
-  <obsolete>
-    Replaced by
-    OptimizationGuide.PredictionModelDownloadManager.ReplaceFileError.{OptimizationTarget}
-    in M96.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>sophiechang@chromium.org</owner>
-  <summary>
-    Records the exact error whenever writing a model file fails at the step
-    where the model is moved from the scratch directory to its final location.
-    Recorded every time a model file is downloaded and being processed for
-    storage.
-  </summary>
-</histogram>
-
-<histogram
     name="OptimizationGuide.PredictionModelDownloadManager.ReplaceFileError.{OptimizationTarget}"
     enum="PlatformFileError" expires_after="M106">
   <owner>mcrouse@chromium.org</owner>
@@ -912,37 +760,6 @@
   <token key="OptimizationTarget" variants="OptimizationTarget"/>
 </histogram>
 
-<histogram base="true"
-    name="OptimizationGuide.PredictionModelEvaluationLatency.{OptimizationTarget}"
-    units="ms" expires_after="2021-10-13">
-  <obsolete>
-    Deprecated as of 2021/10.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>sophiechang@chromium.org</owner>
-  <summary>
-    The duration of evaluating a prediction model for a registered optimization
-    target. Recorded every time a prediction model is successfuly evaluated by
-    the prediction manager. Not recorded if a cached decision is used or if the
-    evaluation fails.
-  </summary>
-  <token key="OptimizationTarget" variants="OptimizationTarget"/>
-</histogram>
-
-<histogram name="OptimizationGuide.PredictionModelExpired"
-    units="BooleanExpired" expires_after="M96">
-  <obsolete>
-    Deprecated in favor of OptimizationTarget variant as of 09/2021.
-  </obsolete>
-  <owner>sophiechang@chromium.org</owner>
-  <owner>mcrouse@chromium.org</owner>
-  <summary>
-    Records whether a prediction model expired if it was not updated or used
-    within the last 30 days by default, but can change subject to Finch
-    configuration.
-  </summary>
-</histogram>
-
 <histogram name="OptimizationGuide.PredictionModelExpired.{OptimizationTarget}"
     enum="BooleanExpired" expires_after="M106">
   <owner>sophiechang@chromium.org</owner>
@@ -957,35 +774,6 @@
 </histogram>
 
 <histogram
-    name="OptimizationGuide.PredictionModelFetcher.GetModelsRequest.HostCount"
-    units="total host count" expires_after="M94">
-  <obsolete>
-    Obsolete as of 04/2021.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>sophiechang@chromium.org</owner>
-  <summary>
-    Records the number of hosts included in a remote Optimization Guide Service
-    client model request. This will be captured when any GetModelsRequest is
-    initiated.
-  </summary>
-</histogram>
-
-<histogram
-    name="OptimizationGuide.PredictionModelFetcher.GetModelsResponse.HostModelFeatureCount"
-    units="units" expires_after="M94">
-  <obsolete>
-    Obsolete as of 04/2021.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>sophiechang@chromium.org</owner>
-  <summary>
-    Records the number of host model features received from the remote
-    Optimization Guide Service for every successful GetModelsRequest.
-  </summary>
-</histogram>
-
-<histogram
     name="OptimizationGuide.PredictionModelFetcher.GetModelsResponse.NetErrorCode"
     enum="NetErrorCodes" expires_after="M106">
   <owner>mcrouse@chromium.org</owner>
@@ -1162,37 +950,6 @@
   </summary>
 </histogram>
 
-<histogram
-    name="OptimizationGuide.ShouldTargetNavigation.PredictionModelStatus.{OptimizationTarget}"
-    enum="OptimizationGuidePredictionManagerModelStatus"
-    expires_after="2021-10-13">
-  <obsolete>
-    Deprecated as of 2021/10.
-  </obsolete>
-  <owner>mcrouse@chromium.org</owner>
-  <owner>sophiechang@chromium.org</owner>
-  <summary>
-    The status of the prediction model for an optimization target within the
-    prediction manager. Recorded when the manager is asked if a navigation
-    should be targeted and the decision for the navigation has not already been
-    made.
-  </summary>
-  <token key="OptimizationTarget" variants="OptimizationTarget"/>
-</histogram>
-
-<histogram name="OptimizationGuide.TargetDecision.{OptimizationTarget}"
-    enum="OptimizationGuideOptimizationTargetDecision" expires_after="M106">
-  <obsolete>
-    Obsolete as of 07/2021.
-  </obsolete>
-  <owner>sophiechang@chromium.org</owner>
-  <owner>mcrouse@chromium.org</owner>
-  <summary>
-    The decision made for whether the page load matches the optimization target.
-  </summary>
-  <token key="OptimizationTarget" variants="OptimizationTarget"/>
-</histogram>
-
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/web_rtc/histograms.xml b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
index d4a66f22..08ccc088 100644
--- a/tools/metrics/histograms/metadata/web_rtc/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
@@ -496,20 +496,6 @@
   </summary>
 </histogram>
 
-<histogram name="WebRTC.Audio.AudioMixer.NumIncomingActiveStreams"
-    units="count" expires_after="M77">
-  <obsolete>
-    Removed as of 2021-05-10.
-  </obsolete>
-  <owner>aleloi@chromium.org</owner>
-  <summary>
-    Reports the number of active incoming streams in the WebRTC audio mixer. An
-    incoming stream is active if it is not muted. When multiple streams are
-    active, adding them can result in saturation and limiting is needed. Logged
-    every second during a WebRTC call.
-  </summary>
-</histogram>
-
 <histogram name="WebRTC.Audio.AudioMixer.NumIncomingActiveStreams2"
     units="streams" expires_after="2022-04-30">
   <owner>alessiob@chromium.org</owner>
@@ -570,20 +556,6 @@
   </summary>
 </histogram>
 
-<histogram name="WebRTC.Audio.EchoCanceller.ActiveRender" enum="Boolean"
-    expires_after="2020-12-01">
-  <obsolete>
-    No longer in use. Removed on 2020-10-19.
-  </obsolete>
-  <owner>peah@chromium.org</owner>
-  <owner>saza@chromium.org</owner>
-  <summary>
-    This histogram logs a value indicating whether the WebRTC echo canceller
-    detects that there is active content in the render signal. A new value is
-    logged every 10 seconds and the logged value is averaged over this period.
-  </summary>
-</histogram>
-
 <histogram name="WebRTC.Audio.EchoCanceller.BufferDelay" units="Blocks"
     expires_after="2022-09-13">
   <owner>peah@chromium.org</owner>
@@ -641,62 +613,6 @@
   </summary>
 </histogram>
 
-<histogram name="WebRTC.Audio.EchoCanceller.ErlBand0" units="dB (shifted)"
-    expires_after="2020-12-01">
-  <obsolete>
-    No longer in use, EchoCanceller.Erl considered sufficient. Removed on
-    2020-10-19.
-  </obsolete>
-  <owner>peah@chromium.org</owner>
-  <owner>saza@chromium.org</owner>
-  <summary>
-    This histogram logs the echo return loss achieved by the WebRTC echo
-    canceller in the lower 4 kHz. A new value is logged every 10 seconds.
-  </summary>
-</histogram>
-
-<histogram name="WebRTC.Audio.EchoCanceller.ErlBand1" units="dB (shifted)"
-    expires_after="2020-12-01">
-  <obsolete>
-    No longer in use, EchoCanceller.Erl considered sufficient. Removed on
-    2020-10-19.
-  </obsolete>
-  <owner>peah@chromium.org</owner>
-  <owner>saza@chromium.org</owner>
-  <summary>
-    This histogram logs the echo return loss achieved by the WebRTC echo
-    canceller between 4 and 8 kHz. A new value is logged every 10 seconds.
-  </summary>
-</histogram>
-
-<histogram name="WebRTC.Audio.EchoCanceller.ErleBand0" units="dB (shifted)"
-    expires_after="2020-12-01">
-  <obsolete>
-    No longer in use, EchoCanceller.Erle considered sufficient. Removed on
-    2020-10-19.
-  </obsolete>
-  <owner>peah@chromium.org</owner>
-  <owner>saza@chromium.org</owner>
-  <summary>
-    This histogram logs the echo return loss enhancement achieved by the WebRTC
-    echo canceller in the lower 4 kHz. A new value is logged every 10 seconds.
-  </summary>
-</histogram>
-
-<histogram name="WebRTC.Audio.EchoCanceller.ErleBand1" units="dB (shifted)"
-    expires_after="2020-12-01">
-  <obsolete>
-    No longer in use, EchoCanceller.Erle considered sufficient. Removed on
-    2020-10-19.
-  </obsolete>
-  <owner>peah@chromium.org</owner>
-  <owner>saza@chromium.org</owner>
-  <summary>
-    This histogram logs the echo return loss enhancement achieved by the WebRTC
-    echo canceller between 4 and 8 kHz. A new value is logged every 10 seconds.
-  </summary>
-</histogram>
-
 <histogram name="WebRTC.Audio.EchoCanceller.Erle{WebRTCEchoCancellerEstimate}"
     units="dB" expires_after="2022-09-13">
   <owner>gustaf@chromium.org</owner>
@@ -800,21 +716,6 @@
   </summary>
 </histogram>
 
-<histogram name="WebRTC.Audio.EchoCanceller.ModelBasedAecFeasible"
-    enum="Boolean" expires_after="2020-12-01">
-  <obsolete>
-    No longer reported. Removed on 2020-10-19.
-  </obsolete>
-  <owner>peah@chromium.org</owner>
-  <owner>saza@chromium.org</owner>
-  <summary>
-    This histogram logs a value every time the WebRTC echo canceller deems that
-    echo path is possible to model using any of the the echo canceller echo path
-    models. A new value is logged every 10 seconds and the logged value is the
-    feasibility assessment at the time when the value is logged.
-  </summary>
-</histogram>
-
 <histogram name="WebRTC.Audio.EchoCanceller.ReliableDelayEstimates"
     enum="WebRTCAecDelayEstimateReliability" expires_after="2022-09-13">
   <owner>peah@chromium.org</owner>
@@ -912,22 +813,6 @@
   </summary>
 </histogram>
 
-<histogram name="WebRTC.Audio.ResidualEchoDetector.EchoLikelihood" units="%"
-    expires_after="2021-05-09">
-  <obsolete>
-    No longer reported. Removed in M84.
-  </obsolete>
-  <owner>hlundin@chromium.org</owner>
-  <owner>ivoc@chromium.org</owner>
-  <summary>
-    The estimated likelihood percentage of echo as detected by the residual echo
-    detector. The residual echo detector can be used to detect cases where the
-    AEC (hardware or software) is not functioning properly. The detector can be
-    non-causal and operates on larger timescales with more delay than the
-    regular AEC.
-  </summary>
-</histogram>
-
 <histogram name="WebRTC.Audio.SpeechExpandRatePercent" units="%"
     expires_after="2022-06-05">
   <owner>hlundin@chromium.org</owner>
@@ -1278,22 +1163,6 @@
   </summary>
 </histogram>
 
-<histogram name="WebRTC.DataChannelMaxRetransmitTime" units="ms"
-    expires_after="2021-12-31">
-  <obsolete>
-    Replaced by WebRTC.DataChannelMaxPacketLifeTime to align better with the
-    specification in M93.
-  </obsolete>
-  <owner>orphis@chromium.org</owner>
-  <owner>toprice@chromium.org</owner>
-  <summary>
-    Recorded when a data channel is created. The length of the time window
-    during which transmissions and retransmissions may occur in unreliable mode.
-    It is set to the value used in the configuration when a RTCDataChannel is
-    created. Expired in M78.
-  </summary>
-</histogram>
-
 <histogram name="WebRTC.DataChannelSctpErrorCode"
     enum="DataChannelSctpErrorCode" expires_after="2022-06-30">
   <owner>orphis@chromium.org</owner>
@@ -1650,20 +1519,6 @@
   </summary>
 </histogram>
 
-<histogram name="WebRTC.PeerConnection.KeyProtocolByMedia"
-    enum="PeerConnectionKeyProtocolByMedia" expires_after="2022-01-09">
-  <obsolete>
-    Support removed 2022-01-13
-  </obsolete>
-  <owner>hta@chromium.org</owner>
-  <owner>webrtc-dev@chromium.org</owner>
-  <summary>
-    What key exchange protocol (DTLS or SDES) is used to establish the crypto
-    keys for a PeerConnection's RTP transport, specified per media type. Note:
-    This histogram was expired after M82, and resurrected in M89.
-  </summary>
-</histogram>
-
 <histogram name="WebRTC.PeerConnection.Latency.Network" units="microseconds"
     expires_after="2022-05-24">
   <owner>handellm@chromium.org</owner>
@@ -1722,20 +1577,6 @@
   </summary>
 </histogram>
 
-<histogram name="WebRTC.PeerConnection.OfferExtmapAllowMixed"
-    enum="PeerConnectionOfferExtmapAllowMixed" expires_after="2022-01-02">
-  <obsolete>
-    Removed as of 2021-08-03.
-  </obsolete>
-  <owner>kron@chromium.org</owner>
-  <summary>
-    What setting for the SDP attribute extmap-allow-mixed has been asked for by
-    the creator of a PeerConnection. This is specified to the constructor
-    through the dictionary property offerExtmapAllowMixed which can be set to
-    either true or false. A default value will be used if it's not specified.
-  </summary>
-</histogram>
-
 <histogram name="WebRTC.PeerConnection.ProvisionalAnswer"
     enum="PeerConnectionProvisionalAnswer" expires_after="2022-06-01">
   <owner>hta@chromium.org</owner>
@@ -2471,11 +2312,6 @@
     after each frame has been decoded. {CodecInfo}
   </summary>
   <token key="CodecInfo">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
     <variant name=".H264.4k.Hw" summary=""/>
     <variant name=".H264.4k.Sw" summary=""/>
     <variant name=".H264.Hd.Hw" summary=""/>
@@ -2744,11 +2580,6 @@
     for a received video stream. {WebRtcCodecs}
   </summary>
   <token key="WebRtcCodecs">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
     <variant name=".Av1" summary=""/>
     <variant name=".Generic" summary=""/>
     <variant name=".H264" summary=""/>
@@ -4200,11 +4031,6 @@
     event. {WebRtcLoggingEvent}
   </summary>
   <token key="WebRtcLoggingEvent">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
     <variant name=".Discard" summary="Discard"/>
     <variant name=".Start" summary="Start"/>
     <variant name=".UploadFailed" summary="Upload failed"/>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 8bcd7b6..0d29a3e 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,23 +6,23 @@
         },
         "win": {
             "hash": "f8bc92e9f883916718b78e5c280dd1415e444e5b",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/585716d40f6593c3e04780d670e1e95fcce3052a/trace_processor_shell.exe"
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/64d175d0f3c22013c653a1d26629c7499cf752db/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "mac": {
-            "hash": "27f08c64c371ac3295e79f133c7ccb246efa163d",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/335cb3023c631f65b91a3133c0c64c75dfb515c9/trace_processor_shell"
+            "hash": "fa3a16e144a1d924f505fbe78ecd163b81b63f39",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/bda2a707d51d84cdea4d381e39bcc1561c9cc95a/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "c0397e87456ad6c6a7aa0133e5b81c97adbab4ab",
             "remote_path": "perfetto_binaries/trace_processor_shell/mac_arm64/cefb3e0ec3a0580c996f801e854fe02963c03d5c/trace_processor_shell"
         },
         "linux": {
-            "hash": "7565b0e10f355e90bfdd261561f443fe4ea6d94f",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/585716d40f6593c3e04780d670e1e95fcce3052a/trace_processor_shell"
+            "hash": "e74792f0962a1dff41b9f8b9f7390d24c0e93c5c",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/bda2a707d51d84cdea4d381e39bcc1561c9cc95a/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/win/ShowGlobals/ShowGlobals.cc b/tools/win/ShowGlobals/ShowGlobals.cc
index cc5b991..464f6428b 100644
--- a/tools/win/ShowGlobals/ShowGlobals.cc
+++ b/tools/win/ShowGlobals/ShowGlobals.cc
@@ -4,7 +4,7 @@
 
 // This tool scans a PDB file and prints out information about 'interesting'
 // global variables. This includes duplicates and large globals. This is often
-// helpful inunderstanding code bloat or finding inefficient globals.
+// helpful in understanding code bloat or finding inefficient globals.
 //
 // Duplicate global variables often happen when constructs like this are placed
 // in a header file:
@@ -17,6 +17,20 @@
 // linker. This duplication can happen with float/double, structs and classes,
 // and arrays - any non-integral type.
 //
+// With C++ 17 these problems can often be fixed by adding an inline keyword:
+//
+//     const inline double sqrt_two = sqrt(2.0);
+//
+// constexpr would be even better in order to ensure that initializations are
+// not being done at runtime.
+//
+// Note that the linker will coalesce identical constant variables in some
+// cases, leaving multiple symbol entries pointing at a single global. This is
+// the global-variable version of code folding (/OPT:ICF). If the argument
+// --show_folded_constants is passed then these will be displayed. Otherwise
+// they will be silently suppressed as not being interesting because they aren't
+// actually wasting space.
+//
 // Global variables are not necessarily a problem but it is useful to understand
 // them, and monitoring their changes can be instructive.
 
@@ -37,11 +51,12 @@
 
 // Use this struct to record data about symbols for sorting and analysis.
 struct SymbolData {
-  SymbolData(ULONGLONG size, DWORD section, const wchar_t* name)
-      : size(size), section(section), name(name) {}
+  SymbolData(ULONGLONG size, DWORD section, DWORD offset, const wchar_t* name)
+      : size(size), section(section), offset(offset), name(name) {}
 
   ULONGLONG size;
   DWORD section;
+  DWORD offset;
   std::wstring name;
 };
 
@@ -70,20 +85,27 @@
 // Use this struct to store data about repeated globals, for later sorting.
 struct RepeatData {
   RepeatData(ULONGLONG repeat_count,
+             int folding_count,
              ULONGLONG bytes_wasted,
              const std::wstring& name)
-      : repeat_count(repeat_count), bytes_wasted(bytes_wasted), name(name) {}
+      : repeat_count(repeat_count),
+        bytes_wasted(bytes_wasted),
+        folding_count(folding_count),
+        name(name) {}
   bool operator<(const RepeatData& rhs) {
     return bytes_wasted < rhs.bytes_wasted;
   }
 
   ULONGLONG repeat_count;
   ULONGLONG bytes_wasted;
+  int folding_count;
   std::wstring name;
 };
 
-bool DumpInterestingGlobals(IDiaSymbol* global, const wchar_t* filename) {
-  wprintf(L"#Dups\tDupSize\t  Size\tSection\tSymbol name\tPDB name\n");
+bool DumpInterestingGlobals(IDiaSymbol* global,
+                            const wchar_t* filename,
+                            bool show_folded_constants) {
+  wprintf(L"#Dups\t#Folded\tDupSize\t  Size\tSection\tSymbol name\tPDB name\n");
 
   // How many bytes must be wasted on repeats before being listed.
   const int kWastageThreshold = 100;
@@ -133,9 +155,12 @@
     if (symbol->get_addressSection(&section) != S_OK)
       section = static_cast<DWORD>(-2);
 
+    DWORD offset = 0;
+    symbol->get_addressOffset(&offset);
+
     CComBSTR name;
     if (symbol->get_name(&name) == S_OK) {
-      symbols.push_back(SymbolData(size, section, name));
+      symbols.push_back(SymbolData(size, section, offset, name));
     }
   }
 
@@ -146,9 +171,13 @@
     auto pScan = p;
     // Scan the data looking for symbols that have the same name
     // and size.
+    int folding_count = 0;
     while (pScan != symbols.end() && p->size == pScan->size &&
-           StringCompare(p->name, pScan->name) == 0)
+           StringCompare(p->name, pScan->name) == 0) {
+      if (pScan->offset == p->offset && p->offset != 0)
+        ++folding_count;
       ++pScan;
+    }
 
     // Calculate how many times the symbol name/size appears in this PDB.
     size_t repeat_count = pScan - p;
@@ -156,9 +185,13 @@
       // Change the count from how many instances of this variable there are to
       // how many *excess* instances there are.
       --repeat_count;
-      ULONGLONG bytes_wasted = repeat_count * p->size;
+      --folding_count;
+      const size_t excess_count =
+          show_folded_constants ? repeat_count : repeat_count - folding_count;
+      const ULONGLONG bytes_wasted = excess_count * p->size;
       if (bytes_wasted > kWastageThreshold) {
-        repeats.push_back(RepeatData(repeat_count, bytes_wasted, p->name));
+        repeats.push_back(
+            RepeatData(repeat_count, folding_count, bytes_wasted, p->name));
       }
     }
 
@@ -170,10 +203,11 @@
   std::sort(repeats.begin(), repeats.end());
   std::reverse(repeats.begin(), repeats.end());
   for (const auto& repeat : repeats) {
-    // The empty field contain a zero so that Excel/sheets will more easily
+    // The empty fields contain a zero so that Excel/sheets will more easily
     // create the pivot tables that I want.
-    wprintf(L"%llu\t%llu\t%6u\t%u\t%s\t%s\n", repeat.repeat_count,
-            repeat.bytes_wasted, 0, 0, repeat.name.c_str(), filename);
+    wprintf(L"%llu\t%d\t%llu\t%6u\t%u\t%s\t%s\n", repeat.repeat_count,
+            repeat.folding_count, repeat.bytes_wasted, 0, 0,
+            repeat.name.c_str(), filename);
   }
   wprintf(L"\n");
 
@@ -228,12 +262,20 @@
 }
 
 int wmain(int argc, wchar_t* argv[]) {
-  if (argc < 2) {
-    wprintf(L"Usage: ShowGlobals file.pdb");
-    return -1;
+  bool show_folded_constants = false;
+
+  const wchar_t* filename = nullptr;
+  for (int i = 0; i < argc; ++i) {
+    if (wcscmp(argv[i], L"--show_folded_constants") == 0)
+      show_folded_constants = true;
+    else
+      filename = argv[i];
   }
 
-  const wchar_t* filename = argv[1];
+  if (!filename) {
+    wprintf(L"Usage: ShowGlobals file.pdb [--show_folded_constants]");
+    return -1;
+  }
 
   HRESULT hr = CoInitialize(NULL);
   if (FAILED(hr)) {
@@ -250,7 +292,7 @@
     if (!(Initialize(filename, source, session, global)))
       return -1;
 
-    DumpInterestingGlobals(global.Get(), filename);
+    DumpInterestingGlobals(global.Get(), filename, show_folded_constants);
   }
 
   CoUninitialize();
diff --git a/tools/win/ShowGlobals/ShowGlobals.vcxproj b/tools/win/ShowGlobals/ShowGlobals.vcxproj
index bd35e560..4c57a16 100644
--- a/tools/win/ShowGlobals/ShowGlobals.vcxproj
+++ b/tools/win/ShowGlobals/ShowGlobals.vcxproj
@@ -28,26 +28,26 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">

     <ConfigurationType>Application</ConfigurationType>

     <UseDebugLibraries>true</UseDebugLibraries>

-    <PlatformToolset>v142</PlatformToolset>

+    <PlatformToolset>v143</PlatformToolset>

     <CharacterSet>Unicode</CharacterSet>

   </PropertyGroup>

   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">

     <ConfigurationType>Application</ConfigurationType>

     <UseDebugLibraries>false</UseDebugLibraries>

-    <PlatformToolset>v142</PlatformToolset>

+    <PlatformToolset>v143</PlatformToolset>

     <WholeProgramOptimization>true</WholeProgramOptimization>

     <CharacterSet>Unicode</CharacterSet>

   </PropertyGroup>

   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">

     <ConfigurationType>Application</ConfigurationType>

     <UseDebugLibraries>true</UseDebugLibraries>

-    <PlatformToolset>v142</PlatformToolset>

+    <PlatformToolset>v143</PlatformToolset>

     <CharacterSet>Unicode</CharacterSet>

   </PropertyGroup>

   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">

     <ConfigurationType>Application</ConfigurationType>

     <UseDebugLibraries>false</UseDebugLibraries>

-    <PlatformToolset>v142</PlatformToolset>

+    <PlatformToolset>v143</PlatformToolset>

     <WholeProgramOptimization>true</WholeProgramOptimization>

     <CharacterSet>Unicode</CharacterSet>

   </PropertyGroup>

diff --git a/ui/accessibility/extensions/caretbrowsing/manifest.json b/ui/accessibility/extensions/caretbrowsing/manifest.json
index 04050275..065b3730 100644
--- a/ui/accessibility/extensions/caretbrowsing/manifest.json
+++ b/ui/accessibility/extensions/caretbrowsing/manifest.json
@@ -9,9 +9,7 @@
     "tabs"
   ],
   "background": {
-    "scripts": [
-      "background.js"
-    ]
+    "service_worker": "background.js"
   },
   "browser_action": {
     "default_icon": "caret_19.png",
diff --git a/ui/accessibility/extensions/colorenhancer/manifest.json b/ui/accessibility/extensions/colorenhancer/manifest.json
index 21f7e7d5..b711deb 100644
--- a/ui/accessibility/extensions/colorenhancer/manifest.json
+++ b/ui/accessibility/extensions/colorenhancer/manifest.json
@@ -10,14 +10,11 @@
   },
   "permissions": [
     "<all_urls>",
+    "storage",
     "tabs"
   ],
   "background": {
-    "scripts": [
-      "src/common.js",
-      "src/storage.js",
-      "src/background.js"
-    ]
+    "service_worker": "src/background.js"
   },
   "browser_action": {
     "default_icon": {
diff --git a/ui/accessibility/extensions/colorenhancer/src/background.js b/ui/accessibility/extensions/colorenhancer/src/background.js
index 6d32bc9..f458fe53 100644
--- a/ui/accessibility/extensions/colorenhancer/src/background.js
+++ b/ui/accessibility/extensions/colorenhancer/src/background.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+importScripts('./common.js', './storage.js');
+
 /**
  * Adds filter script and css to all existing tabs.
  *
@@ -31,7 +33,7 @@
  * Updates all existing tabs with config values.
  */
 function updateTabs() {
-  chrome.windows.getAll({'populate': true}, function(windows) {
+  chrome.windows.getAll({'populate': true}, async function(windows) {
     for (var i = 0; i < windows.length; i++) {
       var tabs = windows[i].tabs;
       for (var j = 0; j < tabs.length; j++) {
@@ -40,20 +42,38 @@
           continue;
         }
         var msg = {
-          'delta': getSiteDelta(siteFromUrl(url)),
-          'severity': getDefaultSeverity(),
-          'type': getDefaultType(),
-          'simulate': getDefaultSimulate(),
-          'enable': getDefaultEnable()
+          'delta': await getSiteDelta(siteFromUrl(url)),
+          'severity': await getDefaultSeverity(),
+          'type': await getDefaultType(),
+          'simulate': await getDefaultSimulate(),
+          'enable': await getDefaultEnable()
         };
         debugPrint('updateTabs: sending ' + JSON.stringify(msg) + ' to ' +
             siteFromUrl(url));
-        chrome.tabs.sendRequest(tabs[j].id, msg);
+        chrome.tabs.sendMessage(tabs[j].id, msg);
       }
     }
   });
 }
 
+async function onInitReceived(sender) {
+  var delta;
+  if (sender.tab) {
+    delta = await getSiteDelta(siteFromUrl(sender.tab.url));
+  } else {
+    delta = await getDefaultDelta();
+  }
+
+  var msg = {
+    'delta': delta,
+    'severity': await getDefaultSeverity(),
+    'type': await getDefaultType(),
+    'simulate': await getDefaultSimulate(),
+    'enable': await getDefaultEnable()
+  };
+  return msg;
+}
+
 /**
  * Initial extension loading.
  */
@@ -61,28 +81,22 @@
   injectContentScripts();
   updateTabs();
 
-  chrome.extension.onRequest.addListener(
-      function(request, sender, sendResponse) {
-        if (request['init']) {
-          var delta = getDefaultDelta();
-          if (sender.tab) {
-            delta = getSiteDelta(siteFromUrl(sender.tab.url));
-          }
-
-          var msg = {
-            'delta': delta,
-            'severity': getDefaultSeverity(),
-            'type': getDefaultType(),
-            'simulate': getDefaultSimulate(),
-            'enable': getDefaultEnable()
-          };
-          sendResponse(msg);
+  chrome.runtime.onMessage.addListener(
+      function(message, sender, sendResponse) {
+        if (message === 'init') {
+          onInitReceived(sender).then(sendResponse);
         }
       });
 
   //TODO(mustaq): Handle uninstall
 
-  document.addEventListener('storage', function(evt) {
+  chrome.storage.onChanged.addListener(function() {
     updateTabs();
-  }, false);
+  });
 })();
+
+chrome.runtime.onMessage.addListener(message => {
+  if (message === 'updateTabs') {
+    updateTabs();
+  }
+});
diff --git a/ui/accessibility/extensions/colorenhancer/src/common.js b/ui/accessibility/extensions/colorenhancer/src/common.js
index 1d6498f..db117d0 100644
--- a/ui/accessibility/extensions/colorenhancer/src/common.js
+++ b/ui/accessibility/extensions/colorenhancer/src/common.js
@@ -17,9 +17,7 @@
  * TODO(wnwen): Remove this, it's terrible.
  */
 function siteFromUrl(url) {
-  var a = document.createElement('a');
-  a.href = url;
-  return a.hostname;
+  return new URL(url).hostname;
 }
 
 
diff --git a/ui/accessibility/extensions/colorenhancer/src/cvd.js b/ui/accessibility/extensions/colorenhancer/src/cvd.js
index f740594..92ad2e71 100644
--- a/ui/accessibility/extensions/colorenhancer/src/cvd.js
+++ b/ui/accessibility/extensions/colorenhancer/src/cvd.js
@@ -404,47 +404,51 @@
 
 
   /**
-   * Process request from background page.
-   * @param {!object} request An object containing color filter parameters.
+   * Process a message from background page.
+   * @param {!object} message An object containing color filter parameters.
    */
-  function onExtensionMessage(request) {
-    debugPrint('onExtensionMessage: ' + JSON.stringify(request));
+  function onExtensionMessage(message) {
+    debugPrint('onExtensionMessage: ' + JSON.stringify(message));
     var changed = false;
 
-    if (request['type'] !== undefined) {
-      var type = request.type;
+    if (!message) {
+      return;
+    }
+
+    if (message['type'] !== undefined) {
+      var type = message.type;
       if (curType != type) {
         curType = type;
         changed = true;
       }
     }
 
-    if (request['severity'] !== undefined) {
-      var severity = request.severity;
+    if (message['severity'] !== undefined) {
+      var severity = message.severity;
       if (curSeverity != severity) {
         curSeverity = severity;
         changed = true;
       }
     }
 
-    if (request['delta'] !== undefined) {
-      var delta = request.delta;
+    if (message['delta'] !== undefined) {
+      var delta = message.delta;
       if (curDelta != delta) {
         curDelta = delta;
         changed = true;
       }
     }
 
-    if (request['simulate'] !== undefined) {
-      var simulate = request.simulate;
+    if (message['simulate'] !== undefined) {
+      var simulate = message.simulate;
       if (curSimulate != simulate) {
         curSimulate = simulate;
         changed = true;
       }
     }
 
-    if (request['enable'] !== undefined) {
-      var enable = request.enable;
+    if (message['enable'] !== undefined) {
+      var enable = message.enable;
       if (curEnable != enable) {
         curEnable = enable;
         changed = true;
@@ -470,8 +474,8 @@
    * values.
    */
   exports.initializeExtension = function () {
-    chrome.extension.onRequest.addListener(onExtensionMessage);
-    chrome.extension.sendRequest({'init': true}, onExtensionMessage);
+    chrome.runtime.onMessage.addListener(onExtensionMessage);
+    chrome.runtime.sendMessage("init", onExtensionMessage);
   };
 
   /**
diff --git a/ui/accessibility/extensions/colorenhancer/src/popup.js b/ui/accessibility/extensions/colorenhancer/src/popup.js
index d875706..50767a2b 100644
--- a/ui/accessibility/extensions/colorenhancer/src/popup.js
+++ b/ui/accessibility/extensions/colorenhancer/src/popup.js
@@ -209,8 +209,8 @@
    * Update the popup controls based on settings for this site or the default.
    * @return {boolean} True if settings are valid and update performed.
    */
-  function update() {
-    var type = getDefaultType();
+  async function update() {
+    var type = await getDefaultType();
     var validType = false;
     CVD_TYPES.forEach(function(cvdType) {
       if (cvdType == type) {
@@ -223,25 +223,25 @@
       return false;
 
     if (site) {
-      $('delta').value = getSiteDelta(site);
+      $('delta').value = await getSiteDelta(site);
     } else {
-      $('delta').value = getDefaultDelta();
+      $('delta').value = await getDefaultDelta();
     }
 
-    $('severity').value = getDefaultSeverity();
+    $('severity').value = await getDefaultSeverity();
 
     if (!$('setup-panel').classList.contains('collapsed'))
-      setCvdTypeSelection(getDefaultType());
-    $('enable').checked = getDefaultEnable();
+      setCvdTypeSelection(await getDefaultType());
+    $('enable').checked = await getDefaultEnable();
 
     debugPrint('update: ' +
         ' del=' + $('delta').value +
         ' sev=' + $('severity').value +
-        ' typ=' + getDefaultType() +
+        ' typ=' + await getDefaultType() +
         ' enb=' + $('enable').checked +
         ' for ' + site
     );
-    chrome.extension.getBackgroundPage().updateTabs();
+    chrome.runtime.sendMessage('updateTabs');
     return true;
   }
 
@@ -326,12 +326,12 @@
       elem.textContent = chrome.i18n.getMessage(msg);
     }
 
-    $('setup').onclick = function() {
+    $('setup').onclick = async function() {
       $('setup-panel').classList.remove('collapsed');
       // Store current settings in the event of a canceled setup.
       restoreSettings = {
-        type: getDefaultType(),
-        severity: getDefaultSeverity()
+        type: await getDefaultType(),
+        severity: await getDefaultSeverity()
       };
       // Initalize controls based on current settings.
       setCvdTypeSelection(restoreSettings.type);
diff --git a/ui/accessibility/extensions/colorenhancer/src/storage.js b/ui/accessibility/extensions/colorenhancer/src/storage.js
index e40dab6..23008d4 100644
--- a/ui/accessibility/extensions/colorenhancer/src/storage.js
+++ b/ui/accessibility/extensions/colorenhancer/src/storage.js
@@ -2,21 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(wnwen): Move to chrome.storage.local, wrap calls, add JsDocs.
-
-/**
- * Convert the string to boolean if possible.
- * @return {(boolean|string)} The Boolean value if possible, 'undefined'
- *     otherwise.
- */
-function stringToBoolean(str) {
-  return (str == 'true') ? true : (str == 'false') ? false : 'undefined';
-}
+// TODO(wnwen): Wrap calls, add JsDocs.
 
 function validBoolean(b) {
   return b == true || b == false;
 }
 
+function store_(key, val) {
+  const newVals = {};
+  newVals[key] = val;
+  chrome.storage.local.set(newVals);
+}
 
 // ======= Delta setting =======
 
@@ -31,13 +27,18 @@
 
 
 function getDefaultDelta() {
-  var delta = localStorage[LOCAL_STORAGE_TAG_DELTA];
-  if (validDelta(delta)) {
-    return delta;
-  }
-  delta = DEFAULT_DELTA;
-  localStorage[LOCAL_STORAGE_TAG_DELTA] = delta;
-  return delta;
+  return new Promise(resolve => {
+    chrome.storage.local.get([LOCAL_STORAGE_TAG_DELTA], (result) => {
+      let delta = result[LOCAL_STORAGE_TAG_DELTA];
+      if (validDelta(delta)) {
+        resolve(delta);
+        return;
+      }
+      delta = DEFAULT_DELTA;
+      store_(LOCAL_STORAGE_TAG_DELTA, delta);
+      resolve(delta);
+    });
+  });
 }
 
 
@@ -45,43 +46,50 @@
   if (!validDelta(delta)) {
     delta = DEFAULT_DELTA;
   }
-  localStorage[LOCAL_STORAGE_TAG_DELTA] = delta;
+  store_(LOCAL_STORAGE_TAG_DELTA, delta);
 }
 
 
 function getSiteDelta(site) {
-  var delta = getDefaultDelta();
-  try {
-    var siteDeltas = JSON.parse(localStorage[LOCAL_STORAGE_TAG_SITE_DELTA]);
-    delta = siteDeltas[site];
-    if (!validDelta(delta)) {
-      delta = getDefaultDelta();
-    }
-  } catch (e) {
-    delta = getDefaultDelta();
-  }
-  return delta;
+  return new Promise(resolve => {
+    chrome.storage.local.get([LOCAL_STORAGE_TAG_SITE_DELTA], (result) => {
+      let delta;
+      try {
+        var siteDeltas = result[LOCAL_STORAGE_TAG_SITE_DELTA] || {};
+        delta = siteDeltas[site];
+        if (!validDelta(delta)) {
+          getDefaultDelta().then(resolve);
+          return;
+        }
+      } catch (e) {
+        getDefaultDelta().then(resolve);
+        return;
+      }
+      resolve(delta);
+    });
+  });
 }
 
 
-function setSiteDelta(site, delta) {
+async function setSiteDelta(site, delta) {
   if (!validDelta(delta)) {
-    delta = getDefaultDelta();
+    delta = await getDefaultDelta();
   }
-  var siteDeltas = {};
-  try {
-    siteDeltas = JSON.parse(localStorage[LOCAL_STORAGE_TAG_SITE_DELTA]);
-  } catch (e) {
-    siteDeltas = {};
-  }
-  siteDeltas[site] = delta;
-  localStorage[LOCAL_STORAGE_TAG_SITE_DELTA] = JSON.stringify(siteDeltas);
+  chrome.storage.local.get([LOCAL_STORAGE_TAG_SITE_DELTA], (result) => {
+    var siteDeltas = {};
+    try {
+      siteDeltas = result[LOCAL_STORAGE_TAG_SITE_DELTA] || {};
+    } catch (e) {
+      siteDeltas = {};
+    }
+    siteDeltas[site] = delta;
+    store_(LOCAL_STORAGE_TAG_SITE_DELTA, siteDeltas);
+  });
 }
 
 
 function resetSiteDeltas() {
-  var siteDeltas = {};
-  localStorage[LOCAL_STORAGE_TAG_SITE_DELTA] = JSON.stringify(siteDeltas);
+  store_(LOCAL_STORAGE_TAG_SITE_DELTA, {});
 }
 
 
@@ -97,13 +105,18 @@
 
 
 function getDefaultSeverity() {
-  var severity = localStorage[LOCAL_STORAGE_TAG_SEVERITY];
-  if (validSeverity(severity)) {
-    return severity;
-  }
-  severity = DEFAULT_SEVERITY;
-  localStorage[LOCAL_STORAGE_TAG_SEVERITY] = severity;
-  return severity;
+  return new Promise(resolve => {
+    chrome.storage.local.get([LOCAL_STORAGE_TAG_SEVERITY], (result) => {
+      var severity = result[LOCAL_STORAGE_TAG_SEVERITY];
+      if (validSeverity(severity)) {
+        resolve(severity);
+        return;
+      }
+      severity = DEFAULT_SEVERITY;
+      store_(LOCAL_STORAGE_TAG_SEVERITY, severity);
+      resolve(severity);
+    });
+  });
 }
 
 
@@ -111,7 +124,7 @@
   if (!validSeverity(severity)) {
     severity = DEFAULT_SEVERITY;
   }
-  localStorage[LOCAL_STORAGE_TAG_SEVERITY] = severity;
+  store_(LOCAL_STORAGE_TAG_SEVERITY, severity);
 }
 
 
@@ -129,10 +142,17 @@
 
 
 function getDefaultType() {
-  var type = localStorage[LOCAL_STORAGE_TAG_TYPE];
-  if (validType(type)) {
-    return type;
-  }
+  return new Promise(resolve => {
+    chrome.storage.local.get([LOCAL_STORAGE_TAG_TYPE], (result) => {
+      var type = result[LOCAL_STORAGE_TAG_TYPE];
+      if (validType(type)) {
+        resolve(type);
+      } else {
+        // TODO(anastasi): add appropriate error handling
+        resolve();
+      }
+    });
+  });
 }
 
 
@@ -140,7 +160,7 @@
   if (!validType(type)) {
     type = INVALID_TYPE_PLACEHOLDER;
   }
-  localStorage[LOCAL_STORAGE_TAG_TYPE] = type;
+  store_(LOCAL_STORAGE_TAG_TYPE, type);
 }
 
 
@@ -151,16 +171,19 @@
 
 
 function getDefaultSimulate() {
-  var simulate = localStorage[LOCAL_STORAGE_TAG_SIMULATE];
+  return new Promise(resolve => {
+    chrome.storage.local.get([LOCAL_STORAGE_TAG_SIMULATE], (result) => {
+      var simulate = result[LOCAL_STORAGE_TAG_SIMULATE];
 
-  simulate = stringToBoolean(simulate);
-
-  if (validBoolean(simulate)) {
-    return simulate;
-  }
-  simulate = DEFAULT_SIMULATE;
-  localStorage[LOCAL_STORAGE_TAG_SIMULATE] = simulate;
-  return simulate;
+      if (validBoolean(simulate)) {
+        resolve(simulate);
+        return;
+      }
+      simulate = DEFAULT_SIMULATE;
+      store_(LOCAL_STORAGE_TAG_SIMULATE, simulate);
+      resolve(simulate);
+    });
+  });
 }
 
 
@@ -168,7 +191,7 @@
   if (!validBoolean(simulate)) {
     simulate = DEFAULT_SIMULATE;
   }
-  localStorage[LOCAL_STORAGE_TAG_SIMULATE] = simulate;
+  store_(LOCAL_STORAGE_TAG_SIMULATE, simulate);
 }
 
 
@@ -178,22 +201,20 @@
 /** @const {string} */ var LOCAL_STORAGE_TAG_ENABLE = 'cvd_enable';
 
 
-function validEnable(enable) {
-  return enable == true || enable == false;
-}
-
-
 function getDefaultEnable() {
-  var enable = localStorage[LOCAL_STORAGE_TAG_ENABLE];
+  return new Promise(resolve => {
+    chrome.storage.local.get([LOCAL_STORAGE_TAG_ENABLE], (result) => {
+      var enable = result[LOCAL_STORAGE_TAG_ENABLE];
 
-  enable = stringToBoolean(enable);
-
-  if (validBoolean(enable)) {
-    return enable;
-  }
-  enable = DEFAULT_ENABLE;
-  localStorage[LOCAL_STORAGE_TAG_ENABLE] = enable;
-  return enable;
+      if (validBoolean(enable)) {
+        resolve(enable);
+        return;
+      }
+      enable = DEFAULT_ENABLE;
+      store_(LOCAL_STORAGE_TAG_ENABLE, enable);
+      resolve(enable);
+    });
+  });
 }
 
 
@@ -201,5 +222,5 @@
   if (!validBoolean(enable)) {
     enable = DEFAULT_ENABLE;
   }
-  localStorage[LOCAL_STORAGE_TAG_ENABLE] = enable;
+  store_(LOCAL_STORAGE_TAG_ENABLE, enable);
 }
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index bf2d893..3150a657 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -6849,11 +6849,10 @@
       return UIA_TextControlTypeId;
 
     case ax::mojom::Role::kAlertDialog:
-      // Our MSAA implementation suggests the use of
-      // |UIA_TextControlTypeId|, not |UIA_PaneControlTypeId| because some
-      // Windows screen readers are not compatible with
-      // |ax::mojom::Role::kAlertDialog| yet.
-      return UIA_TextControlTypeId;
+      // In UIA, all dialogs (including alert dialogs) should return the
+      // UIA_WindowControlTypeId for ATs to be able to "trap" the AT's focus
+      // withing that dialog element.
+      return UIA_WindowControlTypeId;
 
     case ax::mojom::Role::kComment:
     case ax::mojom::Role::kSuggestion:
@@ -6946,7 +6945,7 @@
       return UIA_GroupControlTypeId;
 
     case ax::mojom::Role::kDialog:
-      return UIA_PaneControlTypeId;
+      return UIA_WindowControlTypeId;
 
     case ax::mojom::Role::kDisclosureTriangle:
       return UIA_ButtonControlTypeId;
diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index 7e0b50d..af890c72 100644
--- a/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -5635,53 +5635,56 @@
   root.role = ax::mojom::Role::kRootWebArea;
 
   AXNodeData child1;
-  AXNodeID child1_id = 2;
-  child1.id = child1_id;
+  child1.id = 2;
   child1.role = ax::mojom::Role::kTable;
-  root.child_ids.push_back(child1_id);
+  root.child_ids.push_back(child1.id);
 
   AXNodeData child2;
-  AXNodeID child2_id = 3;
-  child2.id = child2_id;
+  child2.id = 3;
   child2.role = ax::mojom::Role::kLayoutTable;
-  root.child_ids.push_back(child2_id);
+  root.child_ids.push_back(child2.id);
 
   AXNodeData child3;
-  AXNodeID child3_id = 4;
-  child3.id = child3_id;
+  child3.id = 4;
   child3.role = ax::mojom::Role::kTextField;
   child3.AddState(ax::mojom::State::kEditable);
-  root.child_ids.push_back(child3_id);
+  root.child_ids.push_back(child3.id);
 
   AXNodeData child4;
-  AXNodeID child4_id = 5;
-  child4.id = child4_id;
+  child4.id = 5;
   child4.role = ax::mojom::Role::kSearchBox;
-  root.child_ids.push_back(child4_id);
+  root.child_ids.push_back(child4.id);
 
   AXNodeData child5;
-  AXNodeID child5_id = 6;
-  child5.id = child5_id;
+  child5.id = 6;
   child5.role = ax::mojom::Role::kTitleBar;
-  root.child_ids.push_back(child5_id);
+  root.child_ids.push_back(child5.id);
 
-  Init(root, child1, child2, child3, child4, child5);
+  AXNodeData child6;
+  child6.id = 7;
+  child6.role = ax::mojom::Role::kDialog;
+  root.child_ids.push_back(child6.id);
+
+  Init(root, child1, child2, child3, child4, child5, child6);
 
   EXPECT_UIA_INT_EQ(
-      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child1_id),
+      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child1.id),
       UIA_ControlTypePropertyId, int{UIA_TableControlTypeId});
   EXPECT_UIA_INT_EQ(
-      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child2_id),
+      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child2.id),
       UIA_ControlTypePropertyId, int{UIA_TableControlTypeId});
   EXPECT_UIA_INT_EQ(
-      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child3_id),
+      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child3.id),
       UIA_ControlTypePropertyId, int{UIA_EditControlTypeId});
   EXPECT_UIA_INT_EQ(
-      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child4_id),
+      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child4.id),
       UIA_ControlTypePropertyId, int{UIA_EditControlTypeId});
   EXPECT_UIA_INT_EQ(
-      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child5_id),
+      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child5.id),
       UIA_ControlTypePropertyId, int{UIA_TitleBarControlTypeId});
+  EXPECT_UIA_INT_EQ(
+      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child6.id),
+      UIA_ControlTypePropertyId, int{UIA_WindowControlTypeId});
 }
 
 TEST_F(AXPlatformNodeWinTest, UIALandmarkType) {
diff --git a/ui/display/display.cc b/ui/display/display.cc
index 162cbc63..8858e35 100644
--- a/ui/display/display.cc
+++ b/ui/display/display.cc
@@ -230,7 +230,7 @@
   // using it. Using a not supported profile can result in fatal errors in the
   // GPU process.
   auto color_space = gfx::ColorSpace::CreateSRGB();
-#if !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_ANDROID)
   if (HasForceDisplayColorProfile())
     color_space = GetForcedDisplayColorProfile();
 #endif
@@ -307,7 +307,7 @@
                                 const gfx::Rect& bounds_in_pixel) {
   gfx::Insets insets = bounds_.InsetsFrom(work_area_);
   if (!HasForceDeviceScaleFactor()) {
-#if defined(OS_APPLE)
+#if BUILDFLAG(IS_APPLE)
     // Unless an explicit scale factor was provided for testing, ensure the
     // scale is integral.
     device_scale_factor = static_cast<int>(device_scale_factor);
diff --git a/ui/display/display_util.cc b/ui/display/display_util.cc
index 3a32f1c1..9c50edd5 100644
--- a/ui/display/display_util.cc
+++ b/ui/display/display_util.cc
@@ -41,7 +41,7 @@
     screen_info->orientation_angle = 90;
 #endif
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(IS_ANDROID)
   screen_info->orientation_type = GetOrientationTypeForMobile(display);
 #else
   screen_info->orientation_type = GetOrientationTypeForDesktop(display);
diff --git a/ui/display/screen.cc b/ui/display/screen.cc
index 475ac33b..eef97c7e 100644
--- a/ui/display/screen.cc
+++ b/ui/display/screen.cc
@@ -30,7 +30,7 @@
 
 // static
 Screen* Screen::GetScreen() {
-#if defined(OS_APPLE)
+#if BUILDFLAG(IS_APPLE)
   // TODO(scottmg): https://crbug.com/558054
   if (!g_screen)
     g_screen = CreateNativeScreen();
diff --git a/ui/file_manager/file_manager/background/js/BUILD.gn b/ui/file_manager/file_manager/background/js/BUILD.gn
index 74b371b2..0cce8ad 100644
--- a/ui/file_manager/file_manager/background/js/BUILD.gn
+++ b/ui/file_manager/file_manager/background/js/BUILD.gn
@@ -467,6 +467,7 @@
 js_library("launcher") {
   deps = [
     ":app_window_wrapper",
+    "//ui/file_manager/file_manager/common/js:api",
     "//ui/file_manager/file_manager/common/js:files_app_state",
     "//ui/file_manager/file_manager/common/js:util",
   ]
diff --git a/ui/file_manager/file_manager/background/js/launcher.js b/ui/file_manager/file_manager/background/js/launcher.js
index c9dea86d..8c3921726 100644
--- a/ui/file_manager/file_manager/background/js/launcher.js
+++ b/ui/file_manager/file_manager/background/js/launcher.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {getFrameColor} from '../../common/js/api.js';
 import {FilesAppState} from '../../common/js/files_app_state.js';
 import {util} from '../../common/js/util.js';
 
@@ -158,8 +159,11 @@
   nextFileManagerWindowID = Math.max(nextFileManagerWindowID, id + 1);
   const appId = FILES_ID_PREFIX + id;
 
-  const appWindow = new AppWindowWrapper(
-      'main.html', appId, FILE_MANAGER_WINDOW_CREATE_OPTIONS);
+  const windowCreateOptions = FILE_MANAGER_WINDOW_CREATE_OPTIONS;
+  windowCreateOptions.frame.color = await getFrameColor();
+
+  const appWindow =
+      new AppWindowWrapper('main.html', appId, windowCreateOptions);
 
   await appWindow.launch(appState || {}, false);
   if (!appWindow.rawAppWindow) {
diff --git a/ui/file_manager/file_manager/common/js/api.js b/ui/file_manager/file_manager/common/js/api.js
index fd1f950..88b8c518 100644
--- a/ui/file_manager/file_manager/common/js/api.js
+++ b/ui/file_manager/file_manager/common/js/api.js
@@ -130,3 +130,16 @@
   const getEntry = isFile ? getFile : getDirectory;
   return getEntry(directory, filename, options);
 }
+
+/**
+ * Returns the color to be used by frames of each foreground window.
+ * @returns {Promise<!string>}
+ */
+export async function getFrameColor() {
+  try {
+    return await promisify(chrome.fileManagerPrivate.getFrameColor);
+  } catch (e) {
+    console.error('Failed to get frame color.', e);
+    return '#ffffff';
+  }
+}
diff --git a/ui/latency/latency_info.h b/ui/latency/latency_info.h
index 8d15fd8..6097d139 100644
--- a/ui/latency/latency_info.h
+++ b/ui/latency/latency_info.h
@@ -13,14 +13,14 @@
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.pbzero.h"
 
-#if !defined(OS_IOS)
+#if !BUILDFLAG(IS_IOS)
 #include "ipc/ipc_param_traits.h"  // nogncheck
 #include "mojo/public/cpp/bindings/struct_traits.h"  // nogncheck
 #endif
 
 namespace ui {
 
-#if !defined(OS_IOS)
+#if !BUILDFLAG(IS_IOS)
 namespace mojom {
 class LatencyInfoDataView;
 }
@@ -199,7 +199,7 @@
   // gesture_scroll_id_.
   int64_t touch_trace_id_ = 0;
 
-#if !defined(OS_IOS)
+#if !BUILDFLAG(IS_IOS)
   friend struct IPC::ParamTraits<ui::LatencyInfo>;
   friend struct mojo::StructTraits<ui::mojom::LatencyInfoDataView,
                                    ui::LatencyInfo>;
diff --git a/ui/platform_window/platform_window_delegate.h b/ui/platform_window/platform_window_delegate.h
index 931c605..115d74b 100644
--- a/ui/platform_window/platform_window_delegate.h
+++ b/ui/platform_window/platform_window_delegate.h
@@ -12,9 +12,9 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
 
-#if defined(OS_FUCHSIA)
+#if BUILDFLAG(IS_FUCHSIA)
 #include "ui/gfx/geometry/insets.h"
-#endif  // defined(OS_FUCHSIA)
+#endif  // BUILDFLAG(IS_FUCHSIA)
 
 namespace gfx {
 class Rect;
@@ -52,7 +52,7 @@
     // The dimensions of the window, in physical window coordinates.
     gfx::Rect bounds;
 
-#if defined(OS_FUCHSIA)
+#if BUILDFLAG(IS_FUCHSIA)
     // The widths of border regions which are obscured by overlapping
     // platform UI elements like onscreen keyboards.
     //
@@ -67,7 +67,7 @@
     // |    onscreen keyboard   |   |  overlap    |
     // +------------------------+  ---           ---
     gfx::Insets system_ui_overlap;
-#endif  // defined(OS_FUCHSIA)
+#endif  // BUILDFLAG(IS_FUCHSIA)
   };
 
   PlatformWindowDelegate();
diff --git a/ui/platform_window/platform_window_init_properties.h b/ui/platform_window/platform_window_init_properties.h
index fbdfb84..b78a8d67 100644
--- a/ui/platform_window/platform_window_init_properties.h
+++ b/ui/platform_window/platform_window_init_properties.h
@@ -14,7 +14,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
 
-#if defined(OS_FUCHSIA)
+#if BUILDFLAG(IS_FUCHSIA)
 #include <fuchsia/ui/composition/cpp/fidl.h>
 #include <fuchsia/ui/views/cpp/fidl.h>
 #include <lib/ui/scenic/cpp/view_ref_pair.h>
@@ -49,11 +49,11 @@
 
 class WorkspaceExtensionDelegate;
 
-#if defined(OS_FUCHSIA)
+#if BUILDFLAG(IS_FUCHSIA)
 class ScenicWindowDelegate;
 #endif
 
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 class X11ExtensionDelegate;
 #endif
 
@@ -82,7 +82,7 @@
   // Widget::InitProperties::WindowOpacity.
   PlatformWindowOpacity opacity = PlatformWindowOpacity::kOpaqueWindow;
 
-#if defined(OS_FUCHSIA)
+#if BUILDFLAG(IS_FUCHSIA)
   // Scenic 3D API uses `view_token` for links, whereas Flatland
   // API uses `view_creation_token`. Therefore, at most one of these fields must
   // be set. If `allow_null_view_token_for_test` is true, they may both be
@@ -112,7 +112,7 @@
 
   PlatformWindowShadowType shadow_type = PlatformWindowShadowType::kDefault;
 
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   bool prefer_dark_theme = false;
   gfx::ImageSkia* icon = nullptr;
   absl::optional<int> background_color;
diff --git a/ui/shell_dialogs/run_all_unittests.cc b/ui/shell_dialogs/run_all_unittests.cc
index b06fbe1c..7471c14 100644
--- a/ui/shell_dialogs/run_all_unittests.cc
+++ b/ui/shell_dialogs/run_all_unittests.cc
@@ -11,7 +11,7 @@
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/ui_base_paths.h"
 
-#if defined(OS_APPLE)
+#if BUILDFLAG(IS_APPLE)
 #include "base/test/mock_chrome_application_mac.h"
 #endif
 
@@ -36,7 +36,7 @@
 void ShellDialogsTestSuite::Initialize() {
   base::TestSuite::Initialize();
 
-#if defined(OS_APPLE)
+#if BUILDFLAG(IS_APPLE)
   mock_cr_app::RegisterMockCrApp();
 #endif
 
diff --git a/ui/snapshot/snapshot_aura.cc b/ui/snapshot/snapshot_aura.cc
index 2fae89d..d205480 100644
--- a/ui/snapshot/snapshot_aura.cc
+++ b/ui/snapshot/snapshot_aura.cc
@@ -102,7 +102,7 @@
                      std::move(callback)));
 }
 
-#if !defined(OS_WIN)
+#if !BUILDFLAG(IS_WIN)
 bool GrabWindowSnapshot(gfx::NativeWindow window,
                         const gfx::Rect& snapshot_bounds,
                         gfx::Image* image) {
diff --git a/ui/snapshot/snapshot_aura_unittest.cc b/ui/snapshot/snapshot_aura_unittest.cc
index ee00cf984..999e4fa 100644
--- a/ui/snapshot/snapshot_aura_unittest.cc
+++ b/ui/snapshot/snapshot_aura_unittest.cc
@@ -196,24 +196,25 @@
 
 INSTANTIATE_TEST_SUITE_P(All, SnapshotAuraTest, ::testing::Bool());
 
-#if defined(OS_WIN) && !defined(NDEBUG)
+#if BUILDFLAG(IS_WIN) && !defined(NDEBUG)
 // https://crbug.com/852512
 #define MAYBE_FullScreenWindow DISABLED_FullScreenWindow
-#elif defined(OS_LINUX)
+#elif BUILDFLAG(IS_LINUX)
 // https://crbug.com/1143031
 #define MAYBE_FullScreenWindow DISABLED_FullScreenWindow
 #else
 #define MAYBE_FullScreenWindow FullScreenWindow
 #endif
 TEST_P(SnapshotAuraTest, MAYBE_FullScreenWindow) {
-#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_FUCHSIA)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
   // TODO(https://crbug.com/1143031): Fix this test to run in < action_timeout()
   // on the Linux Debug & TSAN bots.
   const base::test::ScopedRunLoopTimeout increased_run_timeout(
       FROM_HERE, TestTimeouts::action_max_timeout());
-#endif  // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_FUCHSIA)
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
+        // BUILDFLAG(IS_FUCHSIA)
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // TODO(https://crbug.com/850556): Make work on Win10.
   base::win::Version version = base::win::GetVersion();
   if (version >= base::win::Version::WIN10)
@@ -229,7 +230,7 @@
 }
 
 TEST_P(SnapshotAuraTest, PartialBounds) {
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // TODO(https://crbug.com/850556): Make work on Win10.
   base::win::Version version = base::win::GetVersion();
   if (version >= base::win::Version::WIN10)
@@ -245,7 +246,7 @@
 }
 
 TEST_P(SnapshotAuraTest, Rotated) {
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // TODO(https://crbug.com/850556): Make work on Win10.
   base::win::Version version = base::win::GetVersion();
   if (version >= base::win::Version::WIN10)
@@ -263,7 +264,7 @@
 }
 
 TEST_P(SnapshotAuraTest, UIScale) {
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // TODO(https://crbug.com/850556): Make work on Win10.
   base::win::Version version = base::win::GetVersion();
   if (version >= base::win::Version::WIN10)
@@ -287,7 +288,7 @@
 }
 
 TEST_P(SnapshotAuraTest, DeviceScaleFactor) {
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // TODO(https://crbug.com/850556): Make work on Win10.
   base::win::Version version = base::win::GetVersion();
   if (version >= base::win::Version::WIN10)
@@ -310,7 +311,7 @@
 }
 
 TEST_P(SnapshotAuraTest, RotateAndUIScale) {
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // TODO(https://crbug.com/850556): Make work on Win10.
   base::win::Version version = base::win::GetVersion();
   if (version >= base::win::Version::WIN10)
@@ -335,7 +336,7 @@
 }
 
 TEST_P(SnapshotAuraTest, RotateAndUIScaleAndScaleFactor) {
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // TODO(https://crbug.com/850556): Make work on Win10.
   base::win::Version version = base::win::GetVersion();
   if (version >= base::win::Version::WIN10)
diff --git a/ui/surface/transport_dib.cc b/ui/surface/transport_dib.cc
index c581abc7..0cc3614 100644
--- a/ui/surface/transport_dib.cc
+++ b/ui/surface/transport_dib.cc
@@ -50,7 +50,7 @@
   if (!base::CheckMul(h, base::CheckMul(w, bpp)).AssignIfValid(&canvas_size))
     return nullptr;
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // This DIB already mapped the file into this process, but PlatformCanvas
   // will map it again.
   DCHECK(!memory()) << "Mapped file twice in the same process.";
diff --git a/ui/views_content_client/views_content_client.cc b/ui/views_content_client/views_content_client.cc
index 6b39f014..8242db0 100644
--- a/ui/views_content_client/views_content_client.cc
+++ b/ui/views_content_client/views_content_client.cc
@@ -12,7 +12,7 @@
 
 namespace ui {
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 ViewsContentClient::ViewsContentClient(
     HINSTANCE instance, sandbox::SandboxInterfaceInfo* sandbox_info)
     : instance_(instance), sandbox_info_(sandbox_info) {
@@ -30,7 +30,7 @@
   ViewsContentMainDelegate delegate(this);
   content::ContentMainParams params(&delegate);
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   params.instance = instance_;
   params.sandbox_info = sandbox_info_;
 #else
diff --git a/ui/views_content_client/views_content_client.h b/ui/views_content_client/views_content_client.h
index c15d54c..37dae9a 100644
--- a/ui/views_content_client/views_content_client.h
+++ b/ui/views_content_client/views_content_client.h
@@ -32,7 +32,7 @@
 //   // Create desired windows and views here. Runs on the UI thread.
 // }
 //
-// #if defined(OS_WIN)
+// #if BUILDFLAG(IS_WIN)
 // int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t*, int) {
 //   sandbox::SandboxInterfaceInfo sandbox_info = {nullptr};
 //   content::InitializeSandboxInfo(&sandbox_info);
@@ -52,7 +52,7 @@
       base::OnceCallback<void(content::BrowserContext* browser_context,
                               gfx::NativeWindow window_context)>;
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   ViewsContentClient(HINSTANCE instance,
                      sandbox::SandboxInterfaceInfo* sandbox_info);
 #else
@@ -95,7 +95,7 @@
   base::OnceClosure& quit_closure() { return quit_closure_; }
 
  private:
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   HINSTANCE instance_;
   raw_ptr<sandbox::SandboxInterfaceInfo> sandbox_info_;
 #else
diff --git a/ui/views_content_client/views_content_client_main_parts.cc b/ui/views_content_client/views_content_client_main_parts.cc
index 0627fe1..51196d6f 100644
--- a/ui/views_content_client/views_content_client_main_parts.cc
+++ b/ui/views_content_client/views_content_client_main_parts.cc
@@ -24,7 +24,7 @@
 ViewsContentClientMainParts::~ViewsContentClientMainParts() {
 }
 
-#if !defined(OS_APPLE)
+#if !BUILDFLAG(IS_APPLE)
 void ViewsContentClientMainParts::PreBrowserMain() {}
 #endif
 
diff --git a/ui/views_content_client/views_content_client_main_parts.h b/ui/views_content_client/views_content_client_main_parts.h
index 64d71a5e..1f4568c 100644
--- a/ui/views_content_client/views_content_client_main_parts.h
+++ b/ui/views_content_client/views_content_client_main_parts.h
@@ -59,7 +59,7 @@
   ViewsContentClientMainParts(content::MainFunctionParams content_params,
                               ViewsContentClient* views_content_client);
 
-#if defined(OS_APPLE)
+#if BUILDFLAG(IS_APPLE)
   views::TestViewsDelegate* views_delegate() { return views_delegate_.get(); }
 #endif
 
diff --git a/ui/views_content_client/views_content_main_delegate.cc b/ui/views_content_client/views_content_main_delegate.cc
index cc87bc2..e891e1a 100644
--- a/ui/views_content_client/views_content_main_delegate.cc
+++ b/ui/views_content_client/views_content_main_delegate.cc
@@ -19,14 +19,14 @@
 #include "ui/views_content_client/views_content_client.h"
 #include "ui/views_content_client/views_content_client_main_parts.h"
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 #include "base/logging_win.h"
 #endif
 
 namespace ui {
 namespace {
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 // {83FAC8EE-7A0E-4dbb-A3F6-6F500D7CAB1A}
 const GUID kViewsContentClientProviderName =
     { 0x83fac8ee, 0x7a0e, 0x4dbb,
@@ -54,7 +54,7 @@
       logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
   bool success = logging::InitLogging(settings);
   CHECK(success);
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   logging::LogEventProvider::Initialize(kViewsContentClientProviderName);
 #endif
 
diff --git a/ui/wm/core/compound_event_filter.cc b/ui/wm/core/compound_event_filter.cc
index 2ee6865..6fbaf591 100644
--- a/ui/wm/core/compound_event_filter.cc
+++ b/ui/wm/core/compound_event_filter.cc
@@ -27,7 +27,7 @@
 // Returns true if the cursor should be hidden on touch events.
 // TODO(tdanderson|rsadam): Move this function into CursorClient.
 bool ShouldHideCursorOnTouch(const ui::TouchEvent& event) {
-#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
   return true;
 #else
   // Linux Aura does not hide the cursor on touch by default.
diff --git a/ui/wm/core/compound_event_filter_unittest.cc b/ui/wm/core/compound_event_filter_unittest.cc
index 8adb8092..54390d5 100644
--- a/ui/wm/core/compound_event_filter_unittest.cc
+++ b/ui/wm/core/compound_event_filter_unittest.cc
@@ -21,11 +21,11 @@
 
 namespace {
 
-#if BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_WIN)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_WIN)
 base::TimeTicks GetTime() {
   return ui::EventTimeForNow();
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_WIN)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_WIN)
 }
 
 namespace wm {
@@ -112,7 +112,7 @@
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_WIN)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_WIN)
 // Touch visually hides the cursor on ChromeOS and Windows.
 TEST_F(CompoundEventFilterTest, TouchHidesCursor) {
   std::unique_ptr<CompoundEventFilter> compound_filter(new CompoundEventFilter);
@@ -167,7 +167,7 @@
   EXPECT_FALSE(cursor_client.IsCursorVisible());
   aura::Env::GetInstance()->RemovePreTargetHandler(compound_filter.get());
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_WIN)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_WIN)
 
 // Tests that if an event filter consumes a gesture, then it doesn't focus the
 // window.
@@ -224,7 +224,7 @@
   aura::Env::GetInstance()->RemovePreTargetHandler(compound_filter.get());
 }
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 // Windows synthesizes mouse messages for touch events. We should not be
 // showing the cursor when we receive such messages.
 TEST_F(CompoundEventFilterTest, DontShowCursorOnMouseMovesFromTouch) {
diff --git a/weblayer/renderer/content_renderer_client_impl.cc b/weblayer/renderer/content_renderer_client_impl.cc
index 76ebad44..775b30d 100644
--- a/weblayer/renderer/content_renderer_client_impl.cc
+++ b/weblayer/renderer/content_renderer_client_impl.cc
@@ -194,6 +194,8 @@
     content::RenderFrame* render_frame,
     const blink::WebURLError& error,
     const std::string& http_method,
+    content::mojom::AlternativeErrorPageOverrideInfoPtr
+        alternative_error_page_info,
     std::string* error_html) {
   auto* error_page_helper = ErrorPageHelper::GetForFrame(render_frame);
   if (error_page_helper)
diff --git a/weblayer/renderer/content_renderer_client_impl.h b/weblayer/renderer/content_renderer_client_impl.h
index 2fbad184..805e228 100644
--- a/weblayer/renderer/content_renderer_client_impl.h
+++ b/weblayer/renderer/content_renderer_client_impl.h
@@ -6,6 +6,7 @@
 #define WEBLAYER_RENDERER_CONTENT_RENDERER_CLIENT_IMPL_H_
 
 #include "build/build_config.h"
+#include "content/public/common/alternative_error_page_override_info.mojom.h"
 #include "content/public/renderer/content_renderer_client.h"
 #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
 
@@ -41,6 +42,8 @@
   void PrepareErrorPage(content::RenderFrame* render_frame,
                         const blink::WebURLError& error,
                         const std::string& http_method,
+                        content::mojom::AlternativeErrorPageOverrideInfoPtr
+                            alternative_error_page_info,
                         std::string* error_html) override;
   std::unique_ptr<blink::URLLoaderThrottleProvider>
   CreateURLLoaderThrottleProvider(